最可靠的方式是用 Blob + URL.createObjectURL() 生成预览链接,需指定 type: 'text/html;charset=utf-8' 避免乱码,配合防抖、主动 revoke 旧 URL、清空 iframe 及 onload 后校验内容来保障兼容性与稳定性。

为什么直接用 URL.createObjectURL() 生成预览链接最可靠
本地草稿预览的核心是绕过服务端,把用户在编辑器里写的 HTML 字符串立刻变成可访问的页面。最直接的方式不是拼接 data: URL(它对大文本有长度限制且部分浏览器会拒绝执行内联脚本),而是用 Blob 构造二进制对象,再通过 URL.createObjectURL() 创建临时地址——这个地址能被 <iframe> 正常加载,且不触发跨域或执行限制。
常见错误是把原始字符串直接传给 new Blob([htmlStr]) 却忽略 MIME 类型和编码:
-
Blob默认按 UTF-8 解析,但若 HTML 中含中文或特殊符号,必须显式指定{ type: 'text/html;charset=utf-8' },否则 iframe 内可能乱码或解析失败 - 不要省略
charset=utf-8—— 某些旧版 Chrome 和 Safari 会默认用 ISO-8859-1 解析,导致中文全变 -
URL.createObjectURL()返回的地址是一次性有效链接,关闭页面或调用URL.revokeObjectURL()后即失效,无需担心内存泄漏(现代浏览器已自动回收)
如何让 iframe 安全加载 Blob 预览页且支持样式/脚本执行
直接设置 iframe.src 为 Blob URL 是可行的,但容易踩两个坑:一是 iframe 加载前未清空旧内容导致样式残留,二是未处理加载失败时的静默失败(比如 Blob 内容语法错误,iframe 只显示空白)。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 每次生成新预览前,先执行
iframe.contentDocument?.write('')或直接iframe.src = 'about:blank'清空,避免上一次的 CSS/JS 干扰 - 监听
iframe.onload,而非onerror——Blob URL 不会触发onerror,即使 HTML 有语法错误,onload仍会触发;需在 onload 回调里检查iframe.contentDocument?.documentElement?.innerHTML是否可读来判断是否真正加载成功 - 若草稿含
<script>标签,确保它们是内联的(非src外链),因为 Blob URL 下外链脚本会被同源策略拦截;如需动态加载,得改用eval()或import()手动执行,但需自行处理作用域和错误捕获
编辑器内容实时同步到 Blob 预览的最小安全更新逻辑
用户边打字边预览时,不能每敲一个字符就重建 Blob URL——太频繁会触发浏览器 URL 资源上限(尤其 Chrome 对同一页面的 Blob URL 数量有限制),也不能等用户停顿才更新(体验卡顿)。
推荐做法是加一层防抖 + 显式销毁旧 URL:
- 用
setTimeout做 300ms 防抖,输入停止后才触发预览更新 - 每次新建 Blob 前,先调用
URL.revokeObjectURL(oldUrl)主动释放旧地址,避免累积过多无效引用 - 注意:不能在
iframe.onload里才 revoke,因为此时新 URL 已设给 iframe,旧 URL 可能已被卸载,revoke 只是保险;应在生成新 URL 后、赋值给iframe.src前立即 revoke 旧的 - 如果编辑器用的是
contenteditable,获取 HTML 内容时用element.innerHTML,别用innerText或textContent,否则会丢失标签结构
移动端 Safari 的特殊兼容处理
iOS Safari 对 Blob URL 的 iframe 加载更敏感:它不允许在非用户手势(如点击)上下文中动态设置 iframe.src,否则会静默失败。这意味着单纯监听 input 事件触发预览,在 iPhone 上大概率白屏。
解决办法很实际:
- 把预览触发绑定到明确的用户操作上,比如「预览」按钮点击,或
textarea的blur事件(失焦代表用户写完一段) - 如果坚持实时预览,可在首次用户点击编辑器区域后,开启定时轮询(例如每秒检查内容是否变化),这样后续的
src赋值就处于合法手势上下文 - 不要依赖
document.hasFocus()判断——Safari 在后台标签页中返回true,不可靠;优先用事件驱动而非状态检测
真正麻烦的从来不是 Blob 本身,而是不同浏览器对“谁允许加载这个临时 URL”的隐式规则不一致;与其反复试错,不如把预览动作收束到明确的用户意图节点上。


















