在 SVG 中使用和重用所有东西... 甚至动画!

Avatar of Mariana Beldi
Mariana Beldi

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

如果您熟悉 SVG 和 CSS 动画并开始经常使用它们,那么在开始工作之前,您可能需要记住一些想法。 本文将介绍如何使用 **<use> 元素、CSS 变量和 CSS 动画** 来构建和优化您的代码。

实时演示

第一部分:SVG <use> 元素

如果您是一位喜欢保持代码 DRY 或非常喜欢 Sass/CSS 变量的开发人员,那么您很有可能会喜欢这个标签。

假设您的图形中多次重复了一个元素。 与在 SVG 中多次重复代码的复杂部分相比,您可以定义此部分一次,然后使用 <use> 元素将其克隆到文档中的其他位置。 这不仅会减少大量的代码,还会使您的标记更简单,更易于操作。

要开始实施 <use> 元素,请转到您的 SVG 并按照以下步骤操作

  1. 识别要克隆的代码部分
  2. 为此部分添加一个 ID
  3. 在您的 <use> 标签中链接它,如下所示:<use xlink:href="#id"/>

就是这样! 您的新克隆已准备就绪,现在您可以更改其属性(例如 xy 位置)以满足您的需求。

让我们深入研究一个非常方便的示例

我想分享这个真实案例,我需要动画制作一个由小立方体单元组成的巨大立方体。(想象一下经典的 魔方。)

我们将首先使用基本形状和变换在 SVG 中绘制立方体单元

<svg viewBox="-130 -20 300 100">
  <g id="cube">
    <rect width="21" height="24" transform="skewY(30)"/>
    <rect width="21" height="24" transform="skewY(-30) translate(21 24.3)"/>
    <rect width="21" height="21"  transform="scale(1.41,.81) rotate(45) translate(0 -21)"/>
  </g>
</svg>

请注意,形状分组在一个 <g> 元素中,以便我们可以将 ID 添加到整个图形。

接下来,让我们构建一个更大的立方体,克隆此单元。 首先,我们需要将上一个示例中的立方体包装在 SVG 内部的 <defs> 标签中。 在 <defs> 元素中,我们可以放置任何想要重用的内容,这可以是一个形状、一个组、一个渐变... 几乎任何 SVG 元素。 除非我们在该标签之外使用它们,否则它们不会在任何地方渲染。

然后,我们可以使用其 ID 链接单元体任意多次,并像这样更改每个克隆的 xy 位置

<use xlink:href="#cube" x="142" y="124"/>
<use xlink:href="#cube" x="100" y="124"/>
<!-- ... -->

现在我们必须定位每个立方体,记住最后一个元素将出现在最前面,之后我们将准备好第一个大立方体!

xlink:href 自 SVG2 以来已弃用,但出于兼容性目的最好使用它。 在现代浏览器中,您只需使用 href,但我已在 Safari 上对其进行了测试,在撰写本文时它在那里无法使用。 如果您使用 xlink:href,请确保在您的 SVG 标签中包含此命名空间:xmlns:xlink="http://www.w3.org/1999/xlink"(如果您决定使用 href,则无需它)。

第二部分:使用 CSS 变量将不同的样式应用于重用图形

我为立方体选择了主色,它是一种较浅和较深的侧面阴影和描边颜色。 但是如果我们想让第二个立方体变成不同的颜色怎么办?

我们可以用 CSS 变量替换填充和描边,以使这些属性更灵活。 这样,我们将能够使用另一个调色板重用相同的立方体单元(而不是为第二个立方体定义第二个单元,该单元具有不同的颜色)。

为什么不向新的立方体添加一个类并用 CSS 更改填充颜色? 我们会这样做,但首先,尝试检查一个 <use> 元素。 您会注意到它在 Shadow DOM 中呈现。 这意味着它不受脚本和样式的影响,就像普通 DOM 中的元素一样。 您在 <defs> 中图形中定义的任何值都将被其所有实例继承,您将无法用 CSS 重写它们。 但是,如果您用变量替换这些值,那么您就可以 在 CSS 中控制它们

在我们的立方体单元中,我们将遍历每个侧面并将填充和描边值替换为语义变量名称。

例如,这

<rect fill="#00affa" stroke="#0079ad" />

... 可以用以下内容替换

<rect fill="var(--mainColor)" stroke="var(--strokeColor)" />

从这里开始,我们必须复制 SVG 以构建第二个立方体。 但是,如果我们将两者都保存在同一个文档中,则无需复制 <defs>。 我们可以向每个 SVG 添加一个类,并通过 CSS 控制调色板,重新定义变量的值。

让我们为蓝色立方体创建一个调色板,为粉色立方体创建另一个调色板

.blue-cube {
  --mainColor: #009CDE;
  --strokeColor: #0079ad;
  --lightColor: #00affa;
  --darkColor: #008bc7;
}

.pink-cube {
  --mainColor: #de0063;
  --strokeColor: #ad004e;
  --lightColor: #fa0070;
  --darkColor: #c7005a;
}

这样,我们可以添加任意数量的立方体,并从一个地方更改所有颜色。

第三部分:重用动画

此实例的想法是在悬停时打破立方体 - 类似于爆炸视图,因此当我们将光标放在立方体上时,一些部件会从中心移开。

让我们从定义两个动作开始,一个用于每个轴:move Ymove X。 通过将动画划分为动作,我们将能够在每个立方体中重用它们。 动画将包括将立方体从其初始位置移动到一个方向上 30px 或 50px 处。 我们可以使用变换平移 (XY) 来实现这一点。 例如

@keyframes moveX {
  to { transform: translateX(-35px);  }
}

但是,如果我们想重用此动画,最好用变量替换数值,如下所示

@keyframes moveX {
  to { transform: translateX(var(--translate, 35px)); }
}

如果未定义变量,则默认值为 35px。

现在我们需要至少一个类来绑定到动画。 但是,在这种情况下,我们需要两个类来在 x 轴上移动立方体:.m-left.m-right

.m-left, .m-right { 
  animation: 2s moveX alternate infinite; 
}

为了使立方体向左移动,我们需要一个负值,但我们也可以声明一个不同的数字。 我们可以像这样在 .m-left内部定义我们的变量

.m-left { --translate: -50px; }

这里发生的事情是,当我们将类 .m-left 添加到一个元素时,这将播放动画 moveX(在 @keyframes 中定义的动画),该动画将持续两秒钟,并在 x 轴上平移,到达左侧 -50px 的新位置。 然后,动画交替方向,以便它从最后一个位置移动,并再花两秒钟回到其原始状态。 等等,因为它是一个无限循环。

我们可以为 .m-right 类声明另一个变量,但如果我们不这样做,请记住它将采用我们最初声明的 35px。

默认的 animation-play-state 值是运行的,但我们可能不想让立方体一直移动。 在网站上使用一些附近的相关内容时,这会非常分散注意力,而且令人讨厌。 因此,让我们尝试仅在悬停时播放动画,通过添加以下内容

svg:hover .m-left {
  animation: 2s moveX alternate infinite;
}

您可以自己尝试一下,会发现每次将光标从立方体移开时,动画都会非常快地跳转到初始状态。 为避免这种情况,我们可以在动画速记的末尾添加值 paused

.m-left {
  animation: 2s moveX alternate infinite paused;
}

现在动画已暂停,但通过添加以下 CSS 行,它将在悬停时运行

svg:hover * { 
  animation-play-state: running; 
}

我们可以将每个类应用于 SVG 中的不同元素。 在第一个蓝色立方体中,我们移动单个立方体; 在第二个立方体中,我们将这些类应用于立方体组。

最后一件事...

直到后来我才意识到,我可以重用一个单元来构建所有单元。 我对小立方体进行了处理,使其具有等距性,以便它可以轻松地与旁边的其他立方体对齐。 此时,我的单元是一个 <path>,但我决定用 SVG 形状替换它,以减少代码并获得更清晰的标记。

我了解到,在绘制每个形状并处理大量代码之前,最好花一些时间分析一下 SVG 可以做什么。 这可能需要更多时间,但在长远来看会为您节省大量时间和精力。