
本文介绍一种可靠方案,通过监听滚动并结合 `getboundingclientrect()` 精确判断标题在粘性区域内的可视状态,实现在 `.sticky` 容器固定后仍能持续响应滚动、逐个激活标题动画的效果。
在构建滚动驱动的标题动画时,一个常见误区是直接依赖视口中心(如 viewportHeight / 2)触发状态切换——这在粘性定位(position: sticky)生效后极易失效,因为粘性元素脱离正常文档流后,其子元素的 getBoundingClientRect() 仍以视口为参考,但逻辑上需以粘性容器自身边界为判定基准。
正确的解法是:将滚动监听与粘性容器的局部坐标系对齐。核心思路是——仅当某个标题完全位于 .sticky 元素的可视区域内时,才将其设为 .active,其余标题统一移除该类。这样既避免了因粘性定位导致的“滚动冻结”假象,又确保动画严格按容器内顺序推进。
以下是完整可运行的实现:
const headings = document.querySelectorAll('.animated-text');
const sticky = document.querySelector('.sticky');
// 防抖优化:避免高频 scroll 触发重绘
let scrollTimer;
window.addEventListener('scroll', () => {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
const stickyRect = sticky.getBoundingClientRect();
// 遍历每个标题,检查其是否完全处于 sticky 区域内
headings.forEach((heading, index) => {
const headingRect = heading.getBoundingClientRect();
// 判定条件:标题顶部 ≥ sticky 顶部,且底部 ≤ sticky 底部
const isInStickyView =
headingRect.top >= stickyRect.top &&
headingRect.bottom <= stickyRect.bottom;
if (isInStickyView) {
// 激活当前项,关闭其他项
headings.forEach((h, i) => {
h.classList.toggle('active', i === index);
});
}
});
}, 16); // ~60fps 节流
});对应的关键 CSS 需注意两点:
- 使用 max-height 替代 height 实现高度过渡(height: auto 不支持 CSS 动画);
- 移除可能干扰布局的 position: absolute,保持标题自然流式排列,确保 getBoundingClientRect() 获取真实位置。
.animated-text {
opacity: 0;
max-height: 0;
overflow: hidden;
transition: opacity 0.8s cubic-bezier(0.34, 1.56, 0.64, 1),
max-height 0.8s ease;
margin: 0;
}
.animated-text.active {
opacity: 1;
max-height: 1000px; /* 足够容纳任意内容的高度 */
}⚠️ 注意事项:
- 确保 .sticky 元素有明确的 top 值(如 top: 0),否则 position: sticky 不生效;
- 若标题内容高度差异大,建议统一设置 line-height 和 margin,避免因高度突变引发视觉跳动;
- 在移动端需额外监听 touchmove(配合 passive: false)以兼容 iOS Safari;
- 如需更精细控制(如部分进入即触发),可将判定逻辑改为 headingRect.top = stickyRect.top。
此方案不依赖第三方库,兼容主流浏览器,且保持 HTML 结构语义清晰——所有标题保留在同一 .text-animations 块级容器中,无需绝对定位或 JavaScript 操控 DOM 位置,真正做到了「结构不变、行为可控、动画流畅」。










