制作Loading组件

最近项目中使用到一个Loading效果,其实是一个很简单的效果,主要是因为这个Loading出现在不同的场景之中,而且大小也不一致。对于这样的效果,往往都会想通过组件的方式来处理,其出发点就是更易维护,易扩展。当然,这对于前端的同学而言并没有什么复杂性,也没有多少技术含量。不过我还是希望把这个过程记录下来。

咱们先来看一个截图:

640?wx_fmt=png

从上图可以看出来,其效果是一样的,不同之处是使用场景不同,大小不同而以。那么接下来,就来聊聊这样的一个效果怎么通过不同的方式来完成。

实现原理

实现上面的这样的一个效果,我们需要有一点点数学相关的知识,这样更有易于后续效果制作。比如说,示例中有14个圆点,那么这14个圆点具有自己相关的特性参数:

  • 圆点的半径,比如说30px

  • 圆点的颜色,比如说#f36

  • 圆点的位置,按一定的比例分布在一个容器上,并且围成一个圆形

比如图所示:

640?wx_fmt=png

注意,上较绘制的不是很标准,只是为了阐述问题。这里将会使用到一些数学公式,因为我们需要知道每个圆点的圆心的位置。

640?wx_fmt=png

继续简化一下,就如下图这样:

640?wx_fmt=png

这里会运用到一些角度和弧度相关的知识,其实这部分知识点,在学习Canvas的时候有所涉猎。在CSS中,咱们做旋转一般使用的是deg(角度)为单位,但在JavaScript绘制圆或圆弧却常用弧度rad为单位。

一个完整的圆的弧度是,所以2π rad = 360°1 π rad = 180°1°=π/180 rad1 rad = 180°/π(约57.29577951°)。以度数表示的角度,把数字乘以π/180便转换成弧度;以弧度表示的角度,乘以180/π便转换成度数。

 
  

rad = (π / 180) * deg

同样的:

 
  

deg = (rad * 180) / π

使用JavaScript来实现角度和弧度之间的换算。一个π大约是3.141592653589793,在JavaScript中对应的是Math.PI。那么角度和弧度之间的换算:

 
  

rad = (Math.PI * deg) / 180

同样的:

 
  

deg = (rad * 180) / Math.PI

下图展示了常见的角度和弧度之间的换算:

640?wx_fmt=png

接下来回到我们的示例中来,示例有14个圆点,那么其每个圆点对应的位置可以通过下面的公式计算出来。首先计算出每个点对应的rad值。

 
  

rad = Math.PI * deg / 180

根据上面的公式,我们需要知道deg。众所周知,一个圆是360deg,我们在这个圆上平均布了14个点,那么每个圆对应的deg值是

 
  

deg = 360 / 14 * i

其中i是一个从0 ~ 13的索引值。套到对应的公式中:

 
  

rad = Math.PI * 360 / 14 * i / 180

在JavaScript中,使用一个for循环,可以打印出其值:

 
  

for (let i = 0, len = 14; i < len; i++) {    let rad = Math.PI * 360 / 14 * i / 180    console.log(`第${i+1}个圆点对应的rad值:${rad}`) }

640?wx_fmt=png

根据上面的计算得到每个圆点对应的rad值,接下来就需要利用三角函数相关的知识,来计算每个圆点圆心的(x,y)值。

640?wx_fmt=png

换成JavaScript中的数学公式:

 
  

dotX = Math.cos(rad) * rdotY = Math.sin(rad) * r

假设外圆的容器半径r = 100。继续将上面的值放到for循环中:

 
  

for (let i = 0, len = 14; i < len; i++) {    let rad = Math.PI * 360 / 14 * i / 180    console.log(`第${i+1}个圆点对应的rad值:${rad}`)    let dotX = Math.cos(rad) * 100    let dotY = Math.sin(rad) * 100    console.log(`第${i+1}个圆点对应坐标值:(${dotX},${dotY})`)         }

640?wx_fmt=png

最后通过CSS的transformtranslate()可以将14个圆点围成一个圆圈。此时,如果你的效果中不是14个圆点,而是五个圆点的时候,你只需要修改相关的参数即可。

圆点排列,通过上面的公式计算,已经搞定。现在是需要一个动画效果,让你的圆点变得更为有趣。也就是说,只有配上了动效,才是我们最终需要的Loadingu效果。接下来,来看一下,圆点的动效。

比如,我们有一个简单的动效,这个动效是通过keyframes来完成的。具体效果可以根据自己的需要来做,这里只是一个简单的效果:

@keyframes ball-spin {    0%,    100% {        
       opacity: 1;        transform: scale(1);    }
   20% {        
       opacity: 1;    }
   80% {        
       opacity: 0;        transform: scale(0);    }
}

不用我解释,大家一看就明白。假设我们的动画的持续时间是1s。效果如下:

640?wx_fmt=gif

如果你要的是这样的一个效果,那到这里,理论上是完成了。可我们要的效果是这样的:

640?wx_fmt=gif

要实现这样的一个效果,我们需要对每个圆点的animation-delay做一些处理。前面提到过,整个动画的animation-duration1s,那么每个圆点的animation-delay依此延迟1/14 s。如此一来,咱们可以计算出时间:

 
  

-1 * (1 + (i + 1) * 1 / 14)

注意:此处的-1控制的是方向,让你能感觉到loading效果是顺时针还是逆时针转。系数为-1表时逆时针,就是上图看到的效果。

整个运用到的原理就如上所述。接下来分别看看几种不同方式的实现。

纯CSS实现方式

Loading效果实现方式有很多种,比如@css_live整理这些Loading动效的Demo,就涵盖了多种实现方式,有纯CSS的、有Canvas的,也有SVG的。而且在CodePen上也有很多Loading的Demo或这里的Demo演示。

这里我们先用纯CSS的方式来完成。我们需要一个完成动效的HTML结构:

 
  

<div class="loading">    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>    <div><span></span></div>
</div>

div.loading下的div + span对应的个数就是你所需要的圆点数。这里使用了14个。为什么要使用div+span样的嵌套标签,简单的解释一下。因为圆点有两个效果:

  • 位置排列,使用的是transfrom中的translate

  • 动效使用的是transform中的scale()

如果我们只用一个div,那么圆点的排列和动效中同时使用到的transform较为难控制。所以为了让实现方式更简单,牺牲了结构,嵌套了一个span标签。也就是排列用在div上,动效用在span上。

众所周知,纯CSS是不具备动态计算的能力,如果我们要实现上面的效果,就要人肉的去计算。不过值得庆幸的是,有很多优秀的CSS处理器,比如说Sass、LESS之类的都具备动态计算,也具备上面所说的数学函数和for循环的特性。这样一来,事情就变得简单多了。

详细代码就不全贴了,整几个关键的代码片段。首先根Demo效果所需要的参数,声明几个变量:

$loadingSize: 200px;    // Loading容器的大小
$dotRadius: 24px;       // 圆点半径  $dotNums: 14;           // 圆点个数(需要和div.loading中子元素div个数对应起来)
$dotColor: #f36;        // 圆点颜色

另外for循环以及圆点位置相关的代码如下:

 
  

.loading {    
   width: $loadingSize;    
   height: $loadingSize;    
   color: $dotColor;    transform-origin: center;    div {        
       color: $dotColor;        
       width: $dotRadius;        
       height: $dotRadius;        margin-top: $dotRadius / 2;        margin-left: $dotRadius / 2;        span {            
           width: $dotRadius;            
           height: $dotRadius;            
           animation: ball-spin 1s infinite ease-in-out;              background-color: currentColor;            
           border: 0 solid currentColor;        }        
       @for $i from 1 through 14 {            &:nth-child(#{$i}) {                transform: translate(cos(($i - 1) * 360deg / $dotNums) * $loadingSize / 2, sin(($i - 1) * 360deg / $dotNums) * $loadingSize / 2);                & > span {                    animation-delay: -(1 + $i * 1 / $dotNums) * 1s                }            }        }    } }

对应一些三角函数的代码这里就不列出来了。如果你感兴趣的话可以使用sass-math。你也可以将示例中相应的函数像SassMagic一样放到一个Sass的仓库中,方便之后使用。

虽然效果出来了,但其可扩展性相对而言较为麻烦一点,比如说我要一个小一点的,个数少一点的,颜色不一样的。那么需要重新修改前面声明的变量,然后再编译出相应的代码,修改对应的HTML结构。感兴趣的同学可以试试。

CSS自定义属性

CSS自定义属性已经是很成熟的CSS属性了。在小站上也有多篇文章介绍了有关于CSS自定义属性。为什么会想到CSS自定义属性呢?因为能使用CSS处理器变量的,就可以使用CSS自定义属性。但在这里有一个蛋疼的地方,就是CSS自定义属性调用通过var()函数来完成,然后计算要依赖于calc()函数。那么要把其结合Sass这样的处理器来做,就基本上是无解(至少我现在没找到可解的方案)。但CSS自定义属性有一个特性setProperty(),可以使用它来完成CSS自定义属性的动态变化。

因此,使用CSS自定义属性方案来实现Loading的动画效果的时候,我借用了原生JavaScript的手段来完成圆点的排列和动画的计算。为了能更好的展示不同的Loading效果,接下来的Demo提供了一些可配置参数。这些可配置参数,让你来动态修改CSS自定义属性的值。先上效果吧:

640?wx_fmt=png

你可以像下面这样,修改Demo右侧的一些参数,实现不一样的Loading效果,如下图所示:

640?wx_fmt=gif640?wx_fmt=gif

为了节省篇幅,也将只贴一些关键的代码:

 
  

:root {    --loadingRadius: 168px;    --dotRadius: 24px;    --dotColor: #d8d8d8;} .loading{    width: var(--loadingRadius);    height: var(--loadingRadius);    color: var(--dotColor);    transform-origin: center;    div {        width: var(--dotRadius);        height: var(--dotRadius);        color: var(--dotColor);        margin-top: calc((var(--dotRadius) / 2) * -1);        margin-left: calc((var(--dotRadius) / 2) * -1);    }    span {        animation: ball-spin 1s infinite ease-in-out;          width: var(--dotRadius);        height: var(--dotRadius);    } }

对应的JavaScript代码如下:

const style = document.documentElement.style;
var rangs = {    dotNums: document.getElementById('dotNums'),    loadingRadiusVal: document.getElementById('loadingRadius'),    dotRadiusVal: document.getElementById('dotRadius'),    dotColorVal: document.getElementById('dotColor') }
// 创建修改CSS自定义属性的函数
function valueChange(id, value) {    style.setProperty('--' + id, value); }
// 动态创建div.loading下的子元素div+span
function insertHtml() {    let loadingWrap = document.getElementById('loading');    
   var dots = rangs.dotNums.value;    
   for(let i = 0; i < dots; i++) {        
       let divEle = document.createElement('div');        
       let spanEle = document.createElement('span');        divEle.appendChild(spanEle);        loadingWrap.appendChild(divEle)    }   }
// 右侧参数面板的控制
rangs.loadingRadiusVal.addEventListener('input', function(e){    valueChange(e.currentTarget.id, e.currentTarget.value + 'px'); }) rangs.dotRadiusVal.addEventListener('input', function(e){    valueChange(e.currentTarget.id, e.currentTarget.value + 'px'); }) rangs.dotColorVal.addEventListener('input', function(e){    valueChange(e.currentTarget.id, e.currentTarget.value); })
function transformDot() {    let dotNums = document.querySelectorAll('.loading > div');    
let loadingRadiusVal = rangs.loadingRadiusVal.value;    
let dotRadiusVal = rangs.dotRadiusVal.value;    
let dotColorVal = rangs.dotColorVal.value;    // 计算圆点的位置和动效    for (let i = 0, len = dotNums.length; i < len; i++) {        
       let rad = 2 * Math.PI / dotNums.length  * i;        
       let dotX =  Math.cos(rad) * loadingRadiusVal / 2;        
       let dotY =  Math.sin(rad) * loadingRadiusVal / 2;        dotNums[i].style.transform = `translate(${dotX}px,${dotY}px)`;        dotNums[i].firstElementChild.style.animationDelay = -1 * (1 + (i + 1) * 1 / dotNums.length) + 's';    } } insertHtml(); transformDot();
// 修改参数后修改动效
rangs.dotNums.addEventListener('input', function(e){    
   let loadingWrap = document.getElementById('loading');    loadingWrap.innerHTML = '';    insertHtml();    transformDot(); })

对应的HTML模板,这里就不展示了。

其实我还在想,这个效果,我们应该也可以使用CSS Houdini来完成。如果你对CSS Houdini有一定的了解,不仿尝试写写。如果你对CSS Houdini一点都不了解,建议点击这里先了解一下,我想你会从此喜欢上她的。

Vue写Loading组件

除了上面的方法之外,为了更好的使用,还可以将其写成一个组件。比如一个Vue组件,当然喜欢使用其他JavaScript框架的同学也可以使用别的,比如React。这里用的是Vue。

首先创建一个Vue组件,你可以使用单的一个文件,也可以使用组件模板。因为Demo是在Codepen上展示的,我就使用了一个组件模板:

 
  

<template id="loading">    <div class="loading">        <div v-for="(dotNum, index) in dotNums" :key="index" :style="dotTransform(index, dotNums)">        <span :style="dotAimation(index, dotNums)"></span>        </div>    </div></template>Vue.component('loading',{    template: '#loading',    props: {        loadingRadiusVal: {            type: Number,            required: true,            default: 168        },        dotRadiusVal: {            type: Number,            required: true,            default: 24        },        dotColorVal: {            type: String,            required: true,            default: '#d8d8d8'        },        dotNums: {            type: Number,            required: true,            default: 10        }    },    methods: {        dotTransform: function(index, dotNums) {            let rad = 2 * Math.PI / dotNums  * index;            let dotX =  Math.cos(rad) * this.loadingRadiusVal / 2;            let dotY =  Math.sin(rad) * this.loadingRadiusVal / 2;            return {                transform: `translate(${dotX}px,${dotY}px)`            };        },        dotAimation: function(index, dotNums) {            let delayTime = `${-1 * (1 + (index + 1) * 1 / dotNums) }s`            return {                animationDelay: delayTime            }        }    }   })

有了这个组件的时候,咱们可以这样调用:

 
  

<loading :dot-color-val="dotColor" :dot-nums="dotNums" :loading-radius-val="loadingRadius" :dot-radius-val="dotRadius" :style="changeStyle"></loading>let app = new Vue({    el: '#app',    data () {        
       return {        
           loadingRadius: 168,        
           dotRadius: 20,        
           dotColor: '#ff3366',        
           dotNums: 12        }    },    
   computed: {        
       changeStyle: function() {            let rootEle = document.documentElement;            rootEle.style.setProperty('--loadingRadius', `${this.loadingRadius}px`)            rootEle.style.setProperty('--dotRadius', `${this.dotRadius}px`)            rootEle.style.setProperty('--dotColor', this.dotColor)        }    } })

和前面的示例一样,也提供了一个参数控制面板,不过在Vue中参数控制面板控制CSS自定义属性就容易的多了,采用v-model的双向绑定即可。最终效果如下:

640?wx_fmt=png

因为是Vue的初学者,如果写得不好,还请各咱大神多多指正。如果你也是Vue的爱好者或初学者,可以和我一起来学习Vue相关的知识。当然,Vue写的各式各样的Loading组件非常的多,比如这里就收集了很多。

总结

这篇文章主要介绍了如何使用不同的方式来实现一个Loading的动效。说实在的,前端就是这样,实现一个效果有很多种方式方法。不同层次的同学可以使用不同的方式。就好比这篇文章中介绍的。不懂JavaScript的可以使用纯CSS(最好你对CSS处理器有所了解),懂JavaScript的话,你可以使用JS,配合一些优秀的CSS特性。当然,为了更易于维护和扩展,也可以借助Vue这样的框架,将整个效果封装成一个组件。

那么整个效果的实现方式不同,但其原理是一样的。对于初学者而言,最关键的是要掌握原理。只有掌握了原理部分,你才能根据自己的环境选择实现方案。

如果上面有不对之处,或者你有更好的方案,欢迎在下面的评论中与我一起共享。如果这篇文章对你有所帮助,可以赏杯咖啡,鼓励我继续创作。(^_^)!!!


文章涉及到图片和代码,如果展示不全给您带来不好的阅读体验,欢迎点击文章底部的 阅读全文。如果您觉得小站的内容对您的工作或学习有所帮助,欢迎关注此公众号。





W3cplus.com

————————————

记述前端那些事,引领web前沿


长按二维码,关注W3cplus


640?wx_fmt=png


猜你喜欢

转载自blog.csdn.net/tja8N2m2G46OMtF/article/details/80754953