canvas不能直接给加粒子效果,需先将图片绘制到离屏canvas再读取像素数据;必须等待img.onload、设置crossorigin,并用alpha或亮度筛选有效像素,初始化时添加随机偏移和速度分布,动画中分离更新与绘制逻辑。

canvas 不能直接给 <img> 加粒子效果——这不是加滤镜,而是用像素数据重构成可逐帧控制的粒子系统。核心就一条:把图片画进离屏 canvas,读取 getImageData(),筛出有效像素,每个像素生成一个粒子对象。
为什么必须用离屏 canvas 读取像素?
浏览器禁止从 <img> 直接提取像素(跨域或未加载完成时会抛 SecurityError)。你得先把它“安全地落盘”到一个不可见的 canvas 上,再调用 getImageData()。
- 必须等
img.onload触发后才能drawImage,否则getImageData()返回空或报错 - 如果图片来自外域,
img.crossOrigin = "anonymous"是硬性要求,且服务器需返回Access-Control-Allow-Origin: * - 别用主渲染 canvas 做像素读取——它会被频繁清空/重绘,干扰采样稳定性
getImageData() 后怎么筛粒子?
data 是一维数组,每 4 个元素一组:[r, g, b, a]。你真正要的是“视觉上可见”的点,不是所有像素都上。
- 按 alpha 判断:只取
data[i + 3] > 125(而非=== 255),容忍半透区域 - 按亮度判断(适合黑白图):
0.299 * r + 0.587 * g + 0.114 * b > 50 - 降采样防卡顿:不要遍历全部像素,比如
for (let x = 0; x 跳着取 - 粒子坐标不是
(x, y),而是(x + offsetX, y + offsetY),因为图片可能没贴左上角绘制
粒子初始化时最容易漏掉的三件事
粒子对象本身不难写,但初始化阶段几个细节不处理,后续动画全乱:
- 粒子初始位置必须加随机偏移(如
x + Math.random() * 10 - 5),否则所有粒子挤在网格点上,动起来像地震仪 - 速度不能全为正或全为零——至少一个分量用
Math.random() * 2 - 1拉开分布范围 - 颜色别直接用 rgba 字符串拼接;建议存成
{ r, g, b, a }对象,后续做渐变/衰减更方便(比如particle.a *= 0.99)
requestAnimationFrame 里只做两件事
动画循环不是“什么都往里塞”的垃圾桶。性能崩塌往往始于这里:
- 更新阶段(
update()):只改数值——x += vx、y += vy、size *= 0.98,不碰 canvas、不 new 对象、不查 DOM - 绘制阶段(
draw()):只调ctx.beginPath()→ctx.arc()→ctx.fill(),别在里面算距离、连线条、做碰撞 - 如果要连线(比如粒子间随距离变细的线),计算逻辑必须提前到 update 阶段缓存好,绘制时只读不算
粒子数量超过 2000 就该考虑降采样或换 WebGL;但绝大多数需求,控制在 300–800 粒子,配合 2px 以下圆点半径,就能在主流设备跑满 60fps。关键不在炫技,在稳——稳住帧率,才谈得上效果。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











