大家好!我是猫小白,本期给大家带来一个简略的实战demo,心愿大家食用得开心,有播种。

==首先申明:此demo存粹为了学习其中的动画常识,请不要用于实在的场景,否则被JC叔叔抓起包吃包住就不应该了瑟!==
此我的项目基于==成都天府衰弱通==,其它地区的场合码不晓得是否有相似的动画,如果没有也没关系,能够学习下是如何实现的。
为啥子要做这个呢?那是因为有一天我进来买菜,回来保安叔叔恩是要我扫一下场合码,作为一个5星好公民,必定是十分配合的掏出手机开始扫 。叮~,屏幕上呈现一个不挺放大放大的箭头。我就想,要不必代码来实现一下这个动画吧。

于是就有了这篇文章,我一共是实现了4种办法:

  1. 第一种,css,零js代码(最简略)
  2. 第二种,svg,零js代码 (较简略)
  3. 第三种,js+dom (较简单)
  4. 第四种,js+canvas (较简单)

全文源码地址:点击跳转到github,能够拉下来亲操练操练,顺便给一个start!

接下来,咱们一个一个来剖析如何实现的。

筹备工作

首先咱们伪装进来买个菜(未解封的同胞除外例如上海的敌人),回来的时候去保安那里故作镇定,连贯的掏出手机关上天府衰弱通(成都的哈)扫一下,而后给保安亮一下,不退出界面,淡定回家。

回到家后,拿出手机,关上刚刚的界面,截一个图:

退出html中,作为div容器的背景图片,而后咱们创立一个红色的框把两头的圆圈和勾遮住,因为咱们要本人实现其中的动画成果。
实现成果如下:

遮板的css代码:

/**遮板**/.shutter {  position: absolute;  display: block;  width: 120px;  height: 120px;  background-color: #fff;  top: 206px;  left: 150px;}

外围html代码构造如下,:

<body>    <div class="app">      <div class="content">        <!--遮板把原来的箭头用红色背景遮住,本人实现箭头-->        <div class="shutter"></div>        <!--本人实现的箭头和圆-->        <div class="circle"></div>      </div>    </div>  </body>
只是列举要害的代码,残缺代码请到github仓库查看。
筹备工作实现,开始正片!

第一种,css,零js代码

应用css3animation动画,能够很容易实现这样的成果,不必一行js代码。单要害是要看明确咱们指标动画是由哪些局部组成的,动画是怎么变动的。所以先再次看看这个gif,留神其中动画局部:

不难发现:

  • 圆圈是由小到大,再由大到小
  • 内圈放大到肯定水平时,外圈(扩散局部)呈现,并且半径不停增大,当内圈放到最大时,扩散圈隐没
  • 外部的勾也会一起放大放大,频率和内圈雷同
    依据下面的法则,咱们先实现一个内圈(绿色圈局部)

外部圈css代码:

/**外部圆圈**/.circle {  position: absolute;  z-index: 9;  width: var(--circle-size);  height: var(--circle-size);  top: 218px;  left: 165px;  background-color: var(--theme-green);  /* background-color: red; */  border-radius: 45px;  animation: circleMove 0.7s alternate infinite cubic-bezier(1, 1, 1, 1);}
--circle-size、--theme-green是咱们在结尾定义的变量,不便后续应用。
:root {  --theme-green: #4ba776; /**主题绿色**/  --circle-size: 90px; /**圆圈大小 宽高一样**/}

外部圈画好了,还差一个红色的"√",为了简略我间接应用了"√"这个字符串,退出伪类元素的content中。你们也能够用图片或者svg。
"√"的css代码如下:

/**外部的勾,应用`.circle`的`before`伪类**/.circle::before {  z-index: 2;//z-index要比外圈(阔散圈)大,否则要被背景笼罩  position: absolute;  display: block;  content: "✔";  font-size: 63px;  color: #fff;  left: 18px;  top: -1px;}

让咱们预览下当初的成果:

哟也~看起来是那么回事了。

当初咱们依照内圈雷同的色彩和大小,画一个内部圈(扩散圈)。
参考下面的"√"的实现,咱们用after伪类来实现:

/**动画时内部突变圈*/.circle::after {  z-index: 1;//z-index要比外部的勾小,否则要笼罩掉勾  position: absolute;  display: block;  content: "";  width: calc(var(--circle-size));  height: calc(var(--circle-size));  box-sizing: border-box;  border-radius: 50%;  background-color: #4ba776;  transform: scale(1.2);  /**外圈动画 一轮工夫是内圈的2倍 外部圈放大0.7s+放大0.7s=1.4s  infinite有限反复**/  /* animation: outCircleMove 1.4s infinite cubic-bezier(1, 1, 1, 1); */}

内圈退出放大放大动画,在.circle中计入animation

/**外部圆圈**/.circle {  position: absolute;  z-index: 9;  width: var(--circle-size);  height: var(--circle-size);  top: 218px;  left: 165px;  background-color: var(--theme-green);  /* background-color: red; */  border-radius: 45px;  /*退出放大放大动画  alternate:轮流播放,放大再放小  infinite:有限播放  ease-in-out:变动函数*/  animation: circleMove 0.7s alternate infinite ease-in-out;}/**定义放大放大动画,最大放大到1.2倍**/@keyframes circleMove {  0% {    transform: scale(1);  }  100% {    transform: scale(1.2);  }}
解读:工夫设置为0.7s,咱们定义了circleMove 动画,意思是在0秒的时候圆圈放弃1倍大小,在0.1~0.7s圆圈逐步变为1.2倍大小。变动的快慢是由animation-timing-function参数决定的,文中设置的ease-in-out是进入和退出较慢,两头速度较快的一种变动曲线。
咱们能够关上开发者工具,点击animation属性的曲线图标,查看和抉择想要的动画曲线

好内圈动画曾经实现,咱们来看下成果:

perfect!当初咱们退出外圈动画,让咱们回顾一下:外圈动画是要在内圈放大到肯定工夫外圈才呈现,咱们暂定1/5,也就是20%的时候显示并且开始放大(放大对应属性:scale);放大后外圈会越来越便通明,直到内圈放到最大,此时齐全通明,感觉像是扩散的成果(通明对应属性:opacity)。
晓得这些后,咱们设置外圈动画如下:
/*动画时内部突变圈/

.circle::after {  z-index: 1;  position: absolute;  display: block;  content: "";  width: calc(var(--circle-size));  height: calc(var(--circle-size));  box-sizing: border-box;  border-radius: 50%;  background-color: #4ba776;  /**外圈动画 一轮工夫是内圈的2倍 外部圈放大0.7s+放大0.7s=1.4s  infinite有限反复**/  animation: outCircleMove 1.4s infinite ease-in-out;}/**定义扩散圈的放大放大动画**/@keyframes outCircleMove {  /**0%~50% 0s~ 0.7s前的工夫 此时内圈放大,扩散圈期待机会呈现**/  0% {    opacity: 0;  }  /**10% 此时内圈曾经放大一段时间, 扩散全显示opacity设为1,倍率为1 **/  10% {    opacity: 1;    transform: scale(1);  }  /**50%到100%是动画0.7s~1.4s的工夫 此时内圈放大,扩散圈变为1.3倍,然而透明度设为0**/  50%,  100% {    transform: scale(1.3);    opacity: 0;  }}

至此就功败垂成了,看下成果:

第二种,svg,零js代码 (较简略)

下面了利用css3的动画属性,操作起来非常简单。当初介绍一种用svg动画的形式实现雷同的成果。咱们晓得svg也是有比拟丰盛的动画个性反对。
咱们简略列举一下svg实现动画的几种办法:

1.set

set元素是SVG动画元素中最简略的元素。在通过特定工夫距离后,它只是将属性设置为特定值。因而,形态不会间断进行动画解决,而只是更改属性值一次。

2.animate

<animate>元素通常搁置到一个SVG图像元素外面,用来定义这个图像元素的某个属性的动画变动过程。

3.animateMotion

<animateMotion>元素也是搁置一个图像元素外面,它能够援用一个当时定义好的动画门路,让图像元素按门路定义的形式静止。

4.animateTransform

动画上一个指标元素变换属性,从而使动画管制平移,缩放,旋转或歪斜。
简略理解过后,咱们明天次要用animate来实现动画。

首先,咱们还是拿出下面的根底布局,祖传代码(背景+遮罩)

<div class="app">  <div class="content">    <!--遮板把原来的箭头用红色背景遮住,本人实现箭头-->    <div class="shutter"></div>    <!--本人实现的箭头动画-svg-->    <svg></svg>  </div></div></body>
根本框架的款式和上一节css动画的款式雷同,就不反复粘贴了。

而后咱们画一个圆和勾,也就是外部的局部;svg中画圆是用<circle>,于是咱们有这样的代码:

<div class="app">  <div class="content">    <!--遮板把原来的箭头用红色背景遮住,本人实现箭头-->    <div class="shutter"></div>    <!--本人实现的箭头和圆-->  <svg    class="circle"    width="130"    height="130"    xmlns="http://www.w3.org/2000/svg"  >    <g>      <circle cx="45" cy="45" r="45" fill="#4ba776"></circle>    </g>  </svg>  </div></div></body>

成果如下:

持续增加两头的红色"√"。为了简介,咱们用<text>文字标签:
增加<text>后的代码如下:

<div class="app">  <div class="conten ">    <!--遮板把原来的箭头用红色背景遮住,本人实现箭头-->    <div class="shutter"></div>    <!--本人实现的箭头和圆-->  <svg    class="circle"    width="130"    height="130"    xmlns="http://www.w3.org/2000/svg"  >    <g>      <circle cx="65" cy="65" r="45" fill="#4ba776"></circle>      <!--text增加-->      <text font-size="63" fill="#fff" y="90" x="38">✔</text>    </g>  </svg>  </div></div></body>

增加文字后成果如下:

好了,内圈增加好了,咱们再增加一个外圈(扩散圈),还是用<circle>,大小和地位都和内圈截然不同,这里就不粘贴代码了。

当初来增加动画,依据上一章节的css3动画,咱们须要不挺的放大放大内圈的大小。所以咱们通过动画改编圆的半径就ok了。下面介绍了<animate>标签能够动静扭转某一属,这里的属性就是半径R:

...其它代码<circle cx="65" cy="65" r="45" fill="#4ba776">  <!--增加到circle标签外部,指定attributeName为半径r,values是半径变动的值,咱们让它先变大再变小,dur是动画工夫 repeatCount:indefinite有限循环-->  <animate    attributeName="r"    values="45;54;45"    dur="1.4s"    repeatCount="indefinite"  /></circle>...其它代码

实现成果:

完满!看起来就是那么回事,前面的步骤其实都是依葫芦画瓢。
增加外部"√"的动画,用<animate>扭转font-size

<text font-size="63" fill="#fff" y="90" x="38">  ✔  <animate    attributeName="font-size"    values="63;69;63"    dur="1.4s"    repeatCount="indefinite"  /></text>

增加后成果咱们来看下:

最初,咱们增加扩散成果。扩散成果其实分两步,第一步是放大半径,另一步是从不通明到通明的过程。所以咱们用<animate>扭转半径的同时,扭转它的透明度。

...其它代码<!--扩散圈---><circle cx="65" cy="65" r="45" fill="#4ba776">  <!--设置放大放大动画--->  <animate    attributeName="r"    values="45;50;65;65;50;45"    dur="1.4s"    repeatCount="indefinite"  />  <!--设置透明度变动动画--->  <animate    attributeName="opacity"    values="1;1;0.2;0;0;0"    dur="1.4s"    repeatCount="indefinite"  /></circle>...其它代码

最终成果如下:

第三种,js+dom (较简单)

后面都是通过css或者svg自带个性实现的动画成果,上面这种是通过js动静扭转圆圈div大小实现的,较为简单,了解起来须要肯定的js根底,在做我的项目优先选择css3,咱们一起来看下是如何实现的。
首先,咱们还是拿出下面的根底布局,增加内外圈的div和布局。
要害css局部:

/**内部圆圈**/.circle,.outCircle {  z-index: 1;  position: absolute;  width: var(--circle-size);  height: var(--circle-size);  top: 218px;  left: 165px;  background-color: var(--theme-green);  /* background-color: red; */  border-radius: 45px;}/**外部的勾**/.circle::before {  position: absolute;  display: block;  content: "✔";  font-size: 63px;  color: #fff;  left: 18px;  top: -1px;}

html局部:

<body>  <div class="app">    <div class="content">      <!--遮板把原来的箭头用红色背景遮住,本人实现箭头-->      <div class="shutter"></div>      <!--本人实现的箭头和圆-->      <div class="circle"></div>      <div class="outCircle"></div>    </div>  </div></body>

至此,元素曾经画好了,就差动画局部了,简略剖析一波:
内圈须要逐步放大再逐步放小,咱们用一个变量size_inner来代表放大放大的倍数,STEP_INNER代表内圈递增或者递加的步长,最终设置到style上的transform属性。
外圈也须要有一个放大过程,且放大的速度比内圈快,咱们用size_outer来代表放大倍数,STEP_OUT代表外圈递增步长。
外圈须要一个透明度逐步小的值opacity_out ,透明度变动的步长用OPA_STEP代替,最终设置style上的opacity属性。
动画是继续一直的,所以咱们须要不停的执行,先临时采纳setInterval函数;
放大到肯定倍数而后再放大,最大倍数咱们用MAX_SCALE常量代表,如果递增到最大而后STEP_INNER设置为正数,意味着开始放大。

最终的源代码如下:

<script>  const circle = document.querySelector(".circle");  const outCircle = document.querySelector(".outCircle");  let size_inner = 1.0; //内圈的放大倍数  let size_outer = 1.0; //内圈的放大倍数  let STEP_INNER = 0.009; //内圈的递增步值  let STEP_OUT = STEP_INNER + 0.009; //外圈要比内圈扩散倍速大一点  let opacity_out = 1; //外圈透明度  const OPA_STEP = 0.03;  const MAX_SCALE = 1.4;  function randerInner() {    //放大过程    if (STEP_INNER >= 0) {      if (size_inner <= MAX_SCALE) {        size_inner += STEP_INNER;        //管制外圈光环--start        if (size_inner >= 1.1) {          //内圈放大到1.1倍 外圈光环才呈现,大小和内圈统一,透明度为1          if (opacity_out === 0) {            opacity_out = 1;            size_outer = size_inner;          }          size_outer += STEP_OUT;          opacity_out -= OPA_STEP;//透明度逐步        }        //管制外圈光环--end      } else {        //放大到MAX_SCALE倍了 从新放大        size_inner = MAX_SCALE;        STEP_INNER = STEP_INNER * -1; //步长乘以-1,开始方方向变动        //管制外圈光环-暗藏        opacity_out = 0;      }    } else {      //放大过程      if (size_inner > 1) {        size_inner += STEP_INNER;      } else {        //缩到1倍了 从新放大        STEP_INNER = STEP_INNER * -1; //步长乘以-1,开始方方向变动        size_inner = 1;      }    }    circle.style.transform = `scale(${size_inner})`; //设这内圈放大款式    outCircle.style.transform = `scale(${size_outer})`; //设这内圈放大款式    outCircle.style.opacity = opacity_out; //设这内圈透明度  }  setInterval(() => {    randerInner();  }, 17);</script>

因篇幅无限,不逐个解释各行代码的含意,有不了解的能够私聊我。
最终成果:

家喻户晓,因为浏览器的eventloop机制,setInterval往往并不能依照设置的工夫执行,在略微简单一些的页面中会呈现卡顿。固可采纳requestAnimationFrameapi代替。
代码如下:

<script>    const circle = document.querySelector(".circle");    const outCircle = document.querySelector(".outCircle");    let size_inner = 1.0; //内圈的放大倍数    let size_outer = 1.0; //内圈的放大倍数    let STEP_INNER = 0.009; //内圈的递增步值    let STEP_OUT = STEP_INNER + 0.009; //外圈要比内圈扩散倍速大一点    let opacity_out = 1; //外圈透明度    const OPA_STEP = 0.03;    const MAX_SCALE = 1.4;    (function randerInner() {      //放大过程      if (STEP_INNER >= 0) {        if (size_inner <= MAX_SCALE) {          size_inner += STEP_INNER;          //管制外圈光环--start          if (size_inner >= 1.1) {            //内圈放大到1.2倍 外圈光环才呈现大小统一            if (opacity_out === 0) {              opacity_out = 1;              size_outer = size_inner;            }            size_outer += STEP_OUT;            opacity_out -= OPA_STEP;          }          //管制外圈光环--end        } else {          //放大到MAX_SCALE倍了 从新放大          size_inner = MAX_SCALE;          STEP_INNER = STEP_INNER * -1;          //管制外圈光环-暗藏          opacity_out = 0;        }      } else {        //放大过程        if (size_inner > 1) {          size_inner += STEP_INNER;        } else {          //缩到1倍了 从新放大          STEP_INNER = STEP_INNER * -1;          size_inner = 1;        }      }      circle.style.transform = `scale(${size_inner})`; //设这内圈放大款式      outCircle.style.transform = `scale(${size_outer})`; //设这内圈放大款式      outCircle.style.opacity = opacity_out; //设这内圈透明度      requestAnimationFrame(() => {        randerInner();        _test();      });    })()</script>

第四种,js+canvas (较简单)

通过下面章节的洗礼,canvas版本和js+dom版本十分类似,惟一的不同就是绘画圆或者文字的api不同。
咱们剖析下要点:

  1. 须要在画布上用arcapi绘制2个圆,一个内圈一个外圈,并且不停扭转圆圈的大小或透明度。
  2. 须要用fillText绘制一个字样,并且扭转字体大小。
  3. 须要不停执行绘制函数,这里应用晦涩的requestAnimationFrameapi。
  4. 每次绘制前,需清空画布clearRect
    其实原理和第三种差不多,都须要用变量记录外部圈变动的大小,到最大值后方向递加。内部圈是须要等到内圈放大到肯定倍数(1.1)倍,才显示进去,并且放大速度要比内圈快,随着外圈变大,透明度逐步减小到0。

我想第四种和第三种其实都有这样的逻辑,为此我花了一点事件把这个动作进行了封装,采纳了一种订阅公布模式来实现。
为了大家浏览我这篇文章的同时,除了能学习到动画常识,也能学习到一些其它的比拟重要的js常识。大家应该理解或者听过js的各种模式,什么工厂模式、单列模式、观察者模式、公布订阅模式等等,都是学习到了理论知识。明天我就用这个例子介绍下公布订阅模式是如何在实践中应用的。

刚咱们剖析了canvas实现的形式和js+dom的形式,这两种形式都须要用变量来记录一个值(这里是放大倍数),而且每次执行渲染函数时都要递增一个常量值(成为步长),达到某个最大值(比方1.3倍大小)后开始反向递加(步长乘以-1),而后递加到最后值(1倍大小)。同时还有一个细节,当内圈递增到肯定倍数(比方1.1倍)要让外圈开始退出动画,当内圈递增到最大时,外圈要隐没(透明度设置为0)

基于这个剖析,咱们得出封装的类须要设计成这样:

/*** * 放大放大数字变动类 * 依据放大放大初始值和步长等递增 * 提供到达某一数值时的回调 * @params {Number} from 数字初始值 * @params {Number} to 数字最大值 * @params {Number} step 数字步长 * @params {Array} dingNums 达到dingNums的区间触发回调 只再放大轮回触发 */export default class MoveCrycle {  constructor({ from = 0, to = 100, step = 1, dingNums = [] }) {    this.from = from;    this.to = to;    this.step = step;    this.dingNums = dingNums;    this.currValue = this.from;    this._turnNum = 0; //放大放大轮次 一次放大一次放大为一轮    this._event = {      onchange: [], //每一次变动触发的回调      onlarge: [], //每次到最大值触发的回调      onsmall: [], //每次到最小值触发的回调      onding: [], //每次到dingNums区间值时触发的回调    };    this._init();  }  _init() {    //注册放大事件    //记录轮次 每次放大+1    this.on("onlarge", function turn() {      this._turnNum++;    });  }  //开始滚动数字  jump() {    if (this.step > 0) {      //放大过程      if (this.currValue <= this.to) {        //递增过程中        this.currValue += this.step;        //判断是否在dingNums区间        if (          this.currValue >= this.dingNums[0] &&          this.currValue <= this.dingNums[1]        ) {          this._emit("onding", this.currValue, this._turnNum);        }      } else {        this._emit("onsmall", this.currValue);        this.currValue = this.to;        this.step *= -1;      }    } else {      //放大过程      if (this.currValue > this.from) {        this.currValue += this.step;      } else {        //缩到初始值了 从新放大        this.step *= -1;        this.currValue = this.from;        this._emit("onlarge", this.currValue);      }    }    this._emit("onchange", this.currValue, this.step > 0, this._turnNum);  }  //触发事件函数  _emit(type, ...args) {    //触发每次变动函数    this._event[type].forEach((f) => {      try {        f.apply({}, args);      } catch (e) {        console.error(e);      }    });  }  //注册回调  on(type, func) {    if (      this._event[type] &&      !this._event[type].filter((f) => f === func).length    ) {      this._event[type].push(func);    }  }  //登记回调  off(type, func) {    if (this._event[type]) {      this._event[type] = this._event[type].filter((f) => f !== func);    }  }}

一看比拟长,别急缓缓剖析:

  1. constructor局部就是接支出参,没有什么好说的。须要留神的是this._init(),初始化时本人也注册一个onlarge事件,来记录每次轮回的次数_turnNum
  2. jump是外围代码,次要治理每一次被执行时的递增或递加状态,同时通过this.emit()函数触发响应的回调事件。放大过程中判断是否达到dingNums的区间,如果在其中则不停触发onding事件,当达到最大值时触发onsmall事件代表行将变小,同时this.step *= -1步长取反方向值。达到最小值时触发onlarge事件,代表行将增大,同时this.step *= -1步长取反方向值。jump函数并没有这个类中进行递归调用,是因为把这个调用权交给使用者会更加灵便,前面会介绍如何调用。
  3. _emit函数是触发事件的控制中心,依据传入的type类型找到事件对象中对应的函数数组,遍历执行。用try{} catch(e){}是为了防止在某个回调函数中出错而影响了其它回调函数失常执行。
  4. onoff顾名思义,就是注册和登记回调的入口。on函数把传入的事件类型和函数退出对应的数组中,如果反复增加同一个函数是有效的。off函数接管事件类型和函数,而后数组中过滤掉。这里留给你想像一下,如果用匿名函数注册的事件能勾销掉吗?

好了,MoveCrycle类封装好了,回到咱们的主题,看下如何在这个箭头动画中应用。

HTML局部

<body>  <div class="app">    <div class="content">      <!--canvas替换箭头局部-->      <canvas        id="canvas"        class="canvas"        width="140px"        height="140px"      ></canvas>    </div>  </div></body>

script局部

引入MoveCrycle类,初始化

import MoveCrycle from "./js/MoveCrycle.js";let cavas = document.getElementById("canvas");var ctx = cavas.getContext("2d");//定义的一些常量和变量上面会用到const R_SMALL = 45;//内圈初始大小...省略局部代码//看着里!!!!初始化封装的类let move = new MoveCrycle({  from: R_SMALL, //初始值  to: R_SMALL * 1.35, //最大1.35倍  step: 0.3, //步长  dingNums: [R_SMALL * 1.1, R_SMALL * 1.35], //外圈开始呈现的区间内圈的1.1倍到1.35倍});...

注册onchange事件,监听每一次变动,而后渲染画布

//注册数值变动事件,每次变动获取值渲染move.on("onchange", (value, step) => {  //绘制内圆  clear(); //清空画布  //绘制内圈  drawInnerCircle(value);  //绘制外圈  drawOutCircle();  //文字放在最初,不然会被下面的圆属性fillStyle笼罩  drawText(value);});

注册onding事件,管制外圈的变大,透明度减小

//再设定的区间触发事件,管制外圈的变大,透明度减小move.on("onding", (value) => {  r_out += R_OUT_STEP; //外圈变大  opa -= OPA_STEP; //外圈变大的同时透明度减小});

注册onlarge事件,管制外圈

//动画圈开始变大管制外圈move.on("onlarge", (value) => {  opa = 1; //开始变大,透明度为1  r_out = 45; //内部开始变大时半径回复到初始值});

注册onsmall事件,管制外圈透明度为0

move.on("onsmall", (value) => {  //管制外圈光环-暗藏  opa = 0;});

绘制内圈、外圈、文字函数

//绘制内圆function drawInnerCircle(val) {  ctx.beginPath();  ctx.arc(SIZE / 2, SIZE / 2, val, 0, 2 * Math.PI);  ctx.fillStyle = `rgba(${COLOR}, 1)`;  ctx.fill();}//绘制外圈圆 变大的同时透明度降落function drawOutCircle() {  ctx.beginPath();  ctx.arc(SIZE / 2, SIZE / 2, r_out, 0, 2 * Math.PI);  ctx.fillStyle = `rgba(${COLOR}, ${opa})`;  ctx.fill();}//放大字体 大小随内圈一起变动function drawText(val) {  //font放大倍数和内圈一样  ctx.font = (val / R_SMALL) * 60 + "px Arial";  ctx.textBaseline = "center"; //设置字体底线居中  ctx.textAlign = "center"; //设置字体对齐形式居中  ctx.fillStyle = `rgba(255,255,255,1)`;  ctx.fillText("✔", 70, 90);}

革除画布

//革除画布function clear() {  ctx.clearRect(0, 0, SIZE, SIZE);}

循环递归函数

//循环递归函数(function start() {  move.jump(); //开始执行  requestAnimationFrame(() => {    start();  });})

script局部残缺代码:

<script type="module">  import MoveCrycle from "./js/MoveCrycle.js";  let cavas = document.getElementById("canvas");  var ctx = cavas.getContext("2d");  const COLOR = "75, 167, 118";  const R_SMALL = 45;//内圈初始大小  const SIZE = 140; //画布宽高  const R_OUT_STEP = 0.3 * 2.5; //外圈变动系数  const OPA_STEP = 0.028;  let r_out = R_SMALL; //外圈半径累加值  let opa = 1; //外圈透明度累加值  let move = new MoveCrycle({    from: R_SMALL, //初始值    to: R_SMALL * 1.35, //最大1.35倍    step: 0.3, //步长    dingNums: [R_SMALL * 1.1, R_SMALL * 1.35], //外圈开始呈现的区间内圈的1.1倍到1.35倍  });  //注册数值变动事件,每次变动获取值渲染  move.on("onchange", (value, step) => {    //绘制内圆    clear(); //清空画布    //绘制内圈    drawInnerCircle(value);    //绘制外圈    drawOutCircle();    //文字放在最初,不然会被下面的圆属性fillStyle笼罩    drawText(value);  });  //再设定的区间触发事件  move.on("onding", (value) => {    r_out += R_OUT_STEP; //外圈变大    opa -= OPA_STEP; //外圈变大的同时透明度减小  });  //动画圈开始变大  move.on("onlarge", (value) => {    opa = 1; //开始变大,透明度为1    r_out = 45; //开始变大时半径回复到初始值  });  //动画圈开始变小  move.on("onsmall", (value) => {    //管制外圈光环-暗藏    opa = 0;  });  //绘制内圆  function drawInnerCircle(val) {    ctx.beginPath();    ctx.arc(SIZE / 2, SIZE / 2, val, 0, 2 * Math.PI);    ctx.fillStyle = `rgba(${COLOR}, 1)`;    ctx.fill();  }  //绘制外圈圆 变大的同时透明度降落  function drawOutCircle() {    ctx.beginPath();    ctx.arc(SIZE / 2, SIZE / 2, r_out, 0, 2 * Math.PI);    ctx.fillStyle = `rgba(${COLOR}, ${opa})`;    ctx.fill();  }  //放大字体 大小随内圈一起变动  function drawText(val) {    //font放大倍数和内圈一样    ctx.font = (val / R_SMALL) * 60 + "px Arial";    ctx.textBaseline = "center"; //设置字体底线居中    ctx.textAlign = "center"; //设置字体对齐形式居中    ctx.fillStyle = `rgba(255,255,255,1)`;    ctx.fillText("✔", 70, 90);  }  //循环递归函数  (function start() {    move.jump(); //开始执行    requestAnimationFrame(() => {      start();    });  })  //革除画布  function clear() {    ctx.clearRect(0, 0, SIZE, SIZE);  }  //开始函数  start();</script>

以上。
残缺代码能够到我的github仓库获取!
各位大佬,不要忘了给我点赞+评论+珍藏