You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas绘制波浪动画时clearRect误清除日落背景问题

问题根源

Canvas是逐像素覆盖的绘制模型,现有逻辑存在核心冲突:

  • 日落天空背景只在代码初始化阶段绘制了一次
  • 动画循环开头直接调用ctx.clearRect(0, 0, cw, ch)把整个画布的所有像素擦除为透明,初始化时绘制的背景自然会被一并清空
  • 如果缩小clearRect的清除范围只覆盖波浪所在区域,波浪运动时超出擦除边界的部分会留下残影,无法完全清理上一帧的绘制内容
解决方法

单Canvas场景下最简便的实现方式是:删掉全画布clear的逻辑,每帧绘制波浪之前先重绘一遍天空背景,用背景直接覆盖上一帧的残留内容,既不会丢失背景,也不会出现波浪残影。
渐变对象只需要在初始化阶段创建一次即可,不需要每帧重复生成,不会造成额外性能开销。

修改后的核心代码如下:

const c = document.getElementById("screen2");
const ctx = c.getContext("2d");
let cw = c.width = window.innerWidth;
let ch = c.height = window.innerHeight;
const cx = cw / 2, cy = ch / 2;
const w = window.innerWidth;
const amplitude = 100;
const frequency = .01;
let phi = 0;
let frames = 0;

// 初始化渐变
const gradientSky = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientSky.addColorStop(0, "#C1274E");
gradientSky.addColorStop(0.25, "#C344B7");
gradientSky.addColorStop(0.5, "#B2244A");
gradientSky.addColorStop(0.75, "#B2244A");
gradientSky.addColorStop(1, "#B2244A");

const gradientA = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientA.addColorStop(0, "#7a88d9");
gradientA.addColorStop(1, "#5a6bd0");

const gradientB = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientB.addColorStop(0, "#646593");
gradientB.addColorStop(1, "#0f2460");

ctx.lineWidth = 4;
let step = 0;

function drawWaves() {
  frames++;
  phi = frames / 88;
  // 移除原clearRect逻辑,每帧先绘制天空背景覆盖上一帧内容
  ctx.fillStyle = gradientSky;
  ctx.fillRect(0, 0, cw, ch);

  // 绘制第一层波浪
  ctx.beginPath();
  ctx.moveTo(0, ch);
  for (let x = 0; x < w; x++) {
    const y = Math.sin(x * frequency + phi) * amplitude / 2 + amplitude / 2;
    ctx.lineTo(x, y + ch - 390 + Math.sin(step / 2) * 20);
  }
  ctx.lineTo(w, ch);
  ctx.lineTo(0, ch);
  ctx.fillStyle = gradientA;
  ctx.fill();

  frames++;
  phi = frames / 60;
  // 绘制第二层波浪
  ctx.beginPath();
  ctx.moveTo(0, ch);
  for (let x = 0; x < w; x++) {
    const y = Math.sin(x * frequency + phi) * amplitude / 4 + amplitude / 4;
    ctx.lineTo(x, y + ch - 380 + Math.sin(step) * 20);
  }
  ctx.lineTo(w, ch);
  ctx.lineTo(0, ch);
  ctx.fillStyle = gradientB;
  ctx.fill();

  step += 0.02;
  window.requestAnimationFrame(drawWaves);
}

window.requestAnimationFrame(drawWaves);

// 可选:监听窗口大小变化,同步更新画布尺寸避免拉伸
window.addEventListener('resize', () => {
  cw = c.width = window.innerWidth;
  ch = c.height = window.innerHeight;
  // 尺寸变化后需要重新生成渐变,避免渐变比例错位
})
可选性能优化方案

如果后续需要添加更多动画元素,可以改用双层Canvas结构获得更稳定的帧率:

  • 底层放置固定Canvas,只在初始化、窗口大小变化时绘制一次天空背景,不执行任何清除操作
  • 上层叠加一个背景透明的Canvas,专门承载波浪动画,每帧只擦除上层画布重绘波浪即可
    这种方案不需要每帧重绘静态的天空背景,渲染压力更小。

内容的提问来源于stack exchange,提问作者vogellatte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 10:57:14