1.效果图
2.使用插件
这里使用了math.js用于处理js的数字精度问题,使用了lodash用于深拷贝父组件数据,便于子组件不直接修改父组件传递的props
//math.js
import * as math from 'mathjs';
export default {
// 加
add(num1,num2){
return math.add(math.bignumber(num1),math.bignumber(num2));
},
// 乘
multiply(num1,num2){
return math.multiply(math.bignumber(num1),math.bignumber(num2));
},
// 减
subtract(num1,num2){
return math.subtract(math.bignumber(num1),math.bignumber(num2));
},
// 除
divide(num1,num2){
return math.divide(math.bignumber(num1),math.bignumber(num2));
}
}
2.初始化参数
initParams() {
this.canvas = this.$refs.canvas;
this.oW = canvas.width;
this.oH = canvas.height;
this.ctx = canvas.getContext("2d");
this.ctx.beginPath();
this.ctx.lineWidth = this.lineWidth;
this.r = this.oW / 2;
this.cR = this.r - 10 * this.lineWidth;
this.bR = this.r - 8 * this.lineWidth;
this.axisLength = 2 * this.r - 16 * this.lineWidth;
this.unit = this.axisLength / 9;
this.nowrange = this.range;
this.xoffset = 8 * this.lineWidth;
this.soffset = -(this.PI / 2);
let value = _.cloneDeep(this.value);
this.decimal =
_math.multiply(value, 100).toString().split(".")[1] /
10 ** _math.multiply(value, 100).toString().split(".")[1].length;
for (
let i = this.soffset;
i < this.soffset + 2 * this.PI;
i += 1 / (8 * this.PI)
) {
this.arcStack.push([
this.r + this.bR * this.Cos(i),
this.r + this.bR * this.Sin(i),
]);
}
// 圆起始点
let cStartPoint = this.arcStack.shift();
this.ctx.strokeStyle = "#1c86d1";
this.ctx.moveTo(cStartPoint[0], cStartPoint[1]);
},
3.canvas画图
将水波图进行拆分,分别是图像展示层以及功能层:其中图像展示层包含外层圆环,内层圆环,进度条,进度条背景,水波切图,中间数据展示,功能重要包含一个水波上涨动效,以及进度条动效和数据变化动效
3.1最外层圈
drawOutCircle() {
this.ctx.beginPath();
this.ctx.lineWidth = this.drawCircleStyle.lineWidth;
this.ctx.strokeStyle = this.drawCircleStyle.color;
this.ctx.arc(this.r, this.r, this.cR + 7, 0, 2 * this.PI);
this.ctx.stroke();
this.ctx.restore();
},
3.2 中间圈
drawContentCircle() {
this.ctx.beginPath();
this.ctx.lineWidth = this.grayCircleStyle.lineWidth;
this.ctx.strokeStyle = this.grayCircleStyle.color;
this.ctx.arc(this.r, this.r, this.cR - 5, 0, 2 * this.PI);
this.ctx.stroke();
this.ctx.restore();
this.ctx.beginPath();
}
3.3 进度条圈
percentCircle() {
this.ctx.beginPath();
this.ctx.strokeStyle = this.CircleStyle.color;
//使用这个使圆环两端是圆弧形状
this.ctx.lineCap = "round";
this.ctx.arc(
this.r,
this.r,
this.cR - 5,
0 * (Math.PI / 180.0) - Math.PI / 2,
this.nowdata * 360 * (Math.PI / 180.0) - Math.PI / 2
);
this.ctx.stroke();
this.ctx.save();
},
3.4波浪
drawWavesCircle() {
this.ctx.beginPath();
this.ctx.arc(this.r, this.r, this.cR - 10, 0, 2 * this.PI, false);
this.ctx.clip();
}
3.5中间显示文字百分数
drawTextValue() {
this.ctx.globalCompositeOperation = "source-over";
let size = 0.4 * this.cR;
this.ctx.font = this.textStyle.font;
let txt =
parseFloat((this.nowdata.toFixed(2) * 100).toFixed(0)) +
this.decimal +
"%";
this.ctx.fillStyle = this.textStyle.color;
this.ctx.textAlign = this.textStyle.textAlign;
this.ctx.fillText(txt, this.r + 5, this.r + 20);
},
4.功能函数
这里一共写了两个功能函数,一个用于动态渲染进度百分数。另外一个实现水波随百分数的升高上涨的函数
//波浪上涨函数
drawUpSine() {
this.ctx.beginPath();
this.ctx.save();
//建立一个栈记录起始点和终点坐标
let Stack = [];
for (
let i = this.xoffset;
i <= this.xoffset + this.axisLength;
i += 20 / this.axisLength
) {
let x = this.sp + (this.xoffset + i) / this.unit;
let y = this.Sin(x) * this.nowrange;
let dx = i;
let dy =
2 * this.cR * (1 - this.nowdata) + (this.r - this.cR) - this.unit * y;
this.ctx.lineTo(dx, dy);
Stack.push([dx, dy]);
}
// 获取初始点和结束点
let startP = Stack[0];
this.ctx.lineTo(this.xoffset + this.axisLength, this.oW);
this.ctx.lineTo(this.xoffset, this.oW);
this.ctx.lineTo(startP[0], startP[1]);
this.ctx.fillStyle = this.sine.color;
this.ctx.lineWidth = 0;
this.ctx.fill();
this.ctx.restore();
},
// 调整百分值
adjustPercent() {
if (this.data >= 0.85) {
if (this.nowrange > this.range / 4) {
this.t = this.range * 0.01;
this.nowrange -= this.t;
}
} else if (this.data <= 0.1) {
if (this.nowrange < this.range * 1.5) {
this.t = this.range * 0.01;
this.nowrange += this.t;
}
} else {
if (this.nowrange <= this.range) {
this.t = this.range * 0.01;
this.nowrange += this.t;
}
if (this.nowrange >= this.range) {
this.t = this.range * 0.01;
this.nowrange -= this.t;
}
}
if (this.data - this.nowdata > 0) {
this.nowdata += this.sine.waveupsp;
}
if (this.data - this.nowdata < 0) {
this.nowdata -= this.sine.waveupsp;
}
this.data = this.value; //给固定值
this.sp += 0.07;
},
5.渲染函数
这里开始使用的是定时器,弄了半天没弄出来,使用了requestAnimationFrame详细用法掘金上有篇文章写的不错,地址:juejin.cn/post/699129…
//渲染canvas
renderCanvas() {
this.ctx.clearRect(0, 0, this.oW, this.oH);
//最外面色圈
this.drawOutCircle();
//内层色圈
this.drawContentCircle();
//进度圈
this.percentCircle();
//中间波浪
this.drawWavesCircle();
//调整数值
this.adjustPercent();
// 开始水波动画
this.drawSine();
// 进度文字
this.drawTextValue();
//动画函数
this.targetId = requestAnimationFrame(this.render);
},
6.初始渲染与清除动画
mounted() {
//初始化参数
this.initParams();
// 开始渲染
this.render();
},
//清除动画函数
beforeDestroy() {
this.targetId = null;
this.canvas = null;
cancelAnimationFrame(this.targetId);
},
7.prop传入
props: {
value: Number | Object,
styles: {
type: Object,
default: {
width: 300,
height: 300,
},
},
textStyle: {
type: Object,
default: {
font: "bold 30px 黑体",
textAlign: "center",
color: "#000",
},
},
drawCircleStyle: {
type: Object,
default: {
lineWidth: 10,
color: "purple",
},
},
grayCircleStyle: {
type: Object,
default: {
lineWidth: 10,
color: "#030b30",
},
},
CircleStyle: {
type: Object,
default: {
color: "blue",
},
},
sine: {
type: Object,
default: {
color: "#43bd00",
waveupsp: 0.006,
},
},
8.动态渲染
这里有个需求是根据值的变化动态的改变水波图进度,这里我使用watch监听从父组件传来的进度值
watch: {
//监听value值以重新渲染
value: {
handler(newVal, oldVal) {
if (newVal != this.value) {
this.initParams();
this.render();
}
},
deep: true,
immediate: true,
},
},
9.使用组件
<template>
<div>
<CorrugatedProgressChart :value="percent" :styles="styleObj" :textStyle='textStyle' :CircleStyle="CircleStyle" :drawCircleStyle='drawCircleStyle' :grayCircleStyle='grayCircleStyle' :sine='sine'/>
</div>
</template>
<script>
import CorrugatedProgressChart from "./components/CorrugatedProgressChart.vue";
export default {
name: "App",
components: {
CorrugatedProgressChart,
},
data() {
return {
percent: 0.563,
styleObj: {
width: 300,
height: 300,
},
textStyle: {
font: 'bold 30px Microsoft Yahei',
textAlign: "center",
color: "yellow",
},
//最外层圈样式
drawCircleStyle:{
lineWidth:24,
color:'#078782'
},
//中间背景圈
grayCircleStyle:{
lineWidth:12,
color:'gold',
},
//进度圈
CircleStyle:{
color:'#006f6b'
},
sine:{
color:"#15B4A5",
//水上涨速度
waveupsp:0.003
},
rangeVal:0,
maxVal:100,
minVal:0
};
},
methods: {
},
};
</script>