解析纯 CSS 实现的打字机效果
https://dev.to/stokry/typing-effect-without-javascript-54ol
该效果的作者为 Stokry 与 Edwin
效果
See the Pen CSS Typing by Erioifpud (@erioifpud) on CodePen.
代码实现
<div class="container">
<div class="type">
This is one cool effect
</div>
</div>
:root {
/** This is the number of characters in the text */
--characters: 24;
}
.container {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: monospace;
font-size: 2em;
}
.type {
max-width: 0;
animation: typing 6s steps(var(--characters)) infinite;
white-space: nowrap;
overflow: hidden;
}
.container:after {
content: " |";
animation: blink 1s infinite;
animation-timing-function: step-end;
}
@keyframes typing {
75%,
100% {
max-width: calc(var(--characters) * 1ch);
}
}
@keyframes blink {
0% {
opacity: 1;
}
25% {
opacity: 0;
}
75% {
opacity: 1;
}
100% {
opacity: 1;
}
}
解析
首先,我们从简单的、用处不大的部分看起,将他们逐个排除。那么第一个看的是 HTML 的结构,一层容器 container
包裹着输入的内容 type
,很简单。
接着是 container
的样式,他看起来只是一个普通的 flex
容器,将内容水平、垂直居中显示,但有个关键的地方:
font-family: monospace;
这个样式将字体改成了等宽的,也就是字符宽度相同的电脑字体,这个的用处后面会解释。
图片来源:维基百科
那么可以先将 container
部分排除了,和动画有关的样式都在 type
上,type
也能分成两部分,分别是光标和打字机。
光标
光标使用伪元素 after
实现,after
的内容 content
就是个光标的形状 " |"
,闪烁通过动画改变透明度 opacity
实现。
animation-timing-function: step-end
是必要的,因为 animation
默认会给状态之间加入过渡的效果,而 animation-timing-function
可以用来取消过渡效果,假如去掉这个样式,光标的闪烁就会变成渐变,从透明度 0 渐变到透明度 1,如下图:
animation-timing-function
的实际用途和用法下面会做详细介绍。
打字机
原理是使用动画改变元素宽度,type
的初始最大宽度 max-width
是 0,并且 overflow: hidden
,也就是说超出元素的文本都会被隐藏,接着 type
在动画中会逐渐变成 “This is one cool effect” 这段话的宽度,文本也就随着宽度变化显示出来了,示意图如下:
但实现起来还是有点技巧的,首先他用了一个 CSS 变量 --characters
来记录文本的长度,这个变量以后要使用两次,接着看动画的最后一个状态:
max-width: calc(var(--characters) * 1ch);
这里用了一个很罕见的单位 ch
,ch
表示了字符 0 的宽度,这里用来表示任意字母的宽度:
前文提到了 container
使用的是等宽字体,那么对于英文字母来说,他们的宽度都是一致的,所以文本的总宽度就能通过 --characters * 1ch
计算得到了(注意,这个效果不支持中文,中文字符的宽度无法用 ch
来表示)。
但只给动画中的 max-width
设置文本的真实宽度后的效果如下:
可以看到文本平滑地“滚”出来了,并不是一个一个字“打”出来的,那么第二个关键点来了:
animation: typing 6s steps(var(--characters)) infinite;
animation
缩写中的第三个参数表示 animation-timing-function
,前面也简单提到了他的用处,实际上这个样式是用来设置动画执行节奏的,现在的动画效果是连续的,从 0ch → 24ch,但我们需要的是 24 个状态,1ch、2ch、3ch......
这就需要 steps
函数来实现了,steps(n, <jumpterm>)
函数的作用是 “把动画分隔成 n 个段落”,比如说 steps(5)
,他会把动画分隔 5 个段落。第二个参数 jumpterm
表示每个分隔段上的状态取舍,默认是 end
。
举个完整的例子,steps(5, end)
先把动画分成 5 个分隔段落:
0% → 20% 一段、20% → 40% 一段,以此类推有 5 段,每段都有起始(比如 0%)和结束状态(比如 20%)。
jumpterm
是用来描述段落上状态取舍的,step-start
会跳过起始状态,举个例子,在一个周期处于 0 到 20% 阶段时,会显示 20% 阶段的状态,直接把 0% 时的状态跳过了。step-end
则相反,在一个周期处于 0 到 20% 阶段时,会显示 0% 阶段的状态,跳过 20% 时的状态,示意图如下:
在一个动画周期内,step-start
、step-end
分别只会显示以上几个时间点的状态,不会产生连贯地过渡动画。
现在回到主题,代码中的 animation
样式就很好理解了,要实现打出 24 个字的效果,就应该把这一段动画分成 24 段,然后跳过中间的过渡部分,直接显示每一个段落的结束状态:
animation: typing 6s steps(var(--characters)) infinite;
/* 默认使用了step-end参数 */
总结
要实现打字机效果,关键点如下:
- 等宽字体
- 隐藏容器外的内容
- 容器初始最大宽度为 0,随着动画推进至文本实际宽度(ch)
- 使用
animation-timing-function
与steps
设置动画节奏与跳帧
123