CSS3 实现宝可梦剑盾精灵球 Loading 效果(带源码)
缘起
11.18「宝可梦朱·紫」正式发售,我才记起还没在 Switch 上玩过「宝可梦剑·盾」,赶紧趁着双十一,在某宝上下单了一张卡带,为双十一做出了一点微博的贡献。
到手才发现,买的是二手卡带。但没关系,价格减半,快乐加倍,一个周末都忙着在旷野地带奔跑,几乎要练成十里坡剑神。
恰巧最近在看 CSS3 相关的内容,对游戏里的 Loading 过场动画产生了兴趣,想着能不能用 CSS3 来实现下面这个效果。
实现效果:
做静态的一个精灵球
要实现这个效果,第一步先要做一个静态的精灵球。精灵球图案不复杂,拆分下来就是几个图层背景的叠加:
- 第一层是线性渐变背景,上半部分 46% 的面积是红色(色值:#cb0905),中间 8% 的部分是黑色(色值:#000000),下半部分 50% 的面积是白色(色值:#ffffff)
- 第二层是径向渐变背景,中间是占 15% 半径的白色圆,接着是一个占 7% 半径的黑色圆环,剩下的部分设为透明
转化成代码:
<!-- 精灵球元素 -->
<div class="pkm_ball_bg"></div>
.pkm_ball_bg {
/* 设置大小 */
width: 400px;
height: 400px;
/* 设置背景 */
background:
radial-gradient(white 15%, black 15%, black 22%, transparent 22%),
linear-gradient(#cb0905 46%, #000000 46%, #000000 54%, #ffffff 54%);
/* 设置为圆形 */
border-radius: 9999px;
}
这样就得到一个方形的精灵球。
可以看到虽然使用了渐变背景,但图案上并没有渐变效果,这里用了一个小技巧:在同一个位置同时设置两个颜色,达到颜色跳变的效果。
如:#cb0905 46%, #000000 46%
,表示在 46%的位置从红色变化到黑色,由于渐变距离为 0 ,表现出来就是颜色跳变的效果。
另外这里需要注意一点:
- 在 HTML 里,元素重叠时,后书写的元素会覆盖在前面书写的元素上。
- 但使用
background
属性叠加多层背景时,图层的放置顺序则是相反的,从顶到底覆盖,类似栈结构,先书写的背景层在上层,后书写的背景层在下层。
形状的调整是通过设置圆角来实现:border-radius: 9999px;
,简单起见,参考 tailwind css 设置成了一个巨大值。
如何让它动起来
有了一个静态的精灵球,让它动起来还不容易?回过头再看下动画效果:
- 精灵球整体绕着圆心在做 360°旋转
- 精灵球的上下两部分,分别顺时针作绘制扇形的处理,先从头到尾将扇形从 0 绘制到 180°,再从尾到头将扇形绘制从 180° 绘制到 0°。
整体绕圆心做旋转,这一步好实现:
.pkm_ball_bg {
/* 设置整体绕着中心旋转 */
transform-origin: center;
animation: rotate-clockwise-360 2s linear infinite;
}
/* 顺时针 360° 旋转的动画 */
@keyframes rotate-clockwise-360 {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
第二步动画就比较棘手了:
- 静态的精灵球使用的是线性渐变实现,而不是使用扇形实现
- 而且 CSS 没提供绘制扇形的 API
- CSS 的帧动画在
linear-gradient
属性上不支持插帧,表现就是跳变
既然不行,那只能换个思路,毕竟计算机视觉是一门欺骗的艺术,重新拆解一下:
扇形可通过叠加两层元素实现:
- 下面一层是真实层,显示我们想要的颜色(比如红色)
- 上面一层是遮盖层,用背景色相同的颜色
当遮盖层相对于右下角旋转时,看起来的效果就像是在绘制圆的左上部分的扇形。
同理,相对于左下角/右上角/左上角旋转时,看起来的效果就像是在绘制圆的右上/左下/右下部分的扇形。
将左上、左下、右上、右下组合起来,再通过动画配置,就能变相实现扇形绘制的效果。
重新绘制一个精灵球
<div class="pkm_ball_loading">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
/* 精灵球 loading 根元素样式 */
.pkm_ball_loading {
width: 400px;
height: 400px;
/* 直接设置背景,而不设置 overflow: hidden 避免裁切边缘有残留颜色 */
background: linear-gradient(#cb0905 50%, #ffffff 50%);
/* 裁剪为圆形 */
border-radius: 9999px;
position: relative;
/* 网格布局,一行 2 个元素,元素宽度为布局的一半宽度 */
display: grid;
grid-template-columns: auto auto;
/* 行间距,模拟中间的黑色横条 */
grid-row-gap: 6%;
grid-column-gap: 0%;
}
/* 精灵球的开关和中间黑色条单独成一层,作为最上层盖住 */
.pkm_ball_loading::after {
content: "";
width: 100%;
height: 100%;
position: absolute;
background:
radial-gradient(white 15%, black 15%, black 22%, transparent 22%),
linear-gradient(transparent 46%, #000000 46%, #000000 54%, transparent 54%);
}
/* 布局中的每个元素的公共属性 */
.pkm_ball_loading > div {
width: 100%;
height: 100%;
/* 避免子元素旋转后超出父布局 */
overflow: hidden;
/* 设置布局,不然伪类不生效 */
display: flex;
/* 设置定位,作为内部子元素的定位基点 */
position: relative;
}
.pkm_ball_loading > div::before {
content: "";
/* 宽高设为 2 倍,确保旋转的时候完全遮盖 */
width: 200%;
height: 200%;
/* 遮盖层的颜色 */
background: #000000;
}
/* 网格布局第一个元素,即左上 */
.pkm_ball_loading > div:nth-child(1)::before {
/* 定位设为放在右下,和旋转点一致 */
position: absolute;
bottom: 0;
right: 0;
/* 设置选择点为右下 */
transform-origin: bottom right;
/* 逆时针旋转 45° */
transform: rotate(-45deg);
}
/* 网格布局第一个元素,即右上 */
.pkm_ball_loading > div:nth-child(2)::before {
/* 定位设为放在左下,和旋转点一致 */
position: absolute;
bottom: 0;
left: 0;
/* 设置选择点为右下 */
transform-origin: bottom left;
/* 顺时针旋转 45° */
transform: rotate(45deg);
}
/* 网格布局第一个元素,即左下 */
.pkm_ball_loading > div:nth-child(3)::before {
/* 定位设为放在右上,和旋转点一致 */
position: absolute;
top: 0;
right: 0;
/* 设置选择点为右下 */
transform-origin: top right;
/* 顺时针旋转 45° */
transform: rotate(45deg);
}
/* 网格布局第一个元素,即右下 */
.pkm_ball_loading > div:nth-child(4)::before {
/* 定位设为放在左上,和旋转点一致 */
position: absolute;
top: 0;
left: 0;
/* 设置选择点为右下 */
transform-origin: top left;
/* 逆时针旋转 45° */
transform: rotate(-45deg);
}
/* loading 整体 360°旋转 */
.pkm_ball_loading {
animation: rotate-clockwise-360 2s linear infinite;
}
/* 第一第四个遮盖物,先逆时针旋转90°,再顺时针旋转90° */
.pkm_ball_loading > div:nth-child(1)::before,
.pkm_ball_loading > div:nth-child(4)::before {
animation: rotate-anticlockwise-90 2s linear infinite;
}
/* 第一第四个遮盖物,先顺时针旋转90°,再逆时针旋转90° */
.pkm_ball_loading > div:nth-child(2)::before,
.pkm_ball_loading > div:nth-child(3)::before {
animation: rotate-clockwise-90 2s linear infinite;
}
/* 顺时针 360° 动画 */
@keyframes rotate-clockwise-360 {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 顺时针 90° 再逆时针 90° 动画 */
@keyframes rotate-clockwise-90 {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(90deg);
}
100% {
transform: rotate(0deg);
}
}
/* 逆时针 90° 再顺时针 90° 动画 */
@keyframes rotate-anticlockwise-90 {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(-90deg);
}
100% {
transform: rotate(0deg);
}
}
最终效果:
特别鸣谢
- 免费在线视频转 GIF 软件:https://www.tutieshi.com/video/