别再折磨你的 React 应用:顺滑如黄油的 Scroll-To-Top 组件
发布: (2026年3月3日 GMT+8 00:58)
4 分钟阅读
原文: Dev.to
Source: Dev.to
陷阱:追踪每一个像素
当我第一次实现这个功能时,掉进了经典的陷阱:在 React 状态中存储精确的 window.scrollY 位置。用户滚动的每一个像素都会触发状态更新,导致组件每秒重新渲染数百次。这是一个巨大的资源消耗,却没有任何必要。
解决方案:阈值状态 & CSS 魔法
我们不需要追踪精确的滚动深度,只关心一件事:用户是否已经滚动超过阈值?(本例中为 300 px)。我们只跟踪一个布尔值,这意味着 React 只会重新渲染两次——一次是按钮出现时,另一次是按钮消失时。
import { useState, useEffect } from 'react';
import './ScrollToTop.css';
const ScrollToTop = () => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const handleScroll = () => {
// 仅在跨过 300px 标记时切换状态
setIsVisible(window.scrollY > 300);
};
// { passive: true } 选项对滚动性能至关重要!
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []); // 空依赖数组确保监听器只挂载一次
const handleClick = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
return (
Scroll to Top ⇑
);
};
export default ScrollToTop;
样式:抛弃 display: none
你无法对在 display: block 与 display: none 之间切换的元素进行动画,它会瞬间出现或消失。为了实现顺滑的淡入淡出,我们使用 opacity 和 visibility,并配合 pointer-events: none,让不可见的按钮不会阻挡底层点击。
.btn {
padding: 12px 20px;
border: none;
border-radius: 8px;
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
background-color: slateblue;
color: white;
cursor: pointer;
/* 通过过渡 opacity 和 visibility 实现平滑淡入淡出 */
transition: opacity 0.4s ease, visibility 0.4s ease, transform 0.3s ease;
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
}
.btn:hover {
background-color: orange;
transform: translateY(-3px);
}
.show {
opacity: 0.85;
visibility: visible;
}
.hide {
opacity: 0;
visibility: hidden;
pointer-events: none; /* 防止不可见时的点击 */
}
为什么这个组件是赢家
- 零拖拽 – 摒弃像素级追踪后,浏览器可以轻松运行。
- 被动监听器 –
{ passive: true }告诉浏览器监听器不会阻止默认的滚动行为,从而保持滚动的极致流畅。 - 可访问且简洁 –
aria-label让屏幕阅读器友好,CSS 动画则提供了高级 UI 体验。
随意把它拎进你的项目!在评论里告诉我你在 React 项目中是如何处理滚动事件的——有没有比标准 useEffect 更喜欢的自定义 Hook?