by Shukant Pal
通过Shukant Pal
The HTML canvas element is used to draw “raster” graphics on a web application. The Canvas API provides two drawing contexts: 2D and 3D, and in this guide, we’re going to talk about the 2D one (which I’ll refer it to the Canvas API for simplicity).
HTML canvas元素用于在Web应用程序上绘制“光栅”图形。 Canvas API提供了两种绘图环境:2D和3D,在本指南中,我们将讨论2D(为简单起见,我将其称为Canvas API)。
Before I get started, I want you to know one very important point. Canvas is a raster graphics API — you manipulate stuff at the pixel level. That means the underlying software does not know the model you are using to display your context — it doesn’t know if you’re drawing a rectangle or a circle.
在开始之前,我希望您知道一个非常重要的观点。 Canvas是一个光栅图形API,您可以在像素级别上进行操作。 这意味着底层软件不知道您用来显示上下文的模型-它不知道您是绘制矩形还是圆形。
I’ve divided the Canvas API in separate chunks, for you to gobble one by one:
我已将Canvas API分为不同的块,以便您一一吞噬:
Path API
路径API
Drawing Styles
绘图样式
Gradients and Patterns
渐变和图案
Direct pixel manipulation & Images
直接像素操作和图像
Transformations
转变
Hit Regions
热门地区
State and the clip() method
状态和clip()方法
To fire up your Canvas tutorial, create an HTML file and a JS file linked to it.
要启动Canvas教程,请创建一个HTML文件和与其链接的JS文件。
<!DOCTYPE html>
<html>
<head><title>Canvas Demo</title></head>
<body>
<canvas id="canvas-demo" width="400" height="400">
This will be displayed if your browser doesn't
support the canvas element. The closing tag is
necessary.
</canvas>
<script src="canvas-demo.js"></script>
</body>
</html>
In your canvas-demo.js
file,
在您的canvas-demo.js
文件中,
// canvas-demo.js
const demoCanvas = document.getElementById(’canvas-demo’).getContext(’2d’);
window.onload = function() {// make sure to use onload
/* Add code here as we go!!! @nodocs
*/
}
Paths are a collection of points in the 2D pixel grid in the canvas. They are drawn with the help of this API. Each shape in a path that you draw is called a “subpath” by the W3C documentation.
路径是画布2D像素网格中点的集合。 它们是在此API的帮助下绘制的。 W3C文档将您绘制的路径中的每个形状称为“子路径”。
beginPath()
and closePath()
: All the shapes that you draw are added into the current path. If you call stroke
or fill
later on, it will apply that to all of the shapes you’ve drawn in the current path. To prevent that, you divide your drawing by calling beginPath
and closePath
.
beginPath()
和closePath()
:您绘制的所有形状都添加到当前路径中。 如果稍后调用stroke
或fill
,它将应用于您在当前路径中绘制的所有形状。 为了避免这种情况,可以通过调用beginPath
和closePath
划分图形。
// Calling this isn't necessary, but a good practice.
demoCanvas.beginPath();
/*
* Drawing code, copy and paste each example (separately) here
*/
demoCanvas.closePath();// this is required if you want to draw
// in a separate path later
moveTo(x,y)
: The signifies the construction of a new shape which starts at the point (x, y).
moveTo(x,y)
:表示从(x,y)点开始的新形状的构造。
lineTo(x,y)
: Draws a line from the last point in the current shape to the passed point. If no shaped was created (via moveTo
), then a new one is created starting at (x, y) (just like moveTo
).
lineTo(x,y)
:从当前形状的最后一个点到通过的点绘制一条线。 如果未创建任何形状(通过moveTo
),则将从(x,y)开始创建一个新形状(就像moveTo
一样)。
quadraticCurveTo(cpx1,cpy1,x,y)
and bezierCurveTo(cpx1,cpy1,cpx2,cpy2,x,y)
: Draws a quadratic/cubic bezier curve starting from the last point in the shape, passing through the control points ( cpx1,cpy1
and cpx2,cpy2
), and ending at x,y
. A Bezier curve is just a “smooth” curve that passes through intermediate “control” points with given ending points. Note that the curve doesn’t have to exactly passe through the control points — it can be smoothed out.
quadraticCurveTo(cpx1,cpy1,x,y)
和bezierCurveTo(cpx1,cpy1,cpx2,cpy2,x,y)
:从形状的最后一个点开始绘制二次/三次贝塞尔曲线,并穿过控制点( cpx1,cpy1
和cpx2,cpy2
),并以x,y
结尾。 贝塞尔曲线仅是一条“平滑”曲线,它通过具有给定端点的中间“控制”点。 请注意,曲线不必精确地通过控制点-可以对其进行平滑处理。
arcTo(x1,y1,x2,y2,radius)
: This is a slightly complicated method to use. Suppose the current point in the path is x0,y0
— then arcTo
will draw an arc that has two tangents connecting these two pairs of points (x1,y1) & (x0,y0)
and (x1,y1) & (x2,y2)
. The arc’s radius will be the one given. The greater the radius, the farther out the arc will be from x1,y1
, (See Example 1.2 for visual clarity). If you haven’t used moveTo
yet, then x0,y0
will by default be 0,0
.
arcTo(x1,y1,x2,y2,radius)
:这是一种使用起来稍微复杂的方法。 假设路径中的当前点是x0,y0
然后arcTo
将绘制一个圆弧,该圆弧具有连接这两对点(x1,y1) & (x0,y0)
和(x1,y1) & (x2,y2)
两个切线(x1,y1) & (x2,y2)
。 圆弧的半径将是给定的。 半径越大,圆弧距离x1,y1
越远(请参见示例1.2,以获取清晰的视觉效果)。 如果尚未使用moveTo
,则x0,y0
默认为0,0
。
arc(x,y,radius,startAngle,endAngle,counterclockwise)
: The connects the current point in the path (by default 0,0
) to the beginning of the arc. It draws the arc from the center x,y
of radius radius
, from startAngle
to endAngle
. (Note: Unlike pen & paper mathematics, angles are described in the clockwise direction in the Canvas API); but in under four special conditions — (x0,y0)
equals (x1,y1)
, (x1,y1)
equals (x2,y2)
, (x0,y0),(x1,y1),(x2,y2)
are collinear, or if radius
is zero, then the call to arc
will be equivalent to lineTo(x1,y1)
and a line will be drawn instead.
arc(x,y,radius,startAngle,endAngle,counterclockwise)
:将路径中的当前点(默认为0,0
)连接到弧的起点。 它从startAngle
到endAngle
从半径radius
的中心x,y
绘制弧。 (注意:与笔和纸的数学不同,角度是在Canvas API中按顺时针方向描述的); 但是在四个特殊条件下- (x0,y0)
等于(x1,y1)
, (x1,y1)
等于(x2,y2)
, (x0,y0),(x1,y1),(x2,y2)
是共线的,或者如果radius
为零,则对arc
的调用将等效于lineTo(x1,y1)
并绘制一条直线。
rect(x,y,w,h)
: Draws a rectangle with the top-left corner x,y
and of width w
and height h
.
rect(x,y,w,h)
:绘制一个矩形,其左上角为x,y
,宽度为w
,高度为h
。
Example 1.1:
范例1.1:
Now we need to try a demo — we are going to draw a few random horizontal lines and then a sketch of an eye. The result will look like something on the left. Don’t forget to go through the code and tinker with the code.
现在我们需要尝试一个演示-我们将绘制一些随机的水平线,然后绘制一个眼睛草图。 结果看起来像左边的东西。 不要忘记阅读代码并修改代码。
/* Draw horizontal subpaths (shapes) in one path. */
// Draw a pattern of vertically stack horizontal
// lines.
demoCanvas.moveTo(10, 10);// start at (10,10)
demoCanvas.lineTo(110, 10);
demoCanvas.moveTo(10, 20);// 10 pts below
demoCanvas.lineTo(180, 20);
demoCanvas.moveTo(10, 30);
demoCanvas.lineTo(150, 30);
demoCanvas.moveTo(10, 40);
demoCanvas.lineTo(160, 40);
demoCanvas.moveTo(10, 50);
demoCanvas.lineTo(130, 50);
// try removing this moveTo, the quad-curve will then
// start from from (130, 50), due to the lineTo.
demoCanvas.moveTo(10, 100);// quad-curve starts from here
demoCanvas.quadraticCurveTo(110, 55, 210, 100);// curve upward
demoCanvas.moveTo(10, 100);// back here, let's draw one below
demoCanvas.quadraticCurveTo(110, 145, 210, 100);// curve below
// that forms the eye outline
demoCanvas.moveTo(132.5, 100);// remove this, a horizontal line will be
// drawn from (210, 100) to (132.5, 100) because arc() connects the last
// point to the start of the arc.
demoCanvas.arc(110, 100, 22.5, 0, 2*Math.PI, false);// pupil (circle)
/* We'll talk about this shortly */
demoCanvas.stroke();// draws (by outlining our shapes in the path)
Example 1.2:
范例1.2:
In the example below, I create a cubic curve (with visual guidelines), arcTo
calls in the middle right, and a pack-man with arc()
on bottom left. The control points (in the cubic curve) are the corners forms by the three guidelines.
在下面的示例中,我创建了三次曲线(具有可视准则),在右中角创建了arcTo
调用,并在左下角创建了带有arc()
的打包人。 控制点(在三次曲线中)是三个准则中的角点形式。
(x1,y1)
for arcTo
is the corner formed by the two tangents.
arcTo
(x1,y1)
是两个切线形成的角。
// comment this block out if you can see the cubic curve
demoCanvas.moveTo(100, 100);
demoCanvas.lineTo(150, 10);
demoCanvas.moveTo(250, 100);
demoCanvas.lineTo(200, 190);
demoCanvas.moveTo(150, 10);
demoCanvas.lineTo(200, 190)
demoCanvas.moveTo(100, 100);
demoCanvas.bezierCurveTo(150, 10, 200, 190, 250, 100);
// arcTo() is too complicated to use
// demoCanvas.stroke(); demoCanvas.closePath(); demoCanvas.beginPath();
demoCanvas.moveTo(200, 200);// comment out above line (and comment this line),
// then the arc's tangent will come from (0,0)!! Try it.
demoCanvas.arcTo(100, 300, 300, 300, 100);
demoCanvas.moveTo(200, 200);
demoCanvas.arcTo(100, 300, 300, 300, 50);
demoCanvas.moveTo(100, 300);
demoCanvas.lineTo(300, 300);
demoCanvas.moveTo(100, 300);
demoCanvas.lineTo(200, 200);
demoCanvas.moveTo(50, 300);
// packman
demoCanvas.arc(50, 300, 35, Math.PI/6, 11*Math.PI/6, false);
demoCanvas.lineTo(50, 300);
demoCanvas.stroke();
Till now, we have been drawing simple thin-lined paths. Drawing styles will help us make our drawing much better.
到目前为止,我们一直在绘制简单的细线路径。 图纸样式将帮助我们使图纸更好。
Note that you cannot apply two different styles on the same path. For example, if you want to draw a red line and a blue line — you will have to create a new path to draw the blue one. If you don’t create a new path, then on calling stroke
the 2nd time after setting your display style color to blue, both lines will be colored blue. Hence, styles are applied to all subpaths, whether or not they have been stroked already.
请注意,您不能在同一路径上应用两种不同的样式。 例如,如果要绘制一条红线和一条蓝线,则必须创建一条新路径来绘制蓝线。 如果不创建新路径,则在将显示样式颜色设置为蓝色后第二次调用stroke
时,两行都将变为蓝色。 因此,样式将应用于所有子路径,无论它们是否已经被描边。
A few properties of the 2D context object demoCanvas
are defined for this purpose:
demoCanvas
,定义了2D上下文对象demoCanvas
一些属性:
lineWidth
: The thickness of the lines being drawn. By default, this is 1; hence, the two examples above used a 1-pixel thick outline.
lineWidth
:绘制的线的粗细。 默认情况下,该值为1;默认值为1。 因此,上面的两个示例使用了1像素厚的轮廓。
lineCap
: This is the cap applied at the ends of subpaths (shapes). It is a string and can have three valid values: “butt”, “round”, “square” (See Example 1.3 for visual clarity). “butt” will end lines with no cap — resulting in rigid, orthogonal ends like thin rectangles. “round” adds a semi-circle to the ends to give smooth ends. “square” adds a square to the end, but it looks like “butt”. “round” and “square” add bit of extra length to each subpath.
lineCap
:这是应用于子路径(形状)末端的上限。 它是一个字符串,可以有三个有效值:“ butt”,“ round”,“ square”(有关视觉清晰度,请参见示例1.3)。 “对接”将无盖的端线-产生刚性,正交的端部,如薄矩形。 “圆形”在端部添加半圆以提供平滑的端部。 “正方形”在末尾添加一个正方形,但看起来像“对接”。 “圆形”和“正方形”为每个子路径增加了一些额外的长度。
lineJoin
: This decides how two overlapping lines are joined. For example, if you want to create a right-hand arrow (>), then you can change how the corner is formed with this property. This has three valid values: “round”, “bevel” and “miter”. Check Example 1.4 for how they change the corners. (The default value is “miter”). “round” will form circular corners, while “bevel” will create rigid three-sided corners, and “miter” will form a sharp edge.
lineJoin
:这决定如何重叠两条重叠的线。 例如,如果要创建向右箭头(>),则可以使用此属性更改拐角的形成方式。 它具有三个有效值:“倒圆角”,“斜角”和“斜角”。 请参见示例1.4,了解它们如何改变拐角。 (默认值为“ miter”)。 “圆”将形成圆角,而“斜角”将形成刚性的三边角,而“斜角”将形成锋利的边缘。
miterLimit
: When lineJoin="miter"
, this decides the maximum distance b/w the inner and outer corner of the line. See Example 1.4(b) for visual clarity. If the miter-limit is too high, then sharp arrows may have a large common area b/w the two lines. If miter-limit is passed, then the display backs into a bevel join.
miterLimit
:当lineJoin="miter"
,它确定线的内外角的最大距离b / w。 有关视觉清晰度,请参见示例1.4(b)。 如果斜接限制值太高,则尖锐的箭头可能会在两条线之间具有较大的公共面积。 如果通过了斜接限制,则显示将返回到斜角连接。
Example 1.3 & 1.4:
范例1.3和1.4:
In Example 1.3 on the left, you can see how the round & square line-capped lines are longer than the default capping. (NOTE: The thicker the line, the greater the increase in length)
在左侧的示例1.3中,您可以看到圆和方线上限的线长于默认上限。 (注意:线越粗,长度增加越大)
In Example 1.4(a), you can see how round and bevel joins work. The lines created are identical in the upper and lower parts. Only the lineJoin
properties are different.
在示例1.4(a)中,您可以看到圆角和斜角连接的工作方式。 创建的线条在上部和下部相同。 仅lineJoin
属性不同。
In Example 4.1(b), you can see how a mitered join works, and what happens if the mitered length is passed.
在示例4.1(b)中,您可以看到斜切连接的工作方式,以及如果经过斜切的长度会发生什么。
Additional display style properties are defined:
定义了其他显示样式属性:
font
: This string defines how you want to style text. For example, demoCanvas.font="10px Times New Roman"
is a valid font value.
font
:此字符串定义您要如何设置文本样式。 例如, demoCanvas.font="10px Times New Roman"
是有效的字体值。
textAlign
: The valid values are — “start”, “end”, “left”, “right”, and “center”. The default is “start”.
textAlign
:有效值为-“开始”,“结束”,“左”,“右”和“中心”。 默认值为“开始”。
textBaseline
: The valid values are — “top”, “hanging”, “middle”, “alphabetic”, “ideographic”, “bottom”. The default is “alphabetic”.
textBaseline
:有效值为-“顶部”,“悬挂”,“中间”,“字母”,“表意”,“底部”。 默认为“字母”。
In the examples till now, you might have noticed I’ve used demoCanvas.stroke()
before closing each path. The stroke method does that actual drawing partly in those examples.
在到目前为止的示例中,您可能已经注意到,在关闭每个路径之前,我已经使用了demoCanvas.stroke()
。 笔触方法在这些示例中部分地执行了实际绘制。
stroke
: This method draws the outline around each subpath (shapes) according to the lineWidth
and related properties.
stroke
:此方法根据lineWidth
和相关属性在每个子路径(形状)周围绘制轮廓。
fill
: This method fills the interior of the shape traced by the path. If the path is not closed, then it will close it automatically by connecting the last point to the first point.
fill
:此方法填充路径所跟踪的形状的内部。 如果未关闭路径,则它将通过将最后一个点连接到第一个点来自动将其关闭。
demoCanvas.moveTo(10,10);
demoCanvas.lineTo(50, 50);
demoCanvas.lineTo(10, 50);
demoCanvas.fill();
The above code does not close the triangle (10,10),(50,50),(10,50) but calling fill()
fills it as expected.
上面的代码不会关闭三角形(10,10),(50,50),(10,50),但是调用fill()
会按预期填充它。
clearRect(x,y,w,h)
: Clears the pixels in the rectangle formed with the given parameters.
clearRect(x,y,w,h)
:清除由给定参数形成的矩形中的像素。
strokeRect(x,y,w,h)
: Equivalent to calling rect
and then stroke
. It doesn’t add the rectangle to the current path — hence, you can change the style later and call stroke
without affecting the rectangle formed.
strokeRect(x,y,w,h)
:等效于调用rect
,然后调用stroke
。 它不会在当前路径中添加矩形-因此,您可以稍后更改样式并调用stroke
而不会影响所形成的矩形。
fillRect(x,y,w,h)
: Equivalent to calling rect
and then fill
. This also doesn’t add the rectangle to the current path.
fillRect(x,y,w,h)
:等效于调用rect
然后fill
。 这也不会将矩形添加到当前路径。
strokeText(text,x,y,maxWidth)
and fillText(text,x,y,maxWidth)
: Writes the text at (x,y) according to the strokeStyle
/ fillStyle
property. maxWidth
is optional and defines the maximum length in pixels that you want the text to occupy. If the text is longer, then it is scaled to a smaller font. measureText("text").width
can be used to find the display width of a piece of text, based on the current font
.
strokeText(text,x,y,maxWidth)
和fillText(text,x,y,maxWidth)
:根据strokeStyle
/ fillStyle
属性在(x,y)处写入文本。 maxWidth
是可选的,它定义了文本要占用的最大长度(以像素为单位)。 如果文本较长,则将其缩放为较小的字体。 measureText("text").width
可用于基于当前font
查找一段文本的显示宽度。
NOTE: fillStyle
and strokeStyle
are the properties that can be set to any CSS color string to set the fill & stroke colors.
注意: fillStyle
和strokeStyle
是可以设置为任何CSS颜色字符串以设置填充和描边颜色的属性。
Out of the box, the 2D context provides linear and radial gradients. The createLinearGradient
and createRadialGradient
methods return CanvasGradient
objects, which can then be modified what we want.
开箱即用的2D上下文提供线性和径向渐变。 该createLinearGradient
和createRadialGradient
方法返回CanvasGradient
对象,然后可以修改我们想要的。
createLinearGradient(x0,y0,x1,y1)
: Constructs a linear gradient that runs on the line x0,y0
to x1,y1
.
createLinearGradient(x0,y0,x1,y1)
:构造一个线性渐变,该渐变在x0,y0
到x1,y1
线上运行。
createRadialGradient(x0,y0,r0,x1,y1,r1)
: Constructs a radial gradient that runs in the cone (of circles) with the top (inner circle) of radius r0
and bottom (outer circle) of radius r1
. The first color would have a radius of r0
.
createRadialGradient(x0,y0,r0,x1,y1,r1)
:构造一个半径渐变,该渐变在半径为r0
的顶部(内圆)和半径为r1
底部(外圆)的圆锥(圆)中运行。 第一种颜色的半径为r0
。
The CanvasGradient
has one method: addColorStop(offset,color)
. The gradient starts at 0 and ends at 1. The color at the position of offset
will be set using this method. For example, addColorStop(.5, "green")
will make the middle color green. Colors b/w two adjacent stops will be interpolated (mixed).
CanvasGradient
有一种方法: addColorStop(offset,color)
。 渐变从0开始到1结束。使用此方法可以设置offset
位置的颜色。 例如, addColorStop(.5, "green")
将使中间颜色addColorStop(.5, "green")
绿色。 黑白两个相邻色标将被插值(混合)。
Example 1.6:
范例1.6:
In the example on the left, you can see how linear and radial gradients work.
在左侧的示例中,您可以看到线性和径向渐变的工作方式。
var linearGrad = demoCanvas.createLinearGradient(5,5,100,5);
linearGrad.addColorStop(0, "blue");
linearGrad.addColorStop(.5, "green");
linearGrad.addColorStop(1, "red");
demoCanvas.strokeStyle=linearGrad;
demoCanvas.lineWidth=50;
demoCanvas.moveTo(5,5);
demoCanvas.lineTo(100,5);
demoCanvas.stroke();// change strokeStyle(l10) to fillStyle(l10)
// and stroke() to fill(). Then, change lineTo(100,5) to rect(5,5,95,50).
// Results should be almost same.
demoCanvas.closePath();
demoCanvas.beginPath();
var radialGrad = demoCanvas.createRadialGradient(50,50,10,50,50,40);
radialGrad.addColorStop(0, "blue");
radialGrad.addColorStop(.5, "green");
radialGrad.addColorStop(1, "red");
demoCanvas.fillStyle=radialGrad;
demoCanvas.arc(50,50,30,0,2*Math.PI,false);
demoCanvas.fill();
You might wonder what if x0,y0
and x1,y1
given to the linear/radial gradient are not equal to the line/arc we create? See Example 1.7
您可能想知道如果给定线性/径向渐变的x0,y0
和x1,y1
不等于我们创建的线/弧? 参见例1.7
Example 1.7
例子1.7
var linearGrad = demoCanvas.createLinearGradient(5,5,100,5);
linearGrad.addColorStop(0, "blue");
linearGrad.addColorStop(.5, "green");
linearGrad.addColorStop(1, "red");
demoCanvas.strokeStyle=linearGrad;
demoCanvas.lineWidth=50;
demoCanvas.moveTo(50,5);
demoCanvas.lineTo(155,5);
demoCanvas.stroke();// change strokeStyle(l10) to fillStyle(l10)
// and stroke() to fill(). Then, change lineTo(100,5) to rect(5,5,95,50).
// Results should be almost same.
demoCanvas.closePath();
demoCanvas.beginPath();
var radialGrad = demoCanvas.createRadialGradient(50,50,10,50,50,40);
radialGrad.addColorStop(0, "blue");
radialGrad.addColorStop(.5, "green");
radialGrad.addColorStop(1, "red");
demoCanvas.fillStyle=radialGrad;
demoCanvas.arc(60,60,30,0,2*Math.PI,false);
demoCanvas.fill();
The ImageData
object can be used to manipulate individual pixels. It has three properties:
ImageData
对象可用于操作单个像素。 它具有三个属性:
width
: The width of the image data in device-display pixels.
width
:以设备显示像素为单位的图像数据的宽度。
height
: The height of the image data in device-display pixels.
height
:以设备显示像素为单位的图像数据的高度。
data
: This is a Uint8ClampedArray
(MDN doc here) which contains the individual pixel data in a series of (R,G,B,A) bytes for the top-most pixel to the bottom-right pixel. So the nth pixel’s red value would be at data[y*width+x]
, green would be at data[y*width+x+1]
, blue would be at data[y*width+x+2]
, and the alpha would be at data[y*width+x+3]
.
data
:这是一个Uint8ClampedArray
( 此处为 MDN doc),其中包含从最高像素到右下方像素的一系列(R,G,B,A)字节序列中的单个像素数据。 因此,第n个像素的红色值为data[y*width+x]
,绿色值为data[y*width+x+1]
,蓝色值为data[y*width+x+2]
,并且alpha将位于data[y*width+x+3]
。
NOTE: A RGBA value can be used to represent a color — where R,G,B are the amounts of red, green, and blue and A is the opacity (alpha value). In the Canvas, these elements can have any integer value in [0, 255].
注意:RGBA值可用于表示颜色-其中R,G,B是红色,绿色和蓝色的数量,而A是不透明度(alpha值)。 在画布中,这些元素可以在[0,255]中具有任何整数值。
You can get a ImageData
object with the following methods in the Canvas API:
您可以在Canvas API中使用以下方法获取ImageData
对象:
createImageData(sw,sh)
: This creates an ImageData
object of width and height sw
and sh
, defined in CSS pixels. All the pixels will be initialized to transparent black (hex R,G,B=0, and also A=0).
createImageData(sw,sh)
:这将创建一个以CSS像素定义的宽度和高度sw
和sh
的ImageData
对象。 所有像素将被初始化为透明黑色(十六进制R,G,B = 0,以及A = 0)。
CSS pixels might map to a different number of actual device pixels exposed by the object itself
CSS像素可能映射到对象本身暴露的不同数量的实际设备像素
createImageData(data)
: Copies the given image-data and returns the copy.
createImageData(data)
:复制给定的图像数据并返回副本。
getImageData(sx,sy,sw,sh)
: Returns a copy of the canvas’s pixels in the rectangle formed by sx,sy,sw,sh
in a ImageData
object. Pixels outside the canvas are set to transparent black.
getImageData(sx,sy,sw,sh)
:返回ImageData
对象中由sx,sy,sw,sh
形成的矩形中画布像素的副本。 画布外部的像素设置为透明黑色。
putImageData(imagedata,dx,dy,dirtyX,dirtyY,dirtyWidth,dirtyHeight)
: (The last four ‘dirty’ arguments are optional). Copies the pixel values in imagedata
into the canvas rectangle at dx,dy
. If you provide the last four arguments, it will only copy the dirty pixels in the image data (the rectangle formed at dirtyX,dirtyY
of dimensions dirtyWidth*dirtyHeight
). Not passing the last four arguments is the same as calling putImageData(imagedata,dx,dy,0,0,imagedata.width,imagedata.height)
.
putImageData(imagedata,dx,dy,dirtyX,dirtyY,dirtyWidth,dirtyHeight)
:(最后四个'dirty'参数是可选的)。 将imagedata
的像素值imagedata
到dx,dy
处的画布矩形中。 如果提供最后四个参数,它将仅复制图像数据中的脏像素(在dirtyX,dirtyY
处形成的矩形,尺寸为dirtyWidth*dirtyHeight
)。 不传递最后四个参数与调用putImageData(imagedata,dx,dy,0,0,imagedata.width,imagedata.height)
。
For all integer values of x and y where dirtyX ≤ x < dirtyX+dirtyWidth and dirtyY ≤ y < dirtyY+dirtyHeight, copy the four channels of the pixel with coordinate (x, y) in the
imagedata
data structure to the pixel with coordinate (dx+x, dy+y) in the underlying pixel data of the canvas.对于x和y的所有整数值,其中dirtyX≤x <dirtyX + dirtyWidth和dirtyY≤y <dirtyY + dirtyHeight,将
imagedata
数据数据结构中坐标为(x,y)的像素的四个通道复制到坐标为(画布的基础像素数据中的dx + x,dy + y)。
Example 1.8:
范例1.8:
I’ve filled the whole 400x400 canvas with random colors (fully opaque) using the getImageData/putImageData
methods.
我已经使用getImageData/putImageData
方法用随机颜色(完全不透明)填充了整个400x400画布。
Note that using beginPath/closePath
isn’t necessary to use the ImageData API — because your not using the Canvas API to form shapes/curves.
请注意,使用ImageData API不需要使用beginPath/closePath
因为您没有使用Canvas API来形成形状/曲线。
/* replace this line with demoCanvas.createImageData(390,390) instead. */
var rectData = demoCanvas.getImageData(10, 10, 390, 390);
for (var y=0; y<390; y++) {
for (var x=0; x<390; x++) {
const offset = 4*(y*390+x);// 4* because each pixel is 4 bytes
rectData.data[offset] = Math.floor(Math.random() * 256);// red
rectData.data[offset+1] = Math.floor(Math.random() * 256);// green
rectData.data[offset+2] = Math.floor(Math.random() * 256);// blue
rectData.data[offset+3] = 255;// alpha, fully opaque
}
}
demoCanvas.putImageData(rectData, 10, 10);
/* beginPath/closePath aren't required for this code */
Images can be drawn onto the canvas directly. The drawImage
can be used in three different ways to do so. It requires a CanvasImageSource
as the pixel source.
图像可以直接绘制到画布上。 drawImage
可以通过三种不同的方式使用。 它需要CanvasImageSource
作为像素源。
A
CanvasImageSource
can be one of the following — HTMLImageElement, HTMLCanvasElement, HTMLVideoElement. To copy into the canvas, you can use a<img style="display:none;" src="..." />
. You could also copy an existing canvas or the screenshot of a video!!!
CanvasImageSource
可以是以下之一:HTMLImageElement,HTMLCanvasElement,HTMLVideoElement。 要复制到画布中,可以使用<img style="display:none;" src="..." />
<img style="display:none;" src="..." />
。 您也可以复制现有的画布或视频的屏幕截图!!!
drawImage(image,dx,dy)
: Copies the image-source into the canvas at (dx,dy). The whole image is copied.
drawImage(image,dx,dy)
:将图像源复制到( dx,dy )的画布中。 整个图像被复制。
drawImage(image,dx,dy,dw,dh)
: Copies the image-source into the rectangle in the canvas at (dx,dy) of size (dw,dh). It will be scaled down or scaled up if necessary.
drawImage(image,dx,dy,dw,dh)
:将图像源以( dw,dh )( dx,dy )复制到画布中的矩形中。 如有必要,它将按比例缩小或放大。
drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh)
: Copies the rectangle in the image source sx,sy,sw,sh
into the rectangle in the canvas dx,dy,dw,dh
and scales up or down if required. However, if the rectangle sx,sy,sw,sh
has parts outside the actual source — then the source rectangle is clipped to include the inbound parts and the destination rectangle is clipped in the same proportion; however, you shouldn’t pass any out-of-bounds rectangle — keep it simple, stupid.
drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh)
:将图像源sx,sy,sw,sh
的矩形复制到画布dx,dy,dw,dh
和scale中的矩形如果需要,向上或向下。 但是,如果矩形sx,sy,sw,sh
部分不在实际源中,则源矩形将被裁剪为包括入站部分,而目标矩形将以相同的比例被裁剪; 但是,您不应传递任何越界矩形-使其简单,愚蠢。
Example 1.9:
范例1.9:
var image = document.getElementById('game-img');
demoCanvas.drawImage(image, 50, 50, 200, 200, 100, 100, 200, 200);
/* beginPath/closePath aren't required for this code */
NOTE: Add this to your HTML —
注意:将此添加到您HTML中—
<img id="game-img" src="/path/to/your/image.ext" style="display:none" />
Now we’re getting to the exciting parts of the Canvas API!!!
现在,我们进入Canvas API令人兴奋的部分!!!
The Canvas uses a transformation matrix to transform the input (x, y) coordinates into the displayed (x, y) coordinates. Note that pixels drawn before the transformation are not transformed — they are untouched. Only stuff drawn after applying the transformation will be changed.
画布使用转换矩阵将输入( x,y )坐标转换为显示的( x,y )坐标。 请注意,在转换之前绘制的像素不会被转换-未被触摸。 仅应用转换后绘制的内容将被更改。
There are three in-built transformation methods:
有三种内置的转换方法:
scale(xf,yf)
: This method scales the input by xf
in the horizontal direction and yf
in the vertical direction. If you want to magnify an image by a factor of m
, then pass xf=yf=m
. To stretch/squeeze an image horizontally by m
, xf=m,yf=1
. To stretch/squeeze an image vertically by m
, xf=1,yf=m
.
scale(xf,yf)
:此方法在水平方向上按xf
缩放输入,在垂直方向上按yf
缩放输入。 如果要将图像放大m
倍,则传递xf=yf=m
。 要水平拉伸/压缩图像m
, xf=m,yf=1
。 要垂直拉伸/压缩图像m
, xf=1,yf=m
。
rotate(angle)
: Rotates the input by an angle of angle
in the clockwise direction, in radians.
rotate(angle)
:旋转通过的角度的输入angle
沿顺时针方向,以弧度表示。
translate(dx,dy)
: Shifts the input by dx,dy
.
translate(dx,dy)
:将输入移位dx,dy
。
Example 2.0:
范例2.0:
var image = document.getElementById('game-img');
demoCanvas.drawImage(image, 0, 0, 400, 400);
demoCanvas.rotate(Math.PI / 6);
demoCanvas.scale(2, 2);
demoCanvas.translate(10, 10);
demoCanvas.drawImage(image, 0, 0, 400, 400);
In Example 2.0, notice how the original image is intact. Only the second image (overlay) is transformed by three methods — rotate, scale, transform.
在示例2.0中,请注意原始图像是完整的。 只有第二个图像(叠加层)可以通过三种方法进行变换-旋转,缩放,变换。
To revert all transformations:
还原所有转换:
demoCanvas.setTransform(1, 0, 0, 0, 0, 1);
// sets the transform to the identity matrix
NOTE:
注意:
For advanced users, you may want to look at the transform
and setTransform
methods. This will let you set the 3D transformation matrix directly.
对于高级用户,您可能需要查看transform
和setTransform
方法。 这将使您直接设置3D转换矩阵。
getImageData
and putImageData
are not affected by the transform. That means if you draw a black rectangle using putImageData
, it won’t be transformed (rotated/scaled/translated).
getImageData
和putImageData
不受转换的影响。 这意味着,如果您使用putImageData
绘制黑色矩形,则将不会对其进行变换(旋转/缩放/翻译)。
As changing the transform only works for drawings done after applying it, you can’t scale/rotate/translate the existing canvas directly (nor does getImageData
and then putImageData
work). You may have to create another hidden canvas of the same size — and then copy the image-data into the 2nd canvas, then use drawImage
on the 2nd canvas.
由于更改转换仅适用于应用后完成的工程图,因此您无法直接缩放/旋转/转换现有画布( getImageData
和putImageData
不起作用)。 您可能必须创建另一个相同大小的隐藏画布-然后将图像数据复制到第二个画布中,然后在第二个画布上使用drawImage
。
Check this example: https://canvasdemo2d.github.io/ (source: https://github.com/canvasdemo2d/canvasdemo2d.github.io). Move your cursor over the canvas and see what it does. It won’t work on mobile phones, unfortunately. The cascading effect is due to the fact that I am translating the canvas w.r.t mouse using drawImage
. drawImage
then writes to the same canvas it’s reading from, which causes the repeating pattern!
检查以下示例: https : //canvasdemo2d.github.io/ (来源: https : //github.com/canvasdemo2d/canvasdemo2d.github.io )。 将光标移到画布上,看看它能做什么。 不幸的是,它不适用于手机。 级联效果是由于以下事实:我正在使用drawImage
翻译画布wrt鼠标。 然后, drawImage
写入要读取的同一画布,这将导致重复图案!
As of the time of writing (March 2019), support for hit regions is experimental in Chrome and on Firefox. Mobile browser don’t even support it at all. Hence, I will explain to you “what” could hit regions be used for.
截至撰写本文时(2019年3月), 对热门地区的支持在Chrome和Firefox上尚处于试验阶段 。 移动浏览器甚至根本不支持它。 因此,我将向您解释“什么”可用于打击区域。
Hit regions are used to catch pointer events on the canvas and know “where” the user clicked. For example, you could have two rectangles A & B — when the user clicks A, you want to perform action $A and when the user clicks B, you want to perform action $B. Let’s walk through the whole process!
命中区域用于捕获画布上的指针事件,并知道用户单击的“位置”。 例如,您可能有两个矩形A和B-当用户单击A时,您要执行操作$ A,而当用户单击B时,您要执行操作$ B。 让我们完成整个过程!
A hit region is related to these three things:
热门区域与以下三件事有关:
Path: The current path when the hit region was created (for example, a rectangle). All pointer events inside the path are routed to that hit region.
路径:创建匹配区域时的当前路径(例如,矩形)。 路径内的所有指针事件都被路由到该命中区域。
Id: An unique id string to identify the hit region by the event handler.
ID:唯一的ID字符串,用于标识事件处理程序的匹配区域。
Control: An alternative DOM element ( HTMLButtonElement
, for example) that gets the pointer events instead.
控制:替代的DOM元素(例如HTMLButtonElement
),它获取指针事件。
NOTE: The path is automatically provided by the canvas when adding a new hit region. Only one — id or control — is needed to form a hit region.
注意:添加新的匹配区域时,画布会自动提供路径。 只需一个ID(即ID或控件)即可构成一个点击区域。
Methods for manipulating the hit-region list of a canvas are:
操纵画布的命中区域列表的方法是:
addHitRegion(options)
: Takes a HitRegionOptions
object and forms a hit-region enclosed by the current path. The options
argument should be a string id
property or a HTMLElement
control
property.
addHitRegion(options)
:获取一个HitRegionOptions
对象,并形成一个被当前路径包围的点击区域。 options
参数应该是字符串id
属性或HTMLElement
control
属性。
removeHitRegion(id)
: Removes the hit region with the id id
so that it no longer receives any pointer events.
removeHitRegion(id)
:删除ID为id
的点击区域,以使其不再接收任何指针事件。
clearHitRegions()
: Removes all hit regions.
clearHitRegions()
:删除所有命中区域。
demoCanvas.fillStyle = 'red';
demoCanvas.rect(10,10,60,60);
demoCanvas.fill();// first rectangle
demoCanvas.addHitRegion({ id: 'btn1' });
demoCanvas.fillStyle = 'blue';
demoCanvas.rect(10,110,60,60);
demoCanvas.fill();
demoCanvas.addHitRegion({ id: 'btn2' });
document.getElementById('demo-canvas').onpointerdown = function(evt) {
// demoCanvas is the 2d context, not the HTMLCanvasElement
console.log('Hello id: ' + evt.region);// region is hitregion id
}
// This code might not work due to this being an
// unsupported (new) feature of HTML5.
NOTE: Hit regions aren’t supported — but that doesn’t mean you have to use them to capture pointer events. You could create your “own hit-region list” and representations of boundaries of regions (cause you can’t get the current path from the canvas, too bad). In the document.getElementById('demo-canvas').onpointerdown
method, get the current clientX,clientY
properties and search through the hit region list. Based on the hit region that contains the point, you can perform the intended action.
注意:不支持命中区域-但这并不意味着您必须使用它们来捕获指针事件。 您可以创建“自己的命中区域列表”以及区域边界的表示形式(因为无法从画布中获取当前路径,太糟糕了)。 在document.getElementById('demo-canvas').onpointerdown
方法中,获取当前的clientX,clientY
属性并搜索clientX,clientY
区域列表。 根据包含该点的命中区域,可以执行预期的操作。
State saving is a convenience provided by the W3C specification. You can save the current state of a canvas and restore it later.
状态保存是W3C规范提供的一种便利。 您可以保存画布的当前状态,并在以后还原它。
You could also build such a system (partially) by writing your own JavaScript model. But you would have to save a quite of stuff: transformation matrix, hit-region list, style properties, and so on. Furthermore, you cannot revert the clipping area (we’ll get to the clip
method in some time) directly.
您也可以(通过部分编写)自己JavaScript模型来构建这样的系统。 但是您将不得不保存很多东西:转换矩阵,命中区域列表,样式属性等等。 此外,您不能直接还原剪切区域(我们将在一段时间后使用clip
方法)。
NOTE: The save
/ restore
methods do not save & restore the actual drawing/pixels. They only save other properties.
注意: save
/ restore
方法不会保存和还原实际的图形/像素。 它们仅保存其他属性。
Hence, I would recommend heavily using the save
& restore
methods to go back and forth instead of erasing stuff on your own or making your own state-saving mechanism.
因此,我建议大量使用save
& restore
方法来回移动,而不要自己擦除内容或建立自己的状态保存机制。
The CanvasRendering2DContext
object has an associated state stack. The save
method will push the current canvas state onto that stack, while the restore
method will pop the latest state from the stack.
CanvasRendering2DContext
对象具有关联的状态堆栈。 save
方法将当前画布状态推入该堆栈,而restore
方法将从堆栈中弹出最新状态。
The Clipping Region
裁剪区域
The clipping region is a specific region in which all drawings are to be done. Obviously, by default, the clipping region is the rectangle is the whole canvas. But you may want to draw in a specific region instead of the whole thing. For example, you may want to draw the lower half of a star formed by multiple lineTo
methods.
剪切区域是要在其中完成所有绘制的特定区域。 显然,默认情况下,裁剪区域是矩形,是整个画布。 但是您可能需要绘制特定区域而不是整个区域。 例如,您可能想绘制由多个lineTo
方法形成的星形的下半部分。
So, for example, let’s say you know how to draw a star in the canvas. It touches all sides of the canvas. But now you want to only display the lower half of the star. In this scenario, you would:
例如,假设您知道如何在画布上绘制星星。 它触及画布的所有侧面。 但是现在您只想显示星星的下半部分。 在这种情况下,您将:
To clip a region, you have to call the clip()
method which does the following:
要裁剪区域,您必须调用执行以下操作的clip()
方法:
The
clip()
method must create a new clipping region by calculating the intersection of the current clipping region and the area described by the path, using the non-zero winding number rule. Open subpaths must be implicitly closed when computing the clipping region, without affecting the actual subpaths. The new clipping region replaces the current clipping region.
clip()
方法必须使用非零缠绕数规则,通过计算当前剪切区域与路径描述的面积的交集来创建新的剪切区域。 计算裁剪区域时,必须隐式关闭打开的子路径,而不会影响实际的子路径。 新的裁剪区域将替换当前的裁剪区域。The
clip()
method must create a new clipping region by calculating the intersection of the current clipping region and the area described by the path, using the non-zero winding number rule. Open subpaths must be implicitly closed when computing the clipping region, without affecting the actual subpaths. The new clipping region replaces the current clipping region.
clip()
方法必须使用非零缠绕数规则,通过计算当前剪切区域与路径描述的面积的交集来创建新的剪切区域。 计算裁剪区域时,必须隐式关闭打开的子路径,而不会影响实际的子路径。 新的裁剪区域将替换当前的裁剪区域。The
clip()
method must create a new clipping region by calculating the intersection of the current clipping region and the area described by the path, using the non-zero winding number rule. Open subpaths must be implicitly closed when computing the clipping region, without affecting the actual subpaths. The new clipping region replaces the current clipping region.When the context is initialized, the clipping region must be set to the rectangle with the top left corner at (0,0) and the width and height of the coordinate space.
clip()
方法必须使用非零缠绕数规则,通过计算当前剪切区域与路径描述的面积的交集来创建新的剪切区域。 计算裁剪区域时,必须隐式关闭打开的子路径,而不会影响实际的子路径。 新的裁剪区域将替换当前的裁剪区域。 初始化上下文时,必须将裁剪区域设置为左上角为(0,0)且坐标空间的宽度和高度为矩形。The
clip()
method must create a new clipping region by calculating the intersection of the current clipping region and the area described by the path, using the non-zero winding number rule. Open subpaths must be implicitly closed when computing the clipping region, without affecting the actual subpaths. The new clipping region replaces the current clipping region.When the context is initialized, the clipping region must be set to the rectangle with the top left corner at (0,0) and the width and height of the coordinate space.
clip()
方法必须使用非零缠绕数规则,通过计算当前剪切区域与路径描述的面积的交集来创建新的剪切区域。 计算裁剪区域时,必须隐式关闭打开的子路径,而不会影响实际的子路径。 新的裁剪区域将替换当前的裁剪区域。 初始化上下文时,必须将裁剪区域设置为左上角为(0,0)且坐标空间的宽度和高度为矩形。The
clip()
method must create a new clipping region by calculating the intersection of the current clipping region and the area described by the path, using the non-zero winding number rule. Open subpaths must be implicitly closed when computing the clipping region, without affecting the actual subpaths. The new clipping region replaces the current clipping region.When the context is initialized, the clipping region must be set to the rectangle with the top left corner at (0,0) and the width and height of the coordinate space.— W3C Documentation for Canvas 2D Context
clip()
方法必须使用非零缠绕数规则,通过计算当前剪切区域与路径描述的面积的交集来创建新的剪切区域。 计算裁剪区域时,必须隐式关闭打开的子路径,而不会影响实际的子路径。 新的裁剪区域将替换当前的裁剪区域。 初始化上下文时,必须将裁剪区域设置为左上角为(0,0)且坐标空间的宽度和高度为矩形。 — Canvas 2D上下文的W3C文档
demoCanvas.save();
demoCanvas.rect(0, 200, 400, 200);// lower-half rectangle subpath
demoCanvas.clip();
/* star drawing method */
demoCanvas.restore();
That’s all for now. I will write an article on animations with the canvas and how to write a custom interface completely on the canvas.
目前为止就这样了。 我将撰写有关画布动画的文章,以及如何在画布上完全编写自定义界面。
Further reading:
进一步阅读:
How to synchronize your game app across multiple Android devices
Shukant Pal is the creator of the Silcos kernel. He is an avid learner and is now practicing advanced web application development. He has hands-on experience with React and its ecosystem.
Shukant Pal是Silcos内核的创建者。 他是一个狂热的学习者,现在正在从事高级Web应用程序开发。 他在React及其生态系统方面具有实践经验。
All quotations are taken from the W3C docs for Canvas 2D Context.
所有报价均来自W3C文档的Canvas 2D Context。
Hey, I’m Shukant Pal. I am developing a lot of web applications in my free time. Follow me on social media.
嘿,我是Shukant Pal。 我的业余时间正在开发许多Web应用程序。 在社交媒体上关注我。
翻译自: https://www.freecodecamp.org/news/full-overview-of-the-html-canvas-6354216fba8d/