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.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,只更新数值,不重建 bufferrequestAnimationFrame 与粒子生命周期的同步问题粒子死亡后若不及时从数组中剔除,会导致内存持续增长、遍历变慢,尤其在长时运行的特效中(如后台常驻雨效)。
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)
WebGL2 的部分扩展,火焰着色器里避免用 textureLod 或 texelFetch;改用 texture2D + 手动 mipmap 计算touchmove 中触发粒子生成 —— 会阻塞滚动;改用 pointerdown + throttle 控制发射频率粒子不是堆数量就有质感,关键是运动逻辑是否符合直觉:雨要垂直加速、雪要左右飘、火焰要有上升+膨胀+消散三阶段。所有“特效”背后都是对物理简化的取舍,先跑通单粒子行为,再批量,最后加随机扰动 —— 这比调一堆参数更快定位问题。