基础知识
html5、canvas、svg、WebGL历史
介绍
html5的草案大概是2008年开始制定,用来取代1999制定的html 4.1,经历了html5草案制定,到2014年最终标准发布。
canvas 由苹果公司在 2004 年前后发明运用于Safari,后来其他的浏览器开始跟进,后来canvas被收入到html5草案中,之后大家对canvas关注越来越多(canvas被大家越来越多的关注,我推测应该在2008左右),一直到2014年随着html5,作为标准发布。
SVG 是由万维网联盟(W3C)自 1999 年开始开发的开放标准,与canvas使用js绘图不同,svg使用xml来画图。
canvas是html5
canvas是html5.0的标准,在此前的标准(W3C标准,html4.1)中从未有过canvas,在html5.0标准于2014年被公布以前,canvas一直存在于html5.0的草案当中。
svg不是html5新创的标签
svg不是html5才出现的新标签,它在1999年就被开始制定,彼时还没有所谓的html5,但这不妨碍svg与html5的联系,因为在html5.0中,svg被丰富了更多的功能。
所以在html5之前与之后的svg,其功能还是有区别的。
canvas与svg的发展和区别
svg不是什么新的技术了,于1999年被创造后,一直到现在,svg并没有太多的突破发展。
相比而言,canvas是比较新的技术,于2008年(年份基于上面的推测)被大家关注以来,到现在,先后被用于2D,3D的位图绘制。
从发展速度而言:
从这一点来看,canvas要比svg发展更快。
从运用的角度来讲:
svg用于矢量图绘制,canvas用于位图绘制,所以二者不是纯粹的替代竞争关系;
- 只要你对矢量图有需求,svg永远不会被消失;
- 只要你对位图有需求,canvas永远不会被消失;
- 只要你对图有需求,svg和canvas都是你的选择;
就兼容性而言:
svg、canvas 所有的浏览器都兼容,不同的是,svg因为很早就出现了,就算是低版本的浏览器也都兼容;
而canvas相对而言比较新,一些低版本的浏览器不支持,好消息是,都9102了,市面上的浏览器的版本基本都支持canvas,不用太担心兼容问题。WebGL与canvas的关系
WebGL是基于canvas元素绘制3D图的js API。
canvas基于状态绘图的特性
介绍
先设置好路径作为绘图状态,再使用绘制的api绘图,例如:1
2
3
4
5
6
7
8//设置状态
context.moveTo(100,100)
context.lineTo(700,700)
context.lineWidth = 10
context.strokeStyle = “#058”
//绘图
context.stroke()
使用beginPath来分别设置状态
如上面代码,canvas不针对某一个形状进行状态设置,因此设置的状态都是针对全局的,假如我们要对画布内几个图形分别设置状态如颜色,
此时需配合beginPath使用:1
2
3
4
5
6
7
8
9
10
11
12
13
14context.beginPath()
context.moveTo(100,100)
context.lineTo(700,700)
context.lineWidth = 10
context.strokeStyle = “#058”
context.beginPath()
context.moveTo(1100,1100)
context.lineTo(1700,1700)
context.lineWidth = 15
context.strokeStyle = “red”
//绘图
context.stroke()
moveTo 与 lineTo
beginPath与lineTo一起,lineTo相当于 moveTo
1 | context.beginPath() |
等同于,因为beginPath相当于从新开始:1
2
3context.beginPath()
context.lineTo(100,100)
context.lineTo(700,700)
先填充后描边:先fill后stroke
如果你要对一个矩形填充,并且绘制样式多样的边线,那么请先fill,后stroke,反之 填充的效果就覆盖了线条的效果。
接口汇集
矩形
1 | rect( x , y , width , height ) |
填充与绘制
1 | stroke() |
beginPath、 closePath
介绍
画一个形状时,需要cxt.beginPath(),但closePath不是必须,可以不使用,下次再使用cxt.beginPath()时,会默认自动closePath上一个路径。1
2
3
4cxt.beginPath();
cxt.arc( x+j*2*(RADIUS+1)+(RADIUS+1) , y+i*2*(RADIUS+1)+(RADIUS+1) , RADIUS , 0 , 2*Math.PI )
cxt.closePath()
cxt.fill()
closePath不是必须
参考上面分析
第一个beginPath可以省略
1 | context.beginPath()//可省略 |
封闭图形推荐使用closePath
封闭图形使用closePath的好处在于,自动封闭严密,一些封闭不齐,有凹角等等问题,都会被自动解决。
使用beginPath来分别设置状态
参考《canvas基于状态绘图的特性 – 使用beginPath来分别设置状态》
生成(2d)画布上下文的要素
注意的是canvas.width,不要使用css的方式嵌入,具体原因待写,最好直接以style属性或js直接写入。1
2
3
4var canvas = document.getElementById('canvas');
var context = canvas.getContext("2d");
canvas.width = WINDOW_WIDTH;
canvas.height = WINDOW_HEIGHT;
线条属性
lineCap 线条的帽子
线条两端的倒角设置。
lineJoin 折线的帽子
设置折线两条线相交处的倒角。
miterLimit 折线交点距离
miterLimit 是折线中心线交点与折线外边线交点距离
此属于通常与lineJoin一起使用,用来设置折线的尖角的尖锐度,下面是原理图:
画五角星

1 | var canvas = document.getElementById('canvas'); |
阴影的理解
阴影是围绕图形的一层阴影图形,这个阴影图形是固定的,当你对这个阴影图形进行向右偏移时,阴影图形大小不变,图形将整体平移,这样阴影图形的左侧就会被原图形给遮挡,导致看起来只看到图形只有右侧才有阴影。
shadowOffsetX
下面两张图片分别展示了 阴影图形平移原理,具体是 阴影图形不变,整体偏移被遮挡。
shadowBlur 模糊程度
一般的人说这个是模糊程度,我觉得这个说法不太准确,shadowBlur是扩散并模糊才对,因为shadowBlur会导致阴影扩散的长度,比如定义为10的时候,阴影将扩散长度10px,然后针对这10px进行模糊,会导致阴影的。
此时,如果定义 shadowBlur 为30,然后偏移量 shadowOffsetX为10,会出现图形四周都有阴影,但左侧阴影长度为20,右侧为40,上下为30.
globalAlpha 透明度
设置canvas的透明度
图形叠加的遮盖设置
可以通过globleCompositeOperation 设置图形叠加时,如何遮盖的问题:
剪辑区域 clip
与路径规划函数(如arc)等等共同使用,clip先使用,使canvas绘制区域只显示在clip的路径内,其他区域只显示整个context.fillStyle。1
2
3
4
5
6
7
8
9
10
11
12
13
14 context.beginPath();
context.fillStyle='yellow'
context.fillRect(0,0,w,h);
context.beginPath();
//指定clip路径
context.arc(ball.x,ball.y,ball.r,0,2*Math.PI);
context.fillStyle='blue';
context.fill();
context.clip();
context.beginPath();
context.font='bold 120px Arial';
context.textAlign='center';
context.fillStyle='#ff55cc';
context.fillText('天若有情',w/2,h/1.6);
探照灯动画 demo
代码如下,新颖的思想是:
- 每一次轮询,创建一个方法 update 来更新路径,然后创建一个方法使用新的路径进行绘制。
- 先绘制,紧接着更新路径,然后轮询这个动作。
- 这个例子再一次印证了canvas的 规划路径 和 绘制是分开的。
demo地址1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56var c = $('#canvas')[0];
var context=c.getContext('2d');//用context进行绘制
var w =canvas.width;
var h =canvas.height
var ball={
x:w/2,
y:h/2,
r:100,
vx:20,
vy:15
}
setInterval(function(){
draw();
update();
},50);
function draw(){
context.clearRect(0,0,w,h);
context.save(); //////////
context.beginPath();
context.fillStyle='black'
context.fillRect(0,0,w,h);
context.beginPath();
context.arc(ball.x,ball.y,ball.r,0,2*Math.PI);
context.fillStyle='white';
context.fill();
context.clip();
context.beginPath();
context.font='bold 120px Arial';
context.textAlign='center';
context.fillStyle='#ff55cc';
context.fillText('天若有情',w/2,h/3);
context.fillText('沧海桑田',w/2,h/4*3);
context.restore();
}
function update(){
ball.x+=ball.vx;
ball.y+=ball.vy;
if(ball.x<=ball.r){
ball.x=ball.r;
ball.vx=-ball.vx;
}
if(ball.x>=w-ball.r){
ball.x=w-ball.r;
ball.vx=-ball.vx;
}
if(ball.y<=ball.r){
ball.y=ball.r;
ball.vy=-ball.vy;
}
if(ball.y>=h-ball.r){
ball.y=h-ball.r;
ball.vy=-ball.vy;
}
}
clearRect
常用于动画,当重新绘制图形时,使用此API清空画布。1
context.clearRect(x,y width, height);
isPointInPath
是否处于图形之内,查看demo1
context.isPointInPath( x , y )
获取canvas坐标
1 | const canvas = document.getElementById('canvas'); |
阴影 shadowColor相关
参考《对阴影的影响》
黑知识
非零环绕原则
非零环绕原则 用来确认某一区域处于图形外面或里面;
如下图,A C处于图形里面,B处于图形外面,判断原则为:
区域内向外画一条线,这条线穿过N条线,指定一种方向为-1,相反方向为1,N条线加起来的值,
若为0,说明该区域处于图形外部;
若为非0,说明处于内部。
圆圈内外圆顺\逆时针影响到的
内外两个圆不指定方向
如下,不指定方向的时候,画出来的图是一个大的实心圆,并非希望的镂空圆圈。1
2
3
4
5
6var c = $('#canvas')[0];
var context=c.getContext('2d');
context.arc(400,250,100,0,2*Math.PI);//不指定方向
context.arc(400,250,50,0,2*Math.PI);//不指定方向
context.fillStyle='blue';
context.fill();
内外圆指定不同方向
1 | var c = $('#canvas')[0]; |
非零环绕原则
当指定不同方向时,利用非零环绕原则,内圆的区域相对于整个图形而言处于图形之外,此时fill方法不会填充此区域,产生镂空效果。
对阴影的影响
代码和效果如下,正常的情况,阴影默认都应该位于图形外侧,如B的位置,
因此如果给内圆定义阴影的画,阴影应该位于A的位置,但实际情况却位于图中所示但C位置。
初一看觉得不合理,但是我们用非零环绕原则时,发现内圆的确处于图形外侧,因此阴影处于C的位置是对的。
1 | var c = $('#canvas')[0]; |

CanvasRenderingContext2D
介绍
对canvas扩展使用 CanvasRenderingContext2D 进行扩展。可以扩展新API,可扩展现在API(谨慎),可扩展一个对象,存放数据。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29var c = $('#canvas')[0];
var context=c.getContext('2d');//用context进行绘制
//扩展对象
CanvasRenderingContext2D.prototype.lastMoveToLoc = {};
//扩展现有方法
var originMoveto = CanvasRenderingContext2D.prototype.moveTo;
CanvasRenderingContext2D.prototype.moveTo = function(x,y){
originMoveto.apply(context, [x, y]);
this.lastMoveToLoc.x = x;
this.lastMoveToLoc.y = y;
}
//扩展新方法
CanvasRenderingContext2D.prototype.drawStar = function(r,R,rot){
this.beginPath();
var x = this.lastMoveToLoc.x;
var y = this.lastMoveToLoc.y;
var rot1 = rot || 0;
for(var i =0;i<=5;i++){
this.lineTo(Math.cos((18+i*72-rot1)/180*Math.PI)*R+x,
-Math.sin((18+i*72-rot1)/180*Math.PI)*R+y)
this.lineTo(Math.cos((54+i*72-rot1)/180*Math.PI)*r+x,
-Math.sin((54+i*72-rot1)/180*Math.PI)*r+y)
}
this.closePath();
this.fill();
}
context.fillStyle = 'blue';
context.moveTo(400, 400);
context.drawStar(150,300,30);
扩展方法
参考上面
扩展对象
参考上面
图形变换
图形变换的API
- 位移 translate( x , y )
- 旋转 rotate( deg )
- 缩放 scale( sx , sy )
变换矩阵

transform 与 变换重置
当对context多次transform后,想丢弃、重置之前所有的transform的影响,可使用setTransform。
transform( a , b , c , d , e , f )
setTransform( a , b , c , d , e , f )
状态的保存和恢复(save\restore)
画两个矩形,第一个矩形向左偏移100,第二个矩形向左偏移200,
由于偏移都是对整个上下文执行的,为了免除第一次偏移的影响,在第一次偏移之前,执行save,保存当时的状态;
在给第二个矩形偏移之前,执行restore,将状态恢复到save时的状态。
scale的副作用
scale会对坐标点的 xy值、图形宽度、线条宽度都产生放大缩小效果。这是scale的特点,但也是副作用,用的时候需注意。
fillStyle
color
1 | context.fillStyle='red'; |
线性渐变color
线性渐变颜色创建context.createLinearGradient。1
2
3
4
5
6
7
8//起始点坐标100 100, 终点 100 300;
var linear = context.createLinearGradient(100,100,100,300);
//addColorStop 第一参数是浮点数,取值范围为0-1,0表示起点,1表示终点
linear.addColorStop(0.0,'green');
linear.addColorStop(0.5,'yellow');
linear.addColorStop(1.0,'blue');
context.fillStyle=linear;
context.fillRect(100,100,300,200);
径向渐变color
用法与线性渐变一致,可网上查阅,这里不列出。
图片填充
使用API createPattern 创建填充图片1
2
3
4
5
6var bi=new Image();
bi.src='autumn.jpg';
var pattern = context.createPattern(bi,'repeat');
context.beginPath();
context.fillStyle=pattern;
context.fillRect(100,100,400,300);
createLinearGradient 线性渐变填充色
此api用于创建线性渐变颜色,详细参考《线性渐变color》
createPattern 图片填充
此API创建填充图片1
2
3
4//接受图片
context.createPattern(img,'repeat')
//接受canvas画布
context.createPattern(canvas,'repeat')
文字渲染API
1 | context.font = "bold 40px Arial" |
一些几何思路
带倒角的矩形

圆的pi概念

月亮

你也可以使用两段闭合的圆弧线来构建一个圆,参考demo
图像处理API
drawImage 图像处理
image.onload时加载
1 | image.src = "img.jpg" |
另外 drawImage有三种赋值模式:
drawImage(image,0,0) –此模式不会缩放图片
在画布的坐标 0,0的位置上开始画图片,图片以原像素和比例展示。
drawImage(image,0,0,100,100)
在画布的坐标0,0上开始画图片,并且将整个图片放置于宽高都为100的区域内,图片会根据区域大小缩放。
drawImage(image,0,0,100,100,0,0,200,200)
参数含义依次为:图片,原图片信息(0,0,100,100),目的图片信息(0,0,200,200);
基本含义为:截取原图片坐标点为0,0起点宽高各100的区域,绘制到画布的坐标点0,0为起点,宽高各200的区域上。
截图的图片会按照目的区域大小缩放
getImageData 获取图片信息
介绍
getImageData 接受四个参数,这四个参数构造了一个矩形区域,分别是坐标点,宽高。表示获取该区域内所以图片的信息。1
2
3
4
5const imageData = getImageData( x , y , width , height )
imageData对象:
width
height
data(图片像素等信息,以多维数组的方式展示)
下面是imageData.data的信息,更多信息参考《putImageData 插入图像》:
跨域问题
图片存储在本地时,是默认没有域名的,如果不启动服务直接打开html,用getImageData方法时,浏览器会判定为跨域而报错!解决方法是启动本地服务打开html。1
2"Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.
at HTMLImageElement.document.getElementById.onload"
putImageData 插入图像
其7个参数意思依次为:getImageData获取的图片imageData对象,坐标点x(也是相对于canvas的相对位移,故写成dx),坐标点y,脏位移x(脏是因为通过putImageData获取的图片信息,不是纯正的原图片信息,是“被污染了的”数据),脏位移y,脏图片宽度,脏图片高度。
要与 getImageData 一起使用,demo 地址:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34var canvasa = document.getElementById("canvasa")
var contexta = canvasa.getContext("2d")
var canvasb = document.getElementById("canvasb")
var contextb = canvasb.getContext("2d")
var image = new Image()
window.onload = function(){
image.src = "autumn.jpg"
image.onload = function(){
contexta.drawImage( image , 0 , 0 , canvasa.width , canvasa.height )
}
}
function copyImage(){
var imageData = contexta.getImageData( 0 , 0 , canvasa.width , canvasa.height )
var pixelData = imageData.data
//改变图片像素,让新copy出来的图片置灰
// for( var i = 0 ; i < canvasb.width * canvasb.height ; i ++ ){
// var r = pixelData[i*4+0]
// var g = pixelData[i*4+1]
// var b = pixelData[i*4+2]
// var grey = r*0.3+g*0.59+b*0.11
// pixelData[i*4+0] = grey
// pixelData[i*4+1] = grey
// pixelData[i*4+2] = grey
// }
contextb.putImageData( imageData , 0 , 0 , 0 , 0 , canvasb.width , canvasb.height )
context.putImageData()
}
曲线绘制
arc 绘制圆弧
注意,arc绘制的是一条弧线,并非闭合的不规则圆。1
2
3
4
5context.arc(
centerx, centery, radius,
startingAngle, endingAngle,
anticlockwise = false
)
arcTo(需与 moveTo 的一起使用)
1 | context.moveTo( x0 , y0 ) 开始点; |

贝塞尔曲线 Bezier
介绍
贝塞尔曲线类似于ps画图软件中的钢笔工具画出的线,无论下面介绍的二次还是三次曲线,它们都需要结合moveTo绘制开始点。
需要moveTo配合绘制开始点
参考上面
二次贝塞尔曲线
二次贝塞尔曲线API是quadraticCurveTo,二次贝塞尔曲线与三次贝塞尔曲线的区别在于,二次不能画波浪线,三次可以:
如下图所示,演示了开始点、控制点、结束点。
三次贝塞尔曲线
三次贝塞尔曲线API是 bezierCurveTo 更多信息参考《二次贝塞尔曲线 Bezier》:
画线条只能用stroke
如题,线条使用fill将无法绘制。1
2
3
4
5
6context.beginPath();
context.moveTo(2,2)
context.lineTo(300,400)
context.lineWidth=5;
context.fillStyle='yellow';
context.stroke();
炫丽的倒计时效果demo
数据建模–多维数组矩阵创建数字模型
计算数字矩阵模型内的元素坐标
如上图所示,只要我们能知道矩阵左上角的坐标值xy,就可以计算出矩阵内所有的元素坐标值;
值得注意的是,上面的i和j分别是二维数组的i和j,它们对应的初始值都是0,具体公式,参考上图。
模拟抛物线路径
实现代码
1 | var aBall = { |
1 | for( var i = 0 ; i < balls.length ; i ++ ){ |
x、y坐标值
参考上面代码
g 重力加速度
参考上面代码
vx 水平方向速度
参考上面代码
vy 垂直方向速度
参考上面代码
空气阻力
参考上面代码
碰撞检测
1 | //只取屏幕内的元素 |
溢出删除的优化处理
参考《碰撞检测》
用50毫秒轮询模拟1秒的动作
1 | setInterval( |
当时在想,既然做一个定时器,那么就写一个1秒的轮询,这样才有道理,其实不然,如果写一个1秒的轮询就会有延迟的现象;
如果你用一个50毫秒的轮询,如果秒钟没有到达下一秒,画面其实也不会跳转,因为50毫秒内拿到什么画面,就显示什么画面,在一秒内,有20个50毫秒,
这20个50毫秒内拿到的时间肯定都是一个值,那么渲染出来的值肯定也是一个值,就不会有跳转,也不会出现延时,就算出现延时也只是50毫秒内,可以接受。
所以,要想做一个响应快速的,应该将轮询值改得更小,而不是放大。
误区一:以为1秒的定时器就应该使用1秒的轮询
参考上面讲解
误区一:以为50秒的轮询会让定时器加速跳转
参考上面讲解
一秒的定时器,必须使用小于一秒的轮询
参考上面讲解
轮询时间越小,延时更小,响应更快
参考上面讲解
轮询时间需要平衡js代码执行时间和性能
代码轮询时,执行了很多js,这些js如果非常大量,也是需要时间才能执行完,所以轮询时间要考虑是否大量执行js会延迟轮询时间,也要考虑性能。