From b6345a8cbc04018edf1a969dae2987189b1365c5 Mon Sep 17 00:00:00 2001 From: Vanessa Date: Tue, 21 Oct 2025 10:49:18 +0800 Subject: [PATCH] :recycle: https://github.com/siyuan-note/siyuan/pull/16161 --- app/src/boot/globalEvent/dragover.ts | 48 ++++++++++++++++- app/src/layout/dock/Outline.ts | 78 ++-------------------------- 2 files changed, 49 insertions(+), 77 deletions(-) diff --git a/app/src/boot/globalEvent/dragover.ts b/app/src/boot/globalEvent/dragover.ts index bb29e5163..b9a7fae83 100644 --- a/app/src/boot/globalEvent/dragover.ts +++ b/app/src/boot/globalEvent/dragover.ts @@ -1,3 +1,5 @@ +import {Constants} from "../../constants"; + export const cancelDrag = () => { const ghostElement = document.getElementById("dragGhost"); if (ghostElement) { @@ -18,7 +20,49 @@ export const cancelDrag = () => { } ghostElement.remove(); document.onmousemove = null; - // 通知取消拖拽,供相关模块停止滚动动画等 - window.dispatchEvent(new CustomEvent("drag-cancel")); + stopScrollAnimation(); + } +}; + +const dragoverScroll: { + animationId?: number, + element?: Element, + space?: number // -1 向上;1 向下 + lastTime?: number +} = {}; +export const stopScrollAnimation = () => { + if (dragoverScroll.animationId) { + cancelAnimationFrame(dragoverScroll.animationId); + dragoverScroll.animationId = null; + dragoverScroll.element = null; + dragoverScroll.space = null; + dragoverScroll.lastTime = null; + } +}; +const scrollAnimation = (timestamp: number) => { + if (!dragoverScroll.lastTime) { + dragoverScroll.lastTime = timestamp - 8; + } + dragoverScroll.element.scroll({ + top: dragoverScroll.element.scrollTop + (timestamp - dragoverScroll.lastTime) * dragoverScroll.space / 64 + }); + // 使用 requestAnimationFrame 继续动画 + dragoverScroll.animationId = requestAnimationFrame(scrollAnimation); + dragoverScroll.lastTime = timestamp; +}; + +export const dragOverScroll = (moveEvent: MouseEvent, contentRect: DOMRect, element: Element) => { + const dragToUp = moveEvent.clientY < contentRect.top + Constants.SIZE_SCROLL_TB; + if (dragToUp || + moveEvent.clientY > contentRect.bottom - Constants.SIZE_SCROLL_TB) { + dragoverScroll.space = dragToUp ? moveEvent.clientY - contentRect.top - Constants.SIZE_SCROLL_TB : + moveEvent.clientY - contentRect.bottom + Constants.SIZE_SCROLL_TB; + if (!dragoverScroll.animationId) { + dragoverScroll.element = element; + dragoverScroll.animationId = requestAnimationFrame(scrollAnimation); + } + } else { + // 离开滚动区域时停止滚动 + stopScrollAnimation(); } }; diff --git a/app/src/layout/dock/Outline.ts b/app/src/layout/dock/Outline.ts index f30c4081f..6499da919 100644 --- a/app/src/layout/dock/Outline.ts +++ b/app/src/layout/dock/Outline.ts @@ -27,6 +27,7 @@ import {Editor} from "../../editor"; import {mathRender} from "../../protyle/render/mathRender"; import {genEmptyElement} from "../../block/util"; import {focusBlock, focusByWbr} from "../../protyle/util/selection"; +import {dragOverScroll, stopScrollAnimation} from "../../boot/globalEvent/dragover"; export class Outline extends Model { public tree: Tree; @@ -36,9 +37,6 @@ export class Outline extends Model { public blockId: string; public isPreview: boolean; private preFilterExpandIds: string[] | null = null; - private scrollAnimationId: number | null = null; - private scrollLastFrameTime: number = 0; - private scrollCurrentFPS: number = 60; constructor(options: { app: App, @@ -334,21 +332,6 @@ export class Outline extends Model { }, response => { this.update(response); }); - - window.addEventListener("drag-cancel", () => { - this.stopScrollAnimation(); - }); - } - - private stopScrollAnimation() { - if (this.scrollAnimationId) { - if (typeof cancelAnimationFrame !== "undefined") { - cancelAnimationFrame(this.scrollAnimationId); - } else { - clearTimeout(this.scrollAnimationId); - } - this.scrollAnimationId = null; - } } private bindSort() { @@ -386,62 +369,7 @@ export class Outline extends Model { } ghostElement.style.top = moveEvent.clientY + "px"; ghostElement.style.left = moveEvent.clientX + "px"; - // 检查是否在滚动边界区域 - if (moveEvent.clientY < contentRect.top + Constants.SIZE_SCROLL_TB || moveEvent.clientY > contentRect.bottom - Constants.SIZE_SCROLL_TB) { - // 如果还没有开始滚动,则开始持续滚动 - if (!this.scrollAnimationId) { - const scrollDirection = moveEvent.clientY < contentRect.top + Constants.SIZE_SCROLL_TB ? -1 : 1; - this.scrollLastFrameTime = performance.now(); - let scrollFrameCount = 0; - - const scrollAnimation = (currentTime: number) => { - if (!this.scrollAnimationId) { - return; - } - - // 每隔 20 帧重新计算一次帧率 - if (scrollFrameCount % 20 === 0) { - const deltaTime = currentTime - this.scrollLastFrameTime; - this.scrollLastFrameTime = currentTime; - // 计算过去 20 帧的平均帧率 - this.scrollCurrentFPS = deltaTime > 0 ? (20 * 1000) / deltaTime : 60; - } - scrollFrameCount++; - - // 基于当前帧率计算滚动步长,确保等效于 60fps 时的 16px/帧 - const baseScrollStep = 16; - const targetFPS = 60; - const scrollStep = baseScrollStep * (targetFPS / this.scrollCurrentFPS); - - this.element.scroll({ - top: this.element.scrollTop + scrollStep * scrollDirection - }); - - // 使用 requestAnimationFrame 继续动画 - this.scrollAnimationId = requestAnimationFrame(scrollAnimation); - }; - - // 检查浏览器是否支持 requestAnimationFrame - if (typeof requestAnimationFrame !== "undefined") { - this.scrollAnimationId = requestAnimationFrame(scrollAnimation); - } else { - // 回退到 setTimeout 方法 - const scrollInterval = 16; // 约 60fps - const scrollStep = 16; // 每次滚动的距离 - - const scrollAnimationFallback = () => { - this.element.scroll({ - top: this.element.scrollTop + scrollStep * scrollDirection - }); - this.scrollAnimationId = window.setTimeout(scrollAnimationFallback, scrollInterval); - }; - this.scrollAnimationId = window.setTimeout(scrollAnimationFallback, scrollInterval); - } - } - } else { - // 离开滚动区域时停止滚动 - this.stopScrollAnimation(); - } + dragOverScroll(moveEvent, contentRect, this.element); if (!this.element.contains(moveEvent.target as Element)) { this.element.querySelectorAll(".dragover__top, .dragover__bottom, .dragover, .dragover__current").forEach(item => { item.classList.remove("dragover__top", "dragover__bottom", "dragover", "dragover__current"); @@ -479,7 +407,7 @@ export class Outline extends Model { ghostElement?.remove(); item.style.opacity = ""; // 清理滚动动画 - this.stopScrollAnimation(); + stopScrollAnimation(); if (!selectItem) { selectItem = this.element.querySelector(".dragover__top, .dragover__bottom, .dragover"); }