zoukankan      html  css  js  c++  java
  • canvas实现有递增动画的环形进度条

    哈?标题不知道啥意思?

    老规矩,直接看图!

    效果如下:

    高清大图!

    码农多年,老眼昏花,动图看不清?!那就看静态截图!!!

    不同分值效果如下:

            

    看完了卖家秀,我们来看产品的制作过程吧!

    canvas绘制圆环

    1、vue中,<template lang="pug">里的代码如下:

    canvas#baseCanvas是底部的灰色圆环

    canvas#myCanvas是上边的彩色圆环

    需要用css样式帮助我们把彩色圆环盖到灰色圆环上边。

    2、css样式:

    3、js-canvas的样式绘制代码

    这段代码也很简单,看canvas的api即可

    3-1、vue组件中,script标签顶部定义需要用的变量

    3-2、vue的methos对象中,定义方法三个:

    drawBaseCanvas:用来绘制底部灰色圆环。由于灰色圆环没有动画效果,所以一开始就绘制一个完整的灰色圆环即可。
    drawClrCanvas:用来绘制上边的彩色圆环。
    clearCanvas:用来清空画布。这是彩色圆环动画需要。
    因为我们圆环动画效果的核心就是,每隔一段时间就把彩色圆环清空一下,然后把结束角度值增大、重画,这样连续起来就是动画。

    以下是三个方法的代码:

    上边三个方法里边的代码,几乎都是对canvas API的应用,看教程即可。

    只有draoClrCanvas方法中,canvas圆形的绘制时,arc的参数里关于开始值、结束值的设置。

    开始值决定了圆环的起始绘制位置,结束值决定了结束的位置(我好像说了一句废话,但是冥思苦想后的思想描述文字,不想删掉哈哈哈)

    这个结束值的计算,对于我来说还是比较麻烦的。

    count变量为什么要这么计算,我也忘了我是怎么鼓捣出来的了。

    this.grade是100以内的正整数,表示分值。被定义在data中,默认是0分。

    所以一开始彩色圆环就看不见,因为起始点和结束点都是0点。

    如果更改grade的值,从0-100,canvas彩色圆环的值也就会更改。

    这样,只要我们逐渐修改grade的值,重新绘制,彩色圆环就会逐渐递增,实现动画效果。 

     

    圆环动画效果

    由于我这里需求特殊,需要用户每次翻到canvas所在swiper时,才会触发动画(后来更麻烦一点需要柱状图和canvas部分有个入场效果后,动画才开始。效果就是上图中最长的那张gif动画那样)。

    所以我得借助swiper才能实现。在swiper切换的回调函数中,从0开始不停递增grade分数,并重新触发彩色圆环的绘制,进而实现动画效果。

    vue中我用的swiper是'vue-awesome-swiper'。她的用法我在其他文章中写过步骤【点击进入传送门】。

    swiper在vue-data中的配置里,有一个on对象。在on对象中的slideChange函数,就是每次翻页swiper时会触发的回调函数。

    这里我说一下几个比较特殊的点:
    (1)vm:是我早就在vue的script中存储的变量,初始化为null,然后在mounted中,将其赋值为vue实例对象。

    初始化数据、绘制灰色圆环

    通过这种方法,我在vue实例对象 - data - swiper - 回调函数中去拿vue实例对象 - data中的grade和gradeTarget属性值,并对其进行修改。

    ps:我也不知道这么做是不是很傻的一种做法,当时做到这里时是我遇到的一个难题,不知道怎么在swiper的on回调中获取vue实例。于是就有了这么曲线救国的方法。如果看官有更好的解决方案,希望可以给我提供一个新的思路,感激不尽哦亲

    (2) (this.activeIndex == 2 && vm.isStar) || (this.activeIndex == 1 && !vm.isStar)

    这里是因为业务,才这么判断,可以忽略。

    this在swiperChange函数中指向swiper对象。this.activeIndex是swiper实例的属性,用官方的话说“返回当前活动块(激活块)的索引。”可以理解他指的是当前翻到的是哪一页,就是当前你所看的swiper-slide的下标。

    我因为用户的身份,会判断性的决定当前canvas所在swiper前一页是否展示。 如果不展示就根本不会绘制前一页,那么相应的当前页的swiper的下标就会变成(index-1)。

    总而言之,当满足条件、用户翻到canvas所在swiper页面后,我就要触发if里边的圆环绘制逻辑。否则就走到else里初始化数据页面的状态、清除定时器暂停动画、并把彩色圆环清空

    (3)vm.aniShow

    在我上篇《纯css绘制柱状图》里边说了,柱状图的动画要跟canvas的动画一起说。因为他们的动画实现需要配合swiper的切换。说的就是这里的代码:

    vue - data - aniShow属性变为true时,div.row就会添加ani这个class类名:

    同样,aniShow为true,progress的高度就会附上自己的目标值,也就是这个progress的实际高度经过百分制转化后被赋予给了style属性的height。(具体换算规则还是见上篇《纯css绘制柱状图》)

    此时,因为progress的transition监听了height变化,就开始有了高度渐增的柱状图递增动画了。

    而ani类名下,progress的transition-delay实现了其高度错开递增效果。

    可能只看文字描述很晦涩,再看一眼效果:

    (4)彩色圆环绘制代码部分

    gradeTarget是实际分值,是最终要绘制到的结果。

    grade从0开始,自增到gradeTarget的大小。

    这里我没有直接++vm.grade,我也不知道自己当时咋想的。

    if判断,如果grade递增到了目标值gradeTarget或者大于目标值,就停止递增,并让grade=gradeTarget。属于临界值的判断。在运动功能中,又算碰撞检测。

    反之,不到目标的话,就清除上一次绘制的canvas画布,在grade递增变化后重新绘制新的彩色圆环。

    (5)所有这些放到setTimeout中,暂停500毫秒再执行,是为了等柱图和环图入场后,在开始绘制圆环的递增效果。

    其实上边代码都是很简单的逻辑处理,看官们读一遍代码应该就差不离了。

    新想法:

    这个效果是我很久以前做的,今天在整理制作方法的时候,我想到自己代码的一种优化方案:

    其实没必要在定时器里重新调用彩色圆环绘制方法。我们直接改的是this.grade属性,监听这个属性的改变就好了其实。这样此属性在定时器中被修改,圆环方法就会自动执行。

    这还是一个想法,还需要我的实践。

     

    中间文字的递增效果:

    因为grade是每次递增的分数,所以利用vue的双向数据绑定,直接把grade当作分数值绑定到对应dom视图处即可。

    最后,圆环和上边柱状图的动画结合,就是animation控制一下动画延迟即可。很简单的。

    index.vue源码:

    (注,源码稍作整理,单独提取。为了完整性也为了保护其他业务代码,部分变量名做了修改,可能会和之前截图中略微不同)

      1 <template lang='pug'>
      2   .indexs#Indexs.app-bg
      3     transition(name="fade")
      4       swiper#swiperBox(:options="swiperOption" ref="mySwiper")
      5         swiper-slide.swiper-slide1
      6           .container
      7           .up
      8         swiper-slide.swiper-slide2(v-if="isShow")
      9           .my-shark
     10           .up
     11         swiper-slide.swiper-slide3
     12           .container
     13             .data-cont
     14               .data.data01
     15                 .data01-charts
     16                   .row(v-for='item,index in Data' :key="index" :class='aniShow ? "ani":""')
     17                     .data-txt {{item.grade > 0 ? item.grade : '无数据'}}
     18                     .progress(:class='item.grade == 0 ? "nodata" : ""' :style="'height: ' + (aniShow ? (item.grade >= 100 ? (100 * 1.5) / 100 : item.grade == 0 ? 0.04 : item.grade * 1.5 / 100) : 0) +'rem'")
     19                       span.pg-data
     20                     .week {{item.week}}
     21               .data.data02
     22                 .data02-charts
     23                   .canvas-box
     24                     //- baseCanvas
     25                     canvas#baseCanvas.my-canvas(ref="baseCanvas" width="174" height="174")
     26                     //- canvas
     27                     canvas#myCanvas.my-canvas.clr-canvas(ref="myCanvas" width="174" height="174")
     28                     .canvas-data #[span.num {{grade}}]分
     29                   
     30 </template>
     31 <script>
     32 var vm = null,
     33   timer1 = null,
     34   /* canvas基础值 */
     35   c = null, //document.getElementById("myCanvas");
     36   ctx = null, //canvas-2d画布
     37   x = 161 / 2 + 1, //圆心坐标
     38   r = (161 - 10) / 2; //半径大小
     39 
     40 /* swiper组件 */
     41 import { swiper, swiperSlide } from "vue-awesome-swiper";
     42 import { getData } from "../io/getData";
     43 
     44 export default {
     45   name: "Indexs",
     46   components: {
     47     swiper,
     48     swiperSlide
     49   },
     50   data() {
     51     return {
     52       grade: 0, //圆环图分数
     53       gradeTarget: 78.54, //实际得分数,可ajax请求数据后修改
     54       isShow: true,//是否展示第二页swiper
     55       aniShow: false,//是否开启柱图动画
     56       Data:[{
     57           week: "第一周",
     58           grade: 0
     59         },
     60         {
     61           week: "第二周",
     62           grade: 30
     63         },
     64         {
     65           week: "第三周",
     66           grade: 99.99
     67         },
     68         {
     69           week: "第四周",
     70           grade: 76.98
     71         },
     72         {
     73           week: "第五周",
     74           grade: 100
     75         }],
     76       
     77       swiperOption: {
     78         //swiper参数
     79         notNextTick: true,
     80         direction: "vertical",
     81         grabCursor: true,
     82         setWrapperSize: true,
     83         autoHeight: true,
     84         slidesPerView: 1,
     85         mousewheel: false,
     86         mousewheelControl: false,
     87         height: window.innerHeight, // 高度设置,占满设备高度
     88         resistanceRatio: 0,
     89         observeParents: true,
     90         initialSlide: 2 - 1, //设置初始化时,swiper的默认展示页面,从零开始
     91         on: {
     92           slideChange() {
     93             if (
     94               (this.activeIndex == 2 && vm.isShow) ||
     95               (this.activeIndex == 1 && !vm.isShow)
     96             ) {
     97               console.log(this.activeIndex, vm.isShow, "绘制动画");
     98               setTimeout(function() {
     99                 // 配合展示柱状图动画
    100                 vm.aniShow = true;
    101                 // 定时器不断触发绘制彩色圆环,实现圆环动画效果
    102                 timer1 = setInterval(function() {
    103                   // 中间分数文案更改
    104                   var num = vm.grade;
    105                   num++;
    106                   if (num >= vm.gradeTarget) {
    107                     vm.grade = vm.gradeTarget;
    108                     clearInterval(timer1);
    109                   } else {
    110                     vm.grade = num;
    111                   }
    112                   vm.clearCanvas();
    113                   vm.drawClrCanvas();
    114                 }, 1000 / 60);
    115               }, 500);
    116             } else {
    117               // 翻页后,初始化数据页面的状态、清除定时器暂停动画、并把彩色圆环清空
    118               console.log("其他页");
    119               clearInterval(timer1);
    120               vm.grade = 0;
    121               vm.aniShow = false;
    122               vm.clearCanvas();
    123             }
    124           }
    125         }
    126       }
    127     };
    128   },
    129   computed: {},
    130   mounted() {
    131     // 初始化数据、绘制灰色圆环
    132     vm = this;
    133     c = this.$refs.myCanvas;
    134     ctx = c.getContext("2d");
    135     this.drawBaseCanvas();
    136   },
    137   methods: {
    138     drawBaseCanvas() {
    139       // canvas绘制
    140       /* 基础值 */
    141       var c = this.$refs.baseCanvas, //document.getElementById("myCanvas");
    142         // debugger;
    143         ctx = c.getContext("2d"),
    144         o = x,
    145         randius = r;
    146       /* 默认灰色圆圈 */
    147       ctx.strokeStyle = "#eee";
    148       ctx.lineWidth = 10;
    149       ctx.beginPath();
    150       ctx.arc(o, o, randius, 0, 2 * Math.PI);
    151       ctx.stroke();
    152     },
    153     clearCanvas() {
    154       // 清除画布
    155       ctx.clearRect(0, 0, 200, 200);
    156     },
    157     drawClrCanvas() {
    158       var gradient = ctx.createLinearGradient(75, 50, 5, 90);
    159       gradient.addColorStop("0", "#C88EFF");
    160       gradient.addColorStop("1.0", "#7E5CFF");
    161       ctx.strokeStyle = gradient; // 用渐变进行填充
    162       ctx.lineWidth = 10;
    163       ctx.lineCap = "round";
    164       ctx.shadowColor = "rgba(191,142,255, 0.36)";
    165       ctx.shadowBlur = 8;
    166       ctx.shadowOffsetY = 8;
    167       ctx.beginPath();
    168       var count = this.grade / (100 / 2) + 1;
    169       ctx.arc(x, x, r, Math.PI, Math.PI * count, false);
    170       ctx.stroke();
    171     }
    172   }
    173 };
    174 </script>
    175 <style lang='scss'>
    176 // 柱图
    177 .row {
    178   position: relative;
    179   z-index: 1;
    180    0.61rem;
    181   margin-bottom: -0.28 - 0.08 - 0.38rem;
    182   text-align: center;
    183 }
    184 
    185 .data-txt {
    186   font-size: 0.2rem;
    187   line-height: 0.2rem;
    188   margin-bottom: 0.09rem;
    189 }
    190 
    191 .progress {
    192   height: 0rem;
    193   transition: height 0.5s ease-in-out;
    194 }
    195 
    196 .ani {
    197   @for $i from 1 to 6 {
    198     &:nth-of-type(#{$i}) {
    199       .progress {
    200         transition-delay: #{$i * 0.15}s;
    201       }
    202     }
    203   }
    204   // &:nth-of-type(1) {
    205   //   .progress {
    206   //     transition-delay: .4s;
    207   //   }
    208   // }
    209 
    210   // &:nth-of-type(2) {
    211   //   .progress {
    212   //     transition-delay: .8s;
    213   //   }
    214   // }
    215 
    216   // &:nth-of-type(3) {
    217   //   .progress {
    218   //     transition-delay: 1s;
    219   //   }
    220   // }
    221 
    222   // &:nth-of-type(4) {
    223   //   .progress {
    224   //     transition-delay: 1.4s;
    225   //   }
    226   // }
    227 
    228   // &:nth-of-type(5) {
    229   //   .progress {
    230   //     transition-delay: 1.8s;
    231   //   }
    232   // }
    233 }
    234 
    235 .pg-data {
    236   display: block;
    237    0.12rem;
    238   height: 100%;
    239   margin: 0 auto;
    240   background: linear-gradient(0deg, #c88eff 0%, #7e5cff 100%);
    241   box-shadow: 0 -0.04rem 0.14rem 0 rgba(129, 93, 255, 0.4);
    242   border-radius: 0.05rem 0.05rem 0 0;
    243 }
    244 
    245 // 0分展示规则
    246 .nodata {
    247   .pg-data {
    248     border-radius: 0;
    249     background: #e7e7e7;
    250     box-shadow: none;
    251   }
    252 }
    253 
    254 .week {
    255   font-size: 0.2rem;
    256   line-height: 0.2rem;
    257   margin-top: 0.08rem;
    258   color: #666;
    259 }
    260 // 环图 - data02数据部分
    261 .data02-charts {
    262   margin-top: 0.32rem;
    263   height: 1.61rem;
    264 }
    265 
    266 .canvas-box {
    267   position: relative;
    268   float: left;
    269    1.61rem;
    270   height: 1.61rem;
    271   margin-left: 0.92rem;
    272 }
    273 
    274 .my-canvas {
    275    1.61rem;
    276   height: 1.61rem;
    277 }
    278 .clr-canvas {
    279   position: absolute;
    280   top: 0;
    281   left: 0;
    282 }
    283 
    284 .canvas-data {
    285   position: absolute;
    286   top: 0.56rem;
    287   left: 0;
    288   right: 0;
    289   margin: auto;
    290   margin-left: -0.1rem;
    291   text-align: center;
    292   font-size: 0.24rem;
    293 
    294   .num {
    295     font-size: 0.32rem;
    296     font-weight: 600;
    297   }
    298 }
    299 </style>
  • 相关阅读:
    [BJWC2011]最小三角形
    用于监视Linux上的内存使用情况的Bash脚本
    使用ps、top、ps_mem命令找出Linux中的最大内存消耗过程
    无法开机 如果您的手机黑屏无法开机,可以按照以下方式操作尝试: 如果是,使用原装充电器或使用弱电流方式充电(例如使用电脑USB接口充电)充电15-30分钟后尝试重新开机;注意:电量过低引起的无法开机,刚插入充电器时可能不亮屏但呼吸灯闪烁状态。
    Centos7 cache/buff过高处理方法
    上,打开SSH服务的配置文件:/etc/ssh/sshd_config 加上如下两行: ClientAliveInterval 120 ClientAliveCountMax 720 第一行,表示每隔120秒向客户端
    inux软件安装管理之——dpkg与apt-*详解
    在虚拟机中安装 Ubuntu
    一看就懂的 安装完ubuntu 18.04后要做的事情和使用教程
    一、apt的简介
  • 原文地址:https://www.cnblogs.com/padding1015/p/11138606.html
Copyright © 2011-2022 走看看