应用样式和颜色
在关于绘制形状的章节中,我们只使用了默认的线条和填充样式。在这里,我们将探索 canvas 中可供我们使用的选项,使我们的绘图更具吸引力。您将学习如何在绘图中添加不同的颜色、线条样式、渐变、图案和阴影。
注意:画布内容无法被屏幕阅读器访问。如果画布纯粹是装饰性的,请在<canvas>
的开始标签中包含role="presentation"
。否则,请将描述性文本作为aria-label
属性的值直接包含在画布元素本身中,或者包含放置在画布开始和结束标签之间的回退内容。画布内容不是 DOM 的一部分,但嵌套的回退内容是。
颜色
到目前为止,我们只看到了绘图上下文的方法。如果要将颜色应用于形状,可以使用两个重要的属性:fillStyle
和 strokeStyle
。
fillStyle = color
-
设置填充形状时使用的样式。
strokeStyle = color
-
设置形状轮廓的样式。
color
是一个字符串,表示 CSS <color>
、渐变对象或图案对象。我们稍后将了解渐变和图案对象。默认情况下,笔触和填充颜色设置为黑色(CSS 颜色值#000000
)。
注意:当您设置strokeStyle
和/或fillStyle
属性时,新值将成为从那时起所有绘制形状的默认值。对于每个想要使用不同颜色的形状,您都需要重新分配fillStyle
或strokeStyle
属性。
根据规范,您可以输入的有效字符串应该是 CSS <color>
值。以下每个示例都描述了相同的颜色。
// these all set the fillStyle to 'orange'
ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255 165 0)";
ctx.fillStyle = "rgb(255 165 0 / 100%)";
一个fillStyle
示例
在这个示例中,我们再次使用两个for
循环来绘制一个矩形网格,每个矩形都使用不同的颜色。生成的图像应该看起来像截图。这里没有发生太惊人的事情。我们使用两个变量i
和j
为每个正方形生成唯一的 RGB 颜色,并且只修改红色和绿色值。蓝色通道的值是固定的。通过修改通道,您可以生成各种调色板。通过增加步长,您可以实现类似于 Photoshop 使用的颜色调色板的效果。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 6; j++) {
ctx.fillStyle = `rgb(${Math.floor(255 - 42.5 * i)} ${Math.floor(
255 - 42.5 * j,
)} 0)`;
ctx.fillRect(j * 25, i * 25, 25, 25);
}
}
}
结果如下所示
一个strokeStyle
示例
此示例类似于上面的示例,但使用strokeStyle
属性更改形状轮廓的颜色。我们使用arc()
方法绘制圆形而不是正方形。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 6; j++) {
ctx.strokeStyle = `rgb(0 ${Math.floor(255 - 42.5 * i)} ${Math.floor(
255 - 42.5 * j,
)})`;
ctx.beginPath();
ctx.arc(12.5 + j * 25, 12.5 + i * 25, 10, 0, 2 * Math.PI, true);
ctx.stroke();
}
}
}
结果如下所示
透明度
除了在画布上绘制不透明的形状外,我们还可以绘制半透明(或半透明)的形状。这可以通过设置globalAlpha
属性或为笔触和/或填充样式分配半透明颜色来完成。
globalAlpha = transparencyValue
-
将指定的透明度值应用于画布上将来绘制的所有形状。该值必须介于 0.0(完全透明)到 1.0(完全不透明)之间。此值默认为 1.0(完全不透明)。
如果您想要在画布上绘制许多具有相似透明度的形状,则globalAlpha
属性可能很有用,但在其他情况下,通常在设置形状颜色时为各个形状设置透明度更有用。
因为strokeStyle
和fillStyle
属性接受 CSS rgb 颜色值,所以我们可以使用以下表示法为它们分配透明颜色。
// Assigning transparent colors to stroke and fill style
ctx.strokeStyle = "rgb(255 0 0 / 50%)";
ctx.fillStyle = "rgb(255 0 0 / 50%)";
rgb()
函数有一个可选的额外参数。最后一个参数设置此特定颜色的透明度值。有效范围指定为介于0%
(完全透明)和100%
(完全不透明)之间的百分比,或介于0.0
(相当于0%
)和1.0
(相当于100%
)之间的数字。
一个globalAlpha
示例
在这个示例中,我们将绘制一个由四种不同颜色正方形组成的背景。在这些正方形之上,我们将绘制一组半透明的圆形。globalAlpha
属性设置为0.2
,这将用于从那时起的所有形状。for
循环中的每个步骤都绘制一组半径递增的圆形。最终结果是径向渐变。通过将越来越多的圆形叠加在一起,我们有效地降低了已经绘制的圆形的透明度。通过增加步数并有效地绘制更多圆形,背景将从图像的中心完全消失。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// draw background
ctx.fillStyle = "#FD0";
ctx.fillRect(0, 0, 75, 75);
ctx.fillStyle = "#6C0";
ctx.fillRect(75, 0, 75, 75);
ctx.fillStyle = "#09F";
ctx.fillRect(0, 75, 75, 75);
ctx.fillStyle = "#F30";
ctx.fillRect(75, 75, 75, 75);
ctx.fillStyle = "#FFF";
// set transparency value
ctx.globalAlpha = 0.2;
// Draw semi transparent circles
for (let i = 0; i < 7; i++) {
ctx.beginPath();
ctx.arc(75, 75, 10 + 10 * i, 0, Math.PI * 2, true);
ctx.fill();
}
}
使用带有 alpha 透明度的rgb()
的示例
在这个第二个示例中,我们做的事情与上面类似,但是,我没有将圆形叠加在一起,而是绘制了不透明度递增的小矩形。使用rgb()
为您提供了更多控制和灵活性,因为我们可以分别设置填充和笔触样式。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// Draw background
ctx.fillStyle = "rgb(255 221 0)";
ctx.fillRect(0, 0, 150, 37.5);
ctx.fillStyle = "rgb(102 204 0)";
ctx.fillRect(0, 37.5, 150, 37.5);
ctx.fillStyle = "rgb(0 153 255)";
ctx.fillRect(0, 75, 150, 37.5);
ctx.fillStyle = "rgb(255 51 0)";
ctx.fillRect(0, 112.5, 150, 37.5);
// Draw semi transparent rectangles
for (let i = 0; i < 10; i++) {
ctx.fillStyle = `rgb(255 255 255 / ${(i + 1) / 10})`;
for (let j = 0; j < 4; j++) {
ctx.fillRect(5 + i * 14, 5 + j * 37.5, 14, 27.5);
}
}
}
线条样式
有几个属性允许我们设置线条样式。
lineWidth = value
-
设置将来绘制的线条的宽度。
lineCap = type
-
设置线条端点的外观。
lineJoin = type
-
设置线条交汇处的“角”的外观。
miterLimit = value
-
在两条线以锐角连接时,在斜接处建立一个限制,让您控制连接处变多厚的程度。
getLineDash()
-
返回当前虚线图案数组,该数组包含偶数个非负数。
setLineDash(segments)
-
设置当前虚线图案。
lineDashOffset = value
-
指定在直线上虚线数组的起始位置。
通过查看下面的示例,您将更好地理解它们的作用。
一个lineWidth
示例
此属性设置当前线条的粗细。值必须是正数。默认情况下,此值设置为 1.0 个单位。
线宽是在给定路径上居中的笔触的粗细。换句话说,绘制的区域扩展到路径两侧线宽的一半。因为画布坐标不直接引用像素,所以必须特别注意才能获得清晰的水平和垂直线。
在下面的示例中,绘制了 10 条线条,线条宽度逐渐增加。最左边的线条宽度为 1.0 个单位。但是,最左边以及所有其他奇数整数宽度粗细的线条并不清晰,因为路径的位置。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
for (let i = 0; i < 10; i++) {
ctx.lineWidth = 1 + i;
ctx.beginPath();
ctx.moveTo(5 + i * 14, 5);
ctx.lineTo(5 + i * 14, 140);
ctx.stroke();
}
}
获得清晰的线条需要了解路径是如何描边的。在下图中,网格表示画布坐标网格。网格线之间的正方形是实际的屏幕像素。在下图中的第一个网格图像中,填充了一个从 (2,1) 到 (5,5) 的矩形。它们之间(浅红色)的整个区域都落在像素边界上,因此生成的填充矩形将具有清晰的边缘。
如果您考虑从 (3,1) 到 (3,5) 的路径,线宽为1.0
,则会得到第二个图像中的情况。要填充的实际区域(深蓝色)仅扩展到路径两侧像素的一半。必须渲染对此的近似值,这意味着那些仅部分阴影的像素,并导致整个区域(浅蓝色和深蓝色)填充颜色,该颜色仅为实际笔触颜色的一半暗。这就是在前面的示例代码中使用1.0
宽度线条时发生的情况。
要解决此问题,您必须在路径创建中非常精确。知道1.0
宽度线条将扩展到路径两侧的半个单位,创建从 (3.5,1) 到 (3.5,5) 的路径会导致第三个图像中的情况——1.0
线宽最终完全精确地填充一个像素垂直线。
注意:请注意,在我们的垂直线示例中,Y 位置仍然引用了一个整数网格线位置——如果不是这样,我们将在端点处看到像素覆盖率的一半(但还要注意,此行为取决于当前的lineCap
样式,其默认值为butt
;您可能希望通过将lineCap
样式设置为square
来计算奇数宽度线条的一半像素坐标的一致笔触,以便笔触围绕端点的外部边界将自动扩展以完全覆盖整个像素)。
还要注意,只有路径的起点和终点会受到影响:如果路径使用closePath()
闭合,则没有起点和终点;相反,路径中的所有端点都使用lineJoin
样式的当前设置连接到其附加的前一个和下一个线段,其默认值为miter
,效果是自动将连接线段的外边界扩展到它们的交点,以便如果这些连接线段是水平和/或垂直的,则渲染的笔触将完全覆盖以每个端点为中心的完整像素。有关这些附加线样式的演示,请参见接下来的两节。
对于偶数宽度的线条,每条线的一半最终都是整数个像素,因此您需要一条位于像素之间的路径(即,从 (3,1) 到 (3,5)),而不是位于像素的中间。
虽然在最初使用可缩放的 2D 图形时有点痛苦,但注意像素网格和路径的位置可以确保您的绘图无论缩放或涉及任何其他变换都能正确显示。在正确位置绘制的 1.0 宽度的垂直线在放大 2 倍时会变成清晰的 2 像素线,并且会出现在正确的位置。
lineCap
示例
lineCap
属性确定每条线的端点如何绘制。此属性有三个可能的值,分别是:butt
、round
和square
。默认情况下,此属性设置为butt
在此示例中,我们将绘制三条线,每条线都使用不同的lineCap
属性值。我还添加了两个参考线以查看这三者之间的确切差异。这些线条中的每一线条都精确地从这些参考线上开始和结束。
左侧的线条使用默认的butt
选项。您会注意到它完全与参考线齐平绘制。第二个设置为使用round
选项。这在末端添加了一个半圆,其半径为线条宽度的一半。右侧的线条使用square
选项。这添加了一个宽度相等且高度为线条粗细一半的框。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// Draw guides
ctx.strokeStyle = "#09f";
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(140, 10);
ctx.moveTo(10, 140);
ctx.lineTo(140, 140);
ctx.stroke();
// Draw lines
ctx.strokeStyle = "black";
["butt", "round", "square"].forEach((lineCap, i) => {
ctx.lineWidth = 15;
ctx.lineCap = lineCap;
ctx.beginPath();
ctx.moveTo(25 + i * 50, 10);
ctx.lineTo(25 + i * 50, 140);
ctx.stroke();
});
}
lineJoin
示例
lineJoin
属性确定形状中两个连接线段(线、弧或曲线)如何连接在一起(长度为零的退化线段,其指定的端点和控制点正好位于相同的位置,将被跳过)。
此属性有三个可能的值:round
、bevel
和miter
。默认情况下,此属性设置为miter
。请注意,如果两个连接线段具有相同的方向,则lineJoin
设置无效,因为在这种情况下不会添加连接区域
round
-
通过填充以连接线段的公共端点为中心的额外圆盘扇形来圆角化形状的角。这些圆角的半径等于线宽的一半。
bevel
-
填充连接线段的公共端点与每个线段的单独外部矩形角之间的额外三角形区域。
miter
-
连接线段通过扩展其外边缘连接到一个点来连接,效果是填充一个额外的菱形区域。此设置受
miterLimit
属性的影响,该属性将在下面解释。
下面的示例绘制了三条不同的路径,演示了这三种lineJoin
属性设置中的每一种;输出如上所示。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
ctx.lineWidth = 10;
["round", "bevel", "miter"].forEach((lineJoin, i) => {
ctx.lineJoin = lineJoin;
ctx.beginPath();
ctx.moveTo(-5, 5 + i * 40);
ctx.lineTo(35, 45 + i * 40);
ctx.lineTo(75, 5 + i * 40);
ctx.lineTo(115, 45 + i * 40);
ctx.lineTo(155, 5 + i * 40);
ctx.stroke();
});
}
miterLimit
属性的演示
正如您在上一个示例中看到的,当使用miter
选项连接两条线时,两条连接线的外部边缘会扩展到它们相遇的点。对于彼此成大角度的线条,该点距离内部连接点不远。但是,随着每条线之间角度的减小,这些点之间的距离(斜接长度)呈指数增长。
miterLimit
属性确定外部连接点可以从内部连接点放置多远。如果两条线的距离超过此值,则会绘制斜角连接。请注意,最大斜接长度是在当前坐标系中测量的线宽与该miterLimit
属性的值的乘积(其默认值为 HTML <canvas>
中的 10.0),因此可以独立于当前显示比例或路径的任何仿射变换来设置miterLimit
:它仅影响线边缘的有效渲染形状。
更准确地说,斜接限制是扩展长度与线宽一半之比的最大允许值(在 HTML canvas 中,它是在线连接边缘的外角与路径中指定的连接线段的公共端点之间测量的)。它可以等效地定义为连接边缘内外连接点之间距离与总线宽之比的最大允许值。然后它等于连接线段最小内角一半的余割,在该内角以下不会渲染斜接连接,而只会渲染斜角连接
miterLimit
= maxmiterLength
/lineWidth
= 1 / sin ( min θ / 2 )- 默认的 10.0 斜接限制将去除所有小于约 11 度的锐角的斜接。
- 等于 √2 ≈ 1.4142136(四舍五入)的斜接限制将去除所有锐角的斜接,仅保留钝角或直角的斜接连接。
- 等于 1.0 的斜接限制有效,但会禁用所有斜接。
- 小于 1.0 的值对于斜接限制无效。
这是一个小演示,您可以在其中动态设置miterLimit
并查看这如何影响画布上的形状。蓝线显示了锯齿图案中每条线的起点和终点。
如果在此演示中将miterLimit
值指定为低于 4.2,则所有可见的角都不会以斜接扩展方式连接,而只会以靠近蓝线的微小斜角方式连接;如果miterLimit
高于 10,则此演示中的大多数角都应以远离蓝线的斜接方式连接,并且其高度在角之间从左到右逐渐减小,因为它们以越来越大的角度连接;对于中间值,左侧的角只会以靠近蓝线的斜角连接,而右侧的角则以斜接扩展方式连接(高度也在减小)。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// Clear canvas
ctx.clearRect(0, 0, 150, 150);
// Draw guides
ctx.strokeStyle = "#09f";
ctx.lineWidth = 2;
ctx.strokeRect(-5, 50, 160, 50);
// Set line styles
ctx.strokeStyle = "#000";
ctx.lineWidth = 10;
// check input
if (document.getElementById("miterLimit").checkValidity()) {
ctx.miterLimit = parseFloat(document.getElementById("miterLimit").value);
}
// Draw lines
ctx.beginPath();
ctx.moveTo(0, 100);
for (let i = 0; i < 24; i++) {
const dy = i % 2 === 0 ? 25 : -25;
ctx.lineTo(Math.pow(i, 1.5) * 2, 75 + dy);
}
ctx.stroke();
return false;
}
使用虚线
setLineDash
方法和lineDashOffset
属性指定线条的虚线图案。setLineDash
方法接受一个数字列表,该列表指定交替绘制线条和间隙的距离,而lineDashOffset
属性设置开始图案的偏移量。
在此示例中,我们正在创建蚂蚁行进效果。这是一种动画技术,通常在计算机图形程序的选择工具中找到。它通过为边框设置动画来帮助用户将选择边框与图像背景区分开来。在本教程的后面部分,您可以学习如何执行此操作和其他基本动画。
const ctx = document.getElementById("canvas").getContext("2d");
let offset = 0;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setLineDash([4, 2]);
ctx.lineDashOffset = -offset;
ctx.strokeRect(10, 10, 100, 100);
}
function march() {
offset++;
if (offset > 5) {
offset = 0;
}
draw();
setTimeout(march, 20);
}
march();
渐变
就像任何普通的绘图程序一样,我们可以使用线性、径向和圆锥渐变来填充和描边形状。我们通过使用以下方法之一创建CanvasGradient
对象。然后,我们可以将此对象分配给fillStyle
或strokeStyle
属性。
createLinearGradient(x1, y1, x2, y2)
-
创建一个线性渐变对象,其起点为 (
x1
,y1
),终点为 (x2
,y2
)。 createRadialGradient(x1, y1, r1, x2, y2, r2)
-
创建一个径向渐变。参数表示两个圆,一个圆的中心位于 (
x1
,y1
),半径为r1
,另一个圆的中心位于 (x2
,y2
),半径为r2
。 createConicGradient(angle, x, y)
-
创建一个圆锥渐变对象,其起始角度为以弧度表示的
angle
,位于位置 (x
,y
)。
例如
const lineargradient = ctx.createLinearGradient(0, 0, 150, 150);
const radialgradient = ctx.createRadialGradient(75, 75, 0, 75, 75, 100);
创建CanvasGradient
对象后,我们可以使用addColorStop()
方法为其分配颜色。
gradient.addColorStop(position, color)
-
在
gradient
对象上创建一个新的颜色停止点。position
是 0.0 到 1.0 之间的一个数字,定义颜色在渐变中的相对位置,而color
参数必须是表示 CSS<color>
的字符串,指示渐变在过渡到该偏移量时应达到的颜色。
您可以根据需要向渐变添加任意数量的颜色停止点。下面是一个从白色到黑色的非常简单的线性渐变。
const lineargradient = ctx.createLinearGradient(0, 0, 150, 150);
lineargradient.addColorStop(0, "white");
lineargradient.addColorStop(1, "black");
createLinearGradient
示例
在此示例中,我们将创建两个不同的渐变。如您在此处看到的,strokeStyle
和fillStyle
属性都可以接受canvasGradient
对象作为有效输入。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// Create gradients
const lingrad = ctx.createLinearGradient(0, 0, 0, 150);
lingrad.addColorStop(0, "#00ABEB");
lingrad.addColorStop(0.5, "#fff");
lingrad.addColorStop(0.5, "#26C000");
lingrad.addColorStop(1, "#fff");
const lingrad2 = ctx.createLinearGradient(0, 50, 0, 95);
lingrad2.addColorStop(0.5, "#000");
lingrad2.addColorStop(1, "rgb(0 0 0 / 0%)");
// assign gradients to fill and stroke styles
ctx.fillStyle = lingrad;
ctx.strokeStyle = lingrad2;
// draw shapes
ctx.fillRect(10, 10, 130, 130);
ctx.strokeRect(50, 50, 50, 50);
}
第一个是背景渐变。如您所见,我们在相同的位置分配了两种颜色。这样做是为了使颜色过渡非常锐利——在本例中是从白色到绿色。通常,定义颜色停止点的顺序无关紧要,但在这种特殊情况下,顺序非常重要。如果您按照希望它们出现的顺序保留赋值,则不会出现问题。
在第二个渐变中,我们没有分配起始颜色(在位置 0.0 处),因为这不是绝对必要的,因为它会自动假设下一个颜色停止点的颜色。因此,在位置 0.5 处分配黑色会自动使渐变从开始到此停止点变为黑色。
createRadialGradient
示例
在此示例中,我们将定义四个不同的径向渐变。因为我们可以控制渐变的起点和终点,所以我们可以实现比我们通常在例如 Photoshop 中看到的“经典”径向渐变(即,具有单个中心点的渐变,渐变从该点以圆形形状向外扩展)更复杂的效果。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// Create gradients
const radgrad = ctx.createRadialGradient(45, 45, 10, 52, 50, 30);
radgrad.addColorStop(0, "#A7D30C");
radgrad.addColorStop(0.9, "#019F62");
radgrad.addColorStop(1, "rgb(1 159 98 / 0%)");
const radgrad2 = ctx.createRadialGradient(105, 105, 20, 112, 120, 50);
radgrad2.addColorStop(0, "#FF5F98");
radgrad2.addColorStop(0.75, "#FF0188");
radgrad2.addColorStop(1, "rgb(255 1 136 / 0%)");
const radgrad3 = ctx.createRadialGradient(95, 15, 15, 102, 20, 40);
radgrad3.addColorStop(0, "#00C9FF");
radgrad3.addColorStop(0.8, "#00B5E2");
radgrad3.addColorStop(1, "rgb(0 201 255 / 0%)");
const radgrad4 = ctx.createRadialGradient(0, 150, 50, 0, 140, 90);
radgrad4.addColorStop(0, "#F4F201");
radgrad4.addColorStop(0.8, "#E4C700");
radgrad4.addColorStop(1, "rgb(228 199 0 / 0%)");
// draw shapes
ctx.fillStyle = radgrad4;
ctx.fillRect(0, 0, 150, 150);
ctx.fillStyle = radgrad3;
ctx.fillRect(0, 0, 150, 150);
ctx.fillStyle = radgrad2;
ctx.fillRect(0, 0, 150, 150);
ctx.fillStyle = radgrad;
ctx.fillRect(0, 0, 150, 150);
}
在这种情况下,我们将起点从终点略微偏移以实现球形 3D 效果。最好避免让内外圆重叠,因为这会导致难以预测的奇怪效果。
四个渐变中的最后一个颜色停止点使用完全透明的颜色。如果您希望从这里到之前的颜色停止点有一个不错的过渡,则两种颜色应相等。从代码中不太明显,因为它使用两种不同的 CSS 颜色方法作为演示,但在第一个渐变中#019F62 = rgb(1 159 98 / 100%)
。
createConicGradient
示例
在此示例中,我们将定义两个不同的圆锥渐变。圆锥渐变与径向渐变的不同之处在于,它不是创建圆形,而是围绕一个点旋转。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// Create gradients
const conicGrad1 = ctx.createConicGradient(2, 62, 75);
conicGrad1.addColorStop(0, "#A7D30C");
conicGrad1.addColorStop(1, "#fff");
const conicGrad2 = ctx.createConicGradient(0, 187, 75);
// we multiply our values by Math.PI/180 to convert degrees to radians
conicGrad2.addColorStop(0, "black");
conicGrad2.addColorStop(0.25, "black");
conicGrad2.addColorStop(0.25, "white");
conicGrad2.addColorStop(0.5, "white");
conicGrad2.addColorStop(0.5, "black");
conicGrad2.addColorStop(0.75, "black");
conicGrad2.addColorStop(0.75, "white");
conicGrad2.addColorStop(1, "white");
// draw shapes
ctx.fillStyle = conicGrad1;
ctx.fillRect(12, 25, 100, 100);
ctx.fillStyle = conicGrad2;
ctx.fillRect(137, 25, 100, 100);
}
第一个渐变位于第一个矩形的中心,并在开始时将绿色颜色停止点移动到结束时的白色颜色停止点。角度从 2 弧度开始,这是因为开始/结束线指向东南方向而显而易见。
第二个渐变也位于其第二个矩形的中心。这个渐变有多个颜色停止点,在每次旋转四分之一时在黑色和白色之间交替。这给了我们棋盘格效果。
图案
在上页面的示例之一中,我们使用一系列循环来创建图像图案。但是,有一种更简单的方法:createPattern()
方法。
createPattern(image, type)
-
创建并返回一个新的画布图案对象。
image
是图像的源(即HTMLImageElement
、SVGImageElement
、另一个HTMLCanvasElement
或OffscreenCanvas
、HTMLVideoElement
或VideoFrame
,或ImageBitmap
)。type
是一个字符串,指示如何使用图像。
type 指定了如何使用图像来创建图案,并且必须是以下字符串值之一
我们使用此方法创建 CanvasPattern
对象,它与我们上面看到的渐变方法非常相似。创建图案后,我们可以将其分配给 fillStyle
或 strokeStyle
属性。例如
const img = new Image();
img.src = "someimage.png";
const ptrn = ctx.createPattern(img, "repeat");
注意:与 drawImage()
方法一样,必须确保在调用此方法之前已加载使用的图像,否则图案可能绘制不正确。
一个 createPattern
示例
在这个最后一个示例中,我们将创建一个图案并将其分配给 fillStyle
属性。唯一值得注意的是使用了图像的 onload
处理程序。这是为了确保在将图像分配给图案之前已加载图像。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
// create new image object to use as pattern
const img = new Image();
img.src = "canvas_createpattern.png";
img.onload = () => {
// create pattern
const ptrn = ctx.createPattern(img, "repeat");
ctx.fillStyle = ptrn;
ctx.fillRect(0, 0, 150, 150);
};
}
阴影
使用阴影仅涉及四个属性
shadowOffsetX = float
-
指示阴影应从对象延伸的水平距离。此值不受变换矩阵的影响。默认为 0。
shadowOffsetY = float
-
指示阴影应从对象延伸的垂直距离。此值不受变换矩阵的影响。默认为 0。
shadowBlur = float
-
指示模糊效果的大小;此值不对应于像素数,也不受当前变换矩阵的影响。默认值为 0。
shadowColor = color
-
一个标准的 CSS 颜色值,指示阴影效果的颜色;默认为完全透明的黑色。
属性 shadowOffsetX
和 shadowOffsetY
指示阴影应在 X 和 Y 方向上从对象延伸多远;这些值不受当前变换矩阵的影响。使用负值会导致阴影向上或向左延伸,使用正值会导致阴影向下或向右延伸。这两个值默认为 0。
shadowBlur
属性指示模糊效果的大小;此值不对应于像素数,也不受当前变换矩阵的影响。默认值为 0。
shadowColor
属性是指示阴影效果颜色的标准 CSS 颜色值;默认为完全透明的黑色。
注意:阴影仅针对 source-over
合成操作 绘制。
一个带阴影的文本示例
此示例绘制一个带有阴影效果的文本字符串。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 2;
ctx.shadowColor = "rgb(0 0 0 / 50%)";
ctx.font = "20px Times New Roman";
ctx.fillStyle = "Black";
ctx.fillText("Sample String", 5, 30);
}
我们将在下一章关于 绘制文本 的内容中介绍 font
属性和 fillText
方法。
Canvas 填充规则
当使用 fill
(或 clip
和 isPointInPath
)时,您可以选择提供一个填充规则算法,以确定点是在路径内还是路径外,从而确定它是否被填充。当路径自交或嵌套时,这很有用。
有两个可能的值
nonzero
-
非零缠绕规则 non-zero winding rule,这是默认规则。
evenodd
-
奇偶缠绕规则 even-odd winding rule。
在此示例中,我们使用的是 evenodd
规则。
function draw() {
const ctx = document.getElementById("canvas").getContext("2d");
ctx.beginPath();
ctx.arc(50, 50, 30, 0, Math.PI * 2, true);
ctx.arc(50, 50, 15, 0, Math.PI * 2, true);
ctx.fill("evenodd");
}