当前位置: 首页 > news >正文

canvas(填充描边,绘制矩形、路径、文本、图像,变换,阴影,渐变等)

一、 基本的画布功能

创建 <canvas> 元素时至少要设置其 width height 属性,这样才能告诉浏览器在多大面积上绘
图。出现在开始和结束标签之间的内容是后备数据,会在浏览器不支持 <canvas> 元素时显示。比如:
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
        与其他元素一样,width height 属性也可以在 DOM 节点上设置,因此可以随时修改。整个元 素还可以通过 CSS 添加样式,并且元素在添加样式或实际绘制内容前是不可见的。
        要在画布上绘制图形,首先要取得绘图上下文。使用 getContext() 方法可以获取对绘图上下文的 引用。对于平面图形,需要给这个方法传入参数"2d" ,表示要获取 2D 上下文对象:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let context = drawing.getContext("2d");// 其他代码
}
使用 <canvas> 元素时,最好先测试一下 getContext() 方法是否存在。有些浏览器对 HTML 规范
中没有的元素会创建默认 HTML 元素对象。这就意味着即使 drawing 包含一个有效的元素引用,
getContext() 方法也未必存在。
        可以使用 toDataURL() 方法导出 <canvas> 元素上的图像。这个方法接收一个参数:要生成图像 的 MIME 类型(与用来创建图形的上下文无关)。例如,要从画布上导出一张 PNG 格式的图片,可以这 样做:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {// 取得图像的数据 URIlet imgURI = drawing.toDataURL("image/png");// 显示图片let image = document.createElement("img");image.src = imgURI;document.body.appendChild(image);
}

浏览器默认将图像编码为 PNG 格式,除非另行指定。Firefox Opera 还支持传入"image/jpeg"

进行 JPEG 编码。因为这个方法是后来才增加到规范中的,所以支持的浏览器也是在后面的版本实现的, 包括 IE9 Firefox 3.5 Opera 10
注意 如果画布中的图像是其他域绘制过来的, toDataURL()方法就会抛出错误。

二、2D 绘图上下文

2D 绘图上下文提供了绘制 2D 图形的方法,包括矩形、弧形和路径。 2D 上下文的坐标原点 (0, 0) 在 <canvas>元素的左上角。所有坐标值都相对于该点计算,因此 x 坐标向右增长, y 坐标向下增长。默认 情况下,width height 表示两个方向上像素的最大值。
1 填充和描边
2D 上下文有两个基本绘制操作:填充和描边。填充以指定样式(颜色、渐变或图像)自动填充形
状,而描边只为图形边界着色。大多数 2D 上下文操作有填充和描边的变体,显示效果取决于两个属性: fillStyle 和 strokeStyle
这两个属性可以是字符串、渐变对象或图案对象,默认值都为 "#000000" 。字符串表示颜色值,可
以是 CSS 支持的任意格式:名称、十六进制代码、 rgb rgba hsl hsla 。比如:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let context = drawing.getContext("2d");context.strokeStyle = "red";context.fillStyle = "#0000ff";
}

这里把 strokeStyle 设置为"red"CSS 颜色名称),把 fillStyle 设置为"#0000ff"(蓝色)。

所有与描边和填充相关的操作都会使用这两种样式,除非再次修改。这两个属性也可以是渐变或图案, 本章后面会讨论。
2 绘制矩形
矩形是唯一一个可以直接在 2D 绘图上下文中绘制的形状。
与绘制矩形相关的方法有 3 个: fillRect()、 strokeRect() clearRect()
这些方法都接收 4 个参数:矩形 x 坐标、矩形 y 坐标、 矩形宽度和矩形高度。这几个参数的单位都是像素。
fillRect()方法
用于以指定颜色在画布上绘制并填充矩形。填充的颜色使用 fillStyle 属性指
定。来看下面的例子:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let context = drawing.getContext("2d");// 绘制红色矩形context.fillStyle = "#ff0000";context.fillRect(10, 10, 50, 50);// 绘制半透明蓝色矩形context.fillStyle = "rgba(0,0,255,0.5)";context.fillRect(30, 30, 50, 50);
}

以上代码先将 fillStyle 设置为红色并在坐标点(10, 10)绘制了一个宽高均为 50 像素的矩形。接

着,使用 rgba() 格式将 fillStyle 设置为半透明蓝色,并绘制了另一个与第一个部分重叠的矩形。
结果就是可以透过蓝色矩形看到红色矩形。
strokeRect()方法

使用通过 strokeStyle 属性指定的颜色绘制矩形轮廓。下面是一个例子:

let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let context = drawing.getContext("2d");// 绘制红色轮廓的矩形context.strokeStyle = "#ff0000";context.strokeRect(10, 10, 50, 50);// 绘制半透明蓝色轮廓的矩形context.strokeStyle = "rgba(0,0,255,0.5)";context.strokeRect(30, 30, 50, 50);
}

以上代码同样绘制了两个重叠的矩形,不过只有轮廓,而不是实心的
        注意 描边宽度由 lineWidth 属性控制,它可以是任意整数值。类似地, lineCap 属性控
制线条端点的形状[ "butt" (平头)、 "round" (出圆头)或 "square" (出方头)],而 lineJoin
属性控制线条交点的形状[ "round" (圆转)、 "bevel" (取平)或 "miter" (出尖)]。
clearRect()方法
可以擦除画布中某个区域。该方法用于把绘图上下文中的某个区域变透明。
通过先绘制形状再擦除指定区域,可以创建出有趣的效果,比如从已有矩形中开个孔。来看下面的例子:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let context = drawing.getContext("2d");// 绘制红色矩形context.fillStyle = "#ff0000";context.fillRect(10, 10, 50, 50);// 绘制半透明蓝色矩形context.fillStyle = "rgba(0,0,255,0.5)";context.fillRect(30, 30, 50, 50);// 在前两个矩形重叠的区域擦除一个矩形区域context.clearRect(40, 40, 10, 10);
}

以上代码在两个矩形重叠的区域上擦除了一个小矩形
3 绘制路径
2D 绘图上下文支持很多在画布上绘制路径的方法。通过路径可以创建复杂的形状和线条。要绘制
路径,必须首先调用 beginPath() 方法以表示要开始绘制新路径。然后,再调用下列方法来绘制路径。
arc(x, y, radius, startAngle, endAngle, counterclockwise)
以坐标 (x, y) 为圆 心,以 radius 为半径绘制一条弧线,起始角度为 startAngle ,结束角度为 endAngle (都是 弧度)。最后一个参数 counterclockwise 表示是否逆时针计算起始角度和结束角度(默认为 顺时针)。
arcTo(x1, y1, x2, y2, radius)
以给定半径 radius ,经由 (x1, y1) 绘制一条从上一点 到(x2, y2) 的弧线。
bezierCurveTo(c1x, c1y, c2x, c2y, x, y)
(c1x, c1y) (c2x, c2y) 为控制点, 绘制一条从上一点到(x, y) 的弧线(三次贝塞尔曲线)。
lineTo(x, y)
绘制一条从上一点到 (x, y) 的直线。
moveTo(x, y)
不绘制线条,只把绘制光标移动到 (x, y)
quadraticCurveTo(cx, cy, x, y)
(cx, cy) 为控制点,绘制一条从上一点到 (x, y) 的弧线(二次贝塞尔曲线)。
rect(x, y, width, height)
以给定宽度和高度在坐标点 (x, y) 绘制一个矩形。这个方法 与 strokeRect() fillRect() 的区别在于,它创建的是一条路径,而不是独立的图形。
创建路径之后,可以使用 closePath() 方法绘制一条返回起点的线。如果路径已经完成,则既可
以指定 fillStyle 属性并调用 fill() 方法来填充路径,也可以指定 strokeStyle 属性并调用
stroke() 方法来描画路径,还可以调用 clip() 方法基于已有路径创建一个新剪切区域。
下面这个例子使用前面提到的方法绘制了一个不带数字的表盘:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let 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();
}

这个例子使用 arc() 绘制了两个圆形,一个外圆和一个内圆,以构成表盘的边框。外圆半径 99
素,原点为 (100,100) ,也就是画布的中心。要绘制完整的圆形,必须从 0 弧度绘制到 弧度(使用数 学常量 Math.PI )。而在绘制内圆之前,必须先把路径移动到内圆上的一点,以避免绘制出多余的线条。 第二次调用 arc() 时使用了稍小一些的半径,以呈现边框效果。然后,再组合运用 moveTo() lineTo() 分别绘制分针和时针。最后一步是调用 stroke() ,得到如图 所示的图像。
        路径是 2D 上下文的主要绘制机制,为绘制结果提供了很多控制。因为路径经常被使用,所以也有 一个 isPointInPath() 方法,接收 x 轴和 y 轴坐标作为参数。这个方法用于确定指定的点是否在路径 上,可以在关闭路径前随时调用,比如:
if (context.isPointInPath(100, 100)) {alert("Point (100, 100) is in the path.");
}

2D 上下文的路径 API 非常可靠,可用于创建涉及各种填充样式、描述样式等的复杂图像。

4 绘制文本
文本和图像混合也是常见的绘制需求,因此 2D 绘图上下文还提供了绘制文本的方法,即 fillText()
strokeText() 。这两个方法都接收 4 个参数:要绘制的字符串、 x 坐标、 y 坐标和可选的最大像素
宽度。而且,这两个方法最终绘制的结果都取决于以下 3 个属性。
font
CSS 语法指定的字体样式、大小、字体族等,比如 "10px Arial"
textAlign
指定文本的对齐方式,可能的值包括 "start" "end" "left" "right" 和 "center"。推荐使用 "start" "end" ,不使用 "left" "right" ,因为前者无论在从左到右书写的语言还是从右到左书写的语言中含义都更明确。
textBaseLine
指定文本的基线,可能的值包括 "top" "hanging" "middle"
"alphabetic" "ideographic" "bottom"
这些属性都有相应的默认值,因此没必要每次绘制文本时都设置它们。
fillText() 方法使用 fillStyle 属性绘制文本,而 strokeText() 方法使用 strokeStyle 属性。通常, fillText() 方法 是使用最多的,因为它模拟了在网页中渲染文本。例如,下面的例子会在前一节示例的表盘顶部绘制数
字“ 12 ”:
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);

因为把 textAlign 设置为了 "center" ,把 textBaseline 设置为了 "middle" ,所以 (100, 20)
示文本水平和垂直中心点的坐标。如果 textAlign "start" ,那么 x 坐标在从左到右书写的语言中
表示文本的左侧坐标,而 "end" 会让 x 坐标在从左到右书写的语言中表示文本的右侧坐标。例如:
// 正常
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);
// 与开头对齐
context.textAlign = "start";
context.fillText("12", 100, 40);
// 与末尾对齐
context.textAlign = "end";
context.fillText("12", 100, 60);

22
字符串 "12" 被绘制了 3 次,每次使用的坐标都一样,但 textAlign 值不同。为了让每个字符串不
至于重叠,每次绘制的 y 坐标都会设置得大一些。
因为表盘中垂直的线条是居中的,所以文本的对齐方式就一目了然了。类似地,通过修改 textBaseline 属性,可以改变文本的垂直对齐方式。比如,设置为"top" 意味着 y 坐标表示文本顶部, "bottom" 表示 文本底部,"hanging" "alphabetic" "ideographic" 分别引用字体中特定的基准点。
由于绘制文本很复杂,特别是想把文本绘制到特定区域的时候,因此 2D 上下文提供了用于辅助确
定文本大小的 measureText() 方法。这个方法接收一个参数,即要绘制的文本,然后返回一个
TextMetrics 对象。这个返回的对象目前只有一个属性 width ,不过将来应该会增加更多度量指标。
measureText() 方法使用 font textAlign textBaseline 属性当前的值计算绘制指定文本
后的大小。例如,假设要把文本 "Hello world!" 放到一个 140 像素宽的矩形中,可以使用以下代码,
100 像素的字体大小开始计算,不断递减,直到文本大小合适:
let fontSize = 100;
context.font = fontSize + "px Arial";
while(context.measureText("Hello world!").width > 140) {
fontSize--;
context.font = fontSize + "px Arial";
}
context.fillText("Hello world!", 10, 10);
context.fillText("Font size is " + fontSize + "px", 10, 50);

fillText() strokeText() 方法还有第四个参数,即文本的最大宽度。这个参数是可选的
Firefox 4 是第一个实现它的浏览器),如果调用 fillText() strokeText() 时提供了此参数,但要
绘制的字符串超出了最大宽度限制,则文本会以正确的字符高度绘制,这时字符会被水平压缩,以达到 限定宽度。
绘制文本是一项比较复杂的操作,因此支持 <canvas> 元素的浏览器不一定全部实现了相关的文本
绘制 API
5 变换
上下文变换可以操作绘制在画布上的图像。 2D 绘图上下文支持所有常见的绘制变换。在创建绘制
上下文时,会以默认值初始化变换矩阵,从而让绘制操作如实应用到绘制结果上。对绘制上下文应用变 换,可以导致以不同的变换矩阵应用绘制操作,从而产生不同的结果。
以下方法可用于改变绘制上下文的变换矩阵。
rotate(angle)
围绕原点把图像旋转 angle 弧度。
scale(scaleX, scaleY)
通过在 x 轴乘以 scaleX 、在 y 轴乘以 scaleY 来缩放图像。 scaleX 和 scaleY 的默认值都是 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()
变换可以简单,也可以复杂。
例如,在前面绘制表盘的例子中,如果把坐标原点移动到表盘中心, 那再绘制表针就非常简单了:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let 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();
}

把原点移动到(100, 100),也就是表盘的中心后,要绘制表针只需简单的数学计算即可。这是因为所 有计算都是基于(0, 0),而不是(100, 100)了。当然,也可以使用 rotate()方法来转动表针

let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let 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.rotate(1);// 绘制分针context.moveTo(0, 0);context.lineTo(0, -85);// 绘制时针context.moveTo(0, 0);context.lineTo(-65, 0);// 描画路径context.stroke();
}

因为原点已经移动到表盘中心,所以旋转就是以该点为圆心的。这相当于把表针一头固定在表盘中
心,然后向右拨了一个弧度。
所有这些变换,包括 fillStyle strokeStyle 属性,会一直保留在上下文中,直到再次修改
它们。虽然没有办法明确地将所有值都重置为默认值,但有两个方法可以帮我们跟踪变化。如果想着什 么时候再回到当前的属性和变换状态,可以调用 save() 方法。调用这个方法后,所有这一时刻的设置 会被放到一个暂存栈中。保存之后,可以继续修改上下文。而在需要恢复之前的上下文时,可以调用 restore()方法。这个方法会从暂存栈中取出并恢复之前保存的设置。多次调用 save() 方法可以在暂 存栈中存储多套设置,然后通过 restore() 可以系统地恢复。下面来看一个例子:
context.fillStyle = "#ff0000";
context.save();
context.fillStyle = "#00ff00";
context.translate(100, 100);
context.save();
context.fillStyle = "#0000ff";
context.fillRect(0, 0, 100, 200); // 在(100, 100)绘制蓝色矩形
context.restore();
context.fillRect(10, 10, 100, 200); // 在(100, 100)绘制绿色矩形
context.restore();
context.fillRect(0, 0, 100, 200); // 在(0, 0)绘制红色矩形

以上代码先将 fillStyle 设置为红色,然后调用 save()。接着,将 fillStyle 修改为绿色,坐

标移动到 (100, 100) ,并再次调用 save() ,保存设置。随后,将 fillStyle 属性设置为蓝色并绘制一
个矩形。因为此时坐标被移动了,所以绘制矩形的坐标实际上是 (100, 100) 。在调用 restore() 之后, fillStyle 恢复为绿色,因此这一次绘制的矩形是绿色的。而绘制矩形的坐标是 (110, 110) ,因为变换 仍在起作用。再次调用 restore() 之后,变换被移除, fillStyle 也恢复为红色。绘制最后一个矩形 的坐标变成了(0, 0)
注意, save() 方法只保存应用到绘图上下文的设置和变换,不保存绘图上下文的内容
6 绘制图像(把现有图像绘制到画布上)
2D 绘图上下文内置支持操作图像。如果想把现有图像绘制到画布上,可以使用 drawImage() 方法。
这个方法可以接收 3 组不同的参数,并产生不同的结果。最简单的调用是传入一个 HTML <img> 元素, 以及表示绘制目标的 x y 坐标,结果是把图像绘制到指定位置。比如:
let image = document.images[0];
context.drawImage(image, 10, 10);
以上代码获取了文本中的第一个图像,然后在画布上的坐标 (10, 10) 处将它绘制了出来。绘制出来的 图像与原来的图像一样大。如果想改变所绘制图像的大小,可以再传入另外两个参数:目标宽度和目标 高度。这里的缩放只影响绘制的图像,不影响上下文的变换矩阵。比如下面的例子:
context.drawImage(image, 50, 10, 20, 30);

执行之后,图像会缩放到 20 像素宽、30 像素高。

还可以只把图像绘制到上下文中的一个区域。此时,需要给 drawImage() 提供 9 个参数:要绘制
的图像、源图像 x 坐标、源图像 y 坐标、源图像宽度、源图像高度、目标区域 x 坐标、目标区域 y 坐标、
目标区域宽度和目标区域高度。这个重载后的 drawImage() 方法可以实现最大限度的控制,比如:
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);

最终,原始图像中只有一部分会绘制到画布上。这一部分从(0, 10)开始,50 像素宽、50 像素高。而 绘制到画布上时,会从(0, 100)开始,变成 40 像素宽、60 像素高。

第一个参数除了可以是 HTML <img> 元素,还可以是另一个 <canvas> 元素,这样就会把另一个
画布的内容绘制到当前画布上。
结合其他一些方法, drawImage() 方法可以方便地实现常见的图像操作。操作的结果可以使用
toDataURL() 方法获取。不过有一种情况例外:如果绘制的图像来自其他域而非当前页面,则不能获取 其数据。此时,调用 toDataURL() 将抛出错误。比如,如果来自 www.example.com 的页面上绘制的是 来自 www.wrox.com 的图像,则上下文就是“脏的”,获取数据时会抛出错误。
7 阴影
2D 上下文可以根据以下属性的值自动为已有形状或路径生成阴影。
shadowColor
CSS 颜色值,表示要绘制的阴影颜色,默认为黑色。
shadowOffsetX
阴影相对于形状或路径的 x 坐标的偏移量,默认为 0
shadowOffsetY
阴影相对于形状或路径的 y 坐标的偏移量,默认为 0
shadowBlur
像素,表示阴影的模糊量。默认值为 0 ,表示不模糊。
这些属性都可以通过 context 对象读写。只要在绘制图形或路径前给这些属性设置好适当的值,
阴影就会自动生成。比如:
let context = drawing.getContext("2d");
// 设置阴影
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = "rgba(0, 0, 0, 0.5)";
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制蓝色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);

这里两个矩形使用了相同的阴影样式,得到了如图 所示的结果。
8 渐变
createLinearGradient线性渐变
渐变通过 CanvasGradient 的实例表示,在 2D 上下文中创建和修改都非常简单。要创建一个新的
线性渐变,可以调用上下文的 createLinearGradient() 方法。这个方法接收 4 个参数:起点 x 坐标、 起点 y 坐标、终点 x 坐标和终点 y 坐标。调用之后,该方法会以指定大小创建一个新的 CanvasGradient 对象并返回实例。 有了 gradient 对象后,接下来要使用 addColorStop() 方法为渐变指定色标。这个方法接收两个参数:色标位置和 CSS 颜色字符串。色标位置通过 0 1 范围内的值表示, 0 是第一种颜色, 1 是最后 一种颜色。比如:
let gradient = context.createLinearGradient(30, 30, 70, 70);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");

这个 gradient 对象现在表示的就是在画布上从(30, 30)(70, 70)绘制一个渐变。渐变的起点颜色

为白色,终点颜色为黑色。可以把这个对象赋给 fillStyle strokeStyle 属性,从而以渐变填充
或描画绘制的图形:
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);

为了让渐变覆盖整个矩形,而不只是其中一部分,两者的坐标必须搭配合适。以上代码将得到如图
所示的结果。
如果矩形没有绘制到渐变的范围内,则只会显示部分渐变。比如:
context.fillStyle = gradient;
context.fillRect(50, 50, 50, 50);

以上代码执行之后绘制的矩形只有左上角有一部分白色。这是因为矩形的起点在渐变的中间,此时
颜色的过渡几乎要完成了。结果矩形大部分地方是黑色的,因为渐变不会重复。保持渐变与形状的一致 非常重要,有时候可能需要写个函数计算相应的坐标。比如:
function createRectLinearGradient(context, x, y, width, height) {return context.createLinearGradient(x, y, x+width, y+height);
}

这个函数会基于起点的 xy 坐标和传入的宽度、高度创建渐变对象,之后调用 fillRect()方法

时可以使用相同的值:
let gradient = createRectLinearGradient(context, 30, 30, 50, 50);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);

计算坐标是使用画布时重要而复杂的问题。使用类似 createRectLinearGradient()这样的辅助

函数能让计算坐标简单一些。
createRadialGradient径向渐变
径向渐变(或放射性渐变)要使用 createRadialGradient() 方法来创建。这个方法接收 6 个参
数,分别对应两个圆形圆心的坐标和半径。前 3 个参数指定起点圆形中心的 x y 坐标和半径,后 3 个参 数指定终点圆形中心的 x y 坐标和半径。在创建径向渐变时,可以把两个圆形想象成一个圆柱体的两 个圆形表面。把一个表面定义得小一点,另一个定义得大一点,就会得到一个圆锥体。然后,通过移动 两个圆形的圆心,就可以旋转这个圆锥体。
要创建起点圆心在形状中心并向外扩散的径向渐变,需要将两个圆形设置为同心圆。比如,要在前
面例子中矩形的中心创建径向渐变,则渐变的两个圆形的圆心都必须设置为 (55, 55) 。这是因为矩形的起 点是(30, 30) ,终点是 (80, 80) 。代码如下:
let gradient = context.createRadialGradient(55, 55, 10, 55, 55, 30);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);

运行以上代码会得到如图 所示的效果。
因为创建起来要复杂一些,所以径向渐变比较难处理。不过,通常情况下,起点和终点的圆形都是
同心圆,只要定义好圆心坐标,剩下的就是调整各自半径的问题了。
9 图案
图案是用于填充和描画图形的重复图像。要创建新图案,可以调用 createPattern() 方法并传入
两个参数:一个 HTML <img> 元素和一个表示该如何重复图像的字符串。第二个参数的值与 CSS 的 background-repeat 属性是一样的,包括 "repeat" "repeat-x" "repeat-y" "no-repeat"
比如:
<body><canvas id="drawing" width="200" height="200">A drawing of something.</canvas><img id="image" src="./img.png" alt="Pattern Image" style="display:none;">
</body>
<script>let drawing = document.getElementById("drawing");let image = document.getElementById("image");// 确保浏览器支持<canvas>if (drawing.getContext) {let context = drawing.getContext("2d");// 等待图片加载完成image.onload = function() {// 创建图案填充let pattern = context.createPattern(image, "repeat");// 绘制矩形context.fillStyle = pattern;context.fillRect(10, 10, 150, 150);};}
</script>
记住,跟渐变一样,图案的起点实际上是画布的原点 (0, 0) 。将填充样式设置为图案,表示在指定位 置而不是开始绘制的位置显示图案。以上代码执行的结果如图 所示。
传给 createPattern() 方法的第一个参数也可以是 <video> 元素或者另一个 <canvas>
元素。

10 图像数据
2D 上下文中比较强大的一种能力是可以使用 getImageData() 方法获取原始图像数据。这个方法
接收 4 个参数:要取得数据中第一个像素的左上角坐标和要取得的像素宽度及高度。例如,要从 (10, 5) 开始取得 50 像素宽、 50 像素高的区域对应的数据,可以这样写:
let imageData = context.getImageData(10, 5, 50, 50);

返回的对象是一个 ImageData 的实例。每个 ImageData 对象都包含 3 个属性:widthheight

data ,其中, data 属性是包含图像的原始像素信息的数组。每个像素在 data 数组中都由 4 个值表 示,分别代表红、绿、蓝和透明度值。换句话说,第一个像素的信息包含在第 0 到第 3 个值中,比如:
let data = imageData.data,
red = data[0],
green = data[1],
blue = data[2],
alpha = data[3];

这个数组中的每个值都在 0~255 范围内(包括 0 255)。对原始图像数据进行访问可以更灵活地操 作图像。例如,通过更改图像数据可以创建一个简单的灰阶过滤器:

let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d"),
image = document.images[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 (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 并应用到画布上显示出来
imageData.data = data;
context.putImageData(imageData, 0, 0);
}
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Grayscale Image on Canvas</title>
</head><body><canvas id="drawing" width="450" height="450">A drawing of something.</canvas><img id="image" src="https://img0.baidu.com/it/u=2325089072,2338520041&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500" width="450" height="450" alt="Pattern Image">
</body>
<script>// 获取 canvas 元素和上下文const canvas = document.getElementById('drawing');const ctx = canvas.getContext('2d');// 创建一个图像对象const img = new Image();img.crossOrigin = "Anonymous"; // 允许跨域加载img.src = "https://img0.baidu.com/it/u=2325089072,2338520041&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500"; // 请替换为你自己的图片路径// 当图像加载完成后执行img.onload = function() {// 将图像绘制到画布上ctx.drawImage(img, 0, 0);// 获取图像数据const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);const data = imageData.data;// 遍历图像数据并应用灰阶过滤for (let i = 0; i < data.length; i += 4) {const r = data[i]; // 红色const g = data[i + 1]; // 绿色const b = data[i + 2]; // 蓝色// 计算灰阶值(平均值)const gray = (r + g + b) / 3;// 将RGB值设置为灰阶值data[i] = gray; // 红色data[i + 1] = gray; // 绿色data[i + 2] = gray; // 蓝色}// 将修改后的图像数据放回画布ctx.putImageData(imageData, 0, 0);};
</script></html>

这个例子首先在画布上绘制了一个图像,然后又取得了其图像数据。for 循环遍历了图像数据中的

每个像素,注意每次循环都要给 i 加上 4 。每次循环中取得红、绿、蓝的颜色值,计算出它们的平均值。 然后再把原来的值修改为这个平均值,实际上相当于过滤掉了颜色信息,只留下类似亮度的灰度信息。 之后将 data 数组重写回 imageData 对象。最后调用 putImageData() 方法,把图像数据再绘制到画 布上。结果就得到了原始图像的黑白版。
当然,灰阶过滤只是基于原始像素值可以实现的其中一种操作。要了解基于原始图像数据还可以实
现哪些操作,可以参考 Ilmari Heikkinen 的文章“ Making Image Filters with Canvas ”。
注意 只有在画布没有加载跨域内容时才可以获取图像数据。如果画布上绘制的是跨域内
容,则尝试获取图像数据会导致 JavaScript 报错。
11 合成
2D 上下文中绘制的所有内容都会应用两个属性: globalAlpha globalComposition Operation
globalAlpha 属性
是一个范围在 0~1 的值(包括 0 1 ),用于指定所有绘制内容的透明度,默 认值为 0。如果所有后来的绘制都需要使用同样的透明度,那么可以将 globalAlpha 设置为适当的值, 执行绘制,然后再把 globalAlpha 设置为 0 。比如:
// 绘制红色矩形
context.fillStyle = "#ff0000";
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;

在这个例子中,蓝色矩形是绘制在红色矩形上面的。因为在绘制蓝色矩形前 globalAlpha 被设置
成了 0.5 ,所以蓝色矩形就变成半透明了,从而可以透过它看到下面的红色矩形。
globalCompositionOperation 属性
表示新绘制的形状如何与上下文中已有的形状融合。这个属 性是一个字符串,可以取下列值。
source-over
默认值,新图形绘制在原有图形上面。
source-in
新图形只绘制出与原有图形重叠的部分,画布上其余部分全部透明。
source-out
新图形只绘制出不与原有图形重叠的部分,画布上其余部分全部透明。
source-atop
新图形只绘制出与原有图形重叠的部分,原有图形不受影响。
destination-over
新图形绘制在原有图形下面,重叠部分只有原图形透明像素下的部分可见。
destination-in
新图形绘制在原有图形下面,画布上只剩下二者重叠的部分,其余部分完全
透明。
destination-out
新图形与原有图形重叠的部分完全透明,原图形其余部分不受影响。
destination-atop
新图形绘制在原有图形下面,原有图形与新图形不重叠的部分完全透明。
lighter
新图形与原有图形重叠部分的像素值相加,使该部分变亮。
copy
新图形将擦除并完全取代原有图形。
xor
新图形与原有图形重叠部分的像素执行“异或”计算。
以上合成选项的含义很难用语言来表达清楚,只用黑白图像也体现不出所有合成的效果。下面来看
一个例子:
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 设置合成方式
context.globalCompositeOperation = "destination-over";
// 绘制蓝色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);

虽然后绘制的蓝色矩形通常会出现在红色矩形上面,但将 globalCompositeOperation 属性的值
修改为 "destination-over" 意味着红色矩形会出现在蓝色矩形上面。
使用 globalCompositeOperation 属性时,一定记得要在不同浏览器上进行测试。不同浏览器在
实现这些选项时可能存在差异。这些操作在 Safari Chrome 中仍然有些问题,可以参考 MDN 文档上的 CanvasRenderingContext2D.globalCompositeOperation,比较它们与 IE Firefox 渲染的差异。

三、 WebGL

WebGL 是画布的 3D 上下文。与其他 Web 技术不同, WebGL 不是 W3C 制定的标准,而是 Khronos Group 的标准。根据官网描述,“ Khronos Group 是非营利性、会员资助的联盟,专注于多平台和设备下 并行计算、图形和动态媒体的无专利费开放标准”。Khronos Group 也制定了其他图形 API ,包括作为浏 览器中 WebGL 基础的 OpenGL ES 2.0
OpenGL 这种 3D 图形语言很复杂,要使用 WebGL 最好熟悉 OpenGL ES 2.0,因为很多概念可以照搬过来。
要了解关于 OpenGL 的更多信息,可以访问 OpenGL 网站。另外,推荐一个 WebGL 教程网站: Learn WebGL
注意 定型数组是在 WebGL 中执行操作的重要数据结构。
1 WebGL 上下文
在完全支持的浏览器中, WebGL 2.0 上下文的名字叫 "webgl2" WebGL 1.0 上下文的名字叫
"webgl1" 。如果浏览器不支持 WebGL ,则尝试访问 WebGL 上下文会返回 null 。在使用上下文之前,
应该先检测返回值是否存在:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let gl = drawing.getContext("webgl");if (gl){// 使用 WebGL}
}

这里把 WebGL context 对象命名为 gl。大多数 WebGL 应用和例子遵循这个约定,因为 OpenGL ES 2.0 方法和值通常以"gl"开头。这样可以让 JavaScript 代码看起来更接近 OpenGL 程序。

2 WebGL 基础
取得 WebGL 上下文后,就可以开始 3D 绘图了。如前所述,因为 WebGL OpenGL ES 2.0 Web 版,所以本节讨论的概念实际上是 JavaScript 所实现的 OpenGL 概念。
可以在调用 getContext() 取得 WebGL 上下文时指定一些选项。这些选项通过一个参数对象传入,
选项就是参数对象的一个或多个属性。
alpha :布尔值,表示是否为上下文创建透明通道缓冲区,默认为 true
depth :布尔值,表示是否使用 16 位深缓冲区,默认为 true
stencil :布尔值,表示是否使用 8 位模板缓冲区,默认为 false
antialias :布尔值,表示是否使用默认机制执行抗锯齿操作,默认为 true
premultipliedAlpha :布尔值,表示绘图缓冲区是否预乘透明度值,默认为 true
preserveDrawingBuffer :布尔值,表示绘图完成后是否保留绘图缓冲区,默认为 false
建议在充分了解这个选项的作用后再自行修改,因为这可能会影响性能。
可以像下面这样传入 options 对象:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {let gl = drawing.getContext("webgl", { alpha: false });if (gl) {// 使用 WebGL}
}

这些上下文选项大部分适合开发高级功能。多数情况下,默认值就可以满足要求。

如果调用 getContext() 不能创建 WebGL 上下文,某些浏览器就会抛出错误。为此,最好把这个
方法调用包装在 try / catch 块中:
Insert IconMargin [download]
let drawing = document.getElementById("drawing"),gl;
// 确保浏览器支持<canvas>
if (drawing.getContext) {try {gl = drawing.getContext("webgl");} catch (ex) {// 什么也不做}if (gl) {// 使用 WebGL} else {alert("WebGL context could not be created.");}
}

1. 常量
如果你熟悉 OpenGL ,那么可能知道用于操作的各种常量。这些常量在 OpenGL 中的名字以 GL_ 开 头。在 WebGL 中, context 对象上的常量则不包含 GL_ 前缀。例如, GL_COLOR_BUFFER_BIT 常量在 WebGL 中要这样访问 gl.COLOR_BUFFER_BIT WebGL 以这种方式支持大部分 OpenGL 常量(少数常量不支持)。
2. 方法命名
OpenGL (同时也是 WebGL )中的很多方法会包含相关的数据类型信息。接收不同类型和不同数量 参数的方法,会通过方法名的后缀体现这些信息。表示参数数量的数字(1~4 )在先,表示数据类型的 字符串(“f ”表示浮点数,“ i ”表示整数)在后。比如, gl.uniform4f() 的意思是需要 4 个浮点数值 参数,而 gl.uniform3i() 表示需要 3 个整数值参数。
还有很多方法接收数组,这类方法用字母“ v ”( vector )来表示。因此, gl.uniform3iv() 就是要接收一个包含 3 个值的数组参数。在编写 WebGL 代码时,要记住这些约定。
3. 准备绘图
准备使用 WebGL 上下文之前,通常需要先指定一种实心颜色清除 <canvas> 。为此,要调用
clearColor() 方法并传入 4 个参数,分别表示红、绿、蓝和透明度值。每个参数必须是 0~1 范围内的 值,表示各个组件在最终颜色的强度。比如:
gl.clearColor(0, 0, 0, 1); // 黑色
gl.clear(gl.COLOR_BUFFER_BIT);

以上代码把清理颜色缓冲区的值设置为黑色,然后调用 clear()方法,这个方法相当于 OpenGL

中的 glClear() 方法。参数 gl.COLOR_BUFFER_BIT 告诉 WebGL 使用之前定义的颜色填充画布。通 常,所有绘图操作之前都需要先清除绘制区域。
4. 视口与坐标
绘图前还要定义 WebGL 视口。默认情况下,视口使用整个 <canvas> 区域。要改变视口,可以调用 viewport()方法并传入视口相对于 <canvas> 元素的 x y 坐标及宽度和高度。例如,以下代码表示要 使用整个<canvas> 元素:
gl.viewport(0, 0, drawing.width,
drawing.height);

这个视口的坐标系统与网页中通常的坐标系统不一样。视口的 x y 坐标起点(0, 0)表示<canvas>

元素的左下角,向上、向右增长可以用点 (width–1, height–1)定义
知道如何定义视口就可以只使用 <canvas> 元素的一部分来绘图。比如下面的例子:
// 视口是<canvas> 左下角四分之一区域
gl.viewport(0, 0, drawing.width/2, drawing.height/2);
// 视口是<canvas> 左上角四分之一区域
gl.viewport(0, drawing.height/2, drawing.width/2, drawing.height/2);
// 视口是<canvas> 右下角四分之一区域
gl.viewport(drawing.width/2, 0, drawing.width/2, drawing.height/2);
定义视口的坐标系统与视口中的坐标系统不一样。在视口中,坐标原点 (0, 0) 是视口的中心点。左下 角是(–1, –1) ,右上角是 (1, 1)
如果绘图时使用了视口外部的坐标,则绘制结果会被视口剪切。例如,要绘制的形状有一个顶点在
(1, 2) ,则视口右侧的图形会被切掉。
5. 缓冲区
JavaScript 中,顶点信息保存在定型数组中。要使用这些信息,必须先把它们转换为 WebGL 缓冲 区。创建缓冲区要调用 gl.createBuffer() 方法,并使用 gl.bindBuffer() 方法将缓冲区绑定到
WebGL 上下文。绑定之后,就可以用数据填充缓冲区了。比如:
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0.5, 1]), gl.STATIC_DRAW);

调用 gl.bindBuffer()buffer 设置为上下文的当前缓冲区。然后,所有缓冲区操作都在 buffer 上直接执行。因此,调用 gl.bufferData()虽然没有包含对 buffer 的直接引用,但仍然是 在它上面执行的。上面最后一行代码使用一个 Float32Array(通常把所有顶点信息保存在 Float32Array 中)初始化了 buffer。如果想输出缓冲区内容,那么可以调用 drawElements()方法 并传入 gl.ELEMENT_ARRAY_BUFFER

gl.bufferData() 方法的最后一个参数表示如何使用缓冲区。这个参数可以是以下常量值。
gl.STATIC_DRAW :数据加载一次,可以在多次绘制中使用。
gl.STREAM_DRAW :数据加载一次,只能在几次绘制中使用。
gl.DYNAMIC_DRAW :数据可以重复修改,在多次绘制中使用。
除非是很有经验的 OpenGL 程序员,否则我们会对大多数缓冲区使用 gl.STATIC_DRAW
缓冲区会一直驻留在内存中,直到页面卸载。如果不再需要缓冲区,那么最好调用 gl.deleteBuffer() 方法释放其占用的内存:
gl.deleteBuffer(buffer);
6. 错误
JavaScript 多数情况下不同的是,在 WebGL 操作中通常不会抛出错误。必须在调用可能失败的方 法后,调用 gl.getError() 方法。这个方法返回一个常量,表示发生的错误类型。下面列出了这些常量。
gl.NO_ERROR :上一次操作没有发生错误( 0 值)。
gl.INVALID_ENUM :上一次操作没有传入 WebGL 预定义的常量。
gl.INVALID_VALUE :上一次操作需要无符号数值,但是传入了负数。
gl.INVALID_OPERATION :上一次操作在当前状态下无法完成。
gl.OUT_OF_MEMORY :上一次操作因内存不足而无法完成。
gl.CONTEXT_LOST_WEBGL :上一次操作因外部事件(如设备掉电)而丢失了 WebGL 上下文。
每次调用 gl.getError() 方法会返回一个错误值。第一次调用之后,再调用 gl.getError()
能会返回另一个错误值。如果有多个错误,则可以重复这个过程,直到 gl.getError() 返 回
gl.NO_ERROR 。如果执行了多次操作,那么可以通过循环调用 getError()
如果 WebGL 代码没有产出想要的输出结果,那么可以调用几次 getError() ,这样有可能帮你找到问题所在。
7. 着色器
着色器 OpenGL 中的另一个概念。 WebGL 中有两种着色器: 顶点着色器 片段 (或像素) 着色器
顶点着色器用于把 3D 顶点转换为可以渲染的 2D 点。片段着色器用于计算绘制一个像素的正确颜色。
WebGL 着色器的独特之处在于,它们不是 JavaScript 实现的,而是使用一种与 C JavaScript 完全不同 的语言 GLSL OpenGL Shading Language )写的。
编写着色器
GLSL 是一种类似于 C 的语言,专门用于编写 OpenGL 着色器。因为 WebGL OpenGL ES 2 的实现,
所以 OpenGL 中的着色器可以直接在 WebGL 中使用。这样也可以让桌面应用更方便地移植到 Web 上。
每个着色器都有一个 main() 方法,在绘制期间会重复执行。给着色器传递数据的方式有两种:
attribute uniform attribute 用于将顶点传入顶点着色器,而 uniform 用于将常量值传入任
何着色器。 attribute uniform 是在 main() 函数外部定义的。在值类型关键字之后是数据类型,
然后是变量名。下面是一个简单的顶点着色器的例子:
// OpenGL 着色器语言
// 着色器,摘自 Bartek Drozdz 的文章“Get started with WebGL—draw a square”
attribute vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}

这个顶点着色器定义了一个名为 aVertexPosition attribute。这个 attribute 是一个包含

两项的数组(数据类型为 vec2 ),代表 x y 坐标。即使只传入了两个坐标,顶点着色器返回的值也会 包含 4 个元素,保存在变量 gl_Position 中。这个着色器创建了一个新的包含 4 项的数组( vec4 ), 缺少的坐标会补充上,实际上是把 2D 坐标转换为了 3D 坐标。 片段着色器与顶点着色器类似,只不过是通过 uniform 传入数据。下面是一个片段着色器的例子:
// OpenGL 着色器语言
// 着色器,摘自 Bartek Drozdz 的文章“Get started with WebGL—draw a square”
uniform vec4 uColor;
void main() {
gl_FragColor = uColor;
}

片段着色器必须返回一个值,保存到变量 gl_FragColor 中,这个值表示绘制时使用的颜色。这
个着色器定义了一个 uniform ,包含颜色的 4 个组件( vec4 ),保存在 uColor 中。从代码上看,这个 着色器只是把传入的值赋给了 gl_FragColor uColor 的值在着色器内不能改变。
注意 OpenGL 着色器语言比示例中的代码要复杂,详细介绍需要整本书的篇幅。因此,
本节只是从使用 WebGL 的角度对这门语言做个极其简单的介绍。要了解更多信息,可以
参考 Randi J. Rost 的著作《 OpenGL 着色语言》。
创建着色器程序
浏览器并不理解原生 GLSL 代码,因此 GLSL 代码的字符串必须经过编译并链接到一个着色器程序中。为便于使用,通常可以使用带有自定义 type 属性的 <script> 元素把着色器代码包含在网页中。
如果 type 属性无效,则浏览器不会解析 <script> 的内容,但这并不妨碍读写其中的内容:
<script type="x-webgl/x-vertex-shader" id="vertexShader">
attribute vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}
</script>
<script type="x-webgl/x-fragment-shader" id="fragmentShader">
uniform vec4 uColor;
void main() {
gl_FragColor = uColor;
}
</script>

然后可以使用 text 属性提取 <script> 元素的内容:
let vertexGlsl = document.getElementById("vertexShader").text,
fragmentGlsl = document.getElementById("fragmentShader").text;

更复杂的 WebGL 应用可以动态加载着色器。重点在于要使用着色器,必须先拿到 GLSL 代码的字
符串。
有了 GLSL 字符串,下一步是创建 shader 对象。为此,需要调用 gl.createShader() 方法,并
传入想要创建的着色器类型( gl.VERTEX_SHADER gl.FRAGMENT_SHADER )。然后,调用
gl.shaderSource() 方法把 GLSL 代码应用到着色器,再调用 gl.compileShader() 编译着色器。下
面是一个例子:
let vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexGlsl);
gl.compileShader(vertexShader);
let fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentGlsl);
gl.compileShader(fragmentShader);

这里的代码创建了两个着色器,并把它们保存在 vertexShader fragmentShader 中。然后,
可以通过以下代码把这两个对象链接到着色器程序:
let program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

第一行代码创建了一个程序,然后 attachShader() 用于添加着色器。调用 gl.linkProgram()
将两个着色器链接到了变量 program 中。链接到程序之后,就可以通过 gl.useProgram() 方法让
WebGL 上下文使用这个程序了:
gl.useProgram(program);

调用 gl.useProgram()之后,所有后续的绘制操作都会使用这个程序。

给着色器传值
前面定义的每个着色器都需要传入一个值,才能完成工作。要给着色器传值,必须先找到要接收值
的变量。对于 uniform 变量,可以调用 gl.getUniformLocation() 方法。这个方法返回一个对象,
表示该 uniform 变量在内存中的位置。然后,可以使用这个位置来完成赋值。比如:
let uColor = gl.getUniformLocation(program, "uColor");
gl.uniform4fv(uColor, [0, 0, 0, 1]);

这个例子从 program 中找到 uniform 变量 uColor,然后返回了它的内存位置。第二行代码调用

gl.uniform4fv() 方法给 uColor 传入了值。
给顶点着色器传值也是类似的过程。而要获得 attribute 变量的位置,可以调用 gl.getAttrib
Location() 方法。找到变量的内存地址后,可以像下面这样给它传入值:
let aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
gl.enableVertexAttribArray(aVertexPosition);
gl.vertexAttribPointer(aVertexPosition, itemSize, gl.FLOAT, false, 0, 0);

这里,首先取得 aVertexPosition 的内存位置,然后使用 gl.enableVertexAttribArray()
来启用。最后一行代码创建了一个指向调用 gl.bindBuffer() 指定的缓冲区的指针,并把它保存在
aVertexPosition 中,从而可以在后面由顶点着色器使用。
调试着色器和程序
WebGL 中的其他操作类似,着色器操作也可能失败,而且是静默失败。如果想知道发生了什么
错误,则必须手工通过 WebGL 上下文获取关于着色器或程序的信息。
对于着色器,可以调用 gl.getShaderParameter() 方法取得编译之后的编译状态:
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(vertexShader));
}

这个例子检查了 vertexShader 编译的状态。如果着色器编译成功,则调用 gl.getShaderParameter() 会返回 true 。如果返回 false ,则说明编译出错了。此时,可以使用 gl.getShaderInfoLog() 并传 入着色器取得错误。这个方法返回一个字符串消息,表示问题所在。gl.getShaderParameter() 和 gl.getShaderInfoLog()既可以用于顶点着色器,也可以用于片段着色器。
着色器程序也可能失败,因此也有类似的方法。 gl.getProgramParameter() 用于检测状态。最
常见的程序错误发生在链接阶段,为此可以使用以下代码来检查:
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
alert(gl.getProgramInfoLog(program));
}
gl.getShaderParameter() 一样, gl.getProgramParameter() 会在链接成功时返回 true
失败时返回 false 。当然也有一个 gl.getProgramInfoLog() 方法,可以在程序失败时获取错误
信息。
这些方法主要在开发时用于辅助调试。只要没有外部依赖,在产品环境中就可以放心地删除它们。
GLSL 100 升级到 GLSL 300
WebGL2 的主要变化是升级到了 GLSL 3.00 ES 着色器。这个升级暴露了很多新的着色器功能,包括 3D
纹理等在支持 OpenGL ES 3.0 的设备上都有的功能。要使用升级版的着色器,着色器代码的第一行必须是:
#version 300 es
这个升级需要一些语法的变化。
顶点 attribute 变量要使用 in 而不是 attribute 关键字声明。 576 18 章 动画与 Canvas 图形
使用 varying 关键字为顶点或片段着色器声明的变量,现在必须根据相应着色器的行为改为使
in out
预定义的输出变量 gl_FragColor 没有了,片段着色器必须为颜色输出声明自己的 out 变量。
纹理查找函数 texture2D textureCube 统一成了一个 texture 函数。
8. 绘图
WebGL 只能绘制三种形状:点、线和三角形。其他形状必须通过这三种基本形状在 3D 空间的组合
来绘制。 WebGL 绘图要使用 drawArrays() drawElements() 方法,前者使用数组缓冲区,后者则
操作元素数组缓冲区。
drawArrays() drawElements() 的第一个参数都表示要绘制形状的常量。下面列出了这些常量。
gl.POINTS :将每个顶点当成一个点来绘制。
gl.LINES :将数组作为一系列顶点,在这些顶点间绘制直线。每个顶点既是起点也是终点,因
此数组中的顶点必须是偶数个才能开始绘制。
gl.LINE_LOOP :将数组作为一系列顶点,在这些顶点间绘制直线。从第一个顶点到第二个顶点
绘制一条直线,再从第二个顶点到第三个顶点绘制一条直线,以此类推,直到绘制到最后一个
顶点。此时再从最后一个顶点到第一个顶点绘制一条直线。这样就可以绘制出形状的轮廓。
gl.LINE_STRIP :类似于 gl.LINE_LOOP ,区别在于不会从最后一个顶点到第一个顶点绘制直线。
gl.TRIANGLES :将数组作为一系列顶点,在这些顶点间绘制三角形。如不特殊指定,每个三角
形都分开绘制,不共享顶点。
gl.TRIANGLES_STRIP :类似于 gl.TRIANGLES ,区别在于前 3 个顶点之后的顶点会作为第三
个顶点与其前面的两个顶点构成三角形。例如,如果数组中包含顶点 A B C D ,那么第一个
三角形使用 ABC ,第二个三角形使用 BCD
gl.TRIANGLES_FAN :类似于 gl.TRIANGLES ,区别在于前 3 个顶点之后的顶点会作为第三个
顶点与其前面的顶点和第一个顶点构成三角形。例如,如果数组中包含顶点 A B C D ,那么
第一个三角形使用 ABC ,第二个三角形使用 ACD
以上常量可以作为 gl.drawArrays() 方法的第一个参数,第二个参数是数组缓冲区的起点索引,
第三个参数是数组缓冲区包含的顶点集合的数量。以下代码使用 gl.drawArrays() 在画布上绘制了一
个三角形:
// 假设已经使用本节前面的着色器清除了视口
// 定义 3 个顶点的 x 坐标和 y 坐标
let vertices = new Float32Array([ 0, 1, 1, -1, -1, -1 ]),
buffer = gl.createBuffer(),
vertexSetSize = 2,
vertexSetCount = vertices.length/vertexSetSize,
uColor,
aVertexPosition;
// 将数据放入缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 给片段着色器传入颜色
uColor = gl.getUniformLocation(program, "uColor");
gl.uniform4fv(uColor, [ 0, 0, 0, 1 ]);
// 把顶点信息传给着色器
aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
gl.enableVertexAttribArray(aVertexPosition);
gl.vertexAttribPointer(aVertexPosition, vertexSetSize, gl.FLOAT, false, 0, 0);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, vertexSetCount);

这个例子定义了一个 Float32Array 变量,它包含 3 组两个点的顶点。完成计算的关键是跟踪顶
点大小和数量。将 vertexSetSize 的值指定为 2 ,再计算出 vertexSetCount 。顶点信息保存在了缓
冲区。然后把颜色信息传给片段着色器。
接着给顶点着色器传入顶点集的大小,以及表示顶点坐标数值类型的 gl.FlOAT 。第四个参数是一
个布尔值,表示坐标不是标准的。第五个参数是 步长值 stride value ),表示跳过多个数组元素取得下一 个值。除非真要跳过一些值,否则就向这里传入 0 即可。最后一个参数是起始偏移量,这里的 0 表示从 第一个数组元素开始。
最后一步是使用 gl.drawArrays() 把三角形绘制出来。通过把第一个参数指定为 gl.TRIANGLES
就可以从 (0, 1) (1, –1) 再到 (–1, –1) 绘制一个三角形,并填充传给片段着色器的颜色。第二个参数表示缓 冲区的起始偏移量,最后一个参数是要读取的顶点数量。
通过改变 gl.drawArrays() 的第一个参数,可以修改绘制三角形的方式。图 18-17 展示了修改第
一个参数之后的两种输出。
9. 纹理
WebGL 纹理可以使用 DOM 中的图片。可以使用 gl.createTexture() 方法创建新的纹理,然后
再将图片绑定到这个纹理。如果图片还没有加载,则可以创建一个 Image 对象来动态加载。图片加载
完成后才能初始化纹理,因此在图片的 load 事件之后才能使用纹理。比如:
let image = new Image(),
texture;
image.src = "smile.gif"; 578 第 18 章 动画与 Canvas 图形
image.onload = function() {texture = gl.createTexture();gl.bindTexture(gl.TEXTURE_2D, texture);gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);// 除当前纹理gl.bindTexture(gl.TEXTURE_2D, null);
}

除了使用 DOM 图片,这些步骤跟在 OpenGL 中创建纹理是一样的。最大的区别在于使用
gl.pixelStorei() 设置了像素存储格式。常量 gl.UNPACK_FLIP_Y_WEBGL WebGL 独有的,在基
Web 加载图片时通常要使用。原因在于 GIF JPEG PNG 图片使用的坐标系统与 WebGL 内部的坐 标系统不一样。如果不使用这个标志,图片就会倒过来。
用于纹理的图片必须跟当前页面同源,或者是来自启用了跨源资源共享( CORS Cross-Origin
Resource Sharing )的服务器上。
注意 纹理来源可以是图片、通过 <video> 元素加载的视频,甚至是别的 <canvas> 元素。
视频同样受跨源限制。
10. 读取像素
2D 上下文一样,可以从 WebGL 上下文中读取像素数据。读取像素的 readPixels() 方法与
OpenGL 中的方法有同样的参数,只不过最后一个参数必须是定型数组。像素信息是从帧缓冲区读出来 并放到这个定型数组中的。readPixels() 方法的参数包括 x y 坐标、宽度、高度、图像格式、类型 和定型数组。前 4 个参数用于指定要读取像素的位置。图像格式参数几乎总是 gl.RGBA 。类型参数指的
是要存储在定型数组中的数据类型,有如下限制:
如果这个类型是 gl.UNSIGNED_BYTE ,则定型数组必须是 Uint8Array
如果这个类型是 gl.UNSIGNED_SHORT_5_6_5 gl.UNSIGNED_SHORT_4_4_4_4 gl.UNSIGNED_
SHORT_5_5_5_1 ,则定型数组必须是 Uint16Array
下面是一个调用 readPixels() 方法的例子:
let pixels = new Uint8Array(25*25);
gl.readPixels(0, 0, 25, 25, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

以上代码读取了帧缓冲区中 25 像素 ×25 像素大小的区域,并把读到的像素信息保存在 pixels 数组
中,其中每个像素的颜色在这个数组中都以 4 个值表示,分别代表红、绿、蓝和透明度值。每个数组值 的取值范围是 0~255 (包括 0 255 )。别忘了先按照预期存储的数据量初始化定型数组。
在浏览器绘制更新后的 WebGL 图像之前调用 readPixels() 没有问题。而在绘制完成后,帧缓冲
区会恢复到其初始清除状态,此时调用 readPixels() 会得到与清除状态一致的像素数据。如果想在绘 制之后读取像素,则必须使用前面讨论过的 preserveDrawingBuffer 选项初始化 WebGL 上下文:
let gl = drawing.getContext("webgl", { preserveDrawingBuffer: true; });

设置这个标志可以强制帧缓冲区在下一次绘制之前保持上一次绘制的状态。这个选项可能会影响性

能,因此尽量不要使用。
3 WebGL1 WebGL2
WebGL1 代码几乎完全与 WebGL2 兼容。在使用 WebGL2 上下文时,唯一可能涉及修改代码以保证 兼容性的就是扩展。在 WebGL2 中,很多扩展都变成了默认功能。
例如,要在 WebGL1 中使用绘制缓冲区,需要先测试相应扩展后再使用:
let ext = gl.getExtension('WEBGL_draw_buffers');
if (!ext) {// 没有扩展的代码
} else {ext.drawBuffersWEBGL([...])
}

而在 WebGL2 中,这里的检测代码就不需要了,因为这个扩展已经直接暴露在上下文对象上了:
gl.drawBuffers([...]);
以下特性都已成为 WebGL2 的标准特性:
ANGLE_instanced_arrays
EXT_blend_minmax
EXT_frag_depth
EXT_shader_texture_lod
OES_element_index_uint
OES_standard_derivatives
OES_texture_float
OES_texture_float_linear
OES_vertex_array_object
WEBGL_depth_texture
WEBGL_draw_buffers
Vertex shader texture access
注意 要了解 WebGL 更新的内容,可以参考 WebGL2Fundamentals 网站上的文章“ WebGL2
from WebGL1 ”。

四、小结

HTML5 <canvas> 元素为 JavaScript 提供了动态创建图形的 API 。这些图形需要使用特定上下文
绘制,主要有两种。
第一种是支持基本绘图操作的 2D 上下文:
填充和描绘颜色及图案
绘制矩形
绘制路径
绘制文本
创建渐变和图案 580 18 章 动画与 Canvas 图形
第二种是 3D 上下文,也就是 WebGL WebGL 是浏览器对 OpenGL ES 2.0 的实现。 OpenGL ES 2.0 是游戏图形开发常用的一个标准。WebGL 支持比 2D 上下文更强大的绘图能力,包括:
OpenGL 着色器语言( GLSL )编写顶点和片段着色器;
支持定型数组,限定数组中包含数值的类型;
创建和操作纹理。
目前所有主流浏览器的较新版本都已经支持 <canvas> 标签。

相关文章:

canvas(填充描边,绘制矩形、路径、文本、图像,变换,阴影,渐变等)

一、 基本的画布功能 创建 <canvas> 元素时至少要设置其 width 和 height 属性&#xff0c;这样才能告诉浏览器在多大面积上绘 图。出现在开始和结束标签之间的内容是后备数据&#xff0c;会在浏览器不支持 <canvas> 元素时显示。比如&#xff1a; <canv…...

STM8单片机学习笔记·GPIO的片上外设寄存器

目录 前言 IC基本定义 三极管基础知识 单片机引脚电路作用 STM8GPIO工作模式 GPIO外设寄存器 寄存器含义用法 CR1&#xff1a;Control Register 1 CR2&#xff1a;Control Register 2 ODR&#xff1a;Output Data Register IDR&#xff1a;Input Data Register 赋值…...

2-2-18-16 QNX系统架构之自适应分区

阅读前言 本文以QNX系统官方的文档英文原版资料为参考&#xff0c;翻译和逐句校对后&#xff0c;对QNX操作系统的相关概念进行了深度整理&#xff0c;旨在帮助想要了解QNX的读者及开发者可以快速阅读&#xff0c;而不必查看晦涩难懂的英文原文&#xff0c;这些文章将会作为一个…...

【Python网络爬虫笔记】11- Xpath精准定位元素

目录 一、Xpath 在 Python 网络爬虫中的作用&#xff08;一&#xff09;精准定位元素&#xff08;二&#xff09;应对动态网页&#xff08;三&#xff09;数据结构化提取 二、Xpath 的常用方法&#xff08;一&#xff09;节点选取&#xff08;二&#xff09;谓词筛选&#xff0…...

Rustdesk 安装客户端以及自己搭建服务器跑通参考资料

Rustdesk 安装客户端以及自己搭建服务器跑通参考资料 下载客户端&#xff1a; rustdesk客户端-github下载地址 windows正常安装就行了&#xff0c;ubuntu安装参考下面&#xff1a; ubuntu安装rustdesk客户端 在centos中利用docker安装rustdesk-server&#xff0c;先进行cento…...

源码编译jdk7 超详细教程 openjdk7

关于源代码 当前的openJDK的源代码已经被发布到了github上了&#xff0c;所以我们可以直接从github上下载到。 OpenJDK7u源码托管地址&#xff1a;https://github.com/openjdk/jdk7u 带后缀U的地址&#xff0c;或者发行的jdk包&#xff0c;表示当前版本下的持续跟新版。而他…...

如何实现日期选择窗口

文章目录 1 概念介绍2 使用方法3 示例代码我们在上一章回中介绍了TimePicker Widget相关的内容,本章回中将介绍DatePickerDialog Widget.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在这里说的DatePickerDialog是一种弹出窗口,只不过窗口的内容固定显示为日期,它…...

Spring Security 6 系列之一 - 开篇入门

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级为6.3.0&#xff0c;关键是其风…...

Spring Cloud概述

1. 认识微服务 1.1 单体架构 很多创业公司早期或者传统企业会把业务的所有功能实现都打包在⼀个项⽬, 这就是单体架构. 业务的所有功能实现都打包在⼀个war包或者Jar包中, 这种⽅式就称为单体架构. 举个例子: 电商系统包括: ⽤⼾管理, 商品管理, 订单管理, ⽀付管理, 库存…...

mvc如何给action传递参数

步骤(常规方法) 新建一个控制器--LarsController.cs using Microsoft.AspNetCore.Mvc; namespace Blog.Controller; public class LarsController:Controller -----继承 {public IActionResult Index(){return View();} }获取id // program.cs中默认值是idpublic IAction…...

【银河麒麟高级服务器操作系统】有关dd及cp测试差异的现象分析详解

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn dd现象 使用银河麒麟高级服务器操作系统执行两次…...

视频智能分析平台LiteAIServer未戴安全帽检测算法助力矿山安全:精准监督矿工佩戴安全帽情况

矿山作业环境复杂多变&#xff0c;安全隐患层出不穷。其中&#xff0c;矿工未佩戴安全帽这一行为&#xff0c;看似微不足道&#xff0c;实则潜藏着巨大的安全风险。一旦发生事故&#xff0c;未佩戴安全帽的矿工将极易受到重创&#xff0c;甚至危及生命。因此&#xff0c;确保每…...

整数的四舍五入----->记录每一点进步

我们知道整数类型的变量对小数部分都不感冒&#xff0c;只要是小数都会舍去&#xff0c;都让他舍去了我们还讲个P&#xff0c;所以接下来就是告诉你怎么不让他全都舍去。 先来试想一下如果这个数字是3.4&#xff0c;存到整数型变量里面是不是直接变成了3&#xff0c;那么我要是…...

Linux栈帧

相关寄存器&指令 寄存器 rax&#xff08;accumulator&#xff09;&#xff1a;return value rbx&#xff08;base&#xff09; rcx&#xff08;count&#xff09;&#xff1a;4st argument rdx&#xff08;data&#xff09;&#xff1a;3st argument rsi&#xff08;sour…...

【代码随想录day59】【C++复健】 47. 参加科学大会(dijkstra(堆优化版)精讲 );94. 城市间货物运输 I

前几天有些事情耽搁了&#xff0c;不过好在图论我完全不会&#xff0c;所以偷看代码思路也没有任何的心理负担&#xff0c;做起来反而相对独立思考更快一点点。 47. 参加科学大会&#xff08;dijkstra&#xff08;堆优化版&#xff09;精讲 &#xff09; 本来一开始以为这个堆…...

【网络安全】WIFI WPA/WPA2协议:深入解析与实践

WIFI WPA/WPA2协议&#xff1a;深入解析与实践 1. WPA/WPA2 协议 1.1 监听 Wi-Fi 流量 解析 WPA/WPA2 的第一步是监听 Wi-Fi 流量&#xff0c;捕获设备与接入点之间的 4 次握手数据。然而&#xff0c;设备通常不会频繁连接或重新连接&#xff0c;为了加速过程&#xff0c;攻…...

穷举vs暴搜vs深搜vs回溯vs剪枝专题一>子集

题目&#xff1a; 两个方法本质就是决策树的画法不同 方法一解析&#xff1a; 代码&#xff1a; class Solution {private List<List<Integer>> ret;//返回结果private List<Integer> path;//记录路径&#xff0c;注意返回现场public List<List<Int…...

试题转excel;word转excel;大风车excel

一、问题描述 一名教师朋友&#xff0c;偶尔会需要整理一些高质量的题目到excel中 以往都是手动复制搬运&#xff0c;几百道题几乎需要一个下午的时间 关键这些事&#xff0c;枯燥无聊费眼睛&#xff0c;实在是看起来就很蠢的工作 就想着做一个工具&#xff0c;可以自动处理…...

Unity NTPComponent应用, 实现一个无后端高效获取网络时间的组件

无后端高效获取网络时间的组件 废话不多说&#xff0c;直接上源码m_NowSerivceTime 一个基于你发行游戏地区的时间偏移&#xff0c; 比如北京时区就是 8, 巴西就是-3&#xff0c;美国就是-5using Newtonsoft.Json; 如果这里报错&#xff0c; 就说明项目没有 NewtonsoftJson插件…...

复合机器人为生产提供精准的建议和决策支持

在现代化生产的浪潮中&#xff0c;智能复合机器人以其卓越的性能和高度智能化特点&#xff0c;正成为保障生产安全与可靠性的重要力量。 智能复合机器人具备精确的感知、判断和决策能力&#xff0c;能够在复杂的生产环境中自主导航、精确操作&#xff0c;避免了人为因素可能导致…...

springboot/ssm二手儿童绘本交易系统Java代码编写web项目闲置书籍源码

springboot/ssm二手儿童绘本交易系统Java代码编写web项目闲置书籍源码 基于springboot(可改ssm)vue项目 开发语言&#xff1a;Java package com.controller;import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Map;import javax.s…...

30. Three.js案例-绘制并渲染圆弧

30. Three.js案例-绘制并渲染圆弧 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染 3D 场景的核心类。它利用 WebGL 技术在浏览器中渲染 3D 图形。 构造器 new THREE.WebGLRenderer(parameters) 参数类型描述parametersObject可选参数对象&#xff…...

类和对象(2)

大家好&#xff0c;今天我们继续来学习类和对象的知识&#xff0c;那么今天我们来看看类的概念和类的定义相关的知识&#xff0c;那么话不多说&#xff0c;我们直接开始。 2.类定义和使用 面向对象程序设计关注的是对象,而对象是现实生活中的实体,比如:洗衣机&#xff0c;但是…...

240004基于ssm+maven+mysql+Java的房屋租赁系统的设计与实现

基于ssmmavenmysql的房屋租赁系统的设计与实现 1.项目描述2.运行环境3.项目截图4.源码获取 1.项目描述 该项目在原有的基础上进行了优化&#xff0c;包括新增了注册功能&#xff0c;房屋模糊查询功能&#xff0c;管理员和用户信息管理等功能&#xff0c;以及对网站界面进行了优…...

HTTP、WebSocket、gRPC 或 WebRTC:各种协议的区别

在为您的应用程序选择通信协议时&#xff0c;有很多不同的选择。 本文将了解四种流行的解决方案&#xff1a;HTTP、WebSocket、gRPC 和 WebRTC。 我们将通过深入学习其背后原理、最佳用途及其优缺点来探索每个协议。 通信方式在不断改进&#xff1a;变得更快、更方便、更可靠&…...

FlowNex 中的两相建模基础知识

通过 FlowNex 中的两相建模解开高效流体动力学的秘密&#xff0c;彻底改变制造业。 挑战 两相流是指两个不同相&#xff08;通常是液体和气体&#xff09;同时流动&#xff0c;它们具有不同的特性和行为。在制造业中&#xff0c;了解两相流对于优化热交换器、化学反应器和流体…...

Mysql笔记

windows安装记录Windows中Mysql安装-CSDN博客 用到的库 通过网盘分享的文件&#xff1a;atguigudb.sql 链接: https://pan.baidu.com/s/1YfC20c2vK9odn-XRJJwUJw 提取码: utk7 --来自百度网盘超级会员v5的分享 Mysql4中表关联关系 1.1对1,比较少用,因为完全可以一张表,当有…...

docker拉取rabbitmq镜像安装延迟队列插件

我这里使用的是rabbitmq:3.12.0-management版本作为示例 1.拉取rabbitmq镜像 docker pull rabbitmq:3.12.0-management 2.启动rabbitmq docker run -d --namerabbitmq --restartalways -p 5672:5672 -p 15672:15672 rabbitmq:3.12.0-management 在咱们拉取时如果出现连接超时可…...

创建一个谷歌插件项目dome上线流程+源码

创建一个简单的 Chrome 扩展程序&#xff0c;其主要功能是 JSON 格式化。用户可以通过点击扩展图标打开一个弹出窗口&#xff0c;在弹出窗口中输入或粘贴 JSON 数据&#xff0c;然后点击格式化按钮来格式化 JSON 数据 谷歌插件&#xff08;即 Chrome 扩展程序&#xff09;主要设…...

举例说明如何在linux下检测摄像头设备具备的功能

假设摄像头设备文件为/dev/video1 &#xff0c;下面是一个专门用于检测 /dev/video1 设备能力的简化程序。这个程序将打印出设备的所有能力、格式和其他相关信息&#xff0c;以帮助你了解设备支持的功能。 检测 /dev/video1 设备能力的程序 #include <fcntl.h> #includ…...

win10配置子系统Ubuntu子系统(无需通过Windows应用市场)实际操作记录

win10配置子系统Ubuntu子系统&#xff08;无需通过Windows应用市场&#xff09;实际操作记录 参考教程 : win10配置子系统Ubuntu子系统&#xff08;无需通过Windows应用市场&#xff09; - 一佳一 - 博客园 开启虚拟机服务的 以管理员方式运行PowerShell运行命令。 &#xf…...

东北大学《2024年839自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《东北大学839自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题...

5G中的ATG Band

Air to Ground Networks for NR是R18 NR引入的。ATG很多部分和NTN类似中的内容类似。比较明显不同的是&#xff0c;NTN的RF内容有TS 38.101-5单独去讲&#xff0c;而ATG则会和地面网络共用某些band&#xff0c;这部分在38.101-1中有描述。 所以会存在ATG与地面网络之间的相邻信…...

nginx负载均衡配置

目录 一、简介 二、nginx下载 二、nginx配置 四、注意点 (1)/api与/api/的区别 (2)http://gatewayserver与http://gatewayserver/的区别 一、简介 Nginx&#xff08;发音为 "engine-x"&#xff09;是一个高性能的HTTP和反向代理服务器&#xff0c;也是一个IMA…...

【教学类-83-02】20241214立体书三角嘴2.0——青蛙(扁菱形嘴)

背景需求&#xff1a; 制作小鸡立体贺卡三角嘴&#xff0c;它的嘴是正菱形&#xff08;四条边长度相等&#xff0c;类似正方形&#xff09; 【教学类-83-01】20241215立体书三角嘴1.0——小鸡&#xff08;正菱形嘴&#xff09;-CSDN博客文章浏览阅读744次&#xff0c;点赞22次…...

vscode设置终端代理

转载请标明出处&#xff1a;小帆的帆的博客 设置终端代理 修改项目的.vscode/settings.json {"terminal.integrated.env.windows": {"http_proxy": "http://127.0.0.1:7890","https_proxy": "http://127.0.0.1:7890"}, }…...

【C++】函数计算题解论

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述&#x1f4af;思路解析3.1 函数的递归定义3.2 边界条件控制3.3 记忆化搜索 &#x1f4af;C实现代码&#x1f4af;添加解释&#x1f4af;小结 &#x1f4af;前言 在…...

Redis篇-21--运维篇3-集群(分片,水平扩展,高可用,集群配置案例,扩展哨兵案例)

1、概述 Redis集群&#xff08;Cluster&#xff09;通过分片&#xff08;sharding&#xff09;实现了水平扩展&#xff0c;允许数据分布在多个节点上&#xff0c;从而提升性能和存储容量。 在Redis集群中&#xff0c;数据被分割成16384个哈希槽&#xff08;hash slots&#x…...

Unity3d场景童话梦幻卡通Q版城镇建筑植物山石3D模型游戏美术素材

注明&#xff1a;网络素材&#xff0c;仅供学习使用&#xff01; https://download.csdn.net/download/elineSea/90017291...

深入理解addEventListener中的第二个参数:listener

起因 首先&#xff0c;之前留给我们的一点东西&#xff0c;js的深入内容关键在addEventListener&#xff0c;这个函数中的参数&#xff0c;它们三个参数&#xff0c;分别为type、listener、options&#xff0c;当然在这里还有一些小的问题&#xff0c;比如mdn文档中它介绍到了另…...

数据库镜像(Database Mirroring):高可用性与灾难恢复技术

一、引言 在现代信息系统中&#xff0c;数据的可用性和完整性至关重要&#xff0c;尤其是对金融、电商、医疗等高并发和高可靠性要求的行业。数据库镜像&#xff08;Database Mirroring&#xff09; 作为一种高可用性与灾难恢复技术&#xff0c;通过将主数据库的数据实时复制到…...

【Qt】按钮类控件:QPushButton、QRadioButton、QCheckBox、ToolButton

目录 QPushButton 例子&#xff1a; QRadioButton 例子&#xff1a; 按钮的常见信号函数 单选按钮分组 例子&#xff1a; QCheckButton 例子&#xff1a; QToolButton QWidget的常见属性及其功能对于它的派生类控件都是有效的(也就是Qt中的各种控件)&#xff0c;包括…...

day-21 内核链表以及栈

1.昨日作业 1.删除指定节点 找到删除就完事了&#xff0c;双向可以停在删除处。 /***************************** 功能&#xff1a;删除指定结点&#xff08;通过姓名&#xff09;* 参数&#xff1a;phead&#xff1b;oldname; * 返回&#xff1a;成功0&#xff0c;失-1&…...

深度与视差的关系及其转换

深度与视差的关系及其转换 在计算机视觉和立体视觉中&#xff0c;深度和视差是两个重要的概念。理解这两者之间的关系对于实现立体图像处理、三维重建以及深度估计至关重要。在这篇博客中&#xff0c;我们将深入探讨深度和视差的概念&#xff0c;并介绍它们之间的转换关系。 …...

Unity全局光照详解

之前就学过但是太久没用又忘了&#xff0c;因此用最简洁易懂的语言做个记录。 全局光照分为两个系统&#xff0c;分别是实时光照和混合光照。&#xff08;点击window/Rendering/Lighing打开此面板&#xff09; 其中全局光照对于我来说都是新技术了&#xff0c;上一次学…...

外观模式的理解和实践

外观模式&#xff08;Facade Pattern&#xff09;是一种常用的软件设计模式&#xff0c;它提供了一个统一的接口&#xff0c;用来访问子系统中的一群接口。该模式定义了一个高层的接口&#xff0c;使得子系统更容易使用。简单来说&#xff0c;外观模式就是通过引入一个外观角色…...

【前端知识】Javascript进阶-类和继承

文章目录 概述一、类&#xff08;Class&#xff09;二、继承&#xff08;Inheritance&#xff09; 三、继承的实现方式作用一、类和作用二、继承和作用 概述 当然可以&#xff0c;以下是对JavaScript中类和继承的详细介绍&#xff1a; 一、类&#xff08;Class&#xff09; 定…...

Kylin麒麟操作系统 | Nginx服务部署

目录 一、理论储备1. Nginx概述2. Nginx与Apache的区别3. Nginx的服务配置 二、任务实施任务1 Nginx的编译安装1. Server配置2. 客户端测试 任务2 Nginx反向代理1. Server1配置2. Server2配置3. 客户端测试 一、理论储备 1. Nginx概述 Nginx是一个轻量级的web服务器&#xff…...

51单片机--- 串口控制仿真

51单片机--- 串口控制仿真 实验目标:51单片机接收串口数据,根据数据点亮LED。 实验步骤: 在Proteus里画出原理图 在Keil里用C语言编写程序 在Proteus中导入HEX文件,启动仿真 实验协议: 波特率115200 数据位:8位,停止位:1位,校验位:无。 命令格式: 一条命令为…...

<数据集>输电线塔杂物识别数据集<目标检测>

数据集下载链接 &#xff1c;数据集&#xff1e;输电线塔杂物识别数据集&#xff1c;目标检测&#xff1e;https://download.csdn.net/download/qq_53332949/90141102数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1099张 标注数量(xml文件个数)&#xff1a;1099 …...