在图片标签中使用自定义字体与 SVG

Avatar of Thomas Yip
Thomas Yip

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

当我们生成 PNG 图片时,我们使用 <img> 标签或 CSS 背景,仅此而已。它非常简单且保证有效。

PNG 在 HTML 中比 SVG 更易于使用

不幸的是,尽管 SVG 拥有诸多优势,但它却并非如此。虽然在 HTML 中使用 SVG 时您可以有多种选择,但实际上归结为 内联<object><img>,所有这些都存在严重的陷阱和权衡。

内联 SVG 的问题

如果您使用内联 SVG,则会失去使用浏览器缓存、服务器和浏览器之间的 Gzip 压缩以及搜索引擎图像索引的功能(内联 SVG 不被视为图像)。即使您的图像可能一点也没有改变,它们也会始终重新加载,这会导致网站加载速度变慢,大多数人都不愿意容忍这种权衡。

此外,内联 SVG 还会导致复杂的依赖关系问题,您无法轻松地将图像插入 HTML,而必须求助于脚本(PHP 或其他)。当您拥有多个图像时,这在维护站点方面会成为一个巨大的问题,因为从本质上讲,您无法再使用 <img> 标签了。

毫无疑问,内联 SVG 在某些方面确实很出色——也就是说,如果您希望您的图像快速显示,而无需等待其他资源加载。除此之外,显然,您不能只内联所有内容。

object 标签的问题

SVG 以其在所有分辨率的设备上显示的出色质量以及引用外部资源(如 CSS 和字体)同时保持文件大小非常小的能力而闻名。其理念是拥有许多都共享单个 CSS 或单个字体文件的 SVG,以减少您需要下载的资源数量。

资源共享的神话

许多人不知道,共享 SVG 的外部资源仅适用于内联 SVG。由于 <object> 标签的使用允许访问这些资源,因此人们认为浏览器将下载 CSS 的单个副本,即使您有许多 <object> 标签都引用了同一个 CSS 文件。

然而,这根本不是真的

多个 object 标签将下载多个 CSS

使问题更加复杂的是,<object> 标签不被识别为图像,因此图像搜索索引是不可能的。

进一步加剧问题的是依赖关系问题。例如,假设您有 100 张图片,其中 25 张使用 Roboto 字体,另 25 张使用 Lato,25 张使用 Open Sans,其余则使用这三种字体的组合。您的 CSS 需要引用所有三种字体,因为要跟踪哪个文件使用哪种字体是相当不可能的,这意味着您可能在某些页面上加载了不需要的字体。

图片标签

这让我们只剩下 <img> 标签,它有很多优点。因为它与其他图像格式使用的标签相同,所以您可以获得熟悉感、浏览器缓存、Gzip 压缩和图像搜索。每个图像都是独立的,没有依赖关系问题。

如果与 <img> 标签一起使用,SVG 会丢失字体

唯一的问题是 **您将丢失字体**。更准确地说,如果您的 SVG 中有任何文本,除非您嵌入字体,否则您的文本将使用系统字体显示,通常是 Times New Roman。您花了几个小时选择了一种漂亮的字体,但当您使用 <img> 标签嵌入 SVG 时,所有这些都消失了。这怎么可能被接受呢?

调查字体光栅化

将字体转换为路径

我们的第一反应是看看是否可以执行字体光栅化。这是一种常用的将字体转换为路径的技术,因此它可以在所有设备上良好渲染并保持零依赖性。从表面上看,这工作得非常好,并且在编辑器中,一切看起来都很完美。

尽管光栅化后的 SVG 的大小高达 137 KB,而光栅化前的 SVG 大小为 15.7 KB,但我们仍然很乐观,因为在使用 Gzip 压缩优化 SVG 后,光栅化后的文件大小缩减至 11 KB,略小于等效 PNG 的 11.9 KB

原始 字体光栅化 字体光栅化(.svgz)
15.7 KB 137 KB 11.0 KB
PNG @ 1x PNG @ 2x PNG @ 3x
11.9 KB 26.5 KB 42.6 KB
带有字体光栅化的 SVG 图片

唉,一旦我们将光栅化后的 SVG 嵌入到 HTML 中,我们就发现我们的乐观情绪是过早的。虽然它在高分辨率显示器上看起来可能很棒,但在低分辨率显示器上的质量却无法接受。

顶部是光栅化后的字体,底部是原始字体

图像底部是原始字体,字体清晰显示,而顶部是使用字体光栅化的字体,字体呈像素化。

在屏幕上显示时的 ClearType 差异

发生的情况是,大多数操作系统都会优化字体,以确保它们在所有屏幕上都清晰锐利地显示。在 Windows 上,这称为 ClearType,并且由于我们对字体进行了光栅化,因此不会应用任何优化,从而导致文本模糊,尤其是在低分辨率屏幕上。

显然,质量下降是不可接受的,所以让我们重新开始。

字体嵌入来救援

最初,我们对字体嵌入持极大的怀疑态度,主要是因为工作流程复杂。

要在 SVG 中嵌入字体,您首先必须知道使用了哪些字体系列。然后,您需要找到这些字体文件并下载它们。下载完成后,您需要将常规、粗体、斜体和粗斜体转换为 Base64 编码。如果您手动执行此操作,在大量文件中,要确定哪个文件使用粗体而哪个文件不使用粗体是不可能的。然后,您必须将所有四个 Base64 编码字符串复制到您的 SVG 中。

当然,一定有更好的方法。这就是我们构建 Nano 的原因。Nano 会自动扫描 SVG 并仅插入使用的字体。例如,如果未使用粗体或不存在文本,则不会嵌入任何字体。

尽管如此,生成的文件仍然很大,并且与等效的 PNG 相比没有竞争力,因此我们继续努力并构建了自己的 SVG 优化器 (Nano),它会将 SVG 文件大小缩减到极小。(了解 Nano 如何压缩 SVG。)此外,我们还优化了将字体嵌入 SVG 的方式,从而使文件大小变得非常小。

带有文本的 SVG 图片,使用 Nano 优化并嵌入字体

比较文件大小和带宽节省

原始 字体光栅化 未优化的字体嵌入 Nano 字体嵌入
大小 15.7 KB 137 KB 65.2 KB 22.0 KB
Gzip 3.57 KB 11.0 KB 44.5 KB 11.7 KB
PNG @ 1x PNG @ 2x PNG @ 3x
大小 11.9 KB 26.5 KB 42.6 KB
Gzip 12.1 KB 26.1 KB 41.7 KB

从上面可以看出,即使嵌入字体,Nano 生成的 SVG 也是非常轻量级的,大小为 11.7 KB(Gzipped),而等效的 PNG @1x 大小为 11.9 KB。虽然这看起来微不足道,但您网站上节省的总带宽肯定非常可观。

假设您 50% 的流量是低分辨率,40% 是 2X 分辨率,其余 10% 是 3X 分辨率。如果您的网站在一张图片上获得了 10,000 次点击

(5,000 * 11.9 KB) + (4,000 * 26.5 KB) + (1,000 * 42.6 KB) = 208.1 MB

如果你使用 Nano,并使用 GZip 压缩 SVG

10,000 * 11.7 KB = 117.0 MB

这将导致:208.1 MB – 117.0 MB = 91.1 MB 的节省,或 43.7% 的带宽节省,无论以任何标准衡量,这都是一个相当可观的数字。

除了带宽节省之外,你还可以获得更简单的流程,无需使用多个带有多个 srcset 的 PNG 图片,并且拥有更好的质量,包括操作系统字体增强功能,以确保你的图片在所有分辨率的设备上保持清晰锐利。最棒的是,为你的用户提供更好的用户体验,因为你的网站加载速度会更快——尤其是在高分辨率设备上。

彻底测试 Nano

我们对这些节省并不满足,于是开始寻找 SVG 图片来彻底测试 Nano。我们使用了总共 2,571 个不同大小和设计的 SVG 文件,总计 16.3 MB。经过 Nano 优化后,大小变为 6.2 MB,惊人的 61.8% 的尺寸节省。

用于测试 Nano 的 2500 多个 SVG 图片的小部分示例

显示视觉差异

由于我们正在测试的文件数量非常庞大,并且随着时间的推移还在增加,因此我们必须构建一个自动测试,包括自动突出显示优化前后的像素差异。其他 SVG 优化器的抱怨之一是,SVG 的最小化可能会破坏你的图片,导致其渲染效果与原始图片不同。

为此,我们将像素差异化引入到 Nano 的自动测试中。也就是说,如果 Nano 检测到它优化的 SVG 与原始 SVG 相比像素差异超过 1%,它会发出警告,从而确保 Nano 的优化永远不会破坏 SVG。

Nano 优化显示视觉差异

由于字体是嵌入式并保留的,加上 SVG 是一种矢量图形格式,因此所有分辨率下的渲染质量都无法与其他光栅格式相比。

接下来是什么?

我们希望我们的工作能够让 SVG 更容易在任何地方使用。我们目前正在努力生成更小的文件大小,并将我们的代码移植到 Node.js 上,以便你可以使用 Nano 自动化你的生产构建,等等。

你认为 Nano 会对你的项目有所帮助吗?请在评论中告诉我!