本章内容:使用 <canvas> 元素、绘制简单 2D 图形
不用说,HTML5添加的最受欢迎的功能就是 <canvas> 元素。<canvass> 元素最早是由苹果公司推出的,当时主要用在其Dashboard 软件中。很快,HTML5 加入了这个元素,主流浏览器也迅速开始支持它。
与浏览器环境中的其他组件类似, <canvas> 由机组 API 构成,但并非所有浏览器都支持所有这些API。除了具备基本绘图能力的2D上下文,<canvas> 还建议了一个名为 WebGL的 3D上下文。
一、基本用法
要使用 <canvas> 元素,必须先设置其 width 和 height 属性,可以指定绘图的区域大小。在开始和结束标签中的内容是后备信息,当浏览器不支持 <canvas> 元素时,就会显示这些信息。
<canvas id="canvas" width="200" height="200"> A drawing of something. </canvas>
要在这块画布上绘图,需要取得绘图上下文。而取得绘图上下文对象的引用,需要调用 getContext() 方法并传入上下文的名字。传入“2d”,就可以获取2D上下文
var canvas = document.getElementById('canvas')
if (canvas.getContext) { // 确定兼容
var ctx = canvas.getContext('2d')
}
使用 toDataURL() 方法,可以导出在 <canvas> 元素上绘制的图像。接受一个参数:图像的MIME类型格式
取得画布中的衣服PNG 格式的图像
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
// 取得图像的数据 URI
var imgURI = canvas.toDataURL('image/png')
// 显示图像
var image = document.createElement('img')
image.src = imgURI
document.body.appendChild(image)
}
二、2D上下文
2D上下文的坐标开始于 <canvas> 元素的左上角,原点坐标是(0, 0)。所有坐标值都基于这个原点计算,默认情况下,width 和 height 表示水平和垂直两个方向上可用的像素数目。
2.1、填充和描边
2D 上下文的两种基本绘图操作是填充和描边。填充,就是用指定的样式(颜色、渐变或图像)填充图像;描边,就是只在图像的边缘画线。对应的两个属性分别是:fillstyle、strokeStyle;这两个属性的值可以是字符串、渐变对象或模式对象,而且他们的默认值都是 ‘#000000’
var ctx = canvas.getContext('2d')
ctx.strokeStyle = 'red'
ctx.fillStyle = '#0000ff'
以上代码将 描边设置为 红色,填充设置为 蓝色
2.2、绘制矩形
矩形是唯一一种在 2D 上下文中绘制的形状。与矩形有关的方法包括 fillRect()、strokeRect()、clearRect()。这三个方法都能接受4个参数:
- 矩形的x坐标
- 矩形的y坐标
- 矩形的宽度
- 矩形的高度
fillRect() 方法在画布上绘制的矩形会填充指定的颜色
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
// 获取 2d 上下文
var ctx = canvas.getContext('2d')
// 绘制紫色矩形
ctx.fillStyle = 'violet'
ctx.fillRect(10, 10, 50, 50)
// 绘制半透明的蓝色矩形
ctx.fillStyle = 'rgba(0, 0, 255, .5)'
ctx.fillRect(30, 30, 50, 50)
}
![](https://img.haomeiwen.com/i18002187/e6a5e6c4bd8ec14f.png)
strokeRect() 方法在画布上绘制的矩形会使用指定的颜色描边,描边颜色通过 strokeStyle 属性指定。
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
// 获取 2d 上下文
var ctx = canvas.getContext('2d')
// 绘制紫色矩形
ctx.strokeStyle = 'violet'
ctx.strokeRect(10, 10, 50, 50)
// 绘制半透明的蓝色矩形
ctx.strokeStyle = 'rgba(0, 0, 255, .5)'
ctx.strokeRect(30, 30, 50, 50)
}
![](https://img.haomeiwen.com/i18002187/fa3a6cb91ef407fa.png)
clearRect() 方法用于清除画布上的矩形区域。这个方法可以把绘制上下文中的某一矩形区域变透明。就可以生成有意思的效果
例如把某个形状切掉一块
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
// 获取 2d 上下文
var ctx = canvas.getContext('2d')
// 绘制紫色矩形
ctx.fillStyle = 'violet'
ctx.fillRect(10, 10, 50, 50)
// 绘制半透明的蓝色矩形
ctx.fillStyle = 'rgba(0, 0, 255, .5)'
ctx.fillRect(30, 30, 50, 50)
// 在两个矩形重叠的地方清除一个小矩形
ctx.clearRect(40, 40, 10, 10)
}
![](https://img.haomeiwen.com/i18002187/404ffccc598c336f.png)
2.3、绘制路径
2D 绘制上下文支持很多在画布上绘制路径的方法。通过路径可以创造出复杂的形状和线条。要绘制路径,首先必须调用 beginPath() 方法,表示要开始绘制新路径。然后,在通过调用下列方法来实际地绘制路径。
- arc(x, y, radius, startAngle, endAngle, conterclockwise):以(x, y) 为圆心绘制一条弧线,弧线半径为 radius,起始和结束角度(用弧度表示)分别为 startAngle、endAngle。最后一个参数表示是否逆时针绘制,值为false表示顺时针
- arcTo(x1, y1, x2, y2, radius):从上一点开始绘制一条弧线,到(x2, y2) 为止,并且以给定的半径 radius 穿过(x1, y1)
- bezierCurveTo(c1x, c1y, c2x, c2y, x, y):从上一点开始绘制一条曲线,到(x, y)为止,并且以(c1x, c1y)和(c2x, c2y)为控制点
- lineTo(x, y):从上一点开始绘制一条直线,到(x, y) 为止
- moveTo(x, y):将绘图游标移动到(x, y),不画线
- quadraticCurveTO(cx, cy, x, y):从上一点开始绘制一条二次曲线,到(x,y)为止,并且以(cx,cy)作为控制点
- rect(x, y, width, height):从点(x,y)开始绘制一个矩形,宽度和高度分别由 width 和 heigh指定。这个方法绘制的是矩形路基,而不是 strokeRect() 和 fillRect() 所绘制的独立的形状。
创建路径后,如果想绘制一条连接到路径起点的线条,可以通过 closePath()。如果路径已经绘制完成,可以对他进行填充fille() 或者 是 描边 stroke()。最后还可以调用 clip(),这个方法可以在路径上创建一个剪切区域。
绘制一个钟表表盘
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
// 获取 2d 上下文
var ctx = canvas.getContext('2d')
// 开始路径
ctx.beginPath()
// 绘制外圆
ctx.arc(100, 100, 99, 0, 2 * Math.PI, false)
// 绘制内圆
ctx.moveTo(194, 100)
ctx.arc(100, 100, 94, 0, 2 * Math.PI, false)
// 绘制分针
ctx.moveTo(100, 100)
ctx.lineTo(100, 15)
// 绘制时针
ctx.moveTo(100, 100)
ctx.lineTo(35, 100)
// 描边路径
ctx.stroke()
}
![](https://img.haomeiwen.com/i18002187/fdf700dc5274fe3f.png)
在 2D 绘图上下文中,路径是一种主要的绘图方式,因为路径能为要绘制的图形提供更多控制。由于路径的使用很频繁,就有了一个名为 isPointInPath() 的方法。这个参数接受 x 和 y坐标作为参数,用于在路径被关闭之前确定画布上的某一点是否位于路径之上
if (ctx.isPointInPath(100, 100)) { // 坐标 (100,100 )是否在当前路径上
// todo
}
2.4、绘制文本
绘制文本主要有两个方法:fillText() 和 strokeText()。这两个方法都可以接受4个参数:
- 要回执的文本
- x坐标
- y 坐标
- 可选的最大像素宽
这两个方法都已下列 3个属性为基础
- font:表示文本样式、大小及字体,用CSS中指定字体的格式来指定,例如:“10px Arial”。
- textAlign:表示文本对齐方式。可能的值有“start”、“end”、“left”、“right”、“center”。建议使用“start” 和 “end”,不要使用“left” 和 “right”,因为前者的意思更稳妥,能同时适合从左到右和从右到左显示的语言
- textBaseline:表示文本的基线。可能的值有“top”、“hanging”、“middle”、“alphabetic”、“ideographic”、“bottom”
在表盘上绘制文本
// 居中对齐
ctx.font = 'bold 14px Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText('12', 100, 20)
// 起点对齐
ctx.textAlign = 'start'
ctx.fillText('12', 100, 40)
// 终点对齐
ctx.textAlign = 'end'
ctx.fillText('12', 100, 60)
![](https://img.haomeiwen.com/i18002187/5a06eb7267660127.png)
类似的,修改 textBaseline 属性的值可以调整文本的垂直对齐方式
由于绘制文本比较复杂,2D上下文提供了 辅助确定文本大小的方法 measureText()。这个方法接受一个参数,即要回执的文本;返回一个 textMetrics 对象,目前这个返回的 对象 只要 一个 width 属性
var fontSize = 100
ctx.font = fontSize + 'px Arial'
while (ctx.measureText('Hello World!').width > 140) {
fontSize--
ctx.font = fontSize + 'px Arial'
}
ctx.fillText('Hello World!', 10, 10)
ctx.finnText('Font size is ' + fontSize + 'px', 10, 50)
以上代码会动态的计算 文本的字体大小,以符合 理想的文本宽度
2.5、变换
绘制上下文时,会默认初始化变换矩阵,在默认的变换矩阵下,所有处理都按描述直接绘制。为绘制上下文应用变换,会导致使用不同的变换矩阵应用处理,从而产生不通过的结果
可以通过如下方法来修改变换矩阵。
- rotate(angle):围绕原点旋转图像 angle 弧度。
- scale(scaleX, scaleY):缩放图像,在x方向乘以 scaleX,在y方向乘以 scaleY。scaleX 和 scaleY 的默认值都是 1.0
- translate(x, y):将坐标原点移动到(x, y)。执行这个变换之后,坐标(0, 0)会变成之前由(x, y)表示的点
-
transform(m1_1, m1_2, m2_1, m2_2, dx, dy):直接变换矩阵,方式是乘以如下矩阵
m1_1 m1_2 dx
m2_1 m2_2 dy
0 0 1 - setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):将变换矩阵重置为默认状态,然后再调用 transform()
变换表盘的原点 到 表盘中心去
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
// 获取 2d 上下文
var ctx = canvas.getContext('2d')
// 开始路径
ctx.beginPath()
// 绘制外圆
ctx.arc(100, 100, 99, 0, 2 * Math.PI, false)
// 绘制内圆
ctx.moveTo(194, 100)
ctx.arc(100, 100, 94, 0, 2 * Math.PI, false)
// 变换原点
ctx.translate(100, 100)
// 绘制分针
ctx.moveTo(0, 0)
ctx.lineTo(0, -85)
// 绘制时针
ctx.moveTo(0, 0)
ctx.lineTo(-65, 0)
// 描边路径
ctx.stroke()
// 居中对齐
ctx.font = 'bold 14px Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText('12', 0, -80)
}
因为修改了原点,所以后面的路径坐标也需要修改
使用 rotate() 选择图形
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
// 获取 2d 上下文
var ctx = canvas.getContext('2d')
// 开始路径
ctx.beginPath()
// 绘制外圆
ctx.arc(100, 100, 99, 0, 2 * Math.PI, false)
// 绘制内圆
ctx.moveTo(194, 100)
ctx.arc(100, 100, 94, 0, 2 * Math.PI, false)
// 变换原点
ctx.translate(100, 100)
// 选择表针
ctx.rotate(1)
// 绘制分针
ctx.moveTo(0, 0)
ctx.lineTo(0, -85)
// 绘制时针
ctx.moveTo(0, 0)
ctx.lineTo(-65, 0)
// 描边路径
ctx.stroke()
// 居中对齐
ctx.font = 'bold 14px Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText('12', 0, -80)
}
![](https://img.haomeiwen.com/i18002187/bdce9e8bc4be8296.png)
在 2d上下文中,fillStyle、strokeStyle 等属性,会在当前上下文中一直有效。如果你知道将来还要返回某组属性与变换的组合,可以调用 save() 方法。调用这个方法后,当时的所有设置都会进入一个栈结构,得以妥善保管。然后 调用 restore() 可以恢复之前的状态。连续调用 save() 可以把更多设置保存在栈结构中,之后再连续调用 restore() 则可以一级一级返回。
var ctx = canvas.getContext('2d')
ctx.fillStyle = 'red'
ctx.save()
ctx.fillStyle = 'cyan'
ctx.save()
ctx.fillStyle = 'violet'
ctx.save()
ctx.fillStyle = 'pink'
ctx.save()
ctx.fillRect(0, 0, 50, 50) // 粉色 ---> 当前上下文的环境
ctx.restore()
ctx.fillRect(50, 50, 50, 50) // 粉色 ---> save() 栈中保存的最后一项
ctx.restore()
ctx.fillRect(100, 100, 50, 50) // 紫色 ---> save() 中保存的倒数第二项
![](https://img.haomeiwen.com/i18002187/f4f1ba266e2c605a.png)
需要注意的是,save() 方法保存的只是对绘图上下文的设置和变换
2.6、绘制图像
2D 绘制上下文内置了对图像的支持, drawImage(),调用这个方法是,可以使用三种不同参数的组合。最简单的方式是 传入三个参数:
- 图片元素
- 绘制图像的起点 x 坐标
- 绘制图像的起点 y 坐标
var image = document.getElementsByTagName('img')[0]
ctx.drawImage(image, 10, 10)
如果你想改变绘制后图像的大小,可以再多传入两个参数,分别表示目标宽度和目标高度。即为传入五个参数:
- 图片的对象
- 绘制在画布上的坐标 x y
- 图片的显示大小 w h
ctx.drawImage(image, 240, 27, 40, 42)
除上诉两种方式,还可以选择把图像中的某个区域绘制到上下文中。这种调用方法总共需要传入九个参数:
- 图片对象
- 图片上定位的坐标x y
- 图片上的截取区域w h
- 画布上的坐标 x y
- 画布上的图片大小 w h
ctx.drawImage(image, 10, 10, 170, 150, 400, 50, 170, 150)
![](https://img.haomeiwen.com/i18002187/acf87b370bcfd1d0.png)
需要注意的是,无论用哪种方式(获取DOM元素,或者创建DOM元素)选择图片,都需要等待这个元素加载完毕后,进行引用,所以一般会把 canvas 绘制的代码,放到 img 的 load 事件加载完毕中执行。
这些操作的结果可以通过 toDataURL() 方法获得。但是需要注意的是,这个方法不能跨域,否则会抛出异常。
2.7、阴影
2D 上下文会根据以下几个属性的值,自动为形状或路径绘制出阴影。
- shadowColor:用CSS 颜色格式表示的阴影颜色,默认为黑色。
- shadowOffsetX:形状或路径 x 轴方向的阴影偏移量,默认为0。
- shadowOffsetY:形状或路径 y 轴方向的阴影偏移量,默认为0。
- shadowBlur:模糊的像素数,默认0,即不模糊。
只要在绘制前为他们设置适当的值,就能自动产生阴影。
var ctx = canvas.getContext('2d')
ctx.shadowOffsetX = 5
ctx.shadowOffsetY = 5
ctx.shadowBlur = 4
ctx.shadowColor = 'rgba(0, 0, 0, .5)'
// 绘制红色矩形
ctx.fillStyle = 'red'
ctx.fillRect(10, 10, 50, 50)
![](https://img.haomeiwen.com/i18002187/713c2908e9d4343d.png)
不同浏览器对阴影的支持有一些差异。
2.8、渐变
渐变由 CanvasGradient 实例表示,很容易通过 2D 上下文来创建和修改。要创建一个新的线性渐变,可以调用 createLinearGradient(),这个方法接受4个参数:
- 起点的 x 坐标
- 起点的 y 坐标
- 终点的 x 坐标
- 终点的 y 坐标
调用这个方法之后,它就会创建一个指定大小的渐变,并返回 CanvasGradient 对象的实例。
下一步就是使用 addColorStop() 方法来指定色标。这个方法接收两个参数:色标位置和 CSS 颜色值。色标位置是一个 0(开始的颜色)到1(结束的颜色)之间的数字。例如:
var gradient = ctx.createLinearGradient(30, 30, 170, 70)
gradient.addColorStop(0, 'red')
gradient.addColorStop(
然后就可以在画布点(30,30)到点(70,70)来进行绘制,将 fillStyle 换货 strokeStyle 设置为这个对象,从而使用渐变来绘制形状或描边
var gradient = ctx.createLinearGradient(30, 30, 170, 70)
gradient.addColorStop(0, 'red')
gradient.addColorStop(1, 'blue')
ctx.fillStyle = gradient
ctx.fillRect(30, 30, 140, 40)
![](https://img.haomeiwen.com/i18002187/91f74b1826d38a49.png)
如果没有把矩形绘制到恰当的位置,那可能就只会显示部分渐变效果。
要创建径向渐变(或放射渐变),可以使用 createRadialGradient() 方法。这个方法接受6个参数,对应两个圆的圆心和半径:
- 起点圆的圆心 x 坐标
- 起点圆的圆心 y 坐标
- 起点圆的半径
- 终点圆的圆心 x 坐标
- 终点圆的圆心 y 坐标
- 终点圆的半径
var gradient = ctx.createRadialGradient(55, 55, 10, 55, 55, 30)
gradient.addColorStop(0, 'cyan')
gradient.addColorStop(1, 'violet')
// 绘制渐变矩形
ctx.fillStyle = gradient
ctx.fillRect(30, 30, 50, 50)
![](https://img.haomeiwen.com/i18002187/fd36d180345ea845.png)
2.9、模式
模式其实就是重复的图像,可以用来填充或描边图形。要创建一个新模式,可以调用createPattern() 方法并传入两个参数:
- 一个 HTML <img> 元素
- 表示如何重复图像的字符串(与CSS的 background-repeat 属性值相同)
- repeat
- repeat-x
- repeat-y
- no-repeat
var ctx = canvas.getContext('2d')
var img = document.images[0]
img.onload = function() {
var pattern = ctx.createPattern(img, 'repeat')
// 绘制矩形
ctx.fillStyle = pattern
ctx.fillRect(10, 10, 150, 150)
}
![](https://img.haomeiwen.com/i18002187/62600c30385530c8.png)
2.10、使用图像数据
2D 上下文的一个明显的长处就是,可以通过 getImageData() 取得原始图像数据。这个方法接收4个参数:
- 要取得数据的画面区域的 x 坐标
- 要取得数据的画面区域的 y 坐标
- 该区域的像素宽度
- 该区域的像素高度
var imageData = ctx.getImageData(20, 20, 10, 10)
返回的对象是 ImageData 的实例。每个 ImageData 对象都有三个属性:width、height、data。其中data属性是一个数组,保存着图像每一个像素的数据。在data中每一个像素用4个元素来表示:红、绿、蓝、透明值
var data = imageData.data,
red = data[0],
green = data[1],
blue = data[2],
alpha = data[3]
console.log(red, green, blue, alpha)
数组中每个元素的值都介于 0 到 255 之间(包括0 和 255)。能够直接访问到原始图像数据,就能够以各种方式来操作这些数据。例如,通过修改图像数据,可以像下面这样创建一个简单的 灰阶过滤器
var canvas = document.getElementById('canvas')
if (canvas.getContext) {
var ctx = canvas.getContext('2d')
var img = document.images[0]
img.onload = function() {
// 绘制原始图像
ctx.drawImage(img, 0, 0)
// 取得图像数据
var imageData = ctx.getImageData(0, 0, img.width, img.height)
var data = imageData.data
for(var i = 0, len = data.length; i < len; i++) {
var red = data[i]
var green = data[i+1]
var blue = data[i+2]
var alpha = data[i+3]
// 求得 rgb 平均值
var average = Math.floor( (red + green + blue) / 3)
// 设置颜色值,透明不变
data[i] = data[i+1] = data[i+2] = average
}
// 回写 图像数据并显示结果
imageData.data = data
ctx.putImageData(imageData, 0, 0)
}
![](https://img.haomeiwen.com/i18002187/fe1784fcbdc159d6.png)
通过操作原始像素值不仅能实现灰阶过滤,还能实现其他功能。
2.11、合成
-
globalAlpha
globalAlpha 是一个介于 0 和 1 之间的值,用于指定所有绘制的透明度,默认值为0.如果所有后续操作都要基于相同的透明度,就可以先把 globalAlpha 设置为适当值,然后绘制,最后再把它设置回默认值0。
var ctx = canvas.getContext('2d')
// 绘制红色矩形
ctx.fillStyle = 'red'
ctx.fillRect(10, 10, 50, 50)
// 修改全局透明
ctx.globalAlpha = .5
// 绘制红色 半透明 矩形
ctx.fillRect(10, 70, 50, 50)
![](https://img.haomeiwen.com/i18002187/eeb878b1dc18042b.png)
第二个属性 **globalCompositionOperation 表示后绘制的图像怎样与先绘制的图像结合。这个属性的值是字符串,可能的值如下:
- source-over (默认值):后绘制的图像位于先绘制的图像上方。
- source-in:后绘制的图形与先绘制的图形重叠的部分可见,两者其它部分完全透明。
- source-out:后绘制的图形与先绘制的图形重叠的部分可见,先绘制的图形完全透明。
- source-atop:后绘制的图形与先绘制的图形重叠的部分可见,先绘制图形不受影响。
- destination-over:后绘制的图形位于先绘制的图形下发,只有之前透明像素下的部分才可见。
- destination-in:后绘制的图形位于先绘制的图形下方,两者不重叠的部分完全透明。
- destination-out:后绘制的图形擦除与先绘制的图形重叠的部分。
- destination-atop:后绘制的图形位于先绘制的图形下方,在两者不重叠的地方,先绘制的图形会变透明。
- lighter:后绘制的图形与先绘制的图形重叠部分的值相加,使该部分变亮。
- copy:后绘制的图形完全代替与之重叠的先绘制图形。
- xor后绘制的图形与先绘制的图形重叠的部分执行“异或”操作
var ctx = canvas.getContext('2d')
// 绘制红色矩形
ctx.fillStyle = 'red'
ctx.fillRect(10, 10, 50, 50)
// 设置合成操作
ctx.globalCompositeOperation = "destination-over"
// 绘制蓝色矩形
ctx.fillStyle = 'blue'
ctx.fillRect(30, 30, 50, 50)
![](https://img.haomeiwen.com/i18002187/16a101eb1c2c4892.png)
把 globalCompositionOperation 设置为 “destination-over” 之后,红色矩形跑到了蓝色矩形上面。
在使用 globalCompositionOperation 的情况下,一定要多测试一些浏览器。因为不同浏览器对这个属性的实现仍然存在较大的差别。
网友评论