0%

canvas基本绘图

在canvas中画图其实也和现实中的画图是一样的,只不过是我们要告诉机器来怎么画。比如我们使用2b铅笔画了一条三厘米的直线,那么转换成canvas能识别的语言就是,我们拿了个什么颜色的笔strokeStyle,笔的线宽lineWidth,以及开始点和结束点,按照这样一系列的指令,canvas就可以画出我们想要的图形了。我们来看下具体的基本图形的使用。

线段

canvas中,线段需要用路径来实现,然后进行描边。设置路径用beginPath()开始路径,closePath()结束路径。我们可以设置笔触的颜色strokeStyle和线段的宽度lineWidth。线段有头尾有拐点,头尾端点的样式可以用lineCap设置,拐点的样式可以用lineJoin设置。

lineCap属性定义线的端点,有三个值:

  • butt: 默认值,端点是垂直于线段边缘的平直边缘
  • round:端点是线段边缘处以线宽为直径的半圆
  • square:端点是线段边缘处以线宽为长、以一半线宽为宽的矩形

lineJoin属性定义两条线相交产生的拐角。也有三个值:

  • miter:默认值,在连接处边缘延长相接。miterLimiter是角长和线宽所允许的最大比例(默认是10)
  • bevel:连接处是一个对角线斜角
  • round:连接处是一个圆实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function drawLine(context) {
context.strokeStyle = 'red';
context.lineWidth = 20;
// 画出三个线,间隔距离为100
var x = 100;
// 线段端点的样式
var lineCapArr = ['butt', 'round', 'square'];
// 线段连接处的样式
var lineJoinArr = ['miter', 'bevel', 'round'];
for (var i = 0; i < 3; i++) {
context.lineCap = lineCapArr[i];
context.lineJoin = lineJoinArr[i];
context.beginPath();
context.moveTo(100 + i * x, 100);
context.lineTo(100 + i * x, 300);
context.lineTo(150 + i * x, 250);
context.stroke();
context.closePath();
}
}

line

弧线

arc(x, y, radius, startAngle, endAngle, anticlockwise);

x和y定义圆心位置,radius定义弧线半径,startAngle和endAngle定义起始和结束的弧度值,anticlockwise表示是否是逆时针,true或false。

1
2
3
4
5
6
7
var sangle = 0, angle = 90;
this.context.strokeStyle = '#cccccc';
this.context.lineWidth = 3;
this.context.beginPath();
this.context.arc(400, 100, 50, angle * Math.PI / 180, angle * Math.PI / 180, true);
this.context.stroke();
this.context.closePath();

贝塞尔曲线

贝塞尔曲线有平方和立方两种形式。

平方贝塞尔曲线

在二维空间中,贝塞尔曲线通过“起点”、“终点”以及两个决定曲线走向的“控制点”定义完成。平方贝塞尔曲线是最简单的曲线,只需要一个终点以及一个控制点。

1
quadraticCurveTo(cpx, cpy, x, y)

其中控制点为(cpx,cpy),终点为(x,y)。好比是起点到终点的直线,通过控制点将直线拉成弧线,越靠近控制点的地方力度越大。
Alt text

1
2
3
4
5
6
// 贝塞尔平方曲线
this.context.beginPath();
this.context.moveTo(200, 100);
this.context.quadraticCurveTo(100, 25, 100, 450);
this.context.stroke();
this.context.closePath();

立方贝塞尔曲线

立方贝塞尔曲线用两个控制点,从而很容易绘制S型曲线。

1
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

Alt text

1
2
3
4
5
6
7
8
9
var p1 = new Point(50, 150);
var p2 = new Point(450, 350);
var start = new Point(250, 50);
var end = new Point(250, 450);
this.context.beginPath();
this.context.moveTo(start.x, start.y);
this.context.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, end.x, end.y);
this.context.stroke();
this.context.closePath();

矩形

  • fillRect(x, y, w, h):在位置(x, y)处以宽为w,高为y绘制一个填充矩形。(需要先设置好填充的颜色fillStyle)
  • strokeRect(x, y, w, h):在位置(x, y)处以宽为w,高为h绘制一个矩形边框。(需要设置描边的属性strokeStyle等)。
  • clearRect(x, y, w, h):在位置(x, y)处以宽为w,高为h,清除区域使其透明。

在canvas中绘图,可以利用所谓的绘图堆栈状态,每个状态都可以随时存储canvas上下文数据。
注意:当前路径和当前位图受canvas环境控制,不属于保存的状态。这个功能允许在画布上对单个对象进行绘画和制作动画。

旋转和平移变换

如果要将画布进行旋转等变换,就需要先将canvas画布设置为identity矩阵。

1
context.setTransform(1, 0, 0, 1, 0, 0);

旋转 rotate

在canvas中的角度都是弧度,所以旋转的角度也要换算为弧度。

1
context.rotate(angle * Math.PI / 180);

需要注意的是,需要在旋转后,再绘制图形。

1
2
3
4
context.setTransform(1, 0, 0, 1, 0, 0);
context.rotate(45 * Math.PI / 180);
context.fillStyle = 'green';
context.fillRect(100, 100, 200, 200);

但是这样旋转后明显发现绘制的图形被旋转到了画布的外面。这是因为没有指定旋转的中心点,按照默认的点(0, 0)来进行的旋转。所以我们需要设置中心点。

注意:旋转会叠加。如果前面使用了旋转,那么后面的旋转会在前面的基础上进行旋转,比如,前面已经旋转了30度,后面要旋转60度,那么相对的只要再旋转30度就好了。

平移 translate

我们需要将原点平移到图形的中心点,这样对象才会围绕着自己转。那如何平移中心点到对象的中心点?

默认的中心点是(0, 0),我们需要绘制的对象的起点坐标为(100, 100),图形的宽高为200,我们计算中心点的话:
translate

1
2
3
var x = 100, y = 100, width = 200, height = 200;
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(x + width / 2, y + height / 2);

同时我们应用旋转:

1
2
3
4
5
6
var x = 100, y = 100, width = 200, height = 200;
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(x + width / 2, y + height / 2);
context.rotate(45 * Math.PI / 180);
context.fillStyle = 'green';
context.fillRect(- width / 2, - height / 2, width, height);

有些图形是基本图形,类似于圆形、正方形,这种图形的中心点都比较好找,但是如果是不规则的图形呢?如何找中心点?

这需要用到边界框理论,任何形状的中心点都是半宽的x值和半高的y值。

缩放 scale

使用context.scale(x, y)来可以对形状进行缩放,x代表水平缩放,y代表垂直缩放。

填充颜色以及渐变

canvas的着色有颜色和渐变两种。

基本颜色

可以使用html4规范的可以使用颜色字符串列表。比如有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
黑色	black	#000000
绿色 green #008000
银色 silver #c0c0c0
青柠色 lime #00ff00
灰色 gray #808080
白色 white #ffffff
黄色 yellow #ffff00
栗色 maroon #800000
海蓝色 navy #000080
红色 red #ff0000
蓝色 blue #0000ff
紫色 purple #800080
蓝深绿色 teal #008080
紫红色 fuchsia #ff00ff
浅蓝绿色 aqua #00ffff

所有这些颜色字符串可以直接使用到strokeStyle和fillStyle中。

当然也可以使用rgb()和rgba()设置填充色。rgba方法可以指定32位色值的填充色,其后8位表示透明度,范围为1(不透明)~0(透明)。

1
2
3
4
context.fillStyle = 'black';
context.fillRect(100, 100, 200, 200);
context.fillStyle = 'rgb(255, 33, 33, 0.8)';
context.fillRect(100, 100, 300, 300)

渐变

在画布上创建渐变有两个选项:线性或径向。线性渐变创建一个水平、垂直、或者对角线的填充图案。径向渐变自中心点创建一个放射状填充。

线性渐变

线性渐变有三种基本样式:水平、垂直、对角线。
首先需要创建一个线性渐变的载体:

1
var gr = context.createLinearGradient(x0, y0, x1, y1);

createLinearGradient方法调用中的4个值是开始渐变的左上角坐标和渐变结束的右下角坐标。

当创建一个水平渐变的时候,y值都是0.
当创建一个垂直渐变的时候,x值都是0.
对角渐变就是从左上角直到右下角。

定义了渐变的大小,就需要加入颜色断点:

1
gr.addColorStop(offset, color);

添加颜色断点的函数addColorStop的两个参数分别为:offset,距离开始位置的相对位置,范围为0~1;color,渐变的颜色。

然后应用到绘制的形状上:

1
2
3
4
5
6
var gr = context.createLinearGradient(100, 0, 400, 0);
gr.addColorStop(0, 'rgb(255, 0, 0)');
gr.addColorStop(.5, 'rgb(0, 255, 0)');
gr.addColorStop(1, 'rgb(255, 0, 0)');
context.fillStyle = gr;
context.fillRect(100, 100, 300, 300)

gradient

不仅可以使用在填充色上,也可以使用在描边上。

径向渐变

径向渐变的过程和线性渐变非常类似。它们都采用颜色断点的概念来创建颜色变换。
同样需要先创建径向渐变的载体:

1
var gr = context.createRadialGradient(x0, y0, r0, x1, y1, r1);

这里createRadialGradient函数需要的留个参数其实是两个圆:圆心为(x0, y0)半径为r0的圆和圆心为(x1, y1)半径为r1的圆。第一个圆是开始圆,第二个结束圆。

然后像线性渐变一样设置颜色断点:

1
gr.addColorStop(offset, color);

言而总之,径向渐变就是从第一个圆心开始,进行颜色渐变过渡,到第二个圆的圆边结束。

渐变的效果具体要看这两个圆的相对位置。我们知道平面内的两个圆的位置有:相交、相切、相离。
这里有一个相离的效果图:
radial-gradient

1
2
3
4
5
6
var gr = context.createRadialGradient(x0, y0, r0, x1, y1, r1);
gr.addColorStop(0, 'rgb(3, 3, 3)');
gr.addColorStop(.5, 'rgb(0, 255, 0)');
gr.addColorStop(1, 'rgb(255, 0, 0)');
context.fillStyle = gr;
context.fillRect(100, 100, 300, 300)

阴影

可以使用下面四个参数给canvas添加阴影:

  • shadowOffsetX 水平方向阴影,可以为正负
  • shadowOffsetY 垂直方向阴影,可以为正负
  • shadowBlur 设置阴影的模糊效果程度
  • shadowColor 阴影的颜色

例如:

1
2
3
4
5
6
context.fillStyle = 'black';
context.shadowOffsetX = -10;
context.shadowOffsetY = -10
context.shadowBlur = 4;
context.shadowColor = 'red';
context.fillRect(100, 100, 300, 300);

canvas-shadow


重新回顾了下canvas的基本绘图api,对一些旋转变换以及渐变和阴影做了补充。一步步了解后,一些不怎么熟悉的嫌麻烦不管涉及的模块也熟悉了,反而觉得理解更深入了一点,每天进步一点吧~😄

本文代码已同步github:
https://github.com/tony-yyj/canvas-game/tree/master/basic-draw

码字辛苦,打赏个咖啡☕️可好?💘