40:canvas - 上

简单来说,<canvas> 是HTML5中的标签,它是一个容器,可以使用JS在其中绘制图形或文字。

一、介绍

  1. canvas 是 HTML5 新增的一个标签,表示画布
    • <canvas></canvas>
  1. canvas 也是 HTML5 的画布技术,可以通过编码的方式在画布上描绘图像
<html>
  <head>
    ...
  </head>
  <body>
    <canvas></canvas>
  </body>
</html><html>
  <head>
    ...
  </head>
  <body>
    <canvas></canvas>
  </body>
</html>
    • canvas 默认是一个行内块元素
    • canvas 默认画布大小是 300 * 150
    • canvas 默认没有边框, 背景默认为无色透明

1.1 canvas 画布大小

  1. 在绘图之前, 先要确定一个画布的大小
    • 因为画布默认是按照比例调整
    • 所以我们调整宽度或者高度的时候, 调整一个, 另一个自然会按照比例自己调整
    • 我们也可以宽高一起调整
  1. 调整画布大小有两种方案
    • 第一种 : 通过 css 样式 ( 不推荐 )
    • 第二种 : 通过标签属性 ( 推荐 )
      • <canvas width="1000" height="500"></canvas>
  1. 两种方案的区别
    • 通过 css 样式的调整方案(不推荐)
      • 因为 css 并没有设置了画布的大小,而是把原来 300 * 150 的画布的可视窗口变成了 1000 * 500。所以真实画布并没有放大, 只是可视程度变大了
      • 如:把一个 300 * 150 的图片,放大到 1000 * 500 的大小来看

img

    • 通过属性的调整方案(推荐)
      • 这才是真正的将画布大小调整到 1000 * 500

img

1.2 画布的坐标

    • canvas 画布和 css 的坐标系一样。左上角为 0 0 ,向右向下延伸为正方向

img

二、canvas 初体验

  1. canvas 画布很简单,类似于 windows 电脑上的画板工具

img

    • 在绘制之前,先选定一个形状工具(直线,矩形,圆形,…)
    • 确定路径起点,落笔
    • 移动到路径终点,抬笔
    • 设定样式(粗细,颜色)
  1. 在 canvas 绘制也是一样的逻辑
    • 创建一个画布工具箱
      • 语法:canvas元素.getContext('2d')
      • 如:const ctx = canvasEle.getContext('2d')
    • 确定路径起点,落笔
      • 将画笔移动到一个指定位置下笔
      • 语法:工具箱.moveTo(x轴坐标, y轴坐标)
      • ctx.moveTo(100, 100)
    • 移动到路径终点,抬笔
      • 将画笔移动到一个指定位置,画下一条轨迹(路径)
      • 注意:这里暂时没有显示,因为只是画了一个轨迹(路径)
      • 语法:工具箱.lineTo(x轴坐标, y轴坐标)
      • ctx.lineTo(300, 100)
      • 如果多个lineTo方法连续执行,表示连续绘制,即在本次抬笔的位置直接落笔。
    • 设定路径样式(可在落笔之前设置)
      • 语法:工具箱.样式属性 = 样式值
      • 线的宽度:ctx.lineWidth = 10
      • 线的颜色:ctx.strokeStyle = '#000'
    • 确定本次绘制的路径信息(生效,显现绘制效果)
      • ctx.stroke()
      • 必须绘制完成后执行
    • 如:绘制一条线段:
// 0. 获取 canvas 标签元素
const canvasEle = document.querySelector('#canvas')

// 1. 创建画布工具箱
// 语法: canvas元素.getContext('2d')
const ctx = canvasEle.getContext('2d')

// 2. 开始绘制(落笔)
// 语法: 工具箱.moveTo(x轴坐标, y轴坐标)
ctx.moveTo(100, 100)

// 3. 将笔移动到一个指定位置, 画下一条轨迹(抬笔)
// 注意: 这里是没有显示的, 因为只是画了一个轨迹
// 语法: 工具箱.lineTo(x轴坐标, y轴坐标)
ctx.lineTo(300, 100)

// 4. 设定本条线的样式
// 设定线的宽度
// 语法: 工具箱.lineWidth = 数字
ctx.lineWidth = 10
// 设定线的颜色
// 语法: 工具箱.strokeStyle = '颜色'
ctx.strokeStyle = '#000'

// 5. 确定本次绘制信息
// 把上边画下的痕迹按照设定好的样式描绘下来
// 语法: 工具箱.stroke()
ctx.stroke()// 0. 获取 canvas 标签元素
const canvasEle = document.querySelector('#canvas')

// 1. 创建画布工具箱
// 语法: canvas元素.getContext('2d')
const ctx = canvasEle.getContext('2d')

// 2. 开始绘制(落笔)
// 语法: 工具箱.moveTo(x轴坐标, y轴坐标)
ctx.moveTo(100, 100)

// 3. 将笔移动到一个指定位置, 画下一条轨迹(抬笔)
// 注意: 这里是没有显示的, 因为只是画了一个轨迹
// 语法: 工具箱.lineTo(x轴坐标, y轴坐标)
ctx.lineTo(300, 100)

// 4. 设定本条线的样式
// 设定线的宽度
// 语法: 工具箱.lineWidth = 数字
ctx.lineWidth = 10
// 设定线的颜色
// 语法: 工具箱.strokeStyle = '颜色'
ctx.strokeStyle = '#000'

// 5. 确定本次绘制信息
// 把上边画下的痕迹按照设定好的样式描绘下来
// 语法: 工具箱.stroke()
ctx.stroke()
    • 从坐标 ( 100, 100 ) 绘制到坐标 ( 300, 100 )
    • 线段长度为 200px
    • 线段宽度为 10px
    • 线段颜色为 ‘#000’ ( 黑色 )

img

三、canvas 线宽颜色问题

  • 需要注意,在绘制任何图形时,尽量不要出现奇数宽度,因为canvas在划分了坐标点后,每次绘制都是绘制在点坐标上。
  • 一个宽度为 1px 的线段就会以如下这种方式被画出来:

img

  • canvas在描绘这个线段的时候,会把线段的最中心点放在这个像素点位上
  • 也就是说,在描述线宽的时候,实际上会从 0.5px 的位置绘制到 1.5px 的位置,合计描述宽度为 1px
  • 但是浏览器不能识别小数像素
  • 也就是说浏览器没办法从 0.5 开始绘制,也没有办法绘制到 1.5 停止
  • 那么就只能是从 0 开始绘制到 2。所以线宽就会变成 2px 了
  • 又因为本身一个像素的黑色被强制拉伸到两个像素宽度,所以颜色就会变浅
  • 就像我们一杯墨水, 倒在一个杯子里面就是黑色
  • 但是到在一个杯子里面的时候, 又倒进去一杯水, 颜色就会变浅
  • 所以最终呈现出来的样式,如下图:

img

  • 所以,我们在进行 canvas 绘制时,涉及到线段的宽度时,一般不会把线段宽度设置成奇数,尽量设置为偶数

四、线段的开始与闭合

  1. 在绘制两条样式不同的独立线段时,如果直接进行绘制,后绘制的线段样式可能会影响先绘制的线段样式,如:
const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
ctx.stroke();

ctx.moveTo(100, 200);
ctx.lineTo(200, 200);
ctx.lineWidth = 4;
ctx.strokeStyle = "#000";
ctx.stroke();const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
ctx.stroke();

ctx.moveTo(100, 200);
ctx.lineTo(200, 200);
ctx.lineWidth = 4;
ctx.strokeStyle = "#000";
ctx.stroke();
  • 最终呈现出下图样式

img

  • 可以看到第二条线段的样式覆盖在了第一条线段上方
  • 这是因为在绘制一条新的线段之前,需要先初始化工具箱中的样式设置。
  • 也就是所谓的,准备绘制一条新的线段:ctx.beginPath();
const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
ctx.stroke();

// 设置线段的开始(初始化工具箱)
ctx.beginPath();
ctx.moveTo(100, 200);
ctx.lineTo(200, 200);
ctx.lineWidth = 4;
ctx.strokeStyle = "#000";
ctx.stroke();const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
ctx.stroke();

// 设置线段的开始(初始化工具箱)
ctx.beginPath();
ctx.moveTo(100, 200);
ctx.lineTo(200, 200);
ctx.lineWidth = 4;
ctx.strokeStyle = "#000";
ctx.stroke();
  • 绘制效果:

img

  1. 还可以通过绘制连续线段,组合成几何图形,如三角形:
const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();

ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(100, 200);
ctx.lineTo(100, 100);

ctx.lineWidth = 8;
ctx.strokeStyle = "red";

ctx.stroke();const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();

ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(100, 200);
ctx.lineTo(100, 100);

ctx.lineWidth = 8;
ctx.strokeStyle = "red";

ctx.stroke();

绘制效果:

img

  • 注意细节:

img

  • 当最后一条线段的终点和第一条线段的起点重复时,并没有看到一种“闭合”的效果。
  • 这就需要我们主动设置连续线段的闭合:ctx.closePath()
  • 只要已经绘制了至少两条连续线段,就可以使用closePath进行闭合,它会自动将最后一条线段的终点和第一条线段的起点进行连接
const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();

ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(100, 200);
ctx.lineTo(100, 100);		// 本次绘制可以省略,亦可出现三角形效果

ctx.lineWidth = 8;
ctx.strokeStyle = "red";

// 闭合连续线段
ctx.closePath();

ctx.stroke();const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();

ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(100, 200);
ctx.lineTo(100, 100);		// 本次绘制可以省略,亦可出现三角形效果

ctx.lineWidth = 8;
ctx.strokeStyle = "red";

// 闭合连续线段
ctx.closePath();

ctx.stroke();
  • 绘制效果:

img

五、端点样式

  1. 在绘制连续线段时,canvas提供了多种线段连接点的样式处理
    • 属性为:ctx.lineJoin = '值'
      • 尖角:miter(默认)
      • 圆角:round
      • 斜角:bevel
const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 150);
ctx.lineTo(100, 200);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
// 设置尖角
ctx.lineJoin = 'miter';
ctx.stroke();

ctx.beginPath();
ctx.moveTo(200, 100);
ctx.lineTo(300, 150);
ctx.lineTo(200, 200);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
// 设置圆角
ctx.lineJoin = 'round';
ctx.stroke();

ctx.beginPath();
ctx.moveTo(300, 100);
ctx.lineTo(400, 150);
ctx.lineTo(300, 200);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
// 设置斜角
ctx.lineJoin = 'bevel';
ctx.stroke();const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 150);
ctx.lineTo(100, 200);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
// 设置尖角
ctx.lineJoin = 'miter';
ctx.stroke();

ctx.beginPath();
ctx.moveTo(200, 100);
ctx.lineTo(300, 150);
ctx.lineTo(200, 200);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
// 设置圆角
ctx.lineJoin = 'round';
ctx.stroke();

ctx.beginPath();
ctx.moveTo(300, 100);
ctx.lineTo(400, 150);
ctx.lineTo(300, 200);
ctx.lineWidth = 10;
ctx.strokeStyle = "red";
// 设置斜角
ctx.lineJoin = 'bevel';
ctx.stroke();

效果如图:

img

  1. 在绘制单条线段时,线段两端的样式也可以被控制
    • 属性:ctx.lineCap = '值'
      • butt,无,默认
      • round,圆
      • square,方
const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

console.log(ctx);

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineWidth = 20;
ctx.strokeStyle = "red";
// 无
ctx.lineCap = "butt";
ctx.stroke();

ctx.beginPath();
ctx.moveTo(100, 150);
ctx.lineTo(200, 150);
ctx.lineWidth = 20;
ctx.strokeStyle = "red";
// 圆
ctx.lineCap = "round";
ctx.stroke();

ctx.beginPath();
ctx.moveTo(100, 200);
ctx.lineTo(200, 200);
ctx.lineWidth = 20;
ctx.strokeStyle = "red";
// 方
ctx.lineCap = "square";
ctx.stroke();const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

console.log(ctx);

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineWidth = 20;
ctx.strokeStyle = "red";
// 无
ctx.lineCap = "butt";
ctx.stroke();

ctx.beginPath();
ctx.moveTo(100, 150);
ctx.lineTo(200, 150);
ctx.lineWidth = 20;
ctx.strokeStyle = "red";
// 圆
ctx.lineCap = "round";
ctx.stroke();

ctx.beginPath();
ctx.moveTo(100, 200);
ctx.lineTo(200, 200);
ctx.lineWidth = 20;
ctx.strokeStyle = "red";
// 方
ctx.lineCap = "square";
ctx.stroke();

效果如图:

img

  • 注意:

    • square 和 round 会让线段稍稍变长
    • 线段端点样式的颜色会和线段颜色保持一致

六、填充

  1. 当使用连续线段绘制出几何图形后,还可以对图形进行颜色填充
    • 设置填充颜色:ctx.fillStyle = '颜色值'
    • 填充:ctx.fill()
const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(200, 200);
ctx.lineTo(100, 100);
ctx.closePath();

ctx.fillStyle = "pink";
ctx.fill();const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(200, 200);
ctx.lineTo(100, 100);
ctx.closePath();

ctx.fillStyle = "pink";
ctx.fill();

效果如图:

img

  • 注意:填充时可以不进行路径闭合,填充方法会自动闭合路径后,再进行填充
  1. 填充和描边可以同时使用
const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(200, 200);
ctx.lineTo(100, 100);
ctx.closePath();
// 设置路径样式
ctx.lineWidth = 10;
ctx.strokeStyle = "black";
ctx.stroke();
// 设置填充样式
ctx.fillStyle = "pink";
ctx.fill();const canvas = document.querySelector(".mycanvas");

const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(200, 200);
ctx.lineTo(100, 100);
ctx.closePath();
// 设置路径样式
ctx.lineWidth = 10;
ctx.strokeStyle = "black";
ctx.stroke();
// 设置填充样式
ctx.fillStyle = "pink";
ctx.fill();

效果如图:

img

七、canvas 的填充规则 - 非零填充(了解)

示例1

  • 绘制一个 “回” 形
  • 注意一个细节 :
    1. 里面的小正方形我们会按照 顺时针 的方向绘制
    2. 外面的大正方形我们也会按照 顺时针 的方向绘制
// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 开始绘制里面的小正方形
ctx.moveTo(200, 100);
ctx.lineTo(300, 100);
ctx.lineTo(300, 200);
ctx.lineTo(200, 200);

// 3. 开始绘制外面的大正方形
ctx.moveTo(150, 50);
ctx.lineTo(350, 50);
ctx.lineTo(350, 250);
ctx.lineTo(150, 250);

// 线段样式
ctx.lineWidth = 2;
ctx.strokeStyle = '#000';
ctx.stroke();// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 开始绘制里面的小正方形
ctx.moveTo(200, 100);
ctx.lineTo(300, 100);
ctx.lineTo(300, 200);
ctx.lineTo(200, 200);

// 3. 开始绘制外面的大正方形
ctx.moveTo(150, 50);
ctx.lineTo(350, 50);
ctx.lineTo(350, 250);
ctx.lineTo(150, 250);

// 线段样式
ctx.lineWidth = 2;
ctx.strokeStyle = '#000';
ctx.stroke();

img

  • 填充后看效果
// 4. 填充
ctx.fill()// 4. 填充
ctx.fill()

img

  • 我们发现,两个都被填充了

  • 这是因为,在填充的时候,就是会一次性把所有的内容都会填充好

  • 注意 :

    • 和是否闭合路径 ( ctx.closePath() ) 没有关系
    • 和里外正方形的绘制先后顺序没有关系

示例2

  • 再绘制一个 “回” 形
  • 注意一个细节:
    1. 里面的小正方形我们会按照 逆时针 的方向绘制
    2. 外面的大正方形我们也会按照 顺时针 的方向绘制
// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 开始绘制里面的小正方形
ctx.moveTo(200, 100);
ctx.lineTo(200, 200);
ctx.lineTo(300, 200);
ctx.lineTo(300, 100);

// 3. 开始绘制外面的大正方形
ctx.moveTo(150, 50);
ctx.lineTo(350, 50);
ctx.lineTo(350, 250);
ctx.lineTo(150, 250);

// 线段样式
ctx.lineWidth = 2;
ctx.strokeStyle = '#000';
ctx.stroke();// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 开始绘制里面的小正方形
ctx.moveTo(200, 100);
ctx.lineTo(200, 200);
ctx.lineTo(300, 200);
ctx.lineTo(300, 100);

// 3. 开始绘制外面的大正方形
ctx.moveTo(150, 50);
ctx.lineTo(350, 50);
ctx.lineTo(350, 250);
ctx.lineTo(150, 250);

// 线段样式
ctx.lineWidth = 2;
ctx.strokeStyle = '#000';
ctx.stroke();

img

填充后看效果:

// 4. 填充
ctx.fill()// 4. 填充
ctx.fill()

img

  • 此时发现,和刚才填充出来的结果不一样了
  • 可以得出结论:填充的区域和线段绘制时的 顺时针 逆时针 方向有关系!

非零填充

  1. 其实我们的填充和顺时针逆时针有关系,但不是简单的顺逆时针的问题
  2. 非零填充的概念 :
    • 从任何一个区域向画布最外层移动
    • 按照经历最少的边数量计算
    • 其中经历的顺时针边,记录为 +1
    • 经历的逆时针边,记录为 -1
    • 只要最终总和不为 零,那么该区域填充
    • 如果最终总和为 零,那么该区域不填充

示例3:

  • 这次我们绘制一个稍微复杂一些的图形

img

  • 这是两个矩形对接在一起, 一个是顺时针绘制, 一个是逆时针绘制
  • 我们来分析一下看看
  • 首先, 最左侧封闭图形区域

img

    • 如果走最短的路线出来的话,会经历一条顺时针的边
    • 记录为 +1
    • 最终为 +1
    • 所以该区域会被填充
  • 然后, 最右侧封闭图形

img

    • 经历最短路线出来的话,会经历一条逆时针的边
    • 记录为 -1
    • 最终为 -1
    • 所以该区域会被填充
  • 最后, 中间的封闭图形

img

    • 经历最短路线出来的话,必然会经历一条顺时针的边 和 一条逆时针的边
    • 顺时针记录为 +1
    • 逆时针记录为 -1
    • 合计为 0
    • 所以该区域不会被填充
  • 实际测试

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 开始绘制里面的小正方形
// 顺时针绘制
ctx.moveTo(100, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 150);
ctx.lineTo(100, 150);
ctx.lineTo(100, 50);
// 逆时针绘制
ctx.moveTo(150, 100);
ctx.lineTo(150, 200);
ctx.lineTo(250, 200);
ctx.lineTo(250, 100);
ctx.lineTo(150, 100);

ctx.lineWidth = 2;
ctx.strokeStyle = '#000';
ctx.stroke();

// 4. 填充
ctx.fill()// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 开始绘制里面的小正方形
// 顺时针绘制
ctx.moveTo(100, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 150);
ctx.lineTo(100, 150);
ctx.lineTo(100, 50);
// 逆时针绘制
ctx.moveTo(150, 100);
ctx.lineTo(150, 200);
ctx.lineTo(250, 200);
ctx.lineTo(250, 100);
ctx.lineTo(150, 100);

ctx.lineWidth = 2;
ctx.strokeStyle = '#000';
ctx.stroke();

// 4. 填充
ctx.fill()
  • 效果如图:

img

八、绘制矩形

  • 绘制矩形的方法有三个:
  1. 矩形路径:
    • 语法:ctx.rect( 矩形起点 x 轴坐标, 矩形起点 y 轴坐标, 矩形宽度, 矩形高度 )
    • 如:ctx.rect(100, 100, 100, 100)
    • 表示在坐标 100,100 的位置绘制一个 100*100 的矩形路径,默认无填充无描边
    • 可通过ctx.stroke()描边,通过ctx.fill()填充
    • 可通过lineWidthstrokeStylefillStyle属性,分别设置描边宽度,描边色,填充色
  1. 描边矩形:
    • 语法:ctx.strokeRect( 矩形起点 x 轴坐标, 矩形起点 y 轴坐标, 矩形宽度, 矩形高度 )
    • 如:ctx.strokeRect(300, 100, 100, 100)
    • 表示在坐标 300,100 的位置绘制一个 100*100 的描边矩形,无填充
    • 可通过lineWidthstrokeStyle属性,设置描边宽度,描边色
  1. 填充矩形:
    • 语法:ctx.fillRect( 矩形起点 x 轴坐标, 矩形起点 y 轴坐标, 矩形宽度, 矩形高度 )
    • 如:ctx.fillRect(500, 100, 100, 100)
    • 表示在坐标 500,100 的位置绘制一个 100*100 的填充矩形,无描边
    • 可通过fillStyle属性,设置填充色
// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 绘制矩形路径
ctx.rect(100, 100, 100, 100);

// 3. 绘制描边矩形
ctx.strokeRect(300, 100, 100, 100);

// 4. 绘制填充矩形
ctx.fillRect(500, 100, 100, 100);// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 绘制矩形路径
ctx.rect(100, 100, 100, 100);

// 3. 绘制描边矩形
ctx.strokeRect(300, 100, 100, 100);

// 4. 绘制填充矩形
ctx.fillRect(500, 100, 100, 100);

效果如图:

img

九、绘制圆形

  1. 什么是圆:
    • 圆 就是从一个点出发,按照半径,画弧线,当弧线饶了一圈回到原点的时候,就是一个圆形。
    • 在canvas内,绘制圆形,其实就是在绘制弧线
  1. 什么是弧度
    • 这是一个圆,圆心为 o,半径为 r

img

    • 以圆心 o 做坐标轴,x 轴正方向上和圆周的交点为弧度起点

img

    • 在圆周上,从弧度起点,顺着圆周移动,移动的距离成为弧长,当弧长和半径一样时
    • 这段弧长所对应的圆心角是 1 弧度

img

    • 根据圆周公式 : 周长 = 2 * π * r
    • 所以:
      • 一个圆周是 : 2 * π
      • 半个圆周是 : π
      • 四分之一圆周是 : π / 2
  1. 了解了什么是弧度,接下来就可以开始绘制弧线了,绘制弧线有两种方式:圆弧,椭圆弧
    • 圆弧
      • 语法:ctx.arc( x, y, r, startAngle, endAngle, counterclockwise )
        • x:圆心的 x 轴坐标
        • y:圆心的 y 周坐标
        • r:圆的半径
        • startAngle:绘制弧线的起点弧度
        • endAngle:绘制弧线的终点弧度
        • counterclockwise:方向,false 为顺时针(默认),true 为逆时针
      • 如:ctx.arc( 150, 150, 100, 0, 1, false )
      • 表示绘制一个圆心在坐标 150,150,半径100,从 0 顺时针 到 1 的弧线路径,默认无填充无描边
// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 绘制圆弧
ctx.arc( 150, 150, 100, 0, Math.PI, false )

// 3. 描边
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.stroke();// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 绘制圆弧
ctx.arc( 150, 150, 100, 0, Math.PI, false )

// 3. 描边
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.stroke();

img

    • 椭圆弧
      • 语法:ctx.ellipse( x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise )
        • x:椭圆中心点的 x 轴坐标
        • y:椭圆中心点的 y 轴坐标
        • radiusX:椭圆在 x 轴方向上的半径
        • radiusY:椭圆在 y 轴方向上的半径
        • rotation:旋转弧度,指讲该椭圆进行旋转
        • startAngle:弧线开始弧度
        • endAngle:弧线结束弧度
        • antiClockwise:方向,false 表示逆时针绘制(默认),true 表示顺时针绘制
      • 如:ctx.ellipse( 300, 150, 200, 100, 0, 0, Math.PI * 2, false )
      • 表示绘制一个圆心在坐标 350,150,x轴半径200,y轴半径为100,不旋转,从 0 顺时针 到 Math.PI * 2 的弧线路径,默认无填充无描边
// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 绘制椭圆弧
ctx.ellipse( 300, 150, 200, 100, 0, 0, Math.PI * 2, false )

// 3. 描边
ctx.lineWidth = 2;
ctx.stroke();// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 绘制椭圆弧
ctx.ellipse( 300, 150, 200, 100, 0, 0, Math.PI * 2, false )

// 3. 描边
ctx.lineWidth = 2;
ctx.stroke();

img

      • 这样一个椭圆就出来了,解释一下这些内容的意义

img

      • 旋转弧度,就是在现在的基础上,让整个图形进行旋转
ctx.ellipse( 300, 150, 200, 100, Math.PI / 2, 0, Math.PI * 2, false )ctx.ellipse( 300, 150, 200, 100, Math.PI / 2, 0, Math.PI * 2, false )

img

十、擦除画布

  1. 就像画画时的橡皮擦,擦除掉指定区域的内容
  2. 语法:工具箱.clearRect( 矩形起点 x 轴坐标, 矩形起点 y 轴坐标, 矩形宽度, 矩形高度 )
    • 如:工具箱.clearRect( 150, 150, 30, 30 )
    • 表示从坐标 150, 150 位置开始,擦除一块 30 * 30 的区域

img

  1. 注意:
    • clearRect默认只能擦除填充和描边,并不能擦除路径
    • canvas中的绘制方法(如stroke,fill),会以“上一次 beginPath 之后的所有路径为基础进行绘制
    • 如果没有使用beginPath()方法,上一次描述的路径没有被清除,这一次进行描边等操作还会绘制出之前的路径,表现出一种类似没有擦除的状态。
    • 所以为了彻底擦除,在使用了clearRect后,一般都会再执行一次beginPath方法

十一、绘制文字

  1. 在canvas内可以直接绘制文字,不需要通过线段一笔一划的写出来
    • 描边文字(空心):
      • 语法:ctx.strokeText("文字内容", x 坐标, y 坐标);
    • 填充文字(实心):
      • 语法:ctx.fillText("文字内容", x 坐标, y 坐标);
  // 0. 获取到页面上的 canvas 标签元素节点
  const canvasEle = document.querySelector('.mycanvas')

  // 1. 获取当前这个画布的工具箱
  const ctx = canvasEle.getContext('2d')

  // 2. 绘制文字
  ctx.fillText("千锋", 100, 100);
  ctx.strokeText("数字智慧大前端", 100, 200);  // 0. 获取到页面上的 canvas 标签元素节点
  const canvasEle = document.querySelector('.mycanvas')

  // 1. 获取当前这个画布的工具箱
  const ctx = canvasEle.getContext('2d')

  // 2. 绘制文字
  ctx.fillText("千锋", 100, 100);
  ctx.strokeText("数字智慧大前端", 100, 200);

img

  1. 文字样式修饰
    • 字体大小:ctx.font = '字体大小 字体'
    • 文字水平对齐:ctx.textAlign = 'left | center | right';
    • 文字垂直对齐:ctx.textBaseline = 'top | middle | bottom';
    • 注意:文字的样式修饰需要在绘制之前设置
// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 设置文字样式
ctx.font = "50px 黑体";
ctx.fillText("千锋", 100, 100);

// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas')

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d')

// 2. 设置文字样式
ctx.font = "50px 黑体";
ctx.fillText("千锋", 100, 100);

img

水平对齐:ctx.textAlign垂直对齐:ctx.textBaseline
start:文本在指定的位置开始end:文本在指定的位置结束center:居中对齐left:左对齐right:右对齐alphabetic:默认,文本基线是普通的字母基线top:文本基线是 em 方框的顶端hanging:文本基线是悬挂基线middle:文本基线是 em 方框的正中ideographic:文本基线是 em 基线bottom:文本基线是 em 方框的底端
imgimg
  1. 获取文本信息
    • 语法:ctx.measureText("文本")
    • 获取文本宽度:ctx.measureText("前端").width
    • 用于给文本添加下划线,或边框线等操作

十二、阴影

  1. 在canvas中,还可以对路径添加阴影效果(文字也可以有阴影)
    • 阴影 x 轴偏移:ctx.shadowOffsetX = number;
    • 阴影 y 轴偏移:ctx.shadowOffsetY = number;
    • 模糊大小:ctx.shadowBlur = number;
    • 阴影颜色:ctx.shadowColor = '颜色值';
// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas');

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d');

// 2. 设置阴影效果
ctx.shadowOffsetX = 30;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 2;
ctx.shadowColor = "#aaa";

// 3. 绘制文本
ctx.font = "50px 黑体";
ctx.strokeText("千锋", 100, 50);
ctx.fillText("千锋", 300, 50);

// 4. 绘制矩形
ctx.strokeRect( 100, 80, 100, 50);
ctx.fillRect( 300, 80, 100, 50);

// 5. 绘制圆
ctx.arc( 150, 200, 50, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc( 350, 200, 50, 0, 2 * Math.PI);
ctx.fill();// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas');

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d');

// 2. 设置阴影效果
ctx.shadowOffsetX = 30;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 2;
ctx.shadowColor = "#aaa";

// 3. 绘制文本
ctx.font = "50px 黑体";
ctx.strokeText("千锋", 100, 50);
ctx.fillText("千锋", 300, 50);

// 4. 绘制矩形
ctx.strokeRect( 100, 80, 100, 50);
ctx.fillRect( 300, 80, 100, 50);

// 5. 绘制圆
ctx.arc( 150, 200, 50, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc( 350, 200, 50, 0, 2 * Math.PI);
ctx.fill();

效果如图:

img

  • 如需不同的阴影效果,每次绘制前可以重新配置

十三、绘制虚线

  • 语法:工具箱.setDashLine([ 第一段长度, 第二段长度, ... ])
  • 如:ctx.setDashLine([5, 10])
  • 表示绘制出的虚线为:实5,虚10,实5,虚10,…
// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas');

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d');
console.log(ctx);

// 2. 设置虚线方案:实线5px 和 虚线10px 重复出现
ctx.setLineDash([ 5, 10 ]);

// 3. 绘制线段
ctx.moveTo( 100, 50 );
ctx.lineTo( 400, 50 );
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();

// 4. 绘制文本
ctx.font = "100px 黑体";
ctx.strokeText("千锋", 100, 200);// 0. 获取到页面上的 canvas 标签元素节点
const canvasEle = document.querySelector('.mycanvas');

// 1. 获取当前这个画布的工具箱
const ctx = canvasEle.getContext('2d');
console.log(ctx);

// 2. 设置虚线方案:实线5px 和 虚线10px 重复出现
ctx.setLineDash([ 5, 10 ]);

// 3. 绘制线段
ctx.moveTo( 100, 50 );
ctx.lineTo( 400, 50 );
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();

// 4. 绘制文本
ctx.font = "100px 黑体";
ctx.strokeText("千锋", 100, 200);

注意:填充无法设置虚线效果

十四、总结

  1. 创建一个画笔对象(获取工具箱)
    • const ctx = canvasElement.getContext('2d');
  1. 常用属性
    • 线条粗细:ctx.lineWidth = number;
    • 描边色:ctx.strokeStyle = '颜色值';
    • 端点样式:ctx.lineCap = 'butt | round | square';
    • 接洽点样式:ctx.lineJoin = 'miter | bevel | round';
    • 填充色:ctx.fillStyle = '颜色值';
    • 字体大小:ctx.font = '字体大小 字体';
    • 文字水平对齐:ctx.textAlign = 'left | center | right';
    • 文字垂直对齐:ctx.textBaseline = 'top | middle | bottom';
    • 阴影 x 轴偏移:ctx.shadowOffsetX = number;
    • 阴影 y 轴偏移:ctx.shadowOffsetY = number;
    • 模糊大小:ctx.shadowBlur = number;
    • 阴影颜色:ctx.shadowColor = '颜色值';
  1. 常用方法
    • 下次绘制开启新路径(弃用已存在路径):ctx.beginPath();
    • 开始绘制线段的起点:ctx.moveTo(x, y);
    • 连线到:ctx.lineTo(x, y);
    • 闭合路径:ctx.closePath();
    • 描边:ctx.stroke();
    • 填充:ctx.fill();
    • 矩形路径:ctx.rect(x, y, w, h);
    • 描边矩形:ctx.strokeRect(x, y, w, h);
    • 填充矩形:ctx.fillRect(x, y, w, h);
    • 圆弧路径:ctx.arc(cx, cy, r, start, end, false);
    • 椭圆弧路径:ctx.ellipse( cx, cy, xr, yr, rotate, start, end, false );
    • 擦除:ctx.clearRect(x, y, w, h);
    • 填充文字:ctx.fillText(string, x, y);
    • 描边文字:ctx.strokeText(string, x, y);
    • 获取文字信息:ctx.measureText('测试');
    • 设置虚线:setLineDash([线段1宽度, 线段2宽度, ...])

十五:作业

ctx.strokeText(“千锋”, 100, 200);


注意:**填充无法设置虚线效果**

# 十四、总结

1. 创建一个画笔对象(获取工具箱)

- - `const ctx = canvasElement.getContext('2d');`

1. 常用属性

- - 线条粗细:`ctx.lineWidth = number;`
  - 描边色:`ctx.strokeStyle = '颜色值';`
  - 端点样式:`ctx.lineCap = 'butt | round | square';`
  - 接洽点样式:`ctx.lineJoin = 'miter | bevel | round';`
  - 填充色:`ctx.fillStyle = '颜色值';`
  - 字体大小:`ctx.font = '字体大小 字体';`
  - 文字水平对齐:`ctx.textAlign = 'left | center | right';`
  - 文字垂直对齐:`ctx.textBaseline = 'top | middle | bottom';`
  - 阴影 x 轴偏移:`ctx.shadowOffsetX = number;`
  - 阴影 y 轴偏移:`ctx.shadowOffsetY = number;`
  - 模糊大小:`ctx.shadowBlur = number;`
  - 阴影颜色:`ctx.shadowColor = '颜色值';`

1. 常用方法

- - 下次绘制开启新路径(弃用已存在路径):`ctx.beginPath();`
  - 开始绘制线段的起点:`ctx.moveTo(x, y);`
  - 连线到:`ctx.lineTo(x, y);`
  - 闭合路径:`ctx.closePath();`
  - 描边:`ctx.stroke();`
  - 填充:`ctx.fill();`
  - 矩形路径:`ctx.rect(x, y, w, h);`
  - 描边矩形:`ctx.strokeRect(x, y, w, h);`
  - 填充矩形:`ctx.fillRect(x, y, w, h);`
  - 圆弧路径:`ctx.arc(cx, cy, r, start, end, false);`
  - 椭圆弧路径:`ctx.ellipse( cx, cy, xr, yr, rotate, start, end, false );`
  - 擦除:`ctx.clearRect(x, y, w, h);`
  - 填充文字:`ctx.fillText(string, x, y);`
  - 描边文字:`ctx.strokeText(string, x, y);`
  - 获取文字信息:`ctx.measureText('测试');`
  - 设置虚线:`setLineDash([线段1宽度, 线段2宽度, ...])`

# 十五:作业

![img](https://img-blog.csdnimg.cn/img_convert/e1b4bc900b624636bb4371c576e19483.png)![img](https://img-blog.csdnimg.cn/img_convert/2a9c80b7b6a33e97f0d117da0f59f831.png)![img](https://img-blog.csdnimg.cn/img_convert/8613e17597f3c256d5f2b57a886a6ba9.png)
ul.head{ border-style: groove; position: fixed; top:0; width:99%; list-style-type: none; margin-top:0; padding-left: 0px; /*创建一个新的BFC*/ overflow: hidden; background:indianred; border-radius:8px;/*圆角边框*/ font-size:18px; } li a{ /* !*实现横向导航栏, display:inline;也可,如果你想链接到具有相同的大小,你必须使用浮动的方法*!*/ float:left; height:20px; text-align: center; color: white; background-color: darksalmon; padding: 12px 20px; text-decoration: none; } li a:hover{ background-color: coral; color: black; } body{ background-attachment: fixed; background-image: url(../resources/image/blue_pink.jpg); background-size:100%; } iframe{ height:300px; width:400px; float:right; } select{ font-size: 20px; color:olive; background-color:cornsilk; } div{ margin-top:60px; } input{font-size: 16px;} button { border-radius:5px;/*圆角边框*/ font-size: 18px; background-color: #4CAF50; color: white; cursor: pointer; transition: background-color 0.3s ease; } #pauseBtn{ position:absolute; bottom:17%; left:40%; } #startBtn{ position:absolute; bottom:59%; left:15%; } #increase-btn1 { position:absolute; left: 17%; bottom:9%; } #decrease-btn1 { position:absolute; left: 19%; bottom:9%; } #increase-btn2 { position:absolute; right: 19%; bottom:25%; } #decrease-btn2 { position:absolute; right: 17%; bottom:25%; } #speedRange{ position:absolute; bottom:17%; left:50%; } button:hover { background-color: #3e8e41; } #introduction{ margin-top: 20px; width:300px; border-style: groove; font-size: 14px; color:darkred; overflow: auto; min-height:300px; max-height:300px; } footer{ position:absolute; bottom:0; width:100%; height:30px; text-align: center; } #canvas{ /*width: 880px;*/ /*height: 440px;*/ width: 100%; height: 100%; /*margin: -210px auto;*/ /*border: 2px solid yellowgreen;*/ /*display: block;*/ overflow: scroll; } .container { height: 440px; width: 880px; margin: -225px auto; border: 2px solid yellowgreen; /*display: block;*/ box-sizing:border-box; overflow: scroll; } #log{ border:2px palegoldenrod; position:absolute; top:9%; right:0.5%; background-color:lightyellow; height: 430px; /* 设置固定高度 */ width: 310px; /* 设置固定宽度 */ font-size: 16px; /* 设置默认字体大小 */ resize: none; /* 其他样式属性 */ } .red-text { color: red; }
06-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值