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
相关产品推荐
相关产品推荐

