meface/docs/article/visual/canvas_base.md

445 lines
16 KiB
Markdown
Raw Normal View History

2023-11-17 10:54:23 +08:00
---
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`