本地SVG批量预览工具:从CORS拦截到同源方案的完整实现指南
1. 为什么非要做这个 SVG 预览页面
先说个场景:我平时做前端,也帮人处理设计资源的本地化。SVG 文件在公司内网的项目里到处都是——图标库、地图导览、还有那些动辄几百 KB 的复杂插画。每次要确认某个 SVG 渲染效果对不对,流程通常是:打开资源管理器 → 找到文件 → 拖进浏览器 → 发现浏览器直接显示 XML 源码 → 再想办法转成图片 → 又发现宽高比不对。这过程烦不烦?太烦了。
后来我就想,干脆自己写一个本地 SVG 预览页面,把文件夹里的图标统一列出来,左边选文件、右边看渲染效果,顺便还能看下尺寸、颜色、路径数量这些元信息。这个需求听起来简单,但真正动手做的时候,第一个大坑就冒出来了:直接把本地 SVG 文件路径塞进 <img> 标签,或者用 fetch 去读本地文件,会触发浏览器的 CORS(跨域资源共享)拦截,甚至连放在同目录下的静态文件,在某些启动方式下也会被拦。
这篇文章就围绕这个项目展开,把整个实现过程、CORS 问题的成因和绕过方法、以及我在实际调试中踩过的坑,完整记录下来。适合谁看?给那些需要批量管理 SVG 资源的设计师、前端开发、以及做本地工具链的朋友。不需要你有多高深的前端基础,但懂一点 HTML/JavaScript 会让后面的操作更顺。
需要提前说明的是:下面所有方案都以"本地开发环境 + 浏览器"为前提,不涉及任何需要在网络侧配置跨域的敏感操作。安全性上,我用的都是最普通的静态文件托管方式,没有任何绕开浏览器安全边界的骚操作,所有实现都能写在公司内网或本机环境里直接跑。
2. 整体方案怎么设计,以及为什么这么选
2.1 核心目标拆解
在动手写代码之前,我先把需求拆成几个独立的小目标,这是做工具类项目最重要的一步:
- 支持批量导入 SVG 文件,而不是一次只处理一个。
- 左侧列表展示文件名称和基础信息(大小、修改时间),右侧画布展示预览效果。
- 对 SVG 里的关键属性(width、height、viewBox、fill 等)做解析,方便快速检查。
- 解决因本地文件读取导致的 CORS 拦截,让预览不白屏。
这里面最容易忽略但最关键的,是第四条。很多人写预览页面,第一步就卡在"为什么我的 fetch 读本地 JSON 文件也报 CORS 错"。
2.2 为什么本地预览会触发 CORS 拦截
说白了,CORS 是浏览器的一种安全机制,它防止一个网页里的脚本随意读取"不属于当前页面来源"的资源。你把 HTML 文件放在 C:\svg-preview\index.html,然后用浏览器直接双击打开,页面所在的"来源"会变成 file://。当你用 fetch 去读取同目录下的 icon.svg 时,浏览器认为 file:// 到 file:// 也是跨域请求,于是直接拦截。
更常见的报错长这样:
origin 显示成 null,就是因为 file:// 协议下,浏览器没给页面分配一个标准的源。这个问题不是 XMLHttpRequest 或 fetch 写错了,而是运行环境本身就不符合浏览器的安全模型。
所以,解决思路有两个方向:
- 不用
fetch去读取文件内容,改用<img>标签或者其他不需要读取文件内容的加载方式。 - 把整个项目跑在一个 HTTP 服务上,让页面和文件处于同一个源(same-origin),这样请求就不会被拦截。
2.3 方案选型对比
我对比过四种常见方案,各有优劣:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
直接双击 HTML,使用 <img> 加载 |
零配置,最简单 | 无法读取 SVG 内部代码,无法做解析 | 只要快速看渲染效果 |
HTML + fetch 读取本地文件 |
可以拿到源码 | file:// 下必被 CORS 拦截 |
基本不可用 |
本地起 HTTP 服务(如 python -m http.server) |
同源请求,不受 CORS 限制 | 需要多敲一条命令 | 大多数开发场景 |
| 用 Electron/Tauri 等桌面壳 | 绕过浏览器跨域模型,完全本地 | 工程量大,依赖重 | 产品级工具 |
我最终选了第三种:本地 HTTP 服务 + fetch 读取 SVG 文本 + 右侧渲染。因为它最贴近"做一个简单的网页工具"这个定位,而且启动成本极低,一行命令就能搞定。用 Electron 那种重型方案,做一个小预览工具实属杀鸡用牛刀。
3. 从零搭建预览页面的完整过程
3.1 目录结构和基础文件
我的项目非常简单,只有三个文件:
把需要预览的 SVG 文件放在 svg-preview/icons/ 目录下。结构保持扁平,不要嵌套太深,后面写代码会省很多事。
3.2 编写 HTML 骨架
我先把页面的基础结构搭出来:
这里有一个值得注意的细节:#svgContainer 我用的是普通 div,而不是 <iframe>。之前也考虑过用 <iframe src="icons/xxx.svg"> 来隔离 SVG 内部的样式和脚本,但 iframe 在跨源情况下拿不到内部 DOM,而且滚动条样式控制起来很费劲。后来统一用 fetch 拿源码,再插入容器里,这样既能渲染又能解析,自由度最高。
3.3 用 fetch 批量读取 SVG 文件
要让页面知道 icons/ 目录下有哪些文件,最简单的办法是维护一份清单。但我懒得手动维护,所以直接在代码里写死了一个文件名数组,或者用一个动态接口去读取目录列表。
在纯静态页面里,没有后端,动态读目录这件事做不到