canvas粒子特效需将canvas绝对定位覆盖body,缩放时必须同步重设canvas.width/height为window.innerwidth/height×currentscale并重绘,鼠标坐标需除以currentscale校正,fixed元素需反向缩放或移至body顶层。

直接用 body 标签本身做不到粒子特效——它只是容器,没有绘图能力;所谓“body 上的粒子效果”,实际是把 <canvas></canvas> 绝对定位覆盖在 body 上,并通过 JS 驱动绘制。缩放时失真,根本原因是 canvas 的逻辑像素没随缩放同步重置,导致坐标系错乱、鼠标响应偏移、粒子密度视觉异常。
canvas.width / height 必须随缩放实时重算
缩放 body 后,window.innerWidth 和 window.innerHeight 仍返回原始物理尺寸(比如 1920×1080),但 canvas 实际渲染区域被 CSS 拉伸了。如果不重设 canvas.width 和 canvas.height,画布缓冲区还是老尺寸,所有粒子坐标都挤在左上角一小块里,视觉上就是“糊成一团”或“只在角落动”。
- 每次缩放后必须执行:
canvas.width = Math.round(window.innerWidth * currentScale),canvas.height = Math.round(window.innerHeight * currentScale) - 不能只改 CSS 的
transform: scale(),否则 canvas 内部坐标系和 DOM 坐标系彻底脱节 - 重设宽高会清空 canvas 缓冲区,需在下一帧重新绘制,所以缩放后要触发一次
requestAnimationFrame强制刷新
鼠标事件坐标必须反向缩放校正
body 缩放后,event.clientX / clientY 返回的是缩放后的视口坐标,但 canvas 的 ctx 坐标系仍是逻辑像素(即重设后的 canvas.width/height)。不校正会导致点击位置和粒子生成点严重偏移,尤其在高倍缩放下偏差可达上百像素。
- 获取真实画布坐标:
const x = (e.clientX - canvas.getBoundingClientRect().left) / currentScale - 同理计算
y,注意getBoundingClientRect()返回的是缩放后的位置,必须除以currentScale才能映射回逻辑像素 - 如果监听
mousemove做吸附效果,每帧都要做这个转换,不能只在 click 时处理
固定定位元素(如导航栏)会破坏 canvas 布局
当 body 应用 transform: scale() 时,position: fixed 元素仍锚定在视口坐标系,但 canvas 是相对 body 定位的——结果就是导航栏盖住粒子、或者 canvas 被挤到下方空白区。
- 解决方案一:把
canvas移出body,直接挂载到document.body顶层,用position: fixed; top: 0; left: 0; z-index: -1覆盖全屏 - 解决方案二:给 fixed 元素也加
transform: scale(1/currentScale)反向抵消,但要注意字体、边框等也会被缩放,需额外重置font-size和border-width - 绝对不要用
zoom或修改viewport,移动端兼容性几乎为零,且无法精确控制
真正麻烦的不是写动画,而是缩放时 canvas 缓冲区、DOM 坐标系、CSS 渲染层三者之间的像素对齐。哪怕 currentScale 算错 0.01,连续缩放几次后坐标偏差就会累积到不可忽视的程度——建议所有缩放操作统一走一个状态管理函数,避免手动拼接 transform 字符串。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











