meface/docs/article/visual/canvas_base.md

445 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Canvas API 速食
date: 2020-12-16
author: ac
tags:
- Canvas
- JavaScript
categories:
- 可视化
---
> Canvas API接口是使用`<canvas>`标签,通过`JavaScript`来绘制图形用于动画、数据可视化、图片编辑、游戏画面以及实时视频等功能的Web API接口。
<!-- more -->
`<canvas>`标签是`H5`中新添加的元素。可以将`<canvas>`标签看作图形的容器(画布),在上面绘制路径、圆、盒子、字符等。
### 一、创建画布
```html
<canvas id='cnv' width='500' height='300'></canvas>
```
`<canvas>`标签实际上只有两个属性`width`和`height``id`属性是每个HTML元素都有的默认属性但这是可选的如果不指定则`width`默认为300像素`heigth`默认为150像素。除了在标签内的属性中定义大小外也可以使用`CSS`样式定义。
> 如果`CSS`样式是以响应式或百分比的形式定义的大小,当`<canvas>`元素的尺寸与原始尺寸不等比例伸缩时,画布内已绘制的图像会出现变形。
被创建的`<canvas>`画布,在没有设置样式前,默认是完全透明的。
### 二、渲染上下文
`<canvas>`元素创建来一个固定大小的画布,它公开了一个或多个**渲染上下文**,用来绘制和处理要展示的内容。
**渲染上下文**的种类有很多但Canvas API主要聚焦于2D图形所以这里将注意力放在2D渲染上下文中。另外像同样是使用`<canvas>`元素的WebGL API 则是使用基于`OpenGL ES`的3D上下文。
渲染上下文可以通过Canvas元素的`getContext()`方法获得该方法是用来获得渲染上下和它的绘画功能。对于2D图像可以在`getContext()`方法中添加一个参数,来指定上下文格式。
```javascript
var canvas = document.getElementById('cnv');
var ctx = canvas.getContext('2d');//返回一个CanvasRenderingContext2D实例
```
> `CanvasRenderingContext2D`接口是Canvas API的一部分可为[`canvas`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/canvas)元素的绘图表面提供2D渲染上下文。 它用于绘制形状,文本,图像和其他对象。
### 三、Canvas坐标
![image-20201216110957358](./images/image-20201216110957358.png)
canvas 是一个二维网格grid
canvas 的左上角(原点)坐标为 (0,0),所有绘制的元素的位置都相对于原点坐标。
### 四、图形绘制
`<canvas>`只支持两种形式的图形绘制:矩形和路径(由一系列点连接成的线段)。
其他复杂的图形都是通过一条或多条路径组合而成的。不过`CanvasRenderingContext2D`已经提供了很多复杂图形路径生成的方法。
#### 矩形
`CanvasRenderingContext2D`中有三个绘制矩形的方法:
1. `fillRect(x, y, width, height)`:绘制填充矩形的方法。当前渲染上下文中的`fillStyle` 属性决定了对这个矩形的填充样式。
2. `clearRect(x, y, width, height)`:通过把矩形区域内的像素设置为透明以达到擦除一个矩形的效果,如果该矩形绘制在空白的区域,则看不出效果。
3. `strokeRect(x, y, width, height)`:使用当前的绘画样式,描绘一个左上角在 *(x, y)* 、宽度为 *width* 、高度为 *height* 的矩形边框的方法。该方法直接绘制到画布而不修改当前路径,因此任何后续[`fill()`](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/fill) 或[`stroke()`](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/stroke)调用对它没有影响。
![image-20201216142917015](./images/image-20201216142917015.png)
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>基本图形的绘制</title>
</head>
<body>
<canvas id="cnv"></canvas>
<script>
var cnv = document.getElementById("cnv");
var ctx = cnv.getContext("2d");
ctx.fillStyle = '#457b9d';//默认#000000
ctx.fillRect(5,5,100,100);
ctx.clearRect(30,30,50,50);
ctx.strokeRect(120,5,50,50);
</script>
</body>
</html>
```
#### 路径
图形的基本元素是路径。路径是通过不同**颜色**和**宽度**的线段或曲线相连形成的不同形状的点的集合。
> 注意:一个路径,甚至一个子路径(线段),都是**闭合**的。
使用路径绘制图形需要的步骤:
1. 首先创建路径起始点。
2. 然后使用[画图命令](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D#Paths)去画出路径。
3. 之后把路径封闭。
4. 一旦路径生成就能通过描边stroke或填充fill路径区域来渲染图形。
`CanvasRenderingContext2D`中绘制路径相关的方法:
- `beginPath()`:清空子路径列表,开始一个新的路径。
- `closePath()`:使画笔返回当前子路径的起始点(尝试从当前点到起始点绘制一条直线)。
- `moveTo(x,y)`将一个新的子路径的起始点移动到x,y坐标。常用于绘制不连续的路径。
- `lineTo(x,y)`:从子路径的最后一个点向(x,y)坐标绘制一条直线。
- `arc(x, y, radius, startAngle, endAngle[,anticlockwise])`:绘制圆弧路径,圆心(x,y)半径radius根据*anticlockwise*(默认为false顺时针)指定的方向从 *startAngle* 开始绘制,到 *endAngle* 结束。
- `arcTo(x1, y1, x2, y2, radius)`根据当前描点与给定的控制点1(x1,y1)连接的直线和控制点1与控制点2(x2,y2)连接的直线,作为使用指定半径的圆的**切线**,画出两条切线之间的弧线路径。
- `ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)`:添加椭圆的路径。
- `rect()`:创建一个矩形路径,矩形的起点位置是 *(x, y)* ,尺寸为 *width**height*
- `fill()`:使用当前的样式填充子路径。
- `stroke()`:根据当前的画线样式,绘制当前或已经存在的路径的方法。即将路径列表中的路径绘制在画布上
- `clip()`:从当前路径创建一个剪切路径。后续绘制的内容只会显示裁剪路径内的图形。
- `isPointInPath(x, y)`:判断在当前路径中是否包含检测点,图形的内部或边上。
- `isPointInStroke()`:判断检测点是否在路径的描边线上。
本质上,路径是由很多子路径(线段)构成的,这些子路径都在一个列表中,包含所有的子路径(线`line`、弧形`arc`、矩形`rect`、椭圆`ellipse`等)构成的图形。使用上面的方法重新解释使用路径绘制图形的步骤:
1. `beginPath`清空路径列表,准备绘制图形
2. 使用线`line`、弧形`arc`、矩形`rect`、椭圆`ellipse`等方法添加路径,可以使用`clip`进行裁剪需要显示的内容。
3. 闭合路径`closePath()`,这不是必需的。
4. 最后通过`stroke()`方法使用线性样式将路径在画布中绘制出来,或使用`fill()`方法使用填充样式将路径填充渲染在画布上。
> **当您调用fill()函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用`closePath()`函数。但是调用`stroke()`时不会自动闭合**。
![image-20201216172336777](./images/image-20201216172336777.png)
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>基本图形的绘制</title>
</head>
<body>
<canvas id="cnv"></canvas>
<div>x:<span id="x"></span></div>
<div>y:<span id="y"></span></div>
<script>
var cnv = document.getElementById("cnv");
var ctx = cnv.getContext("2d");
ctx.fillStyle = '#457b9d';//默认#000000
ctx.beginPath();
//---------------三角形---------------
//当前路径为空即调用beginPath()之后或者canvas刚建的时候
//第一条路径构造命令通常被视为是moveTo无论实际上是什么。
//ctx.lineTo(75, 50);
//ctx.lineTo(100, 75);
//ctx.lineTo(100, 25);
//ctx.fill();
//---------------笑脸---------------
ctx.arc(150,75,50,0,Math.PI*2,true);
ctx.moveTo(190,75);
ctx.arc(150,75,40,0,Math.PI,false);
ctx.moveTo(140,60);
ctx.arc(135,60,5,0,Math.PI*2,false);
ctx.moveTo(175,60);
ctx.arc(170,60,5,0,Math.PI*2,true);
ctx.stroke();
document.onmousemove = function(e){
document.getElementById('x').innerText = e.x;
document.getElementById('y').innerText = e.y;
//console.log(e);
}
</script>
</body>
</html>
```
#### Path2D对象
[`Path2D`](https://developer.mozilla.org/zh-CN/docs/Web/API/Path2D)对象已可以在较新版本的浏览器中使用,用来缓存或记录绘画命令(路径集合)。
```javascript
new Path2D(); // 空的Path对象
new Path2D(path); // 克隆Path对象
new Path2D(d); // 从SVG建立Path对象
```
`Path2D`实例可以使用所有路径绘制方法,如`moveTo`、`rect`、`lineTo`、`arc`等。同时也可以使用`Path2D.addPath(path[,transform])`方法叠加路径,其中可以添加一个转换矩阵。
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Path2D路径缓存</title>
</head>
<body>
<canvas id="cnv"></canvas>
<script>
var cnv = document.getElementById("cnv");
var ctx = cnv.getContext("2d");
ctx.fillStyle = '#457b9d';//默认#000000
ctx.beginPath();
//---------------Path---------------
var rectangle = new Path2D();
rectangle.rect(10,10,50,50);
var circle = new Path2D();
circle.moveTo(125,35);
circle.arc(100,35,25,0,Math.PI*2);
ctx.stroke(circle);
ctx.fill(rectangle);
</script>
</body>
</html>
```
#### 绘制文本
`CanvasRenderingContext2D`中提供三个关于绘制和测量文本的方法:
- `fillText(text,x,y[,maxWidth])`:在 *(x, y)*位置绘制填充文本text。
- `strokeText(text, x, y [, maxWidth])`:在 *(x, y)*位置对文本text进行描边。
- `measureText(text)`:返回一个关于被测量文本[`TextMetrics`](https://developer.mozilla.org/zh-CN/docs/Web/API/TextMetrics) 对象包含的信息(例如它的宽度)。
![image-20201227174130269](./images/image-20201227174130269.png)
```javascript
var cnv = document.getElementById("cnv");
var ctx = cnv.getContext("2d");
ctx.font = "48px serif";
ctx.strokeText("Hello world", 30, 50,200);
ctx.moveTo(30,50);
ctx.lineTo(230,50);
ctx.stroke();
ctx.fillText("Learning Canvas",30,120,200);
ctx.moveTo(30,120);
ctx.lineTo(230,120);
ctx.stroke();
```
### 五、样式与颜色
在绘制图形的时候,可以指定图形的样式,像填充样式`fillStyle`和边界轮廓样式 `strokeStyle`设置图形的颜色,或者使用`lineWidth`指定轮廓线的宽度等。
#### 颜色Color
canvas中使用的Color必须是符合[`CSS3`颜色值标准](https://www.w3.org/TR/css-color-3/)的有效字符串。如:
```javascript
ctx.fillStyle = "blue";
ctx.fillStyle = "#457b9d";
ctx.fillStyle = "rgb(69,123,157)";
ctx.fillStyle = "rgba(69,123,157,1)";
```
#### 样式属性
`CanvasRenderingContext2D`中与图形样式相关的属性可以分为:
1. 填充和描边样式
2. 线型样式
3. 文本样式
4. 渐变和图案
5. 阴影
##### 填充和描边样式
`fillStyle`:图形内部的颜色和样式,默认 `#000` (黑色)。
`strokeStyle`:图形轮廓边线的颜色和样式,默认`#000` (黑色)。
![image-20210108151817844](./images/image-20210108151817844.png)
```javascript
var cnv = document.getElementById("cnv");
var ctx = cnv.getContext("2d");
ctx.fillStyle = "#457b9d";
ctx.lineWidth = 20;
ctx.fillRect(50,50,100,50);
ctx.fill();
ctx.strokeRect(200,50,50,50);
ctx.stroke();
```
可以看到`fillStyle`只对填充的图形才有效,对封闭的路径的图形是没有效果的。`strokeStyle`只对路径有效。
##### 线型样式
`lineWidth`属性:线的宽度。***以路径为中心,两边各绘制一半线宽。***奇数值的线并不能精确呈现。
`lineJoin`属性:线末端的类型,可选值:`round`、`bevel`、`miter`。
![image-20210108165146464](./images/image-20210108165146464.png)
> `round`折线的边角是圆角,圆的半径是线宽。当是`miter`时,线段会在连接处向外延伸直至交予一点,延伸效果会受到`miterLimit`属性约束(斜接限定值)。`miterLimit` 属性是用来设定外延交点与连接点的最大距离,如果交点距离大于此值,连接效果会变成了` bevel`。
`lineCap`属性:两条线相交的拐点的样式类型。`butt``round` 和 `square`。默认是 `butt`
![image-20210108171614811](./images/image-20210108171614811.png)
> `butt`的端点出没有处理,是平直的效果;`round` 的端点加上了一个以线宽一半为半径的半圆;`square`的端点是加上一半线宽的方块。
`lineDashOffset`属性和`setLineDash()`方法:绘制虚线。
`lineDashOffset`属性用于设置起始的偏移量,`setLineDash()`方法接收一个用来描述交替绘制线段和间距(坐标空间单位)长度的数字值型数组。如果数组元素的数量是奇数, 数组的元素会被复制并重复。例如, `[5, 15, 25]` 会变成 `[5, 15, 25, 5, 15, 25]。`[绘制,不绘制,绘制,不绘制,...]。如果数组为空,则是实线模式。
```javascript
let y = 15;
function drawDashedLine(pattern) {
ctx.beginPath();
ctx.setLineDash(pattern);
ctx.moveTo(0, y);
ctx.lineTo(300, y);
ctx.stroke();
y += 20;
}
drawDashedLine([]); //实线
drawDashedLine([1, 1]);
drawDashedLine([10, 10]);
drawDashedLine([20, 5]);
drawDashedLine([15, 3, 3, 3]);
drawDashedLine([20, 3, 3, 3, 3, 3, 3, 3]);
drawDashedLine([12, 3, 3]); // 等于 [12, 3, 3, 12, 3, 3][绘制,不绘制,绘制,不绘制,...]
```
![image-20210108175447524](./images/image-20210108175447524.png)
##### 文本样式
#### 渐变 Gradients
`CanvasGradient` 接口表示描述渐变的不透明对象。通过以下两个方法得到:
- `CanvasRenderingContext2D.createLinearGradient(x0, y0, x1, y1)`
- `CanvasRenderingContext2D.createRadialGradient(x0, y0, r0, x1, y1, r1)`
接口方法:
- addColorStop(offset, color)添加一个由范围在0到1的`偏移(offset)`和颜色(`color`)定义的断点到渐变中。
示例:
![image-20210430103423811](./images/image-20210103423811.png)
```javascript
var gradient = ctx.createLinearGradient(0,0,200,0);
gradient.addColorStop(0,"#457b9d");
gradient.addColorStop(1,"white");
ctx.fillStyle = gradient;
ctx.fillRect(10,10,200,100);
```
![image-20210430104236427](./images/image-20210430104236427.png)
```javascript
//据参数确定两个圆的坐标,绘制放射性渐变的方法。
var gradient = ctx.createRadialGradient(100,100,100,100,100,0);
gradient.addColorStop(0,"white");
gradient.addColorStop(1,"green");
ctx.fillStyle = gradient;
ctx.fillRect(0,0,200,200);
```
![image-20210430112057665](./images/image-20210430112057665.png)
```javascript
var x1=45,y1=45,r1=10,x2=52,y2=50,r2=30;
// 创建渐变
var radgrad = ctx.createRadialGradient(x1,y1,r1,x2,y2,r2);
radgrad.addColorStop(0, '#A7D30C');
radgrad.addColorStop(0.9, '#019F62');
radgrad.addColorStop(1, 'rgba(1,159,98,0)');
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,150,150);
var circle1 = new Path2D();
circle1.moveTo(x1+r1,y1);
circle1.arc(x1,y1,r1,0,2*Math.PI,false);
ctx.stroke(circle1);
var circle2 = new Path2D();
circle2.moveTo(x2+r2,y2);
circle2.arc(x2,y2,r2,0,2*Math.PI,false);
ctx.stroke(circle2);
```
#### 填充规则
在使用`fill`、`clip`、`isPointinPath`方法时,可以选择填充规则,确定某处在路径的外面还是里面来进行填充:
- `nonzero`:非零缠绕规则(默认值)
- `evenodd`:奇偶规则
![image-20210430113137386](./images/image-20210430113137386.png)
根据两个规则填充曲线(顶部):[奇偶规则](https://en.wikipedia.org/wiki/Even-odd_rule)(左)和**非零缠绕规则**(右)。
### 参考文章
[1] [Canvas教程](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial) `https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial`
[2] [CanvasRenderingContext2D](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D)`https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D`