svg-loader:使用外部SVG的不同工作方式

392 阅读6分钟

SVG是非常棒的:它们很小,在任何比例下都看起来很清晰,而且可以在不创建单独文件的情况下进行定制。然而,我觉得现在的网络标准还缺少一些东西:一种将它们作为外部文件的方式,同时保留了该格式的定制能力。

例如,假设你想使用你的网站的标志,存储为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上有一个标志。与其为每个变化创建不同的文件,我可以有一个文件并自定义填充颜色。

CodePen嵌入回退

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

要使用这个库,我唯一要确保的是,被提供的文件有适当的CORS头,以便XHRs能够成功。该库还将文件缓存在本地,使后续的速度大大加快。即使是第一次加载,其性能也可与使用<img> 标签相媲美。

这个概念并不新鲜,svg-inject也做了类似的事情。然而,svg-loader更容易使用,因为我们只需要在你的代码中的某个地方包含这个库(可以通过<script> 标签,或者在JavaScript捆绑中)。不需要额外的代码。

动态添加的元素和属性的变化也会被自动处理,这确保了它能在所有的Web框架中工作。这里有一个React的例子。

但为什么呢?

这种方法可能会让人觉得不伦不类,因为它引入了对JavaScript的依赖,而且已经有多种方法来使用SVG,包括内联和外部来源。但这样使用SVG是有道理的。让我们通过回答常见的问题来研究它们。

我们能不能不自己内联SVG?

内联是使用SVG的最简单的方式。只要在HTML中复制并粘贴SVG代码即可。这就是svg-loader最终要做的事情。那么,为什么要增加额外的步骤来从其他地方加载一个SVG文件呢?有两个主要原因。

  1. 内联SVG使代码变得冗长,SVG的长度可以从几行到几百行不等。如果你需要的只是几个图标,而且它们都很小,那么内联SVG可以很好地工作。但是,如果它们很大或者很多,就会成为一个很大的麻烦,因为那样的话,它们就会变成一长串不是 "业务逻辑 "的代码中的文本。代码变得难以解析。

    这与喜欢用外部样式表而不是<style> 标签,或者用图片而不是数据URI是一样的。难怪在React代码库中,首选的方法是将SVG作为一个单独的组件,而不是将其定义为JSX的一部分。

  2. 外部的SVG则要方便得多,复制和粘贴通常可以完成工作,但外部SVG真的很方便。假设你正在试验在你的应用程序中使用哪个图标。如果你使用内联SVG,这就意味着你要来回跑来获取SVG代码。但是使用外部SVG,你只需要知道文件的名称。

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

为每一个SVG触发一个HTTP请求与制作一个sprite相比,效率不是很低吗?

并非如此。有了HTTP2,发出HTTP请求的成本已经变得不那么重要了。是的,捆绑的好处仍然存在(例如,更好的压缩),但是对于非阻塞资源和XHRs来说,在现实世界的场景中,其优点几乎是不存在的。

下面是一个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的构建工具吗?

我找不到一个明显的方法来从URL中获取SVG,并在常见的捆绑工具中内联它们,比如webpack和Grunt,尽管它们存在用于内联本地存储的SVG文件。即使存在这样的插件,设置捆绑程序也不是那么简单的。事实上,我经常避免使用它们,直到项目达到一定的复杂程度。我们还必须认识到,互联网上的大多数人对webpack和React这样的东西都很陌生。简单的脚本可以有更大的吸引力。

那么,<object> 标签呢?

<object> 标签是一种包含外部SVG文件的本地方式,可以在所有的浏览器上使用:

<object data="https://unpkg.com/mdi-svg@2.2.43/svg/access-point-network.svg" width="32" height="32"></object>

然而,缺点是我们无法定制SVG的属性,除非它被托管在同一个域名上(而且<object> 标签不尊重CORS头)。即使该文件被托管在同一域名上,我们也需要JavaScript来操作fill ,就像这样:

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

简而言之,以这种方式使用外部SVG文件使得使用图标和其他SVG资产变得极为方便。如前所述,通过unpkg,我们可以使用GitHub上的任何图标而不需要额外的代码。我们可以避免在bundler中创建一个管道来处理SVG文件或为每个图标创建一个组件,而只是在CDN上托管这些图标。

用这种方式加载SVG文件有很多好处,但成本很低。