最基础做法是直接用marked.parse()渲染完整Markdown字符串并插入DOM;实时渲染需应对动态、不完整、流式内容,分一次性返回、流式返回(SSE/ReadableStream)、编辑器实时预览三种场景,分别采用全量解析、缓冲切块+稳定区/活动区分处理、防抖增量更新策略。

直接把接口返回的 Markdown 字符串丢给 marked.parse() 渲染成 HTML,再插入 DOM,是最基础的做法。但“实时渲染”真正考验的是如何应对内容动态变化、不完整、流式到达等真实场景,而不是只跑通一次转换。
下面分几种典型情况说清楚怎么做:
接口一次性返回完整 Markdown
适合文档加载、静态内容展示等场景。操作最简单:
- 引入
marked.js(CDN 或 npm) - 调用
marked.parse(text)得到 HTML 字符串 - 用
.innerHTML或textContent(注意 XSS)写入容器
<div id="content"></div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
fetch('/api/doc')
.then(res => res.text())
.then(md => {
const html = marked.parse(md);
document.getElementById('content').innerHTML = html;
});
</script>⚠️ 安全提醒:innerHTML 会执行 <script>、onerror 等危险标签。生产环境建议:
立即学习“Java免费学习笔记(深入)”;
- 开启
marked的sanitize: true(需搭配DOMPurify) - 或使用
marked.use({ sanitizer: DOMPurify.sanitize })
接口流式返回(SSE / fetch + ReadableStream)
比如 AI 对话中逐字/逐块输出 Markdown,这时不能每来一段就全量解析——否则代码块没闭合时会被当普通文本,页面会闪、跳、错位。
文档转 Markdown 转换器 - 将 DOCX、PPTX、Excel 文件转换为 Markdown。用于从 Word 文档、PowerPoint 演示文稿或 E... 提取内容。
正确做法是:维护缓冲区 + 按块切分 + 区分稳定区与活动区
- 所有 chunk 先累积到一个字符串 buffer 中
- 每次更新后,按 Markdown 块级边界(如
^#{1,6},^[-+*], ``` ```)切分 buffer - 对已闭合的块(如完整代码块、完整标题),调用
marked.parse()渲染为最终 HTML - 对未闭合块(如只有 ``
js 没有结束围栏),用<pre class="brush:php;toolbar:false;">` + HTML 转义临时保护,避免破坏结构 </li></ul><p>示例关键逻辑: </p><pre class='brush:js;toolbar:false;'>let buffer = ''; function handleChunk(chunk) { buffer += chunk; const blocks = splitIntoBlocks(buffer); // 自定义切块函数 const stableHtml = blocks .filter(isCompleteBlock) .map(b => marked.parse(b)) .join(''); const pendingHtml = blocks .filter(b => !isCompleteBlock(b)) .map(escapeHtml) // 只转义,不解析 .map(t => `<pre><code>${t}</code></pre>`) .join(''); preview.innerHTML = stableHtml + pendingHtml; }这样代码块即使只输出一半,也不会吞掉后面的文字,页面不会闪烁。
编辑器内实时预览(输入即渲染)
用户边打 Markdown 边看效果,重点在防抖 + 增量更新,避免卡顿:
- 监听
input 事件 - 设置 300ms 防抖,延迟触发解析
- 解析前先清空旧预览,再写入新 HTML
- 可选:用
marked.use({ breaks: true })支持换行转<br>
const input = document.getElementById('md-input'); const preview = document.getElementById('preview'); let timer; input.addEventListener('input', () => { clearTimeout(timer); timer = setTimeout(() => { preview.innerHTML = marked.parse(input.value); }, 300); });如果内容很长(比如 >5000 字),可考虑用
requestIdleCallback或 Web Worker 避免主线程阻塞。
不需要复杂框架,纯 JavaScript 就能搞定。核心不是“怎么调用 marked”,而是根据数据到达方式,决定何时解析、解析多长、怎么插进页面。
- 监听

















