第二章 绘制
# 绘制
本章会介绍:
- 对线条、弧形、圆、曲线及多边形进行描边与填充
- 通过设置绘图环境的属性来改变所绘图形的外观
- 绘制圆角矩形
- 绘制并编辑贝塞尔曲线
- 对2d绘制环境进行扩展,使之可以绘制虚线
- 使用纯色、渐变色及图案对图形进行描边及填充
- 阴影效果
- 在不影响背景的情况下,使用“剪辑区域”技术来查出图形与文本
- 在canvas中拖动图形对象
- 坐标系统的变换
# 坐标系统
canvas坐标系统以左上角为原点,x坐标向右方增长,y坐标向下方延伸,我们也可以对坐标系统进行平移(translate)或者旋转(rotate)和缩放(scale)
# Canvas的绘制模型
在向canvas之上绘制图形或图像时,浏览器将做以下步骤:
- 将图形或图像绘制到一个无线大的透明位图,在绘制时使用当前绘制的阴影设定
- 将图形或图像绘制到另外一幅绘图中,绘制时使用当前绘图环境的阴影设定。
- 将阴影中每一个像素的alpha分量乘以绘图环境对象的globalAlpha属性值
- 将绘有引用的位图与经过剪辑区域剪切过的canvas进行图像合成。操作时使用当前合成模式参数。
- 将图形或图像的每一个像素颜色分量,乘以绘图环境对象的globalAlpha属性值
- 将绘有图形或图像的位图,合成到当前经过剪辑区剪切过的canvas位图上, 在操作时使用当前合成操作符
# 矩形绘制
clearReat(double x,double y, double w, double h) //清除某一部分的矩形
strokeReat(double x,double y, double w, double h) // 绘制空心矩形
fillReat(double x,double y, double w, double h) // 绘制实心矩形
1
2
3
2
3
# 颜色与透明度
strokeStyle与fillStyle的属性值可以是任意有效的css字符串
#ffffff;
#642
rgb(255,255,0)
hsl(20,62%,28%)
hsla(40,82%,33%,0.6)
antiquewhite
burlywood
cadetblue
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 渐变色与图案
Canvas元素支持线性(linear)与放射(radial)渐变
- 通过调用createLinearGradient()会返回一个CanvasGradient实例,通过它的唯一方法addColorStop来向渐变色中添加颜色停止点(color stop)。该方法接受两个参数:一个是位于0-1.0之间的double值,代表颜色停止点在渐变线上的位置,另一个是DOMString类型的css3颜色字串值
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
gradient = context.createLinearGradient(0,0,canvas.width,0);
gradient.addColorStop(0,'blue');
gradient.addColorStop(0.25,'white');
gradient.addColorStop(0.5,'purple');
gradient.addColorStop(0.75,'red');
gradient.addColorStop(1,'yellow');
context.fillStyle = gradient;
context.rect(0,0,canvas.width,canvas.height)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 放射性渐变
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
gradient = context.createRadialGradient(canvas.width/2,canvas.height,10,canvas.width/2,0,100);
gradient.addColorStop(0,'blue');
gradient.addColorStop(0.25,'white');
gradient.addColorStop(0.5,'purple');
gradient.addColorStop(0.75,'red');
gradient.addColorStop(1,'yellow');
context.fillStyle = gradient;
context.rect(0,0,canvas.width,canvas.height);
context.fill()
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 图案
# 阴影
- shadowColor: css3格式颜色
- shadowOffsetX: 水平像素偏移
- shadowOffsetY: 垂直像素偏移
- shadowBlur: 高斯模糊
context.shadowColor = 'rgba(0,0,0,0.7)';
context.shadowOffsetX = 1;
context.shadowOffsetY = 1;
context.shadowBlur = 2;
1
2
3
4
2
3
4
禁用阴影,将shadowColor设置为undefined
# 内嵌阴影
shadowOffsetX与shadowOffsetY设置负偏移量可以形成内嵌阴影
var drawingContext = document.getElementById('canvas').getContext('2d'),
drawingContext.lineWidth = 1;
drawingContext.shadowColor = 'blue';
drawingContext.shadowOffsetX = -5;
drawingContext.shadowOffsetY = -5;
drawingContext.shadowBlur = 20;
drawingContext.strokeStyle = 'rgba(0,0,255,0.6)'
drawingContext.save();
drawingContext.beginPath();
drawingContext.arc(100,100,60,0,Math.PI*2,false);
drawingContext.clip(); //绘制内容不得超出圆形范围之外
drawingContext.stroke();
drawingContext.restore();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
效果如下:
# CanvasRenderingContext2D之中与阴影效果相关的属性
# 路径、描边、填充
var context = document.getElementById('drawingCanvas').getContext('2d');
context.font = '48pt Heivetica';
context.strokeStyle = 'blue';
context.fillStyle = 'red';
context.lineWidth = '2';
context.strokeText('Stroke', 60, 110);
context.fillText('Fill',440,110);
context.lineWidth = '5';
context.beginPath();
context.rect(400,150,150,100);
context.fill();
context.beginPath();
context.arc(820,370,60,0,Math.Pi/2);
context.stroke();
context.fill();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
首先调用beginPath()方法来开始一段新的路径,rect()与arc()方法分别用于创建矩形和弧形路径。然后调用stroke与fill方法,对刚才那些路径进行描边或填充。
# 路径与子路径
在某一时刻,canvas中只能有一条路径存在,canvas称为当前路径。然而这条路径却可以包含许多子路径。而子路径又是由两个或者多个点组成的。
// 绘制连个矩形
context.beginPath();
context.rect(10,10,100,100);
context.stroke();
context.beginPath();
context.rect(50,50,100,100);
context.stroke();
1
2
3
4
5
6
7
2
3
4
5
6
7
# 填充路径所使用的非零环绕规则
# 剪纸效果
let context = document.getElementById('canvas').getContext('2d');
function drawGrid(color, stepx, stepy) {
}
function drawTwoArcs() {
context.beginPath();
context.arc(300,190,150,0,Math.PI*2,false);
context.arc(300,190,150,0,Math.PI*2,true);
context.fill();
context.shadowColor = undefined;
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.stroke();
}
function draw() {
context.clearRect(0,0,context.canvas.width,context.canvas.height);
drawGrid('lightGray',10,10);
context.save();
context.shadowColor='rgba(0,0,0,0)';
context.shadowOffsetX = 12;
context.shadowOffsetY = 12;
context.shadowBlur = 15;
drawTwoArcs();
context.restore();
}
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
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
# 线段
let canvas = document.querySelector('#canvas');
let context = canvas.getContext('2d');
context.lineWidth =1;
context.beginPath();
context.moveTo(50,10);
context.lineTo(200,10);
context.stroke();
context.beginPath();
context.moveTo(50,20);
context.lineTo(200,20);
context.stroke();
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
在下图中,这条垂直线段绘制在两个像素之间,中线左右两端的那半个像素就不再延伸了,它们合起来恰好占据1个像素宽度。所以说,如果要绘制一条真正1像素宽的线段,你必须将该线段绘制在某两个像素之间的那个像素中,而不是他两的交界处
# 网格绘制
var context = document.getElementById('canvas').getContext('2d')l
function drawGrid(context, color, stepx, stepy) {
context.strokeStyle = color;
context.lineWidth = 0.5;
for(var i = stepx +0.5;i<context.canvas.width;i+=stepx) {
context.beginPath();
context.moveTo(i,0);
context.lineTo(i,context.canvas.height);
context.stroke();
}
for(var i = stepy+0.5;i<context.canvas.height;i+=stepy) {
context.beginPath();
context.moveTo(0,i);
context.lineTo(context.canvas.width,i);
context.stroke();
}
}
drawGrid(context,'lightgray',10,10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 橡皮筋式的线条绘制
# 虚线绘制
var context = document.getElementById('canvas').getContext('2d')
function drawDashedLine(context, x1, y1, x2, y2, dashLength) {
dashLength = dashLength === undefined ? 5 : dashLength;
var deltaX = x2 - x1;
var deltaY = y2 - y1;
var numDashes = Math.floor(Math.sqrt(deltaX*deltaX + deltaY*deltaY)/dashLength);
for(var i=0;i<numDashes;++i) {
context[i%2===0?'moveTo':'lineTo'](x1 + (deltaX / numDashes) * i, y1 + (deltaY / numDashes) * i);
context.stroke()
}
drawDashedLine(context,20,20,context.canvas.width-20,20);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 线段断点与连接点的绘制
- lineCap
- lineJoin
属性 | 描述 | 类型 | 取值范围 | 默认值 |
---|---|---|---|---|
lineWidth | 以像素为单位的线段宽度 | double | 非零的整数 | 1.0 |
lineCap | 该值决定浏览器如何绘制线段的端点 | DOMString | butt、round、square | butt |
lineJoin | 该值决定浏览器如何绘制线段的连接点 | DOMString | round、bevel、miter | bevel |
mitterLimit | 斜接线长度与二分之一线宽的比值,如果斜线的长度超过了该值,浏览器就会以bevel方式来绘制线段的连接点 | double | 非零的正数 | 10.0 |
# 圆弧形
方法 | 描述 |
---|---|
arc(double x,double y,double radius,double startAngle,double endAngle,boolean counter-clockwise) | 创建一条以(x,y)为圆心,radius为半径,以startAngle和endAngle为起止角的圆弧路径,最后一个参数false为顺时针,true为逆时针 |
arcTo(double x1,double y1,double x2,double y2,double radius) | 参考(x1,y1)与(x2,y2)两个点,创建一条radius为半径的圆弧路径,该圆弧与当前点到(x1,y1)到(x2,y2)的连线相切 |
# 贝塞尔曲线(bezier curve)
贝塞尔曲线分为平方贝塞尔曲线和立方贝塞尔曲线。平方贝塞尔曲线是一种二次曲线,它是由三个点定义的:两个描点一个控制点。立方贝塞尔曲线则是一种三次曲线,室友四个点来控制:两个描点和两个控制点。
# 二次方贝塞尔曲线
context.quadraticCurveTo(150.8,130,160.6,150.5);
context.quadraticCurveTo(190,250.0,210.5,160.5);
context.quadraticCurveTo(240,100.5,290,70.5);
1
2
3
2
3
quadraticCurveTo方法绘制二次方贝塞尔曲线,接受四个参数,分别表示两个点的x和y坐标。第一个点是曲线的控制点,用于决定曲线的形状,第二个点是描点。
方法 | 描述 |
---|---|
bezierCurveTo(double cpx, double cpy,double cp2x, double cp2y,double x,double y) | 创建一条代表三次方贝塞尔曲线路径,你需要向该方法传入三个点的坐标,前两个点是该曲线的控制点,最后一个是描点 |
# 多边形绘制
points.push(new Point(centerX + radius*Math.sin(angle), centerY - radius*Math.cos(angle)))
angle += 2*Math.PI /sides
1
2
2
在Github上编辑此页 (opens new window)
上次更新: 3/22/2021, 3:47:15 AM