纯CSS无法真正逐字动画,必须手动包裹字符或用JS自动拆分并添加延迟动画;动态文本需JS辅助,同时注意可访问性、性能及用户体验。

用 @keyframes 实现逐字显示动画的核心限制
纯 CSS 无法真正“逐字”控制文本渲染顺序,因为 span 或伪元素不能自动拆分文本节点。所谓“逐字动画”本质是预先将每个字符包裹进独立标签,再对它们统一施加延迟动画。直接对 p 或 div 应用 @keyframes 只会让整段文本一起动,达不到逐字效果。
必须手动拆分字符或用 JS 辅助生成 span
如果坚持只用 CSS,就得提前把文本写成带包裹结构的形式:
H e l l o
然后配合 @keyframes 控制每个 span 的 opacity 和 transform:
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.typewriter span {
display: inline-block;
opacity: 0;
animation: fadeInUp 0.4s forwards;
}
.typewriter span:nth-child(1) { animation-delay: 0.1s; }
.typewriter span:nth-child(2) { animation-delay: 0.2s; }
.typewriter span:nth-child(3) { animation-delay: 0.3s; }
.typewriter span:nth-child(4) { animation-delay: 0.4s; }
.typewriter span:nth-child(5) { animation-delay: 0.5s; }
- 延迟值需手动递增,字符越多越难维护
-
animation-fill-mode: forwards必须加上,否则动画结束后会回退到opacity: 0 - 若文本动态生成(如 API 返回),纯 CSS 方案完全失效
更实用的方案:用 JS 自动包裹 + CSS 动画
在 DOM 加载后,用 JS 把目标文本按字符切开并包上 span,再统一加类触发动画。这样 HTML 干净,逻辑可控:
立即学习“前端免费学习笔记(深入)”;
const el = document.querySelector('.js-typewriter');
if (el) {
const chars = el.textContent.split('');
el.innerHTML = chars.map((c, i) =>
`${c}`
).join('');
el.classList.add('is-animated');
}对应 CSS 只需一套规则:
.js-typewriter span {
opacity: 0;
transform: translateY(8px);
animation: fadeInUp 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}-
cubic-bezier调整让入场更自然,避免机械感 - 延迟间隔建议设在
0.06s–0.1s之间,太快看不清,太慢拖节奏 - 注意空格、换行符也会被当字符处理,必要时用
replace(/\s+/g, ' ')规范
别忽略可访问性与性能问题
逐字动画对屏幕阅读器不友好,且大量 span 会增加重排压力。实际项目中应:
- 给动画容器加
aria-hidden="true",同时保留一份无动画的visually-hidden文本供读屏器使用 - 用
will-change: opacity, transform提升单个span的动画层,但别滥用 - 首次加载完成后再启动动画,避免白屏期看到字符逐个蹦出来
- 移动端慎用,低端机上 20+ 字符可能掉帧
真正卡点不在动画写法,而在字符拆分时机和 DOM 更新节奏——这点 JS 比纯 CSS 可控得多。










