杂记 › 技术笔记 › SVG 转 PNG 后外链图丢了一半:同一份 594 B 文件预览有 3 张图、成品只剩 1 张

SVG 转 PNG 后外链图丢了一半:同一份 594 B 文件预览有 3 张图、成品只剩 1 张

同一份 594 B 的 SVG,预览里 3 个外链图全在、成品 PNG 只剩内嵌那张:预览与导出是两条加载路径,墨迹各不相同;另量了尺寸框与 viewBox 的成对覆盖、超画布上限的静默空载荷与 12 个族名的字宽差。

预览里有 3 张图,成品只剩 1 张

起点是一份 sv_image_rel.svg:594 B,3 个 <image>,两条用相对路径外链同一张 64×64 瓦片,第三条 data: URI 内嵌。预览里三张都在,探针记到 3 次 load;转成 PNG 拿回来的成品 2576 B,外链区墨迹 px=0,只剩内嵌那条的 4096 px。差的是两套加载机制。

内联 DOM 与 Blob→Image→drawImage

把文件交给SVG转换格式之后,「加载」与「转换并下载」是两段不相交的链路。预览侧(行 121-168):DOMParser 解析字符串,取第一个 <svg> 内联进文档,不建 Image 也不建 Blob URL,外链按普通网页资源去取。导出侧(行 200-255):字符串→Blob→objectURL→new Image→onload 后 drawImage 进 canvas。按图片解码时文档是 opaque origin,内部引用另算,分岔从这里来。

四类内部依赖,两侧读数不同

跨文件 <use>(249 B)在 DOM 侧触发了 load,栅格侧整幅 px=0。外链 @import(284 B)给 64px 文本定的规则没生效,computed 回落成 -apple-system、14px,长度读数 85.83;同一条规则内联(332 B)读数是 475.06。成品侧同样分裂:外链那份导出 4972 B 的 PNG,墨迹只有 692 px。

外链 @font-face(322 B)与同一字体 data: 内嵌(612547 B)在 DOM 列同宽,读数都是 400.55;栅格列却是 px=5401、bbox 315×49 对 px=4855、314×43。fill:url(外链图) 那份 256 B:DOM 里 fill 计算值原样带着 url,栅格侧整幅 px=0,成品 2544 B 是张纯空图,toast 照样报成功。

这些不能写成「外链依赖实测不支持」。本机 Chrome 带 --allow-file-access-from-files 启动,改成 file:/// 与 https:// 绝对外链的两份样张,导出时反而都进了栅格,ink px=3327,成品同为 8075 B。能写的只有:预览与导出两条路径的墨迹不同,无头环境未能触发拦截,不外推线上口径。

四类依赖:DOM 列 对 栅格列DOM 侧栅格侧外链 image(594 B)3 个 onloadpx=0 ・ 2576 B外链 @import85.83 对 475.06692 px ・ 4972 B外链 @font-face两条都 400.555401 对 4855 px外链图作 fillrect 221x221px=0 ・ 2544 B同机同码,两侧墨迹不同

图 1 四类内部依赖在两条路径下的读数

两个尺寸框与 viewBox 的成对覆盖

convert-out-w / convert-out-h 写的是画布像素,与 SVG 自身只在「原始尺寸」一步有关:viewBox 存在且 split 出 4 段、宽或高缺位时,两值成对被它的第 3、4 段顶掉;两个属性都写时 viewBox 不参与;导出侧只读框。max="10000" 只是 HTML 属性,代码不读——等比勾选下高打 10000,宽被反算成 13333 写进框;探针写 100000 也原样回读、报错位 null。

svg 根写法 → 两框读数(解析在行 131-141)
svg 根上的写法两框读数取哪一边
只有 viewBox="0 0 320 240"320 × 240缺位时 viewBox 顶上来
width="100" + viewBox320 × 240写进去的 100 被成对丢掉
width="320" height="240" + viewBox="0 0 100 50"320 × 240两个都在,viewBox 不参与

取整方向也不统一:origin 与等比反算用 Math.round,导出取值用 parseInt,同一个框里 "333.7" 截成 333、"1e3" 截成 1;写 0 或留空是假值,退回 origin。

超画布上限,静默交出空载荷

16385×16385 与 20000×20000 时 data URL 只剩 6 字符,载荷就是 "data:,";toast 仍是「已转换为 PNG (20000×20000) 并开始下载」。上限之内是真字节:320×240 基准图出 35990 B,6000×6000 是 697011 B,10000×10000 是 1889260 B,魔数全是 89 50 4e 47 0d 0a 1a 0a。

字宽账:栅格一律裁到 320

"VECTOR-0123" @64,在 12 个 font-family 下的 DOM 长度读数从 352.00(monospace 与宋体)排到 484.97(Arial Black),差 132.97;族名不存在不报错,落回继承那档 392.44。DOM 里的 rect 还要再乘预览缩放,预览因此看着合适;320 宽的画布则把栅格墨迹一律裁到 ≤320。

12 个 font-family 的 DOM 长度读数 @64pxArial Black484.97Arial429.22Microsoft YaHei426.63Courier New422.47Times New Roman404.16Segoe UI392.44NoSuchFace 9000392.44Consolas387.07monospace352.00宋体352.00虚线=栅格画布的 320 上限,十条读数全超它

图 2 族名字宽谱,与 320 宽画布的竖直上限

单次处理与 object URL 回收

这一页没有批量入口:fileInputMultiple=false,拖拽与 change 两处(行 104、107)都只取 files[0]。整轮 13 步测得 object URL made=11 / revoked=11,一次导出 1 造 1 销,没有累积。「该转多大、存成什么格式」是另一笔账,SVG 转 PNG 的导出尺寸与格式选择那篇已经拆过,不复述。

几个常见追问

Q:预览和成品对不上,先查哪里?

查 SVG 里指向外部的引用:<image> 相对路径、跨文件 <use>、外链 @import / @font-face、fill:url(...)。它们都活在 DOM 里、栅格化时另算。

Q:框里写大数没报错,是不是就安全?

不是。max 属性与报错位都拦不住写入,13333 照样进框;越过画布上限也只有 6 字符空载荷配成功 toast,得自己核对成品字节与魔数。

Q:成品文字比预览窄?

font-family 依赖本机字体,12 族读数能差 132.97;320 宽画布再把栅格一律裁到 ≤320。要稳:导出前把规则内联、字体以 data: 内嵌。

📎 相关文章

16 组开关实测 HTML 去标签:报 19 个标签,实际删掉 148 个字符 2026-10-30 二进制转 Base64 这一页:前缀只有 5 档,换行能解、连字符和下划线直接报错 2026-10-30 给应用做商店介绍图,点导出拿到的却是 1242×2688:8 档画布预设的像素账、噪点 24.83 倍体积与默认被裁掉 134.93 px 的手机框 2026-10-30 JSON 转表格的表头到底从哪来:键并集、点号展平与 CSV 引号的真实规则 2026-10-30 取消勾选「保留注释」后,format-js 把 24 字符 URL 压成 13 字符残缺码 2026-10-30