欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

canvas仪表盘

程序员文章站 2022-05-26 22:07:08
...

这是一个仿支付宝芝麻信用分的一个canvas,其实就是一个动画仪表盘。

首先, 上原图:

这个是在下支付宝上的截图,分低各位见笑了。然后看下我用canvas实现的效果图:

<canvas id="canvas" width="400" height="700" data-score='724'></canvas>
<!-- 设置data-score,分数区间[400, 900] -->

唉,总感觉不像。这个是GIF图,可能在网页上打开的效果会好一点(当然可能就是这样)。大家可以点击底部预览codepen上的演示。有两个不完美的地方,一个是实际上芝麻信用表盘上的的刻度是不均匀的,我这为了简单的实现就采取相同的刻度;二是表盘上运动的点是有模糊的效果,还没解决。唉,下次再说吧。

接下来还是来说说怎么实现的吧。第一步,国际惯例,创建画布:

var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d'),
    cWidth = canvas.width,
    cHeight = canvas.height;

然后绘制表盘,虽说不是处女座,但也要尽可能做到跟原图上的一样,那就是这个环形开口的角度是多少呢?请上ps来测一下:

嗯,136°,这个角度确实刁钻,为了方便接下来的计算,那就约等于140°。那么一个分数段的弧度就是:

var deg1 = Math.PI * 11 / 45

先把中间半透明的刻度层画好:

ctx.save(); //中间刻度层
ctx.beginPath();
ctx.strokeStyle = 'rgba(255, 255, 255, .2)';
ctx.lineWidth = 10;
ctx.arc(0, 0, 135, 0, 11 * deg0, false);
ctx.stroke();
ctx.restore();

接着,画6条刻度线,用for循环来实现:

ctx.save(); // 刻度线
for (var i = 0; i < 6; i++) {
  ctx.beginPath();
  ctx.lineWidth = 2;
  ctx.strokeStyle = 'rgba(255, 255, 255, .3)';
  ctx.moveTo(140, 0);
  ctx.lineTo(130, 0);
  ctx.stroke();
  ctx.rotate(deg1);
}
ctx.restore();

同理,再把大刻度细分为5个小刻度:

ctx.save(); // 细分刻度线
for (i = 0; i < 25; i++) {
  if (i % 5 !== 0){
    ctx.beginPath();
    ctx.lineWidth = 2;
    ctx.strokeStyle = 'rgba(255, 255, 255, .1)';
    ctx.moveTo(140, 0);
    ctx.lineTo(133, 0);
    ctx.stroke();
  }
  ctx.rotate(deg1 / 5);
}
ctx.restore();

刻度到这里就ok了,还需要给刻度标上文字和每个分数段的信用级别,具体的参见代码,因为跟刻度实现的原理差不多,就不啰嗦了。现在最关键就是实现表盘上那个运动的点(不知道怎么称呼,下文就叫它动点),我们可以这样想,它是个半径很小的圆,只不过是画在最外层环形轨道上圆,而圆在canvas上的实现方法是:

ctx.arc(x, y, radius, sAngle, eAngle, false);

我们只要控制x, y就能让它动起来,实现我们想要的效果。so,创建一个动点对象:

function Dot() {
  this.x = 0;
  this.y = 0;
  this.draw = function (ctx) {
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle = 'rgba(255, 255, 255, .7)';
    ctx.arc(this.x, this.y, 3, 0, Math.PI * 2, false);
    ctx.fill();
    ctx.restore();
  };
}
var dot = new Dot(),
    dotSpeed = 0.03, //控制动点的速度
    angle = 0, //这个很关键,用来得到动点的坐标x, y
    credit = 400; //信用最低分数

如何得到dot的坐标x, y呢?那就要用到传说中三角函数了。

通过上图我们可以得到

x = r * cos(angle), y = r * sin(angle)

在JavaScript中,dot的中心坐标就变成了:

dot.x = radius * Math.cos(angle); //radius为最外层轨道的半径值
dot.y = radius * Math.sin(angle);

接下来我们只要得到这个angle。这个通过弧度与分数的比例关系就可以得到:

var aim = (score - 400) * deg1 / 100;
if (angle < aim) {
  angle += dotSpeed;
}
dot.draw(ctx);

然后让中间的信用分数也能随动点的转动而变化,创建一个text(),为了使数字变化能和动点保持一致,要根据动点的速率来计算数字变化:

function text(process) {
  ctx.save();
  ctx.rotate(10 * deg0);
  ctx.fillStyle = '#000';
  ctx.font = '80px Microsoft yahei';
  ctx.textAlign = 'center';
  ctx.textBaseLine = 'top';
  ctx.fillText(process, 0 ,10);
  ctx.restore();
}
var textSpeed = Math.round(dotSpeed * 100 / deg1),
if (credit < score - textSpeed) {
  credit += textSpeed;
} else if (credit >= score - textSpeed && credit < score) {
  credit += 1; // 这里确保信用分数最后停下来是我们输入的分数
}
text(credit);

最后这一切都逃不过让window.requestAnimationFrame()来控制绘制动画和用ctx.clearRect(0, 0, cWidth, cHeight)来清除画布。

写的不好,大家将就着看,我相信大家理解代码的能力一定强于理解我这些我自己都不知道说什么的文字。

好了,以上。

 

code:

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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
<!DOCTYPE html>
<html>
 
    <head>
        <meta charset="UTF-8">
        <title>芝麻信用仪表盘</title>
        <style type="text/css">
            html,
            body {
                width: 100%;
                height: 100%;
                margin: 0;
            }
             
            canvas {
                border: 1px solid #eee;
                position: relative;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                background: -webkit-linear-gradient(top, #0e83f5 0%, #21bdf6 100%);
                background: -ms-linear-gradient(top, #0e83f5 0%, #21bdf6 100%);
                background: -moz-linear-gradient(top, #0e83f5 0%, #21bdf6 100%);
                background: linear-gradient(top, #0e83f5 0%, #21bdf6 100%);
            }
        </style>
        <script type="text/javascript">
            window.onload = function() {
                var canvas = document.getElementById('canvas'),
                    ctx = canvas.getContext('2d'),
                    cWidth = canvas.width,
                    cHeight = canvas.height,
                    score = canvas.attributes['data-score'].value,
                    stage = ['较差', '中等', '良好', '优秀', '极好'],
                    radius = 150,
                    deg0 = Math.PI / 9,
                    deg1 = Math.PI * 11 / 45;
 
                if(score < 400 || score > 900) {
                    alert('信用分数区间:400~900');
                } else {
                    var dot = new Dot(),
                        dotSpeed = 0.03,
                        textSpeed = Math.round(dotSpeed * 100 / deg1),
                        angle = 0,
                        credit = 400;
 
                    (function drawFrame() {
 
                        ctx.save();
                        ctx.clearRect(0, 0, cWidth, cHeight);
                        ctx.translate(cWidth / 2, cHeight / 2);
                        ctx.rotate(8 * deg0);
 
                        dot.x = radius * Math.cos(angle);
                        dot.y = radius * Math.sin(angle);
 
                        var aim = (score - 400) * deg1 / 100;
                        if(angle < aim) {
                            angle += dotSpeed;
                        }
                        dot.draw(ctx);
 
                        if(credit < score - textSpeed) {
                            credit += textSpeed;
                        } else if(credit >= score - textSpeed && credit < score) {
                            credit += 1;
                        }
                        text(credit);
 
                        ctx.save();
                        ctx.beginPath();
                        ctx.lineWidth = 3;
                        ctx.strokeStyle = 'rgba(255, 255, 255, .5)';
                        ctx.arc(0, 0, radius, 0, angle, false);
                        ctx.stroke();
                        ctx.restore();
 
                        window.requestAnimationFrame(drawFrame);
 
                        ctx.save(); //中间刻度层
                        ctx.beginPath();
                        ctx.strokeStyle = 'rgba(255, 255, 255, .2)';
                        ctx.lineWidth = 10;
                        ctx.arc(0, 0, 135, 0, 11 * deg0, false);
                        ctx.stroke();
                        ctx.restore();
 
                        ctx.save(); // 刻度线
                        for(var i = 0; i < 6; i++) {
                            ctx.beginPath();
                            ctx.lineWidth = 2;
                            ctx.strokeStyle = 'rgba(255, 255, 255, .3)';
                            ctx.moveTo(140, 0);
                            ctx.lineTo(130, 0);
                            ctx.stroke();
                            ctx.rotate(deg1);
                        }
                        ctx.restore();
 
                        ctx.save(); // 细分刻度线
                        for(i = 0; i < 25; i++) {
                            if(i % 5 !== 0) {
                                ctx.beginPath();
                                ctx.lineWidth = 2;
                                ctx.strokeStyle = 'rgba(255, 255, 255, .1)';
                                ctx.moveTo(140, 0);
                                ctx.lineTo(133, 0);
                                ctx.stroke();
                            }
                            ctx.rotate(deg1 / 5);
                        }
                        ctx.restore();
 
                        ctx.save(); //信用分数
                        ctx.rotate(Math.PI / 2);
                        for(i = 0; i < 6; i++) {
                            ctx.fillStyle = 'rgba(255, 255, 255, .4)';
                            ctx.font = '10px Microsoft yahei';
                            ctx.textAlign = 'center';
                            ctx.fillText(400 + 100 * i, 0, -115);
                            ctx.rotate(deg1);
                        }
                        ctx.restore();
 
                        ctx.save(); //分数段
                        ctx.rotate(Math.PI / 2 + deg0);
                        for(i = 0; i < 5; i++) {
                            ctx.fillStyle = 'rgba(255, 255, 255, .4)';
                            ctx.font = '10px Microsoft yahei';
                            ctx.textAlign = 'center';
                            ctx.fillText(stage[i], 5, -115);
                            ctx.rotate(deg1);
                        }
                        ctx.restore();
 
                        ctx.save(); //信用阶段及评估时间文字
                        ctx.rotate(10 * deg0);
                        ctx.fillStyle = '#fff';
                        ctx.font = '28px Microsoft yahei';
                        ctx.textAlign = 'center';
                        if(score < 500) {
                            ctx.fillText('信用较差', 0, 40);
                        } else if(score < 600 && score >= 500) {
                            ctx.fillText('信用中等', 0, 40);
                        } else if(score < 700 && score >= 600) {
                            ctx.fillText('信用良好', 0, 40);
                        } else if(score < 800 && score >= 700) {
                            ctx.fillText('信用优秀', 0, 40);
                        } else if(score <= 900 && score >= 800) {
                            ctx.fillText('信用极好', 0, 40);
                        }
 
                        ctx.fillStyle = '#80cbfa';
                        ctx.font = '14px Microsoft yahei';
                        ctx.fillText('评估时间:2016.11.06', 0, 60);
 
                        ctx.fillStyle = '#7ec5f9';
                        ctx.font = '14px Microsoft yahei';
                        ctx.fillText('BETA', 0, -60);
                        ctx.restore();
 
                        // ctx.save(); //最外层轨道
                        ctx.beginPath();
                        ctx.strokeStyle = 'rgba(255, 255, 255, .4)';
                        ctx.lineWidth = 3;
                        ctx.arc(0, 0, radius, 0, 11 * deg0, false);
                        ctx.stroke();
                        ctx.restore();
 
                    })();
                }
 
                function Dot() {
                    this.x = 0;
                    this.y = 0;
                    this.draw = function(ctx) {
                        ctx.save();
                        ctx.beginPath();
                        ctx.fillStyle = 'rgba(255, 255, 255, .7)';
                        ctx.arc(this.x, this.y, 3, 0, Math.PI * 2, false);
                        ctx.fill();
                        ctx.restore();
                    };
                }
 
                function text(process) {
                    ctx.save();
                    ctx.rotate(10 * deg0);
                    ctx.fillStyle = '#000';
                    ctx.font = '80px Microsoft yahei';
                    ctx.textAlign = 'center';
                    ctx.textBaseLine = 'top';
                    ctx.fillText(process, 0, 10);
                    ctx.restore();
                }
            };
        </script>
    </head>
 
    <body>
        <canvas id="canvas" width="400" height="700" data-score='724'></canvas>
    </body>
 
</html>

 canvas仪表盘

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
<!DOCTYPE html>
<html>
 
    <head>
        <meta charset="UTF-8">
        <title>芝麻信用仪表盘</title>
        <style type="text/css">
            html,
            body {
                width: 100%;
                height: 100%;
                margin: 0;
            }
             
            canvas {
                border: 1px solid red;
                position: relative;
                left: 50%;
                top: 50%;
                transform: translate(-50%, -50%);
                background: -webkit-linear-gradient(top, #0e83f5 0%, #21bdf6 100%);
                  
              
            }
        </style>
        <script type="text/javascript">
            window.onload = function() {
                var canvas = document.getElementById('canvas'),
                    ctx = canvas.getContext('2d'),
                    cWidth = canvas.width,
                    cHeight = canvas.height,
                    score = canvas.attributes['data-score'].value,
                    radius = 100, //圆的半径
                    deg0 = Math.PI / 9, //0.3490658503988659
                    deg1 = Math.PI * 11 / 45; //0.767944870877505
 
                if(score < 2000 || score > 10000) {
                    console.log('信用分数区间:2000~10000');
                      
                } else {
 
                    var dotSpeed = 0.1,
                        angle = 0,
                        credit = 2000;  //默认值开始数
 
                    (function drawFrame() {
 
                        ctx.save();
                        ctx.clearRect(0, 0, cWidth, cHeight);
                        ctx.translate(cWidth / 2, cHeight / 2);
                        ctx.rotate(8 * deg0);
 
                        var aim = (score - 2000) * deg1 / 1600;
                        if(angle < aim) {
                            angle += dotSpeed;
                        }
 
                        if(credit < score) {
                            credit += 200;
                        } else if(credit >= 10000) {
                            credit = 10000;
                        }
                        text(credit);
 
                        ctx.save();
                        ctx.beginPath();
                        ctx.lineWidth = 5;
                        ctx.strokeStyle = 'rgba(255, 255, 255, 1)';
                        ctx.arc(0, 0, radius, 0, angle, false);
                        ctx.stroke();
                        ctx.restore();
                        ctx.save(); 
                        ctx.rotate(10 * deg0);
                        ctx.restore();
                        ctx.beginPath();
                        ctx.strokeStyle = 'rgba(255, 0, 0, .1)';
                        ctx.lineWidth = 5;
                        ctx.arc(0, 0, radius, 0, 11 * deg0, false);   //设置外圆环
                        ctx.stroke();
                        ctx.restore();
 
                        window.requestAnimationFrame(drawFrame);
                    })();
                }
 
                function text(process) {
                    ctx.save();
                    ctx.rotate(10 * deg0);
                    ctx.fillStyle = 'red';
                    ctx.font = '40px Microsoft yahei';
                    ctx.textAlign = 'center';
                    ctx.textBaseLine = 'top';
                    ctx.fillText(process, 0, 10);
                    ctx.restore();
                }
            };
        </script>
    </head>
 
    <body>
        <canvas id="canvas" width="400" height="500" data-score='10000'></canvas>
    </body>
 
</html>

  

 自动打手CSS零时打造一个:

canvas仪表盘

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
<!DOCTYPE html>
<html>
 
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            #yibiao {
                width: 400px;
                height: 200px;
                background: white;
                margin: 0 auto;
                position: relative;
                overflow: hidden;
            }
             
            .yuan1 {
                width: 400px;
                height: 400px;
                position: absolute;
                top: 0px;
                left: 0px;
                border-radius: 50%;
                background: black;
                opacity: 0.2;
            }
             
            .yuan2 {
                width: 360px;
                height: 360px;
                position: absolute;
                top: 20px;
                left: 20px;
                border-radius: 50%;
                background: white;
            }
             
            .clip {
                width: 400px;
                height: 400px;
                position: absolute;
                top: 0px;
                left: 0px;
                border-radius: 50%;
                background: blue;
                clip: rect(200px, 400px, 400px, 0px);
                transform: rotate(0deg);
            }
             
            .num {
                position: absolute;
                width: 100%;
                height: 100px;
                top: 100px;
                text-align: center;
                font-size: 100px;
            }
        </style>
        <script type="text/javascript" src="js/jquery-3.1.1.js"></script>
        <script type="text/javascript">
            $(function() {
                //默认数字0--10000,默认数字自增步长100
                var buchang = 200;
                var deg = 180 * buchang / 10000; //每个步长代表的度数
                var degs = parseInt($(".num").text()) / buchang * deg; //先计算有几个步长,算出半圆要转的度数
                var du = 0; //起始度数
                var bu = 0; //数字自增步长
                function zhuan() {
 
                    $(".clip").css("transform", "rotate(" + du + "deg)");
                    $(".num").text(bu);
                    du += deg;
 
                     
                    bu += buchang;
                    if(du >= degs) {
                         clearInterval(setin);
                    }
 
                }
                var setin = setInterval(zhuan, 30)
 
            })
        </script>
    </head>
 
    <body>
        <div id="yibiao">
            <div class="yuan1"></div>
            <div class="clip"></div>
            <div class="yuan2"></div>
            <div class="num">5000</div>
        </div>
    </body>
 
</html>