用 animation + ch 单位实现打字效果需设 inline-block、overflow: hidden、white-space: nowrap,width 从 0 到 nch 动画,时长按 0.1s/字动态计算;光标用 border-right 配合 blink-caret 动画;ios safari 跳帧可拆分元素或改用 js 实现。

用 animation + ch 单位实现基础打字效果
核心是让文字逐字“显现”,本质是控制 width 从 0 到目标宽度,并配合 overflow: hidden 截断。用 ch 单位比 em 或 px 更可靠——它代表当前字体中 “0” 字符的宽度,对等宽/非等宽字体都具备可预测性。
关键点:
-
white-space: nowrap必须设置,否则换行会破坏宽度计算 - 动画时长建议按字符数动态算:比如 24 字符 →
2.4s(即0.1s每字),太慢显得迟滞,太快看不清 - 必须设
display: inline-block或block,否则width对inline元素无效
.typewriter {
display: inline-block;
overflow: hidden;
white-space: nowrap;
animation: typing 3.2s steps(32, end), blink-caret 0.75s step-end infinite;
}
@keyframes typing {
from { width: 0 }
to { width: 32ch } /* 字符数要和实际文字一致 */
}
解决中文/全角字符下 ch 计算不准的问题
ch 在多数中文字体里≈汉字宽度,但不绝对。比如某些思源黑体变体中,“一”比“0”窄,导致 32ch 实际撑不满 32 个汉字,末尾被截断。这不是 bug,是字体度量差异。
稳妥做法:
- 用
monospace字体栈兜底(如font-family: ui-monospace, 'Cascadia Code', monospace),中英文都更接近等宽预期 - 改用
max-width+fit-content组合:先让容器自然撑开,再用width动画覆盖,避免硬写ch值 - 对已知固定文案,直接用 JS 读取
offsetWidth后设为动画终点值(适合 SSR 后 hydrate 场景)
光标闪烁必须用 border-right 而非伪元素
很多人用 ::after 画光标,结果动画卡顿或不同步。根本原因是伪元素无法参与主元素的 steps() 动画节奏,且在部分浏览器中触发重排。
正确方式是复用 border-right:
- 给文字容器设
border-right: 2px solid #000 - 在
@keyframes中只控制border-right-color透明/不透明,或直接border-right-width开关 - 务必加
box-sizing: border-box,否则border会影响宽度动画基准
.typewriter {
border-right: 2px solid #000;
box-sizing: border-box;
}
@keyframes blink-caret {
50% { border-color: transparent; }
}
移动端 Safari 下 steps() 动画跳帧的绕过方案
iOS 16+ 之前,Safari 对 steps(N, end) 的支持有偏差,尤其 N > 20 时容易合并步进,导致“两字一闪”。这不是 CSS 写错,是渲染管线优化过度。
实测有效的应对:
- 把长句拆成多个
<span></span>,每段 ≤ 12 字,分别加动画(用animation-delay串连) - 放弃
steps(),改用 JS 控制textContent逐字插入 +requestAnimationFrame,兼容性 100%,CPU 开销可控 - 对纯展示场景,用
animation-timing-function: steps(1, end)配合极短周期(0.05s)反复触发,视觉上仍像逐字
真正麻烦的不是怎么动起来,而是不同设备上“看起来是不是一个字一个字出来的”——这取决于字体、DPR、GPU 加速状态,甚至系统辅助功能是否开启。上线前至少要在 iOS 真机、Chrome Android、Windows Edge 三端并排对比。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











