简介:面向前端初学者与需要优化页面加载反馈的开发者,这份《纯CSS3 Loading文字加载动效》提供了一套不依赖 JavaScript 的文字加载动画实现思路与可运行示例。压缩包共 7 个文件、约 7KB,包含 3 个 CSS 样式文件、2 个 JavaScript 脚本,以及 htm/html 演示页面;其中 CSS 负责关键帧、文字拆分与动画节奏控制,脚本和页面主要用于效果展示与结构参考。已有 281 人学习,说明其在轻量级加载动效场景中具有一定参考价值。读者可从中理解 animation-name、duration、timing-function、delay、direction、iteration-count、fill-mode 与 play-state 等属性如何协同工作,掌握文字上下滚动、循环播放和节奏调节的做法,并将相关样式迁移到表单提交、列表刷新、页面初始化等加载状态中,在减少脚本依赖的同时提升交互表现。
1. 后台列表点进详情,接口慢半拍,屏幕上就剩一个转圈图标在自娱自乐
这种场景几乎每个做中后台的人都遇到过:首屏白屏两秒,产品经理不要旋转菊花,嫌它太“系统默认”,设计给了一张能呼吸的文字稿,开发一看要加动画库、要写定时器,预算立刻超标。这份纯 CSS3 Loading 文字加载动效资源解决的正是这一步——把“Loading”“数据加载中”这类纯文本本身做成等待提示,不需要任何第三方动画库,也不写一行驱动动画的 JS。它常被打包进JS特效、文字特效一类的源码合集里,但拆开看,真正干活的只有@keyframes和几个 CSS 变量。适合两类人:一类是要在项目里快速塞一个不丢人的加载态的前端;另一类是想搞清楚steps()、background-clip: text、clip-path这些属性到底怎么组合出效果的人。
2. 资源结构与技术底座:一个动效一个类名,变量统一收口
2.1 目录组织:常见做法是把“总览页”和“动效库”分开
这类动效包我拆过几个,组织方式大同小异,基本都是“一个index.html当展厅,一个loaders.css当仓库”的结构。展厅页只负责把十几个变体平铺出来、方便挑,真正的动效代码和展厅完全解耦,拷走一个类名就能用。
loading-text/ ├── index.html # 展厅页:每个动效一个卡片,带类名标注 ├── css/ │ ├── base.css # reset、演示排版、字体栈,跟动效无关 │ └── loaders.css # 所有 @keyframes 和 .loader--* 变体都在这里 ├── js/ │ └── demo.js # 只服务展厅页:复制类名、切换深浅色,动效不依赖它 └── fonts/ └── *.woff2 # 可选,等宽字体主要留给打字机变体base.css和loaders.css分开是有讲究的。base.css里通常会定义一组演示用的排版变量和深色模式开关,你把它删掉,loaders.css里的动效照样能跑。反过来,如果你把两个文件合成一个再整包丢进项目,很容易和自己的全局样式打架——尤其是base.css里那种* { box-sizing: border-box }和body { margin: 0 },在别人的项目里属于侵入式改动。我一般只把loaders.css拷进src/styles/,然后在组件里手写那几行结构。
demo.js这个名字容易让人误会,以为动效是靠它驱动的。打开看一眼就知道,它只做两件事:点卡片复制类名到剪贴板、切一下>/* loaders.css —— 全局可调参数,改这里就能整体换风格 */ :root { --loader-color: currentColor; /* 跟随文字色,深浅色主题下自动适配 */ --loader-duration: 1.4s; /* 单次循环时长,所有变体共用基准 */ --loader-step: .07s; /* 逐字动效的错峰间隔 */ --loader-size: 1rem; /* 字号基准,变体内部一律用 em 派生 */ --loader-ease: cubic-bezier(.36, .07, .19, .97); } .loader { display: inline-block; font-size: var(--loader-size); color: var(--loader-color); line-height: 1.2; /* 动画元素独立成层,避免影响兄弟节点的布局计算 */ contain: layout style; }
这段代码里真正起作用的是三个决策。第一,颜色用currentColor而不是写死十六进制,这样深色模式、按钮内的白字、禁用态的灰字全都自动继承,不用为每种场景再写一个变体。第二,--loader-step和--loader-duration是两个独立维度:前者控制“波峰之间的疏密”,后者控制“单字一次呼吸的快慢”,调参时分开调比揉成一个animation值清楚得多。第三,contain: layout style告诉浏览器这个元素内部的布局变化不会外溢,对于一行小字来说收益有限,但当你一屏放二十个加载态时,它能把重排影响的子树规模压下来。
提示:
contain在老版本浏览器里会被直接忽略,属于渐进增强属性,不需要写降级。
2.3 为什么坚持不用 JS:无限循环、无状态、无交互
判断一个动效该不该用 JS,我的标准是三条:有没有终点、有没有状态、有没有交互。加载文字动效三条全不占——它无限循环、不记录进度、用户点不到它。这种场景交给 CSS 是最省事的,动画跑在合成线程上,主线程去处理接口回调和渲染都没人抢。
代价也得说清楚。纯 CSS 做不了“根据真实进度改变宽度”,因为 CSS 不知道后端返回了几个字节;也做不了随机节奏,Math.random()写不进@keyframes。所以真正带进度的条,还是得 JS 把百分比写进style="--progress: 62%",再由 CSS 的width: var(--progress)消费——JS 只负责传值,不负责逐帧计算,主线程依然轻。
另一个隐性好处是组件卸载。React 或 Vue 里用setInterval驱动的加载动画,忘记clearInterval就是内存泄漏和“组件没了动画还在转”的经典 bug,这类问题在代码评审里极难看出来。纯 CSS 动画跟着 DOM 节点走,节点移除,动画自然消失,等于自带一层后悔药。
3. 四类文字动效的核心实现:从打字机到渐变扫光
3.1 打字机加光标:steps()的步数必须等于字符数
打字机是整个包里被抄得最多的一个,也是最容易抄错的一个。核心思路是“容器宽度从 0 长到满宽”,再用steps()把连续变化切成一段段,制造出一格一格蹦出来的错觉。
/* 打字机:char 数与 steps 步数强绑定 */ .typewriter { --chars: 9; /* 文案字符数,必须和 steps() 的步数一致 */ --char-time: .16s; /* 每打出一个字符花费的时间 */ display: inline-block; width: calc(var(--chars) * 1ch); /* 纯英文用 ch;中文文案换成 1em */ overflow: hidden; white-space: nowrap; border-right: 2px solid currentColor; /* 光标就是一条右边框 */ box-sizing: content-box; /* 让光标画在文本宽度之外 */ animation: typing calc(var(--chars) * var(--char-time)) steps(var(--chars)) forwards, caret .9s step-end infinite; } @keyframes typing { from { width: 0; } /* 只写 from,to 自动取元素当前的 width */ } @keyframes caret { 50% { border-color: transparent; } }steps(var(--chars))是关键:steps(9)意味着把 0 到满宽这段区间均分成 9 份,每份之间是瞬时跳变,视觉上就是“一个字一个字地出现”。如果步数是 8 而文案有 9 个字,最后一个字会以半透明的方式被裁一半,这就是最常见的翻车点。
@keyframes typing只写了from,to用的是元素的当前计算值,所以改--chars不用同步改 keyframes,少一处漏改。但这招有前提:元素必须显式声明width,如果宽度是auto,动画就没有终点可算。
光标的闪烁用border-right而不是伪元素,省一个节点。box-sizing: content-box是为了让 2px 的边框挂在文本宽度外侧,否则光标会吃掉最后一个字符的位置。step-end让闪烁变成硬切,比ease更像终端光标。
注意:老版本浏览器对
steps()里塞 CSS 变量的支持不稳定,稳妥的做法是把数字直接写死,用注释标明和--chars保持同步。
3.2 逐字错峰跳动:animation-delay加--i的相位控制
一排字母轮流往上跳,这个效果不能靠一个容器动画实现,必须把每个字符拆成独立的span,然后给每个span一个不同的延迟。延迟值通过内联的--i传进去,CSS 里算乘法。
<!-- 容器用 aria-label 兜住语义,字符 span 对读屏隐藏 --> <span class="loader loader--stagger" role="status" aria-label="加载中"> <span style="--i:0" aria-hidden="true">L</span> <span style="--i:1" aria-hidden="true">o</span> <span style="--i:2" aria-hidden="true">a</span> <span style="--i:3" aria-hidden="true">d</span> <span style="--i:4" aria-hidden="true">i</span> <span style="--i:5" aria-hidden="true">n</span> <span style="--i:6" aria-hidden="true">g</span> </span>.loader--stagger > span { display: inline-block; /* 行内元素不响应 transform */ transform-origin: 50% 100%; animation: lift var(--loader-duration) var(--loader-ease) infinite; animation-delay: calc(var(--i, 0) * var(--loader-step)); } @keyframes lift { 0%, 60%, 100% { transform: translateY(0); opacity: .45; } 30% { transform: translateY(-.35em); opacity: 1; } }display: inline-block是必须的,行内元素上的transform不生效,这是新手最常卡住的一步。translateY(-.35em)用em而不是px,字号一改,跳动幅度自动跟着缩,不用为移动端再写一套尺寸。
animation-delay的相位差有个工程约束:--loader-step × 字符数应该明显小于--loader-duration。如果 7 个字符 × 0.3s = 2.1s,而一轮动画只有 1.4s,延迟就会绕圈,波峰顺序看起来是乱的,像卡顿。我一般让总延迟控制在单轮时长的 40% 以内,波形才干净。
aria-label挂在容器上、字符span上打aria-hidden,是这类“拆字”动效的标配。不这么做,读屏软件会把 “L-o-a-d-i-n-g” 一个字母一个字母念出来,体验非常怪。
3.3 渐变扫光:background-clip: text三条属性必须配对
扫光文字看上去最“高级”,代码其实最短,坑也最集中。它的原理是给元素铺一层 250% 宽的渐变背景,再把背景裁到文字形状里,最后平移背景形成光带扫过的效果。
.loader--shine { --shimmer-hi: #ffffff; /* 高光色 */ --shimmer-lo: #8b93a1; /* 底色 */ display: inline-block; color: transparent; /* 让文字本身的填充色透明 */ -webkit-text-fill-color: transparent; /* 老 WebKit 必须补这一条 */ background-image: linear-gradient( 100deg, var(--shimmer-lo) 0%, var(--shimmer-lo) 38%, var(--shimmer-hi) 50%, var(--shimmer-lo) 62%, var(--shimmer-lo) 100% ); background-size: 250% 100%; background-repeat: no-repeat; -webkit-background-clip: text; background-clip: text; animation: shimmer 1.8s linear infinite; } @keyframes shimmer { from { background-position: 130% 0; } to { background-position: -30% 0; } }三条属性必须同时存在才能出效果:background-clip: text(裁剪成文字形状)、color: transparent(否则文字颜色盖住背景)、background-size大于 100%(否则没有可平移的空间)。少任何一条,你看到的要么是一块纯色矩形,要么是一段完全消失的文字。
background-position从 130% 走到 -30%,比 100% 到 0% 多出 30% 的余量,光带才会从右边完全进入、从左边完全离开,不会出现“扫到一半突然重置”的突兀感。-webkit-text-fill-color在现代浏览器里和color等价,但一些老内核里只认前者,加上一行不亏。
这个变体的性能其实一般,因为background-position不总能进合成器,长文本、大字号下会有可见的重绘。如果只是给一行十几像素的加载文字用,完全没问题;如果要用在首屏大标题上,得先录一次 Performance 看看。
3.4 描边填充:用clip-path代替width,把重排踢出动画
“文字从灰色渐变填充成蓝色”这种效果,早期写法是给伪元素设width: 0然后动画到100%,配overflow: hidden。这写法能跑,但每一帧都在改宽度,是标准的布局抖动来源。
.loader--fill { position: relative; display: inline-block; color: #cdd2da; /* 未填充时的底色 */ } .loader--fill::after { content: attr(data-text); /* 文案只写一份,伪元素直接复用 */ position: absolute; inset: 0; color: #2f6bff; /* 填充色 */ clip-path: inset(0 100% 0 0); /* 初始状态:右侧全裁掉 */ animation: sweep 2.6s cubic-bezier(.65, 0, .35, 1) infinite; } @keyframes sweep { 0% { clip-path: inset(0 100% 0 0); } 65%, 100% { clip-path: inset(0 0 0 0); } }clip-path: inset(上 右 下 左)描述的是一个“内缩矩形”,inset(0 100% 0 0)表示从上右下左各裁掉 0%、100%、0%、0%,也就是整块不可见。把第二个值从 100% 动画到 0%,就是从左往右逐步露出的效果。
content: attr(data-text)让文案只维护一份,HTML 里写<span class="loader--fill">.loader--flip { perspective: 320px; /* 父级给透视,子级才有 3D 感 */ display: inline-block; } .loader--flip > span { display: inline-block; transform-origin: 50% 100%; /* 以底边为轴翻转,像翻日历 */ backface-visibility: hidden; /* 翻到背面时不显示镜像字 */ animation: flip var(--loader-duration) cubic-bezier(.6, 0, .4, 1) infinite; animation-delay: calc(var(--i, 0) * var(--loader-step)); } @keyframes flip { 0%, 60%, 100% { transform: rotateX(0deg); } 25% { transform: rotateX(-92deg); } }
transform-origin: 50% 100%把旋转轴放在字符底边,翻转时字是“往下倒”的,像老式翻页日历。如果保持默认的50% 50%,字会在中间对折,看起来像被拦腰截断,观感差很远。
rotateX(-92deg)而不是 -90deg,是因为正好 90 度时字符会进入零面积状态,不同浏览器对“零面积元素是否还渲染阴影和抗锯齿”的处理不一致,容易闪一下。多转 2 度绕开这个边界,属于典型的玄学微调。
4. 性能与适配:让二十个加载态同时跑也不心虚
4.1 只动transform和opacity,其余属性一律审慎
浏览器渲染一帧分几步:样式计算、布局、绘制、合成。只有transform和opacity的变化能跳过前两步直接进合成器,其余属性——width、height、top、left、margin、letter-spacing、font-size——都会触发重排或重绘。
回到第 3 章的打字机,它动的是width,理论上是要重排的。为什么还能用?因为被重排的子树被overflow: hidden和contain: layout style关在了一个极小的盒子里,父级宽度是固定的,兄弟节点位置不受影响,实际代价可控。但如果这行字在一个flex容器里、宽度又是auto,每帧的宽度变化都会引起整行重新分配空间,那就真会掉帧。
我的一般原则是:短文本、单实例的打字机可以接受width动画;一旦要在一屏里循环渲染几十个(比如表格每行一个加载态),就必须换成clip-path或scaleX版本。scaleX会把字体压扁,得配合反向scaleX抵消,麻烦;clip-path是更好的替代品,虽然它在部分环境下也不算合成属性,但至少不触发布局。
4.2will-change是把双刃剑,滥用比不用更糟
很多人一看到动画性能就条件反射地加will-change: transform。这个属性本质是提前告诉浏览器“这块要变,先给我升个合成层”,代价是显存。给一个字符加一层没事,给七十个字符各加一层,移动端上直接能看到内存曲线往上走,低端机甚至会因为层太多而触发“层爆炸”降级,反而更卡。
我的做法是只在父容器上声明,或者干脆不声明:
/* 推荐:只在容器上提示,不逐字符声明 */ .loader--stagger { will-change: transform; contain: layout style paint; /* 把这个子树的绘制影响关在盒子里 */ } /* 不推荐:每个字符一层,20 个实例就是 140 个合成层 */ .loader--stagger > span { /* will-change: transform; */ }contain: paint让这个元素的子内容不会画到它自己的边界之外,浏览器可以放心地把它当成一个独立的绘制单元。对于加载态这种“盒内自娱自乐”的组件,这个属性和它的语义完全对得上。
另一个容易忽略的点是动画结束后要不要撤掉will-change。常驻的加载态是无所谓,但如果这个元素只是短暂出现(比如按钮点击后转 1 秒),动画结束后合成层会一直留着。严格的做法是监听animationend再移除属性,实际项目里大多数人选择不折腾,因为加载态组件本身生命周期就短。
4.3 尺寸适配:clamp()加em派生,别写死px
加载态最尴尬的场景是:桌面端看着刚好,到了移动端那行字比屏幕还宽,或者小得看不见。解决办法是把字号做成一个可伸缩基准,内部所有尺寸都用em派生。
.loader { /* 视口宽度 320px 时约 12px,1440px 时约 18px,中间线性过渡 */ --loader-size: clamp(0.75rem, 0.5rem + 0.5vw, 1.125rem); font-size: var(--loader-size); } /* 内部一律用 em 派生,跟着字号等比缩放 */ .loader--stagger > span { animation-delay: calc(var(--i, 0) * var(--loader-step)); } @keyframes lift { 30% { transform: translateY(-.35em); } }clamp(最小值, 理想值, 最大值)里的中间项用rem + vw的线性组合,比单纯用vw安全,因为纯vw在极端宽屏下会失控放大。0.5vw的系数意味着屏幕每宽 100px,字号长 0.5px,节奏很缓。
跳动幅度用.35em而不是-6px,字号从 12px 变到 18px 时,幅度自动从 4.2px 变到 6.3px,视觉比例始终一致。这是em相对单位在动效里最实在的价值,比讨论rem和px哪个“更规范”有用得多。
4.4prefers-reduced-motion:不做降级就是不专业
系统级的“减弱动态效果”开关,很多前端是知道但懒得处理的。对于加载态这种一直转的东西,不处理的影响比想象中大:前庭功能敏感的用户会因为持续运动产生不适,而他们往往已经打开了系统开关,你的动画却照转不误。
/* 尊重系统偏好:降级为低频呼吸,不做位移 */ @media (prefers-reduced-motion: reduce) { .loader, .loader > span { animation-duration: 3s !important; /* 拉长周期,降低频率 */ animation-iteration-count: infinite !important; } .loader--stagger > span { animation-name: breathe; /* 换成只有透明度变化的版本 */ } .loader--flip, .loader--fill { animation: none !important; /* 位移和裁切类直接停掉 */ } } @keyframes breathe { 0%, 100% { opacity: .4; } 50% { opacity: 1; } }降级不等于完全静止。加载态如果一动不动,用户会以为页面卡死,反而更糟。正确做法是把“位移类”动画换成“透明度类”动画——运动感没了,但“还在转”的信号保留,这才是合格的降级。
5. 避坑排查:这些动效上线后才暴露的 5 个问题
5.1 尺寸与字体:文字被吃掉一半,光标压在字上
现象:打字机效果在本地看着完美,上线后最后一个字符被裁掉一半,或者光标和最后一个字母重叠。
原因:宽度用ch算,而ch是“数字 0 的宽度”,只对等宽英文成立。中文、全角标点、W、M这类宽字符的宽度都大于1ch,实际文本比容器宽,就被overflow: hidden切了。另外 Web 字体加载完成后字体度量会变,ch的计算结果也跟着变,首次渲染和字体就绪后的宽度对不上。
解决:中文或中英混排一律把1ch换成1em(中文字符基本等于一个字宽);给容器固定font-family并加font-display: swap之外,更稳的办法是干脆别用宽度动画,改用clip-path: inset(0 100% 0 0)从左往右揭开,宽度由内容自己决定,字体怎么变都不影响。
5.2background-clip: text让整段文字消失或变成色块
现象:扫光效果在某个环境里完全不显示文字,只剩一片空白,或者反过来变成一个实心矩形。
原因:三件事之一没做对。第一,只写了background-clip: text没写-webkit-前缀,老内核不认;第二,写了color: transparent但元素是纯inline,背景尺寸按行盒算,导致裁切区域错位;第三,父级或自身有别的background声明把它覆盖了。
解决:三条属性成对写全(background-clip双写前缀、color: transparent、-webkit-text-fill-color: transparent),元素加display: inline-block,然后用开发者工具逐条勾掉属性复现,确认是哪一条在起作用。这个属性没有“差不多能跑”的中间状态,要么全对要么全错。
5.3 一屏二十个加载态,滚动直接掉帧
现象:单个动效跑得好好的,列表页一次性渲染二十行就明显卡顿,滚动时不跟手。
原因:每个字符span都被will-change或 3D 变换升成了独立合成层,二十个实例乘以七八个字符,合成层数量上百,移动端 GPU 扛不住,浏览器被迫降级回软件合成。另外多个动画的animation-delay各自累积,还会导致“波形错乱”看起来像卡顿,其实是节奏问题。
解决:去掉逐字符的will-change,只在容器上保留contain: layout style paint;把列表项里不可见的行用IntersectionObserver加个 class 把动画停掉(这里引入 JS 是当开关用,不是驱动动画,主线程依然轻);同时把--loader-step × 字符数控制在单轮时长的 40% 以内。
5.4 从别的标签页切回来,动画“错位”或停住
现象:用户切到别的标签页几十秒再切回来,加载动效的位置和预期不一致,或者干脆静止不动。
原因:后台标签页里 CSS 动画会被浏览器节流甚至暂停,切回来时是“从暂停点继续”,不是“按真实时间补齐”。如果你的动画不是infinite,而是靠animation-fill-mode: forwards停在终点,切回来就会看到一个已经结束的静态画面。还有一种是多个变体的animation简写写了两条,后一条把前一条覆盖了,只剩一个动画在跑。
解决:加载态一律用animation-iteration-count: infinite,不要依赖一次性动画;同一个元素的多条动画合并成一条简写、用逗号分隔,别写两个独立的animation声明;如果确实需要“运行多久”这种逻辑,交给 JS 计时,CSS 只负责表现。
5.5 开了“减弱动态效果”后变成一块死灰
现象:部分用户反馈“页面卡住了,一直显示加载中”,截图过来是一行静止的灰字。
原因:只写了@media (prefers-reduced-motion: reduce) { animation: none },把动画全停了却没给替代方案,视觉上就和“加载失败”没区别。
解决:停掉位移和裁切类动画,但保留一个低频的透明度呼吸(周期 2~3 秒),既尊重系统偏好,又保住了“仍在进行中”的信号。这条建议值得写进团队的前端规范,成本极低,收益是被投诉的概率直接归零。
6. 二次开发:把展厅里的 demo 变成项目里能传参的组件
改造的第一步不是写代码,是先把变量表和 HTML 结构定下来,让“文案、变体、时长、错峰、颜色”这五个参数能从外部传进来。我一般用>// createTextLoader.js —— 只负责建 DOM 和传变量,动画仍然由 CSS 跑 export function createTextLoader(text, { variant = 'stagger', // 变体名,对应 .loader--xxx duration = 1.4, // 单轮时长(秒) step = 0.07, // 每字符错峰间隔(秒) color = 'currentColor', } = {}) { const el = document.createElement('span'); el.className = `loader loader--${variant}`; el.setAttribute('role', 'status'); // 语义:这是一块状态提示 el.setAttribute('aria-label', text); // 读屏念完整句子,不逐字念 el.style.setProperty('--loader-duration', `${duration}s`); el.style.setProperty('--loader-step', `${step}s`); el.style.setProperty('--loader-color', color); // 只有逐字类变体需要拆 span,扫光/填充类保持单个文本节点 if (variant === 'stagger' || variant === 'flip') { [...text].forEach((ch, i) => { const s = document.createElement('span'); s.textContent = ch === ' ' ? '\u00a0' : ch; // 空格换成不换行空格,保住宽度 s.style.setProperty('--i', i); s.setAttribute('aria-hidden', 'true'); el.appendChild(s); }); } else { el.textContent = text; el.dataset.text = text; // fill 变体的 ::after 靠 attr(data-text) 取文案 } return el; }
两个细节容易被忽略。空格的处理:普通空格在inline-block的span里会被折叠成零宽,波形中间就少一拍,换成\u00a0才对得上。语义的处理:容器上role="status"加aria-label,字符span上aria-hidden,读屏软件只会念一次“加载中”,而不是一个字母一个字母往外蹦。
在框架里用就更简单了,把上面的函数塞进useEffect或者onMounted,返回的节点appendChild进去即可;组件卸载时节点消失,动画自动结束,不需要任何清理逻辑。这也是纯 CSS 方案在框架里最舒服的地方。
改动验证我固定走三步。第一步,打开开发者工具的动画面板,把播放速率调到 10%,逐个变体看关键帧有没有跳变,尤其是打字机的最后一格和扫光的首尾衔接。第二步,用性能面板录 5 秒,看有没有长任务和布局抖动,重点确认没有Layout条出现在每一帧里。第三步,在渲染面板里勾上“模拟减弱动态效果”,确认降级分支真的生效,而不是一片死灰。三步都过,再往项目里合。
血泪经验是:别在生产构建完之后才想起来验,压缩工具和自动前缀插件都可能悄悄改掉-webkit-background-clip这类属性的书写形式。我现在的习惯是每加一个加载动效,都先在开发者工具里把 CPU 降速 6 倍跑一遍,再看一眼“减弱动态效果”下的样子,两个都过了才提交——这两个动作加起来不到两分钟,能省掉后面一堆“线上卡顿”“页面卡死”的排查。资源包解压后先打开根目录那个总览页,把十几个变体挨个点一遍,挑中哪个就把对应的类名和那一段@keyframes一起拷走,剩下的按上面的变量表接进项目就行。希望帮到你。
本文还有配套的精品资源,点击获取