如何让React阅读进度条滚动更流畅?
文章阅读进度条滚动卡顿问题解决
问题描述
实现了文章阅读进度展示功能,但滚动时进度条卡顿严重,存在明显滞后。当前通过useEffect监听滚动事件计算页面高度占比,将占比作为props传递给styled组件设置宽度,功能可用但卡顿问题突出。
原实现代码
export const SingleArticle = () => { const [readProgress, setReadProgress] = useState(0) useEffect(() => { const calcReadProgress = () => { const { scrollHeight, clientHeight } = document.documentElement const height = scrollHeight - clientHeight setReadProgress((document.documentElement.scrollTop / height) * 100) } window.addEventListener('scroll', calcReadProgress) return () => window.removeEventListener('scroll', calcReadProgress) }, []) return ( <ReadProgressBar progress={readProgress} /> ) } type StyledProps = { progress: number } export const ReadProgressBar = styled.div<StyledProps>` width: 100%; height: 10px; background-color: ${({ theme }) => theme.colors.darkBlue}; position: fixed; top: 0; left: 0; z-index: 10; &::after { content: ''; width: ${({ progress }) => (progress ? `${progress}%` : '0%')}; height: 10px; background-color: ${({ theme }) => theme.colors.purple}; position: absolute; top: 0px; left: 0px; } `
优化方案
通过优化滚动事件处理逻辑、减少不必要的重渲染,解决卡顿问题,优化后的代码如下:
import { useState, useEffect, useRef } from 'react'; import styled from 'styled-components'; export const SingleArticle = () => { const [readProgress, setReadProgress] = useState(0); const isUpdating = useRef(false); const currentProgress = useRef(0); useEffect(() => { const calcReadProgress = () => { if (isUpdating.current) return; isUpdating.current = true; requestAnimationFrame(() => { const { scrollHeight, clientHeight, scrollTop } = document.documentElement; const height = scrollHeight - clientHeight; if (height <= 0) { currentProgress.current = 0; setReadProgress(0); isUpdating.current = false; return; } const newProgress = (scrollTop / height) * 100; // 仅当进度变化超过阈值时更新,减少重渲染 if (Math.abs(newProgress - currentProgress.current) > 0.1) { currentProgress.current = newProgress; setReadProgress(newProgress); } isUpdating.current = false; }); }; // 被动监听滚动事件,提升浏览器滚动性能 window.addEventListener('scroll', calcReadProgress, { passive: true }); return () => window.removeEventListener('scroll', calcReadProgress); }, []); return ( <ReadProgressBar progress={readProgress} /> ); }; type StyledProps = { progress: number; }; export const ReadProgressBar = styled.div<StyledProps>` width: 100%; height: 10px; background-color: ${({ theme }) => theme.colors.darkBlue}; position: fixed; top: 0; left: 0; z-index: 10; &::after { content: ''; width: ${({ progress }) => `${progress}%`}; height: 10px; background-color: ${({ theme }) => theme.colors.purple}; position: absolute; top: 0; left: 0; // 轻量过渡动画提升视觉顺滑度 transition: width 0.1s ease-out; } `;
关键优化点说明
requestAnimationFrame同步更新:将进度计算和DOM更新绑定到浏览器的重绘周期,避免主线程阻塞,让进度条更新与滚动动作同步。- 更新锁避免重复计算:用
isUpdating标记当前是否正在处理进度更新,防止同一帧内多次触发计算,减少冗余操作。 - 阈值过滤减少重渲染:只有当进度变化超过0.1%时才更新组件状态,避免微小变化导致的频繁重渲染。
- 被动滚动监听:设置
{ passive: true }告知浏览器不会阻止默认滚动行为,让浏览器可以优化滚动性能。 - 边界情况处理:当页面内容高度小于视口高度时,直接将进度设为0,避免异常值出现。
- 轻量过渡动画:给进度条宽度变化添加短时长过渡,让视觉效果更顺滑,同时不会引入额外卡顿。
内容的提问来源于stack exchange,提问作者IvonaK
相关产品推荐
相关产品推荐

