本质是用width动画配合overflow:hidden截断实现逐字显现,核心为ch单位控制宽度、steps()确保离散帧停、::after伪元素模拟光标;需设white-space:nowrap,动态字数推荐JS注入CSS变量。

用 animation + ch 单位控制逐字显示
打字机效果本质是让文字从左到右“逐渐出现”,关键不是真的一字一字插入 DOM,而是用 width 配合 overflow: hidden 截断,再用 animation 动态拉宽。推荐用 ch 单位(1 ch ≈ 当前字体下 “0” 的宽度),它比 em 或像素更贴合等宽/准等宽场景,避免因字体度量差异导致截断错位。
实操建议:
- 给容器设
white-space: nowrap和overflow: hidden,防止换行和溢出 - 动画的
@keyframes从width: 0到width: <字符数>ch,比如 12 个字就写width: 12ch - 用
steps(12, end)替代默认的ease,确保每步停在整数字符位置,避免模糊过渡 - 若文字含中文或比例字体,
ch可能略窄(尤其全角标点),可微调为12.5ch或改用inline-block+max-width方案
处理光标闪烁:用 ::after 伪元素 + border-left
纯 CSS 打字机必须模拟光标,最稳的方式是用伪元素在末尾加一条竖线,而不是靠 caret-color(它只影响输入框)。
常见错误现象:::after 不显示、光标不随文字同步、闪烁节奏卡顿。
立即学习“前端免费学习笔记(深入)”;
实操建议:
-
content: ""+position: absolute+right: 0定位到容器右端 - 用
border-left: 2px solid #000实现光标,比background更易控制粗细和颜色 - 光标动画单独写一个
@keyframes blink,opacity: 0 → 1 → 0,时长设为0.5s,并用animation-delay错开打字动画结束时间,避免最后一下闪完就消失 - 注意:若容器有
padding-right,伪元素需用transform: translateX(-2px)微调对齐
兼容性与性能:避开 width 重排,优先用 clip-path
老版本 Safari 对 width 动画触发 layout 较敏感,滚动或缩放时可能掉帧;Chrome 100+ 开始支持 clip-path: inset(0 100% 0 0) 从右向左裁剪,性能更好且不触发重排。
使用场景:需要高帧率、页面已有较多动画、目标用户含 iOS 15+ 设备。
实操建议:
- 主动画用
clip-path: inset(0 <初始值> 0 0)→inset(0 0 0 0),初始值按字符数算,如 12 字对应inset(0 88% 0 0)(需手算或 JS 注入) - 降级方案:用
@supports (clip-path: inset(0))包裹新写法,否则回退到width方案 - 避免在
body或大容器上直接套clip-path动画,会导致整个层被提升为合成层,内存占用上升
动态内容怎么办:CSS 无法自动读取文字长度,得靠 JS 补位
CSS 本身没法获取 textContent.length,所以纯 CSS 方案必须提前知道字数——这意味着换内容就得手动改 ch 值或 steps() 参数。
容易踩的坑:用 JS 注入 style 标签但没清空旧规则、多次执行导致重复动画、Vue/React 中 key 未更新引发样式残留。
实操建议:
- 用 JS 获取文字长度后,动态设置
style.setProperty('--char-count', len),CSS 里用width: calc(var(--char-count) * 1ch) - 动画结束后加
animation-fill-mode: forwards,防止 JS 修改前样式回跳 - 若内容由 API 加载,确保在
textNode插入 DOM 后再计算长度(可用requestAnimationFrame保证时机)
真正麻烦的不是怎么动起来,而是字数变多时 ch 单位不准、中英文混排时光标偏移、以及动画中途切换内容时的衔接断裂——这些都得靠具体字符测量或 fallback 到 JS 控制。


















