
本文介绍一种基于 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 容器锁定顶部后,其内部四个标题将严格按顺序逐个“浮现”(淡入+微上移),前一个自动隐藏,形成清晰的视觉引导节奏。











