一、canvas的介绍
canvas是html5出现的新标签,像所有的DOM对象一样它有自己本身 的属性、方法和事件,其中就有绘图的方法,js能够调用它来进行绘图。canvas只有两个属性,而且是可选的,width、height,这两个属性也可以通过 js 来定义。
canvas如果没有定义大小,则默认大小为宽 300px,高 150px。当然使用 css 也是可以设置 canvas 的大小,但如果css设置的宽高比例与 js 或在 标签的属性 width、height上设置的比例不一致,则会变形,而且如果css设置的值是它们的n倍,则图像也会放大n倍。
如下图第一个是长宽50的红色矩形,第二个是css设置为js设置的两倍是得出的图像,第三个是css设置宽高比为1.5:1时得出的图像且高是js设置的两倍。
canvas在某些较老的浏览器(如IE9以下)中并不支持,我们可以在canvas标签中写些内容替代当canvas标签不被支持的时候。也可以使用 getContext 来判断是否支持canvas。
<!-- 插入替换的图片或提示文本 -->
<canvas id="canvas" width="500" height="500"> <img src="images/canvas.png" width="500" height="500" alt=""/> // </canvas>
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
console.log('你的浏览器支持Canvas!');
} else {
console.log('你的浏览器不支持Canvas!');
}
canvas.getContext() 方法返回了一个相当于画笔的“东西”,通过它我们可以进行各种绘画。当然绘画时也会考虑是画2D的还是3D的,这个方法在传入参数后即可,如下:
var context = canvas.getContext("2d");
如果想画3D的该怎么办,这样:
var context = canvas.getContext("webgl");
本文只讨论2D的绘画的使用。
画图首先要有一个参考系,原点在哪,x、y轴的方向,单位是什么?
如下图,坐标轴在左上角,x轴为横向向右,y轴为垂直向下,所以画图的坐标都是正值,如果是负值,图像就超出画布看不见了。坐标轴单位为px。
二、canvas绘制矩形
画矩形首先要知道在哪里画,矩形的大小,边框的粗细,边框的颜色,是否是填充的这些要素。而我们的画笔的API可以做到这些。
context.strokeRect(10,10,50,50),绘制无填充的矩形,四个参数分别表示:x轴坐标,y轴坐标,长,宽。。
context.fillRect(10,10,50,50),绘制填充的矩形,默认填充黑色。
var canvas = document.getElementById("canvas");
canvas.width="500";
canvas.height="500";
//第一步必须获取一个"画笔"
var context = canvas.getContext("2d");
//画矩形
//设置边框宽度 单位px
context.lineWidth="6";
//设置颜色
context.strokeStyle="red";
context.strokeRect(10,10,50,50)
context.fillStyle = '#f00'
context.fillRect(10,10,20,20)
//清除画笔的轨迹 将所在区域的矩形内容清空可以理解为橡皮擦
context.clearRect(15,15,20,20)
画出图像如下,左上角白色的就是清除掉的部分。
如果你设置了边框宽度与颜色,但画出的图像没有,则是要在画之前进行设置,就是在context.strokeRect(10,10,100,100)之前定义,在其之后定义就只对后面画的有效了。
还有一个方法可以绘画矩形:这两个API要永远在一起,分开的就无法绘画了。
context.rect(10,10,100,100);
//绘制的形状
context.stroke()
三、canvas绘制圆和弧线
context.arc(150, 150, 100, 0, 2*Math.PI, false),绘制圆,六个参数分别是:圆心的x轴坐标,圆心的y轴坐标,半径,圆弧的起始弧度,圆的结束弧度,绘制的方向(默认为false,顺时针方向)。圆弧的起始弧度默认为水平方向直径与圆弧的右边交点处,如下图A点处,结束位置是顺时针方向的弧度结束点。360°是2Π,90°是1/2Π。
如果要绘制实心的圆则要用 context.fill() 表示绘制实心圆,这是你可以不写 context.stroke() 那么圆就没有边框。如果绘制的弧度小于2Π那绘制实心的时候出来的是一个月亮一样的图形。绘制图像如下图右边两张。故如果要绘制弧线则让所绘制的的弧度之差小于2Π即可。
context.arc(150,150,100,0,2*Math.PI,false);
context.strokeStyle="#c33"; //边框颜色
context.fillStyle="red"; //填充颜色
// context.fill(); //填充
context.stroke(); //画边框
context.arc(150,150,100,.5*Math.PI,1*Math.PI); // 弧度从1/2*Π 到 Π,及90°到180°
context.strokeStyle="#c33";
// context.fill();
context.stroke();
四、canvas绘制线
context.moveTo(10,50),将“画笔”移动到指定的坐标x以及y上,context.lineTo()绘制路径的结束点。可以设置路径的粗细与颜色,粗细的单位是px。
// 绘制一条线 context.moveTo(10,150); context.lineTo(350,150); context.strokeStyle="black"; context.lineWidth="6"; context.stroke();
所的图像如下,是一条长340px的直线。
这时自然会想到如果同时绘制多条直线与弧会怎样,如下代码:
context.moveTo(10,150);
context.lineTo(350,150);
context.strokeStyle="black";
context.lineWidth="30";
context.stroke();
context.moveTo(50,50);
context.lineTo(200,200);
context.strokeStyle="green";
context.lineWidth="10";
context.stroke();
context.arc(150,150,100,0.5*Math.PI,1.5*Math.PI,false);
context.strokeStyle="#c33";
context.lineWidth="2";
context.stroke();
所得如下图像,可以看到图中右很多线,但我们只画了三条。一条是中间的横线粗30px,黑色;一条是斜线粗10px,绿色;一条是一个粗1px红色的半圆。图上看起来好像有7条线。这是由于在canvas的绘制中,直线是由路径进行绘制的。
下面的痕迹也可以理解为你拿着画笔,笔一直没有离开画布画了这三条线。在不同的线之间移动时画笔也会留下痕迹,故多了那些多余的痕迹。
五、什么是路径?
图形的基本元素是路径。路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。canvas只支持原生绘制矩形,其他的图形绘制都至少需要一条生成路径。
使用路径绘制图形需要一些额外的步骤。
- 你需要创建路径起始点。
- 然后你使用“画笔”去画出路径。
- 把路径封闭。
- 一旦路径生成,你就能通过描边或填充路径区域来渲染图形。
以下是所要用到的函数:
beginPath()
新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。closePath()
闭合路径之后图形绘制命令又重新指向到上下文中。stroke()
通过线条来绘制图形轮廓。fill()
通过填充路径的内容区域生成实心的图形。
调用beginPath()之后,或者canvas刚建的时候,第一条路径构造命令通常被视为是moveTo()。因为你几乎总是要在设置路径的起始位置。生成路径的步骤:
第一步叫做beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。
第二步就是调用函数指定绘制路径,本文稍后我们就能看到了。
第三,就是闭合路径closePath(),不是必需的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的,即当前点为开始点,该函数什么也不做。
ctx.beginPath();
context.moveTo(10,150);
context.lineTo(350,150);
context.strokeStyle="black";
context.lineWidth="30";
context.stroke();
ctx.closePath();
ctx.beginPath();
context.moveTo(50,50);
context.lineTo(200,200);
context.strokeStyle="green";
context.lineWidth="10";
context.stroke();
ctx.closePath();
ctx.beginPath();
context.arc(150,150,100,0.5*Math.PI,1.5*Math.PI,true);
context.strokeStyle="#c33";
context.lineWidth="2";
context.stroke();
ctx.closePath();
所得图像如下:只有三条线了,没有其他的痕迹了。有人会说为啥半圆的方向变了,细心的朋友肯定发现了,其实是arc()的第六个参数改为 true导致圆弧绘制时的方向改变的缘故。
六、函数回调式的写法
有时经常书写画笔的开始、闭合会很麻烦,这是会想可不可以将这些重复的代码封装以下,以后直接调用就行。没错就是这样,这是可以滴。如下:
function draw(cb){
context.beginPath();
context.save();
cb();
context.restore();
context.closePath();
}
draw(()=>{
context.moveTo(10,150);
context.lineTo(350,150);
context.strokeStyle="black";
context.lineWidth="30";
context.stroke();
})
draw(()=>{
context.moveTo(50,50);
context.lineTo(200,200);
context.strokeStyle="green";
context.lineWidth="10";
context.stroke();
})
draw(()=>{
context.arc(150,150,100,0.5*pi,1.5*pi,true);
context.strokeStyle="#c33";
context.lineWidth="2";
context.stroke();
})
如上代码画出的图像和上面的图是一样的,有人问这样写有什么用,这样写在画复杂图形时节省了起始与闭合代码的重复书写,也很清晰的能看出那些是一个画笔画出来的。
这是会有小伙伴问draw函数内的 save()、restore()是干什么的。
七、save()、restore()
用于保存及恢复当前Canvas绘图环境的所有属性。其中save()
可以保存当前状态,而restore()
可以还原之前保存的状态。能起到保存绘制状态和防止污染状态栈。
可以用栈来进行理解,当调用save()时会将“画笔”的状态(填充风格、线条风格、阴影风格的各种绘图状态)压入一个状态栈中,每调用一次就压入一次。当要使用时,用restore()来获取保存 的“画笔”状态,遵循栈的读取规则,先进后出,你最先取出的是你最后压入栈中的。如果你调用restore()的次数比save()多,那多余的次数画出的都是无效的。
save()只是保存了状态,并没有保留之前绘制的图形。
ctx.fillStyle = 'red';
ctx.fillRect(10,30,15,15);
ctx.save(); // 将第一个状态压入栈中
ctx.fillStyle = 'blue';
ctx.fillRect(50,20,30,30);
ctx.save(); // 将第二个状态压入栈中
ctx.fillStyle = 'green';
ctx.fillRect(100,10,50,50);
ctx.save(); // 将第三个状态压入栈中
ctx.restore(); // 取出栈3(第三个状态)
ctx.beginPath();
ctx.arc(200, 35, 30, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
ctx.restore(); // 取出栈2(第二个状态)
ctx.beginPath();
ctx.arc(300, 35, 20, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
ctx.restore(); // 取出栈1(第一个状态)
ctx.beginPath();
ctx.arc(400, 35, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
ctx.restore(); // 取出栈1(第一个状态)
ctx.beginPath();
ctx.arc(400, 35, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
看出最后多余的一次读取状态所绘图形并没有显示出来。所以读取次数要少于压入次数。否则无效。
八、绘制文字
文字的绘制很简单,可以设置字体,大小,颜色,位置。
字体的位置 第一个number参数时x轴坐标,第二number个为y轴坐标,字体的原点在字体的左下角,如下图所示位置。
context.strokeStyle="#c33"; //设置颜色,如果要设置实心的字体颜色则要用 fillStyle
context.font="40px 微软雅黑"; //设置大小与字体
context.fillText("wertantan的博客园",80,80); //实心字体,设置所绘字体内容与位置
context.strokeText("wertantan的博客园",80,150); //空心字体
九、绘制贝塞尔曲线
二次贝塞尔曲线与三次贝塞尔曲线一般用来绘制复杂有规律的图形。可以用贝塞尔曲线组合直线、填充与矩形来绘制复杂的图形。
quadraticCurveTo(cp1x, cp1y, x, y)
绘制二次贝塞尔曲线,cp1x,cp1y
为一个控制点,x,y为
结束点。
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
绘制三次贝塞尔曲线,cp1x,cp1y
为控制点一,cp2x,cp2y
为控制点二,x,y
为结束点。
有人会问起始点去哪了,起始点可以通过moveTo()来定义,直接移动画笔到你所要绘制的起始点,之后再绘制贝塞尔曲线的控制点与结束点。每次绘制贝塞尔曲线都可以用moveTo()来定义起始点的位置。
二次贝塞尔曲线有一个起始点(蓝色)、一个结束点(蓝色)以及一个控制点(红色),而三次贝塞尔曲线有两个控制点。
// 二次贝塞尔曲线 context.beginPath(); context.moveTo(50,50); context.quadraticCurveTo(200,160,50,200); context.stroke(); context.closePath(); // 三次贝塞尔曲线 context.beginPath(); context.moveTo(50,50); context.bezierCurveTo(200,80,200,170,50,200) context.stroke(); context.closePath();
所绘制图像如下:
Canvas除了能绘制基本的形状和文本,还可以实现动画、缩放、各种滤镜和像素转换等高级操作。这里就不再说了,感兴趣的小伙伴可以去 MDN 看一看。
至此canvas的基础使用就结束了,希望各位能有所收获,如有问题还望指正!
本文所有图片均由canvas绘制,截取下来。
这里附上一个小案例,是一个方形从浏览器可视区的左上角移动到右下角的动画,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
*{margin:0;padding:0;}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
<script>
var canvas = document.getElementById("canvas");
var iw = document.documentElement.clientWidth;
var ih = document.documentElement.clientHeight;
var context = canvas.getContext("2d");
canvas.width = iw-50;
canvas.height= ih-50;
var x = 0;
var y = 0;
var b = (ih-50)/(iw-50);
var timer = setInterval(function(){
context.clearRect(0,0,iw,ih);
x++;
y+=b;
context.fillRect(x,y,50,50*b);
if(x>=iw-100){
clearInterval(timer);
}
console.log(x)
},30)
</script>
效果如下:
转载请注明出处!