最简纯CSS打字机效果需用monospace字体、white-space:nowrap、overflow:hidden、display:inline-block及width从0到100%或具体ch值的@keyframes动画,光标用::after伪元素配合blink动画实现。

用 CSS @keyframes 实现最简打字机效果
纯 CSS 方案足够应对大多数静态文本场景,无需 JS 介入,兼容性好且性能轻量。核心是利用 monospace 字体、white-space: nowrap 和 overflow: hidden 配合宽度从 0 到 100% 的动画。
常见错误是直接对 width 做动画却没设 display: inline-block 或 inline-flex,导致动画无效;另一个坑是用了非等宽字体,光标闪烁和字符推进不同步。
- 必须给文字容器设
font-family: monospace(如Consolas,ui-monospace) - 容器需设
white-space: nowrap+overflow: hidden+display: inline-block - 动画关键帧中,
width从0到fit-content不被支持,改用具体字符数对应宽度(如12ch)或100%(配合max-width限制) - 光标用伪元素
::after实现,content: "|"+animation: blink 1s step-end infinite
@keyframes type {
from { width: 0 }
to { width: 14ch } /* "Hello World!" 共12字符,+2留白 */
}
.typewriter {
font-family: ui-monospace;
white-space: nowrap;
overflow: hidden;
display: inline-block;
animation: type 2.5s steps(14, end), blink 0.75s step-end infinite;
}
.typewriter::after {
content: "|";
animation-name: blink;
}用 JavaScript 动态控制打字节奏与中断
CSS 方案无法响应用户交互(比如鼠标悬停暂停、点击跳过),此时必须上 JS。关键不是“逐字插入”,而是用 setTimeout 或 requestAnimationFrame 控制字符输出时机,并维护一个可取消的执行上下文。
容易踩的坑包括:未清除定时器导致重复触发、字符串含 HTML 标签时直接 innerHTML 插入引发 XSS、未处理退格或富文本格式。
立即学习“前端免费学习笔记(深入)”;
- 用
let i = 0计数,每次递增后检查i < text.length,避免越界 - 暂停/恢复需保存当前
i和调用clearTimeout/ 重新setTimeout - 若文本含
<span>等标签,先用document.createElement解析再追加,别用innerHTML += - 想支持“删除重打”,得额外维护一个
isDeleting状态和反向计数逻辑
function typewriter(el, text, delay = 100) {
let i = 0;
let timeoutId;
function tick() {
if (i < text.length) {
el.textContent = text.substring(0, i++);
timeoutId = setTimeout(tick, delay);
}
}
tick();
return () => clearTimeout(timeoutId); // 返回清理函数
}
// 使用
const cleanup = typewriter(document.querySelector('.js-typewriter'), 'Loading...');
// 悬停暂停
el.addEventListener('mouseenter', cleanup);在 Vue/React 中封装为可复用组件
框架里不要手动操作 DOM 或裸写 setTimeout,应交由响应式系统管理生命周期。Vue 用 onMounted/onBeforeUnmount,React 用 useEffect 清理副作用。
典型问题是组件卸载后定时器仍在运行,报错 Cannot set property 'textContent' of null;或者多次挂载导致多个动画叠加。
- Vue 中务必在
onBeforeUnmount里调用clearTimeout,且把 timeoutId 存在ref中 - React 中
useEffect的清理函数必须返回清除逻辑,不能只写clearTimeout(id)而不存闭包引用 - 避免在组件内部硬编码
delay,通过props或setup参数传入 - 需要支持 SSR(如 Nuxt/Next)时,动画逻辑必须包裹在
if (typeof window !== 'undefined')中
光标闪烁与可访问性(a11y)冲突怎么解
纯视觉的竖线光标对屏幕阅读器不友好,会被朗读成“竖线”或直接忽略,导致语义断裂。WCAG 要求动态文本至少提供静态替代(如 aria-live="polite")。
真实项目里常被忽略的一点:打字动画期间,若用户用键盘聚焦到该区域,焦点可能落在“未完成文本”的中间位置,引发阅读混乱。
- 隐藏光标视觉样式(
visibility: hidden),改用aria-label描述状态,例如aria-label="正在加载欢迎信息" - 对动画容器加
aria-live="polite"+aria-atomic="true",确保整段文本渲染完成后才通知 - 禁用光标区域的键盘聚焦:
tabindex="-1",或整个容器设role="status" - 如果必须保留光标,用
aria-hidden="true"显式屏蔽其可访问性
打字机效果看似简单,但真正上线时,90% 的问题出在边界控制——字符长度计算偏差、异步清理遗漏、a11y 支持缺位。别迷信“一行 CSS 解决”,先想清楚谁在看、什么时候会中断、断了之后怎么续。



















