如何使用 Sass 和 SMIL 选项为 SVG 加载动画创建生成器

Avatar of Mariana Beldi
Mariana Beldi

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

在学习 Vue.js 的过程中,我开始构建免费的 Web 工具,这些工具涉及到 SVG 的探索,目标是学习一些关于这两者的知识! 让我们来看一下其中一个工具:一个生成 SVG 加载动画的生成器,它允许您在 SMIL 或 Sass 动画、不同的样式、颜色、形状和效果之间进行选择。 它甚至允许您粘贴自定义路径或文本,然后下载最终的 SVG、复制代码或在 CodePen 上打开演示。

它是如何开始的

三件巧合促使我构建了一个 SVG 加载动画生成器。

巧合 1:Sarah Drasner 的书

我第一次了解 Sass 循环是在 Sarah Drasner 的 SVG 动画 中。 她展示了如何使用 Sass 函数交错动画(例如在第 6 章“动画数据可视化”中所做的那样)。

我从那一章和 Sass 循环的可能性中获得了灵感。

巧合 2:一个 GIF

在人生的同一时期,我被要求复制一个“加载动画”元素,类似于苹果的旧经典动画。

A round segmented spinner where each segment fades in and out in succession to create a circling effect.
这是我被要求制作的加载动画的模型。

我参考了 Sarah 的示例来实现它。 这是我最终得到的 Sass 循环代码

@for $i from 1 to 12 {
  .loader:nth-of-type(#{$i}) {
    animation: 1s $i * 0.08s opacityLoader infinite;
  }
}
@keyframes opacityLoader {
 to { opacity: 0; }
}

这定义了一个从 1 到 12 的数字变量(i),它会随着每个 :nth-child 元素增加动画的延迟。 这是使用 Sass 仅用两行代码即可动画化任意数量元素的完美用例,从而节省了我为每个需要的延迟声明 CSS 的时间。 这是相同的动画,但使用原生 CSS 书写以显示差异

.loader:nth-of-type(1) {
  animation: 1s 0.08s opacityLoader infinite;
}
.loader:nth-of-type(2) {
  animation: 1s 0.16s opacityLoader infinite;
}

/* ... */

.loader:nth-of-type(12) {
  animation: 1s 0.96s opacityLoader infinite;
}
@keyframes opacityLoader {
  to { opacity: 0; }
}

巧合 3:一个想法

有了这些想法,我想到一个加载动画的“画廊”,其中每个加载动画都是由相同的 Sass 循环生成的。 我总是很难在网上找到这类东西,所以我认为它可能对其他人有用,更不用说我自己了。

我之前已经作为个人项目构建过类似的东西,所以 我最终构建了一个加载动画生成器。 如果你发现其中的错误,请告诉我!

一个加载动画,两种输出

在创建生成正确的 Sass 输出的生成器时,我被自己的开发者技能所阻碍。 我决定尝试使用 SMIL 动画 的另一种动画方法,这就是我最终决定使用的方法。

但后来我得到了一些帮助(谢谢,ekrof!)并且最终让 Sass 工作起来。

所以,我最终向生成器添加了两种选项。 我发现让这两种语言返回相同的结果是一个挑战。 实际上,它们有时会产生不同的结果。

SMIL 与 CSS/Sass

在此过程中,我了解了关于 SMIL 和 CSS/Sass 动画的很多知识。 以下是在构建生成器的过程中帮助我的几个关键要点

  • SMIL 不依赖于任何外部资源。 它通过 SVG 标记中的呈现属性直接为 SVG 创建动画。 这是 CSS 和 Sass 都无法做到的。
  • 当 SVG 作为图像或背景图像嵌入时,SMIL 动画会被保留。 可以直接在 SVG 内部添加 CSS <style> 块,但当然,Sass 则不能这样做。 这就是为什么在生成器中选择 SMIL 选项时,可以选择下载实际 SVG 文件的原因。
  • SMIL 动画看起来更流畅一些。 我找不到原因(如果有人对此有更深入的信息,请分享!)。 我认为这与 GPU 加速有关,但它们似乎都使用相同的动画引擎。
Two spinners, one left and one right. They are both red and consist of circles that fade in and out in succession as an animated GIF.
SMIL(左)和 Sass(右)

您可能会注意到两种语言之间动画链式的差异

  • 我在 SMIL 中使用了 additive="sum" 将动画一个接一个地添加。 这确保每个新的动画效果都能避免覆盖之前的动画。
  • 也就是说,在 CSS/Sass 中,W3C 指出[当] 多个动画尝试修改相同的属性时,列表中名称最接近末尾的动画将获胜。

这就是为什么应用动画的顺序可能会改变 Sass 输出的原因。

使用变换

在加载动画的样式中使用变换是一个大问题。 我已将 transform: rotate 内联应用于每个形状,因为这是一种将它们彼此相邻放置在圆圈中并使面朝向中心的方式。

<svg>
  <!-- etc. -->
  <use class="loader" xlink:href="#loader" transform="rotate(0 50 50)" />
  <use class="loader" xlink:href="#loader" transform="rotate(30 50 50)" />
  <use class="loader" xlink:href="#loader" transform="rotate(60 50 50)" />
  <!-- etc. -->
</svg>

我可以使用 <animateTransform> 在 SMIL 中声明一个 type(例如 scaletranslate)以将该特定变换添加到每个形状的原始变换中

<animateTransform
  attributeName="transform"
  type="translate"
  additive="sum"
  dur="1s"
  :begin="`${i * 0.08}s`"
  repeatCount="indefinite"
  from="0 0"
  to="10"
/>

但是,CSS 中的 transform 覆盖了应用于内联 SVG 的任何先前变换。 换句话说,原始位置重置为 0,并且显示的结果与 SMIL 生成的结果大不相同。 这意味着无论如何动画最终看起来都一样。

The same two red spinners as before but with different results. The SMIL version on the left seems to work as expected but the Sass one on the right doesn't animate in a circle like it should.

使 Sass 类似于 SMIL 的(不太漂亮)解决方案是将每个形状放在一个组 (<g>) 元素中,并将内联旋转应用于组,并将动画应用于形状。 通过这种方式,内联变换不会受到动画的影响。

<svg>
  <!-- etc. -->
  <g class="loader" transform="rotate(0 50 50)">
    <use xlink:href="#loader" />
  </g>
  <g class="loader" transform="rotate(30 50 50)">
    <use xlink:href="#loader" />
  </g>
  <!-- etc. -->
</svg>

现在两种语言的结果非常相似。

我使用的技术

我使用了 Vue.js 和 Nuxt.js。 两者都拥有良好的文档,但我选择它们还有更具体的原因。

我喜欢 Vue 有很多原因

  • Vue 将 HTML、CSS 和 JavaScript 封装为“单个文件组件”,其中所有代码都位于一个更容易处理的单个文件中。
  • Vue 绑定和动态更新 HTML 或 SVG 属性的方式非常直观。
  • HTML 和 SVG 不需要任何额外的转换(例如使代码与 JSX 兼容)。

至于 Nuxt

  • 它有一个快速样板,可以帮助您专注于开发而不是配置。
  • 它具有自动路由功能,并支持自动导入组件。
  • 它具有良好的项目结构,包括页面、组件和布局。
  • 借助元标记,它更容易优化 SEO。

让我们看看一些加载动画示例

我喜欢最终结果的一点是,生成器不仅仅是一个单一功能的工具。 使用它并没有唯一的方法。 因为它输出 SMIL 和 CSS/Sass,所以有几种方法可以将加载动画集成到您自己的项目中。

下载 SMIL SVG 并将其用作 CSS 中的背景图片

就像我之前提到的,当 SVG 用作背景图片文件时,SMIL 功能会被保留。因此,只需从生成器下载 SVG,将其上传到您的服务器,并在 CSS 中将其引用为背景图片即可。

类似地,我们可以将 SVG 用作伪元素的背景图片

将 SVG 直接放入 HTML 标记中

SVG 不必是背景图片。毕竟它只是代码。这意味着我们可以简单地将生成器中的代码放入我们自己的标记中,并让 SMIL 完成它的工作。

在内联 SVG 上使用 Sass 循环

这正是我最初想要做的事情,但我遇到了一些障碍。与其为每个动画编写 CSS 声明,我们可以使用生成器生成的 Sass 循环。循环针对已应用于输出 SVG 的 .loader 类。因此,一旦 Sass 编译成 CSS,我们就得到了一个不错的旋转动画。

我仍在努力改进这个功能

我最喜欢的生成器部分是自定义形状选项,您可以在其中添加文本、表情符号或任何 SVG 元素到组合中。

The same circle spinner but using custom SVG shapes: one a word, one a poop emoji, and bright pink and orange asterisk.
自定义文本、表情符号和 SVG

我想做的是为样式添加第三个选项,只使用一个元素,这样您就可以使用您自己的 SVG 元素。这样,需要处理的内容更少,同时允许更简单的输出。

这个项目的挑战在于处理许多事物的自定义值,例如持续时间、方向、距离和度数。对我个人来说,另一个挑战是更熟悉 Vue,因为我想回去清理那些凌乱的代码。也就是说,该项目是开源的,并且欢迎提交请求!请随时发送建议、反馈,甚至 Vue 课程推荐,特别是与 SVG 或制作生成器相关的课程。

这一切都始于我在一本书中读到的一个 Sass 循环。它不是世界上最干净的代码,但我对 SMIL 动画的强大功能感到震惊。我强烈推荐Sarah Soueidan 的指南,以更深入地了解 SMIL 的功能。

如果您对 SMIL 的安全性感到好奇,这是有充分理由的。曾经有一段时间,Chrome 将完全弃用 SMIL(参见 MDN 中的开头说明)。但是,该弃用已被暂停,并且(似乎)已经有一段时间没有讨论过了。

我可以使用 SMIL 吗?

此浏览器支持数据来自Caniuse,其中包含更多详细信息。数字表示浏览器从该版本及更高版本开始支持该功能。

桌面

ChromeFirefoxIEEdgeSafari
54不支持796

移动/平板电脑

Android ChromeAndroid FirefoxAndroidiOS Safari
12712736.0-6.1