textarea是唯一可信输入源,必须与纯渲染div#preview同级共父、禁用contenteditable;实时更新需监听input事件并防抖,输出前须用DOMPurify白名单过滤,布局避坑height:100%。

HTML结构必须用textarea + div组合
不能用contenteditable或富文本编辑器替代textarea——它会导致光标错位、选区丢失、换行符处理异常,尤其在移动端。预览区必须是纯div,且不能设contenteditable="true",否则用户可能误操作破坏渲染结果。
常见错误现象:用div模拟编辑区后,复制粘贴 Markdown 时格式全乱;或预览区被用户双击编辑,导致 HTML 源码泄露。
-
textarea负责接收原始 Markdown 文本,保持纯文本语义 -
div#preview只做渲染容器,不响应任何用户输入事件 - 两者必须同级包裹在同一个父容器中,避免 flex 或 grid 布局错位引发高度计算偏差
实时更新必须监听input事件而非change
change只在失焦时触发,完全不符合“实时”要求;而input能捕获所有输入行为(键盘、粘贴、拖放、剪贴板插入),是唯一可靠选择。
但要注意:直接在input回调里调用marked.parse()并写入innerHTML,会在长文档下造成明显卡顿。实测 300 行 Markdown 连续输入时,帧率可跌至 10fps 以下。
立即学习“前端免费学习笔记(深入)”;
- 必须加防抖,延迟 200–300ms 再执行解析(
setTimeout或requestIdleCallback) - 避免每次全量重绘:对超长文档,可只在内容长度变化超过 50 字符时才触发解析
- 不要在
input里调用focus()或setSelectionRange(),会打断用户输入流
解析后 HTML 必须用DOMPurify.sanitize()过滤
直接把marked.parse()结果赋给innerHTML等于主动打开 XSS 入口。Markdown 允许内联 HTML,而marked默认开启dangerouslyAllowHtml: true(v4+ 版本),这意味着<script>alert(1)</script>会被原样执行。
哪怕你确认用户全是内部人员,也不能跳过净化——历史教训是:某次调试时临时加了console.log(rawInput),结果日志里混入了恶意 payload,被前端监控系统误判为攻击流量。
- 必须引入
DOMPurify,调用DOMPurify.sanitize(html)后再写入div#preview - 不要依赖
marked自带的sanitize选项(已废弃且不可靠) - 如果需要保留
<img alt="HTML中实现Markdown实时预览编辑器的基本结构与思路" >或<a></a>,需显式配置ALLOWED_TAGS白名单
CSS布局要避开height: 100%陷阱
很多教程直接写#preview { height: 100% },结果在 Safari 或 iOS WebView 中预览区高度塌陷为 0。根本原因是:百分比高度依赖父元素有明确高度,而body在无 DOCTYPE 或未设height: 100vh时,计算链会中断。
更隐蔽的问题是:当页面有顶部工具栏或 footer 时,100%会撑满整个视口,导致预览区溢出滚动条嵌套,用户拉到底部后无法看到最后几行。
- 推荐用
calc(100vh - Xpx),X 是固定顶部区域高度(如工具栏+padding) - 编辑区和预览区必须共用同一父容器,并设
display: flex; flex-direction: column或grid,确保高度分配可控 - 移动端需加
@media (max-width: 768px) { flex-direction: column; },否则左右分栏在小屏上直接错位
关键点其实就一个:textarea是唯一可信输入源,其余所有渲染、布局、安全控制都得围着它转。任何试图“优化体验”而绕过它的设计,最后都会在光标、粘贴、移动端或 XSS 上栽跟头。



















