初识canvas
英文中 Canvas 的意思是“画布”,Canvas 是 HTML5 中新出的一个元素,开发者可以在上面绘制一系列图形。Canvas 在 HTML 文件中的写法很简单:
canvas 的元素类型是 inline-block
不兼容IE9以下的浏览器。
canvas标签通常需要指定一个id属性
(用于js脚本引用),width
和 height
属性定义画布的大小。
画布大小设置,使用属性,不使用样式。
<canvas id="myCanvas" width="500" height="500"></canvas>
提示:你可以在HTML页面中使用多个
兼容性处理
HTML5 新增标签不兼容IE9以下的浏览器,当浏览器无法识别元素时,浏览器默认将该元素识别为 行内元素
即span
标签。行内元素没有高度,会导致高度塌陷,影响页面布局。我们的做法是在 canvas 元素外面套一层 div 设置相等的宽高。
<div style="width:500px;height:500px">
<canvas id="myCanvas" width="500" height="500">
您的浏览器不支持 canvas 元素,请升级至最新版本。
</canvas>
</div>
🖌️上下文对象
canvas 元素的图形绘制 依赖于它的绘图API,它被封装在一个context
上下文对象中。
// 获取canvas标签
let mycanvas = document.getElementById('mycanvas');
// 从canvas标签中获取上下文对象
let ctx =mycanvas.getContext('2d');
此处的参数 2d
是指平面绘图,3d 立体绘图的参数为 webgl
我们将 context 上下文对象理解为画笔🖌️。
在图形绘制之前,我们需要设置绘制样式(为画笔调个颜料🎨)。
ctx.fillStyle | 填充样式 |
---|---|
ctx.strokeStyle | 边框样式(线条样式) |
ctx.lineWidth | 边框宽度(边框样式额外的方法) |
ctx.fillStyle = 'red'; // 直接使用颜色名称
ctx.fillStyle = '#EEEEFF'; // 十六进制颜色值
ctx.fillStyle = 'rgb(1, 255, 255)'; // rgb
ctx.fillStyle = 'rgba(1, 255, 255, 0.8)'; // rgba
strokeStyle 同理
矩形
如果只是想绘制一个横平竖直的矩形,可以使用下面两个方法:
// 只描边
ctx.strokeRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);
// 只填充
ctx.fillRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);
以上单位都是像素,不需要px单位,number类型
擦除矩形 clearRect
当我们需要擦除矩形的一部分时,可以使用 ctx.clearRect
,它是canvas中唯一一个可以用于清除画布区域的函数,用于擦除画布内容:
ctx.clearRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);
// 清空画布方法:
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
圆弧绘制 arc和stroke
绘制圆弧的函数参数比较多:
ctx.arc(x, y, radius, startAngle, endAngle, [anticlockwise]);
// 圆心 x 坐标, 圆心 y 坐标, 半径, 起始角度, 终止角度, [是否为逆时针(true为逆时针,false为顺时针,默认false)]
在 JavaScript 中没有角度的概念,有的是弧度(弧的长度除以弧的半径得出的比值)。
360° = 2π弧度 ; 1弧度 = 360°/2π = 360°/2X31416 = 57.296° ;
// 1
ctx.arc(250, 250, 100, 0, 1 / 2 * Math.PI);
ctx.stroke();
// 2
ctx.arc(250, 250, 100, 0, 1 / 2 * Math.PI, true);
ctx.stroke();
arc() 只是设置圆弧,并不进行绘图。使用 stroke() 对设置对圆弧进行绘制。
问题:奇怪的线🧵
// 获取canvas标签
let mycanvas = document.getElementById('mycanvas');
// 从canvas标签中获取上下文对象
let ctx = mycanvas.getContext('2d');
ctx.arc(250, 250, 50, 0, 1 * Math.PI);
ctx.arc(250, 250, 50, 0, 1 * Math.PI, true);
ctx.stroke();
我们使用两个半圆拼接成一整个圆时,中间会多处一个横线。
即使我们将两个圆弧坐标分开还是一样。
ctx.arc(250, 300, 100, 0, 1 * Math.PI);
ctx.arc(250, 250, 100, 0, 1 * Math.PI, true);
这是因为:圆弧在 canvas 中是 线条路径 的分类。所有的线条路径会自动连接在一起。当前第一条线条结束时,它的结尾点会自动连接到下一个线条点开始点。
解决:路径闭合 beginPath closePath
如果不想让线条点路径自动连接,就需要手动设置路径闭合
ctx.beginPath(); // 开始路径
ctx.closePath(); // 结束路径
beginPath()
可以理解为从新的路径(或新起点)开始绘制图形,简单来说新图形的绘制过程不受画布已有图案影响。如不设置beginPath(),多次绘制时,已有图案会收到新图案的绘制样式影响。closePath()
是指闭合路径,即从当前画笔触点至最开始的画笔触点创建连接直线,形成闭合。
所以如果不需要中间的线我们可以这样写⬇️
ctx.beginPath();
ctx.arc(250, 250, 100, 0, 1 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 250, 100, 0, 1 * Math.PI, true);
ctx.stroke();
绘制动画
canvas的动画效果事实是通过周期性计时器setInterval
不断清空画布和重新绘制实现。
window.onload = function () {
// 获取canvas标签
let mycanvas = document.getElementById('mycanvas');
// 从canvas标签中获取上下文对象
let ctx = mycanvas.getContext('2d');
// 定义基础信息,坐标、偏移量
let x = 20,
y = 20,
mx = 2,
my = 3.5;
ctx.fillStyle = 'red';
function cricle(x, y) {
// 在每次绘图之前,清空画布
ctx.clearRect(0, 0, mycanvas.width, mycanvas.height);
ctx.beginPath();
ctx.arc(x, y, 20, 0, 2 * Math.PI);
ctx.fill();
}
setInterval(function () {
// 圆心是20
if (x + mx > mycanvas.width - 20 || x + mx < 20) mx = -mx;
if (y + my > mycanvas.height - 20 || y + my < 20) my = -my;
x += mx;
y += my;
cricle(x, y);
}, 20);
}
绘制线段
// 将画笔移动到某个位置
ctx.moveTo(x,y);
// 从上一个lineTo或moveTo点 绘制线段到本次坐标
ctx.lineTo(x,y);
每次画线都是从 moveTo
的点到 lineTo
的点。
如果没有 moveTo
那么第一次 lineTo
的效果和 moveTo
一样。
每次 lineTo
后如果没有 moveTo
,那么下次 lineTo
的开始点为前一次 lineTo
的结束点。
问题:奇怪的填充机制
我们使用 stroke
绘制一个多边形。
ctx.moveTo(200, 200);
ctx.lineTo(300, 300);
ctx.lineTo(200, 300);
ctx.lineTo(300, 200);
ctx.lineTo(200, 250);
ctx.lineTo(250, 350);
ctx.stroke();
改用 fill
填充会变成这样,是不是出乎意料了。中间两个闭合区域没有被填充。
这是因为空白区域进行了两次穿越。
奇数次穿越填充,偶数次穿越不填充。
canvas笔记-填充规则
文本绘制
font | 属性:设置文本大小和样式 | |
---|---|---|
textBaseline | 属性:修改文字垂直方向的对齐方式 | 对齐的时候是以绘制文本的y作为参照点进行对齐 “ top / middle / bottom" |
textAlign | 属性:修改文字水平方向的对齐方式 | 对齐的时候是以绘制文本的x作为参照点进行对齐 “ star / center / end “ |
fillText(text, x, y) | 方法:绘制实心的文本 | |
strokeText(text, x, y) | 方法:绘制空心的文本 |
// 1.绘制参考线
ctx.moveTo(0, mycanvas.height / 2);
ctx.lineTo(mycanvas.width, mycanvas.height / 2);
ctx.moveTo(mycanvas.width / 2, 0);
ctx.lineTo(mycanvas.width / 2, mycanvas.height);
ctx.stroke();
// 2.通过 font 属性设置文字的大小和样式
ctx.font = '400px 微软雅黑';
// 3.修改文字垂直方向的对齐方式
ctx.textBaseline = 'middle';
// 4.修改文字水平方向的对齐方式
ctx.textAlign = 'center';
// 5.绘制填充(实体)文本
ctx.fillText('码', mycanvas.width / 2, mycanvas.height / 2);
渐变
线性渐变 createLinearGradient
ctx.createLinearGradient(x1, y1, x2, y2)
通过x1, y1 / x2, y2 确定渐变的方向和渐变的范围
// 1.创建渐变范围
let grd = ctx.createLinearGradient(0, 0, 300, 300); // 线性渐变
// let grd = ctx.createRadialGradient(0, 0, 50, 300, 300, 100); // 径向渐变
// 2.在渐变中添加颜色
// 第一个参数是一个百分比 0~1
// 第二个参数是颜色
grd.addColorStop(0, 'blue');
grd.addColorStop(0.5, 'red');
grd.addColorStop(1, 'green');
// 3.将渐变背景颜色设置给对应的画笔。
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 500, 500);
// 对比矩形
ctx.strokeRect(0, 0, 300, 300);
径向渐变一言难尽……
不难发现,渐变设置的是一个范围渐变 而不是给Rect加上渐变,我们设置 (0, 0), (300, 300) 这个范围的渐变 因此 (300, 300), (500, 500) 之间是没有渐变的
径向渐变 createRadialGradient
ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
绘制图片 drawImage
Canvas 中绘制图片必须等图片完全加载成功后才能上屏,否则就会绘制失败。
ctx.drawImage('./一张图.jpg', 0, 0);
:::danger Uncaught TypeError: Failed to execute ‘drawImage’ on ‘CanvasRenderingContext2D’: The provided value is not of type ‘(CSSImageValue or HTMLCanvasElement or HTMLImageElement or HTMLVideoElement or ImageBitmap or OffscreenCanvas or SVGImageElement or VideoFrame)’. :::
因此正确的写法是:
绘制图片需要在图片加载完毕后执行
// 1.创建图片
let img = new Image();
// 或 let img = document.createElement('img');
// 2.加载图片
img.src = './一张图.jpg';
// 3.图片完全加载后进行绘制
img.onload = function () {
// 4.将加载的图片绘制的canvas上
ctx.drawImage(img, 0, 0);
}
drawImage参数补充
如果只有三个参数, 那么第一个参数就是需要绘制的图片,后面的两个参数是指定图片从什么位置开始绘制
ctx.drawImage(img对象, x, y) | 如果只有三个参数 , 那么第一个参数就是需要绘制的图片,后面的两个参数是指定图片从什么位置开始绘制 |
---|---|
ctx.drawImage(img对象, x, y, w, h) | 如果只有五个参数 , 那么第一个参数就是需要绘制的图片,第二和第三个参数是指定图片从什么位置开始绘制,第四、第五两个参数是指定图片需要拉伸到多大 |
ctx.drawImage(img对象, 裁切x, 裁切y, 裁切w, 裁切h, 绘制x, 绘制y, 绘制w, 绘制h) | 如果有九个参数 , 那么第一个参数就是需要绘制的图片,第2~3个参数指定图片上定位的位置,第4~5个参数指定从定位的位置开始截取多大的图片,第6~7个参数指定图片从什么位置开始绘制,最后的两个参数是指定图片需要拉伸到多大 |
变形
在 canvas 中所有的变形属性操作的都是坐标系,而不是图形!!!并且效果都是叠加的
位移 translate
用于移动 canvas 的原点坐标。
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 100);
ctx.translate(100, 100); // 位移 100, 100 可以理解为 将画笔移动到坐标100 100
ctx.fillStyle = 'yellow';
ctx.fillRect(0, 0, 100, 100);
ctx.translate(100, 100); // 多次使用位移 效果是叠加的
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 100);
缩放 scale
用于缩放画布的x轴和y轴的坐标。
// 参照线
ctx.strokeRect(500, 500, -450, -450);
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 100);
ctx.scale(.5, .5);
ctx.fillStyle = 'yellow';
ctx.fillRect(100, 100, 100, 100);
ctx.scale(.5, .5);
ctx.fillStyle = 'blue';
ctx.fillRect(200, 200, 100, 100);
scale(x, y) 参数表示缩放比例,类型是 number ,1=100%(不缩放),0.5=50%,2=200%,依次类推。
旋转 rotate
用于以原点为中心旋转 canvas 。
ctx.fillRect(200, 200, 100, 100);
ctx.rotate(45 * Math.PI / 180);
ctx.fillRect(200, 200, 100, 100);
自定义变形 transform
context.transform(a, b, c, d, e, f);
a: 水平缩放
b: 水平倾斜
c: 垂直倾斜
d: 垂直缩放
e: 水平位移
f: 垂直位移
初时状态是:context.transform(1, 0, 0, 1, 0, 0);
可用于初始化变形。
变形案例:文字倒影
// 位移 200, 200
ctx.translate(200, 200);
// 创建一段文本
ctx.font = '30px 微软雅黑';
ctx.fillStyle = 'orange';
ctx.textAlign = 'center';
ctx.fillText('你好世界', 0, 0);
// 垂直缩放设置为负数,实现文字上线颠倒🙃️
ctx.transform(1, 0, -1.5, -.7, 5, 5);
// 倒影文本
ctx.fillStyle = 'red';
ctx.fillText('你好世界', 0, 0);
变形状态保存恢复
用于保存和恢复变形状态
ctx.save(); // 保存
ctx.restore(); // 恢复
Canvas的状态存储在栈中,每次当 save()
方法被调用后,当前的状态就会被推送到栈中保存。类似于浏览器的历史记录功能。
每当遇到 save()
时就相当于吧当前的状态放到栈中,遇到 restore()
时相当于从栈中拿出来一个。
案例:
⏰时钟
🐍贪吃蛇
🖌️自定义画板
自定义画板源码
存在bug
点击画布 移动鼠标 鼠标移到画布外 松开鼠标 再移回画布 此时画的图形 会在下次绘画时消失