原生 window.scrollTo({top: 0, behavior: ‘smooth’}) 实现轻量平滑回顶,配合 scrollY + requestAnimationFrame 节流显示按钮,并用 class 切换与 pointer-events: none 确保兼容性与交互安全。

回到顶部按钮用 window.scrollTo() 最轻量
不用任何 js 插件,原生 window.scrollTo() 就能实现平滑回到顶部,兼容现代浏览器(chrome 61+、firefox 68+、safari 15.4+、edge 79+)。很多项目硬上 jquery 或第三方滚动库,纯属冗余。
关键参数就两个:top: 0 和 behavior: 'smooth'。示例:
document.getElementById('backToTop').addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); });
- 不支持
behavior: 'smooth'的旧浏览器(如 IE 或早期 android webview)会自动降级为瞬间跳转,不影响功能 - 避免写成
window.scrollTo(0, 0)—— 这种老写法无法触发平滑动画 - 如果页面有固定 header,可微调
top值(如top: -80)避免被遮挡
隐藏按钮靠 scrollY 判断,别用 scroll 事件监听全量
常见错误是给 window 绑定 scroll 事件,每次滚动都执行 dom 操作,造成卡顿。实际只需在滚动后“节流判断”,或改用 IntersectionObserver(但对简单场景太重)。
更省代码的写法:用 scrollY + requestAnimationFrame 防抖:
立即学习“前端免费学习笔记(深入)”;
function toggleBackToTop() { const btn = document.getElementById('backToTop'); if (window.scrollY > 300) { btn.style.display = 'block'; } else { btn.style.display = 'none'; } } window.addEventListener('scroll', () => { requestAnimationFrame(toggleBackToTop); });
-
scrollY > 300是经验值,可根据首屏高度调整;别写死100或50,小屏设备容易误触 - 用
display: none/block而非opacity或visibility,减少重绘开销 - 如果按钮本身已用 css 定位(
position: fixed),确保z-index足够高,否则可能被遮住
CSS 隐藏逻辑必须配合 pointer-events 防误点
仅靠 display: none 隐藏按钮还不够。如果用户快速滚动又立刻点击,可能因渲染延迟导致按钮短暂闪现并响应点击——尤其在低端安卓机上。
安全做法是双重控制:
#backToTop { position: fixed; bottom: 24px; right: 24px; width: 48px; height: 48px; border-radius: 50%; background: #333; color: white; display: none; pointer-events: none; /* 关键:隐藏时彻底禁用交互 */ transition: opacity 0.2s; } backToTop.show {
display: flex; align-items: center; justify-content: center; pointer-events: auto; }
- JS 中只切换
.show类,不直接操作style.display—— 更利于维护和动画控制 -
pointer-events: none比display: none更可靠,能彻底阻断冒泡和点击穿透 - 如果用了 svg 图标,记得给
加aria-hidden="true",避免读屏器误读
移动端需绕过 Safari 的 scrollY 延迟问题
ios Safari 在页面加载完成前,window.scrollY 可能返回 0 即使页面已被外部链接带到了中间位置(比如从微信内打开带 hash 的 URL)。这会导致按钮首次不显示。
解决方法:不只依赖 scrollY,加一层初始检测:
function initBackToTop() { const btn = document.getElementById('backToTop'); // 先检查当前是否已在顶部 const isAtTop = window.scrollY === 0 && document.documentElement.scrollTop === 0; btn.classList.toggle('show', !isAtTop); // 再监听后续滚动 window.addEventListener('scroll', () => { requestAnimationFrame(() => { btn.classList.toggle('show', window.scrollY > 300); }); }); }
// 确保 DOM 加载完再执行 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initBackToTop); } else { initBackToTop(); }
- 同时检查
window.scrollY和document.documentElement.scrollTop,覆盖 Safari 的怪异行为 - 不要用
setTimeout延迟初始化——不可靠,且增加白屏时间 - 如果项目用了 vue/react,这个逻辑应封装进组件的
mounted或useEffect,而非全局脚本
真正省代码的关键不是找更短的插件,而是认清:回到顶部本质是「状态判断 + 原生 API 调用」,所有额外抽象层都在增加不可见的维护成本。特别是 scrollY 的边界情况和 Safari 的初始值陷阱,最容易被忽略。