持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情
canvas 高级功能(下)
在本文中,你将学习到 Canvas 提供的一些更高级的功能。本文将介绍渐变、使用高级路径创建特殊图形,以及如何将画布导出为图像,以便保存或留待将来使用。 本章内容非常精彩,我希望这些内容能够拓宽你的眼界,帮助你学会画布的高级功能。
1. 渐变
有时候,简单的颜色并不够用,或者你确实需要给图形颜色增加额外的真实元素。无论是何种情况,画布的渐变颜色肯定都是一种可以考虑的方法,fillStyle和strokeStyle都可以接受以CanvasGradient对象表示的渐变颜色值。
画布支持两种类型的渐变:线性渐变和放射渐变。每一种渐变在2D渲染上下文中都有对应的创建方法,线性渐变对应的是createLinearGradient,而放射渐变对应的是createRadialGradient。这两种方法都返回一个CanvasGradient对象,可以使用CanvasGradient对象本身的addColorStop方法对它进行进一步处理。下面让我们创建一个基本的线性渐变,以了解它们的使用方法。
这里,我们首先使用createLinearGradient创建一个新的CanvasGradient对象,然后将它赋值给-个变量,这样在将来才能够再次访问这个对象。createLinearGradient方法有4个参数:渐变起点的(x, y)坐标,渐变终点的 (x, y)坐标。起点和终点描述了所绘制渐变效果的长度、位置和方向。在这个例子中,渐变是从画布的左上角开始的,然后延伸到左下角。线性渐变的绘制方向与起点和终点所确定的直线是正交的,所以在这个例子中,渐变是从顶部延伸到底部的。
定义一个CanvasGradient对象还不够,我们还需要给它指定一种颜色。因此,我们两次调用CanvasGradient对象(此对象已经保存到一个变量中了)的addColorStop方法。addColorstop方法有两个参数:颜色的偏移值( 0 表示渐变起点,1 表示终点),以及该偏移的颜色值。与fillStyle相同,这个颜色值可以是任何 CSS 颜色值。在这个例子中,渐变是从起点的黑色(偏移值为0)变化到终点的白色(偏移值为1)。
最后,这个渐变会作为一个颜色值被应用到fillStyle属性中,从而在整个画布上显示从黑色到白色的渐变效果。
创建放射渐变的过程则有些不同。事实上,如果不能真正了解放射渐变的工作方式,那么你就会觉得放射渐变其实是很混乱的,特别是它的效果在不同浏览器上是不相同的(目前的情况)。幸好,现在我们只是分析它的创建过程,从而使你掌握它的使用方法,所以让我们马上开始吧。
放射渐变是使用createRadialGradient方法创建的。这个方法需要6个参数——前3个参数描述一个圆(开始圆),后3个参数描述另一个圆(结束圆)。这两个圆本身不仅描述了方向及渐变的起止位置,而且还描述了渐变的形状。用于描述每一个圆的3个参数是圆心坐标(x, y)和半径。这些参数可以用字符串描述为:createRadialGradient(x0, y0, r0, x1, y1, r1);
实际的渐变效果是连接两个圆周的锥体,其中开始圆之前的锥体部分显示偏移值为 0 的颜色,而结束圆之后的锥体部分则显示偏移值为 1 的颜色。理解这个概念有一些难度,所以我用一个图片来说明,希望你能够理解它的原理。
使用画布代码编写的结果如下:
const gradient = context.createRadialGradient(300, 300, 10, 100, 100, 50);
gradient.addColorStop(0, "rgb(0, 0, 0)");
gradient.addColorStop(1, "rgb(150, 150, 150)");
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
开始圆位于坐标位置(300, 300),半径为10,结束圆的坐标位置为(100, 100),半径为50。最终得到锥体,开始圆为黑色(偏移值0),慢慢褪色为结束圆的灰色(偏移值1)。
如果你想要从中间往外放射,这种放射渐变其实非常容易实现,只需要将开始圆和结束圆放置在同一个位置即可。仅此而已!
const canvasCentreX = canvas.width / 2;
const canvasCentreY = canvas.height / 2;
const gradient = context.createRadialGradient(canvasCentreX, canvasCentreY, 0, canvasCentreX, canvasCentreY, 250);
gradient.addColorStop(0, "rgb(0, 0, 0)");
gradient.addColorStop(1, "rgb(150, 150, 150)");
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
通过将两个圆叠放在一起,我们就可以将渐变锥体环绕360度,只要两个圆的大小不同,那么渐变效果就会从较小的圆延伸到较大的圆上。
注意:虽然渐变的效果很漂亮,但是使用cavans来创建渐变并非总是最好的方法,特别是在将它们作为背景使用时,我们应该考虑使用其他的方法,如专门用来完成这个任务的 CSS3 渐变背景。
2. 复杂路径
画布中的路径不仅限于线条和圆,实际上还可以用它们来创建各种奇妙的图形。单独一条直线路径可以绘制成一条漂亮的线条。但是如何一次绘制多个线条,以及将它们组合成一个图形?事实上,我们已经知道实现这个效果的代码了,所以下面将直接介绍如何将多个路径连接在一起。
context.beginPath();
context.moveTo(100, 50);
context.lineTo(150, 150);
context.lineTo(50, 150);
context.closePath();
context.stroke()
context.fill();
你应该能够读懂所有这些代码——它先开始一条路径,将原点移到当前路径,从当前路径原点绘制一条线到一个指定点,再绘制一条线到另一个点,然后再继续。那么我们在这里做了什么呢?我们刚刚做的就是将多个路径连接在一起,我们只需要不停地画线。这确实非常简单,每次调用moveTo或lineTo都会给所谓子路径增加一个相应的(x, y)坐标值。事实上,moveTo会创建一条全新的子路径,而lineTo只是沿着一条已有的子路径继续画线。这条子路径会记录我们所添加的最后一个坐标值,因而可以连续多次调用lineTo方法。lineTo的每次调用都是从子路径的最后一个坐标值(由moveTo或lineTo调用留下)开始画线,绘制一条线连接lineTo参数所定义的坐标值,然后再将子路径更新到新的坐标值。
绘制三角形的最后一步是调用closePath方法,它会画一条线连接子路径的最后一个点和第一个点一一封闭路径。它也会将起点和终点添加到子路径上,这两个点现在具有相同的坐标值。
贝塞尔曲线
要在画布中绘制一条曲线,我们可以使用arc方法或arcTo方法(在两点间绘制一条弧线),但是这些弧线只是一条具有相同半径的曲线。要创建一条更复杂的曲线,需要使用贝塞尔曲线方法:quadraticCurveTo或bezierCurveTo。
注意:不要被贝塞尔曲线的名称误导,它们都是贝塞尔曲线,即使其中一个方法的名称不包含
Bezier字样,实际上,quadraticCurveTo是一种二次贝塞尔曲线,而bezierCurveTo,是三次贝塞尔曲线。
这两种贝塞尔曲线都是通过控制点将一条直线变成曲线。二次贝塞尔曲线只有一个控制点,这意味着线条中只有一次弯曲,而三次贝塞尔曲线则有两个控制点,这意味着一条线中会有两次弯曲。通过下图,我们可以直观地了解这两种曲线的效果,左边是二次贝塞尔曲线,右边是三次贝塞尔曲线。
要在画布中创建这两种曲线,只需要直接调用quadraticCurveTo或bezierCurveTo。让我们先尝试使用quadraticCurveTo:
context.lineWidth = 5;
context.beginPath();
context.moveTo(50, 250);
context.quadraticCurveTo(250, 100, 450, 250);
context.stroke();
除了quadraticCurveTo方法,这段代码的其他语句你都理解。这个方法有4个参数:控制点的(x, y)坐标值,以及路径目标点的(x, y)坐标。控制点在水平(x)方向上位于线条的中心,在垂直(y)方向上稍微偏上,下图所示的就是这条漂亮的曲线。
然后,创建三次贝塞尔曲线也很简单:
context.lineWidth = 5;
context.beginPath();
context.moveTo(50, 250);
context.bezierCurveTo(150, 50, 350, 450, 450, 250);
context.stroke();
bezierCurveTo函数有6个参数:第一个控制点的(x, y)坐标值,第二个控制点的(x, y)坐标值,以及路径目标点的(x, y)坐标。
3. 将画布导出为图像
到目前为止,我们在画布中绘制的图形都仅限于在画布中使用,而无法在其他位置使用。这实际上是不理想的,特别是如果你希望导出漂亮的画布绘图作品,并将他保存在其他位置。先别着急!其实画布还有一个很有用的toDataURL方法。这个简单的方法能够将画布绘图转换为一个数据URL,我们可以通过它在浏览器上显示一个图像。这是非常棒的方法!
这个方法实际上是很简单的,所以让我们马上尝试使用这个方法来导出一个基本图形。我将在后面详细讲述它的工作原理。
context.save();
context.fillRect(50, 50, 100, 100);
context.fillStyle = "rgb(255, 0, 0)";
context.fillRect(100, 100, 100, 100);
context.restore();
context.fillRect(150, 150, 100, 100);
const dataURL = canvas.toDataURL();
这段代码将绘制一系列相互叠加的正方形,然后将图像数据URL赋值给dataURL变量。你会看到这三个正方形在浏览器的显示效果:
但是现在它仍然是前面介绍的画布图像,而不是导出的图像。下面马上介绍如何显示导出的图像。
这个例子中最关键的部分是dataURL变量,下面是你刚刚存储到此变量中的值的一个片段:
const gradient = context.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, 'rgb(0, 0, 0)');
gradient.addColorStop(1, 'rgb(255, 255, 255)');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAYAAADL1t+KAAAAAXNSR0IArs4c6QAAHF1JREFUeF7t2sFyHMkNBFDq/z96fdFFNq3tIDOSAPr56lEB/YCajB7urw//I0CAAAECBNYL/Fr/BB6AAAECBAgQ+BDoloAAAQIECBwQEOgHhugRCBAgQICAQLcDBAgQIEDggIBAPzBEj0CAAAECBAS6HSBAgAABAgcEBPqBIXoEAgQIECAg0O0AAQIECBA...
实际的输出比这些要长得多,但是实际上只有前面4个单词是我们现在关心的。前面3个单词是data:image/png,它们表示后续内容是一个PNG格式的图像的数据URL。第4个单词是base64,它表示数据采用base64编码格式。这种格式经常用于向使用文本数据的系统传输二进制数据(例如图像)。实际上,在base64后面的所有数字、字母和符号是以文本表示的画布图像。
注意:Canvas规范支持使用
toDataURL方法导出其他类型的图像。然而,PNG支持是默认的要求,而各个浏览器制造商可以自行决定是否支持其他格式的图像。
如果复制dataURL变量中的字符串,然后粘贴到现代浏览器的地址栏(只要不超过输人URL长度限制),那么你就会看到在画布中绘制的图像。然后,在需要时,可以右键单击图像,将它保存到桌面。或者,你可以在例子中用生成的图像替换canvas元素。
实际上,你可以自由决定如何使用这些图像数据,但是首先需要知道如何将画布导出为图像。你甚至可以使用画布随意绘制一个图像,然后导出图像,使用它作为 CSS 背景。