scrollend 事件是唯一能直接反映滚动物理停止时刻的原生机制,由浏览器合成器判定滚动动画完全静止后触发,支持 Chrome 112+、Firefox 119+、Safari 17.4+,适用于平滑滚动、scroll-snap、fragment 导航等场景。

scrollend 事件是目前唯一能**直接反映滚动物理停止时刻**的原生机制,它不依赖猜测、不引入延迟、也不受惯性滚动或 scroll-snap 定位动画干扰。只要浏览器支持(Chrome 112+、Firefox 119+、Safari 17.4+),就能在滚动动画完全静止、触摸释放、按键松开、scrollTo 完成等真实终止节点上触发。
scrollend 的触发时机完全由浏览器渲染引擎判定
它不是基于“位置没变”这种间接信号,而是监听底层合成器(compositor)的滚动动画状态。这意味着:
- 即使用户快速滑动后松手,页面因惯性继续滚动几百毫秒,scrollend 也只在最后一帧真正停稳时触发
- 启用 CSS
scroll-snap-type: x mandatory后,元素自动吸附到位的整个过程(含减速与微调)结束后才触发 - 调用
element.scrollTo({ top: 100, behavior: 'smooth' })时,scrollend 在平滑动画帧完全结束、位置锁定后立即触发 - 滚动到 fragment(如
#section2)或通过scrollIntoView({ behavior: 'smooth' })滚动,同样适用
如何绑定并确保逻辑只执行一次
scrollend 是一次性事件,不会重复触发。推荐使用 addEventListener 并搭配事件选项保障可靠性:
- 对全局视口:
window.addEventListener('scrollend', handler, { once: true }) - 对自定义滚动容器(如时间轴):
timelineEl.addEventListener('scrollend', handler) - 避免使用
onscrollend属性写法,因其无法传参且不易移除 - 若需多次响应,不加
{ once: true }即可,事件会随每次滚动结束持续触发
与传统 setTimeout 防抖的本质区别
传统方案靠“滚动静默期”推测停止,而 scrollend 是确定性信号:
- setTimeout 100ms:可能在惯性滚动中途就执行,导致
setActiveClass()错误高亮未到位的元素 - scrollend:保证回调运行时,滚动位置已稳定、视觉呈现已完成、scroll-snap 对齐已生效
- 例如 Swiper + 时间轴联动中,用 scrollend 替代 scroll + debounce 后,
slideTo()不再打断用户手势,激活态与实际可视项严格一致
兼容性兜底策略要分层处理
对不支持 scrollend 的旧版 Safari 或低版本浏览器,不能简单 fallback 到固定 delay 的 setTimeout:
- 先检测支持性:
if ('onscrollend' in window) { /* 原生 */ } else { /* 模拟 */ } - 模拟时建议结合
requestAnimationFrame+ 防抖(如 150ms),比纯 setTimeout 更贴近视觉帧节奏 - 更稳妥的做法是引入成熟 polyfill,如 scrollyfills,它内部已处理 touch/mouse/keyboard 多种终止路径
- 注意:polyfill 无法 100% 复现原生 scrollend 的精度,仅作降级保障,核心体验仍应优先面向支持环境

















