# canvas文档

canvas画布通过getContext(2d)获取2D上下文来对画布进行绘图,2D上下文提供基础的方法来绘制一些简单的图形,如矩形、弧线、路径和文本,然后通过填充、描边来指定样式,然后通过变换、渐变、阴影、合成(透明度)、模式来改变和丰富绘制的图形。还可以通过一些方法把图片、视频绘制到画布上,并且能对齐进行操作和改变。绘制好的画布还可以把它转换成图片来进行保存。

# 基本用法

画布宽高的设置,可以在元素上直接设置,或者通过js设置。canvas的宽高和css设置的宽高是不一样的,css设置的大小是设置的画布窗口显示的大小,但是底层画布的大小是没有改变的,默认是300*150的,所以有时只设置的css宽高绘制是变形的,只有设置画布的大小和css设置的窗口大小一样才不会变形。

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");

    // 真实宽高
    canvas.width = 300;
    canvas.height = 300;
    // 获取2d上下文
    var ctx = canvas.getContext("2d");
</script>

# 填充和描边

canvas 2d上下文的两种基本操作是填充fillStyle和描边strokeStyle,都是设置颜色的属性,可以进行重复设置。填充和描边要依据绘制的图形进行设置,否则就会没有用。

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    
    ctx.fillStyle ="blue"
    ctx.strokeStyle = "red";

</script>

# 绘制矩形

绘制矩形相关的方法有三个,fillRect/strokeRect/clearRect,fillRect()结合fillStyle绘制实心的,strokeRect()结合strokeStyle绘制描边矩形,clearRect()用来清除指定矩形区域的内容。

绘制描边或者线条还可以通过lineWidth/lineCap/lineJoin来设置宽度接头等样式。

  • fillRect(x, y, w, h) 起点(x, y),宽高(w, h)

  • strokeRect(x, y, w, h) 起点(x, y),宽高(w, h)

  • clearRect(x, y, w, h) 清除某个矩形区域 起点(x, y),宽高(w, h)

  • lineWidth 设置宽度,整数

  • lineCap 设置线条末端形状,butt<平头>、round<圆头>、square<方头>

  • lineJoin 设置线条相交方式,round<圆交>、bevel<斜交>、miter<斜接>

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    
    // 填充实心矩形
    ctx.fillStyle ="red"
    ctx.fillRect(0, 0, 100, 100);
    
    // 描边样式
    ctx.lineWidth = 10;
    ctx.lineCap = "round";
    ctx.lineJoin = "round";
    
    // 矩形边框
    ctx.strokeStyle = "blue";
    ctx.strokeRect(50, 50, 100, 100);

    // 清除某个区域
    ctx.clearRect(60, 60, 30, 30);
</script>

# 绘制路径

绘制路径可以在画布上进行画线,比如,直线、曲线、圆形的线,矩形的线,然后再调用其他方法对画的线进行描边、填充和首位连接。

绘制路线必须先调用beginPath()方法开始,使用moveTo()设置路线起点,如果不设置新的起点,每画一段线都会相互连接,除非使用moveTo再设置新的起点来画就不会相连接了。

每绘制完成后,使用closePath()来让路线首位相连,这个自己设置结束点和起点一样也能相连。 使用stroke()来对路径进行描边,然后先使用strokeStyle对路线进行设置颜色、宽度等。使用fill()来对路线区域进行填充,最好先使用closePath()让路线闭合,然后先使用fillStyle设置填充颜色。

绘制路线方法:

  • moveTo(x, y) 设置起点
  • lineTo(x, y) 画一条直线
  • rect(x, y, w, h) 画一条矩形路线
  • arc(x, y, radius, startAngle, endAngle, clock) 起点(x, y),半径(radius),角度(startAngle, endAngle),是否顺时针(clock)
  • arcTo(x1, y1, x2, y2, radius) 从上一点开始到(x1,y1)为直线,然后从(x1,y1)到(x2,y2)画一条半径为radius的曲线
  • quadraticCurveTo(cx, cy, x, y) 从一点到(x, y)为结束点画一条不规则曲线, 通cx,cy点来控制弯曲度

1、描边三角形

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.strokeStyle = "red"; // 线的颜色

    ctx.beginPath();
    
    // 画一个三角形
    ctx.moveTo(50, 0);
    ctx.lineTo(100, 100);
    ctx.lineTo(0, 100);
    ctx.lineTo(50, 0);

    // 自动闭合三角形
    ctx.moveTo(200, 0); // 新起点,要不然线会连在一起
    ctx.lineTo(250, 100);
    ctx.lineTo(150, 100);
    ctx.closePath();

    ctx.stroke(); // 描边,把线绘制到画布上
</script>

2、填充三角形

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.fillStyle = "red"; // 填充颜色

    ctx.beginPath();
    
    // 画一个三角形
    ctx.moveTo(50, 0);
    ctx.lineTo(100, 100);
    ctx.lineTo(0, 100);
    ctx.lineTo(50, 0);

    // 自动闭合三角形
    ctx.moveTo(200, 0);
    ctx.lineTo(250, 100);
    ctx.lineTo(150, 100);
    ctx.closePath();

    ctx.fill(); // 填充路径
</script>

3、矩形

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    
    ctx.rect(50, 50, 100, 100);

    ctx.stroke();
</script>

4、圆形、弧线

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    
    ctx.moveTo(150, 100);
    // 圆形
    ctx.arc(100, 100, 50, 0, 2 * Math.PI, false);
    // 半圆
    ctx.arc(100, 100, 50, 0, 2 * Math.PI / 2, false);

    // 不规则曲线
    ctx.moveTo(150, 100);
    ctx.quadraticCurveTo(100, 50, 100, 200);

    ctx.stroke();
</script>

5、同心圆

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    
    // 同心圆,不同起点
    ctx.moveTo(150, 100);
    ctx.arc(100, 100, 50, 0, 2 * Math.PI, false);
    ctx.moveTo(130, 100); // 起点设置为第二个圆的0开始的地方
    ctx.arc(100, 100, 30, 0, 2 * Math.PI, false);

    ctx.stroke();
</script>

6、环形

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.strokeStyle = "red";
    ctx.lineWidth = 10;
    ctx.lineCap = "round";

    ctx.beginPath();
    
    ctx.arc(100, 100, 50, 2 * Math.PI / 2, 0, false);

    ctx.stroke();
</script>

# 绘制文本

绘制文本使用fillText()strokeText()两个方法,一般使用第一个,fillText第一个参数是文字,后面两个是位置

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.font = "bold 20px Arial";
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";
    ctx.fillText("绘制文字", 10, 50)

    ctx.strokeStyle = "red"; // 使用strokeText可以设置颜色
    ctx.strokeText("绘制文字", 10, 150)
</script>

# 变换

变换和css的过渡一样,有旋转、缩放,变换原点等。

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    
    ctx.beginPath();
    ctx.rect(50, 50, 200, 50);
    ctx.stroke();
    
    // 变换原点
    ctx.translate(100, 100)
    // 旋转
    ctx.rotate(1)
    // 缩放
    ctx.scale(2, 2)
</script>

# 渐变

渐变和css渐变差不多,分为线性渐变和径向渐变,渐变首先要先创建一个指定区域的渐变对象,然后再用这个对象设置渐变的颜色过渡。再然后把这个渐变赋值给要渐变的填充区域就行了。

  • createLinearGradient(x1, y1, x2, y2) 线性渐变对象,设置渐变区域和方向。 从(x1,y1)到(x2,y2)方向渐变
  • createRadialGradient(x1, y1, r1, x2, y2, r2) 径向渐变对象,沿着圆(x1,y1)的r1外边到圆(x2,y2)的r2内进行渐变,实际是从r1到r2一圈范围内渐变。改变圆心位置和半径r就会实现不规则渐变。
  • addColorStop(ap, color) ap 颜色从0开始渐变到1结束
<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    
    // 创建线性渐变对象
    var gradient = ctx.createLinearGradient(50, 50, 150, 150);
    // 设置渐变过渡颜色
    gradient.addColorStop(0, "red");
    gradient.addColorStop(0.5, "blue");
    gradient.addColorStop(1, "green");
    // 赋值给要渐变的区域
    ctx.fillStyle = gradient;
    ctx.fillRect(50, 50, 100, 100);

    // 创建径向渐变对象
    var gradient = ctx.createRadialGradient(50, 50, 10, 50, 50, 40); // 从10到40径向渐变

    gradient.addColorStop(0, "red");
    gradient.addColorStop(1, "green");

    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, 100, 100);

</script>

# 透明度和重叠状态

透明度和css样式opacity一样,使用globalAlpha设置整个画布的透明度,值为0~1。

属性globalCompositeOperation可以设置先后绘制两个图形相互重叠的状态。

  • source-over 默认值,后绘制的在先绘制之上
  • source-in 重叠部分可见,两者其他部分透明
  • source-out 不重叠部分可见,先绘制的透明
  • source-in 重叠部分可见,先绘制不受影响

等等其他状态。。。

# 裁剪

裁切路径,就是用 clip 绘制一个不可见的区域,让在画布上新绘制的所有内容都将局限在该区域内,区域以外进行绘制是没有任何效果的。已有的内容不受影响。要取消裁切路径的效果,可以绘制一个和画布等大的矩形裁切路径。

<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    
    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, 200, 200);

    // 绘制一个圆形裁剪区域
    ctx.beginPath();
    ctx.arc(100, 100, 100, 0, Math.PI * 2, true);
    ctx.clip();

    // 蓝色矩形被裁剪成一个蓝色的圆
    ctx.fillStyle = "blue";
    ctx.fillRect(0, 0, 200, 200);
</script>

# 保存状态

如果想把设置好的状态保存,再次修改后想返回之前的状态,我们可以使用 save() 方法保存,它会把所有设置推进一个栈结构保存,然后对上下文进行其他修改,等需要回到之前状态时,可以使用restore()方法,返回上一级栈中的状态。 这两个方法都可以连续调用,连续保存和连续返回。

# 模式

模式和css设置背景图一样,把获取到的图片元素给画布某个区域设置背景图片,也可以设置video元素。

  • createPattern(dom, repeat) dom可以是图片或者视频,repeat和背景图片的repeat一样,是否重复。
<img src="./public/img/logo.png" alt="">
<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    
    // 获取图片
    var image = document.getElementsByTagName("img")[0];
    // 创建模式
    var pattern = ctx.createPattern(image, "no-repeat");
    // 赋值给某个区域
    ctx.fillStyle = pattern;
    ctx.fillRect(0, 0, 100, 100);

</script>

# 绘制图片,操作图像

把图像绘制到画布上,可以使用drawImage()方法。

<img src="./public/img/logo.png" alt="">
<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    
    // 获取图片
    var image = document.getElementsByTagName("img")[0];

    // 把获取图片大小不动的绘制到 (10, 10) 的位置上
    ctx.drawImage(image, 10, 10);

    // 把图片绘制到画布位置(10, 10)大小为50x50的区域上 
    ctx.drawImage(image, 10, 10, 50, 50);

    // 获取图片(0, 0)位置大小为50x50区域的地方绘制到画布大小为50x50位置为(10, 10)的位置上
    ctx.drawImage(image, 0, 0, 50, 50, 10, 10, 50, 50);
</script>

操作图像,使用getImageData()方法获取图像数据,改变后再通过putImageData()方法把数据写入画布。

<img src="./public/img/logo.png" alt="">
<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 先绘制图片
    var image = document.getElementsByTagName("img")[0];
    ctx.drawImage(image, 0, 0);
    
    // 获取图片像素数据
    const imageData = ctx.getImageData(0, 0, 380, 380);
    const data = imageData.data;

    // 改变为黑白色
    for (let i = 0; i < data.length; i += 4) {
        const avg = (data[i] + data[i + 1] + data[i + 2]) / 3
        data[i] = avg; // red
        data[i + 1] = avg; // green
        data[i + 2] = avg; // blue
    }

    // 在把图像写入到画布中
    ctx.putImageData(imageData, 0, 0);
</script>

# 绘制视频

# 把画布转成图片

绘制好画布后,我们可以使用toDataURL()方法把画布转成图片导出来。

<img src="./public/img/logo.png" alt="">
<canvas id="canvas" width="300" height="300"></canvas>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, 200, 200);

    // 把画布转成图片
    var imgURL = canvas.toDataURL("image/png");

    var image = document.createElement("img");
    image.src = imgURL;
    document.body.appendChild(image);
</script>