实现水波浪进度球
工作上需要做一个波浪进度球展示系统资源的占用情况
效果图如下
# 一、实现思路
水波球由一个圆和波浪区域组成,重点在于波浪区域的实现。 Canvas 中有提供贝塞尔曲线函数 quadraticCurveTo 可以用它画出平滑的曲线来模拟水波
# 二、实现步骤
首先看一下 quadraticCurveTo 函数签名
context.quadraticCurveTo(cpx,cpy,x,y);
quadraticCurveTo() 方法通过使用表示二次贝塞尔曲线的指定控制点,向当前路径添加一个点。
提示:二次贝塞尔曲线需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath() 和 moveTo() 方法来定义开始点。
# 1. 贝塞尔曲线模拟波浪
我们先写一个画贝塞尔曲线的公共方法
/**
* ctx getContext 返回对象
* W 画布宽
* color 线条颜色
* wave 波浪起伏程度
* Y 结束点y坐标
*/
drawCurve(ctx, X, color, wave, Y) {
ctx.save()
ctx.beginPath()
ctx.moveTo(0, X)
ctx.lineTo(0, Y)
ctx.quadraticCurveTo(X / 4, Y - wave, X / 2, Y)
ctx.lineTo(X / 2, Y)
ctx.quadraticCurveTo((X * 3) / 4, Y + wave, X, Y)
ctx.lineTo(X, X) // lineTo 作为曲线下方的填充
ctx.lineTo(0, X) // lineTo 作为曲线下方的填充
ctx.fillStyle = color
ctx.fill()
ctx.restore()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2.动画
曲线有了,第二步是让它动起来
function init() {
let canvas1: any = document.getElementById('wave-ball-canvas')
let mW = canvas1.clientWidth
// 设置Canvas元素的高
canvas1.style.height = mW
// 设置Canvas画布的宽高
canvas1.width = canvas1.height = mW
let canvas2: any = document.createElement('canvas')
let ctx2: any = canvas2.getContext('2d')
canvas2.width = mW
canvas2.height = mW
let { flat, speed, rate, distance, wave, waveColor, opacity } = this
let x = 0
let ctx1 = canvas1.getContext('2d')
this.drawCurve(ctx2, mW, waveColor, wave, mW - mW * rate)
let rate1 = rate
let wave1 = wave
let that = this
function animation() {
ctx1.clearRect(0, 0, mW, mW)
ctx1.drawImage(canvas2, x, 0, mW + flat, mW)
ctx1.drawImage(canvas2, x - mW - flat, 0, mW + flat, mW)
x >= mW - speed + flat ? (x = 0) : (x += speed)
requestAnimationFrame(animation) // 浏览器每一帧执行动画
}
animation()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
这里有个关键函数 requestAnimationFrame
比起 setTimeout、setInterval 的优势主要有两点:
- requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒 60 帧。
2、在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的的 cpu,gpu 和内存使用量。
使用 requestAnimationFrame 让动画更流畅且性能开销会小一些
动画效果如下
# 3. 让动画更生动
为了波浪更加生动,可以给它加一个带透明度和偏离值的波浪
init() {
let canvas1: any = document.getElementById('wave-ball-canvas')
let mW = canvas1.clientWidth
// 设置Canvas元素的高
canvas1.style.height = mW
// 设置Canvas画布的宽高
canvas1.width = canvas1.height = mW
let canvas2: any = document.createElement('canvas')
let ctx2: any = canvas2.getContext('2d')
canvas2.width = mW
canvas2.height = mW
let canvas3: any = document.createElement('canvas')
let ctx3: any = canvas3.getContext('2d')
canvas3.width = mW
canvas3.height = mW
let { flat, speed, rate, distance, wave, waveColor, opacity } = this
let x = 0
let ctx1 = canvas1.getContext('2d')
this.drawCurve(ctx2, mW, waveColor, wave, mW - mW * rate)
this.drawCurve(ctx3, mW, `${this.sixteenToRgba(waveColor, opacity)}`, wave, mW - mW * rate) //
let rate1 = rate
let wave1 = wave
let that = this
function animation() {
ctx1.clearRect(0, 0, mW, mW)
ctx1.drawImage(canvas2, x, 0, mW + flat, mW)
ctx1.drawImage(canvas2, x - mW - flat, 0, mW + flat, mW)
ctx1.drawImage(canvas3, x + distance, 0, mW + flat, mW) // 加入偏离值distance
ctx1.drawImage(canvas3, x - mW + distance - flat, 0, mW + flat, mW)
x >= mW - speed + flat ? (x = 0) : (x += speed)
requestAnimationFrame(animation) // 浏览器每一帧执行动画
}
animation()
}
// 十六进制颜色转成 rgba 带 透明度
sixteenToRgba(sixteen, opacity) {
let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
let sColor: any = sixteen
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = '#'
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
// 处理六位的颜色值
let sColorChange: any[] = []
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
}
return 'rgba(' + sColorChange.join(',') + `,${opacity})`
} else {
return sColor
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
波浪效果完成
传入不同百分比参数
# 三、封装为Vue组件
Vue 组件完整代码
<template>
<div
class="content"
:style="{
width: size + 'px',
height: size + 'px',
borderColor: waveColor
}"
>
<canvas id="wave-ball-canvas"></canvas>
<p class="value" :style="{ color: fontColor, fontSize: fontSize + 'px' }">
{{ rate * 100 }}%
</p>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
import { State, Getter, Action, Mutation, namespace } from 'vuex-class'
@Component({ components: {} })
export default class WaveBall extends Vue {
@Prop({ type: Number, default: 0.6 }) rate!: any // 进度 比例 min=0 max=1
@Prop({ type: Number, default: 100 }) size!: any // 球大小
@Prop({ type: String, default: '#00c2fb' }) waveColor!: String
@Prop({ type: String, default: '#F5EEDC' }) fontColor!: String
@Prop({ type: Number, default: 26 }) fontSize!: any
@Prop({ type: Number, default: 2 }) speed!: any // 波浪速度 min=1 max=12
@Prop({ type: Number, default: 200 }) flat!: any // 波浪平滑度 min=200 max=600
@Prop({ type: Number, default: 100 }) distance!: any // 波浪偏移量 min=0 max=200
@Prop({ type: Number, default: 10 }) wave!: any // 波浪起伏度 min=5 max=60
@Prop({ type: Number, default: 0.6 }) opacity!: any // 波浪起伏透明度 min=5 max=60
created() {}
mounted() {
this.init()
}
/**
* ctx getContext 返回对象
* X 纵坐标
* color 线条颜色
* wave 波浪起伏程度
* Y 结束点y坐标
*/
drawCurve(ctx, X, color, wave, Y) {
ctx.save()
ctx.beginPath()
ctx.moveTo(0, X)
ctx.lineTo(0, Y)
ctx.quadraticCurveTo(X / 4, Y - wave, X / 2, Y)
ctx.lineTo(X / 2, Y)
ctx.quadraticCurveTo((X * 3) / 4, Y + wave, X, Y)
ctx.lineTo(X, X)
ctx.lineTo(0, X)
ctx.fillStyle = color
ctx.fill()
ctx.restore()
}
init() {
let canvas1: any = document.getElementById('wave-ball-canvas')
let mW = canvas1.clientWidth
// 设置Canvas元素的高
canvas1.style.height = mW
// 设置Canvas画布的宽高
canvas1.width = canvas1.height = mW
let canvas2: any = document.createElement('canvas')
let ctx2: any = canvas2.getContext('2d')
canvas2.width = mW
canvas2.height = mW
let canvas3: any = document.createElement('canvas')
let ctx3: any = canvas3.getContext('2d')
canvas3.width = mW
canvas3.height = mW
let { flat, speed, rate, distance, wave, waveColor, opacity } = this
let x = 0
let ctx1 = canvas1.getContext('2d')
this.drawCurve(ctx2, mW, waveColor, wave, mW - mW * rate)
this.drawCurve(
ctx3,
mW,
`${this.sixteenToRgba(waveColor, opacity)}`,
wave,
mW - mW * rate
)
let rate1 = rate
let wave1 = wave
let that = this
function animation() {
if (rate !== rate1 || wave1 !== wave) {
ctx2.clearRect(0, 0, mW, mW)
ctx3.clearRect(0, 0, mW, mW)
rate1 = rate
wave1 = wave
that.drawCurve(ctx2, mW, waveColor, wave, mW - mW * rate)
that.drawCurve(
ctx3,
mW,
`${that.sixteenToRgba(waveColor, opacity)}`,
wave,
mW - mW * rate
)
}
ctx1.clearRect(0, 0, mW, mW)
ctx1.drawImage(canvas2, x, 0, mW + flat, mW)
ctx1.drawImage(canvas2, x - mW - flat, 0, mW + flat, mW)
ctx1.drawImage(canvas3, x + distance, 0, mW + flat, mW)
ctx1.drawImage(canvas3, x - mW + distance - flat, 0, mW + flat, mW)
x >= mW - speed + flat ? (x = 0) : (x += speed)
requestAnimationFrame(animation) // 浏览器每一帧执行动画
}
animation()
}
// 十六进制颜色转成 rgba 透明度
sixteenToRgba(sixteen, opacity) {
let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
let sColor: any = sixteen
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = '#'
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
// 处理六位的颜色值
let sColorChange: any[] = []
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
}
return 'rgba(' + sColorChange.join(',') + `,${opacity})`
} else {
return sColor
}
}
}
</script>
<style lang="scss" scoped>
.content {
position: relative;
border-radius: 50%;
border: 1px solid #1c86d1;
overflow: hidden;
padding: 8px;
box-sizing: border-box;
background: #a5e2fc;
.value {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
// font-weight: bold;
}
canvas {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
用法
<WaveBall
:rate="0.46"
:size="140"
waveColor="#00c2fb"
fontColor="#F5EEDC"
:fontSize="28"
/>
1
2
3
4
5
6
7
2
3
4
5
6
7
# 四、to do
- 百分比变化 -> 球体 颜色 无极或者阶梯变化
- 百分比变化 -> 球体波浪 起伏度 无极或者阶梯变化
- 百分比变化 -> 球体波浪 速度 无极或者阶梯变化
上次更新: 2023/04/05, 09:41:10