原生 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 的初始值陷阱,最容易被忽略。











