1- import { useRef , useCallback , useState } from 'react' ;
1+ import { useRef , useCallback , useState , useEffect } from 'react' ;
22
33/**
44 * 滚动数据点接口
@@ -59,6 +59,7 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
5959 scrollEndDelay = 150 ,
6060 fastScrollThreshold = 0.3
6161 } = options ;
62+ const speedEpsilon = 0.001 ;
6263
6364 // 滚动状态
6465 const [ isScrolling , setIsScrolling ] = useState ( false ) ;
@@ -68,11 +69,28 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
6869 const scrollDataRef = useRef < {
6970 positions : ScrollDataPoint [ ] ;
7071 timer : NodeJS . Timeout | null ;
72+ rafId : number | null ;
73+ isScrolling : boolean ;
74+ speed : number ;
7175 } > ( {
7276 positions : [ ] ,
73- timer : null
77+ timer : null ,
78+ rafId : null ,
79+ isScrolling : false ,
80+ speed : 0
7481 } ) ;
7582
83+ const recordSample = useCallback ( ( offset : number ) : void => {
84+ const now = Date . now ( ) ;
85+ const data = scrollDataRef . current ;
86+
87+ data . positions . push ( { offset, time : now } ) ;
88+
89+ if ( data . positions . length > maxSamples ) {
90+ data . positions . shift ( ) ;
91+ }
92+ } , [ maxSamples ] ) ;
93+
7694 /**
7795 * 计算滚动速度
7896 *
@@ -81,18 +99,9 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
8199 * @param offset - 当前滚动偏移量
82100 * @returns 标准化的滚动速度(像素/毫秒)
83101 */
84- const calculateSpeed = useCallback ( ( offset : number ) : number => {
85- const now = Date . now ( ) ;
102+ const calculateSpeed = useCallback ( ( ) : number => {
86103 const data = scrollDataRef . current ;
87-
88- // 添加新样本
89- data . positions . push ( { offset, time : now } ) ;
90-
91- // 保持固定数量的样本(FIFO)
92- if ( data . positions . length > maxSamples ) {
93- data . positions . shift ( ) ;
94- }
95-
104+
96105 // 需要至少2个样本才能计算速度
97106 if ( data . positions . length < 2 ) {
98107 return 0 ;
@@ -111,36 +120,50 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
111120
112121 // 避免除以零
113122 return time > 0 ? distance / time : 0 ;
114- } , [ maxSamples ] ) ;
123+ } , [ ] ) ;
115124
116125 /**
117126 * 处理滚动事件
118127 *
119128 * @param scrollOffset - 滚动偏移量
120129 */
121130 const handleScroll = useCallback ( ( scrollOffset : number ) : void => {
131+ const data = scrollDataRef . current ;
132+ recordSample ( scrollOffset ) ;
133+
122134 // 设置滚动状态
123- if ( ! isScrolling ) {
135+ if ( ! data . isScrolling ) {
136+ data . isScrolling = true ;
124137 setIsScrolling ( true ) ;
125138 }
126139
127- // 计算滚动速度
128- const speed = calculateSpeed ( scrollOffset ) ;
129- setScrollSpeed ( speed ) ;
140+ // rAF 合并状态更新,避免每次 scroll 事件触发 setState
141+ data . rafId ??= requestAnimationFrame ( ( ) => {
142+ data . rafId = null ;
143+ const speed = calculateSpeed ( ) ;
144+ if ( Math . abs ( speed - data . speed ) >= speedEpsilon ) {
145+ data . speed = speed ;
146+ setScrollSpeed ( speed ) ;
147+ }
148+ } ) ;
130149
131150 // 清除之前的定时器
132- if ( scrollDataRef . current . timer !== null ) {
133- clearTimeout ( scrollDataRef . current . timer ) ;
151+ if ( data . timer !== null ) {
152+ clearTimeout ( data . timer ) ;
134153 }
135154
136155 // 设置新的定时器,检测滚动停止
137- scrollDataRef . current . timer = setTimeout ( ( ) => {
156+ data . timer = setTimeout ( ( ) => {
157+ data . isScrolling = false ;
158+ if ( data . speed !== 0 ) {
159+ data . speed = 0 ;
160+ setScrollSpeed ( 0 ) ;
161+ }
138162 setIsScrolling ( false ) ;
139- setScrollSpeed ( 0 ) ;
140163 // 清空样本数据
141- scrollDataRef . current . positions = [ ] ;
164+ data . positions = [ ] ;
142165 } , scrollEndDelay ) ;
143- } , [ isScrolling , calculateSpeed , scrollEndDelay ] ) ;
166+ } , [ calculateSpeed , recordSample , scrollEndDelay , speedEpsilon ] ) ;
144167
145168 /**
146169 * 检查是否为快速滚动
@@ -151,15 +174,35 @@ export function useOptimizedScroll(options: ScrollSpeedOptions = {}): {
151174 * 重置滚动状态
152175 */
153176 const reset = useCallback ( ( ) => {
177+ const data = scrollDataRef . current ;
154178 setIsScrolling ( false ) ;
155179 setScrollSpeed ( 0 ) ;
156- scrollDataRef . current . positions = [ ] ;
180+ data . isScrolling = false ;
181+ data . speed = 0 ;
182+ data . positions = [ ] ;
157183
158- if ( scrollDataRef . current . timer !== null ) {
159- clearTimeout ( scrollDataRef . current . timer ) ;
160- scrollDataRef . current . timer = null ;
184+ if ( data . timer !== null ) {
185+ clearTimeout ( data . timer ) ;
186+ data . timer = null ;
187+ }
188+
189+ if ( data . rafId !== null ) {
190+ cancelAnimationFrame ( data . rafId ) ;
191+ data . rafId = null ;
161192 }
162193 } , [ ] ) ;
194+
195+ useEffect ( ( ) => {
196+ const data = scrollDataRef . current ;
197+ return ( ) => {
198+ if ( data . timer !== null ) {
199+ clearTimeout ( data . timer ) ;
200+ }
201+ if ( data . rafId !== null ) {
202+ cancelAnimationFrame ( data . rafId ) ;
203+ }
204+ } ;
205+ } , [ ] ) ;
163206
164207 return {
165208 /** 是否正在滚动 */
0 commit comments