
本文介绍在启用 `scroll-behavior: smooth` 的页面中,如何避免锚点链接触发的平滑滚动误触 scroll 事件,推荐使用 `wheel` 事件替代 `scroll` 事件,并辅以节流控制,实现仅响应真实用户滚动操作。
在现代 Web 开发中,为提升用户体验,常通过 CSS 设置 html { scroll-behavior: smooth; } 实现原生平滑锚点跳转。但该特性会触发 scroll 事件(即使由 点击引发),导致你无法区分「用户手动滚动」与「程序化/导航式滚动」——这在需监听真实滚动行为的场景(如懒加载、滚动进度条、吸顶导航等)中尤为棘手。
直接监听 scroll 事件并尝试在点击时临时移除监听器(如 removeEventListener)并不可靠:
- 平滑滚动是异步且持续数百毫秒的,你无法准确预判其何时开始/结束;
- 多次快速点击或键盘导航(如 Tab + Enter)易造成监听器状态错乱;
- scroll 事件本身在平滑滚动期间高频触发,加剧误判风险。
✅ 更稳健的解决方案:改用 wheel 事件
wheel 事件仅在用户通过鼠标滚轮、触摸板滚动或键盘(空格/方向键)主动触发滚动时触发,而不会被 scroll-behavior: smooth 的锚点跳转、window.scrollTo() 或 element.scrollIntoView() 等 API 触发。因此,它天然区分了「人为滚动」与「声明式导航滚动」。
以下是优化后的完整实现:
function scrollHandler() {
console.log("user scrolled (wheel or keyboard)");
}
// 节流防止高频触发(200ms 间隔)
const throttledScrollHandler = throttle(scrollHandler, 200);
// ✅ 监听 wheel 而非 scroll
window.addEventListener("wheel", throttledScrollHandler, { passive: true });
// 可选:同时监听键盘滚动(PageUp/PageDown/ArrowKeys 等)
window.addEventListener("keydown", (e) => {
if ([32, 33, 34, 35, 36, 37, 38, 39, 40].includes(e.keyCode)) {
// 空格、PgUp、PgDn、Home、End、方向键 —— 常见滚动键
throttledScrollHandler();
}
});
function throttle(callback, delay) {
let timer = null;
return function (...args) {
if (timer) return;
timer = setTimeout(() => {
callback(...args);
timer = null;
}, delay);
};
}⚠️ 注意事项与补充说明:
- wheel 事件默认为 passive: true(推荐显式声明),避免因 preventDefault() 导致滚动卡顿;
- 若需兼容触屏设备上的“拖拽滚动”(非惯性滚动),注意:iOS Safari 和部分 Android 浏览器中 wheel 在触摸拖拽时可能不触发,此时可结合 touchstart + touchend 时间差判断(但本例中锚点跳转仍不会触发,故 wheel 已满足核心需求);
- 不要依赖 scroll 事件的 event.detail 或 deltaY 判断来源——它们无法区分触发源;
- 如后续需支持 scroll 事件的其他用途(如监听 position: sticky 元素状态),请确保逻辑解耦,避免混用。
总结:当页面启用 scroll-behavior: smooth 时,放弃对 scroll 事件的“来源过滤”幻想,转向语义更明确的 wheel(+ keydown)事件组合,是轻量、可靠且符合浏览器规范的最佳实践。










