@@ -323,21 +323,64 @@ const isMultipleOf = (a: number, b: number) => {
323323 return ! decimal || decimal . startsWith ( '0000' ) || decimal . startsWith ( '9999' ) ;
324324} ;
325325
326+ const normalizeWheelDeltaY = ( e : WheelEvent ) => {
327+ switch ( e . deltaMode ) {
328+ // 统一换算为近似像素距离,便于累计滚动量
329+ case WheelEvent . DOM_DELTA_LINE :
330+ return e . deltaY * 16 ;
331+ case WheelEvent . DOM_DELTA_PAGE :
332+ return e . deltaY * window . innerHeight ;
333+ default :
334+ return e . deltaY ;
335+ }
336+ } ;
337+
326338let lastDeltaY = - 1 ;
327339let timeoutId = 0 ;
328340let lastPageNum = - 1 ;
329- let wheelType : undefined | 'trackpad' | 'mouse' ;
341+ let wheelType : undefined | 'trackpad' | 'mouse' | 'highPrecisionWheel' ;
330342let equalNum = 0 ;
331343let diffNum = 0 ;
344+ let highPrecisionWheelDeltaY = 0 ;
345+ let highPrecisionWheelResetId = 0 ;
346+
347+ const highPrecisionWheelPageThreshold = 80 ;
348+ const highPrecisionWheelResetDelay = 100 ;
349+
350+ // 高精度滚轮按累计位移翻页,避免一次滚动触发多个 wheel 事件导致连翻
351+ const handleHighPrecisionWheel = ( deltaY : number , dir : 'next' | 'prev' ) => {
352+ if ( highPrecisionWheelResetId ) clearTimeout ( highPrecisionWheelResetId ) ;
353+ highPrecisionWheelResetId = window . setTimeout ( ( ) => {
354+ highPrecisionWheelDeltaY = 0 ;
355+ } , highPrecisionWheelResetDelay ) ;
356+
357+ if ( highPrecisionWheelDeltaY * deltaY < 0 ) highPrecisionWheelDeltaY = 0 ;
358+ highPrecisionWheelDeltaY += deltaY ;
359+
360+ if ( Math . abs ( highPrecisionWheelDeltaY ) < highPrecisionWheelPageThreshold )
361+ return ;
362+
363+ // 翻页后清空余量,避免单次滚动的尾部事件连翻
364+ highPrecisionWheelDeltaY = 0 ;
365+ if ( ! turnPage ( dir ) ) return false ;
366+ openScrollLock ( ) ;
367+ return true ;
368+ } ;
332369
333370export const handleWheel = ( e : WheelEvent ) => {
334371 if ( store . gridMode ) return ;
335372 e . stopPropagation ( ) ;
336373 if ( e . ctrlKey || e . altKey ) e . preventDefault ( ) ;
337374
338- const isWheelDown = e . deltaY > 0 ;
375+ const normalizedDeltaY = normalizeWheelDeltaY ( e ) ;
376+ const isWheelDown = normalizedDeltaY > 0 ;
339377 const dir = isWheelDown ? 'next' : 'prev' ;
340378 const absDeltaY = Math . abs ( e . deltaY ) ;
379+ const isHighPrecisionWheelEvent =
380+ e . deltaMode === WheelEvent . DOM_DELTA_PIXEL &&
381+ Number . isInteger ( e . deltaY ) &&
382+ absDeltaY >= 5 &&
383+ absDeltaY < 100 ;
341384
342385 // 通过`两次滚动距离是否成倍数`和`滚动距离是否过小`来判断是否是触摸板
343386 if (
@@ -377,15 +420,15 @@ export const handleWheel = (e: WheelEvent) => {
377420 // 并排卷轴模式下
378421 if ( isAbreastMode ( ) && store . option . zoom . ratio === 100 ) {
379422 e . preventDefault ( ) ;
380- scrollBy ( e . deltaY , true ) ;
423+ scrollBy ( normalizedDeltaY , true ) ;
381424 }
382425
383426 // 防止滚动到网页
384427 if ( ! isScrollMode ( ) ) e . preventDefault ( ) ;
385428
386429 // 为了避免因临时卡顿而误判为触摸板
387430 // 在连续几次滚动量均相同的情况下,将 wheelType 相关变量重置回初始状态
388- if ( diffNum < 10 ) {
431+ if ( diffNum < 10 && wheelType !== 'highPrecisionWheel' ) {
389432 if ( lastDeltaY === absDeltaY && absDeltaY > 5 ) equalNum += 1 ;
390433 else {
391434 diffNum += 1 ;
@@ -406,16 +449,29 @@ export const handleWheel = (e: WheelEvent) => {
406449 // 第一次触发滚动没法判断类型,就当作滚轮来处理
407450 // 但为了避免触摸板前两次滚动事件间隔大于帧生成时间导致得重新翻页回去的闪烁,加个延迟等待下
408451 lastPageNum = store . activePageIndex ;
452+ highPrecisionWheelDeltaY = normalizedDeltaY ;
409453 timeoutId = window . setTimeout ( turnPage , 16 , dir ) ;
410454 return ;
411455 }
456+
457+ // 高精度滚轮通常是像素单位的小段滚动,按累计位移处理
458+ if ( isHighPrecisionWheelEvent ) {
459+ if ( timeoutId ) clearTimeout ( timeoutId ) ;
460+ if ( lastPageNum !== - 1 ) setState ( 'activePageIndex' , lastPageNum ) ;
461+ wheelType = 'highPrecisionWheel' ;
462+ return handleHighPrecisionWheel ( normalizedDeltaY , dir ) ;
463+ }
464+
412465 wheelType = 'mouse' ;
413466 }
414467 // falls through
415468
416469 case 'mouse' :
417470 return turnPage ( dir ) ;
418471
472+ case 'highPrecisionWheel' :
473+ return handleHighPrecisionWheel ( normalizedDeltaY , dir ) ;
474+
419475 case 'trackpad' :
420476 return handleTrackpadWheel ( e ) ;
421477 }
0 commit comments