css动画库
纯 CSS 实现“推箱子”逻辑循环 Loader
Dec 22, 2025
78 阅读
官方团队
今天分享一个挺烧脑的加载动画。
乍一看是三个方块在转圈,仔细看你会发现,这其实是三个独立的 `div` 在进行一场精密的“接力赛”。每个方块都在特定的时间点变长、移动、缩短,最后完美地交换了位置。
这种动画不需要复杂的 3D 变换,纯粹靠**时间轴(Keyframes)的数学计算**来实现视觉欺骗。
### 效果预览
看着非常舒适,强迫症福音:
***donghua
<style>
.loader-container {
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
background: #222; /* 加个深色背景对比更明显 */
}
.loader {
width: 112px;
height: 112px;
position: relative; /* 确保子元素绝对定位相对于它 */
}
.box1,
.box2,
.box3 {
border: 16px solid #f5f5f5;
box-sizing: border-box;
position: absolute;
display: block;
background-clip: padding-box; /* 稍微优化一下边框渲染 */
}
.box1 {
width: 112px;
height: 48px;
margin-top: 64px;
margin-left: 0px;
animation: abox1 4s 1s forwards ease-in-out infinite;
}
.box2 {
width: 48px;
height: 48px;
margin-top: 0px;
margin-left: 0px;
animation: abox2 4s 1s forwards ease-in-out infinite;
}
.box3 {
width: 48px;
height: 48px;
margin-top: 0px;
margin-left: 64px;
animation: abox3 4s 1s forwards ease-in-out infinite;
}
@keyframes abox1 {
0% { width: 112px; height: 48px; margin-top: 64px; margin-left: 0px; }
12.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
25% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
37.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
50% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
62.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
75% { width: 48px; height: 112px; margin-top: 0px; margin-left: 0px; }
87.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
100% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
}
@keyframes abox2 {
0% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
12.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
25% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
37.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
50% { width: 112px; height: 48px; margin-top: 0px; margin-left: 0px; }
62.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
75% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
87.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
100% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
}
@keyframes abox3 {
0% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
12.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
25% { width: 48px; height: 112px; margin-top: 0px; margin-left: 64px; }
37.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
50% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
62.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
75% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
87.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
100% { width: 112px; height: 48px; margin-top: 64px; margin-left: 0px; }
}
</style>
<div class="loader-container">
<div class="loader">
<div class="box1"></div>
<div class="box2"></div>
<div class="box3"></div>
</div>
</div>
***
### 核心逻辑拆解
这个动画的代码量主要集中在 `@keyframes` 上,它的核心思想是“时间切片”。
#### 1. 网格布局的错觉
整个容器是 `112px * 112px`,实际上被分成了四个象限(虽然没有显式画出来)。
每个小方块基础尺寸是 `48px * 48px`,加上间隙正好填满这个空间。
#### 2. 接力棒机制
注意看三个动画的时间轴,它们被精细地切分成了 `12.5%` 的倍数(100% / 8步 = 12.5%)。
* **0% - 25%**:`box3` 变长,把位置让出来。
* **25% - 50%**:`box2` 变长,横向移动。
* **50% - 75%**:`box1` 变长,纵向移动。
每个方块在自己的回合里,通过改变 `width` 或 `height` 来模拟“伸展”,然后通过 `margin` 来改变位置。
#### 3. 视觉暂留
最妙的地方在于,当一个方块变长(比如 `width: 112px`)填补空缺时,另一个方块正好缩短并移动。因为速度很快且配合完美,人眼会觉得它们是在互相“推动”,而不是简单的原地变形。
### 源码自取
这段代码非常适合用在深色背景的页面上,作为 Loading 状态。
```html
<style>
/* 容器样式,实际使用时可根据需要调整 */
.loader-container {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
background: #2b2b2b; /* 深色背景效果更佳 */
}
.loader {
width: 112px;
height: 112px;
position: relative;
}
/* 三个方块的公共样式 */
.box1, .box2, .box3 {
border: 16px solid #f5f5f5;
box-sizing: border-box;
position: absolute;
display: block;
}
/* Box 1 初始状态与动画 */
.box1 {
width: 112px;
height: 48px;
margin-top: 64px;
margin-left: 0px;
animation: abox1 4s 1s forwards ease-in-out infinite;
}
/* Box 2 初始状态与动画 */
.box2 {
width: 48px;
height: 48px;
margin-top: 0px;
margin-left: 0px;
animation: abox2 4s 1s forwards ease-in-out infinite;
}
/* Box 3 初始状态与动画 */
.box3 {
width: 48px;
height: 48px;
margin-top: 0px;
margin-left: 64px;
animation: abox3 4s 1s forwards ease-in-out infinite;
}
/* 核心动画逻辑:通过精密的百分比控制形变和位移 */
@keyframes abox1 {
0% { width: 112px; height: 48px; margin-top: 64px; margin-left: 0px; }
12.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
25% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
37.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
50% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
62.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 0px; }
75% { width: 48px; height: 112px; margin-top: 0px; margin-left: 0px; }
87.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
100% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
}
@keyframes abox2 {
0% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
12.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
25% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
37.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 0px; }
50% { width: 112px; height: 48px; margin-top: 0px; margin-left: 0px; }
62.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
75% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
87.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
100% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
}
@keyframes abox3 {
0% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
12.5% { width: 48px; height: 48px; margin-top: 0px; margin-left: 64px; }
25% { width: 48px; height: 112px; margin-top: 0px; margin-left: 64px; }
37.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
50% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
62.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
75% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
87.5% { width: 48px; height: 48px; margin-top: 64px; margin-left: 64px; }
100% { width: 112px; height: 48px; margin-top: 64px; margin-left: 0px; }
}
</style>
<div class="loader-container">
<div class="loader">
<div class="box1"></div>
<div class="box2"></div>
<div class="box3"></div>
</div>
</div>
```