
本文介绍一种基于 position: sticky 容器的滚动驱动标题动画方案,通过监听 scroll 事件并结合 getboundingclientrect() 精确判断元素视口位置,实现标题按序淡入、高度展开的交互动画,同时规避粘性定位导致的滚动检测中断问题。
在构建滚动触发动画(Scroll-triggered animation)时,一个常见陷阱是:当使用 position: sticky 的容器进入粘性状态后,其内部元素的 getBoundingClientRect() 计算可能因布局重排或滚动锚定行为而失效,导致后续滚动无法被正确响应——这正是原问题中“滚动检测停止”的根本原因。
要解决该问题,关键在于不依赖单个 heading 相对于整个视口的位置,而是以 .sticky 容器为局部坐标系参考,动态判断每个标题是否完全位于该容器可视区域内。以下是优化后的完整实现逻辑:
✅ 正确的滚动监听与激活逻辑
const headings = document.querySelectorAll('.animated-text'); const sticky = document.querySelector('.sticky'); // 防抖处理,避免高频触发影响性能 let scrollTimer; window.addEventListener('scroll', () => { clearTimeout(scrollTimer); scrollTimer = setTimeout(() => { const stickyRect = sticky.getBoundingClientRect(); // 遍历所有标题,仅当其完全处于 sticky 区域内时激活 headings.forEach((heading, index) => { const headingRect = heading.getBoundingClientRect(); // 判断:heading 的 top 和 bottom 均在 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 动画配置(兼顾可访问性与性能)
.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, transform 0.8s ease; } .animated-text.active { opacity: 1; max-height: 1000px; /* 足够容纳任意内容高度 */ transform: translateY(-12px); }
⚠️ 注意事项:避免使用 height: auto 触发过渡:CSS 不支持 height: auto 的平滑过渡,应改用 max-height 并设为足够大的固定值(如 1000px);禁用 position: absolute:它会破坏文档流,使 getBoundingClientRect() 返回值脱离 sticky 容器上下文,导致逻辑失效;确保 .sticky 具备明确高度/内容占位:空容器或 height: 0 会导致 stickyRect.bottom 异常,建议保留 padding-bottom 或内部内容撑开;移动端兼容性:safari 对 sticky + scroll Event 的组合存在延迟,建议添加 touch-action: manipulation 提升响应。
✅ html 结构建议(语义化 & 可维护)
Intro
First
Second
Third
此方案无需第三方库,纯原生实现,兼顾性能、可访问性与跨浏览器稳定性。最终效果是:随着用户持续向下滚动,.sticky 容器锁定顶部后,其内部四个标题将严格按顺序逐个“浮现”(淡入+微上移),前一个自动隐藏,形成清晰的视觉引导节奏。