最轻量的 html 实时预览方案是 window.open() + document.write(),需注意 document.close()、html 转义、安全隔离、移动端兼容及多代码块唯一查找,禁用 data:text/html。

直接用 window.open() + document.write() 最轻量
不需要后端、不依赖第三方服务,几行 JS 就能让读者点一下就看到 HTML 效果。核心逻辑就是:取到代码字符串 → 新开空白窗口 → 用 document.write() 写入并关闭文档流。
常见错误现象:document.write() 在页面加载完成后调用会清空整个页面;或没调用 document.close() 导致页面一直显示“正在加载”;还有人忘了对 HTML 特殊字符做转义,比如 被当成标签解析,直接崩掉预览。
实操建议:
- 从
<textarea id="runcode"></textarea>里取内容,先用正则还原 HTML 实体:code.replace(/, '/g, '>').replace(/&/g, '&') - 调用
window.open('', '_blank')后,立刻执行newwin.document.open('text/html', 'replace'),否则在某些浏览器里会写不进内容 - 必须紧接
newwin.document.write(code)和newwin.document.close(),缺一不可 - 加一句
newwin.opener = null,避免新窗口能通过window.opener反向控制原页面(安全 & 防止刷新后报错)
多个代码块共存时,别用全局 id 硬编码
博客里常有多个 <textarea></textarea>,如果统一用 document.getElementById('runcode'),永远只取第一个。更糟的是,用户复制粘贴多段示例后,所有“运行”按钮都指向同一个 ID,点哪个都跑第一个。
使用场景:一篇讲表单验证的博客,分别有“基础 input”“带 pattern 的 input”“完整 form 提交”三段可运行代码。
实操建议:
- 每个代码块用唯一 class 包裹,比如
<div class="runnable-block"> <li>“运行”按钮放在同一容器内,用相对查找:<code>btn.closest('.runnable-block').querySelector('textarea').value - 避免用
onclick="runCode()"写在 HTML 里,改用事件委托绑定,防止动态插入的代码块失效 - 如果用 jQuery,优先选
$(btn).closest('.runnable-block').find('textarea').val(),比$('#runcode')可靠得多 - 彻底放弃
data:text/html方案,回归window.open()+document.write()组合 - 如果真要免弹窗,可把预览区域嵌在当前页的
<iframe></iframe>里,但注意同源策略——非同源 iframe 无法读取其contentDocument,调试困难 - 测试时务必在 Chrome 最新版和 Firefox Nightly 下验证,别只看本地开发环境
- 移动端优先用内联
<iframe sandbox="allow-scripts"></iframe>替代新窗口,设置固定宽高 +scrolling="auto" - 给 iframe 加
loading="lazy"避免首屏卡顿,但首次点击“运行”时需手动iframe.srcdoc = code(注意 Safari 16.4+ 才完全支持srcdoc) - 监听
touchstart而非click,解决 iOS 300ms 延迟;同时加cursor: pointer和user-select: none防误操作 - 代码块本身加
tabindex="0",让键盘用户也能触发,这点几乎没人做,但 WCAG 合规要求它
Chrome/Firefox 对 data:text/html, 的限制越来越严
有人图省事用 window.open('data:text/html,' + encodeURIComponent(code)),看起来简洁,但 2025 年起 Chrome 128+ 和 Firefox 125+ 默认拦截这类 data URL 弹窗,尤其含 script 标签时,控制台直接报 Not allowed to navigate top frame to data URL。
性能 / 兼容性影响:该方式在 Safari 下基本可用,但无法调试(没有 network 请求、无 console 上下文),且不支持跨域资源(如 CDN 加载的 JS 库)。
实操建议:
别忽略移动端点击延迟和 iframe 滚动问题
博客读者大量来自手机,而 window.open() 在 iOS Safari 和部分安卓浏览器中会被拦截为弹窗广告,直接静默失败;就算成功,新开窗口在小屏上体验极差——没有返回按钮、不能滑动、字号错乱。
实操建议:
最麻烦的其实是 HTML 字符转义和上下文隔离——你以为只是把字符串塞进去,其实是在不同文档流、不同执行时机、不同安全策略之间反复横跳。稍不注意,一段本该跑通的 demo 就在读者手机上白屏,还查不出原因。











