From 914f168551a68d2ae0afda5a86851d83dd1fe5a4 Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 16 Jun 2026 15:53:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(editor):=20=E5=88=87=E6=8D=A2=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E6=97=B6=E6=8C=89=E6=BB=9A=E5=8A=A8=E6=AF=94=E4=BE=8B?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复切回富文本时编辑器滚动到底部的问题:移除 commands.focus() (会强制滚动到光标位置),改用滚动比例跨模式同步,富文本与源码 视图按相同比例定位,保持视觉位置一致。 --- libs/tiptap-editor/src/index.ts | 36 ++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/libs/tiptap-editor/src/index.ts b/libs/tiptap-editor/src/index.ts index dd7acf5..3a7f5d5 100644 --- a/libs/tiptap-editor/src/index.ts +++ b/libs/tiptap-editor/src/index.ts @@ -163,6 +163,8 @@ class TiptapEditorInstance { this.sourceTextarea.value = this.editor.getMarkdown() proseMirrorDom.style.display = 'none' this.sourceTextarea.hidden = false + // 按滚动比例同步到源码视图,保持视觉位置一致 + this.syncScrollRatio(proseMirrorDom, this.sourceTextarea) this.sourceTextarea.focus() this.toggleButton.textContent = '✎' this.toggleButton.title = '切换富文本' @@ -170,16 +172,48 @@ class TiptapEditorInstance { } else { // 源码 → 富文本:把 textarea 内容回填到编辑器 const md = this.sourceTextarea.value + // 先记录源码视图的滚动比例(setContent 会重建文档,必须在替换前拿到比例) + const sourceRatio = this.getScrollRatio(this.sourceTextarea) this.setMarkdown(md) this.sourceTextarea.hidden = true proseMirrorDom.style.display = '' + // 等待 DOM 布局更新后,按比例同步富文本视图滚动位置 + requestAnimationFrame(() => { + this.applyScrollRatio(proseMirrorDom, sourceRatio) + }) this.toggleButton.textContent = '' this.toggleButton.title = '切换 Markdown 源码' this.isSourceMode = false - this.editor.commands.focus() + // 注意:不调用 editor.commands.focus(),它会强制滚动到光标位置(默认文档末尾),破坏比例同步 } } + /** + * 读取可滚动容器的滚动比例(0~1)。 + * 比例 = scrollTop / 可滚动总距离,避免两种模式内容高度不同导致像素无法直接对应。 + */ + private getScrollRatio(el: HTMLElement): number { + const max = el.scrollHeight - el.clientHeight + if (max <= 0) return 0 + return el.scrollTop / max + } + + /** + * 按比例设置目标容器的滚动位置。 + */ + private applyScrollRatio(el: HTMLElement, ratio: number): void { + const max = el.scrollHeight - el.clientHeight + if (max <= 0) return + el.scrollTop = max * ratio + } + + /** + * 便捷封装:从源容器读取比例并应用到目标容器。 + */ + private syncScrollRatio(from: HTMLElement, to: HTMLElement): void { + this.applyScrollRatio(to, this.getScrollRatio(from)) + } + setMarkdown(content: string): void { this.editor?.commands.setContent(content, { emitUpdate: false, contentType: 'markdown' }) }