canvas
概述
使用
- canvas 只提供了一个画布,想要对画布进行操作的话,需要使用 js
- 所以可以把 canvas 分为两部分,一部分就是 canvas 标签,另一部分就是 js 部分的绘图程序,他依赖于绘图上下文
canvas 提供的属性和方法
属性
方法
getContext
- 一个参数,类型为 string
- '2d' 返回 2 维上下文
- 更多参数,查看 mdn
- 返回绘图上下文上下文
toDataURL
- 将 canvas 画布生成一个 url
- 两个参数,分别是生成的图像类型和图像质量
- 图像类型:'image/jpeg'或'image/png'
- 图像质量:0-1.0 之间
- 参数不填的话默认为('image/png',1)
- 返回的 url 是 base64 的
- 需要注意的是,如果是生成的是 jpeg,且透明背景,透明部分会是黑色的
const canvas = document.querySelector("canvas");
const img = document.querySelector("img");
const ctx = canvas.getContext("2d");
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(10, 100);
ctx.stroke();
img.src = canvas.toDataURL();
toBlob
- 创建一个表示当前 canvas 图像的 Blob
上传文件就靠它了
- 三个参数
- 回调
- 图像类型:'image/jpeg'或'image/png'
- 图像质量:0-1.0 之间
创建画布
- 创建了一个空白的画布
没有任何颜色(透明背景)的画布
- 因为是 h5 新增的,所以可能会有一些浏览器或设备不支持,这时候就会现实标签中间的文字
- 是一个行内块标签
<canvas id="canvas" width="300" height="300">不兼容</canvas>
设置画布大小
- 画布默认大小是 300pxX150px
- css 设置的宽高优先级高于行间设置的
不推荐
- 设置的宽高的时候,避免使用 css 来设置,会导致画布拉伸,尽量在行间设置
- 画布拉伸问题可以这样理解:
- canvas 大小可以划分为两个,一个为
标签大小(容器),一个为绘图表面大小(真实画布)
- 我们使用 css 来更改 canvas 的时候,修改的是容器大小,当浏览器发现画布大小和容器大小不一致的时候,会将画布拉伸到和容器大小一致
举个例子:将一个1kb的jpg图片打印一个楼那么大
- 这时候,会引起一些问题,常见的有:画质降低,图像拉伸
- 推荐两种设置宽高方法,注意:设置时候不要加单位
<style>
canvas {
width: 1000px;
height: 1000px;
}
</style>
<script>
canvas.style.width = "1000px";
</script>
<canvas width="1000" height="1000"></canvas>
<script>
canvas.width = 1000;
</script>
绘图上下文
2d 上下文
获取 2d 上下文
- 拿到 canvas 的 dom
- 通过 dom 上的 getContext 方法拿到上下文
let canvas = document.querySelector("#canvas");
let ctx = canvas.getContext("2d");
2d 上下文上的属性
canvas
- 指向 canvas 对象,可以拿到 canvas 行间属性的宽高,
不带单位
- 注意:拿到的是 cavas 画布的,而不是标签的
- canvas.width
- canvas.height
console.log(ctx.canvas.width);
console.log(canvas.width);
<style>
canvas {
width: 100px;
height: 100px;
}
</style>
<body>
<canvas id="canvas" width="300" height="200"></canvas>
</body>
<script>
let canvas = document.querySelector("#canvas");
let ctx = canvas.getContext("2d");
console.log(ctx.canvas.width);
</script>
fillstyle
font
- 指定 fillText 和 strokeText 的字体
globalAlpha
lineCap
- 设置绘制线段端点
- butt 默认
- round 圆角
- square 多出一节
lineWidth
lineJoin
- 设置线段交点
两线相交的点
- bevel 凸出一节,将连接处以直线磨平,呈现为一个平角
- round 圆角,将连接处添加弧线,呈显形式为圆角
- miter 默认值 将两条线段的边延伸,直至相交,常见为凸出一个尖
miterLimit
shadowBlur
shadowColor
shadowOffsetX
shadowOffsetY
strokeStyle
textAlign
- 设置 fillText 或 storkeText 设置文字的水平对齐方式
textBaseline
- 设置 fillText 或 storkeText 设置文字的垂直对齐方式
globalCompositeOperation
- 设置后续如何在画布上进行绘制
- 属性:
source-over 默认值 在目标图像上显示源图像。
source-atop 在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。
source-in 在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。
source-out 在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。
destination-over 在源图像上方显示目标图像。
destination-atop 在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。
destination-in 在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。
destination-out 在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。
lighter 混合图像,相交位置会融合
copy 只展示最后一次绘制的图像,前面的所有绘制都会被清除
xor 使用异或操作对源图像与目标图像进行组合。
- 刮刮卡demo
<head>
<style>
* {
margin: 0;
padding: 0;
}
.box {
height: 300px;
width: 100vw;
background: url(../精灵图.png);
}
</style>
</head>
<body>
<div class="box">
<canvas height="300"></canvas>
</div>
</body>
<script>
const div = document.querySelector('.box');
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const width = document.body.clientWidth;
canvas.width = width;
ctx.fillStyle = '#b0b0b0';
ctx.fillRect(0, 0, width, 300);
ctx.globalCompositeOperation = 'destination-out';
let isDown = false;
canvas.addEventListener('mousedown', () => {
isDown = true;
}, false);
canvas.addEventListener('mouseup', () => {
isDown = false;
}, false);
canvas.addEventListener('mousemove', (e) => {
if (!isDown) return;
const {
offsetX,
offsetY
} = e;
ctx.fillStyle = 'none';
ctx.beginPath();
ctx.arc(offsetX, offsetY, 20, 0, 2 * Math.PI);
ctx.fill();
}, false);
</script>
2d 上下文方法
strokeRect 方法
- 作用:描边矩形
- strokeRect(x,y,w,h)
- x 为起始 x 坐标,y 为起始 y 坐标,w 为宽度,h 为高度
- 默认为颜色为黑色,线宽为 1
ctx.strokeRect(0, 0, 20, 10);
fillRect 方法
- 填充矩形
- fillRect(x,y,w,h)
- x 为起始 x 坐标,y 为起始 y 坐标,w 为宽度,h 为高度
- 默认颜色为黑色
ctx.fillRect(50, 40, 40, 40);
clearRect 方法
- 作用:清除矩形
- clearRect(x,y,w,h)
- x 为起始 x 坐标,y 为起始 y 坐标,w 为宽度,h 为高度
ctx.fillStyle = "red";
ctx.strokeStyle = "red";
ctx.strokeRect(20, 20, 20, 20);
ctx.fillRect(50, 40, 40, 40);
ctx.clearRect(19, 19, 22, 22);
beginPath 方法
- 将当前路径之中的所有子路径清除掉,重新开始一条路径,新旧路径互不影响
ctx.strokeStyle = "blue";
ctx.fillStyle = "red";
ctx.lineWidth = '5';
ctx.beginPath();
ctx.rect(20, 30, 10, 100);
ctx.stroke();
ctx.beginPath();
ctx.rect(100, 150, 150, 100);
ctx.fill();
closePath 方法
- 显式封闭某某段开放路径
- 举例:半圆描边的时候,描边是没有链接到一块的,因为起始和结束点没连接到一起,使用这个方法可以将起点终点连接
arc 方法
- 创建圆、半圆、弧线
sAngle和eAngle最大为2π,也就是2*Math.PI
arc(x,y,r,sAngle,eAngle,counterclockwise)
- x为圆心起始x坐标,y为圆心起始y坐标,r为半径,sAngle为起始角度,eAngle为结束角度,
counterclockwise为true就是逆时针绘制,false就是顺时针绘制,默认为false
- 圆为2π,
- 补充知识
已知起始坐标(x1,y1),和结束坐标(x2,y2),求两点距离,方程式为 z²=(x1-x2)²+(y1-y2)²
ctx.strokeStyle = "blue";
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(200, 100, 100, 0, Math.PI);
ctx.closePath();
ctx.stroke();
ctx.fill();
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 10;
const start = {};
function addArc({
x,
y,
r
}) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.stroke();
};
canvas.addEventListener('mousedown', (e) => {
start['x'] = e.clientX;
start['y'] = e.clientY;
});
canvas.addEventListener('mouseup', (e) => {
const {
clientX,
clientY
} = e;
start['r'] = Math.sqrt(Math.pow(start['x'] - clientX, 2) + Math.pow(start['y'] - clientY,
2));
addArc(start);
});
- canvas规定,当时也arc向当前路径中添加子路径的时候,该方法会将上一条子路径的终点与当前圆弧的起点相连
ctx.beginPath();
ctx.arc(300, 190, 40, 0, Math.PI * 2);
ctx.arc(500, 190, 40, 0, Math.PI * 2);
ctx.stroke();
arcTo 方法
- 绘制一个圆角矩形
- 5个值 x,y,w,h,r
- 起始x坐标
- 起始y坐标
- 宽
- 高
- 圆角弧度,最小为0
const roundedRect = (x, y, w, h, r) => {
if (w > 0) ctx.moveTo(x + r, y)
else ctx.moveTo(x - r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
if (w > 0) {
ctx.arcTo(x, y, x + r, y, r);
} else {
ctx.arcTo(x, y, x - r, y, r);
};
};
const drawRounderRect = (strokeStyle, fillStyle, x, y, w, h, r) => {
ctx.beginPath();
roundedRect(x, y, w, h, r);
ctx.strokeStyle = strokeStyle;
ctx.fillStyle = fillStyle;
ctx.stroke();
ctx.fill();
};
drawRounderRect('blue', 'yellow', 50, 40, 100, 100, 50);
drawRounderRect('purple', 'green', 275, 40, -100, -100, 20);
drawRounderRect('blue', 'yellow', 300, 140, 100, -100, 30);
drawRounderRect('blue', 'yellow', 525, 140, -100, -100, 40);
stroke 方法
fill 方法
save 方法
rect 方法
- 建立一个矩形
rect(x,y,w,h)
- x是左上角x轴起始位置,y是左上角y轴起始位置,w是宽,h是高
ctx.strokeStyle = "blue";
ctx.lineWidth = '5';
ctx.beginPath();
ctx.rect(20, 30, 10, 100);
ctx.stroke();
restore 方法
drawImage 方法
- 插入图片
- 如果使用的是 img 标签,那么插入的步骤需要放在 image 的 onload 事件内
- 用法 1:
drawImage(image,x,y)
- image 可为 canvas 画布也可为 img 图片,x 为起点 x 坐标,y 为起点 y 坐标
- 用法 2:
drawImage(image,x,y,w,h)
- 用法 3:
drawImage(image,x1,y1,w1,h2,x,y,w,h)
- x1 为裁剪 x 起点,y1 为裁剪 y 起点,w1 为裁剪宽度,h2 为裁剪高度
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = "url";
img.onload = () => {
ctx.drawImage(img, 0, 0);
};
getImageData 方法
putImageDate 方法
createLinearGradient 线性渐变色
- 用法:
createLinearGradient(x,y,x1,y1)
- x 是开始 x 坐标,y 为开始 y 坐标,x1 为结束 x 坐标,y1 为结束 y 坐标
- 将当前渐变当做颜色,赋值给要填充的形状
- 配合实例上的 addColorStop 方法使用
- addColorStop(num,color);
- 作用:在指定范围内填充颜色
- num 为阈值,区间在 0-1 之间,可为小数
- 可以把它理解为一个色块,x 和 y 代表一个点,x1 和 y1 代表另一个点,水平或垂直就是直线,反之则是倾斜
- 还有一个概念,绘制的 x,y,x1,y1 这些坐标点,是针对于当前画布的,填充图像的时候,可以理解为是 ps 里面的蒙版,它只取对应坐标的颜色,给图像
- 超出 createLinearGradient 范围的颜色,为临近的实色
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.width);
gradient.addColorStop(0, "red");
gradient.addColorStop(1, "yellow");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
createRadialGradient 放射型渐变/圆型
- 用法:createRadialGradient(x,y,r,x1,y1,r1)
- x 为圆心起始 x 坐标,y 为圆心起始 y 坐标,r 为起始圆心半径,x1 为圆心结束 x 坐标,y1 为圆心结束 y 坐标,r1 为圆心结束半径
- 可以用来做圆形渐变,也可以用来做放射型渐变(如手电筒放射光)
const gradient = ctx.createRadialGradient(
canvas.width / 2,
canvas.height,
10,
canvas.width / 2,
0,
100
);
gradient.addColorStop(0, "blue");
gradient.addColorStop(0.25, "white");
gradient.addColorStop(0.5, "purple");
gradient.addColorStop(0.75, "red");
gradient.addColorStop(1, "yellow");
ctx.fillStyle = gradient;
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill();
createPattern 方法
- 创建填充背景图片
- 参数:createPattern(image,填充方式);
image 参数可为
视频、canvas、图片
- 填充方式:
- repeat 平铺
- repeat-y 平铺 y 轴
- repeat-x 平铺 x 轴
- no-repeat 不平铺
注意:如果后期想更新图片的话,需要再创建一个新的 createPattern 实例,只改变 src 的话不生效
image.src = "../企鹅.webp";
image.onload = () => {
var pattern = ctx.createPattern(image, "repeat");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fill();
};
使用阴影
- 浏览器绘制阴影需要先将阴影渲染到一个位图中,然后将位图与canvas进行图像合成,所以如果添加过多阴影可能会有性能问题
添加事件
- 与正常 dom 一致,可以使用 on 方法,也可以使用 addEventListener
canvas.onmousedown = () => {};
canvas.addEventListener("mousedown", () => {});
坐标转换
- 将事件对象上的坐标转换为 canvas 坐标
- 好处:比 offsetX 和 offsetY 更精准(是浮点型)
function windowToCanvas(canvas, x, y) {
var bbox = canvas.getBoundingClientRect();
return {
x: x - bbox.left * (canvas.width / bbox.width),
y: y - bbox.top * (canvas.height / bbox.height),
};
}
canvas.onmousedown = function (e) {
var loc = windowToCanvas(canvas, e.clientX, e.clientY);
};
离屏 canvas
- 指的是当前 canvas 没有在窗口中展示
- 作用:常用在不展示 canvas 的情况下使用 toDataURL 来生成图片
canvas.style.display = "none";
document.createElement("canvas");
非零环绕
- canvas会隐式的对当前视图绘制辅助线,该辅助线穿过当先视图(也可理解Wie穿过我们绘制的线段)
- 顺时针就+1,逆时针就-1,最终绘制不为0的地方
ctx.beginPath();
ctx.arc(300, 190, 150, 0, Math.PI * 2);
ctx.arc(300, 190, 100, 0, Math.PI * 2, true);
ctx.shadowColor = "rgba(0,0,0,0.8)";
ctx.shadowOffsetX = 12;
ctx.shadowOffsetY = 12;
ctx.shadowBlur = 15;
ctx.fill();
ctx.shadowColor = 'red';
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.stroke();
角度和弧度
- 角度就是两边中间的度数,弧度就是整个圆的弧度
- canvas 中有关角度的 api 都需要弧度来表示
- Math.sin()、Math.cos()、Math.tan()也采用的是弧度值
- 角度和弧度的计算公式:
360° = 2π弧度
1° = 180 / π * 1弧度
1弧度 = π / 180 * 1°
- π 约等于 3.14
- 计算得知:
1° = 57.324840764331206弧度,1弧度 = 0.017444444444444446°
正弦、余弦、正切函数
- 正弦( sine 简称 sin),余弦(cosine 简称 cos),正切(tangent 简称 tan)
- cos(n) = 邻边 / 斜边
- sin(n) = 对边 / 斜边
- tan(n) = 对边 / 邻边
已知两点坐标,求距离
- 已知(x1,y1)与(x2,y2)
- 方程式为 z²=(x1-x2)²+(y1-y2)²`