前言:
此事例是在vue组件中,使用canvas实现倒计时动画的效果。其实,实现效果的逻辑跟vue没有关系,只要读懂canvas如何实现效果的这部分逻辑就可以了
canvas动画的原理:利用定时器,给定合理的帧数,不断的清除画布,再重绘图形,即呈现出动画效果。
让我们先看下效果
说明:动画效果如下gif图。此效果在网页上运行的时候是全程流畅的,这里转成gif格式,帧数减少了,才看去是卡顿效果。
左手右手一个慢动作
说明:扇形颜色是渐变的(仔细看图:扇形逆时针方向渐变颜色,内侧浅,外侧深)
动画步骤分析:设定15s的倒计时。开始倒计时后颜色为绿色,匀速扇形1顺时针绕圆心旋转,且外圈圆环同步角度减少周长。
10s后变黄色,且出现快速旋转的扇形2。5s后,所有颜色变红,且扇形2加速运动,越转越快,直至时间到0s。倒计时结束,
扇形圆环消失,提示文字,结束动画。
修炼js宝典,get canvas技能
之前对于canvas一窍不通,网上查了资料,介绍api的时候过于简单,也不好理解,看别人写的示例代码,更是懵逼。而后,回归
原始,修炼经典的《JavaScript高级程序设计》这本js宝典。书本二三十页的介绍canvas部分,系统的学习了一下。边看边写效果,20多页
的书看完了,效果也写出来了。对于新手,我推荐先熟悉下canvas的api,然后实现效果就是分分钟的事了
上代码
<!--
Describe: 倒计时countDown
-->
<template>
<section class="ctD-wrap" >
<canvas id="cD" width="200" height="200">浏览器不支持Canvas,请升级或改用其它浏览器!</canvas>
</section>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data () {
return {
ctx: null,
timer: null,
sec: 15, // 倒计时的时间
ms: 10,
r: 100, // 背景圆半径r。这里100为cavans宽的一半
rB: 90, // 圆环半径90
lineWidth: 8, // 圆环粗细程度
degFan: Math.PI / 4, // 扇形大小
color: ['rgba(0, 255, 0, .6)', 'rgba(0, 100, 0, .6)'], // 颜色
speedFan: 12 // 运动扇形的速度
}
},
computed: {
// 在坐标系的度数:逆时针旋转90度
degCs () {
// 走了一圈的百分之多少
let pro = (1 - this.ms / (this.sec * 1000))
// 走的度数
let deg = pro * Math.PI * 2
return deg - Math.PI / 2
}
},
methods: {
drawText (text, fontSize, x, y) {
// 文字
this.ctx.fillStyle = '#fff'
this.ctx.font = `bold ${fontSize}px ''`
this.ctx.textAlign = 'center'
this.ctx.textBaseline = 'middle'
this.ctx.fillText(text, x, y)
},
drawCirBg () {
// 圆形背景
this.ctx.beginPath()
this.ctx.arc(0, 0, this.r, 0, Math.PI * 2, false)
this.ctx.fillStyle = 'rgba(0, 0, 0, .6)'
this.ctx.fill()
},
drawCirBd () {
// 圆环
this.ctx.beginPath()
this.ctx.arc(0, 0, this.rB, this.degCs, Math.PI * 2 - Math.PI / 2, false)
this.ctx.strokeStyle = this.color[0]
this.ctx.stroke()
},
drawFan (deg) {
// 画扇形:
// 1、半径 this.rB - this.lineWidth / 2;
// 2、角度大小:this.degFan;
// 3、位置:deg;
this.ctx.beginPath()
this.ctx.moveTo(0, 0)
this.ctx.arc(0, 0, this.rB - this.lineWidth / 2, deg, deg + this.degFan, false)
this.ctx.closePath()
// 渐变:确定渐变起终坐标点
let x0 = Math.cos(deg) * this.r
let y0 = Math.sin(deg) * this.r
let x1 = Math.cos(deg + this.degFan) * this.r
let y1 = Math.sin(deg + this.degFan) * this.r
let gradient = this.ctx.createLinearGradient(x0, y0, x1, y1)
gradient.addColorStop(0, this.color[0])
gradient.addColorStop(1, this.color[1])
this.ctx.fillStyle = gradient
this.ctx.fill()
},
colorChange () {
switch (true) {
case this.ms >= 10000 :
this.color[0] = 'rgba(0, 255, 0, .6)'
this.color[1] = 'rgba(0, 100, 0, .6)'
break
case this.ms >= 5000 :
this.color[0] = 'rgba(255, 255, 0, .6)'
this.color[1] = 'rgba(100, 100, 0, .6)'
this.speedFan += 0.01
break
case this.ms <= 5000 :
this.color[0] = 'rgba(255, 0, 0, .6)'
this.color[1] = 'rgba(100, 0, 0, .6)'
this.speedFan += 0.02
break
}
},
clearTimer () {
clearInterval(this.timer)
this.timer = null
},
// canvas绘图
drawCd (endCb) {
let self = this
this.ctx.clearRect((-1) * this.r, (-1) * this.r, 2 * this.r, 2 * this.r)
// 原点移动
this.drawCirBg()
// console.log(this.sec * 1000, this.ms)
if (this.ms <= 0) {
// 转了一圈后
this.drawText('停止下注', 40, 0, 0)
endCb()
this.clearTimer()
} else {
// 一圈之内
this.ctx.lineWidth = this.lineWidth
this.colorChange()
this.drawCirBd()
this.drawFan(this.degCs)
// 快速运动的扇形
if (this.ms < 10000) {
// 速度不断加快
// this.speedFan += 0.015
// console.log(this.speedFan)
let sDeg = this.degCs * this.speedFan
this.drawFan(sDeg)
}
// 文字
let time = Math.ceil(this.ms / 1000)
this.drawText(time, 80, 0, -30)
this.drawText('请下注', 30, 0, 30)
}
},
// 启动定时器,开始倒计时
// endCb:结束倒计时的回调函数
startCd (endCb) {
// fps:1s的帧数
let fps = 100
let sepTime = 1000 / fps
this.timer = setInterval(() => {
this.drawCd(endCb)
this.ms -= sepTime
// console.log(this.ms)
}, sepTime)
}
},
mounted () {
const self = this
let drawing = document.querySelector('#cD')
this.ctx = drawing.getContext('2d')
// 移动原点
this.ctx.translate(this.r, this.r)
this.startCd(() => {
console.log('结束倒计时回调')
})
},
watch: {
sec: {
immediate: true,
handler: function (val) {
this.ms = 1000 * val
}
}
}
}
</script>
<style lang="scss" scoped>
$w: 66.7;
.ctD-wrap {
/*flex-grow: 1;*/
flex: 1;
/*background: #aaa;*/
/*border: 1px solid blue;*/
/*width: 400px;*/
/*height: 400px;*/
position: absolute;
left: .04rem;
top: .1rem;
transform: translateX(-90%);
}
.ctD {
}
#cD {
width: 1rem;
height: 1rem;
}
</style>