news 2026/9/25 1:56:40

150款CSS3 loading动画拆解:从实现原理到组件化落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
150款CSS3 loading动画拆解:从实现原理到组件化落地

做了六年前端,收藏夹里躺着几百个loading动画的链接,平时团队做项目我翻得最多的也是这类素材。前阵子整理资料,我把手头攒下的150款CSS3 loading加载动画重新按风格、动画原理、适用场景过了一遍,发现很多收藏其实处于“躺灰”状态,真到要用的时候还是只会那几个转圈圈。这篇文章我准备把这批动画的底细拆开讲清楚:它们常见的长什么样、核心动效是怎么写的、选型时该看哪些维度、怎么封装成一套真正能落地切换的组件,以及我在实际接入中踩过的坑。适合正在搭前端组件库的同学、被交互细节折磨的页面仔,也适合刚学CSS3动画想找练手素材的初学者。

1. 先说清楚:为什么是150款,而不是15款

1.1 一个loading背后是几类完全不同的设计语言

很多人觉得loading动画就是“转圈圈”,其实不是。你把150款放在一起扫一遍,会发现它们之间的差异比想象中大得多。有的走纯几何路线,用几个色块做旋转和缩放;有的走拟物路线,模仿沙漏、进度条、刷新箭头;有的走文字路线,把字母拆开做逐个位移;还有一类是骨架屏思路,用背景渐变模拟内容加载前的轮廓。选错风格比没有动画更尴尬:给一个严肃的医疗后台配弹跳气泡,用户体验会非常跳戏。

我收藏的这150款里,按实现成本大致能分成三档:最基础的只用单个元素加一两个 keyframes,适合按钮内部的小菊花;中档的需要三五个子元素配合不同延迟,适合页面级loading;高档的会结合裁剪、滤镜、3D变换甚至两步动画串联,能承载品牌感很强的开场动效。整理这批素材时,我给自己定了一个原则:不只看“好看不好看”,还要看“实现方式能不能让我说得出口”。如果一个动画效果非常炫,但代码写得又长又绕,放到团队项目里别人接手困难,我会把它归入“仅欣赏”而不是“可复用”。

1.2 150款的真正价值:建立选型直觉

收藏1000个资源不如真正理解100个的实现思路。前面说“150款不是收藏数量的炫耀”,是因为当你把这么多样本摆在一起横向对比,会自然形成一套自己的选型直觉。

举例来说,看多了你会发现:转圈类loading中,border实现的双半圆环比伪元素拼出的圆环更容易控制粗细和颜色;点阵类loading中,用“透明度+延迟”实现的呼吸效果,比“位移+透明度”同时变化的效果更省GPU资源;骨架屏类loading中,用background-position做扫光,比逐帧替换背景图性能好一个量级。这些判断如果只盯着三五个轮子看,是永远总结不出来的。

另一个隐藏价值是版权和可信度。很多现成的loading仓库要么体积过大,要么License不明确,而自己维护一个经过筛选的CSS组件集合,校验来源、裁剪样式、统一命名,放到内部项目里完全没有后顾之忧。150款的规模刚好能覆盖绝大多数业务场景,也不至于多到维护不过来。

1.3 这份合集适合谁

说实话,如果你只想要一个能跑的loading效果,去CodePen搜“spinner”就够了。但如果你需要的是批量化接入、风格统一、可复用可解释的方案,那么理解这批动画的构成规律会更值钱。

适合三类人:第一类是前端工程师,尤其要做中后台系统、H5活动页、小程序组件,需要根据业务快速切换loading形态;第二类是UI/UX设计师,需要验证自己设计稿里的loading动效有没有更好的实现方式,避免“设计很丰满、CSS很骨感”;第三类是刚接触CSS3动画的新手,从这150款里挑几个经典开始拆解,比啃整本CSS书来得快。说白了,这是一篇“看着炫,拆开更香”的源码级解读。

2. 拆开看看:一款CSS3 loading动画到底由什么组成

2.1 静态骨架:一个容器和若干子元素

所有CSS loading动画,无论看起来多复杂,静态结构都离不开“一个容器 + 若干可复位的子元素”。容器负责定位和尺寸约束,子元素负责被动画属性驱动。经典的点阵loading,HTML通常是3到5个span;转圈loading,往往只需要div加上border边框;波浪loading,则可能需要多个块级元素叠在不同高度上。

我建议把“子元素数量”当作一个重要的选型指标。元素越多,视觉越丰富,但DOM开销和CSS代码量也会上升,在低端机上更容易出现卡顿。150款素材里,单一元素能做到惊艳的是少数,最典型的做法是用border-radius和transform组合出各类形态。如果你想在项目里减少DOM嵌套,优先挑这类“单节点方案”。

2.2 动效实现:animation 与 keyframes 的配合

核心代码永远是这两条:@keyframes定义关键帧,animation属性把时间轴挂到元素上。一个旋转loading的CSS可能短到十行以内:

.spinner { width: 40px; height: 40px; border: 4px solid rgba(0, 0, 0, 0.1); border-top-color: #3498db; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } }

很多新手以为keyframes必须写大段百分比,其实loading场景里,from和to两个关键帧往往就够。150款动画里真正迷人的部分,更多是多个元素设置了不同的animation-delay,让它们依次启动,形成视觉上的“波浪”或“错落”效果。比如三颗小球依次弹跳,就是给第2、第3颗球分别加上0.15s和0.3s的延迟。

2.3 形变转换:transform 中 rotate / scale / translate 怎么用

CSS3里最常用的三个形变函数,恰好对应三种最主流的loading形态。

rotate:转圈类。适合表达“处理中”“刷新中”。性能消耗低,因为它能命中GPU合成层。

scale:缩放类。适合表达“加载占比”“进度反馈”。CSS3的scale缩放方案在loading里最常见的用法是让圆形区域像呼吸一样由小到大、再由大到小。注意缩放最好不要单独作用于整个容器,否则容器里的文字会跟着一起变形,正确做法是把缩放作用于内部装饰层。

translate:位移类。适合模拟小球碰撞、字母跳动、进度条滑动。translate和opacity组合出现的概率非常高,因为位移结合透明度淡入淡出,会让元素移动不生硬。

这三类形变可以任意组合,比如“位移+缩放”“旋转+透明度渐变”。写代码时有个经验:loading动画里尽量只变化transform和opacity,不要动width、height、margin这些布局属性。前者走合成器,后者会强制重排,性能差距在低端手机上能差出十倍。

2.4 缓动与节奏:easing 决定“手感”

同样30帧的动画,linear和cubic-bezier呈现出的“手感”完全不同。线性旋转适合雷达、齿轮这类匀速机械感;弹跳小球则适合cubic-bezier(0.36, 0.07, 0.19, 0.97)这类先快后慢曲线;呼吸缩放更适合ease-in-out,让节点在两头减速。

150款素材中,有相当一部分的“高级感”就来自缓动。仔细观察会发现,很多炫酷效果的keyframes其实很普通,只是中间巧妙设置了一个反向关键帧。例如:

@keyframes pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(0.85); opacity: 0.5; } }

这种“两端相同、中间不同”的写法配合ease-in-out,会让整个动画循环得特别顺。做loading选缓动时,我建议不要直接套默认值,依次试一下linear、ease-in-out、cubic-bezier三档,只用5分钟,效果差别非常明显。

3. 150款loading的分类选型,别再大海捞针

3.1 按视觉形态分:圆环、点阵、骨架屏、文字、混合

整理这批素材时,我按视觉形态把它们分成了五类,分类表如下:

形态代表实现适用场景性能难度
圆环类border圆环、SVG circle描边全局加载、按钮内嵌高低
点阵类多个点缩放/透明度错落卡片局部加载中低
骨架屏灰色背景渐变扫光列表、图片占位中中
文字类字母依次位移/翻转品牌加载页高中
混合类多个动态元素组合引导页、首屏动画低中高

这种分类的价值在于你不需要在150个文件里挨个翻,看到“页面级、品牌感、需要撑住首屏”这些关键词,就直接定位到混合类和文字类;看到“接口请求按钮、表单提交中”就只看圆环类。我做内部组件库时,也按这套分类建目录,后续任何人进来都能快速找到需要的loading。

3.2 按使用场景选型:首屏、按钮、局部容器、图片占位

同一个loading放在不同位置,观感完全不同。选型时先问自己在哪个场景,而不是先看动画好不好看。

首屏加载:空间大、展示时间长,适合骨架屏或带品牌标识的混合动效。这类动画不能太闹,尽量保持页面整体色调统一,背景用浅色渐变,动画集中在视觉焦点。

按钮加载:空间很小,只允许一两秒内的微动效,圆环类最合适。这时有个细节:按钮里的loading必须和文字文案互斥,点击后按钮应进入disabled状态,把原来的文字替换成loading动画,尺寸控制在按钮高度的60%以内。我一般用CSS变量区分三种按钮尺寸:

.btn .spinner { width: calc(var(--btn-height) * 0.4); height: calc(var(--btn-height) * 0.4); border-width: calc(var(--btn-height) * 0.05); }

局部容器:比如表格刷新、面板重载,点阵类和细圆环类都不错。不建议用大骨架屏,那样会让局部区域看起来像整页白屏。图片占位:骨架屏是主流方案,因为它能预告内容的轮廓,减少用户等待的焦虑感。

3.3 配速与配色:经验值直接抄

先给一组我多年实际项目中验证过的参数,直接抄不会有大的偏差。

项目推荐值理由
单圈旋转时长0.6s - 1s低于0.5s显急促,高于1.2s显迟钝
点阵错落延迟0.1s - 0.2s低于0.1s像一起动,高于0.25s像排队
loading主色品牌色或灰色系不要用高饱和对比色作为主体
背景遮罩透明度rgba(0,0,0,0.35) 左右太高压抑,太低看不到loading
动画常驻时长3s内结束最好超过3s需要补充进度文字或重试入口

配色方面,千万别让整个loading由纯黑和大红组成,那会让用户觉得页面报错了。150款素材里面视觉最舒服的那批基本都符合“一个亮色点 + 若干浅色装饰”的规律。想做出质感,把主色饱和度降低5%-10%,页面会立刻温和很多。

4. 把150款变成一套可切换的组件

4.1 目录组织与命名规范

有了一堆CSS文件,直接扔到项目里早晚会乱。我常用的组织方式是按“形态-名称”命名目录:

loading-animations/ ├── spinner-ring/ │ ├── ring-1.html │ ├── ring-1.css │ ├── ring-2.html │ └── ring-2.css ├── dots/ │ ├── dots-1.html │ ├── dots-1.css │ └── ... ├── skeleton/ ├── text/ └── mixed/

每个动画的HTML和CSS尽量独立成文件。这样既方便预览,也方便后续转成组件。如果做成组件库,我建议暴露一个统一的注册表:

export const loadingRegistry = { ring1: { html: '...', css: '...' }, dots1: { html: '...', css: '...' }, skeletonCard: { html: '...', css: '...' }, };

注册表的好处是组件可以按key加载对应的模板和样式,实现真正的按需引用,而不是把所有动画全量打进bundle。

4.2 统一容器:HTML结构如何承载不同动画

无论动画内部长什么样,对外暴露的统一结构建议做成这样:

<div class="ui-loading">.ui-loading__inner { width: var(--ui-loading-size); height: var(--ui-loading-size); position: relative; display: inline-block; }

这样无论内部是圆环、点阵还是文字,尺寸都受同一个CSS变量控制。vBird里看到的那些“只换动画名,其他不变”的组件,原理就是这个。

4.3 CSS变量做主题化配置

主题化是组件能不能推广开的关键。150款动画如果要手动改颜色,人会疯。用CSS变量可以一次性搞定:

:root { --loading-primary: #3498db; --loading-secondary: rgba(52, 152, 219, 0.2); --loading-bg: rgba(255, 255, 255, 0.9); --loading-speed: 0.8s; } .ui-loading__inner { border-top-color: var(--loading-primary); border-color: var(--loading-secondary); }

动画中的每一个颜色、时长、尺寸都尽量抽成变量,后面你会感谢自己。比如项目A要求loading用品牌绿色,项目B要求Loading用天空蓝,只需要在入口处改两个变量,组件代码一行不动。同理,速度变量可以直接控制用户感知的“轻快/沉稳”,深色模式还能通过变量的覆盖自动切换。

4.4 按需加载与动画状态管理

做单页应用时,全量引入150个动画会让JS体积白白增加不少。好的做法是:所有loading组件懒加载,只在某个动画被使用时才注入对应CSS。

现代框架里,最容易落地的是CSS Module或者CSS-in-JS的按需方案。以React为例,可以封装一个组件,根据variant动态import:

function Loading({ variant = 'ring1', size = 32 }) { const [style, setStyle] = useState(null); useEffect(() => { import(`../loading-styles/${variant}.css`).then(() => setStyle(true)); }, [variant]); return <div className="ui-loading" style={{ width: size, height: size }}>{/* ... */}</div>; }

状态管理还要考虑一件事:同一个页面上多个loading同时出现时,不能让所有动画都从零开始跑。可以用一个全局的“loading上下文”记录当前页面的加载状态,当切换路由或关闭弹窗时,统一移除挂载节点并清除动画class,避免僵尸动画一直空转。这个小细节很多组件库都没处理好,我自己踩过一次——弹窗关了,背后loading还在转,排查半天才发现是忘记清定时器。

5. 接入实战:从素材到生产环境的常见问题

5.1 动画不动的诊断清单

收藏的150款动画往项目里搬时,“动画不动”是最高频问题。我列了一份排查清单,按顺序过一遍能解决九成情况:

  1. 检查元素是否能被CSS选中:类名冲突、选择器层级、CSS文件有没有被正确引入。
  2. 检查元素尺寸是否为0:border实现了圆环loading,若容器宽高是0,边框不显示;把width和height临时加到100px验证。
  3. 检查transform是否被覆盖:项目里通用样式如果写了transform: translateZ(0)提性能,可能会覆盖关键帧里的transform: rotate(360deg)。解决方案是用animation-composition或把旋转挂到子元素上。
  4. 检查prefers-reduced-motion:一些系统开启“减少动态效果”后,浏览器会限制CSS动画,可以用代码兜底显示静态loading。
  5. 检查浏览器兼容:低版本内核支持度有限,后面专门列兼容表。

5.2 页面卡顿与GPU加速

Loading动画虽然简单,但如果是全局弹层、满屏刷新,依然可能拖垮低端机。卡顿通常不是单个动画性能差,而是同时有过多元素在做复合动画。

排查手段有两个:打开DevTools的Performance面板录制一段loading,看动画片段里的Scripting和Rendering用时;再开启Rendering面板的“Layer borders”,观察有没有大量黄色图层。出现卡顿后,优先做三件事:

  • 把width、height、margin等布局属性替换成transform中的scale和translate。
  • 给动画元素加上will-change: transform(用完记得移除,否则内存占用会一直很高)。
  • 减少同时变化的元素数量,点阵loading从8个点降到5个点,视觉上几乎无差别。

5.3 与其他CSS框架的冲突处理

很多项目配套了Tailwind、Element、Antd,动画不动可能是因为框架级样式影响了子元素的定位方式。最常见的冲突有四个:

冲突来源现象解决方案
reset.css覆盖border样式border圆环完全消失在组件类名下重写border
flex导致容器尺寸异常点阵全部挤到一行给inner加固定宽高
全局transition干扰动画出现延迟或过渡对inner单独设置transition: none
全局transform属性关键帧rotate被覆盖给动画元素加更具体的选择器

如果项目UI框架自带transition且作用于所有后代元素,我习惯在loading容器上加一句:

.ui-loading__inner, .ui-loading__inner * { transition: none !important; animation: none !important; }

这里不是推荐滥用!important,但loading动画往往需要在框架重压下“保命”,这对生产环境是必要的兜底。

5.4 兼容性:一张表直接对照

CSS3动画主流浏览器支持已经很成熟,但是在老项目、特定浏览器环境里,还是要先看兼容基线:

特性ChromeFirefoxSafariIE/Edge旧内核
transform支持支持支持IE10+
animation支持支持支持IE10+
CSS变量支持支持支持 9.1+不支持
prefers-reduced-motion支持支持支持不支持
backdrop-filter部分支持支持不支持

如果你的产品需要兼容IE11,CSS变量方案就需要换成SCSS变量编译或者直接在组件里写死颜色。另外,animation-delay负数是一个很好用的技巧:把delay设为负值会让动画直接跳到时间轴中间,多个loading同时引入时它能让动画看起来“已经在跑”,而不是齐刷刷从第一帧启动。这点在首屏多组件加载时非常实用。

6. 三个经典loading从零手写

6.1 旋转圆环:最快落地的一款

单元素实现,前面已经贴过基础版。这里我给一个带磨砂遮罩的完整版,适合页面级全局loading:

<div class="loading-mask"> <div class="spinner-ring"></div> </div>
.loading-mask { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.75); backdrop-filter: blur(4px); z-index: 9999; } .spinner-ring { width: 48px; height: 48px; border: 5px solid rgba(52, 152, 219, 0.15); border-top-color: rgba(52, 152, 219, 0.9); border-radius: 50%; animation: ring-rotate 0.8s linear infinite; } @keyframes ring-rotate { to { transform: rotate(360deg); } }

有几个细节值得展开。用inset: 0代替top/left/right/bottom: 0能让代码更整洁;border-top-color单独设置彩色比其他三条边框全彩更耐看,视觉重心更集中;animation-delay不需要设置,因为旋转是连续循环的,任何时候插入都是“已在转”的状态。如果想做双色环,再加一层伪元素,错开旋转方向即可。

6.2 三点呼吸:局部加载最常用

按钮局部加载建议用点阵。以三个点为例,HTML结构保持三个span,CSS里利用nth-child控制延迟:

<div class="dots-loading"> <span></span> <span></span> <span></span> </div>
.dots-loading { display: flex; gap: 6px; } .dots-loading span { width: 8px; height: 8px; border-radius: 50%; background: var(--loading-primary, #3498db); animation: dot-breathe 1.2s ease-in-out infinite; } .dots-loading span:nth-child(2) { animation-delay: 0.2s; } .dots-loading span:nth-child(3) { animation-delay: 0.4s; } @keyframes dot-breathe { 0%, 100% { transform: scale(0.8); opacity: 0.5; } 50% { transform: scale(1.1); opacity: 1; } }

这里使用的“缩放+透明度”双属性组合,比单纯位移更柔和。gap设置成6px左右,点之间不会拥挤。如果你在按钮里用,记得把span尺寸改成按钮高度的40%,并把间距同步缩小,否则三个点会把按钮文案挤爆。

6.3 骨架屏:从列表到卡片都能套

骨架屏不属于典型的旋转loading,但用户体验最佳。以一张商品卡片为例:

<div class="skeleton-card"> <div class="skeleton-block image"></div> <div class="skeleton-line title"></div> <div class="skeleton-line price"></div> </div>
.skeleton-card { padding: 16px; border-radius: 12px; background: #fff; } .skeleton-block, .skeleton-line { background: linear-gradient(90deg, #eee 25%, #ddd 37%, #eee 63%); background-size: 400% 100%; animation: skeleton-sweep 1.4s ease infinite; } .skeleton-block.image { height: 120px; border-radius: 8px; margin-bottom: 12px; } .skeleton-line.title { height: 16px; border-radius: 4px; margin-bottom: 10px; } .skeleton-line.price { width: 80px; height: 16px; border-radius: 4px; } @keyframes skeleton-sweep { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

核心思路是让渐变背景在元素内部循环移动,模拟一道光扫过占位区域。这里用background-size: 400%保证扫光效果有足够的移动空间。骨架屏的动画时间可以比旋转类慢一些,1.3s到1.6s都不错,扫太快会让人分心。另外骨架屏最好搭配一点微弱的整体透明度变化,避免在暗色模式下显得刺眼。

7. 我的真实体会和差异化建议

7.1 收藏150款之后怎么消化

收藏这件事本身没有价值,消化才有。面对150款动画,我的方法是每周抽出半小时做“动画考古”:选三个动画,拆解它们的关键帧,尝试改一个参数看有什么变化。这样持续一个月,你会形成对动画节奏非常敏锐的手感。之后你再看到任何loading,会下意识猜它用了几个关键帧、每个子元素延迟多少,而不是只会说“好看”。

另外,我强烈建议每个开发者都维护一个自己的“小样本库”。不需要150款那么多,把自己最常用的20款CSS3 loading放进去,加上注释:为什么用这个、适合什么场景、有没有性能坑。这比收藏夹里堆几百个链接有用得多。面试或评审时,这也是一个很好的差异化亮点——大多数人都只是“用过”,你能讲清楚“为什么”。

7.2 做loading时的心态与设计原则

最后分享一个我踩过多次坑后总结的原则:loading动画的本质是“让等待变短”。它不是炫技舞台,不是把用户注意力全吸走的设计彩蛋。好的loading应该做到存在感适中、风格统一、对后续内容有所呼应。150款素材每款都不是终点,而是起点——当你理解了它们的构成和取舍,你完全能在此基础上改出自己的品牌版本,而不再需要满世界找素材。

我个人在实际项目里最常用的反而是最简单的圆环和骨架屏,复杂动效一年用不到两三次。但正因为看过了那150款,我才能确定简单的就是最优解,这就是大规模刷素材给我带来的最大底气。如果你也收藏了一堆“炫酷收藏夹”,别让它们吃灰,抽个下午,逐个打开、拆解、跑一遍,你会收获比“会抄代码”多得多的东西。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 1:55:34

Anaconda零基础安装配置教程:从conda环境到Jupyter Notebook实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:55:17

Ubuntu安装黑屏/裁剪问题的底层解决指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:55:13

Nginx 403错误排查全指南:从权限、配置到SELinux

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:55:13

GD32H759在RT-Thread下的I2C与RTC驱动开发实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 1:54:03

卫星组合导航仿真:从IMU噪声注入到紧耦合EKF的完整实践

简介&#xff1a;一套面向组合导航方向的Matlab仿真工程包&#xff0c;聚焦卫星组合导航与捷联惯性导航的算法实现&#xff0c;适合导航专业学生、科研人员及车载定位算法开发者。工程以实验3车载惯性里程计GPS组合导航实验为主线&#xff0c;涵盖初始对准&#xff08;粗/精对准…

作者头像 李华