本文介绍在启用 `scroll-behavior: smooth` 时,如何避免锚点链接跳转触发的滚动干扰原生滚动监听逻辑,核心方案是用 `wheel` 事件替代 `scroll` 事件,并说明其原理与适用边界。
在现代 Web 开发中,为提升用户体验,常通过 CSS 设置
html
{ scroll-behavior: smooth; } 实现锚点链接的平滑滚动。但该行为会触发 scroll 事件,导致原本仅用于监听用户
主动滚动
(如鼠标滚轮、触摸拖拽、键盘方向键)的监听器被误触发,难以区分“真实滚动”与“导航跳转引发的滚动”。
直接监听 scroll 事件无法区分触发源——无论是用户滚动、
win
dow.scrollTo() 调用,还是 点击,只要页面滚动位置变化,scroll 事件就会触发。因此,
不建议依赖 scroll 事件来判断“用户是否正在主动滚动”
。
✅ 推荐解法:改用 wheel 事件
wheel 事件仅在用户
物理滚动输入设备
(如鼠标滚轮、触控板惯性滚动、带滚轮的触控笔)时触发,
完全不会由 锚点跳转、scrollIntoView() 或 scrollTo() 等 JS API 触发
。这使其成为检测“真实用户滚动意图”的理想选择。
以下是优化后的完整实现:
Trae win
Trae Windows 官方版本现已全面支持 Win10 和 Win11 系统(64位)。国内开发者可直接访问 Trae 中文官网(trae.cn 或 trae.com.cn),一键下载官方正版 .exe 安装包,享受国内直连的高速稳定体验。安装过程极其简便,双击即可运行,并支持一键无缝导入 VS Code 或 Cursor 的原有主题、快捷键及插件配置。登录支持手机号、微信或稀土掘金账号。内置免费的豆包与 DeepSeek 等顶级大模型,让你开箱即用,轻松开启高效的 AI 智能编程之旅。
下载
立即学习
“
Java免费学习笔记(深入)
”;
⚠️ 注意事项:
wheel 事件
不覆盖所有手动滚动场景
:例如键盘 PageDown / Space、触摸屏手指拖拽(非惯性滚动)、scrollbar 拖动等
不会触发 wheel
。若需覆盖更广的“用户发起滚动”,可结合 scroll + 状态标记(如 isNavigating = true),并在 scrollend 事件中重置(需注意兼容性)。
scrollend 是较新标准(Chrome 115+、Firefox 119+ 支持),可用于精准捕获平滑滚动结束:
若必须保留 scroll 事件,且仅需排除 跳转,可配合 performance.now() 时间戳做简单过滤(平滑滚动通常在点击后 300–600ms 内发生),但不如 wheel 方案简洁可靠。
✅ 总结:
对于“仅响应用户真实滚动输入”的需求,
优先使用 wheel 事件替代 scroll
——它语义清晰、无副作用、无需状态管理,且完美规避 scroll-behavior: smooth 带来的干扰。在兼容性允许的前提下,可进一步结合 scrollend 实现更精细的滚动生命周期控制。
function onUserScroll() {
console.log("✅ 用户主动滚动(wheel 检测)");
}
// 带节流的 wheel 监听器(防抖更推荐,见下方说明)
const throttledScrollHandler = throttle(onUserScroll, 200);
window.addEventListener("wheel", throttledScrollHandler);
// 可选:同时监听点击,用于调试或补充逻辑
window.addEventListener("click", (e) => {
if (e.target.closest("a[href^='#']")) {
console.log("➡️ 检测到锚点链接点击(不触发滚动监听)");
}
});
// 节流函数(基础版)
function throttle(callback, delay) {
let timer = null;
return function (...args) {
if (timer) return;
timer = setTimeout(() => {
callback(...args);
timer = null;
}, delay);
};
}let isNavigating = false;
document.addEventListener("scroll", () => {
if (!isNavigating) console.log("用户滚动或脚本滚动");
});
document.addEventListener("click", (e) => {
if (e.target.closest("a[href^='#']")) {
isNavigating = true;
setTimeout(() => isNavigating = false, 100); // 降级兜底
}
});
// 更优:监听 scrollend(推荐现代项目使用)
document.addEventListener("scrollend", () => {
isNavigating = false;
});