必须用steps()函数实现逐字显现,因其将动画强制切分为离散步进,每步只显示一个字符;若用linear或ease会导致文字重叠闪出,无法呈现均匀逐字效果。

用 CSS animation 实现逐字显现,但要注意 timing-function 选择
直接用 @keyframes 配合 opacity 或 visibility 做逐字动画时,如果用 ease 或默认缓动,文字会出现“挤在一起闪出”的错觉。真正均匀逐字出现,必须用 steps() 函数——它把动画帧强制切成离散步进,每步只显示一个字。
常见错误是写成 animation-timing-function: linear,结果每个字淡入时间重叠,看不出“逐个”感。正确做法是:
- 把按钮文字拆成单个
<span>子元素(或用 JS 动态包裹) - 给每个
<span>设置相同动画,但用animation-delay错开起始时间 - 动画本身只需控制
opacity从 0 到 1,且animation-timing-function: steps(1, end)
示例关键 CSS:
button span {
opacity: 0;
animation: reveal 0.8s steps(1, end) forwards;
}
button span:nth-child(1) { animation-delay: 0s; }
button span:nth-child(2) { animation-delay: 0.1s; }
button span:nth-child(3) { animation-delay: 0.2s; }
@keyframes reveal {
to { opacity: 1; }
}用 JavaScript 实现更灵活的逐字控制,比如暂停/重播/动态内容
纯 CSS 方案在按钮文字动态变化(比如加载中状态切换)、需响应用户交互(点击暂停)或兼容老浏览器时会力不从心。JS 方案核心是用 setTimeout 或 requestAnimationFrame 控制每个字符的显隐节奏。
立即学习“前端免费学习笔记(深入)”;
容易踩的坑是直接操作 innerHTML 拼接字符串——这会触发重排,且无法单独控制每个字样式。推荐做法是先将文字转为数组,再逐个创建 <span> 并插入:
- 用
split('')拆分字符串,注意处理空格和 emoji(部分 emoji 占两个 UTF-16 码元,split('')可能切错,可用[...str]) - 每个
<span>初始化为opacity: 0,再用定时器逐个设为opacity: 1 - 若需支持重播,得先重置所有子元素的样式,否则第二次执行时部分字可能已显示
简短示意:
const btn = document.querySelector('button');
const chars = [...btn.textContent];
btn.textContent = ''; // 清空,避免重复渲染
chars.forEach((c, i) => {
const span = document.createElement('span');
span.textContent = c;
span.style.opacity = '0';
btn.appendChild(span);
setTimeout(() => span.style.opacity = '1', i * 100);
});移动端 Safari 下 steps(1, end) 不生效?检查是否漏了 -webkit- 前缀
iOS 15 及更早版本 Safari 对 steps() 的原生支持不完整,尤其在 animation-timing-function 中直接写 steps(1, end) 会被忽略,退化为 linear。这不是 bug,是 WebKit 旧版实现限制。
解决方式很简单:加 -webkit-animation-timing-function 并保持值一致。不需要额外 polyfill,也不用改动画逻辑。
- 必须同时写两行:
animation-timing-function: steps(1, end);和-webkit-animation-timing-function: steps(1, end); - 仅加
-webkit-不加标准属性,在 Chrome/Firefox 下反而可能失效 - 别用
step-start替代 —— 它行为不等价,首帧就全显,不是“逐字”
按钮宽高固定时,逐字显现引发布局抖动怎么办
文字逐个出现过程中,按钮宽度会随字符增加而变宽,导致周围元素跳动,尤其在 flex 布局里特别明显。这不是动画问题,是 DOM 渲染流自然反应。
根本解法不是禁用动画,而是提前占位:
- 给按钮设置
min-width为完整文字宽度(可用getBoundingClientRect()测量后设内联 style) - 或更稳妥:用伪元素
::after渲染完整文字但设visibility: hidden,撑开空间 - 避免用
letter-spacing或text-indent强行预留空间——它们影响可访问性,且在不同字体下不准
伪元素方案示例:
button::after {
content: attr(data-full-text);
visibility: hidden;
width: 0;
overflow: hidden;
white-space: nowrap;
}然后在 HTML 中写 <button data-full-text="提交"></button>,JS/CSS 只操作可见内容。
逐字显现看着简单,但真要稳稳落地,timing-function 选型、WebKit 兼容、DOM 占位这三处最容易被忽略——少一处,用户看到的就是卡顿、跳动或干脆不生效。



















