在网页里模拟命令行或聊天机器人的逐字显示,不一定非要写JavaScript。CSS的animation配合steps函数,就能把一段文本变成“一个字一个字蹦出来”的打字效果。这种做法依赖等宽字体、字符宽度单位和溢出隐藏,理解几个关键属性就能稳定落地。

一、打字效果的核心原理
所谓打字效果,本质上是让存放文字的容器宽度随时间逐渐增加,每次只露出下一个字符,其余部分被隐藏。如果宽度变化是连续的,看起来就像文字被拉伸;只有把动画切成和字符数量相同的离散步骤,才会产生逐字出现的错觉。这正是animation-timing-function里steps的作用。
steps函数接收两个参数,第一个是步数,第二个是跳跃方式,常用的是steps(n, end)或steps(n, start)。在打字效果中,我们通常设置步数等于文本字符数,让每一帧刚好前进一个字符的宽度。配合white-space:nowrap和overflow:hidden,文字就不会换行也不会露出框外。等宽字体(如Consolas、Courier New)能保证每个字符占据相同宽度,这是用ch单位计算总宽度的前提。
二、基础实现代码示例
下面是一段可直接运行的HTML与CSS。我们让一个段落从宽度0动画到11ch,文本正好是11个字符,步数设为11,耗时4秒,就能看到逐字打印。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>CSS打字效果</title>
<style>
.typewriter {
font-family: Consolas, "Courier New", monospace;
white-space: nowrap;
overflow: hidden;
width: 0;
border-right: 2px solid #333;
animation: typing 4s steps(11, end) forwards;
}
@keyframes typing {
from { width: 0; }
to { width: 11ch; }
}
</style>
</head>
<body>
<div class="typewriter">hello world</div>
</body>
</html>
上面的代码里,文本“hello world”包含空格共11个字符,所以width终值是11ch,steps也是11。border-right模拟光标,如果想让光标闪烁,可以再写一个blink动画。forwards保证动画结束后保持最后一帧宽度,否则会缩回0。
需要注意,ch单位表示数字0的宽度,在等宽字体下所有字符宽度一致,因此用ch估算总宽度是准确的。若换用非等宽字体,每个字宽度不同,steps切分就会错位,打字感被破坏。所以字体设置绝不能省略。
三、添加光标闪烁与多段文本
真实打字机有光标,我们可以用伪元素或border配合独立动画实现。下面的例子把宽度动画和光标闪烁分开,避免互相干扰。
.typewriter {
font-family: Consolas, monospace;
white-space: nowrap;
overflow: hidden;
width: 0;
border-right: 2px solid black;
animation: typing 3s steps(8, end) forwards,
blink 0.8s step-end infinite;
}
@keyframes typing {
to { width: 8ch; }
}
@keyframes blink {
50% { border-color: transparent; }
}
这里文本设为“你好世界”四个汉字,但汉字在等宽字体下通常占2个ch,所以实际宽度可能是8ch,steps填8。如果内容动态变化,需要后端或前端模板把字符数算好再写到CSS变量里,否则步数不对就会出现跳字或留白。
多段文字依次打字,可以用动画延迟。比如第二段延迟第一段时长,再执行自己的typing。但纯CSS难以循环修改文本,需要借助多个元素堆叠或JS辅助。因此长文案或可变内容,建议用CSS做静态演示,动态数据交给脚本。
四、优缺点与适用边界
纯CSS打字效果的优点很明显:无脚本依赖、不阻塞主线程、实现简单。在官网hero区、技术文档示例、静态标语等场景非常合适。相比setInterval改textContent,它不会触发重排之外的复杂计算,动画由合成器处理更流畅。
缺点同样突出:字符数必须提前知道,响应式下字体缩放会导致ch基准变化;不支持自动换行,长文本只能横向滚动或固定不换行;可访问性上屏幕阅读器可能读出隐藏文字造成困扰。若项目要求多语言、动态接口数据,还是应该用JS控制,或采用CSS变量加少量脚本注入步数。
| 方案 | 依赖 | 动态文本 | 性能 |
|---|---|---|---|
| CSS steps | 无JS | 不支持 | 优 |
| JS定时器 | 需脚本 | 支持 | 中 |
五、常见误区澄清
有人以为用linear timing也能打字,其实linear是连续宽度增长,文字会像拉帘一样展开而非逐字。必须steps离散切帧。还有人把overflow设为visible,结果文字全堆在框外,完全没打字感。这两个坑都源于对动画函数与盒模型溢出规则的不熟悉。
另一个误区是认为ch单位在所有字体下都等于字符宽。只有在等宽字体上下文,ch才是可靠度量。若设计强制用非等宽中文字体,可改用量em估算或借助JS测量scrollWidth再赋给CSS变量,保证steps和宽度匹配。