
本文介绍如何将页面加载即执行的环形进度条动画,改造为仅在元素进入视口时才启动,通过 intersection observer api 实现高性能、无抖动的懒加载式动画触发。
本文介绍如何将页面加载即执行的环形进度条动画,改造为仅在元素进入视口时才启动,通过 intersection observer api 实现高性能、无抖动的懒加载式动画触发。
要让环形进度条(circular progress)在用户滚动至该区域时才开始动画,而非页面一加载就运行,关键在于监听元素是否进入视口(viewport),而非依赖 scroll 事件——后者易引发性能问题且需手动计算位置。现代推荐方案是使用 Intersection Observer API,它轻量、原生支持、且能精准响应可见性变化。
以下是一个完整、可直接运行的实现:
<div class="progress-wrapper"> <div class="circular-progress"></div> <div class="value-container">0%</div> </div>
.circular-progress {
width: 120px;
height: 120px;
border-radius: 50%;
background: conic-gradient(#FBDA9D 0deg, #683279 0deg);
position: relative;
}
.value-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.2rem;
font-weight: bold;
}
// 封装进度动画逻辑,支持复用与重置
function startProgress(el, endValue = 65, speed = 50) {
const progressBar = el.querySelector(".circular-progress");
const valueContainer = el.querySelector(".value-container");
let progressValue = 0;
const progressEndValue = endValue;
const interval = setInterval(() => {
progressValue++;
valueContainer.textContent = `${progressValue}%`;
// 使用 360° / 100 = 3.6° 每 1%,实现百分比映射
const deg = progressValue * 3.6;
progressBar.style.background = `conic-gradient(
#FBDA9D ${deg}deg,
#683279 ${deg}deg
)`;
if (progressValue >= progressEndValue) {
clearInterval(interval);
// 可选:确保最终值精确显示(避免因取整导致差1%)
valueContainer.textContent = `${progressEndValue}%`;
progressBar.style.background = `conic-gradient(
#FBDA9D ${progressEndValue * 3.6}deg,
#683279 ${progressEndValue * 3.6}deg
)`;
}
}, speed);
}
// 使用 Intersection Observer 监听目标元素
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 元素进入视口,启动动画
startProgress(entry.target, 65, 30); // 可传入自定义参数
// 动画启动后可选择停止监听(避免重复触发)
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1 // 当 10% 高度可见时即触发
});
// 观察所有 .progress-wrapper 元素
document.querySelectorAll('.progress-wrapper').forEach(wrapper => {
observer.observe(wrapper);
});
✅ 关键要点说明:
- ❌ 不要用 window.addEventListener('scroll', ...) 手动检测位置——易造成频繁重绘、卡顿,且兼容性和精度差;
- ✅ Intersection Observer 是浏览器原生 API,性能优异,无需 polyfill(现代浏览器全覆盖);
- ✅ threshold: 0.1 表示元素顶部距视口底部还有 90% 时即视为“即将可见”,可按需调整(如 0 表示完全进入,0.5 表示一半可见);
- ✅ 动画启动后调用 observer.unobserve() 防止重复执行,尤其适用于单次动画场景;
- ✅ 将动画逻辑封装为 startProgress() 函数,便于复用、测试和参数化(如不同进度值、速度)。
⚠️ 注意事项:
- 若页面存在多个进度条,确保每个 .progress-wrapper 是独立容器,且内部结构一致;
- 若需支持旧版浏览器(如 IE),需引入 intersection-observer polyfill;
- 避免在 startProgress 中直接操作全局变量,保持函数纯净性与可维护性。
通过以上改造,进度条将真正实现「按需激活」,提升页面性能与用户体验,是现代前端交互动效的标准实践方式。











