如何使用不规则 SVG 笔画实现手写动画

Avatar of Trapti Rahangdale
Trapti Rahangdale

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

我想要为书法字体制作一个手写动画——那种文字 像被无形的笔写出来一样动画化 的动画。由于书法字体具有不均匀的笔画宽度(实际上,它们在 SVG 方面甚至不是笔画),因此使用典型的路径动画技术几乎无法做到这一点。但我发现了一种创新的 SVG 遮罩应用,可以在几分钟内实现这种效果。

在研究如何做到这一点时,我从多个来源收集了信息。我将它们组合在一起,并能够创建最终效果。

让我们一起制作它!

SVG 遮罩

如果一个词或一句话中所有字母的笔画宽度始终均匀,那么 Craig Roblewsky 有一个不错的方法可以制作手写动画 。这是一种巧妙的技术,可以对 SVG 的 stroke-dasharray 和 stroke-offset 属性进行动画处理。

像我们想要在这里进行动画处理的书法字体,在整个字母中具有不均匀的笔画宽度,因此它需要是一个 <path> ,并且以这种方式对其进行动画处理将不起作用。诀窍是使用 SVG 遮罩。

让我们从确定要使用的字体开始。我在本文中将一直使用的是 HaveHeartOne ,它具有非常不错的笔刷笔画外观,非常适合手写。

想法是使用我们要动画化的同一句话创建一个 <mask> ,然后将其放置在该句话的顶部。换句话说,将有两层相同的句子。由于遮罩位于顶部,因此我们将将其设置为白色,这样它将隐藏其下方的原始句子。我们将对遮罩进行动画处理,以便在动画运行时显示底层。

制作图层

此技巧的基础是我们将实际创建两个独立的图层,一个在另一个的顶部。

  1. 底层是使用所需字体(在我的情况下是 HaveHeartOne )的文字。
  2. 顶层是手工制作的路径,近似于这些文字。

创建手工制作的路径并不像您想象的那么难。我们需要一个连续的路径来进行动画处理并显示句子。这意味着这里没有 <text> 。但是,许多插图应用程序(包括 Illustrator)可以将字母转换为路径。

  1. 选择文字。
  2. 打开“属性”面板,然后点击**创建轮廓**。

然后,就像变魔术一样,字母变成了具有遵循形状的矢量点的轮廓。

Showing the words Marketing Lab in red in Illustrator wrapped in blue vector points.
想象一下用手画出所有这些!

此时,为这些路径(存储为图层)提供有意义的名称非常重要。当我们期望将此导出为 SVG 时,应用程序将生成代码,并且它使用这些图层名称作为 ID 和类。

Showing the Illustrator layers of the letters with proper naming.
这些名称比自动生成的名称好得多。

请注意,单个字母具有 fill 但没有 stroke

Showing the letter M selected in Illustrator with the properties panel open indicating there is no stroke on the shape.

在 SVG 中,我们可以以我们想要的方式对 stroke 进行动画处理,因此我们需要将其创建为我们的第二个主要图层,即遮罩。我们可以使用钢笔工具来描绘字母。

  1. 选择钢笔工具。
  2. 将填充选项设置为“无”。
  3. 笔画宽度将取决于您使用的字体。我将笔画宽度选项设置为 5px,并将颜色设置为黑色。
  4. 开始绘画!

我的钢笔工具技能不是很好,但这没关系。重要的是不是完美,而是遮罩覆盖了其下方的图层。

为每个字母创建遮罩,并记住为图层使用好的名称。并且一定要在有多个相同字母的地方重复使用遮罩——没有必要反复重新绘制相同的“A”字符。

Showing the Marketing Lab letter shapes completely covered by the black outline layers.

导出

接下来,我们需要导出 SVG 文件。这可能取决于您使用的应用程序。在 Illustrator 中,您可以使用**文件→导出→导出为→SVG**进行操作。

SVG 选项弹出窗口将打开,以下是为本示例导出首选设置。

Showing the SVG export options in Illustrator.

现在,并非所有应用程序都以完全相同的方式导出 SVG。一些应用程序在生成精简、高效的代码方面做得很好。另一些则不然。无论如何,最好在代码编辑器中打开该文件

当我们使用 SVG 时,有一些技巧需要考虑,以帮助它们尽可能轻量化,以提高性能。

  1. 点数越少,文件越轻。
  2. 使用较小的 viewBox 可以有所帮助。
  3. 大量工具 可以进一步优化 SVG。

手动编辑 SVG 代码

现在, 并非所有应用程序 都以完全相同的方式导出 SVG。一些应用程序在生成精简、高效的代码方面做得很好。另一些则不然。无论如何,最好在代码编辑器中打开该文件并进行一些更改。

一些值得做的事情

  1. <svg> 元素提供 widthheight 属性,这些属性设置为调整最终设计的大小。
  2. 使用 <title> 元素。由于我们使用的是路径,因此屏幕阅读器实际上无法识别这些文字。如果您需要在获得焦点时读取它们,那么这将起作用。
  3. 可能存在具有基于在插图应用程序中命名的图层的 ID 的组元素 (<g>) 。在本演示中,我有两个组元素: #marketing-lab (轮廓)和 #marketing-masks (遮罩)。将遮罩移动到 <defs> 元素中。这将在视觉上隐藏它,这也是我们想要的效果。
  4. 遮罩组中可能存在路径。如果是这样,请继续从其中删除 transform 属性。
  5. 将每个路径元素包装在 <mask> 中,并为其提供一个 .mask 类和一个指示遮罩了哪个字母的 ID。

例如

<mask id="mask-marketing-M">
  <path class="mask" id="mask-M" ... />
</mask>

在轮廓组(我为其提供了 #marketing-lab 的 ID)中,通过使用 mask="url(#mask-marketing-M)" 将遮罩应用于相应的字符路径元素。

<g id="marketting-lab">
  <path mask="url(#mask-marketing-M)" id="marketting-char-M" d="M427,360, ... " />
</g>

以下是使用所有上述修改对一个字符进行编码的代码

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 381 81" class="marketing-lab">
  <title>Marketing Lab</title>
  <defs>
    <g id="marketing-masks">
      <mask id="mask-marketing-M">
        <path class="mask" id="mask-M"
          d="M375.5, ... ,9-10" stroke-linecap="square" stroke-linejoin="bevel" stroke-width="7" />
      </mask>
    </g>
  </defs>
  <g id="marketting-lab">
    <path
      mask="url(#mask-marketing-M)" id="marketting-char-M" 
      d="M427,360.22c-.11.08-.17.14-.17.18H427c0" />
  </g>
</svg>

最后,我们将为 .mask 元素添加 CSS,该元素将使用白色覆盖笔画颜色,使其隐藏在文档的背景颜色中。

.mask {
  fill: none;
  stroke: #fff;
}

动画

我们将对 CSS 的 stroke-dasharray 属性进行动画处理,以获得连续的线条显示效果。我们可以使用 CSS 和 JavasScript 或 Greensock (GSAP) 来进行动画处理。我们将看看这两种方法。

CSS 和 JavaScript

在 CSS 中单独进行此操作相当简单。您可以使用 JavaScript 计算路径长度,然后使用返回的值对其进行动画处理。如果您根本不想使用 JavaScript,则可以计算一次路径长度,并将该值硬编码到 CSS 中。

/* Set the stroke-dasharray and stroke-dashoffset */
.mask {
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
}


/* Animation the stroke-dashoffset to a zero length */
@keyframes strokeOffset {
  to {
    stroke-dashoffset: 0;
  }
}


/* Apply the animation to each mask */
#mask-M {
  animation: strokeOffset 1s linear forwards;
}

如果您希望使用这种方法,JavaScript 可以帮助您进行计数

// Put the masks in an array
const masks = ['M', 'a', 'r', 'k-1', 'k-2', 'e', 't-line-v', 't-line-h', 'i-2', 'i-dot', 'n', 'g', 'lab-l', 'lab-a', 'lab-b']


masks.forEach((mask, index, el) => {
  const id = `#mask-${mask}` // Prepend #mask- to each mask element name
  let path = document.querySelector(id)
  const length = path.getTotalLength() // Calculate the length of a path
  path.style.strokeDasharray = length; // Set the length to stroke-dasharray in the styles
  path.style.strokeDashoffset = length; // Set the length to stroke-dashoffset in the styles
})

GSAP

GSAP 有一个 drawSVG 插件,允许您逐步显示(或隐藏)SVG <path><line><polyline><polygon><rect><ellipse> 的**描边**。在幕后,它使用 CSS stroke-dashoffsetstroke-dasharray 属性。 

以下是它的工作原理

  1. 在代码中包含 GSAP 和 drawSVG 脚本。
  2. 使用 autoAlpha 在开始时隐藏图形。
  3. 创建一个时间轴。
  4. 将图形的autoAlpha 设置为 true
  5. 将所有字符路径蒙版 ID 按正确顺序添加到时间轴。
  6. 使用 drawSVG 动画化所有字符。

参考资料

  1. Jake Archibald 的 SVG 动画线绘制
  2. Cassie Evans 的创建我的 Logo 动画