ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

纯CSS3实现Loading文字动效:从@keyframes到逐字跳动

纯CSS3实现Loading文字动效:从@keyframes到逐字跳动

简介:一份基于CSS3 animation动画属性打造的纯CSS加载动效源码,适用于网页加载过程中的文字提示场景。无需JavaScript参与即可实现文字的拆分、上下滚动与循环播放,既降低页面资源消耗,又让等待过程更具视觉吸引力,适合前端开发者、交互设计师直接套用或学习动画参数配置。压缩包共7个文件,包括3个CSS样式文件、2个JavaScript脚本、1个HTML页面及1个说明文档,整体仅7KB。CSS文件分别控制字符拆分、单元格动画与整体布局,JS脚本用于辅助文字切分及动效控制,说明文档则提供用法与参数注释,结构简洁、易于二次修改。目前已有281人学习浏览,对于希望快速实现轻量级加载动效、或深入研究CSS3 animation八大子属性(如animation-duration、animation-timing-function、animation-iteration-count)的开发者来说,是一份直观的参考示例。

1. Loading 文字动效就该这么写:纯 CSS3 的轻量实现

页面在等接口的时候,最尴尬的就是那块空白的区域。要么放一只默认的菊花转圈,要么写一行“加载中…”干巴巴地杵在那里。这套纯 CSS3 的 Loading 文字动效,解决的就是这个视觉空窗——不依赖任何 JavaScript 动画库,靠 @keyframes 和 animation-delay 让“加载中”几个字逐一跳动起来,三十行上下的代码,复制到一个 HTML 文件里就能直接跑。

如果你平时收藏夹里堆了不少 JS 特效源码下载,又想让页面多一点文字特效的质感,这套方案值得拿来当模板。它适合两种人:想快速给页面加一个轻量加载反馈的前端开发者,以及初学 CSS3 动画、想弄懂逐字动画写法的新手。前者可以直接抄走,后者能顺着关键帧和延迟参数把原理看明白。

2. 技术选型:为什么 @keyframes + animation-delay 能替代 JS 特效库

2.1 先算一笔账:用 JS 写 Loading 动效要付出什么

常见的 JS 加载动画写法是用 setInterval 或 requestAnimationFrame 去修改文字的透明度或位移。这套看似简单,实际要把动画帧的时间戳、元素的当前状态、循环的停止条件全部手动维护起来。很多 JS 常用代码库里的 Loading 组件动辄上百行,还要考虑组件销毁时清理定时器,不然页面切走之后动画还在后台跑,白白吃 CPU。

如果真的只为了“几个字跳一跳”这个效果,这个成本就明显偏高了。纯 CSS3 方案没有定时器,没有状态变量,浏览器原生合成器接管动画的每一帧,代码体积小一个数量级,维护成本几乎为零。

2.2 animation-delay 是实现“逐字错落”的核心机制

逐字 Loading 动画的观感关键在于“错落”二字——所有文字不能同时动,要像一个波浪一样依次起伏。纯 CSS 没有循环语句,不能像 JS 那样对每个字做 for 循环处理,怎么办?答案是把每个文字放在独立的 span 标签里,然后给每个 span 设置不同的 animation-delay。

动画延迟节奏: 第 1 个字 delay = 0s 第 2 个字 delay = 0.2s 第 3 个字 delay = 0.4s 第 4 个字 delay = 0.6s

这样四个字虽然用的是同一套 @keyframes,但因为入场时间错开了,呈现出来的效果就是逐个跳动的波浪感。实际项目中文字数量不固定,常常写成一个通用公式:第 N 个字的 delay = N × step,step 通常取 0.15s 到 0.3s,数值越大波浪越明显,数值越小越接近整体同时运动。

2.3 关键帧节奏设计:duration、easing 与 infinite 的搭配

动画节奏由三个参数共同决定。animation-duration 决定一次完整运动的时长,Loading 场景取 1.2s 到 1.8s 比较合适,太快显得慌乱,太慢会让用户觉得页面卡顿。animation-timing-function 用 ease-in-out 比 ease 或 linear 更舒服,因为文字弹跳需要一个加速和减速的过程,模拟真实的物理运动。

iteration-count 设为 infinite,让动画循环播放。这里有一个细节:@keyframes 里定义的 0% 和 100% 状态要尽量一致,否则每一轮循环结束到下一轮开始的瞬间会出现肉眼可见的“跳变”。比如 0% 是透明度 0.3,100% 也必须是透明度 0.3,而不是 50% 的完全不透明状态。

@keyframes loadingText { 0%, 100% { opacity: 0.3; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-6px); } }

这个关键帧把 opacity 和 transform 叠在一起写,0% 和 100% 共用同一组状态,50% 是整个动画的峰值——文字跳到最高点并且完全不透明。浏览器会自动补全中间的过程,不需要你手动去算每一帧的具体位置。

注意 transform 用 translateY 而不是 margin-top 或者 top 位移。translateY 走的是合成器线程,不触发重排;margin-top 变化会强迫浏览器重新计算布局,移动端低端机上能明显感觉到掉帧。

3. 核心代码拆解:从 span 包裹到关键帧定义

3.1 HTML 结构:为什么必须逐字包裹

<div class="loading"> <span>加</span> <span>载</span> <span>中</span> <span>…</span> </div>

这段 HTML 是整套动效的基础。四个 span 分别包裹四个字符,每个 span 是独立的动画作用对象。如果不拆开,直接写“加载中…”一整段文字,CSS 动画只能让这段文字整体运动,无法实现逐字错落的波浪效果。

这里有两个可调整的点。第一,文字内容按需替换,比如“请稍候”“努力加载中”都可以,字符数量变了以后,只需要增删 span 标签,动画会自动适配——前提是下面要讲的 nth-child 延迟规则也要跟着增删。第二,省略号“…”单独占一个 span,实际动画中它会和前面的字一起跳动,替代了常见的“三个点轮流闪现”做法,写法更干净。

3.2 容器样式:让文字始终居中显示

.loading { display: flex; justify-content: center; align-items: center; min-height: 100vh; font-size: 42px; font-weight: 600; color: #333; }

容器用 flex 布局,justify-content 和 align-items 都设成 center,这样无论屏幕尺寸怎么变,文字永远在可视区域正中间。min-height: 100vh 保证了撑满整个视口,页面单独打开这个 HTML 时能看到完整的居中效果。

font-size 用 42px 是演示环境的默认值,实际项目里建议改成相对单位,比如 clamp(24px, 5vw, 48px),让文字在手机和桌面显示器上等比缩放。color 也可以换成你品牌色,或者干脆去掉颜色设置,跟随页面现有样式。

3.3 动画样式:transform 生效的关键一步

.loading span { display: inline-block; animation: loadingText 1.6s ease-in-out infinite; }

给每个 span 设置了 display: inline-block,这一步是整个动画能不能跑起来的分水岭。span 默认是行内元素,行内元素不响应 transform 和 opacity 之外的很多交互动效,尤其是位移类变换经常直接失效。改成 inline-block 之后,每个字变成一个独立的块级盒子,transform 才能正常作用。

animation 是简写属性,顺序有讲究:动画名称 loadingText、持续时间 1.6s、缓动函数 ease-in-out、循环次数 infinite。这个顺序写错了浏览器也能解析,但如果你在后面追加第五个参数 animation-delay,一定要先确认前面四个参数的位置没写错,否则 delay 可能被浏览器当成无效值丢弃。

3.4 逐字延迟:nth-child 控制每个字的入场时机

.loading span:nth-child(1) { animation-delay: 0s; } .loading span:nth-child(2) { animation-delay: 0.25s; } .loading span:nth-child(3) { animation-delay: 0.5s; } .loading span:nth-child(4) { animation-delay: 0.75s; }

nth-child 选择器按位置匹配每个 span,然后单独覆盖 animation-delay。这里的间隔 0.25s 配合 1.6s 的 duration,波浪感最明显。数值改小到 0.1s,四个字几乎同时运动,效果接近整体跳动;改大到 0.4s,每一轮的间隔会拉得很长,看起来像“一个字动完下一个才动”,就不是波浪而是接力了。

想让“加载中…”后面那三个点依次闪烁,也可以在这里做文章,把省略号三个点分别放进三个 span,然后把动画名称从 loadingText 换成另一个只改 opacity 的关键帧。

3.5 最终拼接:一份可以直接保存的完整代码

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>纯CSS3 Loading文字加载动效</title> <style> .loading { display: flex; justify-content: center; align-items: center; min-height: 100vh; font-size: 42px; font-weight: 600; color: #333; } .loading span { display: inline-block; animation: loadingText 1.6s ease-in-out infinite; } .loading span:nth-child(1) { animation-delay: 0s; } .loading span:nth-child(2) { animation-delay: 0.25s; } .loading span:nth-child(3) { animation-delay: 0.5s; } .loading span:nth-child(4) { animation-delay: 0.75s; } @keyframes loadingText { 0%, 100% { opacity: 0.3; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-6px); } } </style> </head> <body> <div class="loading"> <span>加</span> <span>载</span> <span>中</span> <span>…</span> </div> </body> </html>

完整 HTML 文件就这么长,没有外部依赖、没有 JavaScript、没有网络请求。把这段代码存成 .html 文件,双击用浏览器打开就是成品效果。如果你加到现有页面上,只需要保留 .loading 的样式部分,去掉 min-height: 100vh 和 flex 居中,改成适配你项目容器的方式即可。

参数取值作用
animation-duration1.6s单次动画时长,影响整体节奏
animation-delay0.25s 递增控制逐字错落间隔
translateY-6px文字弹跳高度,越大跳得越高
opacity0.3 / 1最低淡出程度与高亮程度

4. 踩坑排查:五种真机上让人摸不着头脑的加载动画问题

4.1 动画一点反应都没有,字纹丝不动

现象:代码照抄,浏览器打开后“加载中…”几个字静态显示,没有任何动画效果。

原因:九成是 span 还是行内元素。拷贝代码的时候如果漏掉了 .loading span 里的 display: inline-block,transform: translateY 在行内元素上就是不生效。还有一种情况是直接给 .loading 加了 animation,而不是给 .loading span 加——整段文字整体动了反而掩盖了问题,或者干脆没动。

解决:先确认 display: inline-block 写在样式里,再确认 animation 挂在 span 选择器上。用 DevTools 检查 span 的计算样式,animation 名称和 duration 有没有被其他样式覆盖掉。

4.2 四个字同时跳动,没有错落感

现象:动画在跑,但四个字像绑在一起一样同起同落。

原因:animation-delay 没有生效。常见的低级错误是写 animation 简写属性时把 delay 放在了 duration 的位置,比如 animation: loadingText 0.25s ease-in-out infinite,浏览器会把第一个数值当成 duration 而不是 delay。另一种可能是 nth-child 的写法和 HTML 结构不匹配,比如 HTML 里 span 外面还套了别的标签,导致 nth-child 匹配到了错误的元素。

解决:把 delay 单独写成一行,不要混进简写属性里。检查 HTML 层级,确保 .loading 的直接子元素就是 span,中间不要插入注释节点或者包裹层。

4.3 页面刚打开时文字闪一下高亮

现象:动画开始前,所有字先以完全不透明的状态显示一瞬间,然后才进入淡出跳动的循环。

原因:animation-delay 这段时间里,元素还没有套用关键帧里的样式,所以显示的是 CSS 里设置的默认 color 和默认 opacity: 1。延迟时间越长,这个“闪一下”越明显。

解决:把 animation-fill-mode 设为 backwards,让元素在动画延迟阶段直接应用第一帧的样式:

.loading span { animation: loadingText 1.6s ease-in-out infinite backwards; }

这个属性让动画开始前和结束后都保持第一帧的状态,视觉上从一进页面就是“未高亮”的状态,没有闪变。

4.4 渐变颜色文字在部分浏览器上直接变白

现象:有人喜欢把文字做成渐变效果,用 background: linear-gradient 加 background-clip: text,结果某几个浏览器里文字完全看不到。

原因:background-clip: text 的兼容性需要 -webkit- 前缀,漏掉前缀时浏览器不支持该属性,背景图整个盖在文字层上,看起来就是一团模糊的渐变块,文字被盖没了。

解决:先写带 -webkit- 前缀的版本,再写标准版本。如果使用场景是普通 Loading 文字,建议直接放弃渐变,用纯色加 opacity 过渡最稳,毕竟重点在“跳动”而不是“颜色”。

4.5 动画在手机端掉帧严重

现象:桌面浏览器流畅,切到低端安卓手机上就出现卡顿和丢帧。

原因:动画里用了 filter 或 animated property 过多。filter: blur 这类效果会触发昂贵的渲染开销,transform 和 opacity 虽然走合成器线程,但如果同时动画的属性太多,主线程和合成线程之间的同步也会拖慢帧率。

解决:除 transform 和 opacity 之外不要同时动画其他属性。如果非要模糊效果,可以把一次 blur 动画改成 opacity 渐入,视觉差距很小,性能差距巨大。

5. 二次开发:把 Demo 参数化成三个可复用的加载文字组件

5.1 用 CSS 变量抽离关键参数

直接抄代码能跑,但每换一个项目就要去改 animation-delay 的具体数值,慢慢就乱了。用 CSS 变量把节奏控制抽出来,一行就能调完整套动画:

.loading { --duration: 1.6s; --step: 0.25s; --distance: -6px; } .loading span { display: inline-block; animation: loadingText var(--duration) ease-in-out infinite; animation-delay: calc(var(--step) * (var(--i) - 1)); }

配合行内变量给每个 span 编号:

<div class="loading"> <span style="--i: 1">加</span> <span style="--i: 2">载</span> <span style="--i: 3">中</span> <span style="--i: 4">…</span> </div>

改造之后,调整 --step 就能控制波浪的疏密程度,调整 --distance 就能改变跳起的高度。新增文字的时候只需要复制一行 span 并把 --i 加一,不用再碰 nth-child 规则。如果文字内容是动态的,也可以在 JS 循环渲染时顺手输出这个变量。

5.2 在不改变纯 CSS 性质的前提下做动画变体

同样的 DOM 结构,只要换一套 @keyframes,就能做出完全不同的效果。比如把 translateY 跳动改成透明度呼吸,文字从几乎看不见慢慢亮起再淡出,适合数据加载时间较长时使用,观感更安静——把 loadingText 里 50% 那一段的 translateY(0) 保留,opacity 改成 0.15 到 1 的渐变即可。

做“进度百分比”动效时,数字固定为 100,把关键帧改成从 0 到 100 的数字滚动:

@keyframes progressCount { 0% { content: "0"; } 100% { content: "100"; } }

注意 content 不能直接动画,这套方案需要用 ::before 配合 steps(11) 逐帧切换,在实际项目里更推荐让后端接口返回进度值,前端只负责更新数字节点,纯 CSS 的百分比播放更多用于无后端参与的本地演示场景。

5.3 接入框架:动态渲染后动画如何重启

如果项目的加载文字是动态出现的,比如从接口返回错误信息后显示“加载失败”,再用同一套动画去渲染,会遇到动画不重启的问题——因为 CSS 动画只认同一元素的同名动画,DOM 复用时浏览器觉得动画状态没变,不会重新播放。

解决思路是给元素换一个不同的动画名称来强制覆盖,或者在插入动画节点前先移除再重新插入 DOM。我一般会在动态渲染的代码里主动清除旧节点:

const container = document.querySelector('.loading'); container.replaceChildren(...newSpans);

这样每个新生成的 span 都是一次全新挂载,动画状态自然从头开始。从那以后我每次写完一个带动画的组件,都会顺手检查一遍“如果数据更新了,动画到底会不会重启”,已经成了肌肉记忆。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进