
简介一份基于纯CSS3实现的Loading文字加载动效源码面向网页前端开发者与动效爱好者用于在页面加载初期以轻量方式呈现数据加载状态核心动效不依赖JavaScript驱动降低资源消耗并提升视觉体验。压缩包内共7个文件包含3个CSS样式文件负责动画关键帧与文字拆分效果、2个JS辅助文件用于文字拆分等辅助逻辑、1个HTML与1个htm演示页面整体仅7KB结构简洁便于直接引用或二次修改。已有281人学习/下载。资源覆盖animation属性的主要子项动画名称、时长、速度曲线、延迟、方向、播放次数、填充模式与播放状态并配以可运行示例页面读者可直观对照CSS代码理解文字上下滚动、往复循环等动效实现思路。此外示例中还对文字做了逐字拆分方便观察每个字符的独立动画节奏适合想借助CSS3替代传统JS加载动画、或在网页中快速套用文字特效的开发者参考。1. 纯CSS3 Loading文字加载动效不靠GIF和JS把加载这个等待态做出质感打开任何一个后台管理系统加载状态几乎都是绕不开的交互环节。过去我们习惯用一张旋转GIF或一个转圈SVG但放在今天的前端项目里这种方案的问题很明显颜色和品牌不搭时难改、尺寸拉伸会糊、无法做细节微调。纯CSS3 Loading文字加载动效解决的是这样一类需求——不依赖任何图片资源不写一行JavaScript逻辑用animation、transform、keyframes这几个基础能力把「正在加载」这件本无聊的事做成有品牌感、有反馈感的动效。它适合页面骨架、请求等待、首屏过渡等场景也适合你只想快速让一个静态文字「活」起来的前端小需求。CSS3做文字加载动效思路比想象中更简单文字本身还是那个文字动的是它的透明度、位置、背景裁切范围或阴影偏移。真正让老手和新手拉开差距的是对animation的时序控制、steps()的跳跃节奏、以及多重动画叠加时的协调性。这篇文章从最小可用的代码开始逐步讲到带CSS变量的可配置版本、性能优化和那些让你翻车几十次的细节坑。2. 理解CSS3动画的底层逻辑先看懂animation和keyframes的节奏感2.1 animation的八个子属性哪些是文字加载动效里真正离不开的任何CSS3动画都绕不开animation这个简写属性背后的八个子属性animation-name、animation-duration、animation-timing-function、animation-delay、animation-iteration-count、animation-direction、animation-fill-mode、animation-play-state。在纯CSS3 Loading文字加载动效这个具体场景里真正决定动效质感的只有四个时长、缓动函数、延迟和填充模式。时长决定一个完整循环要多快。文字加载动效一般建议单次循环在0.8秒到1.5秒之间——太短会让人觉得紧张太长又让用户以为卡死了。缓动函数决定动画是「匀速」「先快后慢」还是「带回弹」。默认的ease适合大多数透明度变化但如果做文字跳动的loading我会优先用cubic-bezier(0.22, 0.61, 0.36, 1)这种带轻微过冲的曲线让每个字落下时有一点自然的「砸到底」的感觉。延迟animation-delay在文字加载动效里是最容易出效果的一个属性。逐字加载的「逐」就是靠多个元素各自设置不同延迟实现的——第一个字延时0毫秒第二个字延时100毫秒第三个字延时200毫秒视觉上就形成了从左到右的波浪。填充模式animation-fill-mode则决定了动画在开始前和结束后怎么定格backwards让第一个循环开始前的延迟期就应用第一帧的样式避免页面一刷新就看到所有文字挤在一起。animation-direction和animation-play-state在基础文字加载动效里用得相对少但后者有个很实用的场景当页面切到后台标签再切回来时很多浏览器会暂停requestAnimationFrame而CSS动画是靠合成器线程走的行为不一定一致。如果你想在visibilitychange事件时手动控制暂停恢复animation-play-state: paused反而是比JS更可靠的手段。2.2 keyframes的百分比节点怎么定透明度与位移的配合keyframes里的百分比节点决定了动画在时间轴上的关键姿态。纯CSS3 Loading文字加载动效最常见的做法是每个文字经历「隐藏 - 出现 - 停留 - 隐藏」四个阶段。以1秒循环为例0%时透明度为0且向上偏移10px30%时透明度为1且回到原位80%前保持完整可见100%时再次消失并偏移。这样写的好处是动画从结束到下一轮开始之间会有一段「全部文字可见」的停留用户能真正读到这个加载提示的内容而不是一直处于模糊闪烁状态。不要只做透明度变化。只改opacity的loading动效看起来像「呼吸灯」缺少物理感。我会在同一个keyframes里同时改transform: translateY()和opacity让文字像从水底浮上来一样出现。transform不会触发布局计算比起改margin-top或top它不会引起周围元素抖动——这是做文字动效时最容易忽略的性能点。位移幅度也要克制。文字向上偏移8到14像素最舒服超过20像素会显得动作过大放到页面上会干扰视线。另外transform的值要写成translateY(10px)而不是translate(10px)后者会同时影响X轴多写一个零只想表达Y轴位移时就多一次出错机会。2.3 从零拼一个逐字波浪加载文字最小Demo与每个参数的说明先看一个最基础的可运行示例。HTML结构上我用span标记包裹每一个需要单独动画的文字——注意这里不可以直接对父容器做动画因为逐字动效的时序本身就要求每个字独立控制。div classloading-text span加/span span载/span span中/span /div.loading-text span { display: inline-block; font-size: 28px; font-weight: 600; color: #333; opacity: 0; transform: translateY(12px); animation: wave 1.2s ease-in-out infinite; } .loading-text span:nth-child(1) { animation-delay: 0s; } .loading-text span:nth-child(2) { animation-delay: 0.15s; } .loading-text span:nth-child(3) { animation-delay: 0.3s; } keyframes wave { 0%, 100% { opacity: 0; transform: translateY(12px); } 30%, 60% { opacity: 1; transform: translateY(0); } }这里有几个参数值得细说。display: inline-block是必须的——如果不改元素的盒模型span作为行内元素不会响应transform动画效果直接失效。ease-in-out让每个字浮起和落下两端都平滑适合波浪这类往复运动。opacity: 0写在元素上是动画开始前的兜底样式配合animation-fill-mode缺省值none时第一轮动画开始前也就是delay期间元素会保持这个透明度避免出现文字全部可见、然后又突然消失的闪跳。若希望布局从一开始就预留好空间这组写法在文字加载场景更稳妥。这个方案已经是可用的但还有两个常见延伸。一是把延时改成0.1s会让波浪更紧凑改成0.25s会显得更松缓——具体数值根据总时长和你的视觉偏好调没有绝对标准二是如果把infinite改成2这个动效就会变成「加载中」提示只闪两次然后定格适合加载结果即将出现的场景避免动效与真实状态错位。3. 把静态Loading做成可配置组件CSS变量驱动的参数化设计3.1 为什么需要CSS变量颜色、速度、位移幅度集中管理写死数值的原型Demo在交付到真实项目时一定会遇到需求变更。设计师说「这个蓝色不够亮」产品说「动画太快有点慌」运营说「能不能让文字大字号也不变形」——如果每个数值都散落在三四个选择器里每一轮改动都要全局搜索。CSS自定义属性CSS变量在这里的用处不是炫技而是让做纯CSS3 Loading文字加载动效的代码具备最基本的可维护性。我习惯把动效相关的变量统一放在一个作用域里例如:root或.loading-text自身。变量覆盖以下三类参数动效节奏时长、延迟、位移距离、视觉样式颜色、字号、透明度范围、循环行为播放次数、方向。在变量作用下调整一个值就能影响所有文字的节拍而不必逐条修改nth-child规则。变量的另一个价值是支持「类名级别的主题切换」。同一个加载组件在明亮背景和暗黑背景下可能需要不同的文字颜色和阴影通过[data-themedark] .loading-text覆盖变量即可动效逻辑不用复制第二份。这是CSS变量与预处理器变量最本质的区别自定义属性运行在浏览器里能响应选择器作用域变化而Sass变量在编译后就固化了。3.2 用变量重写逐字波浪标记自己的代码而不是别人的基于上一节的Demo我用CSS变量做一次重构。目标是让调用方只需改一个内联style或一组类名就能调整行为内部选择器不再出现重复的魔法数字。.loading-text { --load-duration: 1.2s; --load-delay-step: 0.15s; --load-distance: 14px; --load-color: currentColor; --load-font-size: 28px; } .loading-text span { display: inline-block; font-size: var(--load-font-size); font-weight: 600; color: var(--load-color); opacity: 0; transform: translateY(var(--load-distance)); animation: wave var(--load-duration) ease-in-out infinite; } .loading-text span:nth-child(1) { animation-delay: 0s; } .loading-text span:nth-child(2) { animation-delay: var(--load-delay-step); } .loading-text span:nth-child(3) { animation-delay: calc(var(--load-delay-step) * 2); } .loading-text span:nth-child(4) { animation-delay: calc(var(--load-delay-step) * 3); } keyframes wave { 0%, 100% { opacity: 0; transform: translateY(var(--load-distance)); } 30%, 60% { opacity: 1; transform: translateY(0); } }calc(var(--load-delay-step) * 2)是这版重构里的关键写法。当有六个字时第6个字的延迟可以统一写成calc(var(--load-delay-step) * 5)一眼就能看出延迟与序号的线性关系不需要手动数0.75秒还是0.9秒。注意calc()的乘法要求右侧必须是数字所以--load-delay-step建议定义为0.15s这种带单位的时长值乘法写在另一侧避免报错或产生无效值。这里有个容易踩的小坑CSS变量的使用确实让代码更清晰但keyframes里的transform无法响应变量变化——var()在关键帧内部是支持的所以0%和100%都要把translateY值写成变量。如果你在某次调试时发现改了--load-distance没有生效先检查是否把变量写在了animation简写里而不是transform上。3.3 通过nth-child生成全组序列SCSS循环只是PR视角的优雅写静态CSS处理10个甚至更多文字时手动写animation-delay会变成一件又蠢又容易错的事。10个选择器没有任何本质区别只有数字不同——这是典型的「循环生成」场景。Sass/SCSS的for指令是常见做法但要注意这只是在写代码阶段帮你生成CSS最终产物仍然是静态的、逐条的选择器浏览器层面没有任何区别。常见做法是用SCSS这样写.loading-text { span { display: inline-block; animation: wave 1.2s ease-in-out infinite; for $i from 1 through 6 { :nth-child(#{$i}) { animation-delay: ($i - 1) * 0.15s; } } } }编译产物就是六条独立规则。这里和手写的差异只是维护心智负担减小了运行时性能不变。如果项目没有预编译环境也可以直接在HTML模板里用JavaScript输出内联styleanimation-delay: ...——这不是违背「纯CSS3」精神因为动画本身仍是CSS在驱动JS只做批量生成CSS值不做逐帧计算。我给团队的不成文约定是文案内容固定就用CSS写死文案内容来自接口就用JS接管HTML生成。4. 三种高阶文字Loading动效与响应式适配4.1 背景裁切文字Loading渐变流动的技术点在哪里「文字自己会发光流动」这类精品Loading在CSS3里不是靠文字本身变色而是靠background-clip: text配合background-position的动画。原理是给文字设置透明的字体颜色把渐变背景裁剪到文字形状内然后让背景位置沿X轴循环移动造成光线扫过的视觉效果。这是要把纯CSS3 Loading文字加载动效做出高级感时最值得优先掌握的一种。最小实现是三段式color: transparent让文字本身不可见background-image定义渐变-webkit-background-clip: text把渐变裁到字形轮廓内最后filter或background-position做循环动画。.loading-shine { font-size: 44px; font-weight: 800; color: transparent; background-image: linear-gradient(90deg, #666 0%, #666 40%, #fff 50%, #666 60%, #666 100%); background-size: 200% 100%; -webkit-background-clip: text; background-clip: text; animation: shine 2.2s linear infinite; } keyframes shine { from { background-position: 100% 0; } to { background-position: -100% 0; } }background-size: 200% 100%是一切的关键。渐变本身定义了从灰到白再回到灰的完整区间放大到元素的200%宽度后background-position的移动才会呈现出「一条光带扫过文字」的效果而不是整体颜色明暗交替。如果background-size写成100%background-position移动时不会产生任何视觉变化——这是很多人做了半天发现文字「没动」的经典原因。4.2 跳跃与弹性的文字Loading利用cubic-bezier制造物理感上一节提到的translateY波浪动画是匀速平滑的但有些场景想要更「调皮」的感觉比如每个字落下时带一点弹性过冲。CSS3的cubic-bezier就是为这类需求准备的。比ease-out更强的弹性曲线是cubic-bezier(0.34, 1.56, 0.64, 1)它的特点是曲线的后半段超过1.0让动画在结束前先越过目标位置再拉回产生橡皮筋一样的拉伸感。配合这个曲线文字落下时不需要在keyframes里单独写overshoot节点。例如定义从translateY(-16px)到translateY(0)使用上述贝塞尔曲线的动画会在接近末尾时短暂偏移到translateY(4px)再回正这一全过程完全由缓动函数生成比手工加两个关键帧更自然、更顺滑。需要提醒的是cubic-bezier的Y值允许大于1但不能为负数。在浏览器控制台或动效调试面板里拖动曲线手柄时看到曲线最高点超出1.0是正常现象那正是弹性感的来源。跳跳型文字加载适合标题场景不适合表单里需要保持视觉「安静」的辅助提示。如果页面上同时有实时刷新数据这种活泼动效反而会干扰用户注意力形成一种无意义的噪音。4.3 响应式文字Loading字号与位移量如何随容器变化文字加载动效在移动端和桌面端同步使用时一个常见问题是位移量按像素写死在小屏低字号时显得动作幅度过大在大屏高字号时又嫌动作太轻。这个问题的合理解法是通过em单位让位移量相对字号缩放translateY(0.4em)的物理像素距离会随font-size变化字号24px时位移约10px字号48px时位移约19px比例始终协调。字号本身建议用clamp()函数做流式响应例如font-size: clamp(22px, 5vw, 40px)这样在小屏手机上是22px在大屏桌面显示器上是40px中间值随视口宽度连续变化不会出现需要断点重设字号的裂痕。将这两个技巧组合后整组动效在不同终端上的节奏和幅度感知是统一的不需要为移动端单独写一套修正CSS。颜色的响应式适配则靠媒体查询或prefers-color-scheme切换CSS变量即可。与字号相比颜色适配更建议直接跟随设计系统的token不要为loading单独发明颜色值。5. Loading动效的避坑指南从表现异常到彻底失效的五个现场5.1 动画不生效display:inline-block没有设置现象样式写好了keyframes也定义了但浏览器里文字纹丝不动控制台没有任何报错。原因span和a这类行内元素上transform属性大部分时候不会按块级盒模型预期执行。CSS规范中可替换元素与行内级别的盒模型对transform的支持行为实际表现就是部分浏览器会忽略行内元素的形变。解决给每个参与动画的文字显式设置display: inline-block这也是做文字动效时写进规范的第一条。另外flex容器里的span子项本身就是块化的但全局依赖容器类型总会在某些角落漏掉这个设定保险起见直接写在自身样式里。5.2 动画错位闪跳animation-fill-mode的默认值与初始可见性冲突现象页面刚加载的一瞬间所有Loading文字完整显示大约一帧后突然全部消失然后才开始逐字波浪动画。原因animation简写未指定animation-fill-mode默认值为none。在animation-delay大于0的等待阶段元素不使用关键帧里的任何一帧样式于是回退到元素自身定义的样式——如果元素自身没有写透明的兜底样式它就是完整可见的。解决给动画加上animation-fill-mode: backwards;让延迟期间应用关键帧第一帧的样式或者在元素基础选择器里就写好opacity: 0我通常两个都做。这个现象只在第一轮循环可见但它足够让用户一眼看出「加载中」这个状态做得不够精细。5.3 动画导致整页长宽抖动改正transform属性的位移现象每个字跳起来时页面右侧出现水平滚动条或下方的按钮跟着一上一下震动。原因用了margin-top或top来驱动位移变化。margin和top可纳入布局计算每次变化触发重排浏览器需要重新计算整行乃至整块文档流。解决改用transform: translateY()。它运行在合成器线程不触发layout与paint性能开销小两个量级。top属性还要小心它只在position非static的元素上生效——忘写position: relative时动画效果可能直接被吞掉。5.4 动画流畅度不佳无限循环动画的屏幕层级与will-change设置现象手机浏览器上文字加载动画掉帧明显尤其在页面同时存在滚动和渐入渐出动画时观感卡顿。原因无限循环动画让元素始终处于活跃状态。而synthesizer线程处理数位多、区域大时若未提示浏览器该元素需要独立层它会与页面其他内容合并在同一个绘制层里反复重绘。解决合理加will-change: transform, opacity提升为独立合成层但注意不要滥用。如果页面上同时存在10条Loading文字且全部提升为独立层内存占用反而上升安卓低端机上更容易卡。折中做法只对循环中的动画元素使用加载结束后立即移除类名来释放合成层。5.5 用户偏好减弱动效prefers-reduced-motion被忽略现象有前庭功能障碍的用户或系统开启「减弱动态效果」的用户在页面加载时感到眩晕部分单位内网办公电脑的图形驱动也仍会在CSS动画时持续产生较高GPU占用。原因prefers-reduced-motion: reduce的媒体查询未覆盖根本对应的CSS动效仍照常运行。解决把动画包在media (prefers-reduced-motion: no-preference)内或在其reduce分支里直接用animation: none关闭。关闭动效不等于放弃Loading提示——保留静态文字加一个微弱的淡入即可用户要的是信息而非装饰。如果你的产品面向政企、医疗这类对可访问性有明确要求的受众这一步是上线前必须做的。6. 用JavaScript桥接CSS动效动态生成、结束回调与降级控制的落地技巧到这里你可能已经发现只靠静态CSS能做好一套动效但它跟业务状态之间始终隔着一层。最常见的联动场景有三个一是Loading文字来自接口JS要负责生成逐字span二是加载完成后动效要收尾且收尾动作必须与下一步业务动作同步三是非首次进入页面时希望动画只播一次。这三个需求仍可以用「JS只控制CSS类名与内联变量动画执行交给CSS」的方式来落地不破坏纯CSS3动效的基本原则。动态生成文字的结构时JS只是做模板拼接和CSS变量注入。把用户的加载文案「正在努力加载中」拆成单个字符逐个输出span同时把延迟的步进值直接写进内联styleanimation-delay: calc(var(--load-delay-step) * 1)。注意变量--load-delay-step定义在父容器子元素内联只用calc表达式避免在JS里计算出一堆带毫秒的魔法数字。加载完成的回调监听不需要JS轮询CSS状态正确事件是animationend。它可以监听单个字符串或容器本身的动画结束——比如在最后一个字的animationend事件里把父容器的infinite动画类移除并切换到「加载完成」的淡出态这样就能保证视觉收尾和请求结束发生在同一帧不会出现内容已经渲染但Loading还在转的尴尬窗口。降级处理的技巧是把prefers-reduced-motion: reduce判断放在JS初始化函数里这比只用CSS媒体查询更灵活——既能去掉动画又不误伤其他模块。最后分享一个多年养成的习惯每次为业务方写完一套Loading动效我都会在浏览器里做一次极端验证把CSS的animation-duration临时改成20秒盯完一个完整周期。这个做法能暴露70%的时序协调问题因为正常0.8秒的动效一闪而过肉眼很难判断波浪的先后顺序、延迟间隔和停留时长是否舒服放到20秒后每个阶段的占比一目了然调节间距和延时就是一次滑杆的事。也希望这篇笔记能帮你在做文字加载动效时少走几步弯路也希望你下次接到「做个Loading」的需求时直接能交付一套有参数、有降级、不打折的纯CSS3方案。本文还有配套的精品资源点击获取