应用样式和颜色

在关于绘制形状的章节中,我们只使用了默认的线条和填充样式。在这里,我们将探索 canvas 中可供我们使用的选项,使我们的绘图更具吸引力。您将学习如何在绘图中添加不同的颜色、线条样式、渐变、图案和阴影。

注意:画布内容无法被屏幕阅读器访问。如果画布纯粹是装饰性的,请在<canvas>的开始标签中包含role="presentation"。否则,请将描述性文本作为aria-label属性的值直接包含在画布元素本身中,或者包含放置在画布开始和结束标签之间的回退内容。画布内容不是 DOM 的一部分,但嵌套的回退内容是。

颜色

到目前为止,我们只看到了绘图上下文的方法。如果要将颜色应用于形状,可以使用两个重要的属性:fillStylestrokeStyle

fillStyle = color

设置填充形状时使用的样式。

strokeStyle = color

设置形状轮廓的样式。

color 是一个字符串,表示 CSS <color>、渐变对象或图案对象。我们稍后将了解渐变和图案对象。默认情况下,笔触和填充颜色设置为黑色(CSS 颜色值#000000)。

注意:当您设置strokeStyle和/或fillStyle属性时,新值将成为从那时起所有绘制形状的默认值。对于每个想要使用不同颜色的形状,您都需要重新分配fillStylestrokeStyle属性。

根据规范,您可以输入的有效字符串应该是 CSS <color>值。以下每个示例都描述了相同的颜色。

js
// 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循环来绘制一个矩形网格,每个矩形都使用不同的颜色。生成的图像应该看起来像截图。这里没有发生太惊人的事情。我们使用两个变量ij为每个正方形生成唯一的 RGB 颜色,并且只修改红色和绿色值。蓝色通道的值是固定的。通过修改通道,您可以生成各种调色板。通过增加步长,您可以实现类似于 Photoshop 使用的颜色调色板的效果。

js
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()方法绘制圆形而不是正方形。

js
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属性可能很有用,但在其他情况下,通常在设置形状颜色时为各个形状设置透明度更有用。

因为strokeStylefillStyle属性接受 CSS rgb 颜色值,所以我们可以使用以下表示法为它们分配透明颜色。

js
// 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循环中的每个步骤都绘制一组半径递增的圆形。最终结果是径向渐变。通过将越来越多的圆形叠加在一起,我们有效地降低了已经绘制的圆形的透明度。通过增加步数并有效地绘制更多圆形,背景将从图像的中心完全消失。

js
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()为您提供了更多控制和灵活性,因为我们可以分别设置填充和笔触样式。

js
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 个单位。但是,最左边以及所有其他奇数整数宽度粗细的线条并不清晰,因为路径的位置。

js
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) 的矩形。它们之间(浅红色)的整个区域都落在像素边界上,因此生成的填充矩形将具有清晰的边缘。

Three coordinate grids. The grid lines are actual pixels on the screen. The top left corner of each grid is labeled (0,0). In the first grid, a rectangle from (2,1) to (5,5) is filled in light-red color. In the second grid, (3,1) to (3,5) is joined with a 1-pixel thick royal blue line. The royal-blue line is centered on a grid line, extends from 2.5 to 3.5 on the x access, halfway into the pixels on either side of the graph line, with a light blue background on either side extending from 2 to 4 on the x-access. To avoid the light blue blur extension of the line in the second coordinate grid, the path in, the third coordinate grid is a royal-blue from line (3.5,1) to (3.5,5). The 1 pixel line width ends up completely and precisely filling a single pixel vertical line.

如果您考虑从 (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属性确定每条线的端点如何绘制。此属性有三个可能的值,分别是:buttroundsquare。默认情况下,此属性设置为butt

butt

线条的末端在端点处被截平。

round

线条的末端被圆角化。

square

线条的末端通过添加一个宽度相等且高度为线条粗细一半的框来截平。

在此示例中,我们将绘制三条线,每条线都使用不同的lineCap属性值。我还添加了两个参考线以查看这三者之间的确切差异。这些线条中的每一线条都精确地从这些参考线上开始和结束。

左侧的线条使用默认的butt选项。您会注意到它完全与参考线齐平绘制。第二个设置为使用round选项。这在末端添加了一个半圆,其半径为线条宽度的一半。右侧的线条使用square选项。这添加了一个宽度相等且高度为线条粗细一半的框。

js
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属性确定形状中两个连接线段(线、弧或曲线)如何连接在一起(长度为零的退化线段,其指定的端点和控制点正好位于相同的位置,将被跳过)。

此属性有三个可能的值:roundbevelmiter。默认情况下,此属性设置为miter。请注意,如果两个连接线段具有相同的方向,则lineJoin设置无效,因为在这种情况下不会添加连接区域

round

通过填充以连接线段的公共端点为中心的额外圆盘扇形来圆角化形状的角。这些圆角的半径等于线宽的一半。

bevel

填充连接线段的公共端点与每个线段的单独外部矩形角之间的额外三角形区域。

miter

连接线段通过扩展其外边缘连接到一个点来连接,效果是填充一个额外的菱形区域。此设置受miterLimit属性的影响,该属性将在下面解释。

下面的示例绘制了三条不同的路径,演示了这三种lineJoin属性设置中的每一种;输出如上所示。

js
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 = max miterLength / lineWidth = 1 / sin ( min θ / 2 )
  • 默认的 10.0 斜接限制将去除所有小于约 11 度的锐角的斜接。
  • 等于 √2 ≈ 1.4142136(四舍五入)的斜接限制将去除所有锐角的斜接,仅保留钝角或直角的斜接连接。
  • 等于 1.0 的斜接限制有效,但会禁用所有斜接。
  • 小于 1.0 的值对于斜接限制无效。

这是一个小演示,您可以在其中动态设置miterLimit并查看这如何影响画布上的形状。蓝线显示了锯齿图案中每条线的起点和终点。

如果在此演示中将miterLimit值指定为低于 4.2,则所有可见的角都不会以斜接扩展方式连接,而只会以靠近蓝线的微小斜角方式连接;如果miterLimit高于 10,则此演示中的大多数角都应以远离蓝线的斜接方式连接,并且其高度在角之间从左到右逐渐减小,因为它们以越来越大的角度连接;对于中间值,左侧的角只会以靠近蓝线的斜角连接,而右侧的角则以斜接扩展方式连接(高度也在减小)。

js
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属性设置开始图案的偏移量。

在此示例中,我们正在创建蚂蚁行进效果。这是一种动画技术,通常在计算机图形程序的选择工具中找到。它通过为边框设置动画来帮助用户将选择边框与图像背景区分开来。在本教程的后面部分,您可以学习如何执行此操作和其他基本动画

js
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对象。然后,我们可以将此对象分配给fillStylestrokeStyle属性。

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)。

例如

js
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>的字符串,指示渐变在过渡到该偏移量时应达到的颜色。

您可以根据需要向渐变添加任意数量的颜色停止点。下面是一个从白色到黑色的非常简单的线性渐变。

js
const lineargradient = ctx.createLinearGradient(0, 0, 150, 150);
lineargradient.addColorStop(0, "white");
lineargradient.addColorStop(1, "black");

createLinearGradient示例

在此示例中,我们将创建两个不同的渐变。如您在此处看到的,strokeStylefillStyle属性都可以接受canvasGradient对象作为有效输入。

js
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 中看到的“经典”径向渐变(即,具有单个中心点的渐变,渐变从该点以圆形形状向外扩展)更复杂的效果。

js
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示例

在此示例中,我们将定义两个不同的圆锥渐变。圆锥渐变与径向渐变的不同之处在于,它不是创建圆形,而是围绕一个点旋转。

js
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 是图像的源(即 HTMLImageElementSVGImageElement、另一个 HTMLCanvasElementOffscreenCanvasHTMLVideoElementVideoFrame,或 ImageBitmap)。type 是一个字符串,指示如何使用图像。

type 指定了如何使用图像来创建图案,并且必须是以下字符串值之一

repeat

在垂直和水平方向上平铺图像。

repeat-x

水平平铺图像,但垂直不平铺。

repeat-y

垂直平铺图像,但水平不平铺。

no-repeat

不平铺图像。它只使用一次。

我们使用此方法创建 CanvasPattern 对象,它与我们上面看到的渐变方法非常相似。创建图案后,我们可以将其分配给 fillStylestrokeStyle 属性。例如

js
const img = new Image();
img.src = "someimage.png";
const ptrn = ctx.createPattern(img, "repeat");

注意:drawImage() 方法一样,必须确保在调用此方法之前已加载使用的图像,否则图案可能绘制不正确。

一个 createPattern 示例

在这个最后一个示例中,我们将创建一个图案并将其分配给 fillStyle 属性。唯一值得注意的是使用了图像的 onload 处理程序。这是为了确保在将图像分配给图案之前已加载图像。

js
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 颜色值,指示阴影效果的颜色;默认为完全透明的黑色。

属性 shadowOffsetXshadowOffsetY 指示阴影应在 X 和 Y 方向上从对象延伸多远;这些值不受当前变换矩阵的影响。使用负值会导致阴影向上或向左延伸,使用正值会导致阴影向下或向右延伸。这两个值默认为 0。

shadowBlur 属性指示模糊效果的大小;此值不对应于像素数,也不受当前变换矩阵的影响。默认值为 0。

shadowColor 属性是指示阴影效果颜色的标准 CSS 颜色值;默认为完全透明的黑色。

注意:阴影仅针对 source-over 合成操作 绘制。

一个带阴影的文本示例

此示例绘制一个带有阴影效果的文本字符串。

js
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(或 clipisPointInPath)时,您可以选择提供一个填充规则算法,以确定点是在路径内还是路径外,从而确定它是否被填充。当路径自交或嵌套时,这很有用。

有两个可能的值

nonzero

非零缠绕规则 non-zero winding rule,这是默认规则。

evenodd

奇偶缠绕规则 even-odd winding rule

在此示例中,我们使用的是 evenodd 规则。

js
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");
}