Canvas系统学习笔记
大约 7 分钟
Canvas系统学习笔记
基础入门
什么是Canvas?
Canvas是一块网页上的画布,提供了一种在网页上进行图形绘制的强大工具,让开发者能够自由地创造各种视觉效果。
Canvas的应用场景
- 游戏开发
- 数据可视化
- 动画和特效
- 图像编辑
代码案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas绘图</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
overflow: hidden;
}
/* 可以直接使用css设置canvas画布的样式 */
#canvasDom {
background-color: #f0f0f0;
}
</style>
</head>
<body>
<!-- 定义一个canvas画布 -->
<canvas id="canvasDom"></canvas>
<script>
// 获取canvas元素
const canvasDom = document.getElementById("canvasDom");
// 获取上下文:在canvas上进行绘图,需要先获取上下文对象,简称画笔
const ctx = canvasDom.getContext("2d");
// 设置画布大小:若使用css方式,会导致画布变形,推荐使用width和height属性
canvasDom.width = window.innerWidth;
canvasDom.height = window.innerHeight;
/* 基础元素 */
// 绘制直线
ctx.beginPath(); // 开始绘制路径
ctx.moveTo(300, 300); // 设置线条起始点
ctx.lineTo(500, 500); // 绘制一条直线到目标点
ctx.moveTo(300, 500); // 设置线条起始点
ctx.lineTo(500, 300); // 绘制一条直线到目标点
ctx.stroke(); // 绘制路径
// 绘制矩形:fillRect(x, y, width, height)
ctx.fillRect(10, 10, 100, 100);
// 矩形边框
ctx.strokeRect(200, 10, 100, 100);
// 绘制圆形:arc(x, y, radius, startAngle, endAngle, anticlockwise),anticlockwise默认为false,表示顺时针
ctx.beginPath();
ctx.lineWidth = 5; // 边框宽度
ctx.arc(450, 60, 50, 0, Math.PI * 2);
// 填充颜色
ctx.fillStyle = "red"; // 填充颜色
ctx.strokeStyle = "blue"; // 边框颜色
ctx.stroke();
// 绘制路径
ctx.beginPath();
ctx.moveTo(900, 600);
ctx.lineTo(920, 620);
ctx.lineTo(940, 640);
ctx.lineTo(960, 660);
ctx.closePath();
ctx.lineWidth = 2;
ctx.strokeStyle = "green"; // 边框颜色
ctx.stroke(); // 渲染
/* 图形变换 */
// 平移变换:移动到x:50,y:100的位置
ctx.translate(50, 100);
// 缩放变换:x方向缩放0.5,y方向缩放0.5
ctx.scale(0.5, 0.5);
// 旋转变换:绕坐标原点旋转45度
ctx.rotate(Math.PI / 4);
// 绘制矩形
ctx.fillStyle = "blue"; // 填充颜色
ctx.fillRect(100, 100, 100, 100);
/* 渐变 */
// 线性
ctx.beginPath();
ctx.moveTo(500, 200);
ctx.lineTo(700, 200);
ctx.lineWidth = 25;
// createLinearGradient(x1, y1, x2, y2):创建一个线性渐变对象,参数为渐变的起始点和结束点坐标
ctx.strokeStyle = ctx.createLinearGradient(500, 200, 700, 200);
ctx.strokeStyle.addColorStop(0, "red");
ctx.strokeStyle.addColorStop(1, "blue");
ctx.stroke();
// 径向渐变
ctx.beginPath();
// createRadialGradient(x0, y0, r0, x1, y1, r1):创建径向渐变,参数分别是起始圆和结束圆的中心坐标及半径
const radialGradient = ctx.createRadialGradient(
// 起始圆
600,
350,
10,
// 结束圆
600,
350,
100
);
// 添加颜色停靠点
radialGradient.addColorStop(0, "yellow"); // 对应起始圆的边缘
radialGradient.addColorStop(0.5, "orange"); // 对应起始圆和结束圆之间的位置
radialGradient.addColorStop(1, "red"); // 对应结束圆的边缘
// 设置填充样式为径向渐变
ctx.fillStyle = radialGradient;
// 绘制圆形
ctx.arc(600, 350, 100, 0, Math.PI * 2);
// 填充圆形
ctx.fill();
/* 绘制图像:drawImage(image, x, y, width, height),其中image可为img元素、canvas元素或video元素 */
// 重置变换矩阵(清除平移、缩放、旋转)
ctx.setTransform(1, 0, 0, 1, 0, 0);
// 加载图像
const img = new Image();
img.src =
"https://q5.itc.cn/images01/20241122/636aff6d266742c9a9ce417dc4e23e35.png";
// 等待图像加载完成
img.onload = function () {
// 绘制图像
ctx.drawImage(img, 100, 100, 100, 100);
// 截取部分图像drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight):
// 从img图像截取一部分,sx,sy为截取区域的起始点坐标,sWidth,sHeight为截取区域的宽度和高度,
// dx,dy为绘制区域的起始点坐标,dWidth,dHeight为绘制区域的宽度和高度
ctx.drawImage(img, 200, 200, 100, 100, 300, 300, 100, 100);
};
/* 图像合成 */
const bottomImg = new Image();
bottomImg.src =
"https://lostsystem-wut.oss-cn-hangzhou.aliyuncs.com/lostThings/12151052_61b94a563cc4d.jpg";
const topImg = new Image();
topImg.src =
"https://img0.baidu.com/it/u=1462552351,2550230177&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889";
// 等待图像加载完成
bottomImg.onload = function () {
topImg.onload = function () {
// 合成图像
ctx.globalCompositeOperation = "lighter"; // lighter lighter 颜色会混合,形成新的颜色
// 绘制底部图像
ctx.drawImage(bottomImg, 800, 300, 200, 200);
// 绘制顶部图像
ctx.drawImage(topImg, 800, 300, 200, 200);
};
};
/* 批量图片预加载 */
// 方法1:使用 Promise 预加载单张图片
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
img.src = src;
});
}
// 方法2:批量预加载图片
async function preloadImages(imageUrls) {
try {
// 使用 Promise.all 并行加载所有图片
const images = await Promise.all(
imageUrls.map(url => loadImage(url))
);
console.log(`成功预加载 ${images.length} 张图片`);
return images; // 返回加载完成的图片数组
} catch (error) {
console.error('图片预加载失败:', error);
throw error;
}
}
// 使用示例
const imageUrls = [
"https://q5.itc.cn/images01/20241122/636aff6d266742c9a9ce417dc4e23e35.png",
"https://lostsystem-wut.oss-cn-hangzhou.aliyuncs.com/lostThings/12151052_61b94a563cc4d.jpg",
"https://img0.baidu.com/it/u=1462552351,2550230177&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889"
];
// 预加载所有图片,然后进行绘制
preloadImages(imageUrls).then(images => {
// 所有图片加载完成后执行
console.log('所有图片已加载完成,开始绘制');
// 示例:绘制第一张图片
ctx.drawImage(images[0], 100, 500, 150, 150);
// 示例:绘制第二张图片
ctx.drawImage(images[1], 300, 500, 150, 150);
// 示例:绘制第三张图片
ctx.drawImage(images[2], 500, 500, 150, 150);
});
// 方法3:带进度回调的预加载(可选)
async function preloadImagesWithProgress(imageUrls, onProgress) {
const total = imageUrls.length;
let loaded = 0;
const images = await Promise.all(
imageUrls.map(async (url) => {
const img = await loadImage(url);
loaded++;
if (onProgress) {
onProgress(loaded, total, (loaded / total * 100).toFixed(2));
}
return img;
})
);
return images;
}
// 带进度的使用示例
preloadImagesWithProgress(imageUrls, (loaded, total, percent) => {
console.log(`加载进度: ${loaded}/${total} (${percent}%)`);
}).then(images => {
console.log('所有图片加载完成');
});
</script>
</body>
</html>
动画代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas动画</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
overflow: hidden;
}
/* 可以直接使用css设置canvas画布的样式 */
#animationCanvas {
background-color: #f0f0f0;
}
</style>
</head>
<body>
<canvas id="animationCanvas"></canvas>
<script>
const canvas = document.getElementById("animationCanvas");
const ctx = canvas.getContext("2d");
// 设置画布大小
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 创建一个矩形
ctx.fillStyle = "red";
ctx.fillRect(100, 100, 100, 100);
/* 动画1:设置每50ms执行一次,通过更新x位置来实现动画 */
let x = 0;
const intervalId = setInterval(() => {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(x, 50, 50, 50);
x += 5;
if (x > canvas.width) {
clearInterval(intervalId);
}
}, 50);
/* 交互:事件处理,click、mousedown、mouseup、mousemove等 */
canvas.addEventListener("click", (e) => {
console.log("点击了画布", e);
});
canvas.addEventListener("mousemove", (e) => {
// console.log("鼠标移动了", e);
});
canvas.addEventListener("mousedown", (e) => {
console.log("鼠标按下了", e);
});
canvas.addEventListener("mouseup", (e) => {
console.log("鼠标松开了", e);
});
/* Demo1:移动矩形*/
let isDragging = false;
let startX, startY, offsetX, offsetY;
const rect = {
x: 50,
y: 50,
width: 100,
height: 100,
};
// 鼠标按下
canvas.addEventListener("mousedown", (e) => {
let x = e.offsetX;
let y = e.offsetY;
// 判定是否在矩形内部
if (
x >= rect.x &&
x <= rect.x + rect.width &&
y >= rect.y &&
y <= rect.y + rect.height
) {
// 记录拖动开始时的位置
isDragging = true;
startX = x;
startY = y;
offsetX = rect.x - startX;
offsetY = rect.y - startY;
}
});
// 鼠标移动
canvas.addEventListener("mousemove", (e) => {
if (isDragging) {
// 处于拖动状态时
// 修改矩形的位置
rect.x = e.offsetX + offsetX;
rect.y = e.offsetY + offsetY;
// 绘制
draw();
}
});
// 鼠标抬起
canvas.addEventListener("mouseup", (e) => {
isDragging = false;
});
// 绘制矩形
const draw = () => {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制矩形
ctx.fillStyle = "#1890ff";
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
};
// 初始化绘制
draw();
/* 缩放图形:监听鼠标滚轮事件实现缩放*/
canvas.addEventListener("wheel", (event) => {
// 判断是否在矩形内
let x = event.offsetX;
let y = event.offsetY;
if (
x >= rect.x &&
x <= rect.x + rect.width &&
y >= rect.y &&
y <= rect.y + rect.height
) {
let delta = event.deltaY > 0 ? -1 : 1;
console.log(event.deltaY, delta);
// 缩放图形
rect.width += delta * 10;
rect.height += delta * 10;
// 绘制
draw();
}
});
/* 动画优化:使用requestAnimationFrame优化,requestAnimationFrame会依据浏览器的刷新频率来自动调整执行速率,避免了动画执行过程中出现的卡顿现象 */
let x = 100;
// 绘制圆形
const drawCircle = () => {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(x, 300, 50, 0, 2 * Math.PI);
ctx.fill();
};
// 动画函数
let speed = 1;
const animate = () => {
if (x == canvas.width) {
speed = -1;
} else if (x == 0) {
speed = 1;
}
x = x + speed;
console.log(speed)
console.log(x)
drawCircle();
requestAnimationFrame(animate);
};
// 启动动画
animate();
drawCircle();
</script>
</body>
</html>
文字代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas文字</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
overflow: hidden;
}
/* 可以直接使用css设置canvas画布的样式 */
#fontCanvas {
background-color: #f0f0f0;
}
</style>
</head>
<body>
<canvas id="fontCanvas"></canvas>
<script>
const canvas = document.getElementById("fontCanvas");
const ctx = canvas.getContext("2d");
// 设置画布大小
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
/* 绘制字体 */
// font:定义文字的字体、大小和样式
ctx.font = "100px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// 文字的阴影和轮廓效果
// shadowColor:设置阴影的颜色
// shadowOffsetX:设置阴影水平方向的偏移量
// shadowOffsetY:设置阴影垂直方向的偏移量
// shadowBlur:设置阴影的模糊半径
ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 5;
// fillText:绘制填充文字
ctx.fillText("Hello Canvas", canvas.width / 2, canvas.height / 2);
// strokeText:绘制轮廓文字
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.strokeText("Hello Canvas", canvas.width / 2, canvas.height / 2);
</script>
</body>
</html>
Loading...
