本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
svg标签,是html5中矢量图的应用;
canvas标签,是html5中位图的应用。
Cnavas标签
如下为一个canvas标签的基本结构,以下所有示例均在此基础上完成(重复代码部分不再在写出)
<canvas id="c1" width="600" height="500"></canvas>
<script>
let canvas=document.getElementById("c1");
let ctx=canvas.getContext("2d");
// 示例代码...
</script>
Canvas坐标系统
canvas基于栅格(grid)和坐标空间完成图形的定位和长度的度量。
如图,canvas元素默认被网格所覆盖。通常来说网格中的一个单元相当于canvas元素中的一像素。栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点来定位。
依据坐标原点平移、网格旋转以及缩放。
图中蓝色方形左上角的坐标为距离左边(X轴)x像素,距离上边(Y轴)y像素。(坐标为(x,y))。
Canvas如何绘制图形?
canvas图形的绘制分成两部分:
-
骨骼,图形的架子,也叫路径path。使用moveTo、lineTo等指定绘制的点、线坐标。
-
绘制,给定颜色进行描绘。绘制分两种方式:
- 描边
- 1.1 描边样式:描边颜色
ctx.strokeStyle="red"
; 描边粗细ctx.lineWidth=2
; - 1.2 执行绘制,即绘制线条
ctx.stroke()
;
- 1.1 描边样式:描边颜色
- 填充
- 2.1 填充颜色
ctx.fillStyle="blue"
; - 2.2 执行绘制,即实现填充
ctx.fill()
。
- 2.1 填充颜色
- 描边
由此描边和填充执行的先后顺序不同,将会导致一些不同的显示结果,如下图所示
canvas中的绘图是基于状态的绘图,即先设置一个绘图的状态,然后再调用具体的函数进行绘制。
如画一条直线:
// 绘制一个20,20到120,120的直线
ctx.moveTo(20,20);
ctx.lineTo(120,120);
// 调用stroke()进行具体的绘制
ctx.stroke();
路径绘制
一个路径,甚至一个子路径,都是闭合的。
使用路径绘制图形的步骤:
- 创建路径起始点。
beginPath()
新建一条路径,moveTo(x, y)
设置路径起始点。
绘制路径前要显式调用beginPath()方法
- 调用绘制方法去绘制出路径
如lineTo(x,y)
绘制一条从当前路径到指定坐标的直线。
- 把路径封闭
closePath()
闭合路径之后,图形绘制命令又重新指向到canvas画布的上下文中。
(实际使用中常常省略,确保必要时新开始一条路径即可。比如fill()
填充会自动闭合路径)。
- 一旦路径生成,就可以通过描边或填充路径区域来渲染图形。
stroke()
用线条来绘制图形轮廓。fill()
填充路径的内容区域生成实心的图形。
line线条及属性
line线条
绘制直线组合成三角形,并设置边框和填充
- 三角形
// 直线 三角形 边框样式
ctx.beginPath()
ctx.moveTo(10,180);
ctx.lineTo(300,20);
ctx.lineTo(400,220);
ctx.closePath(); // closePath会闭合路径,形成三角形。取消闭合 为两条线段
ctx.strokeStyle="green";// 线条颜色 .默认黑色
ctx.stroke();
- 实心三角形
// 填充的三角形
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 200);
ctx.fillStyle="red"; //填充的颜色
ctx.fill(); //填充闭合区域。如果path没有闭合,则fill()会自动闭合路径。
绘制多边形
随意绘制一个闭合的多边形
// 多个线条绘制多边形
ctx.beginPath();
ctx.moveTo(20,30);
ctx.lineTo(300,20);
ctx.lineTo(350,200);
ctx.lineTo(280,330);
ctx.lineTo(70,290);
// ctx.closePath();
ctx.strokeStyle="red";
ctx.stroke();
line线条属性
lineCap——线段末端样式
round
: 线段末端增加一段圆形结束butt
(默认值)线条末端方形结束square
线段末端增加一个(宽度和线段相同,高度是线段厚度(宽度)一半)方形结束
//
ctx.beginPath();
ctx.moveTo(50,50);
ctx.lineTo(50,300);
ctx.lineWidth=20;
ctx.strokeStyle="red";
ctx.lineCap="butt";// 默认
ctx.stroke();
ctx.beginPath();
ctx.moveTo(100,50);
ctx.lineTo(100,300);
ctx.lineWidth = 20;
ctx.strokeStyle = "red";
ctx.lineCap="round";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(150, 50);
ctx.lineTo(150, 300);
ctx.lineWidth = 20;
ctx.strokeStyle = "red";
ctx.lineCap = "square";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, 49);
ctx.lineTo(300, 49);
ctx.moveTo(0, 301);
ctx.lineTo(300, 301);
ctx.lineWidth = 0.5;
ctx.strokeStyle = "blue";
ctx.stroke();
线条末端的样式
lineJoin——线条与线条接合处的样式
同一个 path 内,设定线条与线条间接合处的样式。
-
round
通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。 圆角的半径是线段的宽度。 -
bevel
在相连部分的末端填充一个额外的以三角形为底的区域, 每个部分都有各自独立的矩形拐角。 -
miter
(默认) 通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。
// linejoin()
var lineJoin = ['round', 'bevel', 'miter'];
ctx.lineWidth = 20;
ctx.strokeStyle = "red";
for (var i = 0; i < lineJoin.length; i++){
ctx.lineJoin = lineJoin[i];
ctx.beginPath();
ctx.moveTo(50, 50 + i * 50);
ctx.lineTo(100, 100 + i * 50);
ctx.lineTo(150, 50 + i * 50);
ctx.lineTo(200, 100 + i * 50);
ctx.lineTo(250, 50 + i * 50);
ctx.stroke();
}
虚线——setLineDash()和lineDashOffset
setLineDash()
方法和 lineDashOffset
属性实现虚线样式。
setLineDash
方法接受一个数组,来指定线段与间隙的交替;lineDashOffset
c属性设置起始偏移量。
//虚线
ctx.setLineDash([20, 5]); // [实线长度, 间隙长度]
ctx.lineDashOffset = -0;
ctx.strokeRect(50, 50, 210, 210);
填充和描边顺序不同导致结果不同
// 先填充后描边和先描边后填充
ctx.beginPath();
ctx.moveTo(30, 40);
ctx.lineTo(200, 30);
ctx.lineTo(200, 200);
ctx.closePath();
ctx.fillStyle = "blue";
ctx.fill();
ctx.strokeStyle="red";
ctx.lineWidth=20;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(230, 40);
ctx.lineTo(400, 30);
ctx.lineTo(400, 200);
ctx.closePath();
ctx.strokeStyle="red";
ctx.lineWidth=20;
ctx.stroke();
ctx.fillStyle="blue";
ctx.fill();
矩形——直接绘制图形
<canvas>
只支持一种原生的 图形绘制:矩形。所有其他图形都至少需要生成一种路径(path)。
矩形相关的方法有fillRect
、strokeRect
、clearRect
,其具体效果如下:
// 绘制矩形
//ctx.fillStyle="red";//#f00
ctx.fillStyle = "rgba(0, 0, 200, 0.3)";
ctx.fillRect(10,10,50,50);// 绘制实心的 x,y,width,height 的矩形
ctx.strokeRect(10, 60, 50, 50);// 绘制 x,y,width,height 的矩形边框
ctx.clearRect(20, 20, 30, 30);// 清除指定的矩形区域 完全透明。
圆弧
绘制圆弧有两种方法:arc()
和arcTo()
arc(x, y, r, startAngle, endAngle, anticlockwise)
: 以(x, y) 为圆心,以r 为半径,从 startAngle 弧度开始到endAngle弧度结束。anticlosewise:true——逆时针,false——顺时针(默认顺时针)。arcTo(x1, y1, x2, y2, radius)
: 根据给定的控制点和半径画一段圆弧,最后再以直线连接两个控制点。
角度和弧度
Math.PI=180度
angle=(Math.PI/180)*degrees //角度转弧度
degrees=(180/Math.PI)*angle //弧度转角度
arc绘制圆弧
- 圆弧或填充圆弧
let degreeToAngle = function (degree) {
return Math.PI / 180 * degree;
}
// arc 圆弧
let width=canvas.width,height=canvas.height;
// 圆点
let origin={
x:width/2,
y:height/2
}
// 圆弧 及圆弧填充
ctx.beginPath();
ctx.arc(origin.x,origin.y,100, degreeToAngle(0),degreeToAngle(70));
ctx.strokeStyle="blue";
ctx.lineWidth=4;
ctx.stroke();
ctx.fillStyle="red";
ctx.fill();
- 填充扇形或圆
// 填充扇形
ctx.beginPath();
ctx.moveTo(origin.x, origin.y); //起始位于圆心
// ctx.arc(origin.x,origin.y,100,degreeToAngle(0),degreeToAngle(110));
ctx.arc(origin.x, origin.y, 100, degreeToAngle(0), degreeToAngle(360));
ctx.closePath(); // 闭合路径 扇形边
ctx.lineWidth=6;
ctx.strokeStyle="blue";
ctx.stroke();
ctx.fillStyle="red";
ctx.fill();
arcTo绘制圆弧
// 路径当前位置为起始点
// x1, y1 为控制点1
// x2, y2 为控制点2
arcTo(x1, y1, x2, y2, r)
arcTo
中控制点的说明如下图:
arcTo()
可以理解为:绘制的弧形是由两条切线所决定(与两条直线相切,但由于半径参数,所以不会是真正的圆形)。第 1 条切线:起始点和控制点1决定的直线。
第 2 条切线:控制点1 和控制点2决定的直线。
ctx.beginPath();
// 起始点
ctx.moveTo(50, 50);
//参数1、2:控制点1坐标 参数3、4:控制点2坐标 参数4:圆弧半径
ctx.arcTo(200, 50, 200, 200, 100);
ctx.lineTo(200, 200); // 如果不画lineTo, 圆弧未必会画到控制点2 就结束
ctx.stroke();
ctx.beginPath();
ctx.rect(50, 50, 10, 10);
ctx.rect(200, 50, 10, 10);
ctx.rect(200, 200, 10, 10);
ctx.fill();
Transparency(透明度)设置
rgba颜色设置透明度
设置指定线条或填充的透明度
ctx.beginPath();
ctx.rect(20, 20, 100, 80);
ctx.strokeStyle = "rgba(0,255,0,.3)";
ctx.lineWidth = 20;
ctx.stroke();
ctx.fill();
globalAlpha全局透明度
globalAlpha = transparencyValue
: 设置 canvas 所有图形的透明度,有效值 0.0(完全透明)到 1.0(完全不透明),默认是 1.0。
ctx.beginPath();
ctx.rect(20,20,100,80);
ctx.strokeStyle="rgba(0,255,0,1)"
ctx.globalAlpha = 0.3;
ctx.lineWidth=20;
ctx.stroke();
ctx.fill();
综合示例
笑脸
let width=canvas.width,height=canvas.height;
// 圆点
let origin={
x:width/2,
y:height/2
}
// 笑脸
let face_radius=100;
ctx.beginPath();
ctx.arc(origin.x,origin.y, face_radius,0,2*Math.PI);
ctx.lineWidth = 2;
ctx.stroke();
//眉毛和眼睛
let per_dist= face_radius* 2 / 3;
let leftEyeOrigin={
x: origin.x - per_dist / 2,
y: origin.y - per_dist / 3
}
// 眉毛
ctx.beginPath();
ctx.arc(leftEyeOrigin.x, leftEyeOrigin.y, face_radius/3,degreeToAngle(225),degreeToAngle(315));
ctx.lineWidth = 2;
ctx.stroke();
let rightEyeOrigin = {
x: origin.x + per_dist / 2,
y: origin.y - per_dist / 3
}
ctx.beginPath();
ctx.arc(rightEyeOrigin.x, rightEyeOrigin.y, face_radius / 3, degreeToAngle(225), degreeToAngle(315));
ctx.lineWidth = 2;
ctx.stroke();
// 眼睛
ctx.beginPath();
ctx.arc(leftEyeOrigin.x,leftEyeOrigin.y,face_radius/5,0,2*Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(leftEyeOrigin.x, leftEyeOrigin.y, face_radius / 10, 0, 2 * Math.PI);
ctx.fillStyle = "#7b5028";
ctx.fill();
ctx.beginPath();
ctx.arc(rightEyeOrigin.x, rightEyeOrigin.y, face_radius / 5, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(rightEyeOrigin.x, rightEyeOrigin.y, face_radius / 10, 0, 2 * Math.PI);
ctx.fillStyle="#7b5028";
ctx.fill();
// mouth
mouth={
x:origin.x,
y:origin.y+ face_radius/10
}
ctx.beginPath();
ctx.arc(mouth.x,mouth.y,face_radius/2,degreeToAngle(30),degreeToAngle(150));
ctx.stroke();
月亮
let width=canvas.width,height=canvas.height;
// 圆点
let origin={
x:width/2,
y:height/2
}
// 填充整个画布
ctx.fillStyle="#000";
ctx.fillRect(0,0,canvas.width,canvas.height);
// 月亮
let earth_radius=100;
let moon_radius=80;
let moon_origin={
x:origin.x-60,
y:origin.y
}
ctx.beginPath();
ctx.arc(moon_origin.x,moon_origin.y,moon_radius,0,2*Math.PI);
ctx.fillStyle="yellow";
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(origin.x, origin.y, earth_radius, 0, 2 * Math.PI);
ctx.fillStyle="#000";
ctx.fill();
ctx.stroke();