Markdown编辑器如何实时预览并保持滚动同步?

Code°可欣 阅读 5

我用React写了个简单的Markdown编辑器,左边是textarea输入,右边用marked.parse()转成HTML预览。现在问题是:输入内容多了以后,两边滚动位置完全不一致,用户体验很差。

我试过监听textarea的scroll事件去同步右边容器的scrollTop,但右边是渲染后的HTML,高度和左边字符数没法直接对应,根本对不上。有没有靠谱的方案能实现类似Typora那种滚动同步效果?

我来解答 赞 2 收藏
二维码
手机扫码查看
1 条解答
书生シ芳芳
核心思路是用textarea当前光标位置的行号比例,去同步预览区的滚动位置。

代码实现:

const MarkdownEditor = () => {
const textareaRef = useRef(null);
const previewRef = useRef(null);
const isSyncing = useRef(false);

// 获取光标所在的行号
const getCursorLineNumber = (textarea) => {
const text = textarea.value.substring(0, textarea.selectionStart);
return text.split('n').length; };

// 计算总行数
const getTotalLines = (text) => {
return text.split('n').length;
};

const handleScroll = () => {
if (isSyncing.current) return;

const textarea = textareaRef.current;
const preview = previewRef.current;

const cursorLine = getCursorLineNumber(textarea);
const totalLines = getTotalLines(textarea.value);

// 关键:用行号比例来同步,而不是直接用scrollTop
const scrollRatio = (cursorLine - 1) / Math.max(totalLines - 1, 1);
const maxScroll = preview.scrollHeight - preview.clientHeight;

isSyncing.current = true;
preview.scrollTop = scrollRatio * maxScroll;

setTimeout(() => {
isSyncing.current = false;
}, 50);
};

return (

ref={textareaRef}
value={content}
onChange={(e) => setContent(e.target.value)}
onScroll={handleScroll}
style={{ width: '50%', padding: '10px', fontFamily: 'monospace' }}
/>
ref={previewRef}
style={{ width: '50%', padding: '10px', overflow: 'auto' }}
dangerouslySetInnerHTML={{ __html: marked.parse(content) }}
/>

);
};


这个方案比直接映射scrollTop靠谱,因为它是基于内容行号来算比例的,两边内容高度差异再大也能对上。 Typora内部也是类似的行号映射逻辑。
点赞
2026-03-13 15:00