【Canvas学习】02-了解canvas(二)
发表于:2023-11-01 |

前言

时隔几个月我又来更新canvas的知识了,双十一我买了新的笔记本,等笔记本到了,和大家一起继续学习3d的知识,我现在的笔记本配置有点跑不起来3d项目了,先不说这个,今天和大家一起分享下canvas的知识。几个月前也有一篇canvas的知识,大家可以通过归类的标签去统一查看。

绘制文本

canvas 提供了两种方法来渲染文本:
fillText(text, x, y [, maxWidth])
在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的。
strokeText(text, x, y [, maxWidth])
在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的。

绘制填充文本

1
2
3
4
5
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = "48px serif";
ctx.fillText("Hello world", 10, 50);
}

效果图

绘制文本轮廓

1
2
3
4
5
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = "48px serif";
ctx.strokeText("Hello world", 10, 50);
}

效果图

文本样式

在上面的例子用我们已经使用了 font 来使文本比默认尺寸大一些。还有更多的属性可以让你改变 canvas 显示文本的方式:
font = value
当前我们用来绘制文本的样式。这个字符串使用和 CSS font 属性相同的语法。默认的字体是 10px sans-serif。
textAlign = value
文本对齐选项。可选的值包括:start, end, left, right or center. 默认值是 start。
textBaseline = value
基线对齐选项。可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic。
direction = value
文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit。

预测量文本宽度

当你需要获得更多的文本细节时,下面的方法可以给你测量文本的方法。
measureText()
将返回一个 TextMetrics对象的宽度、所在像素,这些体现文本特性的属性。
下面的代码段将展示如何测量文本来获得它的宽度:

1
2
3
4
5
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var text = ctx.measureText("foo"); // TextMetrics object
text.width; // 16;
}

变形

canvas现在被大量地运用于游戏等动画领域,最主要的归功于它提供的一系列几何变换方法,使得动画更加地容易。所以其几何变换是非常重要的一节。
在前面的部分中,我们已经了解了 Canvas 网格和坐标空间。到目前为止,我们只是根据我们的需要使用默认的网格,改变整个画布的大小。变形是一种更强大的方法,可以将原点移动到另一点、对网格进行旋转和缩放。

状态的保存和恢复 Saving and restoring state

在了解变形之前,我先介绍两个在你开始绘制复杂图形时必不可少的方法。

  • save()
    保存画布 (canvas) 的所有状态
  • restore()
    save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。
    Canvas 状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。一个绘画状态包括:
    ● 当前应用的变形(即移动,旋转和缩放,见下)
    ● 以及下面这些属性:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled
    ● 当前的裁切路径(clipping path)
    你可以调用任意多次 save方法。每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。

save和restore的应用例子

我们尝试用这个连续矩形的例子来描述 canvas 的状态栈是如何工作的。
第一步是用默认设置画一个大四方形,然后保存一下状态。改变填充颜色画第二个小一点的蓝色四方形,然后再保存一下状态。再次改变填充颜色绘制更小一点的半透明的白色四方形。
到目前为止所做的动作和前面章节的都很类似。不过一旦我们调用 restore,状态栈中最后的状态会弹出,并恢复所有设置。如果不是之前用 save 保存了状态,那么我们就需要手动改变设置来回到前一个状态,这个对于两三个属性的时候还是适用的,一旦多了,我们的代码将会猛涨。
当第二次调用 restore 时,已经恢复到最初的状态,因此最后是再一次绘制出一个黑色的四方形。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');

ctx.fillRect(0,0,150,150); // 使用默认设置绘制一个矩形
ctx.save(); // 保存默认状态

ctx.fillStyle = '#09F' // 在原有配置基础上对颜色做改变
ctx.fillRect(15,15,120,120); // 使用新的设置绘制一个矩形

ctx.save(); // 保存当前状态
ctx.fillStyle = '#FFF' // 再次改变颜色配置
ctx.globalAlpha = 0.5;
ctx.fillRect(30,30,90,90); // 使用新的配置绘制一个矩形

ctx.restore(); // 重新加载之前的颜色状态
ctx.fillRect(45,45,60,60); // 使用上一次的配置绘制一个矩形

ctx.restore(); // 加载默认颜色配置
ctx.fillRect(60,60,30,30); // 使用加载的配置绘制一个矩形
}

效果图

移动 Translating

我们先介绍 translate方法,它用来移动 canvas 和它的原点到一个不同的位置。
translate(x, y)
translate方法接受两个参数。*x *是左右偏移量,y 是上下偏移量,如右图所示。
在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用 restore 方法比手动恢复原先的状态要简单得多。又,如果你是在一个循环中做位移但没有保存和恢复 canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。

translate的例子

这个例子显示了一些移动 canvas 原点的好处。如果不使用 translate方法,那么所有矩形都将被绘制在相同的位置(0,0)。translate方法同时让我们可以任意放置这些图案,而不需要在 fillRect() 方法中手工调整坐标值,既好理解也方便使用。
我在 draw方法中调用 fillRect() 方法 9 次,用了 2 层循环。每一次循环,先移动 canvas,画螺旋图案,然后恢复到原始状态。

1
2
3
4
5
6
7
8
9
10
11
12
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
ctx.save();
ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';
ctx.translate(10 + j * 50, 10 + i * 50);
ctx.fillRect(0, 0, 25, 25);
ctx.restore();
}
}
}

效果图

旋转 Rotating

第二个介绍 rotate方法,它用于以原点为中心旋转 canvas。
rotate(angle)
这个方法只接受一个参数:旋转的角度 (angle),它是顺时针方向的,以弧度为单位的值。
旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到 translate方法。

rotate的例子

在这个例子里,见图,我用 rotate方法来画圆并构成圆形图案。当然你也可以分别计算出 x 和 y 坐标(x = r*Math.cos(a); y = r*Math.sin(a))。这里无论用什么方法都无所谓的,因为我们画的是圆。计算坐标的结果只是旋转圆心位置,而不是圆本身。即使用 rotate旋转两者,那些圆看上去还是一样的,不管它们绕中心旋转有多远。
这里我们又用到了两层循环。第一层循环决定环的数量,第二层循环决定每环有多少个点。每环开始之前,我都保存一下 canvas 的状态,这样恢复起来方便。每次画圆点,我都以一定夹角来旋转 canvas,而这个夹角则是由环上的圆点数目的决定的。最里层的环有 6 个圆点,这样,每次旋转的夹角就是 360/6 = 60 度。往外每一环的圆点数目是里面一环的 2 倍,那么每次旋转的夹角随之减半。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.translate(75,75);

for (var i=1;i<6;i++){ // Loop through rings (from inside to out)
ctx.save();
ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';

for (var j=0;j<i*6;j++){ // draw individual dots
ctx.rotate(Math.PI*2/(i*6));
ctx.beginPath();
ctx.arc(0,i*12.5,5,0,Math.PI*2,true);
ctx.fill();
}
ctx.restore();
}
}

效果图

缩放 Scaling

接着是缩放。我们用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。
scale(x, y)
scale方法可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比 1 小,会缩小图形,如果比 1 大会放大图形。默认值为 1,为实际大小。
画布初始情况下,是以左上角坐标为原点的第一象限。如果参数为负实数,相当于以 x 或 y 轴作为对称轴镜像反转(例如,使用translate(0,canvas.height); scale(1,-1); 以 y 轴作为对称轴镜像反转,就可得到著名的笛卡尔坐标系,左下角为原点)。
默认情况下,canvas 的 1 个单位为 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。

scale的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');

// draw a simple rectangle, but scale it.
ctx.save();
ctx.scale(10, 3);
ctx.fillRect(1, 10, 10, 10);
ctx.restore();

// mirror horizontally
ctx.scale(-1, 1);
ctx.font = '48px serif';
ctx.fillText('MDN', -135, 120);
}

效果图

变形 Transforms

最后一个方法允许对变形矩阵直接修改。
transform(a, b, c, d, e, f)
这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵,如下面的矩阵所示:

`
a c e
[b d f]
0 0 1

`
如果任意一个参数是Infinity,变形矩阵也必须被标记为无限大,否则会抛出异常。
这个函数的参数各自代表如下:
a (m11)
水平方向的缩放
b(m12)
竖直方向的倾斜偏移
c(m21)
水平方向的倾斜偏移
d(m22)
竖直方向的缩放
e(dx)
水平方向的移动
f(dy)
竖直方向的移动
setTransform(a, b, c, d, e, f)
这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。
resetTransform()
重置当前变形为单位矩阵,它和调用以下语句是一样的:ctx.setTransform(1, 0, 0, 1, 0, 0);

transform/setTransform的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');

var sin = Math.sin(Math.PI/6);
var cos = Math.cos(Math.PI/6);
ctx.translate(100, 100);
var c = 0;
for (var i=0; i <= 12; i++) {
c = Math.floor(255 / 12 * i);
ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
ctx.fillRect(0, 0, 100, 10);
ctx.transform(cos, sin, -sin, cos, 0, 0);
}

ctx.setTransform(-1, 0, 0, 1, 100, 100);
ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
ctx.fillRect(0, 50, 100, 100);
}

效果图

合成

我们总是将一个图形画在另一个之上,对于其他更多的情况,仅仅这样是远远不够的。比如,对合成的图形来说,绘制顺序会有限制。不过,我们可以利用 globalCompositeOperation 属性来改变这种状况。此外,clip属性允许我们隐藏不想看到的部分图形。

globalCompositeOperation

我们不仅可以在已有图形后面再画新图形,还可以用来遮盖指定区域,清除画布中的某些部分(清除区域不仅限于矩形,像clearRect()方法做的那样)以及更多其他操作。
source-over
这是默认设置,并在现有画布上绘制新图形。

source-in
仅在新形状和目标画布重叠的地方绘制新形状。其他的都是透明的。

source-out
在不与现有画布内容重叠的地方绘制新图形。

source-atop
只在与现有画布内容重叠的地方绘制新图形。

destination-over
在现有画布内容的后面绘制新的图形。

destination-in
仅保留现有画布内容和新形状重叠的部分。其他的都是透明的。

destination-out
仅保留现有画布内容和新形状不重叠的部分。

destination-atop
仅保留现有画布内容和新形状重叠的部分。新形状是在现有画布内容的后面绘制的。

lighter
两个重叠图形的颜色是通过颜色值相加来确定的。

copy
只显示新图形。

xor
形状在重叠处变为透明,并在其他地方正常绘制。

multiply
将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片。

screen
像素被倒转、相乘、再倒转,结果是一幅更明亮的图片(与 multiply 相反)。

overlay
multiply 和 screen 的结合。原本暗的地方更暗,原本亮的地方更亮。

darken
保留两个图层中最暗的像素。

lighten
保留两个图层中最亮的像素。

color-dodge
将底层除以顶层的反置。

color-burn
将反置的底层除以顶层,然后将结果反过来。

hard-light
类似于 overlay,multiply 和 screen 的结合——但上下图层互换了。

soft-light
柔和版本的 hard-light。纯黑或纯白不会导致纯黑或纯白。

difference
从顶层减去底层(或反之亦然),始终得到正值。

exclusion
与 difference 类似,但对比度较低。

hue
保留底层的亮度(luma)和色度(chroma),同时采用顶层的色调(hue)。

saturation
保留底层的亮度和色调,同时采用顶层的色度。

color
保留了底层的亮度,同时采用了顶层的色调和色度。

luminosity
保持底层的色调和色度,同时采用顶层的亮度。

globalCompositeOperation = type
这个属性设定了在画新图形时采用的遮盖策略,其值是一个标识遮盖方式的字符串。
查看下面Compositing 示例的代码。
效果图
效果图
效果图
效果图
效果图
效果图
效果图
效果图
效果图

结语

本篇文章就到了这里,每天学习一点点,努力做大做强~

上一篇:
浏览器关闭请求
下一篇:
vue双向prop