# 04 Canvas 基础用法

## 基础用法

### 属性介绍

`<canvas>` 标签只有两个可选的属性 `width` 和 `height`。当没有设置宽度和高度的时候，canvas 会初始化宽度为 300 像素和高度为 150 像素。宽高属性会自动忽略单位，以像素展示，所以使用 em 或 rem 等单位无效。

在视觉表现上，CSS 的宽高属性权重要高于 `<canvas>` 标签的宽高权重。可以将 `<canvas>` 看作 `<img>` 元素，主要区别是 `<canvas>` 的等比例特性是强制的，会忽略 HTML 属性的设置，但 `<img>` 不会。

```markup
<img src="1.jpg" width="300" height="150" style="height:100px;" />
<canvas width="300" height="150" style="height:100px;"></canvas>
```

如上代码所示，此时 `<img>` 宽度不会随高度缩放，最终以 `300x100` 尺寸显示，而 `<canvas>` 宽度会按高度等比例缩放，以 `200x100` 尺寸显示。

需要注意：在使用 Canvas API 绘制图像时，是以 HTML 的宽高属性为原点，与 CSS 属性无关。

可以在 `<canvas>` 标签中提供替换内容。不支持的浏览器将会忽略容器并在其中渲染后备内容。

```markup
<canvas width="150" height="150">
  你的浏览器不支持 canvas，请升级你的浏览器
</canvas>
```

### 渲染上下文

`<canvas>` 标签创建画布，并公开渲染上下文（The rendering context），用来绘制内容。使用方法 `getContext()` 可以获取渲染上下文对象，该方法接受一个参数表示上下文格式，一般传入 `2d`，当然还有 `3d` 模式，这里不细谈。

```javascript
const canvas = document.getElementById("yoo")
const ctx = canvas.getContext("2d")
```

### 绘制图形

#### 绘制矩形

原生 canvas 只支持一种图形绘制：矩形。所有其他的图形的绘制都至少需要生成一条路径。canvas 提供了三种方法绘制矩形：

* `fillRect(x, y, width, height)`： 绘制一个填充矩形
* `strokeRect(x, y, width, height)`： 绘制一个矩形边框
* `clearRect(x, y, width, height)`： 清除指定矩形区域，使之变透明

三种方式示例如下：

```javascript
ctx.fillStyle = "#fb618d"
ctx.fillRect(50, 50, 200, 200)
ctx.clearRect(70, 70, 160, 160)
ctx.strokeRect(90, 90, 120, 120)
```

![三种方式绘制矩形](https://3391885248-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M3u58XVueZWq507CWAj%2Fuploads%2Fgit-blob-43b2827e667dca21cab1867f39c1eea263ebb35f%2F%E4%B8%89%E7%A7%8D%E6%96%B9%E5%BC%8F%E7%BB%98%E5%88%B6%E7%9F%A9%E5%BD%A2.png?alt=media)

#### 绘制路径

图形的基本元素是路径，使用路径绘制图形的步骤如下：

1. 创建路径起始点
2. 画出路径
3. 将路径封闭
4. 描边或填充路径区域

整个步骤需要使用一下函数：

1. `beginPath()`：新建一条新路径
2. `closePath()`：闭合路径
3. `stroke()`：通过线条来绘制图形轮廓
4. `fill()`：通过填充路径的内容区域生成实心图形
5. `moveTo(x, y)`：移动笔触到指定坐标
6. `lineTo(x, y)`：绘制一条从当前位置到指定坐标的直线
7. `arc(x, y, radius, startAngle, endAngle, anticlockwise)`：绘制圆弧，`anticlockwise` 为 true 时逆时针，默认为顺时针。

当 canvas 初始化或者 `beginPath()` 调用后，通常会使用 `moveTo()` 函数设置起点。或者使用该方法绘制不连续的路径。

示例 1：绘制三角形

```javascript
// 填充三角形
ctx.beginPath()
ctx.moveTo(40, 40)
ctx.lineTo(220, 40)
ctx.lineTo(40, 220)
ctx.fill()

// 描边三角形
ctx.beginPath()
ctx.moveTo(260, 260)
ctx.lineTo(260, 80)
ctx.lineTo(80, 260)
ctx.closePath()
ctx.stroke()
```

注意到填充三角形和描边三角形有些不同，当路径使用填充 `fill()` 时会自动闭合，而使用描边 `stroke()` 时则不会闭合路径，所以需要调用 `closePath()` 方法。

![绘制三角形](https://3391885248-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M3u58XVueZWq507CWAj%2Fuploads%2Fgit-blob-c9ab65b88b581ffa6732e2b8a146c8e55f366c88%2F%E7%BB%98%E5%88%B6%E4%B8%89%E8%A7%92%E5%BD%A2.png?alt=media)

示例 2：绘制笑脸

```javascript
ctx.beginPath()
ctx.moveTo(260, 150)
ctx.arc(150, 150, 110, 0, Math.PI * 2, true) // 脸
ctx.moveTo(220, 150)
ctx.arc(150, 150, 70, 0, Math.PI, false) // 嘴
ctx.moveTo(120, 110)
ctx.arc(110, 110, 10, 0, Math.PI * 2, false) // 左眼
ctx.moveTo(200, 110)
ctx.arc(190, 110, 10, 0, Math.PI * 2, false) // 右眼
ctx.stroke()
```

![笑脸](https://3391885248-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M3u58XVueZWq507CWAj%2Fuploads%2Fgit-blob-3b1491c147f649bd16d8ca84c2ac72afe5343ed5%2F%E7%AC%91%E8%84%B8.png?alt=media)

### 贝塞尔曲线

canvas 里使用二次贝塞尔曲线和三次贝塞尔曲线可以用来绘制复杂的图形。

canvas API `quadraticCurveTo(cp1x, cp1y, x, y)`，用来绘制二次贝塞尔曲线，`cp1x,cp1y` 为控制点，`x,y` 为结束点。

![二次贝塞尔曲线](https://3391885248-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M3u58XVueZWq507CWAj%2Fuploads%2Fgit-blob-b6a80dc18faf967f18c2c22426607aa4b5eed062%2F%E4%BA%8C%E6%AC%A1%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF.gif?alt=media)

canvas API `bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)`，用来绘制三次贝塞尔曲线，`cp1x,cp1y` 为控制点一，`cp2x,cp2y` 为控制点二，`x,y` 为结束点。

![三次贝塞尔曲线](https://3391885248-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M3u58XVueZWq507CWAj%2Fuploads%2Fgit-blob-e003f9d25f9e5b73c0d570ec00be9232330c14a2%2F%E4%B8%89%E6%AC%A1%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF.gif?alt=media)

关于贝塞尔曲线的使用，这里不再细研究\~\~（看得头痛）\~\~，下次如有机会再说。

### Path2D

之前所介绍的 canvas API 都是使用路径和绘画命令来把对象“画”在画布上，不能复用命令。较新的浏览器支持 Path2D 对象，用来缓存或记录绘画命令，这样可以复用路径，简化代码和优化性能。

`Path2D()` 会返回一个新初始化的 Path2D 对象，可能将某一个路径作为变量——创建一个它的副本，或者将一个包含 SVG path 数据的字符串作为变量。

```javascript
new Path2D() // 空的Path对象
new Path2D(path) // 克隆Path对象
new Path2D("M10 10 h 80 v 80 h -80 Z") // 从SVG建立Path对象
```

之前介绍的所有 canvas API 都可以在生成的 Path2D 对象上使用。

```javascript
const rectangle = new Path2D()
rectangle.rect(10, 10, 50, 50)

const circle = new Path2D()
circle.moveTo(125, 35)
circle.arc(100, 35, 25, 0, 2 * Math.PI)

ctx.stroke(rectangle)
ctx.fill(circle)
```
