演示地址(页面中最下面可以直接下载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;
};
完事