canvas是html5提供的独立画布,能够更绘制更加丰富的图形和2d动画;并且由于canvas是独立画布,不会引起大范围的回流,因此性能上也更加出色。以下是canvas的入门学习,目标是能够使用canvas绘制简单的图形和动画
提供画布
canvas可以像普通的html元素一样设置样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas id="canvas" width="300" height="200"></canvas>
</body>
</html>
这样就提供了一个宽300,高200,带边框的画布
canvas的尺寸说明
canvas的宽高默认是300 * 150,也可以通过width/height修改,它对应屏幕上的300px * 150px
<canvas id="canvas" width="300" height="150"></canvas>
也可以设置canvas的宽高和css不一致,画布宽高500300,而css像素是250150,后续绘图使用的是canvas的宽高,再转换到页面显示会不一样,最好保持二者一致
<canvas id="myCanvas" width="500" height="300" style="width: 250px; height: 150px;"></canvas>
绘制图形
canvas只支持绘制矩形和路径( 由一系列点连成的线段 ) ,其他所有类型的图形都是通过一条或者多条路径组合而成。
- canvas.getContext,用来判断浏览器是否支持canvas
- canvas.getContext('2d'),用来获取2d上下文,使用这个对象来绘制图形
const canvas = document.getElementById("canvas");
//
if (canvas.getContext) {}
绘制矩形
- fillRect(x, y, width, height),用于填充矩形
-
- x/y,矩形做上点的坐标
- width/height,矩形宽高
- 矩形的样式由当前的fillStyle决定
- strokeRect(x, y, width, height),用于绘制矩形边框
-
- 边框的样式由当前stokeStyle决定
- clearRect(x, y, width, height),用于清空画布
function draw() {
const canvas = document.getElementById("canvas");
if (canvas.getContext) {
const ctx = canvas.getContext("2d");
// 默认填充黑色
ctx.fillRect(25, 25, 100, 100);
ctx.clearRect(45, 45, 60, 60);
ctx.strokeRect(50, 50, 50, 50);
}
}
draw();
效果如下:
绘制路径
- beginPath,新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
- closePath,闭合路径之后图形绘制命令又重新指向到上下文中。
- stroke,通过线条来绘制图形轮廓。
- fill,通过填充路径的内容区域生成实心的图形。
- moveTo(x, y),把画笔移动到坐标(x, y)处
- lineTo(x, y),绘制一条从当前位置到指定 x 以及 y 位置的直线。
- arc(x, y, radius, startAngle, endAngle, anticlockwise)
-
- 画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针)来生成。
- 路径中还有贝塞尔曲线等复杂的路径,暂时跳过
通过上面这些api,就可以开始画一些简单的图形了。
画两个三角形
function draw() {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var 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();
}
}
效果如下:
设置样式
- fillStyle = color,填充颜色
- strokeStyle = color,描边颜色
- globalAlpha,设置透明度
- lineWidth = value,设置线条宽度
- lineType = type,设置线条末端样式
- 阴影/渐变等更多样式,请自行查阅文档
使用fillStyle绘制调色板
function draw() {
var ctx = document.getElementById("canvas").getContext("2d");
for (var i = 0; i < 6; i++) {
for (var j = 0; j < 6; j++) {
ctx.fillStyle =
"rgb(" +
Math.floor(255 - 42.5 * i) +
"," +
Math.floor(255 - 42.5 * j) +
",0)";
ctx.fillRect(j * 25, i * 25, 25, 25);
}
}
}
效果如下:
绘制线条
function draw() {
var ctx = document.getElementById("canvas").getContext("2d");
for (var i = 0; i < 10; i++) {
ctx.lineWidth = 1 + i;
ctx.beginPath();
ctx.moveTo(5 + i * 14, 5);
ctx.lineTo(5 + i * 14, 140);
ctx.stroke();
}
}
绘制文本
- font,指定文本样式
- textAlign = value,对齐方式
- direction = value,文本方向
- fillText(text, x, y [, maxWidth])
-
- 在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的。
- strokeText(text, x, y [, maxWidth])
-
- 在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的。
function draw() {
var ctx = document.getElementById("canvas").getContext("2d");
ctx.font = "48px serif";
ctx.fillText("Hello world", 10, 50);
}
变形Transform
- save(),保存canvas的所有状态,入栈
- restore(),恢复栈中上一个canvas的状态
在做变形前,最好都保存一下当前的状态
- translate(x, y),把canvas往右下角移动一定距离
- rotate(angle),这个方法只接受一个参数:旋转的角度 (angle),它是顺时针方向的,以弧度为单位的值。旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到
translate方法。 - scale(x, y),缩放画布,比1大放大图形,比1小缩小图形
一个移动的案例
save/restore之后,每次都是在原点的基础上进行移动
function draw() {
var ctx = document.getElementById("canvas").getContext("2d");
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
ctx.save();
ctx.fillStyle = "rgb(" + 51 * i + ", " + (255 - 51 * i) + ", 255)";
ctx.translate(10 + j * 50, 10 + i * 50);
ctx.fillRect(0, 0, 25, 25);
ctx.restore();
}
}
}
小球坠落动画
小球自由坠落
绘制小球
<canvas id="canvas" width="600" height="300"></canvas>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var ball = {
x: 100,
y: 100,
radius: 25,
color: "blue",
draw: function () {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
},
};
ball.draw();
完成小球的绘制:
增加动画
- window.requestAnimationFrame(callback),可以让浏览器重新绘制前执行一次callback,这个函数是一次性的,因此要在callback中递归的调用,才会有连续动画的效果。
-
- 使用cancelAnimationFrame取消
- 增加vy/vx,控制坐标的增量,每次执行清空画布、渲染、修改左边三步
-
- 遇到边界,需要反转vy/vx
- vy有加速度,更真实
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
canvas {
display: block;
margin: 200px auto;
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="300"></canvas>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let raf;
const ball = {
x: 100,
y: 100,
// 要改变的距离
vx: 5,
vy: 2,
radius: 25,
color: "blue",
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
},
};
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ball.draw();
// 每次距离改变这么多
ball.x += ball.vx;
ball.y += ball.vy;
// 下降时,vy为正,vy逐渐变大;
// 上升时,vy为负,vy逐渐变小;符合物体坠落、弹跳的运动规律
ball.vy *= 0.99;
ball.vy += 0.25;
// 边界处理,遇到边界,方向转向
if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
ball.vy = -ball.vy;
}
if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
ball.vx = -ball.vx;
}
// 空闲时执行,一次性,必须递归调用
raf = window.requestAnimationFrame(draw);
}
canvas.addEventListener("mouseover", (e) => {
raf = window.requestAnimationFrame(draw);
});
canvas.addEventListener("mouseout", (e) => {
window.cancelAnimationFrame(raf);
});
ball.draw();
</script>
</body>
</html>
增加长尾效果
之前每次绘制时使用clearRect清空画布,现在使用半透明的颜色遮盖画布,没遮盖一次,之前的小球就会变浅一点,直到完全消失。
ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
ctx.fillRect(0, 0, canvas.width, canvas.height);