Canvas粒子系统流畅可维护的关键是分层:画布需适配DPR动态设置宽高并缩放;粒子更新与绘制严格分离;交互统一节流至主循环;结构拆分为Particle、ParticleSystem、Renderer三层职责模块。

Canvas 粒子系统要真正流畅、清晰、可维护,关键不在“画得多”,而在“分得清”——把画布管理、粒子逻辑、动画驱动三件事彻底分开,各自专注,互不干扰。
画布必须动态适配设备像素比(DPR)
写死 canvas.width = 800 是上线前最常踩的坑。Canvas 的 width/height 属性控制的是绘图缓冲区的逻辑像素数,不是显示尺寸。只设 CSS 宽高而不同步设置 canvas 内部尺寸,会导致拉伸模糊。
- 每次 resize 或初始化时,先获取
const dpr = window.devicePixelRatio || 1 - 然后设置:
canvas.width = window.innerWidth * dpr,canvas.height = window.innerHeight * dpr - 再用 CSS 缩放回视觉尺寸:
canvas.style.width = '100vw'; canvas.style.height = '100vh'; - 最后别漏掉:
ctx.scale(dpr, dpr),否则所有坐标和大小都要手动乘 dpr,极易出错
粒子更新与绘制必须严格分离
把 particle.update() 和 particle.draw() 塞进同一个循环里,等于每帧都扛着物理计算 + 渲染双重压力。一掉帧,粒子就跳变或拖影。
用户要生成可打印的中文字帖/练习纸、导出多页 A4 PDF 报告,或把 SVG 设计稿零误差还原到 Canvas 时使用。本技能是「Canvas 内容工厂闭环」的总控,编排:网格渲染引擎(13 种教育网格+拼音标注) → 多页 PDF 导出(A4 合成) → SVG 精准复刻(坐标误差<0.001px)。触发词:生成字帖、练习纸、导出 PDF、SVG 转 Canvas、印刷级还原、A4 报告、米字格田字格。
- 更新阶段(
tick())只做纯数值运算:位置 += 速度、加阻尼、边界检测、生命周期减一 - 绘制阶段(
render())只调用ctx.beginPath() → ctx.arc() → ctx.fill(),不查 DOM、不 new 对象、不调Math.random() - 用
ctx.globalAlpha控制透明度,比反复设fillStyle更快 - 维护两个数组:完整粒子池
particles,和实时存活索引列表aliveParticles,避免遍历时 splice
交互需统一节流到主循环
原生 mousemove 每秒可能触发上百次,但 requestAnimationFrame 最多 60 次。直接在事件里改粒子状态,会导致更新频率远超渲染节奏,粒子“抽搐”或响应滞后。
- 在
mousemove(或touchmove)中只更新一个全局鼠标对象:mouse.x = e.clientX; mouse.y = e.clientY; - 在
tick()阶段统一计算粒子与鼠标的距离、施加力(如force = Math.max(0, 100 - dist) / 100) - 触摸设备记得加
{ passive: false },防止默认行为拦截 - 点击爆发类操作(如烟花),只在
mousedown中标记状态(如isExploding = true),后续由tick()触发生成逻辑
结构上拆成三层职责明确的模块
避免所有代码堆在 requestAnimationFrame 里打乱仗。清晰分层才能长期迭代、多人协作、快速定位问题。
-
Particle 类:只管自己——封装位置、速度、颜色、生命周期;提供
update()和draw(ctx);带isDead()方便回收 -
ParticleSystem 管理器:只管“人”——批量创建、更新调用、按需清理、限制总数、暴露
spawn()/reset()接口 -
Renderer 渲染器:只管“画”——封装
clear()、resize()、setGlobalCompositeOperation();render(particles)只遍历并调用 draw


















