Canvas系统学习笔记

时游大约 7 分钟

Canvas系统学习笔记

基础入门

什么是Canvas?

Canvas是一块网页上的画布,提供了一种在网页上进行图形绘制的强大工具,让开发者能够自由地创造各种视觉效果。

Canvas的应用场景

  1. 游戏开发
  2. 数据可视化
  3. 动画和特效
  4. 图像编辑

代码案例

<!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>
上次编辑于:
贡献者: Sunshine,15327360835
Loading...