@@ -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 ,
@@ -331,6 +334,21 @@ export class Outline extends Model {
331334 } , response => {
332335 this . update ( response ) ;
333336 } ) ;
337+
338+ window . addEventListener ( "drag-cancel" , ( ) => {
339+ this . stopScrollAnimation ( ) ;
340+ } ) ;
341+ }
342+
343+ private stopScrollAnimation ( ) {
344+ if ( this . scrollAnimationId ) {
345+ if ( typeof cancelAnimationFrame !== "undefined" ) {
346+ cancelAnimationFrame ( this . scrollAnimationId ) ;
347+ } else {
348+ clearTimeout ( this . scrollAnimationId ) ;
349+ }
350+ this . scrollAnimationId = null ;
351+ }
334352 }
335353
336354 private bindSort ( ) {
@@ -368,18 +386,68 @@ export class Outline extends Model {
368386 }
369387 ghostElement . style . top = moveEvent . clientY + "px" ;
370388 ghostElement . style . left = moveEvent . clientX + "px" ;
389+ // 检查是否在滚动边界区域
390+ if ( moveEvent . clientY < contentRect . top + Constants . SIZE_SCROLL_TB || moveEvent . clientY > contentRect . bottom - Constants . SIZE_SCROLL_TB ) {
391+ // 如果还没有开始滚动,则开始持续滚动
392+ if ( ! this . scrollAnimationId ) {
393+ const scrollDirection = moveEvent . clientY < contentRect . top + Constants . SIZE_SCROLL_TB ? - 1 : 1 ;
394+ this . scrollLastFrameTime = performance . now ( ) ;
395+ let scrollFrameCount = 0 ;
396+
397+ const scrollAnimation = ( currentTime : number ) => {
398+ if ( ! this . scrollAnimationId ) {
399+ return ;
400+ }
401+
402+ // 每隔 20 帧重新计算一次帧率
403+ if ( scrollFrameCount % 20 === 0 ) {
404+ const deltaTime = currentTime - this . scrollLastFrameTime ;
405+ this . scrollLastFrameTime = currentTime ;
406+ // 计算过去 20 帧的平均帧率
407+ this . scrollCurrentFPS = deltaTime > 0 ? ( 20 * 1000 ) / deltaTime : 60 ;
408+ }
409+ scrollFrameCount ++ ;
410+
411+ // 基于当前帧率计算滚动步长,确保等效于 60fps 时的 16px/帧
412+ const baseScrollStep = 16 ;
413+ const targetFPS = 60 ;
414+ const scrollStep = baseScrollStep * ( targetFPS / this . scrollCurrentFPS ) ;
415+
416+ this . element . scroll ( {
417+ top : this . element . scrollTop + scrollStep * scrollDirection
418+ } ) ;
419+
420+ // 使用 requestAnimationFrame 继续动画
421+ this . scrollAnimationId = requestAnimationFrame ( scrollAnimation ) ;
422+ } ;
423+
424+ // 检查浏览器是否支持 requestAnimationFrame
425+ if ( typeof requestAnimationFrame !== "undefined" ) {
426+ this . scrollAnimationId = requestAnimationFrame ( scrollAnimation ) ;
427+ } else {
428+ // 回退到 setTimeout 方法
429+ const scrollInterval = 16 ; // 约 60fps
430+ const scrollStep = 16 ; // 每次滚动的距离
431+
432+ const scrollAnimationFallback = ( ) => {
433+ this . element . scroll ( {
434+ top : this . element . scrollTop + scrollStep * scrollDirection
435+ } ) ;
436+ this . scrollAnimationId = window . setTimeout ( scrollAnimationFallback , scrollInterval ) ;
437+ } ;
438+ this . scrollAnimationId = window . setTimeout ( scrollAnimationFallback , scrollInterval ) ;
439+ }
440+ }
441+ } else {
442+ // 离开滚动区域时停止滚动
443+ this . stopScrollAnimation ( ) ;
444+ }
371445 if ( ! this . element . contains ( moveEvent . target as Element ) ) {
372446 this . element . querySelectorAll ( ".dragover__top, .dragover__bottom, .dragover, .dragover__current" ) . forEach ( item => {
373447 item . classList . remove ( "dragover__top" , "dragover__bottom" , "dragover" , "dragover__current" ) ;
374448 } ) ;
375449 return ;
376450 }
377- 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- }
383451 selectItem = hasClosestByClassName ( moveEvent . target as HTMLElement , "b3-list-item" ) as HTMLElement ;
384452 if ( ! selectItem || selectItem . tagName !== "LI" || selectItem . style . position === "fixed" ) {
385453 return ;
@@ -410,6 +478,8 @@ export class Outline extends Model {
410478 documentSelf . onselect = null ;
411479 ghostElement ?. remove ( ) ;
412480 item . style . opacity = "" ;
481+ // 清理滚动动画
482+ this . stopScrollAnimation ( ) ;
413483 if ( ! selectItem ) {
414484 selectItem = this . element . querySelector ( ".dragover__top, .dragover__bottom, .dragover" ) ;
415485 }
0 commit comments