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

最终运行效果不变。

相关推荐
y先森4 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy4 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189114 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿5 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡6 小时前
commitlint校验git提交信息
前端
虾球xz6 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇7 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒7 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员7 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐7 小时前
前端图像处理(一)
前端