ElementUI中的loading图标是怎么实现的

一、背景和意义

loading图标是前端使用频率较高的图标之一,前端在向后端请求数据时,在数据返回之前经常要先展示一下loading图标。ElementUI中就有v-loading属性用于展示loading图标,对应的文档页为:https://element.eleme.cn/2.13/#/zh-CN/component/loading,大致效果为:

文本大致介绍其loading图标是怎么实现的,掌握了loading图标的实现方法后,我们也可以不用引入ElementUI,自己就可以直接写一个loading图标。

二、抽取loading图标相关代码

使用chrome开发者工具可以查看到loading图标的HTML代码:

把相关的HTML代码抽取出来,得到内容如下:

html 复制代码
<style>
.el-loading-mask {
  position: absolute;
  z-index: 2000;
  background-color: hsla(0,0%,100%,.9);
  margin: 0;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  transition: opacity .3s;
}
.el-loading-spinner {
  top: 50%;
  margin-top: -21px;
  width: 100%;
  text-align: center;
  position: absolute;
}
.el-loading-spinner .circular {
  height: 42px;
  width: 42px;
  animation: loading-rotate 2s linear infinite;
}
.el-loading-spinner .path {
    animation: loading-dash 1.5s ease-in-out infinite;
    stroke-dasharray: 90,150;
    stroke-dashoffset: 0;
    stroke-width: 2;
    stroke: #409eff;
    stroke-linecap: round;
}
@keyframes loading-rotate {
    to {
        transform: rotate(1turn)
    }
}
@keyframes loading-dash {
    0% {
        stroke-dasharray: 1,200;
        stroke-dashoffset: 0
    }
    50% {
        stroke-dasharray: 90,150;
        stroke-dashoffset: -40px
    }
    to {
        stroke-dasharray: 90,150;
        stroke-dashoffset: -120px
    }
}
</style>
<div class="el-loading-mask">
  <div class="el-loading-spinner">
    <svg viewBox="25 25 50 50" class="circular">
      <circle cx="50" cy="50" r="20" fill="none" class="path"></circle>
    </svg>
  </div>
</div>

将这一段代码保存成html文件,然后用浏览器打开,到看到如下效果:

在上面的代码中,<div class="el-loading-mask">是遮罩层,<div class="el-loading-mask">的作用是使loading图标位于页面正中间。现在我们主要关注loading图标的实现,可以将那两个div去掉,代码简化为:

html 复制代码
<style>
.circular {
  height: 42px;
  width: 42px;
  animation: loading-rotate 2s linear infinite;
}
.path {
    animation: loading-dash 1.5s ease-in-out infinite;
    stroke-dasharray: 90,150;
    stroke-dashoffset: 0;
    stroke-width: 2;
    stroke: #409eff;
    stroke-linecap: round;
}
@keyframes loading-rotate {
    to {
        transform: rotate(1turn)
    }
}
@keyframes loading-dash {
    0% {
        stroke-dasharray: 1,200;
        stroke-dashoffset: 0
    }
    50% {
        stroke-dasharray: 90,150;
        stroke-dashoffset: -40px
    }
    to {
        stroke-dasharray: 90,150;
        stroke-dashoffset: -120px
    }
}
</style>
<svg viewBox="25 25 50 50" class="circular">
  <circle cx="50" cy="50" r="20" fill="none" class="path"></circle>
</svg>

用浏览器打开,得到效果如下:

三、loading图标相关代码解读

3.1 svg部分解读

首先,代码中大概看出loading图标是用svg画出来的,另外还有两个keyframes动画序列。为了层层拆解,先利用chrome开发工具,将两个animation取消勾选,即禁掉动画,看到一个静态的四分之三圆,如图所示:

在HTML代码中,圆对应的元素是<circle>,即<circle cx="50" cy="50" r="20" fill="none" class="path"></circle>,其中cx="50" cy="50"表示圆心坐标,即圆心的位置距离画布左边缘和上边缘的距离都是50像素,r="20"表示圆的半径是20像素。fill="none"表示圆是空心的,无填充颜色。

不过看到这里可能会有一个疑问,如果圆心坐标是(50, 50),半径是20,那么圆到左边缘和上边缘的距离就是30,应该看起来大于圆的半径了,但从页面效果看不是这样。这其实是svg中的viewBox="25 25 50 50"这一属性起的作用,这一属性表示将以(25, 25)为左上角、长和宽都是50的矩形区域的内容缩放到整个svg画布。作为对比,我们把viewBox属性去掉,看到效果为:

圆只剩下一下角了,这是因为目前画布大小为42px,圆的大部分都在画布之外了。将画布调大一点,比如将width和height都设置为100%,另外再加上如下一段代码以红色标出以(25, 25)为左上角、长和宽都是50的矩形区域:

<div style="position: absolute;top: 25;left: 25;width: 50;height: 50;border: 1px solid red;"></div>

得到的效果如下:

初看红色矩形区域和圆所在的区域好像也对不上,这其实是由于body中有一个8px的margin导致的:

给body加上一个position: relative属性,最后看到红色矩形区域和圆所在的区域差不太多:

viewBox="25 25 50 50"是将上图的红色矩形区域缩放到整个画布,如果svg元素的长和宽大于50则会放大,如果小于50则会缩小。这样通过设置svg的width和height,就能等比例放大和缩小loading图标。

3.2 stroke相关属性解读

circle元素设置了如下几个stroke开头的CSS属性:

其中stroke-width: 2;stroke: #409eff;相对比较好理解,分别设置了圆圈的宽度和颜色。stroke-linecap: round;用于设置描边,图标小的时候其实看不出描边的效果,如果将svg调大一点,可以看到描边和不描边的效果差别:

stroke-dasharray: 90,150;表示画圆的边时,先画长度为90px的线,再留长度为150px的空白。由于该圆的半径是20px,那么一圈的长度大概是3.14 * 20 * 2 = 125.6,90px就是将近3/4圆。那这90px是从哪个点开始往哪个方向开始画呢,为了方便查看,可以将stroke-dasharray属性改成10 5 30 1000,该值表示先画10px的线,再留5px的空白,再画30px的线,后面的留1000px的空白,将看到如下效果:

显然,画圆时是先从右边中间处开始,沿着顺时针方向画。

最后一个stroke属性是stroke-dashoffset: 0;,该属性表示偏移量,目前是设置为0,相当于没有偏移。沿用stroke-dasharray: 10 5 30 1000;这一设置,一个正的偏移相当于往画线的相反方向拉拽画出的线:

如果将stroke-dashoffset设置为5,则第一根10px的蓝线应该就只剩下的一半了,看到的效果如下:

如果将stroke-dashoffset设置为10,则第一根10px的蓝线就刚好没了:

负的偏移则相当于往画线的方向拉拽画出的线:

将stroke-dashoffset设置为-10的效果如下:

10px和30px的线都还在,只是往画线方向移动了。

3.3 动画部分解读

第一个动画loading-rotate相对比较简单:

css 复制代码
.circular {
  ...
  animation: loading-rotate 2s linear infinite;
}
...
@keyframes loading-rotate {
    to {
        transform: rotate(1turn)
    }
}

transform: rotate(1turn)表示旋转一周,前面的2s linear infinite;表示每2秒旋转一周,重复无限次。如果只保留loading-rotate一个动画,另外一个动画先禁用,看到的效果为:

第二个动画loading-dash相对更复杂一些:

css 复制代码
.path {
    animation: loading-dash 1.5s ease-in-out infinite;
    ...
}
...
@keyframes loading-dash {
    0% {
        stroke-dasharray: 1,200;
        stroke-dashoffset: 0
    }
    50% {
        stroke-dasharray: 90,150;
        stroke-dashoffset: -40px
    }
    to {
        stroke-dasharray: 90,150;
        stroke-dasharray: -120px
    }
}

先看@keyframes部分,动画都是在stroke-dasharray和stroke-dasharray的不同值之间过渡,上一小节已经介绍了这两个属性的作用,这里不再详细解释。初始状态stroke-dasharray: 1,200; stroke-dashoffset: 0对应的是从一个非常小的点开始:

然后过渡到stroke-dasharray: 90,150; stroke-dashoffset: -40px这个状态:

在这两个状态之间,蓝线会一直变长,在第二个状态的时候达到最长。接下来蓝线会逐渐变短,过渡到了第三个状态,再次接近于成为一个蓝点:

这三个状态连起来就是这样的效果:

animation中的ease-in-out表示过渡效果是先缓慢地开始,然后加速,然后缓慢地结束。如果像之前的loading-rotate一样用linear则是这样的效果:

稍微显得有些机械和呆板,如果loading-dash也是用linear过渡效果,两个动画结合在一起的效果是这样:

跟原作相比是稍微差了一点点:

四、代码层面上的优化

ElementUI的loading图标的svg代码为:

html 复制代码
<svg viewBox="25 25 50 50" class="circular">
  <circle cx="50" cy="50" r="20" fill="none" class="path"></circle>
</svg>

其实viewBox也可以简化为以(0, 0)作为起点:

html 复制代码
<svg viewBox="0 0 50 50" class="circular">
  <circle cx="25" cy="25" r="20" fill="none" class="path"></circle>
</svg>

另外loading-dash的代码为:

html 复制代码
@keyframes loading-dash {
    0% {
        stroke-dasharray: 1,200;
        stroke-dashoffset: 0
    }
    50% {
        stroke-dasharray: 90,150;
        stroke-dashoffset: -40px
    }
    to {
        stroke-dasharray: 90,150;
        stroke-dasharray: -120px
    }
}

单位px也可以省略,变成这样:

html 复制代码
@keyframes loading-dash {
    0% {
        stroke-dasharray: 1,200;
        stroke-dashoffset: 0
    }
    50% {
        stroke-dasharray: 90,150;
        stroke-dashoffset: -40
    }
    to {
        stroke-dasharray: 90,150;
        stroke-dasharray: -120
    }
}

最终运行效果不变。

相关推荐
J不A秃V头A40 分钟前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂1 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客2 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹2 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码2 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
天下无贼!3 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr3 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林3 小时前
npm发布插件超级简单版
前端·npm·node.js
罔闻_spider4 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔4 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab