HTML5本身不提供建模或粒子系统能力,需借助Canvas 2D(轻量雨雪)或WebGL/Three.js(火焰爆炸)实现;核心是粒子数组管理、requestAnimationFrame更新、性能优化与物理逻辑模拟。

HTML5 本身不提供“建模”或“粒子系统”的内置能力,所谓“HTML5 建模+粒子特效”,实际是借助 Canvas 或 WebGL(常通过 Three.js)在浏览器中实现的实时渲染效果。雨、雪、火焰这类粒子特效,本质是大量受物理规则(重力、风速、生命周期、碰撞)控制的小图形(circle、image、sprite)批量更新与绘制。
用 Canvas 2D 实现轻量雨雪效果(适合页面装饰)
适用于不需要 3D 深度、性能要求不高、兼容性优先的场景。核心是手动管理粒子数组,在 requestAnimationFrame 中更新位置并重绘。
- 每个雨滴用一个对象表示:
{ x, y, speed, length, opacity },y每帧 +=speed,超出画布底部就重置到顶部 - 避免直接
clearRect(0,0,w,h)全屏清空——会闪;改用半透明黑色覆盖(ctx.fillStyle = 'rgba(0,0,0,0.1)'; ctx.fillRect(0,0,w,h))模拟残影 - 雪粒建议加轻微左右飘移(
x += Math.sin(y * 0.01) * 0.5),并随机缩放(Math.random() * 1.5)增强真实感 - 注意 Canvas 像素比:用
canvas.width = canvas.offsetWidth * window.devicePixelRatio防止模糊
const canvas = document.getElementById('rain');
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth * window.devicePixelRatio;
canvas.height = canvas.offsetHeight * window.devicePixelRatio;
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
const drops = [];
for (let i = 0; i < 150; i++) {
drops.push({
x: Math.random() canvas.offsetWidth,
y: Math.random() -canvas.offsetHeight,
speed: 5 + Math.random() 10,
length: 8 + Math.random() 12,
opacity: 0.4 + Math.random() * 0.6
});
}
function drawRain() {
ctx.fillStyle = 'rgba(0,0,0,0.05)';
ctx.fillRect(0, 0, canvas.offsetWidth, canvas.offsetHeight);
drops.forEach(d => {
ctx.beginPath();
ctx.moveTo(d.x, d.y);
ctx.lineTo(d.x + 2, d.y + d.length);
ctx.strokeStyle = rgba(200, 200, 255, ${d.opacity});
ctx.lineWidth = 1;
ctx.stroke();
d.y += d.speed;
if (d.y > canvas.offsetHeight) {
d.y = Math.random() * -50;
d.x = Math.random() * canvas.offsetWidth;
}});
}
function animate() {
drawRain();
requestAnimationFrame(animate);
}
animate();
用 Three.js 做火焰/爆炸粒子(带物理与光照)
真正需要体积感、光影交互、碰撞反馈的火焰或爆炸,必须上 WebGL。Three.js 的 Points + ShaderMaterial 是主流方案,比纯 CPU 粒子(Sprite)性能高得多。
- 别用
ParticleSystem(已废弃),改用BufferGeometry+PointsMaterial构建 GPU 粒子池 - 火焰需动态顶点着色器:在
vertexShader中用sin(time * 2.0)和噪声纹理扰动position,模拟升腾扭曲 - 关键参数:设
material.sizeAttenuation = true让粒子随距离自动缩放;material.alphaTest = 0.5避免半透边缘发虚 - 性能陷阱:每帧修改
geometry.attributes.position.array并调用geometry.attributes.position.needsUpdate = true会卡顿;应预分配足够大的Float32Array,只更新数值,不重建 buffer
requestAnimationFrame 与粒子生命周期的同步问题
粒子死亡后若不及时从数组中剔除,会导致内存持续增长、遍历变慢,尤其在长时运行的特效中(如后台常驻雨效)。
立即学习“前端免费学习笔记(深入)”;
- 不要用
splice()在循环中删除 —— 会跳过下一个元素;改用倒序遍历:for (let i = list.length-1; i>=0; i--) - 更推荐“对象池”模式:创建固定长度数组(如
new Array(500)),粒子死亡后标记active = false,新粒子复用空闲槽位,避免频繁push/pop - 用
performance.now()替代Date.now()计算粒子 age,精度更高(微秒级),避免时间跳跃导致批量死亡
移动端适配和性能断崖点
手机端粒子数超过 300 个就容易掉帧,尤其 Safari 对 Canvas 2D 的 fill/stroke 调用非常敏感。
- 检测设备:用
navigator.userAgent.includes('Mobile')或window.innerWidth 降级粒子数量(比如从 200 → 80) - iOS Safari 不支持
WebGL2的部分扩展,火焰着色器里避免用textureLod或texelFetch;改用texture2D+ 手动 mipmap 计算 - 禁止在
touchmove中触发粒子生成 —— 会阻塞滚动;改用pointerdown+throttle控制发射频率
粒子不是堆数量就有质感,关键是运动逻辑是否符合直觉:雨要垂直加速、雪要左右飘、火焰要有上升+膨胀+消散三阶段。所有“特效”背后都是对物理简化的取舍,先跑通单粒子行为,再批量,最后加随机扰动 —— 这比调一堆参数更快定位问题。











