@@ -36,6 +36,9 @@ export class Outline extends Model {
3636 public blockId : string ;
3737 public isPreview : boolean ;
3838 private preFilterExpandIds : string [ ] | null = null ;
39+ private scrollAnimationId : number | null = null ;
40+ private scrollLastFrameTime : number = 0 ;
41+ private scrollCurrentFPS : number = 60 ;
3942
4043 constructor ( options : {
4144 app : App ,
@@ -374,11 +377,68 @@ export class Outline extends Model {
374377 } ) ;
375378 return ;
376379 }
380+ // 检查是否在滚动边界区域
377381 if ( moveEvent . clientY < contentRect . top + Constants . SIZE_SCROLL_TB || moveEvent . clientY > contentRect . bottom - Constants . SIZE_SCROLL_TB ) {
378- this . element . scroll ( {
379- top : this . element . scrollTop + ( moveEvent . clientY < contentRect . top + Constants . SIZE_SCROLL_TB ? - Constants . SIZE_SCROLL_STEP : Constants . SIZE_SCROLL_STEP ) ,
380- behavior : "smooth"
381- } ) ;
382+ // 如果还没有开始滚动,则开始持续滚动
383+ if ( ! this . scrollAnimationId ) {
384+ const scrollDirection = moveEvent . clientY < contentRect . top + Constants . SIZE_SCROLL_TB ? - 1 : 1 ;
385+ this . scrollLastFrameTime = performance . now ( ) ;
386+ let scrollFrameCount = 0 ;
387+
388+ const scrollAnimation = ( currentTime : number ) => {
389+ if ( ! this . scrollAnimationId ) {
390+ return ;
391+ }
392+
393+ // 每隔 20 帧重新计算一次帧率
394+ if ( scrollFrameCount % 20 === 0 ) {
395+ const deltaTime = currentTime - this . scrollLastFrameTime ;
396+ this . scrollLastFrameTime = currentTime ;
397+ // 计算过去 20 帧的平均帧率
398+ this . scrollCurrentFPS = deltaTime > 0 ? ( 20 * 1000 ) / deltaTime : 60 ;
399+ }
400+ scrollFrameCount ++ ;
401+
402+ // 基于当前帧率计算滚动步长,确保等效于 60fps 时的 16px/帧
403+ const baseScrollStep = 16 ;
404+ const targetFPS = 60 ;
405+ const scrollStep = baseScrollStep * ( targetFPS / this . scrollCurrentFPS ) ;
406+
407+ this . element . scroll ( {
408+ top : this . element . scrollTop + scrollStep * scrollDirection
409+ } ) ;
410+
411+ // 使用 requestAnimationFrame 继续动画
412+ this . scrollAnimationId = requestAnimationFrame ( scrollAnimation ) ;
413+ } ;
414+
415+ // 检查浏览器是否支持 requestAnimationFrame
416+ if ( typeof requestAnimationFrame !== "undefined" ) {
417+ this . scrollAnimationId = requestAnimationFrame ( scrollAnimation ) ;
418+ } else {
419+ // 回退到 setTimeout 方法
420+ const scrollInterval = 16 ; // 约 60fps
421+ const scrollStep = 16 ; // 每次滚动的距离
422+
423+ const scrollAnimationFallback = ( ) => {
424+ this . element . scroll ( {
425+ top : this . element . scrollTop + scrollStep * scrollDirection
426+ } ) ;
427+ this . scrollAnimationId = window . setTimeout ( scrollAnimationFallback , scrollInterval ) ;
428+ } ;
429+ this . scrollAnimationId = window . setTimeout ( scrollAnimationFallback , scrollInterval ) ;
430+ }
431+ }
432+ } else {
433+ // 离开滚动区域时停止滚动
434+ if ( this . scrollAnimationId ) {
435+ if ( typeof cancelAnimationFrame !== "undefined" ) {
436+ cancelAnimationFrame ( this . scrollAnimationId ) ;
437+ } else {
438+ clearTimeout ( this . scrollAnimationId ) ;
439+ }
440+ this . scrollAnimationId = null ;
441+ }
382442 }
383443 selectItem = hasClosestByClassName ( moveEvent . target as HTMLElement , "b3-list-item" ) as HTMLElement ;
384444 if ( ! selectItem || selectItem . tagName !== "LI" || selectItem . style . position === "fixed" ) {
@@ -410,6 +470,15 @@ export class Outline extends Model {
410470 documentSelf . onselect = null ;
411471 ghostElement ?. remove ( ) ;
412472 item . style . opacity = "" ;
473+ // 清理滚动动画
474+ if ( this . scrollAnimationId ) {
475+ if ( typeof cancelAnimationFrame !== "undefined" ) {
476+ cancelAnimationFrame ( this . scrollAnimationId ) ;
477+ } else {
478+ clearTimeout ( this . scrollAnimationId ) ;
479+ }
480+ this . scrollAnimationId = null ;
481+ }
413482 if ( ! selectItem ) {
414483 selectItem = this . element . querySelector ( ".dragover__top, .dragover__bottom, .dragover" ) ;
415484 }
0 commit comments