
本文详解如何通过分离动画类、利用 animationend 事件与精确的 settimeout 延迟,实现 10 个随机位置圆点以 100ms 间隔依次入场、缩放淡出,并无限循环——彻底解决“单次触发”“动画不重播”等常见陷阱。
本文详解如何通过分离动画类、利用 animationend 事件与精确的 settimeout 延迟,实现 10 个随机位置圆点以 100ms 间隔依次入场、缩放淡出,并无限循环——彻底解决“单次触发”“动画不重播”等常见陷阱。
要实现「10 个圆点在屏幕随机位置逐个出现(间隔 0.1 秒)、从中心放大并淡出,且全过程无限循环」,关键在于解耦样式状态与动画触发逻辑。原始代码失败的核心原因有二:
- ❌ 直接在 forEach 中连续调用 circ() 导致所有圆点瞬间渲染,失去时间差;
- ❌ 将 animation 写死在 .circle 类中,导致动画仅在元素首次添加样式时生效,后续 transform/opacity 变更无法重新触发动画(CSS 动画不会因属性重设而重播,除非移除再添加动画类)。
✅ 正确解法是:用独立的 .anim 类承载动画声明,每次播放前先移除、再异步添加该类,并借助 animationend 事件链式驱动下一轮循环。
✅ 推荐实现方案(含完整代码)
HTML(保持简洁,10 个占位元素):
<link rel="stylesheet" href="style.css"><span class="circle"></span> <span class="circle"></span> <!-- ... 共 10 个 --> <script src="script.js"></script>
CSS(分离动画控制类,禁用滚动避免干扰):
body {
background-color: #000;
width: 100vw;
height: 100vh;
margin: 0;
overflow: hidden; /* 防止动画溢出触发滚动条 */
position: relative; /* 确保 absolute 子元素相对 body 定位 */
}
.circle {
position: absolute;
border-radius: 50%;
transform: translate(-50%, -50%) scale(0.1); /* 初始极小尺寸 */
/* 动画逻辑完全交给 .anim 类 */
}
.anim {
animation: scale 1s ease-out forwards; /* forwards 保持最终状态(opacity: 0) */
}
@keyframes scale {
to {
transform: translate(-50%, -50%) scale(3);
opacity: 0;
}
}
JavaScript(精准定时 + 动画重播 + 无限循环):
function startCircleSequence() {
const circles = document.querySelectorAll('.circle');
const width = window.innerWidth;
const height = window.innerHeight;
// 单个圆点的初始化与动画触发函数
const initCircle = (circle, index) => {
// 生成随机参数
const x = Math.floor(Math.random() * width);
const y = Math.floor(Math.random() * height);
const size = Math.floor((Math.random() * 6) + 5) * 10;
const r = Math.floor(Math.random() * 255).toString(16).padStart(2, '0');
const g = Math.floor(Math.random() * 255).toString(16).padStart(2, '0');
const b = Math.floor(Math.random() * 255).toString(16).padStart(2, '0');
// 重置样式(移除旧动画、重设位置尺寸)
circle.classList.remove('anim');
circle.style.left = `${x}px`;
circle.style.top = `${y}px`;
circle.style.width = `${size}px`;
circle.style.height = `${size}px`;
circle.style.backgroundColor = `#${r}${g}${b}`;
// 关键:异步添加动画类,确保浏览器重排后触发
setTimeout(() => circle.classList.add('anim'), 0);
};
// 为每个圆点绑定动画结束事件,用于启动下一轮(或自身下一次)
circles.forEach((circle, index) => {
circle.addEventListener('animationend', () => {
// 动画结束后,延迟 100ms 启动下一个(形成 100ms 间隔队列)
// 最后一个圆点(index === 9)结束后,延迟 100ms 重启整个序列
const nextDelay = index === circles.length - 1 ? 100 : 0;
setTimeout(() => initCircle(circle, index), nextDelay);
});
// 启动第 N 个圆点(N × 100ms 延迟)
setTimeout(() => initCircle(circle, index), index * 100);
});
}
// 首次启动
startCircleSequence();
// 可选:监听窗口大小变化,动态适配
window.addEventListener('resize', () => {
// 如需实时响应视口变化,可在此处重置所有圆点位置
});
⚠️ 关键注意事项
- setTimeout(..., 0) 不可省略:直接 classList.add('anim') 在同一任务队列中执行,浏览器来不及重排(reflow),动画将被跳过。setTimeout 将其推入下一宏任务,确保样式已应用。
- animationend 是循环枢纽:它比 setTimeout(go, 1000) 更精准可靠——不依赖固定间隔,而是严格按动画实际完成时间触发,避免因性能波动导致节奏偏移。
- .padStart(2, '0') 补零:防止 toString(16) 生成如 "a" 这样的单字符十六进制值,确保颜色格式为 #rrggbb。
- forwards 动画填充模式:配合 opacity: 0,使圆点动画结束后保持透明,避免闪回。
✅ 效果验证要点
- 打开页面,观察是否看到 10 个彩色圆点以约 0.1 秒间隔从不同位置“弹出”;
- 每个圆点均从极小尺寸平滑放大至较大尺寸并同步淡出;
- 最后一个圆点消失后约 0.1 秒,第一个圆点立即在新随机位置重新开始动画;
- 控制台无报错,animationend 事件稳定触发。
此方案兼顾性能、可维护性与视觉一致性,是 CSS 动画与 JS 时序控制协同的经典实践。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










