我想要为书法字体制作一个手写动画——那种文字 像被无形的笔写出来一样动画化 的动画。由于书法字体具有不均匀的笔画宽度(实际上,它们在 SVG 方面甚至不是笔画),因此使用典型的路径动画技术几乎无法做到这一点。但我发现了一种创新的 SVG 遮罩应用,可以在几分钟内实现这种效果。
在研究如何做到这一点时,我从多个来源收集了信息。我将它们组合在一起,并能够创建最终效果。
让我们一起制作它!
SVG 遮罩
如果一个词或一句话中所有字母的笔画宽度始终均匀,那么 Craig Roblewsky 有一个不错的方法可以制作手写动画 。这是一种巧妙的技术,可以对 SVG 的 stroke-dasharray 和 stroke-offset 属性进行动画处理。
像我们想要在这里进行动画处理的书法字体,在整个字母中具有不均匀的笔画宽度,因此它需要是一个 <path>
,并且以这种方式对其进行动画处理将不起作用。诀窍是使用 SVG 遮罩。
让我们从确定要使用的字体开始。我在本文中将一直使用的是 HaveHeartOne ,它具有非常不错的笔刷笔画外观,非常适合手写。

想法是使用我们要动画化的同一句话创建一个 <mask>
,然后将其放置在该句话的顶部。换句话说,将有两层相同的句子。由于遮罩位于顶部,因此我们将将其设置为白色,这样它将隐藏其下方的原始句子。我们将对遮罩进行动画处理,以便在动画运行时显示底层。
制作图层
此技巧的基础是我们将实际创建两个独立的图层,一个在另一个的顶部。
- 底层是使用所需字体(在我的情况下是 HaveHeartOne )的文字。
- 顶层是手工制作的路径,近似于这些文字。
创建手工制作的路径并不像您想象的那么难。我们需要一个连续的路径来进行动画处理并显示句子。这意味着这里没有 <text>
。但是,许多插图应用程序(包括 Illustrator)可以将字母转换为路径。
- 选择文字。
- 打开“属性”面板,然后点击**创建轮廓**。
然后,就像变魔术一样,字母变成了具有遵循形状的矢量点的轮廓。

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

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

在 SVG 中,我们可以以我们想要的方式对 stroke
进行动画处理,因此我们需要将其创建为我们的第二个主要图层,即遮罩。我们可以使用钢笔工具来描绘字母。
- 选择钢笔工具。
- 将填充选项设置为“无”。
- 笔画宽度将取决于您使用的字体。我将笔画宽度选项设置为 5px,并将颜色设置为黑色。
- 开始绘画!
我的钢笔工具技能不是很好,但这没关系。重要的是不是完美,而是遮罩覆盖了其下方的图层。
为每个字母创建遮罩,并记住为图层使用好的名称。并且一定要在有多个相同字母的地方重复使用遮罩——没有必要反复重新绘制相同的“A”字符。

导出
接下来,我们需要导出 SVG 文件。这可能取决于您使用的应用程序。在 Illustrator 中,您可以使用**文件→导出→导出为→SVG**进行操作。
SVG 选项弹出窗口将打开,以下是为本示例导出首选设置。

现在,并非所有应用程序都以完全相同的方式导出 SVG。一些应用程序在生成精简、高效的代码方面做得很好。另一些则不然。无论如何,最好在代码编辑器中打开该文件
当我们使用 SVG 时,有一些技巧需要考虑,以帮助它们尽可能轻量化,以提高性能。
- 点数越少,文件越轻。
- 使用较小的
viewBox
可以有所帮助。 - 有 大量工具 可以进一步优化 SVG。
手动编辑 SVG 代码
现在, 并非所有应用程序 都以完全相同的方式导出 SVG。一些应用程序在生成精简、高效的代码方面做得很好。另一些则不然。无论如何,最好在代码编辑器中打开该文件并进行一些更改。
一些值得做的事情
- 为
<svg>
元素提供width
和height
属性,这些属性设置为调整最终设计的大小。 - 使用
<title>
元素。由于我们使用的是路径,因此屏幕阅读器实际上无法识别这些文字。如果您需要在获得焦点时读取它们,那么这将起作用。 - 可能存在具有基于在插图应用程序中命名的图层的 ID 的组元素 (
<g>
) 。在本演示中,我有两个组元素:#marketing-lab
(轮廓)和#marketing-masks
(遮罩)。将遮罩移动到<defs>
元素中。这将在视觉上隐藏它,这也是我们想要的效果。 - 遮罩组中可能存在路径。如果是这样,请继续从其中删除
transform
属性。 - 将每个路径元素包装在
<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-dashoffset
和 stroke-dasharray
属性。
以下是它的工作原理
- 在代码中包含 GSAP 和 drawSVG 脚本。
- 使用 autoAlpha 在开始时隐藏图形。
- 创建一个时间轴。
- 将图形的
autoAlpha
设置为true
。 - 将所有字符路径蒙版 ID 按正确顺序添加到时间轴。
- 使用 drawSVG 动画化所有字符。
@CSS-Tricks
你应该让 Craig Roblewsky @PointC 来自 https://www.motiontricks.com/ 写一篇关于 SVG 手写字体的客座文章。或者至少在文章中提到他下面的现代教程。;-)
https://www.motiontricks.com/animated-handwriting-effect-part-1/
https://www.motiontricks.com/animated-handwriting-effect-part-2/
https://www.motiontricks.com/svg-calligraphy-handwriting-animation/
motiontricks 是一个关于 SVG 和 SVG + GSAP 的很棒的资源网站。
不错的文章!有
pathLength
SVG 属性可以简化 CSS 线动画(无需计算 dasharray 和 dashoffset):https://css-tricks.cn/a-trick-that-makes-drawing-svg-lines-way-easier/很酷的效果。
这里有一个让描边更快的小技巧
https://community.adobe.com/t5/illustrator/convert-a-solid-filled-shape-into-a-single-line/td-p/7614879?page=1
谢谢:)
哇。我使用来自 Microsoft 商店的 Boxy SVG 编辑器。这个教程可以应用到 Boxy 吗?
我没有使用过 Boxy,这个教程在您能以我教程中解释的方式从任何编辑器中获取 SVG 时很有用。
只是想说,我非常喜欢这篇文章!我也能够使用每个路径设置的 css
animation-delay
来实现字母之间的延迟:) 感谢您的帮助。我得到了我想要的效果。我很高兴听到这个。
我正在尝试使用 GreenSock 进行本地测试,但我在控制台日志中不断收到以下错误
Uncaught TypeError: Cannot read property ‘getComputedStyle’ of undefined
我已经确保包含了引用 DrawSVG 和 GSAP 库的外部源标签。我不明白问题出在哪里。
请帮忙?
“getComputedStyle” 是 javascript 方法(https://mdn.org.cn/en-US/docs/Web/API/Window/getComputedStyle)。您试图计算样式的特定项目未定义。确保它是正确的。