第一次接触canvas,<canvas></canvas>是html5出现的新标签,IE9开始支持,像所有的dom对象一样它有自己本身的属性、方法和事件,其中就有绘图的方法,js能够调用它来进行绘图。使用的领域涉及到小游戏的开发以及数据的可视化。
一、两个概念:
1、 绘图环境的含义:
绘制图形所需的东西,就叫绘图环境。
2、 路径的含义:
路径是一个图形的轮廓,是对将来要绘制图形的一个规划。如果要让路径最终生效需要描边。
二、使用canvas绘制图形
canvas要展示的绘制效果是通过js来绘制的。
1、准备工作:
(1)需要获取一个绘图环境:
通过canvasDOM元素提供的一个方法,getContext。
var cvs = document.querySelector('canvas');
(2)获取绘图环境的方法:
canvas.getContext( '2d' || 'webgl' ) 传入2d代表获取一个2d绘图环境,传入3d代表获取一个3d绘图环境。
var ctx = cvs.getContext('2d');
2、绘制图形
(1)先移动钢笔到指定的位置
ctx.moveTo( 0, 0 );
(2)画图形的路径( 绘制矩形 )
ctx.lineTo(100, 0);
ctx.lineTo(100, 100);
ctx.lineTo(0, 100);
ctx.lineTo(0, 0);
(3)描边
ctx.stroke();
三、画布设置
1、 画布的默认设置:
canvas画布默认大小为300*150。
2、 动态设置画布的大小:
(1)canvasDOM对象有一个width和height属性,通过修改这两个属性值,就可以动态设置画布的大小。因为canvas绘制的图形是位图(像素图),默认就是基于像素单位的(px),所以不用加单位。
例:cvs.width = 500;
cvs.height = 500;
(2)动态设置画布大小会清除画布的内容,绘制图形之后,动态修改画布的大小,绘自动清除画布已经绘图的内容。
(3)不要设置canvas样式中的宽高,因为会拉伸画布,原理和img设置样式宽高拉伸图片一样。直接设置<canvas width=" " height=" "></canvas>
四、基于状态
canvas绘图环境中的很多方法也是基于状态的,即修改了canvas绘图环境中的某些属性值, 相关连的一些方法最终的执行结果可能就会收到改变。
案例:
function Person( name, age ) { this.name = name; this.age = age; } // 这个方法就是基于状态(属性值)的 Person.prototype.run = function() { if( this.age < 5 ) { console.log('爬着跑'); }else if( this.age >=5 && this.age < 16 ) { console.log('跳着跑'); }else if( this.age >=16 && this.age < 48 ) { console.log('慢跑'); }else { console.log('拄着拐杖跑'); } }; var xiaofang = new Person('小芳', 12); var fangma = new Person('小芳妈', 37); var fangnai = new Person('小芳奶', 60); xiaofang.run(); fangma.run(); fangnai.run();
五、描边色、填充、填充色及设置线宽
1、 设置描边色
(1) ctx.strokeStyle = css支持的所有颜色表示方式,这里都支持。
当修改了这个描边色之后,一些和描边相关的方法,再次调用时都会受到影响。
(2)设置 描边色后要调动stroke()方法,新的图形才会显示。
(3)多次绘制图形描边会把前面的覆盖。
(4)解决上面出现的问题:清除路径
ctx.beginPath();
2、填充 填充色
(1)填充:ctx.fill();
(2)设置填充色:ctx.fillStyle=css
3、设置线宽
(1)ctx.lineWidth=number; 注意:不用加"px"
(2)设置线宽后,顶端会出现锯齿,防止锯齿出现:闭合路径。
绘制完路径后,调用closePath()方法,有了这个方法后,最后一条路径可以不用绘制了。
六、 非零环绕原则
1、作用:
用来判断路径围起来的图形,是在路径内,还是路径外。
2、原理:
(1) 在路径包含的区域内随便找一点,向外发送一条线,让这条线贯穿所有围绕该点的路径即可
(2)开始一个计数器,初始值为0
(3)开始计数,遇到顺时针围绕点的路径,数+1,遇到逆时针围绕点的路径,数-1
(4)最终看看结果,如果不是0,那么认为这个块区域在路径包含之内
七、 清除画布
ctx.clearRect(起点x轴坐标,起点y轴坐标,清除区域的宽,清除区域的高);
清除整个画布: ctx.clearRect(0 , 0 , cvs.width , cvs.height);
八、小属性及虚线绘制介绍
1、 线帽样式:
ctx.lineCap = 'butt' || 'round' || 'square' 默认值为butt
round用来设置圆头(圆的半径是线宽的一半),square是两段个加长线宽的一半。
2、 焦点样式:
ctx.lineJoin = 'miter' || 'round' || 'bevel' 默认值为miter
设置箭头长度,该属性只在lineJoin为miter的时候有效:ctx.miterLimit
3、 虚线
(1)设置虚线样式:
ctx.setLineDash([ ]); 注意:数组的长度是任意的,现实后空;可以传入一个参数,此时实空的长度一样。
(2)获取虚线样式:
ctx.getLineDash();
九、 画弧路径:
ctx.arc( 圆心x轴坐标,圆心y轴坐标,半径,弧的起始位置,弧的结束位置,是否逆时针画(可选) ) 默认是顺时针画弧。
案例:
<canvas style="border: 1px solid red" width="500" height="500"></canvas>
<script>
var cvs = document.querySelector('canvas');
var ctx = cvs.getContext('2d');
// 角度转换为弧度
function angleToRadian( angle ) {
return Math.PI / 180 * angle;
}
// 顺时针从0度到90度画弧
ctx.arc( 100, 100, 50, angleToRadian(0), angleToRadian(90) );
ctx.stroke();
// 逆时针从0度到90度画弧
ctx.beginPath();
ctx.arc( 300, 100, 50, angleToRadian(0), angleToRadian(90), true );
ctx.stroke();
十、 封装
1、 面向对象封装等腰三角形
// 绘制等腰三角形的方法 function triangle( x, y, width, height, strokeStyle ) { /* * 实现思路: * 1、为了防止重绘之前的路径,先清除一下 * 2、移动钢笔到图形起点 * 3、画想要图形的路径 * 4、设置描边色 * 5、描边 * */ ctx.beginPath(); ctx.moveTo( x, y ); ctx.lineTo( x + width / 2, y + height ); ctx.lineTo( x - width / 2, y + height ); ctx.lineTo( x, y ); ctx.strokeStyle = strokeStyle; ctx.stroke(); } triangle( 100, 100, 100, 50, 'green' );
2、 面向对象封装
// 等腰三角形的构造函数 function Triangle( x, y, width, height, strokeStyle ) { this.x = x; this.y = y; this.width = width; this.height = height; this.strokeStyle = strokeStyle; } // 根据实例的属性绘制 Triangle.prototype.draw = function() { /* * 实现思路: * 1、为了防止重绘之前的路径,先清除一下 * 2、移动钢笔到图形起点 * 3、画想要图形的路径 * 4、设置描边色 * 5、描边 * */ ctx.beginPath(); ctx.moveTo( this.x, this.y ); ctx.lineTo( this.x + this.width / 2, this.y + this.height ); ctx.lineTo( this.x - this.width / 2, this.y + this.height ); ctx.lineTo( this.x, this.y ); ctx.strokeStyle = this.strokeStyle; ctx.stroke(); }; var triangle1 = new SanJiaoXing( 100, 100, 100, 50, 'green' ); triangle1.draw();
十一、 绘制坐标系
/* * constructor { LineChart } 折线图构造函数 * param { ctx: Context } 绘图上下文 * param { paddingArr: Array } 折线图到画布四边的距离,存储顺序为上右下左 * param { arrowArr: Array } 折线图中箭头的宽和高 * */ function LineChart( ctx, paddingArr, arrowArr ) { this.ctx = ctx; this.paddingArr = paddingArr; this.arrowArr = arrowArr; this.arrowWidth = this.arrowArr[0]; this.arrowHeight = this.arrowArr[1]; // 计算上顶点的坐标 this.vertexTop = { x: this.paddingArr[ 3 ], y: this.paddingArr[ 0 ] }; // 计算原点的坐标 this.origin = { x: this.paddingArr[ 3 ], y: this.ctx.canvas.height - this.paddingArr[ 2 ] }; // 计算右顶点的坐标 this.vertexRight = { x: this.ctx.canvas.width - this.paddingArr[ 1 ], y: this.ctx.canvas.height - this.paddingArr[ 2 ] }; } // 置换原型 LineChart.prototype = { constructor: LineChart, // 绘制坐标轴中的两条线 drawLine: function() { this.ctx.beginPath(); this.ctx.moveTo( this.vertexTop.x, this.vertexTop.y ); this.ctx.lineTo( this.origin.x, this.origin.y ); this.ctx.lineTo( this.vertexRight.x, this.vertexRight.y ); this.ctx.stroke(); }, // 绘制坐标轴中的两个箭头 drawArrow: function() { // 先绘制上面箭头 this.ctx.beginPath(); this.ctx.moveTo( this.vertexTop.x, this.vertexTop.y ); this.ctx.lineTo( this.vertexTop.x - this.arrowWidth / 2, this.vertexTop.y + this.arrowHeight ); this.ctx.lineTo( this.vertexTop.x, this.vertexTop.y + this.arrowHeight / 2 ); this.ctx.lineTo( this.vertexTop.x + this.arrowWidth / 2, this.vertexTop.y + this.arrowHeight ); this.ctx.closePath(); this.ctx.stroke(); // 再绘制右面箭头 this.ctx.beginPath(); this.ctx.moveTo( this.vertexRight.x, this.vertexRight.y ); this.ctx.lineTo( this.vertexRight.x - this.arrowHeight, this.vertexRight.y - this.arrowWidth / 2 ); this.ctx.lineTo( this.vertexRight.x - this.arrowHeight / 2, this.vertexRight.y ); this.ctx.lineTo( this.vertexRight.x - this.arrowHeight, this.vertexRight.y + this.arrowWidth / 2 ); this.ctx.closePath(); this.ctx.stroke(); } }; var lineChart = new LineChart( ctx, [ 20, 20, 20, 20 ], [ 10, 20 ] ); lineChart.drawLine(); lineChart.drawArrow();
十二、 绘制折线图
/* * constructor { LineChart } 折线图构造函数 * param { ctx: Context } 绘图上下文 * param { paddingArr: Array } 折线图到画布四边的距离,存储顺序为上右下左 * param { arrowArr: Array } 折线图中箭头的宽和高 * param { data: Array } 存储了折线图中所需的数据 * */ function LineChart( ctx, paddingArr, arrowArr, data ) { this.ctx = ctx; this.paddingArr = paddingArr; this.arrowArr = arrowArr; this.arrowWidth = this.arrowArr[0]; this.arrowHeight = this.arrowArr[1]; this.data = data; // 计算上顶点的坐标 this.vertexTop = { x: this.paddingArr[ 3 ], y: this.paddingArr[ 0 ] }; // 计算原点的坐标 this.origin = { x: this.paddingArr[ 3 ], y: this.ctx.canvas.height - this.paddingArr[ 2 ] }; // 计算右顶点的坐标 this.vertexRight = { x: this.ctx.canvas.width - this.paddingArr[ 1 ], y: this.ctx.canvas.height - this.paddingArr[ 2 ] }; // 根据数据得到对应的坐标 this.processData(); } // 置换原型 LineChart.prototype = { constructor: LineChart, // 绘制折线图 draw: function() { this.drawCoordinate(); this.drawArrow(); this.drawPoint(); this.drawLine(); }, // 绘制坐标轴中的两条线 drawCoordinate: function() { this.ctx.beginPath(); this.ctx.moveTo( this.vertexTop.x, this.vertexTop.y ); this.ctx.lineTo( this.origin.x, this.origin.y ); this.ctx.lineTo( this.vertexRight.x, this.vertexRight.y ); this.ctx.stroke(); }, // 绘制坐标轴中的两个箭头 drawArrow: function() { // 先绘制上面箭头 this.ctx.beginPath(); this.ctx.moveTo( this.vertexTop.x, this.vertexTop.y ); this.ctx.lineTo( this.vertexTop.x - this.arrowWidth / 2, this.vertexTop.y + this.arrowHeight ); this.ctx.lineTo( this.vertexTop.x, this.vertexTop.y + this.arrowHeight / 2 ); this.ctx.lineTo( this.vertexTop.x + this.arrowWidth / 2, this.vertexTop.y + this.arrowHeight ); this.ctx.closePath(); this.ctx.stroke(); // 再绘制右面箭头 this.ctx.beginPath(); this.ctx.moveTo( this.vertexRight.x, this.vertexRight.y ); this.ctx.lineTo( this.vertexRight.x - this.arrowHeight, this.vertexRight.y - this.arrowWidth / 2 ); this.ctx.lineTo( this.vertexRight.x - this.arrowHeight / 2, this.vertexRight.y ); this.ctx.lineTo( this.vertexRight.x - this.arrowHeight, this.vertexRight.y + this.arrowWidth / 2 ); this.ctx.closePath(); this.ctx.stroke(); }, // 把传入进来的数据转化为对应画布的坐标 processData: function() { // 用来存储转换后的坐标数据 this.processArr = []; // 遍历所有的数据,依次转换为对应的坐标 for( var i = 0, len = this.data.length; i < len; i+=2 ) { /* * 数据转化为相当于画布的坐标: * canvasX = this.origin.x + x * canvasY = this.origin.y - y * */ this.processArr.push( this.origin.x + this.data[ i ] ); this.processArr.push( this.origin.y - this.data[ i + 1 ] ); } }, // 根据数据绘制相应的点 drawPoint: function() { var r = 4; // 遍历所有的坐标,依次绘制点 for( var i = 0, len = this.processArr.length; i < len; i+=2 ) { this.ctx.beginPath(); this.ctx.arc( this.processArr[ i ], this.processArr[ i + 1 ], r, 0, Math.PI*2 ); this.ctx.fill(); } }, // 根据数据绘制折线 drawLine: function() { this.ctx.beginPath(); for( var i = 0, len = this.processArr.length; i < len; i+=2 ) { this.ctx.lineTo( this.processArr[ i ], this.processArr[ i + 1 ] ); } this.ctx.stroke(); } }; var lineChart = new LineChart( ctx, [ 20, 20, 20, 20 ], [ 10, 20 ], [ 10, 10, 30, 20, 50, 50, 60, 80, 100, 100 ] ); lineChart.draw();
总结: