CSS实现打字机动画的核心是用overflow: hidden配合width或ch单位动画,结合steps(字符数, end)逐字显示,并用::before/::after伪元素模拟光标闪烁;JS方案更灵活,可动态控制节奏、暂停及防重复触发。

用 CSS @keyframes 实现基础打字机动画
核心是控制文字逐个显示,配合“光标”闪烁。不能直接用 display: none 或 visibility: hidden 控制单个字符——浏览器渲染时会破坏行内布局连续性。正确做法是用 overflow: hidden 配合 width 动画,或更稳妥的 ch 单位宽度裁剪。
示例按钮结构:
<button class="typewriter-btn">提交</button>
关键 CSS:
.typewriter-btn {
position: relative;
overflow: hidden;
white-space: nowrap;
}
.typewriter-btn::before {
content: "提交";
display: inline-block;
width: 0;
animation: typewriter 1.2s steps(2, end) forwards;
}
@keyframes typewriter {
from { width: 0ch; }
to { width: 2ch; }
}
注意:steps(2, end) 中的 2 必须等于文字字符数(中文算 1 个 ch),否则动画卡顿或截断。
立即学习“前端免费学习笔记(深入)”;
JavaScript 动态生成打字效果更灵活
纯 CSS 对多字、换行、异步加载文本支持差。JS 方案能精确控制节奏、暂停、重播,也方便绑定点击逻辑。
常见错误:直接用 setTimeout 递归拼接字符串,但没清空初始内容,导致重复叠加;或忽略按钮禁用状态,造成多次触发。
实操建议:
- 初始化时先清空按钮文本:
btn.textContent = '' - 用
let i = 0计数器 +setInterval逐字写入,比嵌套setTimeout更易管理 - 写完后手动设置
btn.disabled = false,避免动画期间误点 - 若需支持 HTML 内容(如加粗字),改用
btn.innerHTML = text.slice(0, i),但要先做 XSS 过滤
简短示例:
function typeText(btn, text, speed = 150) {
let i = 0;
btn.disabled = true;
const timer = setInterval(() => {
btn.textContent = text.slice(0, ++i);
if (i > text.length) {
clearInterval(timer);
btn.disabled = false;
}
}, speed);
}
光标闪烁必须用 border 或 ::after 模拟
真实打字机有机械式光标停顿感,纯 CSS 动画容易和文字出现不同步。不要用 outline 或 box-shadow 模拟——它们无法精准对齐末尾字符右侧。
推荐方案:
- 给
::before或::after设置固定宽高(如1px × 1em),position: absolute定位到文字末尾 - 动画用
opacity而非visibility,避免重排 - 闪烁周期设为
0.75s左右,比人眼反应稍慢一点才显得“有延迟感”
例如:
.typewriter-btn::after {
content: '|';
position: absolute;
right: 4px;
opacity: 0;
animation: blink 1.5s infinite;
}
@keyframes blink {
50% { opacity: 1; }
}
移动端点击区域变小问题怎么修
打字动画期间,如果按钮高度靠文字撑开,iOS Safari 可能判定点击热区不足 44×44px,导致点不中。这不是样式 bug,而是系统级交互限制。
解决方法很简单但容易被忽略:
- 给按钮显式设置
min-height: 44px和padding(至少上下各 12px) - 避免用
line-height单独撑高——某些安卓 WebView 里它不参与点击区域计算 - 若按钮含图标,确保图标 SVG 也设置了
width/height,而非仅靠font-size
另外,动画过程中的 transform 或 will-change 可能触发 iOS 强制启用硬件加速,反而让文字渲染模糊,慎用。
真正麻烦的是中英文混排时的 ch 单位失效——中文字符在多数字体里实际宽度 ≠ 1ch,这时只能切回 JS 方案,或用 monospace 字体兜底。



















