之前用过echarts
等图表插件,首次尝试自己写原生canvas
,还以为多难多复杂的东西,深入了解之后发现就是数学中的几何图形嘛~ 欢迎大家和我一起来入坑!
Canvas基本使用
<canvas>
元素
<canvas>
看起来和 <img>
标签一样,只是 <canvas>
只有两个可选的属性 width、heigth 属性,而没有 src、alt 属性。
如果不给 <canvas>
设置 widht、height 属性时,则默认 width为300、height 为 150,单位都是 px。也可以使用 css 属性来设置宽高,但是如宽高属性和初始比例不一致,他会出现扭曲。所以,建议永远不要使用 css 属性来设置 <canvas>
的宽高。
替换内容
由于某些较老的浏览器(尤其是 IE9 之前的 IE 浏览器)或者浏览器不支持 HTML 元素 <canvas>
,在这些浏览器上你应该总是能展示替代内容。
支持 <canvas>
的浏览器会只渲染 <canvas>
标签,而忽略其中的替代内容。不支持 <canvas>
的浏览器则 会直接渲染替代内容。
用文本替换:
<canvas>
你的浏览器不支持 canvas,请升级你的浏览器。
</canvas>
用 <img>
替换:
<canvas>
<img src="./美女.jpg" alt="">
</canvas>
结束标签 </canvas>
不可省略。
与 <img>
元素不同,<canvas>
元素需要结束标签(</canvas>
)。如果结束标签不存在,则文档的其余部分会被认为是替代内容,将不会显示出来。
2d渲染上下文
var canvas = document.getElementById('tutorial');
//获得 2d 上下文对象
var ctx = canvas.getContext('2d');
检测支持性
var canvas = document.getElementById('tutorial');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
// drawing code here
} else {
// canvas-unsupported code here
}
代码模板
<canvas id="tutorial" width="300" height="300"></canvas>
<script type="text/javascript">
function draw(){
var canvas = document.getElementById('tutorial');
if(!canvas.getContext) return;
var ctx = canvas.getContext("2d");
//开始代码
}
draw();
</script>
绘制形状
栅格 (grid) 和坐标空间
如下图所示,canvas 元素默认被网格所覆盖。通常来说网格中的一个单元相当于 canvas 元素中的一像素。栅格的起点为左上角,坐标为 (0,0) 。所有元素的位置都相对于原点来定位。所以图中蓝色方形左上角的坐标为距离左边(X 轴)x 像素,距离上边(Y 轴)y 像素,坐标为 (x,y)。
后面我们会涉及到坐标原点的平移、网格的旋转以及缩放等。
绘制矩形
<canvas>
只支持一种原生的图形绘制:矩形。所有其他图形都至少需要生成一种路径 (path)。不过,我们拥有众多路径生成的方法让复杂图形的绘制成为了可能。
canvas 提供了三种方法绘制矩形:
- fillRect(x, y, width, height):绘制一个填充的矩形。
- strokeRect(x, y, width, height):绘制一个矩形的边框。
- clearRect(x, y, widh, height):清除指定的矩形区域,然后这块区域会变的完全透明。
说明:这 3 个方法具有相同的参数。
- x, y:指的是矩形的左上角的坐标。(相对于canvas的坐标原点)
- width, height:指的是绘制的矩形的宽和高。
function draw(){
var canvas = document.getElementById('tutorial');
if(!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.fillRect(10, 10, 100, 50); // 绘制矩形,填充的默认颜色为黑色
ctx.strokeRect(10, 70, 100, 50); // 绘制矩形边框
}
draw();
ctx.clearRect(15, 15, 50, 25);
绘制路径 (path)
图形的基本元素是路径。
路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。
一个路径,甚至一个子路径,都是闭合的。
使用路径绘制图形需要一些额外的步骤:
- 创建路径起始点
- 调用绘制方法去绘制出路径
- 把路径封闭
- 一旦路径生成,通过描边或填充路径区域来渲染图形。
下面是需要用到的方法:
beginPath()
新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径moveTo(x, y)
把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。closePath()
闭合路径之后,图形绘制命令又重新指向到上下文中stroke()
通过线条来绘制图形轮廓fill()
通过填充路径的内容区域生成实心的图形
绘制线段
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath(); //新建一条path
ctx.moveTo(50, 50); //把画笔移动到指定的坐标
ctx.lineTo(200, 50); //绘制一条从当前位置到指定坐标(200, 50)的直线.
//闭合路径。会拉一条从当前点到path起始点的直线。如果当前点与起始点重合,则什么都不做
ctx.closePath();
ctx.stroke(); //绘制路径。
}
draw();
绘制三角形边框
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 200);
ctx.closePath(); //虽然我们只绘制了两条线段,但是closePath会closePath,仍然是一个3角形
ctx.stroke(); //描边。stroke不会自动closePath()
}
draw();
填充三角形
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 200);
ctx.fill(); //填充闭合区域。如果path没有闭合,则fill()会自动闭合路径。
}
draw();
绘制圆弧
有两个方法可以绘制圆弧:
- arc(x, y, r, startAngle, endAngle, anticlockwise): 以(x, y) 为圆心,以r 为半径,从 startAngle 弧度开始到endAngle弧度结束。anticlosewise 是布尔值,true 表示逆时针,false 表示顺时针(默认是顺时针)。
注意:
- 这里的度数都是弧度。
- 0 弧度是指的 x 轴正方向。
radians=(Math.PI/180)*degrees //角度转换成弧度
- arcTo(x1, y1, x2, y2, radius): 根据给定的控制点和半径画一段圆弧,最后再以直线连接两个控制点。
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(50, 50, 40, 0, Math.PI / 2, false);
ctx.stroke();
}
draw();
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(50, 50, 40, 0, Math.PI / 2, false);
ctx.stroke();
ctx.beginPath();
ctx.arc(150, 50, 40, 0, -Math.PI / 2, true);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(50, 150, 40, -Math.PI / 2, Math.PI / 2, false);
ctx.fill();
ctx.beginPath();
ctx.arc(150, 150, 40, 0, Math.PI, false);
ctx.fill();
}
draw();
function draw(){
var canvas = document.getElementById('tutorial');
if (!canvas.getContext) return;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(50, 50);
//参数1、2:控制点1坐标 参数3、4:控制点2坐标 参数4:圆弧半径
ctx.arcTo(200, 50, 200, 200, 100);
ctx.lineTo(200, 200)
ctx.stroke();
ctx.beginPath();
ctx.rect(50, 50, 10, 10);
ctx.rect(200, 50, 10, 10)
ctx.rect(200, 200, 10, 10)
ctx.fill()
}
draw();
arcTo 方法的说明:
这个方法可以这样理解。绘制的弧形是由两条切线所决定。
第 1 条切线:起始点和控制点1决定的直线。
第 2 条切线:控制点1 和控制点2决定的直线。
其实绘制的圆弧就是与这两条直线相切的圆弧。
canvas实战
环形饼图的实现
//html
<canvas id="circle-pei-chart"></canvas>
//调用
let chartDatas=[ {color: "rgb(253, 122, 79)",title: "后端开发",percent: 0.2}, **];
let defalutIndex=0
let circlePeiChart = new multiCircleChart("circle-pei-chart",chartDatas, defalutIndex,(i)=>{defalutIndex=i});
circlePeiChart.draw();
//重绘
circlePeiChart.defaultIndex=2;
circlePeiChart.draw();
/*
chartDatas [ {color: "rgb(253, 122, 79)",title: "后端开发",percent: 0.2}, **];
*/
class multiCircleChart {
constructor(id, chartDatas, defalutIndex,callback) {
this.canvas = document.getElementById(id);
this.size = this.canvas.parentNode.clientWidth * 4;
this.canvas.style.width = this.size / 4 + "px";
this.canvas.style.height = this.size / 4 + "px";
this.canvas.width = this.size;
this.canvas.height = this.size;
this.ctx = this.canvas.getContext("2d");
this.defalutIndex = defalutIndex;
this.chartDatas = chartDatas;
this.lineWidth = this.size/5;
this.startAngle = -0.5;
this.callback=callback;
this.canvas.addEventListener('click',this.mouseDownEvent.bind(this));
this.AngleList=[];
}
draw() {
this.ctx.clearRect(0,0,this.size,this.size);
if (this.chartDatas.length == 0) return;
this.ctx.lineWidth = this.lineWidth;
this.ctx.lineCap="butt";
let startAngle = Math.PI * -0.5;
let endAngle = startAngle;
this.AngleList=[];
this.chartDatas.map((item, i) => {
this.ctx.beginPath();
this.ctx.strokeStyle = item.color;
if (i > 0) {
startAngle = endAngle;
}
endAngle = startAngle + item.percent * Math.PI * 2;
this.AngleList.push(endAngle);
//选中当前项,需要向外偏移
if (i == this.defalutIndex) {
//选中当前项,需要向外偏移
let centerAngle=(startAngle+endAngle)/2;
let x=this.lineWidth*0.2*Math.cos(centerAngle);
let y=this.lineWidth*0.2*Math.sin(centerAngle);
this.ctx.arc(this.size / 2+x, this.size / 2+y, this.size / 2 - this.lineWidth / 2 - this.lineWidth * 0.2, startAngle, endAngle);
} else {
this.ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.lineWidth / 2 - this.lineWidth * 0.2, startAngle, endAngle);
}
this.ctx.stroke();
});
}
mouseDownEvent(e){
const [x1,y1]=[e.offsetX,e.offsetY];
const [x0,y0]=[this.size/2/4,this.size/2/4];
let angle=0;
if(x1>x0){
y1<y0?angle=-0.5*Math.PI+Math.atan((x1-x0)/(y0-y1)):angle=Math.atan((y1-y0)/(x1-x0));
}else{
y1<y0?angle=Math.PI+Math.atan((y0-y1)/(x0-x1)):angle=Math.atan((x0-x1)/(y1-y0))+Math.PI*0.5;
}
for(let i=0;i<this.AngleList.length;i++){
if(angle<this.AngleList[i]){
this.defalutIndex=i;
this.draw();
this.callback?this.callback(i):'';
break;
}
}
}
}
时钟的实现
init();
function init(){
let canvas = document.querySelector("#solar");
let ctx = canvas.getContext("2d");
draw(ctx);
}
function draw(ctx){
requestAnimationFrame(function step(){
drawDial(ctx); //绘制表盘
drawAllHands(ctx); //绘制时分秒针
requestAnimationFrame(step);
});
}
/*绘制时分秒针*/
function drawAllHands(ctx){
let time = new Date();
let s = time.getSeconds();
let m = time.getMinutes();
let h = time.getHours();
let pi = Math.PI;
let secondAngle = pi / 180 * 6 * s; //计算出来s针的弧度
let minuteAngle = pi / 180 * 6 * m + secondAngle / 60; //计算出来分针的弧度
let hourAngle = pi / 180 * 30 * h + minuteAngle / 12; //计算出来时针的弧度
drawHand(hourAngle, 60, 6, "red", ctx); //绘制时针
drawHand(minuteAngle, 106, 4, "green", ctx); //绘制分针
drawHand(secondAngle, 129, 2, "blue", ctx); //绘制秒针
}
/*绘制时针、或分针、或秒针
* 参数1:要绘制的针的角度
* 参数2:要绘制的针的长度
* 参数3:要绘制的针的宽度
* 参数4:要绘制的针的颜色
* 参数4:ctx
* */
function drawHand(angle, len, width, color, ctx){
ctx.save();
ctx.translate(150, 150); //把坐标轴的远点平移到原来的中心
ctx.rotate(-Math.PI / 2 + angle); //旋转坐标轴。 x轴就是针的角度
ctx.beginPath();
ctx.moveTo(-4, 0);
ctx.lineTo(len, 0); // 沿着x轴绘制针
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.lineCap = "round";
ctx.stroke();
ctx.closePath();
ctx.restore();
}
/*绘制表盘*/
function drawDial(ctx){
let pi = Math.PI;
ctx.clearRect(0, 0, 300, 300); //清除所有内容
ctx.save();
ctx.translate(150, 150); //一定坐标原点到原来的中心
ctx.beginPath();
ctx.arc(0, 0, 148, 0, 2 * pi); //绘制圆周
ctx.stroke();
ctx.closePath();
for (let i = 0; i < 60; i++){//绘制刻度。
ctx.save();
ctx.rotate(-pi / 2 + i * pi / 30); //旋转坐标轴。坐标轴x的正方形从 向上开始算起
ctx.beginPath();
ctx.moveTo(110, 0);
ctx.lineTo(140, 0);
ctx.lineWidth = i % 5 ? 2 : 4;
ctx.strokeStyle = i % 5 ? "blue" : "red";
ctx.stroke();
ctx.closePath();
ctx.restore();
}
ctx.restore();
}
学习canvas基础点它 -》 学习 HTML5 Canvas 这一篇文章就够了
做canvas实例点它 -》Canvas环形饼图与手势控制的实现代码