过个年一下荒废了个把月。 最近刚接触canvas,将一些概念点简单概括下,canvas是基于像素的图像API,与svg的最大的区别在于canvas须要重绘(canvas移除图片时须要从新绘制,而SVG能够经过编辑元素节点来编辑图片),而且基于基于像素绘制(svg顾名思义是矢量),更详细的对比mark在此:?SVG 与 HTML5 的 canvas 各有什么优势 并且我我的认为虽然canvas的API也很复杂,可是svg更复杂,囧rz。如下是我将我接触canvas过程当中认为须要厘清的概念点概括以下。html
canvas元素自己没有任何外观,它就是一块空白画板,提供给JS的一套API,最先由Safari引入,IE9以前可使用一些类库在IE中模拟canvas,大部分的API都不在canvas元素自身定义,canvas元素自身属性与常规的HTML元素并无多大区别, 它的绘图API都定义在一个CanvasRenderingContext2D
对象上(这里简单翻译成上下文对象),该对象经过getContext()
方法得到,代码示例:canvas
<html> <head> <title>坐标系demo</title> </head> <body> <canvas id = 'square' width= 200 heigth=200></canvas> </body> <script> var canvas = document.getElementById('square') var ctx = canvas.getContext('2d')//2d表示画板维度,输入3d将获得一个更为复杂的3d图形API,也称WebGL
图像绘制须要参考坐标系定位, canvas的默认坐标系即画布左上角原点(0,0),可是若是图像的每次绘制都参考一个固定点将缺乏灵活性,因而canvas引入了“当前坐标系”的概念,所谓“当前坐标系”即指图像在此时绘制的时候所参考的坐标系,它也会做为图像状态(图像状态的概念将在后介绍)的一部分。好比rotate
旋转操做,改变当前坐标系也就是改变了rotate
的参考点,试想下若是没有当前坐标系的概念,不管是旋转,缩放,倾斜等操做不就只能参考画布左上角原点了吗(默认坐标系)?iphone
canvas提供了translate()
和setTransform()
这两个方法分别影响当前坐标系与默认坐标系。svg
translate()
与setTransform()
方法translate()
方法将坐标原点进行上下左右移动。它所影响的就是在图像在绘制的时候所参考的“当前坐标系”,举个例子?:spa
能够直接在demo中操做观察:.net
坐标系DEMO翻译
代码:3d
<html> <head> <title>坐标系demo</title> </head> <body> <canvas id = 'square' width= 200 heigth=200></canvas> </body> <script> var canvas = document.getElementById('square') var ctx = canvas.getContext('2d') ctx.beginPath() ctx.translate(20,20) //translate影响了当前坐标系 ctx.moveTo(0,0) ctx.lineTo(100,20) ctx.stroke() </script> </html>
无任何坐标系变化的图像绘制:rest
translate()
方法将坐标原定移动到(20,20)后获得当前坐标系后的绘制code
了解这点后setTransform()
也很容易,该方法影响的是默认坐标系,也就是说它并不是将原点移来移去,而是重置当前坐标系,定义一个新的默认坐标系,什么叫影响默认坐标系,好比说前面的translate()
所移动的坐标原点(0,0)仍是初始的默认坐标系,而如今setTransform()
所影响的就是这个原点(0,0)的坐标系,仍是以前的demo,当加入ctx.setTransform(1,0.5,-0.5,1,30,10)
这条语句后,图像绘制将变成:
这是由于setTransform()
将默认坐标系从新定义了,因而translate()
基于新的默认坐标系来获得当前坐标系。理解了这两个概念也就掌握了canvas中坐标系的变换。
setTransform()
与transform()
方法setTransform()
这个API略复杂, 它所接受的参数与transform()
(使用transform()
可直接获得一个变换结构,可代替rotate()
等方法,而且更为灵活)同样为6个参数,setTransform(a,b,c,d,e,f)
而坐标系变化的原理就是经过与这6个参数进行如下运算后得出的:
x' = ax + cy +e
y' = bx + dy +f
这种坐标系变换也被称为仿射变换(affine transform),关于该变换的栗子可参考这两篇博客:
?Html5 Canvas 变换矩阵与坐标变形之间的关系
路径是绘制全部图形的基础,不一样于SVG中path
使用属性M
,L
,A
等控制的XML文档,canvas调用上下文对象的方法来完成路径的绘制,调用beginPath()
开始一段新路径,每段路经又有子路径,正是依靠这些子路径使得图造成形。调用beginPath()
后调用MoveTo()
开始一段子路径。绘制完成后使用closePath()
闭合路径,从而造成一个闭合区域,这时候就可使用fill()
等方法填充该区域了。每次开始一段新路径的绘制必须再次调用beginPath()
,不然新绘制的路径将做为以前路径的子路径继续绘制。
相似于lineTo()
是最简单的直线段路径方法, canvas还提供了bezierCurveTo()
和quadraticCurveTo()
这些复杂的曲线路径方法,很是复杂,因此估计通常这种操做仍是先找轮子解决。
另外须要注意的是,当一条路径的两条子路径不相交的时候(好比绘制一个镂空的图形),画布将采用“非零绕数原则”判断某点是在路径内仍是路径外, 这样以便于填充的时候区别哪些区域是须要填充的。
有关非零绕数原则的原理能够参考这里:mark? 非零环绕数规则和奇-偶规则
canvas的属性与方法与咱们面向对象中的属性方法并无太大区别,只是这里涉及到了一个图像状态的概念。在canvas中,没法经过getContext()
方法得到多个上下文(context)对象,而图像属性都是基于canvas的上下文对象,也就是说没法同时拥有两个属性。形象地比喻就是图像属性就像画笔, 粗细,大小,颜色。因为同一时间只能有一个上下文对象因此只能同一时间使用一支画笔。这时候当须要其它的图像属性(另外一支画笔)的时候就只能经过保存当前图像状态,而后新建一个图像状态来切换。
这时候就须要借助save()
和restore()
来切换图像状态,每次save()
都将保存当前图像状态,图像状态包括当前的图像属性,当前坐标系,裁剪区域等信息。好比如下demo以两种颜色画线:
直接在demo中修改代码观察图像状态demo
JS代码:
var canvas = document.getElementById('square') var ctx = canvas.getContext('2d') ctx.beginPath() ctx.strokeStyle = "red" ctx.moveTo(0,0) ctx.lineTo(100,20) ctx.stroke() ctx.save()//保存当前图像状态(画笔) ctx.beginPath() ctx.strokeStyle = "blue" ctx.moveTo(0,0) ctx.lineTo(100,40) ctx.stroke() ctx.restore()//恢复到最近保存图像状态(画笔) ctx.beginPath() ctx.moveTo(0,0) ctx.lineTo(100,60) ctx.stroke()
输出以下:
这些图像属性包括:
fillStyle
font
globalAlpha
globalCompositeOperation
lineCap
lineJoin
lineWidth
miterLimit
textAlign
textBaseline
shadowBlur
shadowColor
shadowOffsetX
shadowOffsetY
strokeStyle
通常的纯色背景填充可使用fillStyle
属性,可是当涉及更复杂的图片或者渐变色填充就须要CanvasPattern
和CanvasGradient
对象了,能够经过creatPattern()
方法获得CanvasPattern
,这里须要注意的是该API不只能够代入通常的图片,也可使用canvas元素,好比画面外一个不可见的canvas元素用于插入。
关于这两个API的细节直接参考文档:
基于像素的canvas能够实现针对单个像素的操做,这也是画布底层的API,经过调用getImageData()
方法将返回一个ImageData
对象,该对象表示画布中原始的RGBA像素信息,经过调用creatImageData()
方法也能够建立一个空的ImageData
对象,最后putImageData()
方法将处理后的像素输出到画布中。
微软有篇不错的教程(使用 Canvas 将彩色照片变成黑白照片)解释像素操做,其中的操做是将彩色照片转成灰白,使用的原理是将RGB三个份量提取出来,通过计算后(关键计算语句以下)从新赋值为灰度变量。
myGray = parseInt((myRed + myGreen + myBlue) / 3); // Assign average to red, green, and blue. myImage.data[i] = myGray; myImage.data[i + 1] = myGray; myImage.data[i + 2] = myGray;