canvas和普通的html标签用法差很少,canvas拥有width(默认300)和height(默认150)两个属性,有时出现模糊扭曲的状况极可能是忘记设置width和height了,并非元素的style中的width和height。javascript
<canvas id="tutorial" width="300" height="150"></canvas>
复制代码
经过元素已经建立好一个固定大小的绘画区域,经过元素的getContext('2d')得到一个CanvasRenderingContext2D的绘画上下文。也能够经过元素是否有getContext方法来判断浏览器是否支持cnavas(都2021年了!)。画布的方向符合浏览器的滚动条属性,左上为 (0,0),向下是无穷大的Y轴,向右是无穷大的X轴。设置元素对应的width和height后坐标中的一格表明画布的一个像素。css
const canvas = document.getElementById('tutorial');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
// drawing code here
} else {
// canvas-unsupported code here
}
复制代码
canvas只支持两个基本形状: 矩形和路径(由直线链接的点列表)。全部其余形状都必须经过组合一个或多个路径来建立。html
x 和 y 指定矩形左上角在画布上的位置(相对于原点)。width和height提供了矩形的大小。java
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>canvas</title>
<style> body { background: #f5f5f5; } canvas { border: 1px solid #1890ff; } </style>
</head>
<body>
<canvas id="tutorial" width="520" height="200"></canvas>
</body>
<script> const draw = ctx => { ctx.fillRect(10, 10, 300 - 20, 150 - 20); ctx.clearRect(20, 20, 300 - 40, (150 - 40) / 2); ctx.strokeRect(30, 30, 30, 30); } const render = () => { const canvas = document.getElementById('tutorial'); if (canvas.getContext) { const ctx = canvas.getContext('2d'); draw(ctx); } else { // canvas-unsupported code here } } window.setTimeout(render, 0); </script>
</html>
复制代码
const draw = (ctx) => {
ctx.beginPath();
ctx.moveTo(10, 20);
ctx.lineTo(10, 80);
ctx.lineTo(40, 50);
ctx.fill();
ctx.lineTo(150, 50);
ctx.stroke();
ctx.closePath();
}
复制代码
const draw = (ctx) => {
ctx.beginPath();
ctx.moveTo(10, 20);
ctx.lineTo(10, 80);
ctx.lineTo(40, 50);
ctx.stroke();
ctx.lineTo(150, 50);
ctx.stroke();
ctx.closePath();
}
复制代码
const draw = (ctx) => {
ctx.beginPath();
ctx.moveTo(10, 20);
ctx.lineTo(10, 80);
ctx.lineTo(40, 50);
ctx.stroke();
ctx.closePath();
ctx.lineTo(150, 50);
ctx.stroke();
ctx.closePath();
}
复制代码
const draw = (ctx) => {
ctx.beginPath();
ctx.moveTo(10, 20);
ctx.lineTo(10, 80);
ctx.lineTo(40, 50);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(10, 20);
ctx.lineTo(150, 50);
ctx.stroke();
ctx.closePath();
}
复制代码
经过quadraticCurveTo、bezierCurveTo、arc、arcTo、ellipse能够绘制一些奇奇怪怪的图形chrome
样式属性在设置以后会应用到后续的全部图形绘制上,对于不一样样式的每一个形状,都须要从新分配canvas
const draw = ctx => {
ctx.beginPath()
ctx.lineWidth = 10
ctx.strokeStyle = '#1890ff'
ctx.moveTo(0, 20);
ctx.lineTo(200, 20);
ctx.setLineDash([20, 10])
ctx.stroke();
ctx.beginPath()
ctx.moveTo(0, 40);
ctx.lineWidth = 10
ctx.setLineDash([])
ctx.lineCap = 'round' // 两端为圆角
ctx.lineJoin = 'round' // 线段交叉处为圆角
ctx.globalAlpha = 0.5
ctx.lineTo(200, 40);
ctx.lineTo(100, 80);
ctx.stroke();
ctx.closePath()
}
复制代码
渐变色api
上述函数调用后会生成CanvasGradient对象,经过对象的addColorStop(offset, color)方法生成过渡色,offset取值为0~1,表示渐变的起始位置数组
const draw = ctx => {
const gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, 'green');
gradient.addColorStop(.5, 'white');
gradient.addColorStop(1, 'pink');
ctx.fillStyle = gradient;
ctx.fillRect(10, 10, 200, 100);
}
复制代码
const draw = ctx => {
const gradient = ctx.createRadialGradient(100, 100, 10, 100, 100, 70);
gradient.addColorStop(0, 'green');
gradient.addColorStop(.5, 'white');
gradient.addColorStop(1, 'pink');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 200, 200);
}
复制代码
const draw = ctx => {
const gradient = ctx.createConicGradient(0, 100, 100);
gradient.addColorStop(0, 'green');
gradient.addColorStop(.5, 'white');
gradient.addColorStop(1, 'pink');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 200, 200);
}
复制代码
const draw = ctx => {
ctx.strokeStyle = "#1890ff";
ctx.beginPath();
ctx.moveTo(0,48);
ctx.lineTo(500,48);
ctx.stroke();
ctx.closePath();
ctx.font = "48px serif";
ctx.textBaseline = "middle";
ctx.strokeText("Hello world", 0, 48);
}
复制代码
const draw = ctx => {
ctx.fillStyle = "#1890ff";
ctx.shadowOffsetX = 8;
ctx.shadowOffsetY = 8;
ctx.shadowBlur = 2;
ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
ctx.font = "32px Times New Roman";
ctx.fillText("Hello World", 0, 32);
ctx.fillRect(0, 64, 50, 50)
}
复制代码
若调用 drawImage 时,图片没装载完,那什么都不会发生(在一些旧的浏览器中可能会抛出异常)。所以应该用load事件来保证不会在加载完毕以前使用这个图片。浏览器
const draw = ctx => {
const img = new Image();
img.onload = function () {
ctx.drawImage(img, 0, 0);
}
img.src = 'https://sf3-ttcdn-tos.pstatp.com/img/mosaic-legacy/3795/3033762272~300x300.image';
}
复制代码
const draw = ctx => {
const img = new Image();
img.onload = function () {
ctx.drawImage(img, 0, 0);
ctx.drawImage(img, 200, 0, 90, 180);
}
img.src = 'https://sf3-ttcdn-tos.pstatp.com/img/mosaic-legacy/3795/3033762272~300x300.image';
}
复制代码
const draw = ctx => {
const img = new Image();
img.onload = function () {
ctx.drawImage(img, 0, 0);
ctx.drawImage(img, 0, 0, 90, 90, 200, 0, 180, 180);
}
img.src = 'https://sf3-ttcdn-tos.pstatp.com/img/mosaic-legacy/3795/3033762272~300x300.image';
}
复制代码
在绘制复杂图形时必不可少的两个方法:markdown
canvas 的状态就是当前画面应用的全部样式和变形的一个快照。canvas状态存储在栈中,每当 save 方法被调用后,当前的状态就被推送到栈中保存;每一次调用 restore 方法,上一个保存的状态就从栈中弹出,全部设定都恢复。一个绘画状态包括:
仅单独调用方法时不能体现这两个方法的用处,仅仅一个压栈和出栈的过程:
// 仅调用save
const draw = ctx => {
ctx.fillStyle = "#1890ff";
ctx.fillRect(0, 0, 150, 150);
ctx.save();
}
// 仅调用restore
const draw = ctx => {
ctx.fillStyle = "#1890ff";
ctx.fillRect(0, 0, 150, 150);
ctx.restore();
}
// 仅调用save和restore
const draw = ctx => {
ctx.fillStyle = "#1890ff";
ctx.fillRect(0, 0, 150, 150);
ctx.save();
ctx.restore();
}
/* 三种方式都是相同的结果:填充一个蓝色的矩形 */
复制代码
当调用save方法后当前画笔的状态不会被清除,仍然和save前的同样,后续改了状态后,调用restore后会将栈中的状态取出并自动改变当前画笔的状态
const draw = ctx => {
ctx.fillStyle = "#1890ff"; // 设置画笔颜色为蓝色
ctx.fillRect(0, 0, 30, 30); // 第一个矩形绘制
ctx.save(); // 把蓝色(#1890ff)压入栈中
ctx.fillRect(40, 0, 30, 30); // 第二个矩形绘制
ctx.fillStyle = "#389E0D"; // 设置画笔颜色为绿色
ctx.fillRect(80, 0, 30, 30); // 第三个矩形绘制
ctx.save(); // 把绿色(#389E0D)压入栈中
ctx.fillStyle = "#F5222D"; // 设置画笔颜色为红色
ctx.restore(); // 弹出第二次压栈的状态
ctx.fillRect(120, 0, 30, 30); // 第四个矩形绘制
ctx.restore(); // 弹出第一次压栈的状态
ctx.fillRect(160, 0, 30, 30); // 第五个矩形绘制
}
复制代码
scale(x, y) 方法能够缩放画布的水平和垂直的单位。两个参数都是实数,能够为负数,x 为水平缩放因子,y 为垂直缩放因子,若是比1小,会缩小图形,若是比1大会放大图形。默认值为1,为实际大小。
const draw = (ctx) => {
ctx.fillRect(0, 0, 50, 50);
ctx.scale(2,2);
ctx.fillRect(50, 0, 50, 50);
}
复制代码
能够看到不只是目标的长宽发生了缩放,距离原点(0,0)的偏离也出现了相应的缩放。
注意缩放的数值若是为负数,至关因而对X、Y轴作镜像的翻转,且绘制的窗宽的方向也变成了负数
const draw = (ctx) => {
ctx.fillRect(0, 0, 50, 50); // 宽度为正数,从左往右计算
ctx.fillRect(100, 50, -50, 50); // 宽度为负数,从右往左计算
ctx.scale(-1,1); // 原点左侧为0到正无穷,右侧为0到负无穷,Y轴没作缩放
ctx.fillRect(-50, 100, 50, 50); // 宽度为正是数,从右往左计算
ctx.fillRect(-50, 150, -50, 50); // 宽度为负数,从左往右计算
}
复制代码
translate(x, y)方法接受两个参数。x 是左右偏移量,y 是上下偏移量。和其余属性同样,一旦设置之后对后续操做都是叠加效应,每次移动是基于上一次移动的基础之上的,并不会一直相对于原点。
const draw = (ctx) => {
ctx.fillRect(0, 0, 20, 20);
ctx.translate(30, 0)
ctx.fillRect(0, 0, 20, 20);
ctx.translate(0, 30); // 只平移了Y值,而X值仍然是以前的30
ctx.fillRect(0, 0, 20, 20);
ctx.translate(30, 0); // 是在以前(30,0)+(0,30)的基础上再加(30,0),至关于相对于原点的位移是(60,30)
ctx.fillStyle = '#1890ff';
ctx.fillRect(0, 0, 20, 20);
}
复制代码
能够配合使用save和restore方法来使位移一直相对于原点,更符合咱们的计算
const draw = (ctx) => {
ctx.save(); // 绘制前保存一个原始的状态
ctx.fillRect(0, 0, 20, 20);
ctx.restore(); // 绘制后还原到原始的状态
ctx.save();
ctx.translate(20, 20)
ctx.fillRect(0, 0, 20, 20);
ctx.restore();
ctx.save();
ctx.translate(40, 40)
ctx.fillRect(0, 0, 20, 20);
ctx.restore();
ctx.save();
ctx.translate(60, 60)
ctx.fillRect(0, 0, 20, 20);
ctx.restore();
}
复制代码
rotate(angle)方法只接受一个参数:旋转的角度(angle),它是以原点为中心顺时针方向的角度,以 弧度 为单位的值。
const draw = (ctx) => {
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 0);
ctx.stroke();
ctx.save();
ctx.rotate(2 * Math.PI / 360 * 30)
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 0);
ctx.stroke();
ctx.restore();
ctx.save();
ctx.rotate(2 * Math.PI / 360 * 60)
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 0);
ctx.stroke();
ctx.restore();
ctx.save();
ctx.rotate(2 * Math.PI / 360 * 90)
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 0);
ctx.stroke();
ctx.restore();
}
复制代码
若是要绕图形的中心旋转,须要经过旋转角度反推计算平移的距离
const draw = (ctx) => {
ctx.fillRect(0, 0, 40, 40);
ctx.save();
ctx.translate(80, 40)
ctx.globalAlpha = 0.5
ctx.fillRect(0, 0, 40, 40); // 图形2, 位移以后的参考坐标
ctx.rotate(2 * Math.PI / 360 * 45)
ctx.fillStyle = "#1890ff"; // 蓝色
ctx.fillRect(0, 0, 40, 40);
ctx.restore();
// 若是想在图形2的位置中心旋转,须要动态计算水平偏移坐标
ctx.save();
ctx.globalAlpha = 0.5
ctx.fillStyle = "#FFEC3D"; // 黄色
const R = 20; // 2分之一的边长
const r = 60; // 旋转角度
const diagonalR = Math.sqrt(Math.pow(R, 2) + Math.pow(R, 2)) // 2分之一的对角线长
const translateX = 80 + R - diagonalR * Math.sin(2 * Math.PI / 360 * (45 - r));
const translateY = 40 + R - diagonalR * Math.cos(2 * Math.PI / 360 * (45 - r));
ctx.translate(translateX, translateY)
ctx.rotate(2 * Math.PI / 360 * r)
ctx.fillRect(0, 0, 2 * R, 2 * R);
ctx.restore();
}
复制代码