写在前面
在某个技术相关的群里,一个在参加某大厂青训营的学弟展示了他接下来了的一个创意,其中就涉及到在部分使用一个Loading动画组件。虽然对于学弟来说,他展示的组件截图,对于一个普通的Loading已经达标,但是我这种没机会参加的已经工作的社会闲杂人士,看到以后还是耐不住手痒,想写一个效果更接近的。
虽然参加工作已经一段时间了,但是实际工作中其实也非常少使用CSS做这种轮子,大都是来自于组件,所以昨晚从开始策划写,到发现思路错误调整思路最终写完,一共花费了接近两个小时,想我配置一个Gitlab也才五个小时而已,实在是学艺不精甚是惭愧,中途还花费了十分钟查阅SCSS官方文档。
最终完成效果展示
效果图是用mp4的录屏转的gif,所以看起来有点掉帧,实际效果非常流畅(ffmpeg还是不够熟练)。
查看代码
开发过程
需求分析
虽然动画效果很简单,但是开发前还是要明确一下实际开发要做一些什么,我开发花费的两小时时间,其中一半以上都是花费在了错误思路的实现上,不但实现困难,并且一点效果也没达到,非常的痛苦。
但是最终在多次观察Window加载的动态效果后,调整思路,实际花费不到半小时。
在动画中无非是5个白球围绕一个圆心旋转的过程,并且在刚出场时,是一个全透明快速过度到不透明的过程。其中旋转是分成三个阶段的,第一阶段180度,然后第二阶段是基于第一阶段快速旋转360度,第三阶段则是在第二阶段基础上,旋转180度,并且在旋转快要完成时,慢慢的变成透明色消失。
不过为了方便,干脆去除了第二阶段,将第一阶段直接过渡到第三阶段,加上无限循环后,效果也还是可以的😆。
上述的提到的动画其实正好对于了几个CSS的属性:
- transform:rotate 旋转
- opacity 透明度 而在实现过程中,由于要固定一个圆心,方便调整球的初始位置,还有后续的角度旋转轴心,所以还需要用到:
- transform-origin 变换中心
transform
是CSS3提供的一个实现形状变化与位置变化的一个属性,配合transition
或者animation
属性可以产生动画过度的一个效果。
代码设计
技术选择
由于需要产生批量的元素,并且要给批量元素快速绑定对应的CSS样式,所以HTML与CSS都要选取对应的预处理语言,方便我们快速构建(偷懒)。
技术选择如下:
Pug部分
首先我们需要构建基础的文档结构,在上面分析中,球是围绕一个圆心旋转的,所以我们需要先构建一个圆心
#app
// app 是总体所在的容器
.loading
// loading就是圆心
然后我们需要绘制球,但是这里要注意,由于HTML渲染机制的问题,我们的球无论通过什么方式绘制,计算球本身的圆点相对于.loading
时都是在球的左上角,所以我们还需要一个容器负责我们将所有球的整体坐标做一个偏移,达到对准圆心的目的,方便添加动画时还能保证绝对的局中。
而球本身也要注意,若球本身想设定相对于圆心的绝对位置,那么使用position:absolute
后,设定对应的left
与top
可以更方便直接模拟出坐标系的xy点。但是通过这种方式设定完的球并不方便在添加后续的位移动画。所以我们将球在拆分一级,让最里面一层来等待附加动画。
画球的时候要注意,我们需要的球正好是五个,正好对应坐标系里0度角,30度角,45度角,60度角以及90度角,他们的后续的坐标位置应该差不多都在标准圆的边上,五个球在添加完宽度的密集程度也是最合适的。这里我们会使用到pug的循环语句:
#app
.loading
.wrapper
for i in Array(5)
.item
.ball
Scss部分
接下来开始附加样式,由于球是白色的,所以首先就得让背景变成能够凸显白色的黑色,我选择了一个比较中性一些的黑色
body {
background-color: #333;
}
然后先让Loading整体来到画面的正中央
#app {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
然后调整一下球的偏差位置,我这里将球的大小设定成了10px
#app {
.loading {
$size: 10px;
.wrapper {
$offset: -#{$size/2};
position: absolute;
left: $offset;
top: $offset;
}
}
}
然后只需要把球画出来即可,这里则使用到了scss的@for from through
关键字来完成循环创建。由于数学不好,并没有想到循环下如何用公式直接计算出五个点的xy坐标,所以干脆选择提前将坐标计算后,通过map
的形式直接映射上去,这里要注意,若要使用SCSS的map
要使用@use
添加对应的功能模块。
@use "sass:map";
#app {
.loading {
$size: 10px;
$xyMaps: (
1: (
"x": 0px,
"y": 50px
),
2: (
"x": 19px,
"y": 44px
),
3: (
"x": 35.3px,
"y": 35.3px
),
4: (
"x": 44px,
"y": 19px
),
5: (
"x": 50px,
"y": 0px
),
);
.wrapper {
.ball {
border: $size/2 solid #fff;
border-radius: 50%;
background-color: #fff;
transform-origin: -#{$offsetX} -#{$offsetY};
}
@for $i from 1 through 5 {
.item:nth-child(#{$i}) {
$item: map.get($xyMaps, $i);
$offsetX: map.get($item, "x");
$offsetY: map.get($item, "y");
position: absolute;
left: $offsetX;
top: $offsetY;
.ball { //这里待会儿添加动画 }
}
}
}
}
}
此时已经可以看到大概的样式出来了
剩下的就是把动画写出来,然后附加上去即可。
动画定义如下
@keyframes rotate180 {
0% {
transform: rotate(0deg);
opacity: 0;
}
10% {
opacity: 0.5;
}
25% {
opacity: 1;
transform: rotate(180deg);
}
50% {
transform: rotate(180deg);
}
70% {
opacity: 1;
}
71% {
opacity: 0.5;
}
73% {
opacity: 0;
}
75% {
transform: rotate(360deg);
}
100% {
transform: rotate(360deg);
opacity: 0;
}
}
动画的定义非常暴力,就是按时间计算好每个时间点应该完成的动作。
然后只需要附加上去即可
.ball {
opacity: 0;
animation: rotate180 2.5s #{($i - 1) * 0.05}s ease infinite;
}
至此就大功告成啦🎉🎉
写在最后
写这个动画本身并不复杂,但是需要一点耐心跟理解能力,昨晚我是在吃完火锅脑袋充血的情况下开始写,所以中间因为错误的思路几乎都想放弃,不过最终还是耐心写完了,可喜可贺。