CSS梭哈一个Windows10的开机Loading动画

1,294 阅读5分钟

写在前面

在某个技术相关的群里,一个在参加某大厂青训营的学弟展示了他接下来了的一个创意,其中就涉及到在部分使用一个Loading动画组件。虽然对于学弟来说,他展示的组件截图,对于一个普通的Loading已经达标,但是我这种没机会参加的已经工作的社会闲杂人士,看到以后还是耐不住手痒,想写一个效果更接近的。
虽然参加工作已经一段时间了,但是实际工作中其实也非常少使用CSS做这种轮子,大都是来自于组件,所以昨晚从开始策划写,到发现思路错误调整思路最终写完,一共花费了接近两个小时,想我配置一个Gitlab也才五个小时而已,实在是学艺不精甚是惭愧,中途还花费了十分钟查阅SCSS官方文档。

最终完成效果展示

windowsLoad.gif

效果图是用mp4的录屏转的gif,所以看起来有点掉帧,实际效果非常流畅(ffmpeg还是不够熟练)。

查看代码

WindowsLoading (codepen.io)

开发过程

需求分析

虽然动画效果很简单,但是开发前还是要明确一下实际开发要做一些什么,我开发花费的两小时时间,其中一半以上都是花费在了错误思路的实现上,不但实现困难,并且一点效果也没达到,非常的痛苦。
但是最终在多次观察Window加载的动态效果后,调整思路,实际花费不到半小时。
在动画中无非是5个白球围绕一个圆心旋转的过程,并且在刚出场时,是一个全透明快速过度到不透明的过程。其中旋转是分成三个阶段的,第一阶段180度,然后第二阶段是基于第一阶段快速旋转360度,第三阶段则是在第二阶段基础上,旋转180度,并且在旋转快要完成时,慢慢的变成透明色消失。
不过为了方便,干脆去除了第二阶段,将第一阶段直接过渡到第三阶段,加上无限循环后,效果也还是可以的😆。 上述的提到的动画其实正好对于了几个CSS的属性:

  • transform:rotate 旋转
  • opacity 透明度 而在实现过程中,由于要固定一个圆心,方便调整球的初始位置,还有后续的角度旋转轴心,所以还需要用到:
  • transform-origin 变换中心 transformCSS3提供的一个实现形状变化与位置变化的一个属性,配合transition或者animation属性可以产生动画过度的一个效果。

代码设计

技术选择

由于需要产生批量的元素,并且要给批量元素快速绑定对应的CSS样式,所以HTMLCSS都要选取对应的预处理语言,方便我们快速构建(偷懒)。 技术选择如下:

Pug部分

首先我们需要构建基础的文档结构,在上面分析中,球是围绕一个圆心旋转的,所以我们需要先构建一个圆心

#app
    // app 是总体所在的容器
    .loading
        // loading就是圆心
      

然后我们需要绘制球,但是这里要注意,由于HTML渲染机制的问题,我们的球无论通过什么方式绘制,计算球本身的圆点相对于.loading时都是在球的左上角,所以我们还需要一个容器负责我们将所有球的整体坐标做一个偏移,达到对准圆心的目的,方便添加动画时还能保证绝对的局中。
而球本身也要注意,若球本身想设定相对于圆心的绝对位置,那么使用position:absolute后,设定对应的lefttop可以更方便直接模拟出坐标系的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的形式直接映射上去,这里要注意,若要使用SCSSmap要使用@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 { //这里待会儿添加动画 }
                }
            }
        }
    }
}

此时已经可以看到大概的样式出来了

image.png 剩下的就是把动画写出来,然后附加上去即可。
动画定义如下

@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;
}

至此就大功告成啦🎉🎉

写在最后

写这个动画本身并不复杂,但是需要一点耐心跟理解能力,昨晚我是在吃完火锅脑袋充血的情况下开始写,所以中间因为错误的思路几乎都想放弃,不过最终还是耐心写完了,可喜可贺。

引用