一. 基本概念
1.<canvas>
元素:
-
是一个双标签元素,默认大小为 300px × 150px。
-
通过
width
和height
属性可以设置画布的大小。 -
Canvas 的默认分辨率较低,可以通过 CSS 和
canvas.width
/canvas.height
调整。2.渲染上下文(Rendering Context):
-
<canvas>
本身只是一个容器,需要通过 JavaScript 获取其渲染上下文来进行绘图。 -
常见的上下文类型:
-
2D 上下文:用于绘制 2D 图形(
CanvasRenderingContext2D
)。 -
WebGL 上下文:用于绘制 3D 图形(基于 OpenGL ES)。
-
-
二. 2D 绘图基础
1.获取上下文:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
2.绘制基本形状:
-
矩形:
ctx.fillRect(x, y, width, height); // 填充矩形
ctx.strokeRect(x, y, width, height); // 描边矩形
ctx.clearRect(x, y, width, height); // 清除矩形区域
路径:
ctx.beginPath(); // 开始路径
ctx.moveTo(x, y); // 移动到起点
ctx.lineTo(x, y); // 画线到某点
ctx.arc(x, y, radius, startAngle, endAngle); // 画弧
ctx.closePath(); // 闭合路径
ctx.stroke(); // 描边路径
ctx.fill(); // 填充路径
3.颜色和样式:
- 设置填充颜色:
ctx.fillStyle = 'red';
-
设置描边颜色:
ctx.strokeStyle = 'blue';
-
设置线宽:
ctx.lineWidth = 5;
-
4.文本:
ctx.font = '20px Arial'; // 设置字体
ctx.fillText('Hello Canvas', x, y); // 填充文本
ctx.strokeText('Hello Canvas', x, y); // 描边文本
5.图像:
const img = new Image();
img.src = 'image.png';
img.onload = () => {
ctx.drawImage(img, x, y, width, height); // 绘制图像
};
三. 高级特性
1.变换:
-
平移:
ctx.translate(x, y);
-
旋转:
ctx.rotate(angle);
-
缩放:
ctx.scale(scaleX, scaleY);
-
保存和恢复状态:
ctx.save(); // 保存当前状态
ctx.restore(); // 恢复到上次保存的状态
2.渐变:
-
线性渐变:
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
径向渐变:
const gradient = ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
3.阴影:
ctx.shadowColor = 'black';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
4.裁剪区域:
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.clip(); // 设置裁剪区域
四. 动画
1.基本动画:
-
使用
requestAnimationFrame
实现动画循环。 -
示例:
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空画布
// 绘制内容
requestAnimationFrame(draw);
}
draw();
2.性能优化
- 避免频繁的
clearRect
,可以只清除需要更新的区域。 - 使用离屏 Canvas 预渲染复杂图形。
五. 事件处理
1.鼠标事件:
-
监听
click
、mousemove
、mousedown
、mouseup
等事件。 -
示例:
canvas.addEventListener('click', (e) => {
const x = e.clientX - canvas.offsetLeft;
const y = e.clientY - canvas.offsetTop;
console.log(`Clicked at (${x}, ${y})`);
});
2.键盘事件:
监听 keydown
、keyup
等事件,结合 Canvas 实现交互。
六. WebGL(3D 绘图)
-
基本概念:
-
WebGL 是基于 OpenGL ES 的 3D 绘图 API。
-
通过
canvas.getContext('webgl')
获取 WebGL 上下文。 -
需要了解着色器(Shader)、顶点缓冲(VBO)、纹理(Texture)等概念。
-
-
简单示例:
const gl = canvas.getContext('webgl');
// 设置视口
gl.viewport(0, 0, canvas.width, canvas.height);
// 清除画布
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
七.补充
1.ctx.arc() 画圆
0, 0
: 这是圆心的坐标。第一个0
代表x轴的位置,第二个0
代表y轴的位置。在这个例子中,圆心位于Canvas坐标的原点(0, 0)。10
: 这是圆的半径。它决定了圆的大小,在这个例子中,圆的半径是10个像素。0
: 这是起始角度,以弧度为单位,从正x轴开始计算。在这个例子中,起始角度为0弧度,意味着从正x轴开始画。Math.PI * 2
: 这是结束角度,同样以弧度为单位。Math.PI * 2
表示360度,也就是一个完整的圆。因此,该圆将被完整地绘制出来。true
: 最后一个布尔值参数指示了绘制方向。如果设置为true
,则表示逆时针方向;如果是false
,则是顺时针方向。在这个例子中,圆将按照逆时针方向绘制。
ctx.arc(0, 0, 10, 0, Math.PI * 2, true);
2.ctx.save() ctx.restore()的作用
1. ctx.save()
的作用
ctx.save()
用于将当前的绘图状态保存到一个状态栈中。绘图状态包括以下属性:
-
当前的变换矩阵(
translate
、rotate
、scale
等)。 -
当前的绘图样式(
strokeStyle
、fillStyle
、lineWidth
、lineCap
等)。 -
当前的裁剪区域(
clip
)。 -
其他上下文属性(如
globalAlpha
、globalCompositeOperation
等)。
调用 ctx.save()
后,当前的状态会被推入状态栈中,后续的修改不会影响已保存的状态。
2. ctx.restore()
的作用
ctx.restore()
用于从状态栈中弹出最近保存的绘图状态,并将其恢复到当前上下文中。调用 ctx.restore()
后,上下文会恢复到之前保存的状态。
3. 为什么需要 ctx.save()
和 ctx.restore()
?
在复杂的绘图操作中,我们经常需要临时修改上下文的状态(例如旋转、缩放、改变颜色等),但又不希望这些修改影响后续的绘图操作。通过 ctx.save()
和 ctx.restore()
,可以方便地保存和恢复上下文状态,避免手动记录和恢复每个属性。
ctx.save() //调用 ctx.save() 后,当前的状态会被推入状态栈中,后续的修改不会影响已保存的状态。
for (let i = 0; i < 12; i++) {
ctx.beginPath()
ctx.rotate(Math.PI / 6)
ctx.moveTo(100, 0) //从距中心100像素处开始
ctx.lineTo(120, 0) // 画线到距中心120像素处
ctx.stroke() // 绘制线条
}
ctx.restore()
3.浏览器读取本地图片方法
1.FileReader
2.URL:createObjectURL() 静态方法
let img = null // 全局变量
function handleFileChange(e) {
file.value = e.target.files[0]
//将文件进行base64编码
/* const reader = new FileReader()
reader.readAsDataURL(file.value)
reader.onload = (e) => {
let result = e.target.result
console.log(result)
let img = new Image()
img.src = result
img.onload = function () {
document.body.appendChild(img)
}
} */
// const canvas = document.getElementById('canvas')
// const ctx = canvas.getContext('2d')
//生成一个本地的URL
const url = URL.createObjectURL(file.value)
img = new Image()
img.src = url
img.onload = function () {
//将图片绘制到这个canvas上
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
isImageLoaded.value = true //图片加载完毕
enableDrawing() // 启用绘制功能
}
}
4.Element:contextmenu 事件
contextmenu
事件会在用户尝试打开上下文菜单时触发。该事件通常在鼠标点击右键或者按下键盘上的菜单键时被触发。
在后一种情况下,上下文菜单显示在聚焦元素的左下方,除非该元素是树形,在这种情况下,上下文菜单显示在当前行的左下方。
任何没有被禁用的鼠标右击事件(通过调用单击事件的 preventDefault()方法)将会使得 contextmenu
事件在目标元素上被触发。
// 为canvas元素添加一个事件监听器,监听'contextmenu'事件,即用户右击鼠标时触发。
canvas.addEventListener('contextmenu', function (e) {
// 阻止默认行为,即阻止浏览器默认的上下文菜单出现。
e.preventDefault();
// 移除任何可能存在的mousemove事件监听器,确保不会发生不必要的交互。
canvas.onmousemove = null;
// 获取鼠标点击时相对于视口的位置坐标。
let x = e.clientX; // 鼠标指针距离视口左侧的距离。
let y = e.clientY; // 鼠标指针距离视口顶部的距离。
// 尝试获取ID为'menu'的现有元素。
let menu = document.getElementById('menu');
// 如果存在旧的菜单,则将其从DOM中移除,以避免多次创建菜单。
if (menu) {
menu.remove(); // 移除旧菜单。
}
// 创建一个新的div元素用于自定义菜单。
menu = document.createElement('div');
// 设置新创建的div元素的ID为'menu',以便后续可以找到它。
menu.id = 'menu';
// 设置菜单样式为固定位置,使其相对于浏览器窗口定位。
menu.style.position = 'fixed';
// 根据鼠标点击位置设置菜单的top和left样式属性,使菜单出现在鼠标点击处。
menu.style.top = y + 'px'; // 设置菜单距离页面顶部的位置。
menu.style.left = x + 'px'; // 设置菜单距离页面左侧的位置。
// 设置菜单的背景颜色。
menu.style.backgroundColor = '#ccc'; // 浅灰色背景。
// 添加内边距,使文本不紧贴边界。
menu.style.padding = '5px';
// 设置圆角边框,提高视觉效果。
menu.style.borderRadius = '5px';
// 设置菜单内部文本内容为“清除所有”。
menu.innerText = '清除所有';
// 将创建的菜单元素添加到文档的body中,使其可见。
document.body.appendChild(menu);
// 为菜单添加一个单击事件监听器。
menu.onclick = function () {
// 清空shapes数组,假设这是一个保存所有形状数据的数组。
shapes.length = 0;
// 当菜单被点击后,移除菜单自身。
menu.remove();
}
});
八、JavaScript 补充
1.生成一个介于 0 和 (max - min)
之间的随机数。
function getRandom(min, max) {
return Math.random() * (max - min) + min;
}
-
Math.random()
:生成一个介于 0(包括)和 1(不包括)之间的随机浮点数。 -
(max - min)
:计算范围的大小。 -
Math.random() * (max - min)
:生成一个介于 0 和(max - min)
之间的随机数。 -
+ min
:将随机数偏移到min
和max
之间。
2.Math.hypot
计算二维空间中两个点之间的欧几里得距离
const distance = Math.sqrt(
Math.pow(pointA.x - pointB.x, 2) + Math.pow(pointA.y - pointB.y, 2)
)
-
const
: 这是一个关键字,用来声明一个常量,即一旦赋值后不能被重新赋值或改变的变量。这里distance
被声明为常量,意味着它所指向的值在初始化后不应再被改变。 -
Math.sqrt()
: 这是JavaScript内置的数学对象Math
的一个静态方法,用于返回一个数的平方根。该方法接收一个参数,即要计算平方根的数值。 -
Math.pow()
: 同样是Math
对象下的一个静态方法,用于返回基数(第一个参数)的指数次幂(第二个参数)。在这里它用来计算每个坐标差的平方。 -
pointA.x
,pointB.x
,pointA.y
,pointB.y
: 这些表示的是两个点(pointA
和pointB
)各自的x轴和y轴坐标。假定这两个点都是具有x
和y
属性的对象,这些属性分别代表它们在二维平面上的位置。
3.使用 class
关键字定义一个类
在JavaScript中,class
是一种用于创建对象的语法糖,它基于原型继承机制。通过 class
关键字,可以更清晰、更简洁地定义构造函数和原型方法。
1)使用 class
关键字定义一个类。
-
类名通常采用首字母大写的驼峰命名法(如
Point
和View
)。
class Point {
// 类的内容
}
2)构造函数 (constructor
)
-
constructor
是一个特殊的方法,用于创建和初始化类的实例。 -
当你使用
new
关键字创建类的实例时,constructor
方法会被自动调用。 -
你可以在
constructor
中定义实例属性。
class Point {
constructor(r = 10) {
this.r = r;
this.x = getRandom(this.r, canvas.width - this.r);
this.y = getRandom(this.r, canvas.height - this.r);
}
}
3)实例方法
-
在类中定义的方法会成为实例方法,每个实例都可以调用这些方法。
-
实例方法可以访问和操作实例的属性。
class Point {
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fillStyle = "#fff";
ctx.fill();
}
}
4) 实例属性
-
实例属性是通过
this
关键字在constructor
或实例方法中定义的。 -
每个实例都有自己独立的属性。
class Point {
constructor(r = 10) {
this.r = r; // 实例属性
this.x = getRandom(this.r, canvas.width - this.r); // 实例属性
this.y = getRandom(this.r, canvas.height - this.r); // 实例属性
}
}
5) 类的实例化
-
使用
new
关键字创建类的实例。 -
实例化时会调用
constructor
方法。
const point = new Point(15);
6)类的数组初始化
-
你可以使用
Array.fill()
和Array.map()
方法来初始化一个包含多个类实例的数组。
this.pointsArr = new Array(points).fill().map(() => new Point(15));
7)类的继承
-
使用
extends
关键字可以实现类的继承。 -
子类可以继承父类的属性和方法,并可以重写或扩展它们。
class ChildClass extends ParentClass {
constructor() {
super(); // 调用父类的构造函数
}
}
8) this
的绑定
-
在类的方法中,
this
指向当前实例。 -
如果方法作为回调函数传递(如
requestAnimationFrame
),可能会丢失this
的上下文。此时可以使用bind
方法来绑定this
。
requestAnimationFrame(this.draw.bind(this));
9)类的静态方法
-
使用
static
关键字可以定义静态方法。 -
静态方法属于类本身,而不是类的实例,因此不能通过实例调用。
class Point {
static staticMethod() {
console.log('This is a static method');
}
}
Point.staticMethod(); // 直接通过类名调用
10) 类的封装
-
类提供了一种封装数据和行为的机制,使得代码更加模块化和易于维护。
-
通过类,你可以将相关的属性和方法组织在一起,形成一个独立的单元。
11) 类的多态
-
多态是指子类可以重写父类的方法,从而在调用时表现出不同的行为。
-
这是面向对象编程中的一个重要概念。
12) 类的使用场景
-
类非常适合用于创建具有相似属性和行为的对象。
总结
-
class
提供了一种更清晰、更结构化的方式来定义对象和它们的行为。 -
通过
constructor
可以初始化实例属性。 -
实例方法可以操作实例属性,并且可以通过
this
访问当前实例。 -
类可以用于封装数据和行为,使代码更易于维护和扩展。