HTML5学习之Canvas基础知识

4,156 阅读15分钟
  • canvas元素本身是没有绘图能力的,所有的绘制工作必须在 JavaScript 内部完成。
    var cv = document.getElementById("myCanvas");
    var context = cv.getContext("2d"); //申请一个绘制2d动画的环境

  • 检测浏览器是否支持:也可以用js脚本检查if(canvas.getContext)
    在不支持时可以在canvas标签文本中插入提示不支持信息。
    <canvas>hey!你现在的浏览器不支持canvas画布哦!请更新浏览器。</canvas> 
    

  • 常规绘制例子
  1. 用canvas绘制直线
    (a) context.moveTo(起点x,起点y)
    (b) context.lineTo(终点x,终点y):从moveTo提供的起点开始,到lineTo的坐标绘制一条直线;若没有moveTo,则lineTo相当于moveTo。若在一个lineTo后再增设lineTo,即从上一lineTo的坐标开始向下一lineTo坐标延伸直线。
    (c) context.stroke():延给定的坐标画一条直线,也可以指定绘制直线的样式,颜色粗细等。

  2. 用canvas绘制矩形
    (a) context.fillStyle = "color":填充颜色
    (b) context.fillRect(起点x,起点y,宽,高):矩形起点和大小,或者绘制一个没有填充颜色只有边框的矩形 (a) context.strokeStyle = "color" :边框颜色
    (b) context.strokeRect(起点x,起点y,宽,高) :只描边不填充

  3. 用canvas绘制圆形
    (a) context.fillStyle = "color";
    (b) context.beginPath():表示开始绘制
    (c) context.arc(圆心坐标x,圆心坐标y,半径,开始角度,结束角度,是否为按照给定的开始角度和结束角度的按顺时针方向绘制)
    其中:开始角度为0,结束角度为0.5 PI(正下方),1 PI和1.5 PI(正上方),为画饼图提供了扇形范围的依据.第五个参数是弧长Math.PI*2就是整个圆,Math.PI是半圆
    (d) context.closePath():表示结束绘制,会将结束点和开始点通过一点直线连在一起, 即将起点和终点封闭起来,有时可以不要
    (e) context.fill():表示填充图形,有时可以用context.stroke()代替

例子:

canvas-圆形.png
    for(var i = 0;i<=15;i++){
		context.beginPath();
		context.arc(150,150,i*10,0,Math.PI*0.5,true); //在0开始,顺时针Math.PI*0.5结束,即圆正下方结束
		context.stroke(); //不填充而只画边框
	}



canvas-圆形2.png
      for(var i = 0;i<=15;i++){
		context.beginPath();
		context.arc(150,150,i*10,0,Math.PI*1,true);//在0开始,顺时针Math.PI结束,即圆正下方半圆结束
		context.stroke(); //不填充而只画边框
	}



canvas-圆形3.png
	for(var i = 0;i<=15;i++){
		context.beginPath();
		context.arc(150,150,i*10,0,Math.PI*1.5,true);//在0开始,顺时针Math.PI*1.5结束,即圆正上方结束
		context.stroke(); //不填充而只画边框
	    }



canvas-圆形4.png
	for(var i = 0;i<=15;i++){
		context.strokeStyle = "red"; //设置边框颜色
		context.beginPath();
		context.arc(150,150,i*10,0,Math.PI*2,true);
		context.stroke(); //不填充而只画边框
	   }



  1. 用canvas绘制三角形
  • //空心三角形
canvas-三角形1.png
	context.beginPath();  //开始绘制路径
	context.moveTo(100,100); //起点坐标设为(100,100)
	context.lineTo(150,150); //下一点的坐标设为(150,150)
	context.lineTo(50,150); //下一点的坐标设为(50,150)
	context.closePath();//连接起点与终点
	context.stroke(); //绘画线条

  • //实心三角形
canvas-三角形2.png
	context.fillStyle = "red"; //设置填充颜色
	context.moveTo(150,50); //设置起点为(150,50)
	context.lineTo(250,250);//下一点的坐标设为(150,150)
	context.lineTo(50,250);//下一点的坐标设为(50,150)
	context.fill(); //填充颜色,因为会整个图像填充,所以可以有closePath(),也可以没有

  • //清除画布[或某部分]
canvas-三角形3.png
	context.fillStyle = "red";
	context.moveTo(150,50);
	context.lineTo(250,250);
	context.lineTo(50,250);
	context.closePath();
	context.fill();
	
	var clearBtn = document.getElementById("clearCanvas");
	clearBtn.onclick=function(){
	context.clearRect(0,0,150,300); //清除某一部分画布图案
	//context.clearRect(起点x,起点y,画布宽,画布高);若宽高和整个画布大小一样即清除整个画布
	};

  1. 绘制二次方贝塞尔曲线(quadraticCurveTo)
canvas-贝塞尔1.png
context.strokeStyle = "black"; //设置线条颜色
context.beginPath(); //开始绘制
context.moveTo(0,200); //定义开始点,从(0,200)开始绘制
//context.quadraticCurveTo(控制点x,控制点y,结束点x,结束点y),
//控制点为与其与开始坐标和结束坐标连线的两直线的交点        context.quadratciCurveTo(150,100,300,200);
context.stroke(); //绘制线条
context.globalCompositeOperation = "source-over";
//globalCompositeOperation 属性一般用于图形组合,设置如何将一个源(新)图像绘制到目标(已有)的图像上。  
//源图像 = 您打算放置到画布上的绘图。目标图像 = 您已经放置在画布上的绘图。
//源图像:↓(二次贝塞尔曲线是根据直线部分画出来的)
context.strokeStyle = "red";
context.beginPath();
context.moveTo(150,100);
context.lineTo(0,200);
context.moveTo(150,100);
context.lineTo(300,200);
context.stroke();	

  1. 绘制三次方贝塞尔曲线(bezierCurveTo
    参考理解:https://blog.csdn.net/cdnight/article/details/48468653
canvas-贝塞尔2.png

context.strokeStyle = "black";
context.beginPath();  //开始绘制
context.moveTo(0,200); //从(0,200)开始绘制
context.bezierCurveTo(25,50,75,50,300,200);
//context.bezierCurveTo(控制点1坐标x,控制点1坐标y,控制点2坐标x,控制点2坐标2,终点坐标x,终点坐标y)
context.bezierCurveTo(25,50,75,50,300,200);
context.stroke();
context.globalCompositeOperation = "source-over";

context.strokeStyle = "red";
context.beginPath();
//控制点1到开始点的直线
context.moveTo(25,50);
context.lineTo(0,200);
//控制点1到控制点2(三次贝塞尔曲线就是根据这条线画出来的)
context.moveTo(25,50);
context.lineTo(75,50);
//控制点2到结束点直线
context.moveTo(75,50);
context.lineTo(300,200);
context.stroke();

  1. 画布的图像可以按照当前状态以堆stack的方式保存下来和按照上一次状态恢复
    A. save::用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作.

    B. restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响.
    注意:save表示保存save函数之前的状态,restore表示获取save保存的状态

    例子:

	//矩形一,填充色为橙色,边框为红色
	context.fillStyle = "orange";
	context.strokeStyle = "red";
	context.fillRect(20,20,100,100);
	context.strokeRect(20,20,100,100);
	context.fill();
	context.stroke();
	//保持当前canvas状态
	context.save();
	
	//绘制矩形二,填充色为黄色,边框色为粉红色
	context.fillStyle="yellow";
	context.strokeStyle="pink";
	context.fillRect(150,20,100,100);
	context.strokeRect(150,20,100,100);
	context.fill();
	context.stroke();
	
	//恢复上一个矩形的状态来进行绘画
	// context.restore();
	context.fillRect(20,150,100,100);
	context.strokeRect(20,150,100,100);

无context.restore()时的画面↓:保存的是最后一次绘画样式

canvas-save1.png

有context.restore()时的画面↓:保存的是context.save()的绘画样式

canvas-save2.png



8. 移动坐标空间,绘制渐变伞效果
画布默认左上角为坐标原点,向右为x轴正向,向下为y轴正向。坐标空间的单位为像素,在绘制图像时,可以使用translate来移动坐标空间,使坐标空间原点移动到指定位置。

例子:

canvas-umbrella.png
var canvasMethod = {
	drawTop:function(context,fillColor){
		context.fillStyle = fillColor;
		context.strokeStyle = fillColor;
		context.beginPath();
		//从坐标0,0开始,画一个半径为30像素的正半上方圆
		context.arc(0,0,30,0,Math.PI,true); 
		context.closePath();
		context.fill();
		context.save();//保存画布当前状态,移动位置,fillStyle,strokeStyle为fillColor
	},

	drawStick:function(context){
		context.fillRect(-1.5,0,1,40);//绘制一个矩形,但这个矩形其实是向左偏移1.5像素,宽为1高为40的一条直线,根据save的样式进行填充
		context.beginPath();
		context.strokeStyle = "blue";
		context.arc(-5,40,4,Math.PI,Math.PI*2,true);//圆心为(-5,40),半径为4,顺从从PI到2PI的方向,画的下半圆,即终点在(-1,40)
		context.restore();//采用save中的画布样式,即fillStyle,strokeStyle为fillColor
		context.stroke();//根据save的样式进行描边
	},

	printUmbrella:function(){
		var context = document.getElementById("canvas1").getContext("2d");
		context.translate(80,80);//第一次移动坐标原点到(80,80)
		//绘制十把伞
		for (var i = 0; i < 10; i++) {
			context.save();//保存画布上一次状态,移动位置,fillStyle,strokeStyle默认为黑
			context.translate(60*i,0);//不断改变坐标原点,每次向右移60像素(圆的直径)
			this.drawTop(context,"rgb("+(30*i)+","+(255-30*i)+",255)");//绘制半圆并改变填充色彩
			this.drawStick(context);//绘制雨伞柄
			context.restore();//采用画布save的设置,移动位置,fillStyle,strokeStyle默认为黑
	    }
	}
};

window.onload = function(){
	canvasMethod.printUmbrella();
}



9. 旋转坐标空间

利用rotate()方法指定一个角度,改变了画布坐标和Web浏览器中的Canvas元素的像素之间的映射,使得任意后续绘图在画布中都显示为旋转的。但它并没有旋转Canvas元素本身。 只有一个参数,为旋转的角度。
接着上一个雨伞例子,改变canvasMethod.printUmbrella()方法。

canvas-umbrella2.png
printUmbrella:function(){
	var context = document.getElementById("canvas1").getContext("2d");
	context.translate(150,150);//第一次移动坐标原点到(150,150)
	
	//绘制8把伞
	for (var i = 0; i < 8; i++) {
		context.save();//保存画布上一次的移动状态
		context.rotate(Math.PI*0.25*i); //2PI/8=0.25PI,每次旋转0.25PI 
		context.translate(0,-100);//不断改变坐标原点,每次向上移100像素
		this.drawTop(context,"rgb("+(30*i)+","+(255-30*i)+",255)");//绘制半圆并改变填充色彩
		this.drawStick(context);//绘制雨伞柄
		context.restore();//采用画布save的设置

	}
}



10. 缩放图形

画布通过scale()去改变canvas上下文对象(context)中横纵方向的像素数目来改变图形的大小。

接收两个参数分别是x轴方向缩放倍数和y轴方向缩放倍数,都必须为正数,若要比原来大则>1,比原来小则<1。
例如,传递一个值 2.0 和 0.5 将会导致绘图路径宽度变为原来的两倍,而高度变为原来的 1/2。

例子:

canvas-scale1.png
function scaleCircle(context){
	context.translate(250,20);
	for (var i = 0; i < 80; i++) {
		context.save(); //保存上一次画布的设置
		context.translate(30,30);//每次迭代新的原点都移动到上一次原坐标的(30,30)
		context.scale(0.95,0.95);//每次迭代横纵因子都缩小到原来的0.9倍
		context.rotate(Math.PI/12)//旋转
		context.beginPath();//开始进行绘制
		// context.fillStyle = "red";
		// context.globalAlpha="0.4";//设置颜色透明度
		context.fillStyle= "rgba(155, 187, 89, 0.7)";
		context.arc(0,0,50,0,Math.PI*2,true);//绘制圆
		context.closePath();
		context.fill();
	}
}

window.onload = function(){
	scaleCircle(context);
}



11. 矩阵变换

引用了理解网站内容:http://jo2.org/html5-canvas-transform/

canvas上下文对象(context)每次产生一个图像都会创建一个对应的矩阵对象,可直接对canvas变形矩阵作修改,可通过矩阵变换,使图形产生移动、旋转、切片、镜像反射的效果。

那么问题来了:图形都有矩阵,那一个图形的默认矩阵是什么样子的? 答案是:(1,0,0,1,0,0).

①为什么不全为0?其中:一个图形在没有缩放,旋转,位移什么的时候,他也会有一个属性会是1,就是缩放!因为在没有缩放的情况下,图形的缩放其实是原大小的1倍.所以,这个默认矩阵里面才会有两个1. 所以参数位置1上的1,是表示x轴上的缩放,参数位置4上的1是表示y轴上的缩放!
即:缩放可以根据:context.transform(scaleX,0,0,scaleY,0,0);来设置效果。

举一反三:
②根据图形默认矩阵,知矩阵中的最后两位参数就是表示位移距离的数字(在没有位移的情况下为0)。 即:移动可以根据:context.transform(scaleX,0,0,scaleY,transX,transY);来设置效果。

③那么剩下的两个数字(参数位置2,参数位置3)是不是表示旋转呢? 不是,他们是表示斜切。什么是斜切?把一个矩形的任一条边用力一拉,变成平行四边形,这就是斜切。 例子:

未进行斜切之前
ctx.arc(200,50,50,0,Math.PI*2) 
.fillRect(200,100,50,50)
.stroke()

斜切之后
ctx.transform(1,Math.tan(Math.PI/180*30),0,1,0,0)//矩形X轴产生了斜切效果
.arc(200,50,w/2,0,Math.PI*2)
.fillRect(200,100,50,50)
.stroke()

代码中我们可以看到使用了一个tan函数,如果要用斜切,比如想斜切30度,那么就必须用tan把30度包起来,x/y轴都是如此.

可以理解为图形矩阵为ctx.transform(scaleX,skewX,skewY,scaleY,transX,transY);
新坐标x' = (scaleX)x + (skewY)y + transX
新坐标y' = (skewX)x + (scaleY)y + transY

④那怎么用transform实现图形转换呢?
旋转的效果要斜切配合缩放实现的。
比如,其他的都不变,只把图形旋转30度,代码:

var deg = Math.PI/180;
ctx.transform(Math.cos(30*deg),Math.sin(30*deg),-Math.sin(30*deg),Math.cos(30*deg),0,0)
.arc(200,50,50,0,Math.PI*2)
.fillRect(200,100,50,50)
.stroke()

其中:

cos(30*deg),
sin(30*deg),
-sin(30*deg),
cos(30*deg)

在使用transform方法实现旋转时,旋转N度时,N*deg不变,只要记住对应位置的对应三角函数cos,sin,-sin,cos就行。
原博主传授的记忆方法:CS-SC=初三-上床,不要忘记负号.

注意:canvas也提供setTransform方法直接把矩阵设为你传给他的值,会清空前面所有的transform造成的效果。

一个简单的应用例子:

canvas-transform2.png
context.translate(200,20);
for (var i = 0; i < 80; i++) {
	context.save();
	context.transform(0.95,0,0,0.95,30,30); //缩至0.95倍,移动至(30,30)
	context.rotate(Math.PI/12);
	context.beginPath();
	context.fillStyle = "red";
	context.globalAlpha="0.4";//设置颜色透明度	
	//其中可以利用globalAlpha()为设置透明度,也可以利用fillStyle=rgva()设置透明度
	context.arc(0,0,50,0,Math.PI*2,true);
	// context.closePath();
	context.fill();		
}

context.setTransform(1,0,0,1,10,10); //清空之前的transform样式,设置新的图形位置
context.fillStyle = "yellow";
context.fillRect(0,0,50,50);
context.fill();



12. 图形组合
默认情况下,两个图形有重叠部分时,另外一个图形会将另一个图形覆盖掉。
可以通过globalCompositeOperation属性设置。

懒得敲之属性展示系列:

canvas-globalCompositeOperation1.png canvas-globalCompositeOperation2.png canvas-globalCompositeOperation3.png canvas-globalCompositeOperation4.png canvas-globalCompositeOperation5.png
  1. 裁切路径
    使用clip(),:一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域).
    或者可以理解为,在使用clip()之后,只看到clip之前信息,而clip之后的只能显示在clip所选定的区域中的图形部分,而其他会被隐藏起来。

    参考理解网址:https://www.w3cplus.com/canvas/clip.html

  2. 应用不同的线型
    利用lineWidth、lineGap、lineJoin、miterLimit设置不同线形样式。
    lineWidth:线粗细,默认为1
    lineGap:线端点样式,默认为butt,还有round,square可选
    lineJoin:线段连接的样式,默认为round(圆形),还有bevel(斜角), miter(垂直矩形)可选
    miterLimit:线段相交交叉点的粗细,越大lineJoin所选的样式越明显

  3. 绘制线性渐变
    一般用到createLinearGradient()创建线性的渐变对象, 再用addColorStop对线性渐变对象分段添加不同颜色达到渐变效果.

例子:

canvas-linear1.png
// createLinearGradient(渐变起始点x,渐变起始点y,渐变结束点x,渐变结束点y)
var shadow = context.createLinearGradient(0,0,200,0); //也可以理解为渐变的方向的坐标
shadow.addColorStop(0,'#ff0000');
shadow.addColorStop(1/7,'#ff9900');
shadow.addColorStop(2/7,'#ffff00');
shadow.addColorStop(3/7,'#00ff00');
shadow.addColorStop(4/7,'#00ffff');
shadow.addColorStop(5/7,'#0000ff');
shadow.addColorStop(6/7,'#ff00ff');
shadow.addColorStop(1,"#ff0000");
context.fillStyle =shadow;
context.strokeStyle = shadow;
context.fillRect(10,10,200,200);



16. 绘制径向渐变
利用createRadialGradient()创建径向渐变对象, 再用addColorStop对径向渐变对象发射状添加不同颜色达到径向渐变效果.
例子:

canvas-radial1.png
// createRadialGradient(渐变开始圆坐标x0.渐变开始圆坐标y0,开始圆半径r0,渐变结束圆坐标x1,渐变结束圆坐标y1,结束圆半径r1)
var shadow = context.createRadialGradient(85,85,3,100,100,100);
shadow.addColorStop(0,"white");
shadow.addColorStop(1,"orange");
context.fillStyle= shadow;
context.fillRect(10,10,200,200);



17. 绘制图案

A.context.createPattern(image,"repeat|repeat-x|repeat-y|no-repeat")
方法在指定的方向内重复指定的元素.
和设置图片背景有点类似,先new Image()对象,再将img对象传进createPattern(img,type),再将其赋予给fillStyle,即可达到填充图片的效果。
例子:

canvas-pic1.png
var img = new Image(); //创建新的Image对象
img.src = "https://www.easyicon.net/api/resizeApi.php?id=1209623&size=128";  //设置图像路径
img.onload = function(){  //加载图像
    //创建图案
    var pattern = context.createPattern(img,'repeat');
    context.fillStyle = pattern;
    context.fillRect(0,0,380,190);
	}

B.drawImage()方法
①在画布上定位图像:context.drawImage(img,x,y);
②画布上定位图像,并规定图像的宽度和高度:context.drawImage(img,x,y,width,height);
③剪切图像,并在画布上定位被剪切的部分:context.drawImage(img,开始剪切坐标位置x,开始剪切坐标位置y,被剪切图像宽度swidth,被剪切图像高度sheight,图片位置x,图片位置y,显示图片宽度width,显示图片高度height);

var img = new Image(); //创建新的Image对象
img.src = "https://www.easyicon.net/api/resizeApi.php?id=1209623&size=128";  //设置图像路径
img.onload = function(){  //加载图像
    //创建图案
    context.drawImage(img,0,0);
    context.font = "nomal 100px SimHei";
    context.shadowOffsetX = 3;
    context.shadowOffsetY = 3;
    context.shadowBlur = 3;
    context.shadowColor = "pink";
    context.createPattern(img,'repeat');
    context.fillStyle = "white";
    context.fillText("wowww",50,165);
	}



18. 创建阴影

例子:

canvas-shadow2.png
//设置阴影
context.shadowOffsetX =3; //x轴方向的阴影偏移量,负数为向右偏移量
context.shadowOffsetY =3; //y轴方向的阴影偏移量,负数为向上偏移量
context.shadowBlur = 2;//阴影模糊强度
context.shadowColor = "pink";

//绘制矩形
context.fillStyle = "orange";
context.fillRect(20,20,300,80);

//绘制文本
context.font = "nomal 45px SimHei"; //设置文本字体样式
context.fillStyle = "white"; //设置文本颜色,填充颜色
context.fillText("HTML5/CSS3" ,30,64)//设置文本内容和文本位置

注意:在设置了阴影后,文字和矩形都会有阴影。

  1. 绘制填充/轮廓文字

A.context.fillText(文字内容,位置x,位置y[,最大宽度]),若文字内容长度超过最大宽度,文字会自动被压缩

//绘制填充文本
context.font = "nomal 45px SimHei"; //设置文本字体样式
context.fillStyle = "white"; //设置文本填充颜色
context.fillText("HTML5/CSS3" ,30,64)//设置文本内容和文本位置

B.context.strokeText(文字内容,位置x,位置y[,最大宽度]),若文字内容长度超过最大宽度,文字会自动被压缩
//绘制轮廓文本
context.font = "nomal 45px SimHei"; //设置文本字体样式
context.strokeStyle = "white"; //设置文本轮廓颜色
var text = "HTML5/CSS3";
context.strokeText(text, ,30,64)//设置文本内容和文本位置

其中:可以通过context.measureText(text)来获取对应text的宽度.



小白第一次在掘金写学习总结,从自己理解的角度记录笔记,可能对某些方法理解存在错误,欢迎指出!持续学习更新中....