**
如何实现一个简单的canvas动态水球图。
**
由于在项目中遇到有个制作一个水球图需求,在网上查找相关资料比较少,样式又不符合预期,在这样的情况下封装了一个自己可更改、定制化的水球图动效组件。
效果图:
代码如下:
1.封装组件:
<template>
<div class="sun-waterpolo_conrainer" :style="'box-shadow: 0 0 20px '+lineColor+';width: '+width+'px; height: '+width+'px;'">
<span v-show="text">{
{text}}</span>
<span v-show="!text">{
{num|rounding}}<span class="unit"> {
{unit}}</span></span>
<div class="sun-canvas_conrainer" :style="'box-shadow: 0 0 5px '+lineColor+' inset;width: '+width+'px; height: '+width+'px;'">
<canvas :id="canvasId"></canvas>
</div>
</div>
</template>
<script>
export default {
data () {
return {
oCanvas: null,
oContext: null,
options: null
}
},
filters: {
rounding (value) {
if (value === 0) {
return '0.00'
}
if (!value || isNaN(value)) {
return '--'
}
value = Number(value)
if (typeof obj === 'number' && value % 1 === 0) {
value = value + '.00'
} else {
value = value.toFixed(2)
}
return value
}
},
props: {
text: {
type: [String, Number],
default: ''
},
num: {
type: [String, Number],
default: '40'
},
unit: {
type: String,
default: '%'
},
canvasId: {
type: String,
default: 'canvasId_1'
},
color: {
type: String,
default: '#01c6df'
},
lineColor: {
type: String,
default: '#01c6df'
},
width: {
type: Number,
default: 100
}
},
mounted () {
this.init()
},
methods: {
init () {
this.oCanvas = document.getElementById(this.canvasId)
this.context = this.oCanvas.getContext('2d')
this.oCanvas.width = this.width
this.oCanvas.height = this.width
this.options = {
value: 40,
a: this.width / 8, // 振幅
pos: [1, this.width * 0.5], // 水球图位置
r: this.width / 2 - 1, // 水球图半径
color: [this.color, this.color, this.color, this.color]
// color: ['#2E5199', '#1567c8', '#1593E7', '#42B8F9']
}
this.start(this.options)
},
/**
* 绘制图表
*/
start (options) {
this.context.translate(options.pos[0], options.pos[1])
this.context.font = 'normal 16px Arial'
this.context.textAlign = 'center'
this.context.textBaseLine = 'baseline'
this.createParams(options)
requestAnimationFrame(this.startAnim) // 循环动画
},
// 生成水波动画参数
createParams (options) {
options.w = [] // 存储水波的角速度
options.theta = [] // 存储每条水波的位移
for (let i = 0; i < 4; i++) {
options.w.push(Math.PI / (100 + 20 * Math.random()))
options.theta.push(20 * Math.random())
}
},
// 绘制水波线
drawWaterLines (options) {
let offset
let A = options.a // 正弦曲线振幅
let y, x, w, theta
let r = options.r
// 遍历每一条水纹理
for (let line = 0; line < 4; line++) {
this.context.save()
// 每次绘制时水波的偏移距离
theta = Math.random()
offset =
r +
A / 2 -
((r * 19) / 8 + A) * (options.value / 100) +
(line * r) / 12
// 获取正弦曲线计算参数
w = options.w[line]
theta = options.theta[line]
this.context.fillStyle = options.color[line]
this.context.moveTo(0, 0)
this.context.beginPath()
for (x = 0; x <= 2 * r; x += 0.1) {
y = A * Math.sin(w * x + theta) + offset
// 绘制点
this.context.lineTo(x, y)
}
// 绘制为封闭图形
this.context.lineTo(x, r)
this.context.lineTo(x - 2 * r, r)
this.context.lineTo(0, A * Math.sin(theta) - options.width)
this.context.closePath()
// 填充封闭图形
this.context.fill()
// 截取水波范围,绘制文字
this.context.clip()
// this.context.fillStyle = '#071C5C'
// this.context.fillText(parseInt(options.value, 10) + '%', 1, 1)
this.context.restore()
}
},
// 绘制最底层的深色文字
drawText1 (options) {
this.context.fillStyle = options.color[0]
this.context.fillText(parseInt(options.value, 10) + '%', 1, 1)
},
// 帧动画循环
startAnim () {
this.options.theta = this.options.theta.map(item => item - 0.03)
this.options.value += this.options.value > 100 ? 0 : 0.1
this.options.value = this.options.value > 40 ? 40 : this.options.value
this.context.save()
this.resetClip(this.options) // 剪切绘图区
// this.drawText1(this.options)// 绘制蓝色文字
this.drawWaterLines(this.options) // 绘制水波线
this.context.restore()
requestAnimationFrame(this.startAnim)
},
// 重新剪裁绘图区域
resetClip (options) {
let r = options.r
this.context.strokeStyle = this.lineColor
this.context.fillStyle = '#071C5C'
this.context.lineWidth = 1
this.context.beginPath()
this.context.arc(r, 0, r, 0, 2 * Math.PI, false)
this.context.closePath()
this.context.fill()
this.context.shadowColor = this.lineColor
this.context.shadowBlur = 1
this.context.shadowOffsetX = 0
this.context.shadowOffsetY = 0
this.context.stroke()
this.context.beginPath()
this.context.arc(r, 0, r + 1, 0, 2 * Math.PI, true)
this.context.clip()
}
}
}
</script>
<style scoped>
.sun-waterpolo_conrainer{
border-radius: 50%;
position: relative;
}
.sun-canvas_conrainer{
display: inline-block;
overflow: hidden;
border-radius: 50%;
position: relative;
}
.sun-waterpolo_conrainer>span{
position: absolute;
top: 50%;
margin-top: -9px;
display: inline-block;
width: 200%;
left: 50%;
margin-left: -100%;
font-size: 18px;
color: #ffffff;
text-shadow: 0 3px 3px rgba(0,0,0,1);
text-align: center;
z-index: 9;
}
.sun-waterpolo_conrainer>span .unit{
font-size: 12px;
}
</style>
2.如何引用:
<template>
<WaterPolo :width="90" :height="90" lineColor="#01c6df" text="40%" canvasId="liquidFill_1" color="rgba(13,245,249,.5)"></WaterPolo>
<WaterPolo :width="90" :height="90" lineColor="#c2b128" text="40%" canvasId="liquidFill_2" color="rgba(243,214,22,.5)"></WaterPolo>
</template>
<script>
import WaterPolo from '@/components/waterpolo/waterpolo'
export default {
components: {
WaterPolo
}
}
</script>
这样我们就能实现自己喜欢的水球图动画了,冲冲冲!!!。