
本文介绍如何利用三角函数(如 Math.sin() 和 Math.cos())替代硬编码位移,结合动态屏幕适配与随机相位扰动,实现流畅、有机、无闪烁的粒子漂浮动画,完全基于 Canvas 或 DOM 元素,无需 CSS transition 依赖。
本文介绍如何利用三角函数(如 `math.sin()` 和 `math.cos()`)替代硬编码位移,结合动态屏幕适配与随机相位扰动,实现流畅、有机、无闪烁的粒子漂浮动画,完全基于 canvas 或 dom 元素,无需 css transition 依赖。
在前端粒子系统中,“生硬跳跃”往往源于直接修改 top/left 的离散增量(如 += 10px),这违背了物理运动的连续性。真正的平滑并非靠 CSS transition(它仅作用于属性变化瞬间,无法解决逻辑抖动),而应从运动建模入手:将每个粒子的位置定义为时间 t 的连续函数,例如正弦波叠加——它天然具备周期性、可微性与视觉柔和感。
以下是一个轻量、高性能、响应式且可扩展的纯 JS 粒子系统实现(基于 DOM,亦可轻松迁移至 <canvas></canvas>):
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
✅ 核心设计原则
- 动态粒子数量:按屏幕面积自动计算(如每 5000px² 生成 1 粒子)
- 无边界突跳:粒子始终在视口内浮动,通过三角函数约束范围,无需碰撞检测
- 多频扰动:每个粒子叠加多个不同频率/相位的正弦项,避免规律性运动
-
零 HTML 依赖:所有元素动态创建,支持自定义图像(通过
background-image或mask-image)
? 完整可运行代码
(() => {
// 配置项
const PARTICLE_SIZE = 8; // 粒子尺寸(px)
const DENSITY_FACTOR = 5000; // 每多少 px² 生成 1 粒子
const PHASE_COUNT = 4; // 每粒子叠加的正弦波数量(越多越自然)
const BASE_SPEED = 0.02; // 时间步进系数,控制整体运动速度
let particles = [];
let phaseData = []; // 存储每个粒子的随机频率与相位参数
let t = 0; // 全局时间计数器(避免 setInterval 累积误差)
// 1️⃣ 动态生成粒子数量
const calcParticleCount = () =>
Math.max(15, Math.floor(window.innerWidth * window.innerHeight / DENSITY_FACTOR));
// 2️⃣ 创建粒子元素并初始化随机相位
const initParticles = () => {
// 清空旧粒子
document.querySelectorAll('.js-particle').forEach(el => el.remove());
particles = [];
phaseData = [];
const count = calcParticleCount();
for (let i = 0; i < count; i++) {
const particle = document.createElement('div');
particle.className = 'js-particle';
particle.style.cssText = `
position: fixed;
width: ${PARTICLE_SIZE}px;
height: ${PARTICLE_SIZE}px;
border-radius: 50%;
pointer-events: none;
z-index: -1;
`;
// ✨ 支持自定义图片(取消注释并替换路径)
// particle.style.background = `url('your-particle.png') center/contain no-repeat`;
// 随机颜色(可选)
particle.style.backgroundColor = `hsl(${Math.random() * 360}, 70%, 60%)`;
document.body.appendChild(particle);
particles.push(particle);
// 为每个粒子预生成 PHASE_COUNT 组 [频率, 相位] 参数
const phases = [];
for (let j = 0; j < PHASE_COUNT; j++) {
phases.push({
freq: 0.5 + Math.random() * 2, // 频率:0.5 ~ 2.5,控制波动快慢
phase: Math.random() * Math.PI * 2 // 初始相位:0 ~ 2π,打破同步感
});
}
phaseData.push(phases);
}
};
// 3️⃣ 平滑位置更新(核心:正弦合成)
const updatePositions = () => {
t += BASE_SPEED; // 全局时间推进
particles.forEach((particle, i) => {
let xSum = 0, ySum = 0;
const phases = phaseData[i];
// 叠加多个正弦波 → 生成复杂但自然的轨迹
for (const { freq, phase } of phases) {
xSum += Math.sin(t * freq + phase);
ySum += Math.cos(t * freq + phase * 0.7); // cos 略偏移,增强方向多样性
}
// 映射到屏幕安全区域(留出粒子半径边距)
const x = window.innerWidth / 2 + (window.innerWidth / 2 - PARTICLE_SIZE) * xSum / PHASE_COUNT;
const y = window.innerHeight / 2 + (window.innerHeight / 2 - PARTICLE_SIZE) * ySum / PHASE_COUNT;
particle.style.left = `${x}px`;
particle.style.top = `${y}px`;
});
};
// 4️⃣ 响应式重载
const setupResizeHandler = () => {
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
initParticles();
}, 150);
});
};
// ? 启动
initParticles();
setupResizeHandler();
// 使用 requestAnimationFrame 替代 setInterval —— 更精准、更节能
const animate = () => {
updatePositions();
requestAnimationFrame(animate);
};
animate();
})();⚠️ 关键注意事项
-
避免
setInterval:原方案中setInterval(..., 125)易导致帧率不稳定。requestAnimationFrame与浏览器刷新率同步,动画更顺滑且省电。 -
CSS 优化:为粒子添加
will-change: top, left(仅当数量 >50 时启用)可触发 GPU 加速;同时确保#particles或body无意外overflow: hidden裁剪。 -
性能提示:若粒子数超 200,建议迁移到
<canvas></canvas>渲染(CPU 计算 + GPU 绘制分离),DOM 方案在 50–150 粒子内表现极佳。 -
图像支持:将
particle.style.background替换为particle.style.backgroundImage = 'url(./img/particle.svg)'即可使用矢量图标;注意设置background-size: contain与background-position: center。
? 总结
平滑粒子的本质是用数学函数描述运动,而非“插值视觉”。sin/cos 提供无限可微的曲线,多频叠加模拟真实流体混沌,而动态密度与响应式重载确保跨设备一致性。这套模式可无缝扩展:加入鼠标扰动(x += (mouseX - x) * 0.02)、深度分层(z-index 随 sin(t*0.3) 变化)或色彩渐变(HSL 中 h 绑定 t),让页面拥有呼吸感的生命力。

















