哈?标题不知道啥意思?
老规矩,直接看图!
效果如下:
高清大图!
码农多年,老眼昏花,动图看不清?!那就看静态截图!!!
不同分值效果如下:
看完了卖家秀,我们来看产品的制作过程吧!
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对象中,定义方法三个:
以下是三个方法的代码:
上边三个方法里边的代码,几乎都是对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>