栅格
栅格将帮助我们理解在Canvas中绘制图形的位置坐标,以及移动缩放等相关数据计算的视感。请参考下图理解:
上图解析:
- canvas元素默认被网格所覆盖,一个网格单元相当于canvas元素中的一像素。
- 栅格的起点为左上角(坐标:0,0),所有的元素位置坐标都是相对于原点定位。
图形绘制原理
Canvas只支持两种形式的图形绘制:
- 矩形
- 路径:有一系列点连成的线段。
所有其他图形都是通过一条或多条路径组合而成的。
绘制矩形
canvas 提供了三种方法绘制矩形:
- 绘制一个填充矩形,语法:fillRect(x, y, width, height)
- 绘制一个矩形边框,语法:strokeRect(x, y, width, height)
- 清除指定矩形区域,让清除部分完全透明,语法:clearRect(x, y, width, height)
语法解析:
上面三个方法中都包含了相同的参数。
x 与 y 指定了在 canvas 画布上所绘制的矩形的左上角(相对于原点)的坐标。
width 和 height 设置矩形的尺寸。
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas绘制矩形</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
// 1. 先绘制一个边长为 100px 的黑色正方形
ctx.fillRect(25, 25, 100, 100);
// 2. 从上一行代码绘制的正方形的中心开始擦除了一个 60*60px 的正方形
ctx.clearRect(45, 45, 60, 60);
// 3. 在清除区域内绘制一个 50*50 的正方形边框
ctx.strokeRect(50, 50, 50, 50)
} else {
alert('您的浏览器不支持Canvas!');
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate;"></canvas>
</>
</html>
运行效果:
绘制路径
绘制原理
图形的基本元素是路径。路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。一个路径,甚至一个子路径,都是闭合的。具体绘制步骤如下:
- 第一步:使用beginPath()方法新建路径。
- 本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。
- 而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。
- 第二步:使用moveTo()指定路径起始位置。
- 第三步:调用相关函数指定绘制路径。
- 第四步:使用closePath()方法闭合路径。
- 该步骤不是必须的。
- 该方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的,即当前点为开始点,该函数什么也不做。、
- 注意:当调用 fill() 函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用 closePath() 函数。但是调用 stroke() 时不会自动闭合。
示例:绘制一个三角形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas绘制三角形</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(100, 75);
ctx.lineTo(100, 25);
ctx.fill();
// 可自行脑补一下绘制过程
} else {
alert('您的浏览器不支持Canvas!');
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate;"></canvas>
</body>
</html>
运行效果:
关键方法
moveTo(x, y) 移动笔触
该方法将笔触移动到指定的坐标 x 以及 y 上。
如果不理解,你可以想象一下在纸上作业,一支钢笔或者铅笔的笔尖从一个点到另一个点的移动过程。
当 canvas 初始化或者beginPath()调用后,你通常会使用moveTo()函数设置起点。我们也能够使用moveTo()绘制一些不连续的路径。
代码示例:画一个笑脸
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas绘制笑脸</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
// 画一个圆,代表脸的轮廓
ctx.arc(75, 75, 50, 0, Math.PI * 2, true);
// 画口
ctx.moveTo(110, 75);
ctx.arc(75, 75, 35, 0, Math.PI, false);
// 画左眼
ctx.moveTo(65, 65)
ctx.arc(60, 65, 5, 0, Math.PI * 2, true)
// 画右眼
ctx.moveTo(95, 65)
ctx.arc(90, 65, 5, 0, Math.PI * 2, true)
ctx.stroke();
} else {
alert('您的浏览器不支持Canvas!');
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate;"></canvas>
</body>
</html>
运行效果:
lineTo(x, y) 绘制直线
该方法绘制一条从当前位置到指定 x 以及 y 位置的直线。
该方法有两个参数:x 以及 y,代表坐标系中直线结束的点。开始点和之前的绘制路径有关,之前路径的结束点就是接下来的开始点,以此类推。开始点也可以通过moveTo()函数改变。
代码示例:两个三角形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas绘制三角形</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
// 填充三角形
ctx.moveTo(25, 25);
ctx.lineTo(105, 25);
ctx.lineTo(25, 105);
ctx.fill();
// 描边三角形
ctx.beginPath();
ctx.moveTo(125, 125);
ctx.lineTo(125, 45);
ctx.lineTo(45, 125);
ctx.closePath();
ctx.stroke();
} else {
alert('您的浏览器不支持Canvas!');
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate;"></canvas>
</body>
</html>
运行效果:
arc(x, y, radius, startAngle, endAngle, anticlockwise) 绘制圆或圆弧
画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针)来生成。
该方法有6个参数:
- x,y为绘制圆弧所在圆上的圆心坐标;
- radius为半径;
- startAngle以及endAngle参数用弧度定义了开始以及结束的弧度。这些都是以 x 轴为基准。
- 参数anticlockwise为一个布尔值。为 true 时,是逆时针方向,否则顺时针方向。
代码示例:圆和圆弧综合示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas绘制圆和圆弧综合示例</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 3; j++) {
ctx.beginPath();
const x = 45 + j * 70; // x 坐标值
const y = 45 + i * 70; // y 坐标值
const radius = 20; // 圆弧半径
const startAngle = 0; // 开始点
const endAngle = Math.PI + (Math.PI * j) / 2; // 结束点
const anticlockwise = i % 2 == 0 ? false : true; // 顺时针或逆时针
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
if (i > 1) {
ctx.fill();
} else {
ctx.stroke();
}
}
}
} else {
alert('您的浏览器不支持Canvas!');
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate;"></canvas>
</body>
</html>
运行效果:
二次贝塞尔曲线及三次贝塞尔曲线
贝塞尔曲线是十分有用的路径类型。二次及三次贝塞尔曲线都十分有用,一般用来绘制复杂有规律的图形。
二者名称听起来很相似,具体区别请看下图:
二次贝塞尔曲线有一个开始点(蓝色)、一个结束点(蓝色)以及一个控制点(红色),
而三次贝塞尔曲线有两个控制点。
绘制方法:
quadraticCurveTo(cp1x, cp1y, x, y)
该方法用来绘制二次贝塞尔曲线,cp1x,cp1y 为一个控制点,x,y 为结束点。bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
该方法用来绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。
示例一:使用二次贝塞尔曲线绘制对话气泡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas使用二次贝塞尔曲线绘制对话气泡</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 25);
ctx.quadraticCurveTo(25, 25, 25, 62.5);
ctx.quadraticCurveTo(25, 100, 50, 100);
ctx.quadraticCurveTo(50, 120, 30, 125);
ctx.quadraticCurveTo(60, 120, 65, 100);
ctx.quadraticCurveTo(125, 100, 125, 62.5);
ctx.quadraticCurveTo(125, 25, 75, 25);
ctx.stroke();
} else {
alert('您的浏览器不支持Canvas!');
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate;"></canvas>
</body>
</html>
运行效果:
示例二:使用三次贝塞尔曲线绘制心形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas使用三次贝塞尔曲线绘制心形</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 40);
ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
ctx.fill();
} else {
alert('您的浏览器不支持Canvas!');
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate;"></canvas>
</body>
</html>
运行效果:
rect(x, y, width, height) 绘制矩形路径
该方法将一个矩形路径增加到当前路径上。
该方法有4个参数:x,y 为左上角坐标,width 以及 height 为矩形的宽和高。
注意:当该方法执行的时候,moveTo() 方法自动设置坐标参数(0,0)。也就是说,当前笔触自动重置回默认坐标。
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas添加矩形路径</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 40);
ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
ctx.fill();
// 将矩形添加到路径中
ctx.beginPath();
ctx.rect(150, 150, 100, 100);
ctx.stroke();
ctx.beginPath();
ctx.rect(160, 160, 80, 80);
ctx.fill();
} else {
alert('您的浏览器不支持Canvas!');
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate;"></canvas>
</body>
</html>
运行效果:
组合使用示例
现在阅读起来可能比较复杂,难点在于贝塞尔曲线的应用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas 组合使用</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
drawRoundedRect(ctx, 12, 12, 250, 250, 15);
drawRoundedRect(ctx, 19, 19, 236, 236, 9);
drawRoundedRect(ctx, 53, 53, 49, 33, 10);
drawRoundedRect(ctx, 53, 119, 49, 16, 6);
drawRoundedRect(ctx, 135, 53, 49, 33, 12);
drawRoundedRect(ctx, 135, 119, 65, 49, 10);
ctx.beginPath();
ctx.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7, false);
ctx.lineTo(32, 37);
ctx.fill();
for (var i = 0; i < 8; i++) {
ctx.fillRect(44 + i * 16, 35, 4, 4);
}
for (i = 0; i < 6; i++) {
ctx.fillRect(115, 51 + i * 16, 4, 4);
}
for (i = 0; i < 8; i++) {
ctx.fillRect(51 + i * 16, 99, 4, 4);
}
ctx.beginPath();
ctx.moveTo(83, 116);
ctx.lineTo(83, 102);
ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
ctx.lineTo(111, 116);
ctx.lineTo(106.333, 111.333);
ctx.lineTo(101.666, 116);
ctx.lineTo(97, 111.333);
ctx.lineTo(92.333, 116);
ctx.lineTo(87.666, 111.333);
ctx.lineTo(83, 116);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(91, 96);
ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
ctx.moveTo(103, 96);
ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
ctx.fill();
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(101, 102, 2, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.arc(89, 102, 2, 0, Math.PI * 2, true);
ctx.fill();
} else {
alert('您的浏览器不支持Canvas!');
}
}
function drawRoundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y + height - radius);
ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
ctx.lineTo(x + width - radius, y + height);
ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
ctx.lineTo(x + width, y + radius);
ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
ctx.lineTo(x + radius, y);
ctx.quadraticCurveTo(x, y, x, y + radius);
ctx.stroke();
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate"></canvas>
</body>
</html>
运行效果:
Path2D对象
为了简化代码和提高性能,Path2D对象已可以在较新版本的浏览器中使用,用来缓存或记录绘画命令,这样你将能快速地回顾路径。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas Path2D</title>
<style>
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
var rectangle = new Path2D();
rectangle.rect(10, 10, 50, 50);
var circle = new Path2D();
circle.moveTo(125, 35);
circle.arc(100, 35, 25, 0, 2 * Math.PI);
ctx.stroke(rectangle);
ctx.fill(circle);
} else {
alert('您的浏览器不支持Canvas!');
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvas" width="300" height="300" style="background-color: #f3f4f8; color: chocolate"></canvas>
</body>
</html>
运行效果: