svg-loader:一种不同的外部 SVG 使用方法

Avatar of Shubham Jain
Shubham Jain

DigitalOcean 为您的旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费积分!

SVG 非常棒:它们体积小,在任何比例下都清晰锐利,并且可以自定义而无需创建单独的文件。 但是,我觉得当今的 Web 标准中缺少一些东西:一种将它们作为外部文件包含进来,同时保留格式的自定义能力的方法。

例如,假设您想使用存储为 web-logo.svg 的网站徽标。 您可以执行以下操作

<img src="/images/logo.svg" />

如果您的徽标在所有地方都保持相同外观,这样做就可以了。 但在许多情况下,您可能会有 2-3 个相同徽标的变体。 例如,Slack 有 两个版本

即使主徽标中的颜色也略有不同。

如果我们有一种方法可以自定义上面徽标的填充颜色,我们可以传递任何任意颜色来渲染所有变体。

再以图标为例。 您不会想做这样的事情,对吧?

<img src="/icons/heart-blue.svg" />
<img src="/icons/heart-red.svg" />

将外部 SVG 作为内联元素加载

为了解决这个问题,我创建了一个名为 svg-loader 的库。 简而言之,它通过 XHR 获取 SVG 文件并将它们加载为内联元素,从而允许您自定义 fillstroke 等属性,就像内联 SVG 一样。

例如,我的一个副项目 SVGBox 上有一个徽标。 我可以拥有一个文件并自定义填充颜色,而不是为每个变体创建不同的文件。

我使用 data-src 设置 SVG 文件的 URL。 fill 属性会覆盖原始 SVG 文件的 fill

要使用该库,我唯一需要确保的是正在提供的文件具有适当的 CORS 标头,以便 XHR 成功。 该库还会在本地缓存文件,使后续加载速度更快。 即使是第一次加载,性能也与使用 <img> 标签相当。

这个概念并不新鲜。 svg-inject 也有类似的功能。 但是,svg-loader 更易于使用,因为我们只需要在代码中的某个地方包含该库即可(可以通过 <script> 标签或 JavaScript 包)。 不需要额外的代码。

动态添加的元素和属性的更改也会自动处理,确保它适用于所有 Web 框架。 以下是在 React 中的示例

但这为什么呢?

这种方法可能感觉非正统,因为它引入了 JavaScript 依赖项,并且已经有多种方法可以使用 SVG,包括内联和从外部源使用。 但用这种方式使用 SVG 有一定的道理。 让我们通过回答常见问题来研究它们。

难道我们不能自己内联 SVG 吗?

内联是使用 SVG 的最简单方法。 只需将 SVG 代码复制粘贴到 HTML 中即可。 这正是 svg-loader 最终要做的。 那么,为什么还要添加额外的步骤从其他地方加载 SVG 文件呢? 主要有两个原因

  1. 内联 SVG 使代码冗长: SVG 的长度可以从几行到几百行不等。 如果您只需要几个图标,而且它们都很小,内联 SVG 可以很好地工作。 但如果图标体积较大或数量众多,就会很麻烦,因为它们会在代码中变成很长的文本字符串,而这些文本字符串并非“业务逻辑”。 代码变得难以解析。

    这就像更喜欢外部样式表而不是 <style> 标签,或者使用图像而不是 data URI 一样。 难怪在 React 代码库中,首选方法是将 SVG 作为单独的组件使用,而不是将其定义为 JSX 的一部分。
  1. 外部 SVG 更加方便: 复制粘贴通常可以完成工作,但外部 SVG 可能会非常方便。 假设您正在尝试确定在应用程序中使用哪个图标。 如果您使用的是内联 SVG,则意味着要来回切换以获取 SVG 代码。 但使用外部 SVG,您只需要知道文件名称。

    看看这个例子。 GitHub 上最广泛的图标库之一是 Material Design Icons。 使用 svg-loader 和 unpkg,我们可以立即开始使用 5,000 多个图标中的任何一个。

对每个 SVG 触发 HTTP 请求效率不高吗? 与创建精灵相比如何?

并非如此。 使用 HTTP2,发出 HTTP 请求的成本已变得不那么重要了。 没错,打包仍然有很多好处(例如,更好的压缩),但对于非阻塞资源和 XHR,在实际场景中,这些好处几乎不存在。

这里有一个 Pen 以与上面类似的方式加载 50 个图标。(以隐身模式打开,因为文件默认情况下会被缓存)

<use> 标签(SVG 符号)怎么样?

SVG 符号 将 SVG 文件的定义与其使用分开。 我们无需在每个地方都定义 SVG,而是可以这样做

<svg>
  <use xlink:href="#heart-icon" />
</svg>

问题是,没有浏览器支持使用托管在第三方域上的符号文件。 因此,我们无法执行以下操作

<svg>
  <use xlink:href="https://icons.com/symbols.svg#heart-icon" />
</svg>

Safari 甚至不支持托管在 同一域 上的符号文件。

难道我们不能使用将 SVG 内联的构建工具吗?

我无法找到在常见的打包程序(如 webpack 和 Grunt)中从 URL 获取 SVG 并将其内联的明显方法,尽管它们存在于 将本地存储的 SVG 文件内联 中。 即使存在执行此操作的插件,设置打包程序也不容易。 事实上,我经常避免使用它们,直到项目达到一定的复杂程度。 我们还必须认识到,互联网上大部分内容都不了解 webpack 和 React 之类的东西。 简单脚本的吸引力要大得多。

<object> 标签怎么样?

<object> 标签是一种在所有浏览器中都可用的,用于包含外部 SVG 文件的原生方法。

<object data="https://unpkg.com/[email protected]/svg/access-point-network.svg" width="32" height="32"></object>

但是,缺点是我们无法自定义 SVG 的属性,除非它托管在同一域(并且 <object> 标签不尊重 CORS 标头)。 即使文件托管在同一域上,我们也需要 JavaScript 来操作 fill,例如

<object data="https://unpkg.com/[email protected]/svg/access-point-network.svg" width="32" height="32" onload="this.contentDocument.querySelector('svg').fill = 'red'"></object>

简而言之,以这种方式使用外部 SVG 文件可以使使用图标和其他 SVG 资源变得非常方便。 如前所述,使用 unpkg,我们可以使用 GitHub 上的任何图标,而无需额外的代码。 我们可以避免创建管道来处理 SVG 文件或为每个图标创建组件,而只需将图标托管在 CDN 上即可。

以这种方式加载 SVG 文件有很多好处,而成本却很低。