Canvas 学习笔记

323 阅读9分钟

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>

设置线宽,需要被注意的地方 图片.png

用路径,画弧线

<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>

图片.png

在画布上合成

  • 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>

图片.png

用图案填充颜色

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); 
};

图片.png

创建阴影

<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>

图片.png

清除画布

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的结果。