steps() 配合 ch 单位是目前最稳定可控的纯 CSS 打字机实现方式,需确保字符数准确、字体等宽且容器无干扰样式;ch 与字符宽度绑定,天然适配字号缩放,而 % 和 px 易受布局或字体变化影响。

用 steps() 配合 ch 单位控制 width 是目前最稳定、最可控的纯 CSS 打字机实现方式,前提是字符数准确、字体等宽、容器无干扰样式。
为什么必须用 ch 而不是 % 或 px
ch 表示当前字体中 “0” 字符的宽度,它与字符实际渲染宽度绑定,不受父容器缩放或行高影响;而 % 依赖容器宽度(可能被 padding/border 干扰),px 则完全无法跨字体、跨字号复用。中英文混排时,只要字体支持(如 font-family: "PingFang SC", "Microsoft YaHei", monospace),ch 仍能按“视觉字符格”对齐——浏览器会把每个中文、英文字母、标点都当作 1ch 宽来处理(前提是没开连字)。
- Firefox 对连字(如
fi、fl)默认按 1ch 算,但显示占位可能略宽,加font-variant-ligatures: none可规避 - 如果用了
rem或em做width动画,字号一变,步进就错位;ch天然随font-size缩放,无需重算 -
width: 0ch → 12ch比width: 0% → 100%更可靠:后者在响应式容器里容易因换行或 flex 布局导致终点不精准
steps(n, end) 的 n 必须严格等于字符数
这个 n 不是估算,不是 textContent.length 就完事——它必须包含所有可见字符:中文、英文、空格、标点、全角/半角符号,一个都不能少。少 1,最后一字永远出不来;多 1,动画末尾会卡顿一帧空白。
- 手动数最稳妥,尤其当文本含零宽空格、软连字符、Unicode 控制符时,
.length会误判 - 若用 JS 动态注入文本,必须同步更新 CSS 变量:
el.style.setProperty('--char-count', text.length),然后在@keyframes中引用width: calc(var(--char-count) * 1ch) - 不要写
steps(10, start):start 会让第一帧就显示第一个字,丢失“光标在起点等待”的初始感;end才符合打字节奏——每步结束才跳变
光标闪烁必须用独立动画 + step-end
光标不能和打字动画耦合。用同一个 animation 里塞两个 @keyframes 会导致节奏漂移,尤其当打字动画时长变化时,光标会不同步。
立即学习“前端免费学习笔记(深入)”;
- 推荐用
border-right:宽度设为.15em(比px更适配字号缩放),颜色用currentColor保持主题一致 - 闪烁动画必须用
step-end,不是steps(2, end):前者保证每次状态切换都在帧末瞬时完成,避免模糊过渡;后者在某些 Safari 版本中会有微延迟 - 如果容器有
padding,光标位置会偏移,此时改用伪元素::after绝对定位更可控,且可自由定制形状(方块、下划线、竖线)
box-sizing 和父容器宽度是隐形陷阱
哪怕所有参数都对,动画不动或终点偏移,八成是 box-sizing 或父容器干扰了 width 计算。
- 容器必须声明
box-sizing: border-box,否则padding和border会挤占width可用空间,导致12ch实际不够显示全部文字 - 父容器若设了
display: flex或inline-block,可能触发最小宽度收缩,需加min-width: max-content或显式width: fit-content - 绝对定位元素若父级没设
position: relative,width: 12ch会被当成相对于视口计算,结果完全不可控
真正难的从来不是写对那几行 CSS,而是确认字符数、锁死字体度量、隔离布局干扰——这些细节一旦漏掉,效果就会在某个浏览器或某次字号调整后突然失效。


















