1.效果如下:
2.首先此控件采用了vue框架(懒得写原生js)和svg实现。不bb,上代码。使用方法如下。
<EnvProgress
:radius="radius"
:value="currentShowItem.value"
:maxValue="currentShowItem.maxValue"
:minValue="currentShowItem.minValue"
style="left: 50%;top: 50%;width: 240px;height: 240px;position: absolute;transform: translateX(-50%) translateY(-50%)">
</EnvProgress>
3.准备好dom
<div id="main">
<svg style="position: absolute;left: 100px;top: 100px;width: 200px;height: 200px" id="svg" width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="50" r="40"
fill="red"/>
<text x="0" :x="maxValue.x" :y="maxValue.y" :fill="maxValue.style.fill">{{maxValue.value}}</text>
<text x="0" :x="minValue.x" :y="minValue.y" :fill="minValue.style.fill">{{minValue.value}}</text>
<line v-for="item in lines" :x1="item.x1" :y1="item.y1" :x2="item.x2" :y2="item.y2"
v-bind:style="{stroke:item.color}"
style="stroke-width:2"/>
</svg>
</div>
2.引入vue
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
3.定义vue组件属性如下
props: {
//半径
radius: {
type: Number,
default: 140
},
//最大值
maxValue: {
type: Number,
default: 100
},
//最小值
minValue: {
type: Number,
default: 0
},
//当前值
value: {
type: Number,
default: 50
},
//最大最小值的文本颜色
labelColor: {
type: String,
default: "#535353"
},
//刻度条颜色
activeColor: {
type: String,
default: "#dace01"
},
//刻度条底色
defaultColor: {
type: String,
default: "#666974"
},
//起始角度。采用角度制,未采用弧度制。
startAngle: {
type: Number,
default: 135
},
//终止角度
endAngle: {
type: Number,
default: 405
},
//间隔角度
step: {
type: Number,
default: 5
},
//刻度的长度
strokeWidth: {
type: Number,
default: 20
}
},
4.data属性:
data() {
return {
//刻度线
lines: [],
//当前value对应的角度
currentAngle: 0,
//最大最小值的坐标
label: {
x1: 0,
y1: 0,
x2: 0,
y2: 0
},
timer: null,
isAnimating: false,
i: 0,
//上面的圆形指示器
indicator: {
x: 0,
y: 0,
radius: 5
}
}
},
5.在组件mounted钩子下初始化。
init() {
//起始角度
let startAngle = this.startAngle;
//结束角度
let endAngle = this.endAngle;
//步长
let step = this.step;
//半径
let r1 = this.radius;
//半径2
let r2 = r1 - this.strokeWidth;
//进度
let progress = this.value / (this.maxValue - this.minValue);
let range = endAngle - startAngle;
let currentAngle = range * progress + startAngle;
this.currentAngle = currentAngle;
if (currentAngle < startAngle) {
currentAngle = startAngle
}
if (currentAngle > endAngle) {
currentAngle = endAngle
}
//指示器的x,y坐标 根据圆上的点的坐标公式
// x=centerX + radius*cos(angle) y =centerX + radius*sin(angle)
this.indicator.x = (r1 + (r1 - 5) * Math.cos(currentAngle * Math.PI / 180));
this.indicator.y = (r1 + (r1 - 5) * Math.sin(currentAngle * Math.PI / 180));
//遍历角度,算出每条刻度线的起始坐标和终止坐标。
for (let i = startAngle; i <= endAngle; i += step) {
let color = this.defaultColor;
if (i <= currentAngle) {
color = this.activeColor;
} else {
color = this.defaultColor;
}
let x = (r1 + (r1 - 11) * Math.cos(i * Math.PI / 180));
let y = (r1 + (r1 - 11) * Math.sin(i * Math.PI / 180));
let x2 = (r1 + (r2 - 11) * Math.cos(i * Math.PI / 180));
let y2 = (r1 + (r2 - 11) * Math.sin(i * Math.PI / 180));
this.lines.push({
angle: i,
x1: x,
y1: y,
x2: x2,
y2: y2,
color: color
})
}
this.label.x1 = (r1 + r1 * Math.cos(startAngle * Math.PI / 180));
this.label.y1 = (r1 + r1 * Math.sin(startAngle * Math.PI / 180)) + 20;
this.label.x2 = (r1 + r1 * Math.cos(endAngle * Math.PI / 180)) - 20;
this.label.y2 = (r1 + r1 * Math.sin(endAngle * Math.PI / 180)) + 20;
}
6.动画效果。观察value值变化的时候执行。
startAnimate() {
this.isAnimating = true;
this.lines.forEach(v => {
v.color = this.defaultColor;
});
let self = this;
self.i = 0;
this.indicator.x = (this.radius + (self.radius - 5) * Math.cos(this.startAngle * Math.PI / 180));
this.indicator.y = (this.radius + (self.radius - 5) * Math.sin(this.startAngle * Math.PI / 180));
self.lines[self.i].color = self.activeColor;
if(this.timer){
clearInterval(this.timer);
}
this.timer = setInterval(function () {
self.i++;
if (self.lines.length == self.i) {
self.isAnimating = false;
self.i = 0;
self.clearIn();
return
} else {
if (self.lines[self.i].angle <= self.currentAngle) {
self.indicator.x = (self.radius + (self.radius - 5) * Math.cos(self.lines[self.i].angle * Math.PI / 180));
self.indicator.y = (self.radius + (self.radius - 5) * Math.sin(self.lines[self.i].angle * Math.PI / 180));
self.lines[self.i].color = self.activeColor
} else {
self.lines[self.i].color = self.defaultColor
}
}
}, 100)
},
7.最后完整版代码如下。
<template>
<svg style="" v-bind:style="{width:radius*2+'px',height:radius*2+'px'}" id="svg" width="100%" height="100%"
version="1.1"
xmlns="http://www.w3.org/2000/svg">
<circle :cx="indicator.x" :cy="indicator.y" :r="indicator.radius"
:fill="activeColor"/>
<text x="0" :x="label.x1" :y="label.y1" :fill="labelColor">{{minValue}}</text>
<text x="0" :x="label.x2" :y="label.y2" :fill="labelColor">{{maxValue}}</text>
<line v-for="item in lines" :x1="item.x1" :y1="item.y1" :x2="item.x2" :y2="item.y2"
v-bind:style="{stroke:item.color}"
style="stroke-width:2"/>
</svg>
</template>
<script>
export default {
name: 'EnvProgress',
props: {
radius: {
type: Number,
default: 140
},
maxValue: {
type: Number,
default: 100
},
minValue: {
type: Number,
default: 0
},
value: {
type: Number,
default: 50
},
labelColor: {
type: String,
default: "#535353"
},
activeColor: {
type: String,
default: "#dace01"
},
defaultColor: {
type: String,
default: "#666974"
},
startAngle: {
type: Number,
default: 135
},
endAngle: {
type: Number,
default: 405
},
step: {
type: Number,
default: 5
},
strokeWidth: {
type: Number,
default: 20
}
},
watch: {
value(newValue) {
console.log("newValue=" + newValue);
newValue = parseInt(newValue);
let progress = newValue / (this.maxValue - this.minValue);
let range = this.endAngle - this.startAngle;
let currentAngle = range * progress + this.startAngle;
this.currentAngle = currentAngle;
if (currentAngle < this.startAngle) {
currentAngle = this.startAngle
}
if (currentAngle > this.endAngle) {
currentAngle = this.endAngle
}
// console.log('value 变化了.oldvalue='+oldValue+",newValue="+newValue);
this.startAnimate();
}
},
methods: {
startAnimate() {
this.isAnimating = true;
this.lines.forEach(v => {
v.color = this.defaultColor;
});
let self = this;
self.i = 0;
this.indicator.x = (this.radius + (self.radius - 5) * Math.cos(this.startAngle * Math.PI / 180));
this.indicator.y = (this.radius + (self.radius - 5) * Math.sin(this.startAngle * Math.PI / 180));
self.lines[self.i].color = self.activeColor;
if(this.timer){
clearInterval(this.timer);
}
this.timer = setInterval(function () {
self.i++;
if (self.lines.length == self.i) {
self.isAnimating = false;
self.i = 0;
self.clearIn();
return
} else {
if (self.lines[self.i].angle <= self.currentAngle) {
self.indicator.x = (self.radius + (self.radius - 5) * Math.cos(self.lines[self.i].angle * Math.PI / 180));
self.indicator.y = (self.radius + (self.radius - 5) * Math.sin(self.lines[self.i].angle * Math.PI / 180));
self.lines[self.i].color = self.activeColor
} else {
self.lines[self.i].color = self.defaultColor
}
}
}, 100)
},
clearIn() {
console.log("clear");
clearInterval(this.timer);
},
init() {
//起始角度
let startAngle = this.startAngle;
//结束角度
let endAngle = this.endAngle;
//步长
let step = this.step;
//半径
let r1 = this.radius;
//半径2
let r2 = r1 - this.strokeWidth;
//进度
let progress = this.value / (this.maxValue - this.minValue);
let range = endAngle - startAngle;
let currentAngle = range * progress + startAngle;
this.currentAngle = currentAngle;
if (currentAngle < startAngle) {
currentAngle = startAngle
}
if (currentAngle > endAngle) {
currentAngle = endAngle
}
this.indicator.x = (r1 + (r1 - 5) * Math.cos(currentAngle * Math.PI / 180));
this.indicator.y = (r1 + (r1 - 5) * Math.sin(currentAngle * Math.PI / 180));
for (let i = startAngle; i <= endAngle; i += step) {
let color = this.defaultColor;
if (i <= currentAngle) {
color = this.activeColor;
} else {
color = this.defaultColor;
}
let x = (r1 + (r1 - 11) * Math.cos(i * Math.PI / 180));
let y = (r1 + (r1 - 11) * Math.sin(i * Math.PI / 180));
let x2 = (r1 + (r2 - 11) * Math.cos(i * Math.PI / 180));
let y2 = (r1 + (r2 - 11) * Math.sin(i * Math.PI / 180));
this.lines.push({
angle: i,
x1: x,
y1: y,
x2: x2,
y2: y2,
color: color
})
}
this.label.x1 = (r1 + r1 * Math.cos(startAngle * Math.PI / 180));
this.label.y1 = (r1 + r1 * Math.sin(startAngle * Math.PI / 180)) + 20;
this.label.x2 = (r1 + r1 * Math.cos(endAngle * Math.PI / 180)) - 20;
this.label.y2 = (r1 + r1 * Math.sin(endAngle * Math.PI / 180)) + 20;
}
},
data() {
return {
lines: [],
currentAngle: 0,
label: {
x1: 0,
y1: 0,
x2: 0,
y2: 0
},
timer: null,
isAnimating: false,
i: 0,
indicator: {
x: 0,
y: 0,
radius: 5
}
}
},
mounted() {
this.init();
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer);
}
}
}
</script>
<style scoped lang="less">
</style>