在我们开始画图之前,我们需要了解一下画布栅格以及坐标空间。通常来说网格中的一个单元相当于 canvas
元素中的一像素。栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点定位。距离左边(X
轴)x
像素,距离上边(Y
轴)y
像素(坐标为(x,y))。
有以下三种方法可以绘制矩形:
用于绘制一个填充的矩形。
fillRect(x, y, width, height)
x
:矩形起始点的 x
轴坐标y
:矩形起始点的 y
轴坐标width
:矩形的宽度,负值的话宽度是绝对值,但是往左移动宽度绝对值像素。height
:矩形的高度,负值的话高度是绝对值,但是往上移动高度绝对值的像素。ctx.fillRect(10, 10, 55, 50);
如下图所示,绘制的矩形左上顶点在 (10, 10),宽高分别是 55 和 50 像素。由于没有设置颜色,默认是黑色。
下面这个代码片段填充了整个画布。这样做通常的目的是在开始绘制其他内容前设置一个背景。为了达到这样的效果,需要让填充的范围和画布的范围相同,需要获取到canvas
元素的width
和 height
属性。
ctx.fillRect(0, 0, canvas.width, canvas.height);
用于绘制一个矩形的边框。
ctx.strokeRect(x, y, width, height)
strokeRect(x, y, width, height)
x
:矩形起始点的 x
轴坐标y
:矩形起始点的 y
轴坐标width
:矩形的宽度。负值的话宽度是绝对值,但是往左移动宽度绝对值像素。height
:矩形的高度。负值的话高度是绝对值,但是往上移动高度绝对值的像素。ctx.strokeRect(55, 10, 55, 50);
ctx.strokeRect(55, 10, -55, 50);
可以看到整个矩形往左移动了 55 像素,由于设置的x
值为 55,刚好与边界重叠。
用于清除指定矩形区域,让清除部分完全透明。这个方法通过把像素设置为透明(rgba(0,0,0,0)
)以达到擦除一个矩形区域的目的。
clearRect(x, y, width, height)
x
:矩形起始点的 x
轴坐标y
:矩形起始点的 y
轴坐标width
:矩形的宽度height
:矩形的高度路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。一个路径,甚至一个子路径,都是闭合的。使用路径绘制图形比直接绘制矩形要多一些额外的步骤:
下面是方法总结介绍:
beginPath()
: 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。closePath()
: 闭合路径,用于将当前路径的起点和终点连接起来,形成一个封闭的路径。stroke()
: 通过线条来绘制图形轮廓。fill()
: 通过填充路径的内容区域生成实心的图形。moveTo(x, y)
: 将起点移动到指定的坐标 x
以及 y
上(默认是在(0, 0))。lineTo(x, y)
: 绘制一条从当前位置到指定 x
以及 y
位置的直线(通常结合moveTo(x, y)
使用,先设置起点在划线)。arc()
: 绘制圆弧或者圆.quadraticCurveTo()
: 绘制二次贝塞尔曲线bezierCurveTo()
: 绘制三次贝塞尔曲线通过清空子路径列表开始一个新路径的方法。 当我们想创建一个新的路径时,调用此方法。
可能下面会涉及到除了beginPath
外的方法,但是我们先不用管,那些方法会在下面继续介绍.
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(10,100);
ctx.stroke();
当时当我们把beginPath
去掉不调用时,发现其实还是成功绘制出来了,那么这个方法到底用来干什么呢?
我们不调用beginPath
在画一条红线来看看有什么效果
ctx.moveTo(10,10);
ctx.lineTo(10,100);
ctx.stroke();
ctx.moveTo(20,10);
ctx.lineTo(20,100);
ctx.strokeStyle = "red";
ctx.stroke();
明明第一条没有设置红色,但是页面上看到两条都是红色的线.
这是因为这两条线都放在同一个路径列表中,而这整一个列表就是我们当前绘制的路径.即使在绘制第一条的线的已经调用了stroke
方法将当前路径进行描边,并将描边的结果添加到画布上。如果我们在之前的路径上连续调用 stroke()
方法,那么每次调用都会将之前的路径再次描边,从而导致之前的线条被重复绘制(对于复杂的canvas
动画,会因为动画越来越卡)。
而调用beginPath()
方法的时候,就会把之前的路径列表清空,并重新开始一条新的路径,而不会影响已经绘制的路径。
调用beginPath
方法清除之前的路径列表.
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(10,100);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(20,10);
ctx.lineTo(20,100);
ctx.strokeStyle = "red";
ctx.stroke();
在绘制路径时,通常我们会使用 moveTo()
方法将画笔移动到起始点,然后使用其他绘图方法(如 lineTo()
、arcTo()
等)定义路径的形状和轮廓。当我们想要将路径闭合时,我们可以使用 closePath()
方法。
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(20, 50);
ctx.lineTo(80, 50);
ctx.stroke();
// 使用 closePath()
ctx.beginPath();
ctx.moveTo(20, 70);
ctx.lineTo(20, 100);
ctx.lineTo(80, 100);
ctx.closePath();
ctx.stroke();
使用了 closePath()
方法的路径是封闭的,起点和终点通过一条直线连接。我们调用了 closePath()
方法来将路径封闭,所以画笔会自动将终点与起点连接起来,形成一个封闭的路径。
是否需要使用
closePath()
方法取决于我们希望绘制的图形是否需要封闭.
如果绘制的图形是封闭的,比如一个闭合的多边形或封闭曲线,那么使用closePath()
方法可以确保路径的起点和终点连接起来,形成一个封闭的路径。这样,在调用fill()
方法时,填充颜色会被应用到整个封闭区域内。
如果绘制的图形已经是封闭的,或者使用的绘图方法已经自动将路径封闭(例如使用arc()
方法绘制圆),则不必显式调用closePath()
方法。
closePath()
方法的作用仅限于路径的封闭,不会直接影响fill()
或stroke()
方法的结果。它只是确保路径是封闭的,从而在填充或描边时产生预期的效果。
closePath()
只是闭合当前的路径,从起点到现在的这个点形成一个闭合的回路,但是,这并不意味着他之后的路径就是新路径了,不要企图通过closePath()
来开始一条新的路径.
用于绘制所有moveTo()
和lineTo()
方法定义的路径。默认颜色是黑色.
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(10,100);
ctx.stroke();
就使用之前的例子来展示了.
用于填充颜色.
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(20, 50);
ctx.lineTo(80, 50);
ctx.fill();
当你调用
fill()
函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用closePath()
函数。但是调用stroke()
时不会自动闭合。
用于画笔起点,接受两个参数x
,y
,设置后画笔起点为(x, y),默认(0, 0).
继续使用之前例子,画了两条线,这两条线的起点都使用moveTo
方法更改过.
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(10,100);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(20,10);
ctx.lineTo(20,100);
ctx.strokeStyle = "red";
ctx.stroke();
绘制一条从当前位置到指定 x
以及 y
位置的直线,该方法有两个参数:x
以及 y
,代表坐标系中直线结束的点。开始点和之前的绘制路径有关,之前路径的结束点就是接下来的开始点,开始点也可以通过 moveTo()
函数改变。
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(10,100);
ctx.stroke();
绘制圆弧路径的方法。圆弧路径的圆心在 (x, y) 位置,半径为 r,根据anticlockwise (默认为顺时针)指定的方向从 startAngle 开始绘制,到 endAngle 结束.
语法:
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
x
: 圆弧中心(圆心)的 x
轴坐标。y
: 圆弧中心(圆心)的 y
轴坐标。radius
: 圆弧的半径。startAngle
: 圆弧的起始点,x
轴方向开始计算,单位以弧度表示.endAngle
: 圆弧的终点,单位以弧度表示。anticlockwise
: 可选的Boolean
值,如果为 true
,逆时针绘制圆弧,反之,顺时针绘制。ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI, true);
ctx.fill();
arc()
函数中表示角的单位是弧度,不是角度。角度与弧度的js
表达式: 弧度=(Math.PI/180
)*角度。
绘制二次贝塞尔曲线,cpx
,cpy
为一个控制点,x
,y
为结束点
语法:
ctx.quadraticCurveTo(cpx, cpy, x, y)
cpx
: 控制点的 x
轴坐标。cpy
: 控制点的 y
轴坐标。x
: 终点的 x
轴坐标。y
: 终点的 y
轴坐标。ctx.beginPath();
ctx.moveTo(20, 10);
ctx.quadraticCurveTo(30, 50, 50, 20);
ctx.stroke();
本文不讲解贝塞尔曲线相关的知识,如果大家想了解更多关于贝塞尔曲线的知识,可以查看wiki上关于贝塞尔曲线的介绍或者自行查看其他相关文档.
绘制三次贝赛尔曲线路径的方法。该方法需要三个点。第一、第二个点是控制点,第三个点是结束点。起始点是当前路径的最后一个点,绘制贝赛尔曲线前,可以通过调用 moveTo()
进行修改。
语法:
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
cp1x
: 第一个控制点的 x
轴坐标。cp1y
: 第一个控制点的 y
轴坐标。cp2x
: 第二个控制点的 x
轴坐标。cp2y
: 第二个控制点的 y
轴坐标。x
: 结束点的 x
轴坐标。y
: 结束点的 y
轴坐标。除了图形外,还可以绘制文本.有两种方法可以渲染文本:
fillText
: 在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的。strokeText
: 在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的`语法:
ctx.fillText(text, x, y, [maxWidth]);
text
: 使用当前的 font
, textAlign
, textBaseline
和 direction
值对文本进行渲染。x
: 文本起点的 x
轴坐标。y
: 文本起点的 y
轴坐标。maxWidth
: 可选,绘制的最大宽度。如果指定了值,并且经过计算字符串的值比最大宽度还要宽,字体为了适应会水平缩放(如果通过水平缩放当前字体,可以进行有效的或者合理可读的处理)或者使用小号的字体。ctx.fillText("Hello world", 30, 50);
绘制的最大宽度小于文本的宽度,文本的进行了水平缩放.
ctx.fillText("Hello world", 30, 50, 20);
绘制文字轮廓.
text
: 使用当前的 font
, textAlign
, textBaseline
和 direction
值对文本进行渲染。x
: 文本起点的 x
轴坐标。y
: 文本起点的 y
轴坐标。maxWidth
: 可选,绘制的最大宽度。如果指定了值,并且经过计算字符串的值比最大宽度还要宽,字体为了适应会水平缩放(如果通过水平缩放当前字体,可以进行有效的或者合理可读的处理)或者使用小号的字体。为了能更清晰的看到是文字轮廓,设置了font
属性加大字体.
ctx.font = "bold 20px serif";
ctx.strokeText("Hello world", 20, 90);
canvas
还具有图像操作能力。可以用于动态的图像合成或者作为图形的背景,以及游戏界面等等。浏览器支持的任意格式的外部图片都可以使用,比如 PNG
、GIF
或者 JPEG
。甚至可以将同一个页面中其他 canvas
元素生成的图片作为图片源。
引入图像到 canvas
里需要以下两步基本操作:
html
元素对象或者另一个 canvas
元素的引用作为源,也可以通过提供一个 URL
的方式来使用图片drawImage()
函数将图片绘制到画布上可以使用的html
的元素对象:
HTMLImageElement
: 由 Image()
函数构造出来的,或者任何的
元素HTMLVideoElement
: 从video
标签的视频中抓取当前帧作为一个图像HTMLCanvasElement
: 使用另一个
元素作为你的图片源。ImageBitmap
: 这是一个高性能的位图,可以低延迟地绘制,它可以从上述的所有源以及其他几种源中生成(通过createImageBitmap
方法创建)。我们下面将一个个展示:
const imageObj = new Image();
imageObj.onload = function() {
ctx.drawImage(imageObj, 0, 0);
};
imageObj.src = 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg';
imageObj.src = 'data:image/xxxxxxxxxxx';
添加一个video
标签.
<video src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" controls width="200px" height="200px">video>
const video = document.getElementsByTagName('video')[0]
video.onplay = function() {
ctx.drawImage(video, 0, 0);
}
添加多一个canvas
并添加一些线条作为图片源.
<canvas width="150" height="150" id="source">canvas>
const source = document.getElementById("source");
const sourceCtx = source.getContext("2d");
sourceCtx.beginPath();
sourceCtx.moveTo(10, 10);
sourceCtx.lineTo(10, 100);
sourceCtx.stroke();
sourceCtx.beginPath();
sourceCtx.moveTo(20, 10);
sourceCtx.lineTo(20, 100);
sourceCtx.strokeStyle = "red";
sourceCtx.stroke();
const canvas = document.getElementById("demo");
const ctx = canvas.getContext("2d");
ctx.drawImage(source, 0, 0);
使用createImageBitmap
加载雪碧图并从中提取出两个小图渲染到画布上.
const image = new Image();
image.onload = () => {
Promise.all([
createImageBitmap(image, 0, 0, 80, 80),
createImageBitmap(image, 32, 0, 80, 80),
]).then((sprites) => {
ctx.drawImage(sprites[0], 0, 0);
ctx.drawImage(sprites[1], 32, 32);
});
};
image.src = "sprites.png";
前面都使用到了drawImage
这个方法去渲染,但是并未讲解,现在就让我们来看看具体是怎么用的.
语法:
ctx.drawImage(image, x, y)
ctx.drawImage(image, x, y, width, height)
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
通过不同的传参,渲染结果也不一样:
这种方式是最容易理解的,image
表示图片源(前面有详细介绍过),x
和 y
是其在目标 canvas
里的起始坐标.
SVG
图像必须在根指定元素的宽度和高度。
比如说前面这个例子:
当我们把x
和y
设置成其他的看看效果.
ctx.drawImage(source, -10, -10);
可以看到现在图片源往左和上都移动了10像素,起始点在原点了.
用于缩放图片,与第一种的区别就是增加了两个用于控制图像在 canvas
中缩放的参数
把width
和height
都设置成75
,为原来的1/2.
ctx.drawImage(source, 0, 0, 75, 75);
图像可能会因为大幅度的缩放而变得起杂点或者模糊。如果您的图像里面有文字,那么最好还是不要进行缩放,因为那样处理之后很可能图像里的文字就会变得无法辨认了。
最后一个有8个新参数,用于控制做切片显示的.
除了第一个参数跟之前两个方法的一样都是图片源以外,剩余的都没关联,后8个的新参数的前4个用来定义图像源的切片位置和大小,后 4 个则是定义切片的目标显示位置和大小。
切片是个做图像合成的强大工具。假设有一张包含了所有元素的图像,那么你可以用这个方法来合成一个完整图像。例如,我们想画一张图表,而手上有一个包含所有必需的文字的 PNG
文件,那么可以很轻易的根据实际数据的需要来改变最终显示的图表。这方法的另一个好处就是你不需要单独装载每一个图像。
ctx.drawImage(source, 10, 10, 20, 100, 50, 50, 20, 50)
在这个例子中,从(10, 10)的地方开始切割出宽20高100的图片源,并放在目标canvas
画布(50, 50)的地方,并再次切成宽20高50.
前面提到了,过度缩放图像可能会导致图像模糊或像素化,可以通过使用绘图环境的imageSmoothingEnabled
属性来控制是否在缩放图像时使用平滑算法。默认值为true,即启用平滑缩放。您也可以像这样禁用此功能:
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;