canvas

基本用法

要使用<canvas>元素必须先设置其widthheight属性,指定可以绘图的区域大小。出现在开始和结束标签中的内容是后备信息,如果浏览器不支持<canvas>元素,就会显示这些信息。

<canvas id='drawing' width='200' height='200'>您的浏览器不支持canvas</canvas>

<canvas>元素对应的DOM元素对象也有widthheight属性,可以随意修改。而且,也能通过css为该元素添加样式,如果不添加任何样式或者不绘制任何图形,在页面中是看不到该元素的。
要在画布上绘图,需要取得绘图上下文。而取得绘图上下文的引用,需要调用getContext()方法并传入上下文的名字。传入2d就可以取得2D上下文对象。

var drawing = document.getElementById('drawing');
// 确定浏览器支持<canvas>元素
if(drawing.getContext) {
  var context = drawing.getContext('2d');
  // 更多操作
}

使用toDataURL()方法可以导出<canvas>元素上绘制的图像。这个方法接收一个参数,即图像的MIME类型格式,而且适合用于创建图像的任何上下文。

var drawing = document.getElementById('drawing');
if(drawing.getContext) {
  var imgURI = drawing.toDataURL('image/png');
  var image = document.creatElement('img');
  image.src = imgURI;
  document.body.appendChild(image);
}

2D上下文

2D上下文的坐标开始于<canvas>元素的左上角,原点坐标是(0, 0),所有坐标值都基于这个原点计算,x值越大表示越靠右,y值越大表示越靠下。默认情况下,widthheight表示水平和垂直两个方向上可用的像素数目。

填充和描边

填充就是用指定的样式填充图形;描边就是只在图形的边缘画线。与这两个操作相关的属性是:fillStylestrokeStyle
这两个属性的值可以是字符串、渐变对象或模式对象,而且他们的默认值都是#000。如果为他们指定表示颜色的字符串值,可以使用CSS中指定颜色值的任何格式,包括颜色名、十六进制码、rgbrgbahslhsla

var drawing = document.getElementById('drawing');
if(drawing.getContext) {
  var context = drawing.getContext('2d');
  context.strokeStyle = 'red';
  context.fillStyle = '#00f';
}

设定这两个属性之后的所有涉及描边和填充的操作都将使用这两个样式,直至重新设定这两个值。

绘制矩形

与矩形有关的方法包括fillRect()strokeRect()clearRect()。这三个方法都能接收四个参数:矩形的x坐标、矩形的y坐标、矩形宽度和矩形高度。这些参数的单位都是像素。
fillRect()方法绘制的矩形会填充指定的颜色。填充的颜色通过fillStyle属性指定。

var drawing = document.getElementById('drawing');
if(drawing.getContext) {
  var context = drawing.getContext('2d');
  // 绘制红色矩形
  context.fillStyle = '#f00';
  context.fillRect(10, 10, 50, 50);
}

strokeRect()方法绘制的矩形会使用指定的颜色描边。描边颜色通过strokeStyle属性指定。

var drawing = document.getElementById('drawing');
if(drawing.getContext) {
  var context = drawing.getContext('2d');
  // 绘制红色描边矩形
  context.strokeStyle = '#f00';
  context.strokeRect(10, 10, 50, 50);
}

clearRect()方法用于清除画布上的矩形区域。本质上,这个方法可以把绘制上下文中的某一矩形区域变透明。

绘制路径

通过路径可以创造出复杂的形状和线条。要绘制路径,首先必须先调用beginPath()方法,表示要开始绘制新路径。然后,再通过调用下列方法来实际的绘制路径。

  • arc(x, y, radius, startAngle, endAngle, counterclockwise):以(x, y)为圆心绘制一条弧线,弧线半径为radius,起始和结束角度(用弧度表示)分别为startAngleendAngle。最后一个参数表示是否按逆时针方向,值为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)开始绘制一个矩形,宽度和高度分别由widthheight指定。这个方法绘制的是矩形路径,而不是strokeRect()fillRect()绘制的独立的形状。

创建了路径后,接下来有几种可能的选择。如果想绘制一条连接到路径起点的线条,可以调用closePath()。如果路径已经完成,想用fillStyle填充它,可以调用fill()方法。另外,还可以调用stroke()方法对路径描边,描边使用的是strokeStyle。最后还可以调用clip()方法在路径上创建一个剪切区域。

var drawing = document.getElementById('drawing');
if(drawing.getContext) {
  var context = drawing.getContext('2d');
  // 开始路径
  context.beginPath();
  // 绘制外圆
  context.arc(100, 100, 99, 0, 2 * Math.PI, false);
  // 绘制内圆
  context.moveTo(194, 100);
  context.arc(100, 100, 94, 0, 2 * Math.PI, false);
  // 绘制分针
  context.moveTo(100, 100);
  context.lineTo(100, 15);
  // 绘制时针
  context.moveTo(100, 100);
  context.lineTo(35, 100);
  // 描边路径
  context.stroke();
}

由于路径的使用很频繁,所以就有了一个名为isPointInPath()的方法。这个方法接收xy坐标作为参数,用于在路径被关闭之前确定画布上的某一点是否位于路径上。

if(context.isPointInPath(100, 100)) {
  console.log('Point (100, 100) is in the path');
}

绘制文本

绘制文本主要有两个方法:fillText()strokeText()。这两个方法都接收4个参数:要绘制的文本字符串、x坐标、y坐标和可选的最大像素宽度。而且,这两个方法都以下列3个属性为基础。

  • font:表示文本样式、大小及字体,用CSS中指定字体的格式来指定,如10px Arial
  • textAlign:表示文本对齐方式。可能的值有:startendleftrightcenter
  • textBaseline:表示文本的基线。可能的值有:tophangingmiddlealphabeticideographicbottom

这几个属性都有默认值,因此没有必要每次使用它们都重新设置一遍值。fillText()方法使用fillStyle属性绘制文本,而strokeText()方法使用strokeStyle属性为文本描边。

// 正常
context.font = 'bold 14px Arial';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('12', 100, 20);
// 起点对齐
context.textAlign = 'center';
context.fillText('12', 100, 20);
// 终点对齐
context.textAlign = 'center';
context.fillText('12', 100, 20);

变换

创建绘制上下文时,会以默认值初始化变换矩阵,在默认的变换矩阵下,所有处理都按描述直接绘制。
可通过下面的方法来修改变换矩阵。

  • rotate(angle):围绕原点旋转图像angle弧度。
  • scale(scaleX, scaleY):缩放图像,在x方向乘以scaleX,在y方向乘以scaleYscaleXscaleY的默认值都是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 drawing = document.getElementById('drawing');
if(drawing.getContext) {
  var context = drawing.getContext('2d');
  // 开始路径
  context.beginPath();
  // 绘制外圆
  context.arc(100, 100, 99, 0, 2 * Math.PI, false);
  // 绘制内圆
  context.moveTo(194, 100);
  context.arc(100, 100, 94, 0, 2 * Math.PI, false);
  // 变换原点
  context.translate(100, 100);
  // 绘制分针
  context.moveTo(0, 0);
  context.lineTo(0, -85);
  // 绘制时针
  context.moveTo(0, 0);
  context.lineTo(-65, 0);
  // 描边路径
  context.stroke();
}

虽然没有什么办法把上下文中的一切都重置回默认值,但有两个方法可以跟踪上下文的状态变化。如果将来还要返回某组属性与变换的组合,可以调用save()方法。调用这个方法后,当时的所有设置都会进入一个栈结构,得以妥善保管。然后可以对上下文进行其他修改。等想要回到之前保存的设置时,可以调用restore()方法,在保存设置的栈结构中向前返回一级,恢复之前的状态。连续调用save()可以把更多设置保存到栈结构中,之后再连续调用restore()则可以一级一级返回。

context.fillStyle = '#f00';
context.save();
context.fillStyle = '#0f0';
context.translate(100, 100);
context.save();
context.fillStyle = '#00f';
context.fillRect(0, 0, 100, 100); // 从点(100, 100)开始绘制蓝色矩形
context.restore();
context.fillRect(10, 10, 100, 100); // 从点(110, 110)开始绘制绿色矩形
context.restore();
context.fillRect(0, 0, 100, 100); // 从点(0, 0)开始绘制红色矩形

save()方法保存的只是对绘图上下文的设置和变换,不会保存绘图上下文的内容。

绘制图像

如果想把图像绘制到画布上,可以使用drawImage()方法。调用这个方法时,可以使用三种不同的参数组合。最简单的调用方式就是传入一个<img>元素,以及绘制该图像的起点的x坐标和y坐标。

var image = document.images[0];
context.drawImage(image, 10, 10);

上面的代码,绘制到画布上的图像大小与原始大小一样。如果要改变绘制后图像的大小,可以再多传两个参数,分别表示目标宽度和目标高度。

context.drawImage(image, 50, 10, 20, 30);

还可以选择把图像中的某个区域绘制到上下文中。这种调用方式总共需要传入9个参数:要绘制的图像、源图像的x坐标、源图像的y坐标、源图像的宽度、源图像的高度、目标图像的x坐标、目标图像的y坐标目标图像的高度和目标图像的宽度。这样调用drawImage()方法可以获得更多的控制。

context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);

除了给drawImge()方法传入Img元素,还可以传入另一个canvas元素作为其第一个参数。
结合使用drawImage()和其它方法,可以对图形进行各种操作。而操作的结果可以通路toDataURL()方法获得。不过有一个例外,即图像不能来自其它域。如果图像来自其它域,调用toDataURL()会抛出一个错误。

阴影

2D上下文会根据以下几个属性的值,自动为形状或路径绘制阴影。

  • shadowColor:用CSS颜色格式表示的阴影颜色,默认为黑色。
  • shadowOffsetX:形状或路径x轴方向的阴影偏移量,默认为0。
  • shadowOffsetY:形状或路径y轴方向的阴影偏移量,默认为0。
  • shadowBlur:模糊的像素数,默认为0,即不模糊。

这些属性的值都可以通过context对象来修改。只要在绘制前为他们设置适当的值,就能自动产生阴影。

var context = drawing.getContext('2d');
// 设置阴影
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = 'rgba(0, 0, 0, 0.5)';
// 绘制红色矩形
context.fillStyle = '#f00';
context.fillRect(10, 10, 50, 50);
// 绘制蓝色矩形
context.fillStyle = 'rgba(0, 0, 255, 1)';
context.fillRect(30, 30, 50, 50);

渐变

要创建一个新的线性渐变,可以调用createLinearGradient()方法。这个方法接收4个参数:起点的x坐标、起点的y坐标、终点的x坐标和终点的y坐标。调用这个方法后,它就会创建一个指定大小的渐变,并返回CanvasGradient对象的实例。
创建了渐变对象后,下一步就是使用addColorStop()方法来指定色标。这个方法接收两个参数:色标位置和CSS颜色值。色标位置是一个0(开始颜色)到1(结束颜色)之间的数字。

var gradient = context.createLinearGradient(30, 30, 70, 70);
gradient.addColorStop(0, 'white');
gradient.addColorStop(1, 'black');
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);

为了让渐变覆盖整个矩形,而不是仅仅应用到矩形的一部分,矩形和渐变对象的坐标必须匹配才行。如果没有把矩形绘制到恰当的位置,可能只会显示部分渐变效果。确保渐变和形状对象很重要,有时候可以考虑使用函数来确保坐标合适。

function createRectLinearGradient(context, x, y, width, height) {
  return context.createLinearGradient(x, y, x + width, y + height);
}
var gradient = createRectLinearGradient(context, 30, 30, 50, 50);
gradient.addColorStop(0, 'white');
gradient.addColorStop(1, 'black');
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);

要创建径向渐变可以使用createRadialGradient()方法。这个方法接收6个参数,对应着两个圆的圆心和半径。前三个参数指定的是起点圆的圆心和半径,后三个参数指定的是终点圆的圆心和半径。可以把径向渐变想象成一个长圆桶,而这6个参数定义的正是这个桶的两个圆形开口的位置。如果把一个圆形开口定义得比另一个小一些,那这个圆筒就变成了圆锥体,而通过移动每个圆形开口的位置,就可以达到像旋转这个圆锥体一样的效果。
如果想从某个形状的中心点开始创建一个向外扩散的径向渐变效果,就要将两个圆定义为同心圆。

var gradient = createRadialGradient(55, 55, 10, 55, 55, 30);
gradient.addColorStop(0, 'white');
gradient.addColorStop(1, 'black');
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);

模式

模式其实就是重复的图像,可以用来填充或描边图形。要创建一个新模式可以调用createPattern()方法并传入两个参数:一个<img>元素和一个表示如何重复图像的字符串。第二个参数的值与background-repeat的值相同,包括repeatrepeat-xrepeat-yno-repeat

var image = document.images[0];
var pattern = context.createPattern(image, 'repeat');
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150);

createPattern()方法的第一个参数也可以是一个<video>元素,或另一个<canvas>元素。

使用图像数据

通过getImageData()可以取得原始图像数据。这个方法接收4个参数:要取得其数据的画面区域的xy坐标以及该区域的像素宽度和高度。

// 取左上角坐标为(10, 5),大小为50*50px的区域的图像数据
var imageData = context.getImageData(10, 5, 50, 50);

这里返回的对象是ImageData的实例。每个ImageData对象有三个属性:widthheightdatadata属性是一个数组,保存着图像中每一个像素的数据。在data数组中,每一个像素用4个元素来保存,分别表示红、绿、蓝和透明度值。因此第一个像素的数据就保存在数组的第0到第3个元素中。

var data = imageData.data;
red = data[0];
green = data[1];
blue = data[2];
alpha = data[3];

数组中每个元素的值都介于0到255之间(包括0和255)。能够直接访问到原始图像数据,就能够以各种方式来操作这些数据。

// 简单的灰阶过滤器
var drawing = document.getElementById('drawing');
if(drawing.getContext) {
  var context = drawing.getContext('2d'),
    image = document.image[0], imageData, data, i,
    len, average, red, green, blue, alpha;
  // 绘制原始图像
  context.drawImage(image, 0, 0);
  // 取得图像数据
  imageData = context.getImageData(0, 0, image.width, image.height);
  data = imageData.data;
  for(var i = 0, len = data.length; i < len; i+=4) {
    red = data[i];
    green = data[i + 1];
    blue = data[i + 2];
    alpha = data[i + 3];
    // 求得rgb平均值
    average = Math.floor((red + green + blue) / 3);
    // 设置颜色值,透明度不变
    data[i] = average;
    data[i + 1] = average;
    data[i + 2] = average;
  }
  // 回显图像数据并显示结果
  imageData.data = data;
  context.putImageData(imageData, 0, 0);
}

合成

还有两个会应用到2D上下文中所有绘制操作的属性:globalAlphaglobalCompositionOperationglobalAlpha是一个介于0和1之间的值(包括0和1)。用于指定所有绘制的透明度。默认值为0。

// 绘制红色矩形
context.fillStyle = '#f00';
context.fillRect(10, 10, 50, 50);
// 修改全局透明度
context.globalAlpha = 0.5;
// 绘制蓝色矩形
context.fillStyle = 'rgba(0, 0, 255, 1)';
context.fillRect(30, 30, 50, 50);
// 重置全局透明度
context.globalAlpha = 0;

蓝色矩形会呈半透明效果,透过它可以看到下面的红色矩形。
globalCompositionOperation表示后绘制的图形怎样与先绘制的图形结合。这个属性的值是字符串,可能的值如下:

  • source-cover(默认值):后绘制的图形位于先绘制的图形上方。
  • source-in:后绘制的图形与先绘制的图形重叠的部分可见,两者其他部分完全透明。
  • source-out:后绘制的图形与先绘制的图形不重叠的部分可见,先绘制的图形完全透明。
  • source-atop:后绘制的图形与先绘制的图形重叠的部分可见,先绘制的图形不受影响。
  • destination-over:后绘制的图形位于先绘制的图形下方,只有之前透明像素下的部分才可见。
  • destination-in:后绘制的图形位于先绘制的图形下方,两者不重叠的部分完全透明。
  • destination-out:后绘制的图形擦除与先绘制的图形重叠的部分。
  • destination-atop:后绘制的图形位于先绘制的图形下方,在两者不重叠的地方,先绘制的图形会变透明。
  • lighter:后绘制的图形与先绘制的图形重叠的部分的值相加,使该部分变亮。
  • copy:后绘制的图形完全替代与之重叠的先绘制图形。
  • xor:后绘制的图形与先绘制的图形重叠的部分执行异或操作。
// 绘制红色矩形
context.fillStyle = '#f00';
context.fillRect(10, 10, 50, 50);
// 设置合成操作
context.globalCompositionOperation = 'destination-over';
// 绘制蓝色矩形
context.fillStyle = 'rgba(0, 0, 255, 1)';
context.fillRect(30, 30, 50, 50);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容

  •   HTML5 添加的最受欢迎的功能就是 元素。这个元素负责在页面中设定一个区域,然后就可以通过 JavaScri...
    霜天晓阅读 2,945评论 0 2
  • canvas元素的基础知识 在页面上放置一个canvas元素,就相当于在页面上放置了一块画布,可以在其中进行图形的...
    oWSQo阅读 10,219评论 0 19
  • 一:canvas简介 1.1什么是canvas? ①:canvas是HTML5提供的一种新标签 ②:HTML5 ...
    GreenHand1阅读 4,604评论 2 32
  • 元素负责在页面中设定一个区域,然后就可以通过 JavaScript 动态地在这个区域中绘制图形。 一、基本用法 要...
    LemonnYan阅读 18,358评论 0 7
  • 第15章 使用 Canvas 绘图 1. 基本用法 (1) 要使用 元素,必须先设置其width和 height ...
    yinxmm阅读 256评论 0 0