
本文详解如何利用 intersectionobserver 为多个标题元素实现“进入视口时才逐字动画显示”的效果,解决原方案中所有标题在页面加载时即统一动画的问题,并提供可直接运行的 css/js 完整代码与关键原理说明。
本文详解如何利用 intersectionobserver 为多个标题元素实现“进入视口时才逐字动画显示”的效果,解决原方案中所有标题在页面加载时即统一动画的问题,并提供可直接运行的 css/js 完整代码与关键原理说明。
在实现标题逐字(word-by-word)动画时,一个常见误区是:将动画属性(如 animation)直接写在基础类上,导致浏览器在解析 HTML 时就已计算并触发动画——即使元素尚未进入视口。你当前的代码正是如此:.heading-reveal span 默认设置了 animation,而 --word-index 又在 onload 后才动态注入,造成所有 <span></span> 初始 animation-delay: calc(0 * 0.25s) = 0s,从而在页面加载瞬间全部播放。
✅ 正确解法是:分离「样式」与「动画触发」 —— 仅在元素真正进入视口后,通过添加 .reveal 类来激活动画,确保动画时机完全受 IntersectionObserver 控制。
以下是优化后的完整实现:
✅ 核心 CSS 修改(关键!)
.heading-reveal {
display: flex;
flex-wrap: wrap;
}
/* 基础状态:隐藏文字,不启用动画 */
.heading-reveal span {
opacity: 0;
}
/* 仅当添加 .reveal 类时才启用动画(延迟由 --word-index 决定) */
.heading-reveal span.reveal {
animation: revealWord 0.5s forwards;
animation-delay: calc(var(--word-index, 0) * 0.25s); /* fallback 0 防止未设变量时报错 */
}
@keyframes revealWord {
to { opacity: 1; }
}
/* 辅助:制造滚动空间,便于测试多标题视口触发 */
.v-100 { height: 100vh; }
✅ JavaScript 保持简洁可靠
window.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver(
(entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 为当前标题内每个单词 span 设置索引并添加 reveal 类
entry.target.querySelectorAll('span').forEach((span, index) => {
span.style.setProperty('--word-index', index);
span.classList.add('reveal');
});
obs.unobserve(entry.target); // 一次性触发,避免重复执行
}
});
},
{
root: null, // 监听视口
rootMargin: '0px',
threshold: 0.1 // 元素 10% 进入视口即触发(可根据需要调整为 0 或 0.3)
}
);
document.querySelectorAll('.heading-reveal').forEach(el => {
observer.observe(el);
});
});
✅ HTML 结构(无需改动,保持语义化)
<h1 class="heading-reveal"> <span>Title</span> <span>Goes</span> <span>Here</span> </h1> <div class="v-100"></div> <h1 class="heading-reveal"> <span>2nd</span> <span>Title</span> <span>Here</span> </h1> <div class="v-100"></div> <h1 class="heading-reveal"> <span>3rd</span> <span>Title</span> <span>Here</span> </h1>
⚠️ 注意事项与进阶建议
-
threshold调试技巧:若动画触发过早/过晚,可尝试threshold: [0, 0.2, 0.5]数组,让 Observer 在多个比例点回调,更精细控制。 -
性能优化:对大量标题,建议使用
debounce或requestIdleCallback包裹回调逻辑;但单页少量标题无需过度优化。 -
无障碍友好:可添加
prefers-reduced-motion查询,在用户开启“减少动画”时禁用动画:@media (prefers-reduced-motion: reduce) { .heading-reveal span.reveal { animation: none; opacity: 1; } } - 兼容性提示:IntersectionObserver 在现代浏览器中支持良好(Chrome 51+, Firefox 55+, Safari 12.1+),如需支持旧版 IE,需引入 intersection-observer polyfill。
通过以上重构,每个 <h1 class="heading-reveal"></h1> 将严格在其内容进入视口时,才逐字(按顺序、带延迟)淡入显示——真正实现高性能、可预测、用户友好的延迟动画体验。










