如何缩放 SVG

Avatar of Amelia Bellamy-Royds
Amelia Bellamy-Royds

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

以下是 Amelia Bellamy-Royds 的客座文章。Amelia 在 SVG 方面经验丰富,作为 SVG Essentials 的合著者以及即将出版的 Using SVG with CSS3 and HTML5 的作者。Amelia 和我将在即将举行的 RWD 峰会 上就 SVG 发表演讲!在这里,她分享了关于缩放 SVG 的一个精彩指南,涵盖了您可能想要进行缩放的所有方法。这并不像缩放光栅图形那么简单,但这可能是一件好事,因为它开辟了有趣的可能性。

您已经做出了决定。您终于要开始行动了。今年,您将开始在 Web 设计中 使用 SVG。您在 Inkscape 中创建了一个很棒的标题徽标,并将它吐出的 SVG 代码复制粘贴到您的 WordPress 标题文件。当然,您不会放弃去年始终使用响应式设计的决心,因此您在 CSS 中设置了 svg.banner { width: 100%; height: auto; },您认为您已经准备就绪了。

直到您在测试浏览器中打开网页,发现一些浏览器在图像上方和下方留下了巨大的空白区域,而另一些浏览器则将其裁剪得太短。

SVG 代表可缩放矢量图形。所以,缩放 SVG 应该很容易,对吧?难道这不是 SVG 支持者一直以来所说的吗?SVG 在任何大小下看起来都很好?是的,但事实并非如此。SVG 在任何比例下看起来都很棒,但它可以以多种不同的方式进行缩放,因此对于 SVG 初学者来说,让它按预期的方式运行可能很令人困惑。浏览器最近才开始采用一种标准方法来调整内联 SVG 内容的大小,这并没有什么帮助。

SVG 不是(仅仅是)一张图片

缩放 SVG 如此困难的部分原因是,我们对图像的缩放方式有一种特定的想法,而 SVG 的行为方式不同。

像 JPG、PNG 和 GIF 这样的光栅图像具有明确定义的大小。图像文件描述了浏览器如何着色一个具有特定像素宽度和特定像素高度的网格。一个重要的副作用是,光栅图像具有明确定义的 _纵横比_:宽度与高度的比率。

您可以强制浏览器以与其固有高度和宽度不同的尺寸绘制光栅图像,但是如果您强制其使用不同的纵横比,则事物会失真。出于这个原因,从 Web 的早期开始,就一直支持图像上的 _自动_ 缩放:您设置高度 _或_ 宽度,浏览器会调整另一个维度,以使纵横比保持不变。

缩放光栅图像也适用于将其用作 backgrouund-image 的情况,只是 我们使用 object-fit 属性进行大小调整和裁剪

相比之下,SVG 图像可以在任何像素尺寸下绘制,因此它们不需要明确定义的高度或宽度。它们也不总是具有明确定义的纵横比。如果您希望 SVG 缩放以适合您给出的尺寸,您将需要明确提供此信息(以及更多信息)。

如果您不这样做,SVG 根本不会缩放。以下示例使用内联 SVG,调整元素的尺寸(虚线),而从未更改所绘制图形的大小

为什么它会以这种方式表现?因为 SVG 不是(仅仅是)一张图片。SVG 是一个文档。虽然上面的示例使用内联 SVG,但它也可以轻松地使用 <object><iframe>。即使您使用 <img> 标签嵌入相同的 SVG 代码,它看起来也完全相同。

当您包含带有 <iframe> 的 HTML 文件时,您不会期望里面的文本在您更改框架大小时进行缩放。SVG 也是如此。默认情况下,它将以代码中指定的大小绘制,而不管画布的大小如何。如果您为这些 SVG 设置高度或宽度(或两者)为 auto 会发生什么?HTML 替换元素的默认大小将被使用:宽度为 300px,高度为 150px。这适用于 <img><object><iframe>。默认的 300×150 大小也适用于 HTML 文档中的内联 <svg> 元素,但这是 HTML5 规范中最近达成的共识:其他浏览器默认情况下会将内联 SVG 扩展到视窗的整个大小——相当于 width: 100vw; height: 100vh; —— 这是直接在其自己的浏览器选项卡中打开的 SVG 文件的默认大小。Internet Explorer 介于两者之间,对图像和内联 SVG 使用 100% 的宽度和 150px 的高度。

换句话说,即使您认为 300×150 是一个完美的图像大小(尽管为什么您会这样做?),也不要依赖于 <svg> 在 HTML 中具有默认大小。

除了决定您希望 SVG 的大小外,您还需要决定您希望图形如何缩放以适合该大小。下面,我将描述您需要获取最常见情况所需的缩放比例的代码

  • 缩放以适合特定大小,不扭曲图像
  • 缩放以适合特定大小,根据需要拉伸或压缩图形
  • 缩放以适合可用宽度,同时保持宽高比
  • 以非均匀方式进行缩放,以便图形的某些部分以不同于其他部分的方式进行缩放

但首先:如果您想控制 SVG 的比例,您需要熟悉 SVG 缩放属性以及可用的其他工具。

SVG 缩放工具箱

其他图像之所以能够缩放,是因为浏览器知道图像的高度、宽度和纵横比,并且它会一起调整所有内容。为 SVG 提供这些属性是让它进行缩放的第一步。但是,缩放 SVG 超越了其他图像所能实现的范围。

heightwidth 属性

初看 SVG 规范,您可能会认为顶级 svg 元素上的 heightwidth 属性会隐式设置纵横比,从而使 SVG 像其他图像一样进行缩放。没错,设置高度和宽度会在您将 SVG 用作图像时覆盖默认尺寸。但当然,这并不那么容易

  • 如果您使用 <img> 嵌入 SVG,设置 heightwidth 会在 _大多数_ 浏览器中使 SVG 可预测地进行缩放,但在 Internet Explorer 中则不会。使用类似 img { width: 100%; height: auto; } 的 CSS,IE 会自动缩放图像区域以保持宽高比不变,但它不会缩放实际图形以匹配图像尺寸的比例。
  • 如果您使用 <object><embed><iframe> 嵌入 SVG,则在 <svg> 上设置高度和宽度不会改变框架的大小;如果 SVG 太大,您只会得到 iframe 内的滚动条。
  • 如果您使用内联 SVG(即直接在您的 HTML5 代码中使用 <svg>),那么 <svg> 元素将同时扮演双重角色,定义网页内的图像区域以及 SVG 内的图像区域。您使用 CSS 为 SVG 设置的任何高度或宽度都会覆盖 <svg> 上的高度和宽度属性。因此,像 svg {width: 100%; height: auto;} 这样的规则会取消您在代码中设置的尺寸和纵横比,并为您提供内联 SVG 的默认高度。正如上面提到的,它将是 150px 或 100vh,具体取决于浏览器。

所以,忘掉 heightwidth 吧。您实际上并不想设置 _确切的_ 高度和宽度,您希望 SVG 缩放以匹配您在 CSS 中设置的宽度和/或高度。您想要做的是为图像设置一个 _纵横比_,并让图形缩放以适应。您需要一个 viewBox

viewBox 属性

SVG 的 viewBox 是许多魔法汇聚在一个小属性中。它是使矢量图形成为 _可缩放_ 矢量图形的最后一块拼图。viewBox 做了许多事情

  • 它定义了图像的纵横比。
  • 它定义了 SVG 内部使用的所有长度和坐标应如何缩放以适应可用的总空间。
  • 它定义了 SVG 坐标系的原点,即 x=0 且 y=0 的点。

viewBox<svg> 元素的一个属性。它的值是一个由空格或逗号分隔的四个数字列表:x、y、width、height。width 是在 SVG 代码中,以用户坐标/px 为单位的宽度,它应该被缩放以填满你绘制 SVG 的区域的宽度(在 SVG 行话中称为视窗)。类似地,height 是应该被缩放以填满可用高度的 px/坐标数。即使你的 SVG 代码使用其他单位,如英寸或厘米,这些单位也将被缩放以匹配由 viewBox 创建的整体比例。

x 和 y 数字指定在缩放的 viewBox 坐标系中,用于 SVG 视窗左上角的坐标。(坐标从左到右、从上到下递增,与 JavaScript 中识别页面位置的方式相同)。为了简单缩放,你可以将这两个值都设置为 0。但是,x 和 y 值对于两个目的很有用:创建一个以绘图中心为原点的坐标系(这可以使定义和变换形状更容易),或者比原始定义更紧密地裁剪图像。

一些 viewBox 值示例

  • viewBox="0 0 100 100":定义一个 100 个单位宽、100 个单位高的坐标系。换句话说,如果你的 SVG 包含一个以图形为中心,半径为 50px 的圆,它将填满 SVG 图像的高度或宽度,即使图像被全屏显示。如果你的 SVG 包含一个 height="1in" 的矩形,它也将几乎填满屏幕,因为 1 英寸 = 96px 在 CSS 中,所有长度都将被等比例缩放。
  • viewBox="5 0 90 100":几乎相同的视图,但在左右两侧被裁剪了 5%,因此总宽度=90 个单位,左边的 x 坐标=5。
  • viewBox="-50 -50 100 100":一个具有相同比例的视图,但现在左上角的坐标为 (-50, -50)。这意味着右下角的坐标为 (+50, +50)。在 (100, 100) 处绘制的任何形状都将远离屏幕。如果你想绘制一个完全填充图像区域的圆,它将以 (0, 0) 为中心。

一旦你在你的 <svg> 中添加了一个 viewBox(编辑器如 Inkscape 和 Illustrator 会默认添加它),你可以使用该 SVG 文件作为图像,或者作为内联 SVG 代码,它将完美地缩放以适应你给它的任何大小。但是,它仍然不会像任何其他图像一样缩放。默认情况下,**它不会被拉伸或扭曲**,如果你给它不匹配长宽比的尺寸。相反,比例将被调整以保持代码中定义的长宽比。

preserveAspectRatio 属性

viewBox 属性有一个助手,preserveAspectRatio。除非存在 viewBox 来定义图像的长宽比,否则它没有效果。当存在 viewBox 时,preserveAspectRatio 描述了如果 viewBox 的长宽比与视窗的长宽比不匹配,图像应该如何缩放。大多数情况下,默认行为运行良好:图像被缩放,直到它刚好适合高度和宽度,并且它在任何额外的空间内居中。

就像 viewBox 一样,preserveAspectRatio 在单个属性中包含大量信息。默认行为可以用 preserveAspectRatio="xMidYMid meet" 明确设置。第一部分,xMidYMid 告诉浏览器在 x 和 y 方向上将缩放的 viewBox 区域在可用的视窗区域内居中。你可以用 MinMax 替换 Mid 来使图形与一边或另一边对齐。注意驼峰式大小写,因为 SVG 是 XML,因此区分大小写。x 是小写,但 Y 是大写。

默认 preserveAspectRatio 的后半部分,meet,是告诉浏览器缩放图形,直到它刚好适合高度和宽度。对于 CSS 背景图像,它的等价物是 background-size: contain;。SVG 的替代值为 slice(等价于 background-size: cover;)。slice 值将缩放图像以适应更宽松的尺寸,并切断多余的部分。但是,它并不一定会切断多余的部分;这取决于 overflow 属性的值。

如果你希望每张图片都可以在你给它的尺寸中居中,而不是被拉伸或扭曲,新的 object-fit CSS 属性允许你对其他类型的图像执行相同的操作.

还有一个 preserveAspectRatio="none" 选项,允许你的 SVG 像光栅图像一样缩放(但分辨率更高),拉伸或压缩以适应你给它的高度和宽度。

如何将 SVG 缩放以适应特定大小(不扭曲图像)

可能最常见的需求是让 SVG 图标缩放以适应特定大小,不产生扭曲。viewBox 属性实际上是你这里需要的全部,虽然你可以使用 preserveAspectRatio 来调整对齐方式。

如前所述,如果你在图形编辑器中创建你的 SVG,它可能已经包含了一个 viewBox。但是,你可能想调整 viewBox 以获得恰当的位置。对于以下示例,花盆图形被赋予了 viewBox="0 0 60 55"。这在周围留了一些额外的空间;为了创建一个紧密裁剪的图标,你可以使用 viewBox="4.5 1.5 51 49"。以下示例还展示了默认 preserveAspectRatio 的效果,将图形在提供的空间中居中

如何将 SVG 缩放以适应可用宽度(并调整高度以匹配)

具有 viewBox 的 SVG 将缩放以适应你给它的高度和宽度。但自动调整大小呢?对于光栅图像,你可以设置 width height,让另一个缩放以匹配。SVG 能做到吗?

它可以,但它变得复杂。有几种不同的方法可供选择,具体取决于你是如何包含你的 SVG 的。

选项 1:使用图像自动调整大小

当 SVG 文件具有 viewBox 并且它嵌入在 <img> 中时,浏览器将(几乎总是)缩放图像以匹配 viewBox 中定义的长宽比。

然而,Internet Explorer 仍然是 SVG 的祸根。虽然它通常运行良好,但我在此示例的早期版本中使用 display: table-cell 来布局这些图形,IE 以奇怪的方式扭曲了图像。

如果你完全自动调整图像大小,Internet Explorer 会应用标准的默认 300×150 大小。但是,如果图像有 viewBox,其他浏览器会默认应用 { width: 100%; height: auto; };这种行为在任何规范中都没有定义。

所以回顾一下:要自动缩放用作 <img> 的 SVG,

  1. 设置 viewBox 属性。
  2. 设置至少 一个 高度或宽度。
  3. 如果你关心对 Internet Explorer 的支持,不要将它放在表格布局中。

选项 2:使用 CSS 背景图像和 padding-bottom 技巧

在大多数情况下,将 SVG 用作 CSS 背景图像的工作方式与在 <img> 中使用它非常相似(但具有额外的优势,你可以为旧浏览器定义光栅回退)。旧浏览器在将图像转换为光栅后缩放图像而不是之前(即像素化)存在一些错误,但总的来说,viewBox 是你需要的全部。

但是,自动调整大小不是 CSS 背景图像的选项;毕竟,图像应该次要于元素的 HTML 内容。如果你希望元素与你将使用的图像的长宽比完全匹配,你需要进行一些小技巧。

有一些 CSS 属性允许你根据可用宽度调整基于高度的属性。如果你将块布局元素的 borderpaddingmargin 设置为百分比值,这些百分比将相对于容器的可用 宽度 计算,即使对于 顶部底部 边框、填充和边距也是如此。

目标是即使高度是自动的,也能创建大小一致的边框和填充。但这无关紧要。对于我们的目的,关键在于您可以根据宽度调整元素的总高度。换句话说,您可以控制纵横比。要创建一个宽度为 100% 且与用作其背景的图像的 4:3 纵横比完全匹配的 <div>,您可以使用

.ratio4-3 {
 width: 100%;
 background-image: url(image-with-4-3-aspect-ratio.svg);
 background-size: cover;
 height: 0;
 padding: 0; /* reset */
 padding-bottom: calc(100% * 3 / 4);
}

注意事项

  • 要获得所需高度作为可用宽度的百分比,您需要将百分比宽度乘以所需高度因子,再除以所需宽度因子。
  • 如果您想支持不支持 calc() 的浏览器,您需要自己进行计算(或使用 CSS 预处理器)。
  • 如果您默认情况下 将每个元素设置为 box-sizing: border-box,则需要将其重新设置为使用 box-sizing: content-box。毕竟,我们希望它是 height: 0 加上 填充。
  • padding-bottom 属性被用来代替 padding-top 因为 IE5 中存在问题。虽然您可能并不担心支持 IE5,但您最好保持一致。毕竟,它被称为 padding-bottom 技巧。

对于金罐图像,纵横比为 60:55,计算出底部填充为 92%。实际应用中,它看起来像这样

选项 3:使用内联 SVG 和最新的 Blink/Firefox 浏览器

SVG 图像很好,但在很多情况下,您更喜欢使用内联 SVG。内联 SVG 减少了 HTTP 请求数量,允许用户交互,并且可以由主网页中的 CSS 修改。但它会缩放吗?

如果使用最新的 Firefox 或 Blink 浏览器,它将缩放。只需在您的 <svg> 上设置 viewBox,并将高度或宽度之一设置为 auto。浏览器将进行调整,以使整体纵横比与 viewBox 相匹配。很漂亮。

但很有可能,这些不是您需要支持的唯一浏览器。

许多浏览器——IE、Safari 以及 2014 年夏季之前发布的 Opera 和 Chrome 版本——不会自动调整内联 SVG 的大小。如果您没有指定高度和宽度,这些浏览器将应用其通常的默认大小,正如之前提到的,这在不同的浏览器中将是不同的。图像将缩放以适应该高度或宽度,再次在图像周围留下额外的空白。同样,如果您同时将高度和宽度保留为 auto,也会出现不一致的情况。

解决方法是再次使用 padding-bottom 技巧来自己控制纵横比。最简单的方案是使用容器元素,它适用于内联 SVG 以及 <object><iframe> 和其他替换元素,例如 <video>

选项 4:在容器上使用 padding-bottom 技巧

要使用容器 <div>,请向 div 添加类或内联样式以赋予其正确的纵横比,就像上面使用背景图像时所做的那样。但也要在容器上设置 position: relative,以便它成为绝对定位内容的参考系。然后将 SVG(或其他对象)设置为 position: absolute,高度和宽度为 100%。需要绝对定位,以便百分比相对于 <div> 的高度(包括填充)计算,而不是相对于零高度内容区域计算。

除非您有许多具有相同纵横比的图形,否则通常将 padding-bottom 声明为内联是有意义的,以便它紧挨着需要匹配的 viewBox

<div class="scaling-svg-container" 
   style="padding-bottom: 92% /* 100% * 55/60 */">
  <svg class="scaling-svg" viewBox="0 0 60 55">
    <!-- SVG content -->
  </svg>
</div>
.scaling-svg-container {
 position: relative; 
 height: 0; 
 width: 100%; 
 padding: 0;
 padding-bottom: 100%; 
 /* override this inline for aspect ratio other than square */
}
.scaling-svg {
 position: absolute; 
 height: 100%; 
 width: 100%; 
 left: 0; 
 top: 0;
}

容器方法有效,但需要在标记中添加一个额外的包装元素。而且它也不是一个通用的容器:它是一个必须根据 SVG 所需的确切纵横比进行自定义的容器。如果您不希望它缩放至完整的 100%,则情况会更加棘手;您需要使用另一个包装 <div> 来设置所需宽度和其他定位属性。我个人更愿意将有关 SVG 纵横比的所有信息都保存在 SVG 代码本身中。为了对内联 SVG 执行此操作,您需要告诉浏览器在线条之外进行绘制,并绘制到填充区域中。

选项 5:在内联 <svg> 元素上使用 padding-bottom 技巧

要使用 padding-bottom 技巧来控制整个 <svg> 区域的纵横比,官方高度将为(本质上)零。使用默认的 preserveAspectRatio 值,图形将缩放到无。相反,您希望您的图形拉伸以覆盖您给定的整个宽度,并溢出到您仔细设置为正确纵横比的填充区域中。

同样,我喜欢对 padding-bottom 纵横比使用内联样式,因为它需要根据 viewBox 属性进行自定义。在下面的示例中,我还将它用于其他样式属性,虽然如果您有很多需要相同效果的图形,您可以使用类。

<svg viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice"
   style="width: 100%; padding-bottom: 92%; height: 1px; overflow: visible">
  <!-- SVG content -->
</svg>

里面还有一些其他的细节

  • 高度为 1px,而不是 0,否则 SVG 可能根本不会被绘制(Firefox)或根本不会缩放(Chrome)。
  • preserveAspectRatio 使用 YMin 进行垂直对齐,以便图形整齐地对齐到 <svg> 内容区域的顶部,溢出到底部填充区域中。
  • 虽然 overflow: visible 可能是 HTML 的默认值,但需要为 SVG 显式设置它。

如果您希望 SVG 缩放至小于 100% 宽度,请记住相应地调整 padding-bottom。或者使用包装 <div> 来设置大小。

如何缩放、拉伸和压缩 SVG 以完全适合特定尺寸

虽然通常希望保持纵横比,但有时图像是一个抽象或灵活的图像,您希望将其拉伸以适应。

选项 1:使用百分比

一种拉伸以适应的方法是为 SVG 中的所有大小和位置属性使用百分比值。

有关百分比和 SVG 的注意事项

  • 如果您使用百分比进行拉伸和压缩,请勿包含 viewBox(虽然您可以指定默认高度和宽度)。
  • SVG 中的某些长度与高度或宽度没有明确的关联;例如,圆形的半径。如果您在这些情况下使用百分比值,长度将被计算为高度和宽度等效百分比的几何平均值(平方和的平方根,除以 2 的平方根)。这保留了对角线与矩形线的毕达哥拉斯定理关系,但在其他方面有些令人困惑。
  • SVG 中的许多长度无法用百分比指定,最重要的是 <path><polygon> 元素的坐标。

选项 2:使用 preserveAspectRatio="none"

如果您想要一个灵活缩放的 SVG,其中也包含 SVG 路径,则需要使用 viewBoxpreserveAspectRatio="none"。这是一个稍微复杂一些的彩虹版本,带有多云。

请注意,使用 preserveAspectRatio="none" 时,所有内容都会以相同的方式拉伸或压缩,就像您不均匀地缩放其他图像类型一样。这意味着圆形会被拉伸成椭圆形,文本也会被扭曲。为了避免这种情况,您需要使用多种缩放方法。

如何分别缩放 SVG 的部分

viewBoxpreserveAspectRatio 属性非常灵活。一旦你不再把 SVG 当作另一种图片格式,就可以开始思考你想要你的图形在窗口大小改变时如何缩放。

需要认识到的是,你不需要为整个 SVG 定义一个 viewBoxpreserveAspectRatio 选项。相反,你可以使用嵌套的 <svg> 元素,每个元素都有自己的缩放属性,从而使图形的不同部分独立缩放。(你也可以在 <symbol><pattern> 元素上使用这些属性,并且可以在嵌入到 SVG 中的其他图片上使用 preserveAspectRatio)。使用这种方法,你可以创建一个标题图形,它可以拉伸以填充宽屏显示,而不会占用过高的高度。