Tailwind Preflight 让 docx-preview 浮动图片消失的真相
把一个跑了两年的 vanilla JS 合同模板编辑器迁移到 Next.js + Tailwind 栈,文字、表格渲染完全一致,唯独公司 Logo 和印章全部消失了。检查 renderAsync 配置没问题,JSZip 拆包也正常,blob URL 能打开……直到我在 DevTools 里盯着那张图片的 clientWidth: 0 发呆了三分钟,才想到去看父节点。
现象
- Word 文档的正文、表格、页眉页脚渲染完全正常。
- 浮动图片(Word 里的「嵌入型之外」的图片:Logo、水印、印章、签名图)整块消失。
- DOM 里
<img>节点存在,src是合法的blob:URL,naturalWidth/naturalHeight均有值,complete === true——图片文件本身没有问题。 - 但
clientWidth === 0,getBoundingClientRect().width === 0。
// DevTools 探针
const img = document.querySelector('.docx img')
console.log({
natW: img.naturalWidth, // 693 — 有值
clientW: img.clientWidth, // 0 — 消失
complete: img.complete, // true
})根因
Tailwind v3(@tailwind base)和 v4(@import 'tailwindcss')的 preflight 都包含:
img, video {
max-width: 100%;
height: auto;
}docx-preview 对 Word 里的浮动图形使用一个「零尺寸锚点」父容器来定位:
<div style="display: block; position: relative; width: 0px; height: 0px; left: -3.25pt; top: -8.25pt;">
<img src="blob:…" style="position: relative; left: 0pt; top: 0pt; width: 37.45pt; height: 31.2pt;">
</div>父容器的 width: 0px 是故意的——它只是一个定位锚点,图片自己通过内联 width: 37.45pt 跳出正常文档流渲染。在纯 CSS 环境下,内联 width 正常生效,图片按 37.45pt 宽度显示。
引入 Tailwind preflight 后,max-width: 100% 相对父容器解析为 max-width: 0px。CSS 规范里 max-width 优先级高于 width,结果:内联 width: 37.45pt 被截死,图片宽度归零。
preflight 的 max-width: 100% 是相对于包含块(父容器)解析的。浮动图片的锚点父容器刻意是 width: 0,于是 100% = 0,图片内联的正向 width 被完全覆盖。
症状很像图片资源加载失败,但 naturalWidth > 0 + complete === true 已经排除了网络问题;真正的凶手隐藏在 CSS 层叠顺序里。
正解
在 doc 渲染容器的作用域内覆盖掉 preflight:
/* globals.css 或对应的组件样式 */
/* docx-preview 的浮动图片父容器 width: 0,
Tailwind preflight 的 max-width: 100% 在此会解析为 0,
需要在 doc 表面作用域内恢复内联尺寸。 */
.docx-wrapper img,
.docx img {
max-width: none;
height: auto;
}如果你给 doc 渲染容器加了自己的包裹类,用那个类收窄选择器更精确:
.template-doc-surface img {
max-width: none;
height: auto;
}一定要限定在 doc viewer 容器内,不要全局写 img { max-width: none }——其他地方的头像、缩略图、卡片封面依赖 max-width: 100% 才能在弹性布局里正常收缩,全局关掉会引入一批新问题。
height: auto 也要一并写上:preflight 同样设置了 height: auto,如果图片内联同时指定了 width 和 height,只覆盖 max-width 而不覆盖 height,宽度恢复后高度仍受 auto 影响,会造成比例失真。
排查捷径
碰到 docx / Word 文档渲染图片消失,先做这三步,不要动 renderAsync 配置:
- DevTools 选中消失区域,确认
<img>节点存在且src有效。 - 检查
img.clientWidth——若为 0 但naturalWidth > 0,立即看父容器width。 - 父容器
width: 0px+ 项目用了 Tailwind / shadcn → 本文的坑,五分钟内可修。
一句话外卖
Tailwind preflight 的
max-width: 100%会被零宽父容器截死——第三方文档渲染库里大量使用「零宽锚点」做浮动定位,迁移到 Tailwind 栈后务必在 doc viewer 容器内用max-width: none局部覆盖,而不是全局关掉 preflight。