fix(outline): Typora 式瞬时跳转——smooth scroll 在导航场景是反模式(慢+与 ProseMirror scrollIntoView 冲突). 参考 Typora 设计: 1.瞬时设置 scrollEl.scrollTop=targetTop, 不用 behavior:smooth; 2.setTextSelection 设光标(不触发滚动) + view.focus() 直接聚焦(跳过 editor.commands.focus 的 scrollIntoView); 3.闪烁高亮放最后. 一击即达, 无需二次点击, 无冲突卡顿
This commit is contained in:
@@ -64,7 +64,8 @@ export function OutlinePanel() {
|
|||||||
return () => { scrollEl!.removeEventListener('scroll', onScroll); };
|
return () => { scrollEl!.removeEventListener('scroll', onScroll); };
|
||||||
}, [editor, headings]);
|
}, [editor, headings]);
|
||||||
|
|
||||||
// FR-O2:滚动到标题——先滚后选,避免 ProseMirror focus scroll 覆盖手动滚动
|
// FR-O2:Typora 式大纲跳转——瞬时定位 + 光标设置 + 闪烁高亮
|
||||||
|
// 不用 smooth scroll(导航场景应瞬时跳转),不用 editor.commands.focus(会触发 scrollIntoView)
|
||||||
const scrollTo = useCallback((h: HeadingItem) => {
|
const scrollTo = useCallback((h: HeadingItem) => {
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
const el = editor.view.nodeDOM(h.pos) as HTMLElement | null;
|
const el = editor.view.nodeDOM(h.pos) as HTMLElement | null;
|
||||||
@@ -75,22 +76,22 @@ export function OutlinePanel() {
|
|||||||
scrollEl = scrollEl.parentElement;
|
scrollEl = scrollEl.parentElement;
|
||||||
}
|
}
|
||||||
if (scrollEl) {
|
if (scrollEl) {
|
||||||
// 先计算目标位置(此时 rect 还未被 focus scroll 干扰)
|
// 计算目标位置——标题紧贴容器顶部
|
||||||
const elRect = el.getBoundingClientRect();
|
const elRect = el.getBoundingClientRect();
|
||||||
const containerRect = scrollEl.getBoundingClientRect();
|
const containerRect = scrollEl.getBoundingClientRect();
|
||||||
const targetTop = Math.max(0, elRect.top - containerRect.top + scrollEl.scrollTop);
|
const targetTop = Math.max(0, elRect.top - containerRect.top + scrollEl.scrollTop);
|
||||||
// 先设置光标但不 focus(不触发 ProseMirror 滚动)
|
// 瞬时跳转(不用 smooth scroll——导航操作应该立即到位)
|
||||||
editor.commands.setTextSelection(h.pos + 1);
|
scrollEl.scrollTop = targetTop;
|
||||||
// 平滑滚动——必须在 focus 之前执行,否则 ProseMirror 的 scrollIntoView 会覆盖
|
|
||||||
scrollEl.scrollTo({ top: targetTop, behavior: 'smooth' });
|
|
||||||
}
|
}
|
||||||
// 闪烁高亮
|
// 设置光标到标题位置——setTextSelection 不触发滚动
|
||||||
|
editor.commands.setTextSelection(h.pos + 1);
|
||||||
|
// focus 但不触发 ProseMirror scrollIntoView——直接调 view.focus()
|
||||||
|
editor.view.focus();
|
||||||
|
// 闪烁高亮(放在最后,确保滚动已完成)
|
||||||
el.classList.remove('outline-flash');
|
el.classList.remove('outline-flash');
|
||||||
void el.offsetWidth;
|
void el.offsetWidth;
|
||||||
el.classList.add('outline-flash');
|
el.classList.add('outline-flash');
|
||||||
setTimeout(() => el.classList.remove('outline-flash'), 1500);
|
setTimeout(() => el.classList.remove('outline-flash'), 1500);
|
||||||
// focus 放到下一帧,让 smooth scroll 先启动
|
|
||||||
requestAnimationFrame(() => editor.commands.focus());
|
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
// FR-O4:未打开文档时显示占位
|
// FR-O4:未打开文档时显示占位
|
||||||
|
|||||||
Reference in New Issue
Block a user