演示地址(頁面中最下面可以直接下載js檔案)
需求:
- 最大值12,單位1
- 漸變
- 不用數字刻度
- 有根長指標
觀察設計圖,圖表需要控制的變數如下:
{放哪個元素上,當前資料,最大值,大標題,下面的文字,是否需要動畫,每個刻度間要多少條線}
手把手教程,有canvas基礎建議直接看演示裡的程式碼
衝!
準備工作
新建gauge.js放這個圖表的程式碼
// gauge.js
function Gauge(c) {
this.el = c.el // canvas的id
this.maxLineNums = c.maxLineNums // 最大刻度
this.unitLineNums = c.unitLineNums // 單位刻度內線條數
this.data = c.data // 資料
this.title = c.title // 大標題
this.textBottom = c.textBottom // 下面的文字
this.isAnimation = c.isAnimation //是否有動畫,預設關閉
}
複製程式碼
建立一個圖表,canvas高寬建議在標籤內指定 引入js檔案,建立一個例項
// gauge.html
<canvas id="aaa" width="200px" height="200px"></canvas>
<script src="../src/gauge/gauge.js"></script>
<script>
let canvas1 = new Gauge({
el: 'aaa', //canvas的id(在標籤內指定高寬)
maxLineNums: 24, //最大刻度
unitLineNums: 5, //單位刻度內線條數
data: 24, //資料
title: '標題1',
textBottom: '下面的文字a',
isAnimation: true //關閉動畫,預設關閉
})
</script>
複製程式碼
html裡的操作就完事了,回到gauge.js
建立一個canvas
- 獲取元素尺寸,設定文字居中
- 設定動畫指標、開始的角度、圓的半徑
- 生成漸變色陣列,漸變演算法
// function Gauge(c)內部
let canvas = document.getElementById(this.el),
ctx = canvas.getContext('2d'),
cWidth = canvas.width,
cHeight = canvas.height;
ctx.textAlign = "center"
let animationData = 0 //動畫,當前指標的資料
let startAngel = 0.75 * Math.PI // 儀表盤是個圓弧,設定1.5pi。
let r = 0.4 * canvas.width
// 半徑將隨著canvas的尺寸而變化
//生成漸變色組,可更改前兩個引數改變顏色
let gradientColor = gradientColors('#17deea', '#e97f03', this.maxLineNums + 1)
複製程式碼
開始畫畫
將繪畫過程放在函式裡,方便做動畫
在這個方法裡,animationData
是此時畫布的data,通過迴圈0->data達到動畫效果
let draw = () => {
// 動畫就是每一幀畫布重繪,所以要先清除畫布
ctx.clearRect(-200, -200, 400, 400);
ctx.beginPath();
// 1.畫標題和文字
// 2.畫刻度線
}
// 3.判斷動畫是否執行
// 4.畫單位刻度內的小線
複製程式碼
1.畫標題和文字
if (this.title !== undefined) {
ctx.fillStyle = '#777';
ctx.font = "18px serif";
ctx.fillText(this.title, cWidth / 2, cHeight * 0.4); //大標題的顏色、字號、位置
}
if (this.textBottom !== undefined) {
ctx.font = "12px serif";
ctx.fillText(this.textBottom, cWidth / 2, cHeight * 0.8); //底部文字的顏色(與大標題一致,可自行增加)、字號、位置
}
ctx.font = "28px serif";
ctx.fillStyle = gradientColor[animationData]; //中間資料的顏色(與指標顏色一致,可自行更改)
ctx.fillText(animationData, cWidth / 2, cHeight * 0.55); //中間資料的位置
複製程式碼
2.畫刻度線
通過迴圈畫出單位刻度線。有兩種方法畫出圓:旋轉畫布、用三角函式算出線條的起點和終點。第一種在動畫時因為要不停迴圈會導致我角度算不出來,所以採用三角函式,推薦第二種。畫線的步驟如下:
- 計算當前刻度的角度 = 開始角度 + (第幾根線)*(1.5pi/刻度數)
- 計算上一個單位刻度的角度
- 線條顏色通過
gradientColor()
漸變演算法得到,若當前刻度>animationData,顏色是#ccc - 迴圈繪製上一個刻度到當前刻度內的線(條數this.unitLineNums)
- 將上一個刻度傳入
drawSmallLine(previousAngle)
- 計算刻度
- 將上一個刻度傳入
- 設定單位刻度的線寬
- 移動畫筆起點
- 畫單位刻度線,如果當前刻度===animationData,那這條線要長一點,通過
let rL = 1.2 * r
調整長度
for (let i = 1; i <= this.maxLineNums; i++) {
let currentAngle = startAngel + i * (1.5 * Math.PI / this.maxLineNums)
let previousAngle = startAngel + (i - 1) * (1.5 * Math.PI / this.maxLineNums)
for (let j = 0; j < this.unitLineNums; j++) {
ctx.strokeStyle = (i > animationData) ? '#cccccc' : gradientColor[i];
drawSmallLine(previousAngle)
previousAngle = previousAngle + 1.5 * Math.PI / (this.maxLineNums * this.unitLineNums)
}
ctx.save();
ctx.beginPath();
ctx.lineWidth = 1; //單位刻度線條寬度,推薦1~2
ctx.strokeStyle = (i > animationData) ? '#cccccc' : gradientColor[i];
ctx.moveTo(Math.cos(currentAngle) * r * 0.75 + cWidth / 2, Math.sin(currentAngle) * r * 0.75 + cHeight / 2);
if (i === animationData) {
let rL = 1.2 * r
ctx.lineTo(Math.cos(currentAngle) * rL + cWidth / 2, Math.sin(currentAngle) * rL + cHeight / 2);
} else {
ctx.lineTo(Math.cos(currentAngle) * r + cWidth / 2, Math.sin(currentAngle) * r + cHeight / 2);
}
ctx.stroke();
}
複製程式碼
3.判斷動畫是否執行
- 如果
isAnimation
真,動畫執行,將呼叫requestAnimationFrame(animation)
,這個api可以順滑執行動畫,但是時間不可控制,也可以用別的方法在這裡寫一個迴圈替代 - 如果不執行動畫,
animationData = this.data
,只繪製一次
if (this.isAnimation) {
let animation = () => {
draw()
animationData++
if (animationData <= c.data) {
requestAnimationFrame(animation)
}
}
requestAnimationFrame(animation);
} else {
animationData = this.data
draw()
}
複製程式碼
4.畫單位刻度內的小線function drawSmallLine
function drawSmallLine(currentAngle) {
ctx.save();
ctx.beginPath();
ctx.lineWidth = 1; //單位刻度內線條寬度,推薦1~2
ctx.moveTo(Math.cos(currentAngle) * r * 0.75 + cWidth / 2, Math.sin(currentAngle) * r * 0.75 + cHeight / 2);
ctx.lineTo(Math.cos(currentAngle) * r + cWidth / 2, Math.sin(currentAngle) * r + cHeight / 2);
ctx.stroke();
}
複製程式碼
漸變色演算法
這個演算法是cv來的,但是忘了在哪複製的了,先謝謝這位大佬
let gradientColors = function (start, end, steps, gamma) {
// 顏色漸變演算法
// convert #hex notation to rgb array
let parseColor = function (hexStr) {
return hexStr.length === 4 ? hexStr.substr(1).split('').map(function (s) {
return 0x11 * parseInt(s, 16);
}) : [hexStr.substr(1, 2), hexStr.substr(3, 2), hexStr.substr(5, 2)].map(function (s) {
return parseInt(s, 16);
})
};
// zero-pad 1 digit to 2
let pad = function (s) {
return (s.length === 1) ? '0' + s : s;
};
let i, j, ms, me, output = [],
so = [];
gamma = gamma || 1;
let normalize = function (channel) {
return Math.pow(channel / 255, gamma);
};
start = parseColor(start).map(normalize);
end = parseColor(end).map(normalize);
for (i = 0; i < steps; i++) {
ms = i / (steps - 1);
me = 1 - ms;
for (j = 0; j < 3; j++) {
so[j] = pad(Math.round(Math.pow(start[j] * me + end[j] * ms, 1 / gamma) * 255).toString(16));
}
output.push('#' + so.join(''));
}
return output;
};
複製程式碼
完事