Canvas 学习笔记
- 参考书籍《canvas开发详解第2版》
浏览器是否支持Canvas
function isCanvas() {
const canvas = document.getElementById("canvas");
if (canvas.getContext) {
console.log("支持");
} else {
console.log("不支持");
}
}
isCanvas();
画布的大小
canvas 标签上有width 与 height属性,它们表示的画布的大小,同样在style属性中也有width 与 height的大小
### 一、定义与用途
1. **`<canvas>`标签自带的`width`与`height`属性**:
- **定义**:这两个属性直接定义了`<canvas>`元素的画布(即可绘制区域)的宽度和高度。
- **用途**:它们决定了画布的实际尺寸,即绘制图形时的基准尺寸。画笔context在绘制时,是以这两个属性作为基准的。
- **默认值**:如果不设置这两个属性,`<canvas>`元素的默认宽度为300像素,高度为150像素。
1. **`style`中的`width`与`height`属性**:
- **定义**:这两个属性是CSS样式属性,用于设置`<canvas>`元素在页面上显示的宽度和高度。
- **用途**:它们决定了元素在页面布局中的尺寸,以及元素在页面上的显示大小。如果这两个属性与`<canvas>`标签自带的`width`和`height`属性不一致,画布上的图形会被放大或缩小以适应新的显示尺寸。
- **默认值**:如果不在`style`中设置这两个属性,它们将不会改变`<canvas>`元素的默认显示尺寸(即300x150像素,除非在`<canvas>`标签中明确设置了其他值)。
### 二、行为差异
1. **尺寸一致性**:
- 当`<canvas>`标签自带的`width`与`height`属性与`style`中的`width`和`height`属性设置一致时,画布的实际尺寸与显示尺寸相同,图形不会失真。
- 如果两者不一致,画布上的图形会被放大或缩小以适应显示尺寸,这可能导致图形失真或模糊。
1. **最佳搭配**:
- 为了避免图形失真,最佳搭配是使`<canvas>`标签自带的`width`与`height`属性是`style`中相应属性的n倍(n为整数),这样可以确保在放大或缩小时保持图形的清晰度。
// 由于长度不一致,浏览器会自动拉宽图片的宽度,来适应style中的width
<canvas
id="canvas"
width="500"
height="300"
style="width: 1000px; height: 300px; border: 1px solid black"
></canvas>
<script>
const canvas = document.getElementById("canvas");
const content = canvas.getContext("2d");
let testImg = new Image();
testImg.onload = () => {
content.drawImage(testImg, 0, 0);
};
testImg.src = "./test.jpg";
</script>
// 解决方法1,通常是会让style width 与 height 跟画布的高宽保持一致
// 解决方法2,设置canvas属性,这样浏览器就不会自动进行拉伸计算
canvas {
object-fit: contain; /* 或其他值,如cover、fill、scale-down等 */
}
基本图形
// 绘制图形有三种方式,描边,填充,清楚
const canvas = document.getElementById("canvas");
const content = canvas.getContext("2d");
content.fillStyle = "#000";
content.strokeStyle = "#ff00ff";
content.lineWidth = 2;
content.fillRect(10, 10, 40, 40);
content.strokeRect(0, 0, 60, 60);
content.clearRect(20, 20, 20, 20);
路径
- 理解路径的概念,先描述一个路径,然后再画,这样canvas的笔会按照路径画下去
- 属性
- lineWidth 线宽
- lineCap 线端点,类型
- lineJoin 线的连接处
<script>
const canvas = document.getElementById("canvas");
const content = canvas.getContext("2d");
content.strokeStyle = "black";
content.lineWidth = 10;
content.lineCap = "square";
content.beginPath();
content.moveTo(20, 0);
content.lineTo(100, 0);
content.stroke();
content.closePath();
</script>
设置线宽,需要被注意的地方
用路径,画弧线
<script>
const canvas = document.getElementById("canvas");
const content = canvas.getContext("2d");
content.beginPath();
content.strokeStyle = "black";
content.lineWidth = 40;
content.lineCap = "square";
content.arc(100, 100, 20, 0, 2 * Math.PI, false);
content.stroke();
content.closePath();
</script>
贝塞尔曲线
- 平方
- 立方
<script>
const canvas = document.getElementById("canvas");
const content = canvas.getContext("2d");
content.beginPath();
content.moveTo(0, 0);
content.lineTo(100, 200);
content.arcTo(350, 350, 100, 100, 20);
content.quadraticCurveTo(100, 25, 0, 50);
content.bezierCurveTo(0, 125, 300, 175, 150, 300);
content.stroke();
content.closePath();
</script>
裁切区
<script>
const canvas = document.getElementById("canvas");
const content = canvas.getContext("2d");
content.fillStyle = "black";
content.beginPath();
content.fillRect(10, 10, 200, 200);
content.strokeStyle = "green";
content.rect(0, 0, 49, 49);
content.stroke();
content.save();
// 裁剪区
content.beginPath();
content.rect(0, 0, 50, 50);
content.clip();
// 红色圈
content.beginPath();
content.strokeStyle = "red";
content.lineWidth = 1;
content.arc(25, 25, 30, 0, 2 * Math.PI, false); // 这个园只能在 content.clip() 区域中画,区域外,是没有东西的
content.stroke();
content.closePath();
content.restore();
// 再次裁剪切整个画布
content.beginPath();
content.rect(0, 0, 500, 500);
content.clip();
// 绘制一个没有被裁切的蓝线
content.beginPath();
content.strokeStyle = "blue";
content.lineWidth = 5;
content.arc(100, 100, 50, 0, 2 * Math.PI, false);
content.stroke();
content.closePath();
</script>
在画布上合成
source-over(默认):在目标图像上绘制源图像,正常显示。source-atop:在目标图像顶部显示源图像,但只显示源图像与目标图像重叠的部分,且目标图像的非重叠部分不受影响。source-in:只在目标图像内部显示源图像,目标图像的其他部分变为透明。source-out:只在目标图像外部显示源图像,目标图像内部变为透明。destination-over:在源图像下方显示目标图像,正常显示。destination-atop:在源图像顶部显示目标图像,但只显示目标图像与源图像重叠的部分,源图像的非重叠部分变为透明。destination-in:只在源图像内部显示目标图像,源图像的其他部分变为透明。destination-out:只在源图像外部显示目标图像,源图像内部变为透明。lighter:将源图像和目标图像的颜色相加,颜色值不超过255。copy:只显示源图像,忽略目标图像。xor:使用“异或”操作组合源图像和目标图像。
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 绘制背景
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制第一个矩形(目标图像)
ctx.fillStyle = "green";
ctx.fillRect(50, 50, 100, 100);
// 设置合成操作
ctx.globalCompositeOperation = "source-atop"; // 合成要放在中间的位置
// 绘制第二个矩形(源图像)
ctx.fillStyle = "red";
ctx.fillRect(75, 75, 50, 50);
// 你可以尝试更改globalCompositeOperation的值来查看不同的效果
</script>
画布变换-平移,旋转
- 注意,先设置旋转,然后在绘图!!!
- 围绕画布的原点进行旋转
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 绘制一个未旋转的矩形
ctx.fillStyle = "blue";
ctx.fillRect(50, 50, 100, 100);
// 重置变换矩阵
ctx.setTransform(1, 0, 0, 1, 0, 0);
// 旋转画布45度
ctx.rotate((45 * Math.PI) / 180);
// 绘制一个旋转后的矩形(注意:它会围绕原点旋转)
ctx.fillStyle = "red";
ctx.fillRect(50, 50, 100, 100);
</script>
- 围绕指定点进行旋转
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 指定旋转的中心点
const centerX = 150;
const centerY = 150;
// 平移画布,使旋转中心点成为新的原点
ctx.translate(centerX, centerY);
// 旋转画布45度
ctx.rotate((45 * Math.PI) / 180);
// 绘制一个矩形(它会围绕新的原点,即指定的中心点旋转)
ctx.fillStyle = "green";
ctx.fillRect(-50, -50, 100, 100); // 注意:坐标是相对于新的原点计算的
// 重置变换矩阵(可选,但推荐在绘制完所有需要变换的内容后执行)
ctx.setTransform(1, 0, 0, 1, 0, 0);
</script>
- 旋转文本
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d'); // 指定旋转的中心点
const centerX = 150;
const centerY = 50; // 平移画布,使旋转中心点成为新的原点
ctx.translate(centerX, centerY); // 旋转画布45度
ctx.rotate((45 * Math.PI) / 180); // 设置字体和颜色
ctx.font = '24px Arial';
ctx.fillStyle = 'purple'; // 绘制旋转后的文本(它会围绕新的原点旋转)
ctx.fillText('Hello, World!', -50, 0);
// 注意:坐标是相对于新的原点计算的
// 重置变换矩阵(可选,但推荐在绘制完所有需要变换的内容后执行)
ctx.setTransform(1, 0, 0, 1, 0, 0);
缩放
- 无论是缩放,还是旋转,它都是基于画布在变化,而不是图像本身!!!
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 绘制一个未缩放的矩形
ctx.fillStyle = "blue";
ctx.fillRect(50, 50, 100, 100);
// 重置变换矩阵(可选,但推荐在绘制前执行以确保没有意外的变换影响)
ctx.setTransform(1, 0, 0, 1, 0, 0);
// 缩放画布(2倍放大)
ctx.scale(2, 2);
// 绘制一个缩放后的矩形(注意:它会比未缩放的矩形大两倍)
ctx.fillStyle = "red";
ctx.fillRect(50, 50, 100, 100);
</script>
找到任何形状的中心点
- 需要用边界框理论
颜色填充
- 纯色填充
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d'); // 设置填充颜色为红色
ctx.fillStyle = 'red'; // 填充一个矩形
ctx.fillRect(50, 50, 100, 100); // 填充一个圆形(需要先绘制路径)
ctx.beginPath(); ctx.arc(200, 100, 50, 0, Math.PI * 2, true); // 外圈顺时针绘制
ctx.fill();
- 渐变色填充
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d'); // 创建一个线性渐变对象
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0); // 添加颜色停止点 gradient.addColorStop(0, 'blue'); // 起始颜色
gradient.addColorStop(1, 'red'); // 结束颜色
// 将渐变设置为填充样式
ctx.fillStyle = gradient;
// 填充一个矩形
ctx.fillRect(50, 50, 100, 100);
- 经向变色
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 创建一个径向渐变对象
const radialGradient = ctx.createRadialGradient(
150,
150,
10,
150,
150,
100
);
// 添加颜色停止点
radialGradient.addColorStop(0, "yellow"); // 内圈颜色
radialGradient.addColorStop(1, "green"); // 外圈颜色
// 将渐变设置为填充样式
ctx.fillStyle = radialGradient;
// 填充一个圆形(需要先绘制路径)
ctx.beginPath();
ctx.arc(150, 150, 50, 0, Math.PI * 2, true); // 外圈顺时针绘制
ctx.fill();
</script>
用图案填充颜色
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d'); // 创建一个Image对象并设置其src属性
const img = new Image(); img.src = 'path/to/your/image.jpg';
// 替换为你的图像路径
// 监听图像的load事件
img.onload = function() {
// 创建一个平铺图案
const pattern = ctx.createPattern(img, 'repeat');
// 设置fillStyle为图案
ctx.fillStyle = pattern;
// 填充一个矩形
ctx.fillRect(50, 50, 100, 100);
};
创建阴影
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// 设置阴影属性
ctx.shadowOffsetX = 10; // 阴影在X轴的偏移量
ctx.shadowOffsetY = 10; // 阴影在Y轴的偏移量
ctx.shadowBlur = 20; // 阴影的模糊程度
ctx.shadowColor = "rgba(0, 0, 0, 1)"; // 阴影的颜色
// 绘制一个带有阴影的矩形
ctx.fillStyle = "blue";
ctx.fillRect(50, 50, 200, 100);
</script>
清除画布
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
// 方法一:使用 clearRect 清除整个画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
检查某个点是否在路径上
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
// 绘制一个路径(例如,一个圆形)
ctx.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI * 2, true);
// 绘制一个圆心在(100, 100),半径为50的圆
ctx.closePath();
// 关闭路径(对于圆形来说不是必需的,因为arc已经自动关闭了路径)
// 检查点(120, 100)是否在路径上
var x = 120, y = 100;
var isPointInside = ctx.isPointInPath(x, y);
if (isPointInside) {
console.log('点(' + x + ', ' + y + ')在路径上。');
}
else {
console.log('点(' + x + ', ' + y + ')不在路径上。');
}
// 注意:在调用isPointInPath之前,不要调用任何会改变当前路径状态的方法(如fill, stroke, clearRect等),
// 因为这会导致路径被重置或修改,从而影响isPointInPath的结果。