這是一個仿支付寶芝麻信用分的一個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:
<!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>

<!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零時打造一個:

<!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>
