第五章 渲染效果
在前几章的内容中,我们讲述了各种几何图形的绘制,并学习了使用颜色填充几何图形或者给几何图形描边,本章将会讲解在绘制几何图形的时候使用各种渲染效果。本章的内容包括:
1. 边框样式
在图形系统开发实战-基础篇:绘制基本图形中讲述了在绘制矩形、直线、多边形、圆等内容时我们学习到了以下几类边框样式:
- 通过
strokeStyle
属性可以指定边框的颜色
- 通过
lineWidth
属性可以指定描边的粗细
- 通过
setLineDash()
方法可以指定虚线风格
本节我们还将学习:
- 线条末端样式
- 线条连接风格
- 斜接面限制比例
- 多边形
线条末端样式
通过画布的渲染上下文对象lineCap属性可指定线路末端样式,其API如下所示:
属性值 |
说明 |
butt |
线段末端以方形结束 |
round |
线段末端以圆形结束 |
square |
线段末端以方形结束 |
1 2 3
| ctx.lineCap = "butt"; ctx.lineCap = "round"; ctx.lineCap = "square";
|
运行效果如下图所示:
线段的末端样式规则为:
- round: 增加了一个半径等于线路宽度一半的半圆
- square: 增加了一个宽度和线段宽度相同,长度是线段宽度一半的矩形区域
其完整源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <!DOCTYPE html> <html lang="cn"> <head> <title>渲染效果(stroke)</title> <meta charset="UTF-8"> <script src="./js/helper.js"></script> </head>
<body style="overflow: hidden; margin:10px; background-color: white;"> <canvas id="canvas" width="750" height="300" style="border:solid 1px #CCCCCC;"></canvas> </body> <script> let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); drawGrid('lightgray', 10, 10);
ctx.lineWidth = 20;
ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(450, 50); ctx.stroke();
ctx.beginPath(); ctx.moveTo(50, 110); ctx.lineTo(450, 110); ctx.lineCap = "butt"; ctx.stroke();
ctx.beginPath(); ctx.moveTo(50, 170); ctx.lineTo(450, 170); ctx.lineCap = "round"; ctx.stroke();
ctx.beginPath(); ctx.moveTo(50, 230); ctx.lineTo(450, 230); ctx.lineCap = "square"; ctx.stroke(); </script>
</html>
|
尝试一下 »
线条连接风格
通过画布的渲染上下文对象lineJoin
属性可指定线路末端样式,其API如下所示:
1 2 3
| ctx.lineJoin = "bevel"; ctx.lineJoin = "round"; ctx.lineJoin = "miter";
|
属性值 |
说明 |
bevel |
在相连部分的末端填充一个额外的以三角形为底的区域,每个部分都有各自独立的矩形拐角。 |
round |
通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。圆角的半径是线段的宽度。 |
miter |
通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。 |
运行效果如下图所示:
其源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <script> let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); drawGrid('lightgray', 10, 10);
ctx.lineWidth = 40;
ctx.beginPath(); ctx.moveTo(50, 70); ctx.lineTo(220, 120); ctx.lineTo(50, 170); ctx.lineJoin = "bevel"; ctx.stroke();
ctx.beginPath(); ctx.moveTo(320, 70); ctx.lineTo(490, 120); ctx.lineTo(320, 170); ctx.lineJoin = "round"; ctx.stroke();
ctx.beginPath(); ctx.moveTo(590, 70); ctx.lineTo(760, 120); ctx.lineTo(590, 170); ctx.lineJoin = "miter"; ctx.stroke(); </script>
|
尝试一下 »
闭合多边形
在讲解绘制曲线和路径时我们提到过,在绘制多边形时,可通过ctx.closePath()
闭合一个多边形,有时也会通过ctx.moveTo()
到起始点的坐标闭合一个多边形。这两种方法均可实现闭合一个多边形,但在设置了lineJoin
属性后,其运行结果会有所差异,如下图所示:
从上图可以看出,使用ctx.closePath()
闭合多边形才会更符合我们所希望的效果。
2. 阴影
阴影颜色和模糊程度
通过画布的渲染上下文对象shadowBlur
属性和shadowColor
可指定线路末端样式,其API如下所示:
1 2
| ctx.shadowBlur = level; ctx.shadowColor = color;
|
属性 |
说明 |
shadowBlur |
描述模糊效果程度的,float 类型的值。注意:只有设置 shadowColor 属性值为不透明,阴影才会被绘制。 |
shadowColor |
可以转换成 CSS <color> 值的DOMString 字符串。默认值是 fully-transparent black.。 |
下图是设置了该属性的运行效果:
其源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script> let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); drawGrid('lightgray', 10, 10);
ctx.shadowBlur = 30; ctx.shadowColor = "red";
ctx.beginPath(); ctx.arc(100, 100, 50, 0, 2 * Math.PI) ctx.ellipse(300, 100, 75, 50, 0, 0, 2 * Math.PI); ctx.rect(450, 50, 200, 100); ctx.fillStyle = "green"; ctx.fill(); </script>
|
尝试一下 »
阴影偏移
在设置阴影效果时,除了阴影颜色和模糊程度外,还可设置阴影的偏移距离,其API如下:
1 2
| ctx.shadowOffsetX = offset; ctx.shadowOffsetY = offset;
|
属性 |
说明 |
shadowOffsetX |
阴影水平偏移距离的 float 类型的值。默认值是 0。 |
shadowOffsetY |
阴影垂直偏移距离的 float 类型的值。默认值是 0。 |
下图是设置了该属性的运行效果:
其源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <script> let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); drawGrid('lightgray', 10, 10);
ctx.shadowBlur = 30; ctx.shadowColor = "red";
ctx.beginPath(); ctx.arc(100, 100, 50, 0, 2 * Math.PI) ctx.ellipse(300, 100, 75, 50, 0, 0, 2 * Math.PI); ctx.rect(450, 50, 200, 100); ctx.fillStyle = "green"; ctx.fill();
ctx.translate(0, 160); ctx.shadowOffsetX = 10; ctx.shadowOffsetY = 10; ctx.beginPath(); ctx.arc(100, 100, 50, 0, 2 * Math.PI) ctx.ellipse(300, 100, 75, 50, 0, 0, 2 * Math.PI); ctx.rect(450, 50, 200, 100); ctx.fillStyle = "green"; ctx.fill();
ctx.translate(0, 160); ctx.shadowOffsetX = -10; ctx.shadowOffsetY = 10; ctx.beginPath(); ctx.arc(100, 100, 50, 0, 2 * Math.PI) ctx.ellipse(300, 100, 75, 50, 0, 0, 2 * Math.PI); ctx.rect(450, 50, 200, 100); ctx.fillStyle = "green"; ctx.fill(); </script>
|
尝试一下 »
3. 渐变
在之前的内容中,我们使用过ctx.strokeStyle=color
和ctx.fillStyle=color
设置绘制图形时的边框颜色和填充颜色,其实这两个属性除了可以指定颜色之外,还可以指定为渐变对象或填充图案对象,这一节我们讲解设置渐变风格的实现过程。
渐变风格又包括了“线性渐变”和“径向渐变”两种不同的风格。
线性渐变
线性渐变是一种颜色过渡方式,它以一条直线(水平或垂直)为轴线,从起点到终点颜色进行顺序渐变。渲染上下文对象提供了建立线性渐变的方法,其定义如下:
1
| CanvasGradient ctx.createLinearGradient(x0, y0, x1, y1);
|
参数 |
说明 |
x0 |
起点的 x 轴坐标 |
y0 |
起点的 y 轴坐标 |
x1 |
终点的 x 轴坐标 |
y1 |
终点的 y 轴坐标 |
该方法的返回值是一个CanvasGradient对象,该对象包含了一个方法:
1
| void gradient.addColorStop(offset, color);
|
参数 |
说明 |
offset |
偏移位置,0到1之间的值 |
color |
颜色值 |
我们通过一个例子熟悉一下线性渐变的用法,运行效果如下:
这个例子使用了线性渐变填充在圆形、矩形和文本。起始颜色是金色(gold),终止颜色是红色(red),其源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <script> let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); drawGrid('lightgray', 10, 10);
const gradient = ctx.createLinearGradient(0, 50, 0, 200); gradient.addColorStop(0.05, "gold"); gradient.addColorStop(0.95, "red"); ctx.fillStyle = gradient;
ctx.beginPath(); ctx.arc(125, 125, 75, 0, 2 * Math.PI); ctx.fill();
ctx.translate(200, 0); ctx.beginPath(); ctx.rect(50, 50, 150, 150); ctx.fill();
ctx.translate(250, 0); ctx.font = "150px 黑体" ctx.textBaseline = "top"; ctx.fillText("图形", 0, 50); </script>
|
尝试一下 »
在这个例子中建立的线性渐变对象,其坐标为(0,50)至(0,200),表示其渐变颜色的方向为从上往下垂直渐变。改变坐标就能改变颜色逐渐变换的方向,例如可以改为水平方向,也可以改为沿着斜线的方向。我们看看下面这个示例:
渐变对象的addColorStop()
方法用于指定渐变颜色,该方法可以调用n次,第一次表示渐变开始的颜色,第二次表示渐变结束的颜色,第三次会以第二次为开始颜色渐变,以此类推。我们看看下面这个示例:
其源代码如下:
1 2 3 4 5 6
| const gradient = ctx.createLinearGradient(0, 50, 0, 200); gradient.addColorStop(0.05, "red"); gradient.addColorStop(0.5, "gold"); gradient.addColorStop(0.95, "green"); ctx.fillStyle = gradient;
|
尝试一下 »
径向渐变
径向渐变是指从起点到终点颜色从内到外进行圆形渐变(从中间向外拉)。渲染上下文对象提供了建立径向渐变的方法,其定义如下:
1
| CanvasGradient ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
|
参数 |
说明 |
x0 |
开始圆形的 x 轴坐标 |
y0 |
开始圆形的 y 轴坐标 |
r0 |
开始圆形的半径 |
x1 |
结束圆形的 x 轴坐标 |
y1 |
结束圆形的 y 轴坐标 |
r1 |
结束圆形的半径 |
该方法的返回值是一个CanvasGradient对象,该对象包含了方法addColorStop()
与线性渐变的该方法完全一样。接下来看一个径向渐变的例子,运行效果如下:
径向渐变同样可填充在圆形、矩形和文本等图形中。从上图可知径向渐变是一种颜色从内到外的渐变,从一个起点向所有方向进行的渐变。完整源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <script> let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); drawGrid('lightgray', 10, 10);
const gradient = ctx.createRadialGradient(125, 125, 0, 125, 125, 160); gradient.addColorStop(0.05, "gold"); gradient.addColorStop(0.95, "red"); ctx.fillStyle = gradient;
ctx.beginPath(); ctx.arc(125, 125, 75, 0, 2 * Math.PI); ctx.fill();
ctx.translate(200, 0); ctx.beginPath(); ctx.rect(50, 50, 150, 150); ctx.fill();
ctx.translate(250, 0); ctx.font = "150px 黑体" ctx.textBaseline = "top"; ctx.fillText("图形", 0, 50); </script>
|
尝试一下 »
重要提示:渐变对象可同时应用于多个形状或文字,该示例中的圆形、矩形和文字均使用了该渐变对象。在绘制了圆形之后,由于使用了translate()
将画布坐标系进行了偏移,在绘制矩形和文字时的坐标仍与绘制圆的坐标位置基本一致,因此该渐变对象的坐标可同时适用于此三个形状/文字。关于translate()
的用法我们将在后续的章节中进行介绍。
使用createRadialGradient()
创建径向渐变对象时可指定开始圆的位置和半径,也可指定结束圆的位置和半径,下面的效果图片就是改变了这几个参数后的运行效果,如下图:
4. 图案/纹理
除了渐变对象外,fillStyle
还可以使用图案/纹理方式填充各类图形,使得画布可以很方面的实现类似于CAD等绘图软件提供的图案填充功能。渲染上下文对象提供了创建图案的方法createPattern()
,其定义如下:
1
| CanvasPattern ctx.createPattern(image, repetition);
|
参数 |
说明 |
image |
填充图案源 |
repetition |
重复方式 |
- 填充图案源可以是位图、也可以是的画布对象
- 重复方式包括:
- “repeat” :水平和垂直方向均重复
- “repeat-x” :仅水平方向重复
- “repeat-y” :仅垂直方向重复
- “no-repeat” :不重复
默认值是:repeat
使用位图作为填充图案
填充图案亦可应用于路径、矩形和文字中,下面这个示例将在圆形、矩形和文本绘制中使用图案填充,其效果如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <script> let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); drawGrid('lightgray', 10, 10);
loadImage("./images/15.jpg", function (image) { let pattern = ctx.createPattern(image, "repeat"); ctx.save();
ctx.fillStyle = pattern; ctx.lineWidth = 2;
ctx.beginPath(); ctx.arc(125, 125, 75, 0, 2 * Math.PI); ctx.fill(); ctx.stroke();
ctx.translate(200, 0); ctx.beginPath(); ctx.rect(50, 50, 150, 150); ctx.fill(); ctx.stroke();
ctx.translate(250, 0); ctx.font = "150px 黑体" ctx.textBaseline = "top"; ctx.fillText("图形", 0, 50); ctx.strokeText("图形", 0, 50); ctx.restore(); })
function loadImage(src, callback) { let image = new Image(); image.onload = function () { callback(image); } image.src = src } </script>
|
尝试一下 »
在图形系统中,经常应用“填充图案”描述图形中一些特殊的区域或者材质,如下图所示:
使用画布作为填充图案
除了直接使用位图作为填充图案之外,还可以使用画布的内容作为填充图案,比较适合一些使用简易图案作为填充图案的场景,下面这个示例展示了使用画板作为填充图案,运行效果如下图所示:
在这个示例中,使用document.createElement("canvas")
创建了一个临时画布,在这个画布中绘制了两个线条,并使其重复填充至主画布的圆形和矩形中,就实现了上面这个效果,其完整源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <script> let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); drawGrid('lightgray', 10, 10); ctx.fillStyle = getParrent(25, 25); ctx.lineWidth = 2;
ctx.beginPath(); ctx.arc(130, 125, 100, 0, 2 * Math.PI); ctx.fill(); ctx.stroke();
ctx.translate(220, 0); ctx.beginPath(); ctx.rect(50, 50, 500, 180); ctx.fill(); ctx.stroke();
function getParrent(width, height) { let pcanvas = document.createElement("canvas"); pcanvas.width = width; pcanvas.height = height; pctx = pcanvas.getContext('2d'); pctx.beginPath(); pctx.moveTo(0, 0); pctx.lineTo(width, height); pctx.moveTo(width, 0); pctx.lineTo(0, height); pctx.strokeStyle = "blue"; pctx.lineWidth = 0.5; pctx.stroke();
return ctx.createPattern(pcanvas, "repeat"); } </script>
|
尝试一下 »
掌握了使用渐变和图案作为填充效果之后,接下来我们看一个“围棋”实际案例,该围棋棋盘和棋子的绘制效果非常逼真,如下图所示:
在该示例中,使用“图案填充”技术填充了围棋棋盘,使用“径向渐变”实现了棋子的光影效果,这两种技术都是我们刚刚讲述过的,因此其实现的代码也很容易理解,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| <script> let canvas = document.getElementById('canvas'); let ctx = canvas.getContext('2d'); let size = 100;
let image = new Image(); image.onload = function () { draw(); } image.src = "./images/wood2.png";
function draw() { let pattern = ctx.createPattern(image, "repeat"); ctx.save(); ctx.fillStyle = pattern; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore();
drawGrid('lightgray', 10, 10);
drawBoards();
drawPiece(2, 1, 1); drawPiece(3, 0, 1); drawPiece(3, 1, 1); drawPiece(2, 1, 1); drawPiece(0, 2, 1); drawPiece(1, 2, 1); drawPiece(2, 2, 1); drawPiece(3, 2, 0); drawPiece(4, 0, 0); drawPiece(4, 1, 0); drawPiece(0, 3, 0); drawPiece(1, 3, 0); drawPiece(2, 3, 0); drawPiece(3, 3, 0); }
function drawBoards() { ctx.save(); ctx.beginPath(); ctx.lineCap = "square"; for (let i = size; i < canvas.width; i += size) { ctx.moveTo(i, size); ctx.lineTo(i, canvas.height) } for (let i = size; i < canvas.width; i += size) { ctx.moveTo(size, i); ctx.lineTo(canvas.width, i) } ctx.lineWidth = 6; ctx.stroke(); ctx.restore(); }
function drawPiece(x, y, type) { x = (x + 1) * 100; y = (y + 1) * 100; let radius = size/2 - 2;
const gradient = ctx.createRadialGradient(x + radius / 2, y - radius / 2, 0, x, y, radius); gradient.addColorStop(0, type == 1 ? "#B9B9B9" : "#FFFFFF"); gradient.addColorStop(0.95, type == 1 ? "#000000" : "#DCDCDC"); gradient.addColorStop(0.97, type == 1 ? "#000000" : "#DCDCDC00"); gradient.addColorStop(1, type == 1 ? "#00000000" : "#DCDCDC00");
ctx.save(); ctx.beginPath(); ctx.arc(x, y, radius, 0, 2 * Math.PI); ctx.fillStyle = gradient; ctx.fill(); ctx.restore(); } </script>
|
尝试一下 »
5. 本章小结
本章讲解了Canvas所提供的的一些渲染效果的用法,这些效果不仅仅是增强了图形的立体感和视觉效果,更重要的是让图形更具有真实感,使图形看起来更接近现实世界,因此这些这些效果在图形系统中得到了广泛应用。
本章内容使用了Canvas 2D API以下属性和方法:
属性
属性值 |
说明 |
ctx.lineCap |
线条末端样式 |
ctx.lineJoin |
线条连接风格 |
ctx.shadowBlur |
模糊程度 |
ctx.shadowColor |
阴影颜色 |
ctx.shadowOffsetX |
阴影水平偏移距离 |
ctx.shadowOffsetY |
阴影垂直偏移距离 |
方法
方法名 |
说明 |
ctx.createLinearGradient(x0, y0, x1, y1) |
创建线性渐变样式 |
ctx.createRadialGradient(x0, y0, r0, x1, y1, r1) |
创建径向渐变样式 |
gradient.addColorStop(offset, color) |
添加一个由偏移值和颜色值指定的断点到渐变 |
ctx.createPattern(image, repetition) |
创建填充图像样式 |
练习一下
阴影
按照以下格式要求在Canvas中绘制你喜欢的一本书的名字:
- 垂直居中
- 水平居中
- 字体大小:50px
- 字型:黑体
- 颜色:blue
- 添加阴影效果
渐变和图案
在本章渐变与图案的示例中,均是将这两种风格应用在了填充样式ctx.fillStyle
中,而且示例中的几何对象也是几个简单的几何对象。其实渐变与图案是可以应用于更复杂的路径,同时还可以应用于描边样式ctx.strokeStyle
的,下面这张图片就是将渐变和图案应用到了贝塞尔曲线中了。
你也动手试一试吧。
尝试一下 »
本文为“图形开发学院”(graphanywhere.com)网站原创文章,遵循CC BY-NC-ND 4.0版权协议,商业转载请联系作者获得授权,非商业转载请附上原文出处链接及本声明。
0评论