From aabe685c48db98c9209cf5363e7a54034795c14a Mon Sep 17 00:00:00 2001 From: cirry <812852553@qq.com> Date: Wed, 8 Jul 2026 14:59:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E5=BC=8F=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MarkdownEditor.vue | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/components/MarkdownEditor.vue b/src/components/MarkdownEditor.vue index dac924f..872eb5b 100644 --- a/src/components/MarkdownEditor.vue +++ b/src/components/MarkdownEditor.vue @@ -89,6 +89,61 @@ const isEditingEmptyDocument = computed( () => editingBlockId.value === EMPTY_BLOCK_ID, ); let textareaResizeFrame: number | null = null; +let modeScrollRestoreFrame: number | null = null; + +interface ScrollSnapshot { + ratio: number; +} + +function getScrollableDistance(el: HTMLElement) { + return Math.max(0, el.scrollHeight - el.clientHeight); +} + +function captureScrollSnapshot(): ScrollSnapshot | null { + const el = scrollRef.value; + if (!el) return null; + + const maxScrollTop = getScrollableDistance(el); + return { + ratio: maxScrollTop > 0 ? el.scrollTop / maxScrollTop : 0, + }; +} + +function restoreScrollSnapshot(snapshot: ScrollSnapshot | null) { + const el = scrollRef.value; + if (!el || !snapshot) return; + + const maxScrollTop = getScrollableDistance(el); + el.scrollTop = maxScrollTop * snapshot.ratio; +} + +function restoreScrollAfterModeChange( + snapshot: ScrollSnapshot | null, + mode: DocumentMode, +) { + if (!snapshot) return; + + if (modeScrollRestoreFrame !== null) { + cancelAnimationFrame(modeScrollRestoreFrame); + modeScrollRestoreFrame = null; + } + + void nextTick(() => { + if (mode === "edit" && editorTextareaRef.value) { + resizeTextarea(editorTextareaRef.value); + } + + restoreScrollSnapshot(snapshot); + + modeScrollRestoreFrame = requestAnimationFrame(() => { + modeScrollRestoreFrame = null; + if (mode === "edit" && editorTextareaRef.value) { + resizeTextarea(editorTextareaRef.value); + } + restoreScrollSnapshot(snapshot); + }); + }); +} function syncRenderedState(md: string, mode = activeDocumentMode.value) { if (mode === "preview") { @@ -455,6 +510,7 @@ function flushPendingChanges(): string | undefined { } watch(activeDocumentMode, (mode, oldMode) => { + const scrollSnapshot = captureScrollSnapshot(); let committedValue: string | undefined; if (oldMode === "mixed" && mode !== "mixed" && editingBlockId.value) { committedValue = finalizeBlockEdit(); @@ -465,12 +521,17 @@ watch(activeDocumentMode, (mode, oldMode) => { if (mode === "edit") { void nextTick(scheduleEditorTextareaResize); } + + restoreScrollAfterModeChange(scrollSnapshot, mode); }); onBeforeUnmount(() => { if (textareaResizeFrame !== null) { cancelAnimationFrame(textareaResizeFrame); } + if (modeScrollRestoreFrame !== null) { + cancelAnimationFrame(modeScrollRestoreFrame); + } }); defineExpose({