初识Canvas

218 阅读9分钟

一. 概述

canvas又称为"画布",是HTML5的核心技术之一.我们常说的Canvas技术,指的就是使用Canvas元素集合Javascript来绘制各种图形的技术。
canvas的作用:
1. 绘制图形
canvas可以用来绘制各种基本图形如矩形、曲线、圆等,也可以绘制各种复杂绚丽的图形。
2. 绘制图表
很多公司业务的数据展示都离不开图表,使用Canvas可以用来绘制满足各种需求的图表。
3.动画效果
使用Canvas,我们也可以制作出各种华丽的动画效果
4. 游戏开发

简单的说,HTML5 canvas,就是一门使用JavaScript来操作Canvas元素的技术。使用Canvas元素来绘制图形,需要以下三步。 (1)获取Canvas对象 (2)获取上下文环境对象context (3) 开始绘制图形
注意:Canvas使用的是W3C坐标系,而不是数学坐标系。其中,W3C坐标系的Y轴正方向向上。如下:

图片1.png

二. 直线 曲线图形

直线图形

直线、三角形、矩形的绘制如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id="canvas" width="500" height="600" style="border:1px dashed gray;"></canvas>

    <script>
      // 1. 找到画布
      let cnv=document.getElementById('canvas')
      // 2. 获取画笔,上下文对象
      let cxt=cnv.getContext('2d')
      // 3.绘制直线
      cxt.moveTo(50, 50);
      cxt.lineTo(100, 50);
      cxt.moveTo(50, 100);
      cxt.lineTo(100, 100);
      cxt.stroke();
       
      //绘制三角形
      cxt.moveTo(200, 200);
      cxt.lineTo(250, 150);
      cxt.lineTo(250, 200);
      cxt.lineTo(200, 200);
      cxt.stroke();

      //绘制矩形
      cxt.moveTo(300, 300);
      cxt.lineTo(400, 300);
      cxt.lineTo(400, 500);
      cxt.lineTo(300, 500);
      cxt.lineTo(300, 300);
      cxt.stroke();
    </script>
</body>
</html>

在浏览器中展示如下:

微信图片_20230520152254.png 注:
(1)moveTo(x1,y1)的含义是"将画笔移到(x1,y1)位置上,然后开始绘图"。 lineTo(x2,y2)的含义是"将画笔从起点(x1,y1)开始画直线,一直画到终点坐标(x2,y2)"。lineTo()方法是可以重复使用的。第一次使用lineTo()后,Canvas会以"上一个终点坐标"作为第二次调用的起点坐标,然后开始画直线,以此类推。多边形也是使用moveTo()和lineTo()这两个方法画出来的。
(2)moveTo()和lineTo()仅仅是确定直线的“起点坐标”和“终点坐标”这两个状态,但是实际上画笔还没开始动。因此我们还需要调用上下文对象的stroke()方法才有效。
(3)对于绘制矩形,Canvas还为我们提供了独立的方法来实现。在canvas中,矩形分为两种,即"描边"矩形和"填充"矩形。 "描边"矩形语法:

cxt.strokeStyle=属性值
cxt.strokeRect(x,y,width,height)

实现效果等同于:

  cxt.strokeStyle=属性值
  cxt.rect(x,y,width,height)
  cxt.stroke()

"填充"矩形语法:

cxt.fillStyle=属性值
cxt.fillRect(x,y,width,height)

实现效果等同于:

  cxt.fillStyle=属性值
  cxt.rect(x,y,width,height)
  cxt.fill()

曲线图形

圆形

在Canvas中,我们可以使用arc()方法来画一个圆。
语法:

 cxt.beginPath();
 cxt.arc(x,y,半径,开始角度,结束角度, anticlockwise);
 cxt.closePath();
 cxt.strokeStyle = "颜色值";
 cxt.stroke();

说明:

(1) 我们必须先调用beginPath()方法来声明“开始一个新路径”,然后才可以开始画圆。在使用arc()方法画圆完成之后,还要调用closePath()方法来关闭当前路径。
(2)x和y表示圆心坐标,anticlockwise表示“是否逆时针”。开始角度和结束角度使用的是“弧度”。
(3) 跟矩形一样,对于圆形,我们也可以分为“描边圆形”和“填充圆形”。在Canvas中,我们可以使用stroke()方法来绘制一个“描边圆”,可以使用fill()方法来绘制一个“填充圆”。

图片2.png

弧线

在Canvas中,如果我们想要画弧线,常见的有2种方法:(1)arc();(2)arcTo()。
1、arc()画弧线*

在Canvas中,arc()不仅可以用来画圆形,还可以用来画弧线。

语法:

  //状态描述
 cxt.beginPath();
 cxt.arc(x,y,半径,开始角度,结束角度, anticlockwise);
 //描边
 cxt.strokeStyle = "颜色值";
 cxt.stroke();

说明:

x和y表示圆心坐标,anticlockwise表示“是否逆时针”。当anticlockwise取值为true时,表示按逆时针方向绘制;当anticlockwise取值为false时,表示按顺时针方向绘制。默认情况下,anticlockwise取值为false。 特别注意,“arc()画弧线”与“arc()画描边圆”最大的不同在于:“arc()画弧线不使用closePath()来关闭路径。” 这一点大家一定要区分开,因为弧线不是一个闭合图形。而closePath()是来绘制“封闭图形”的。

图片3.png

2、arcTo()画弧线
在Canvas中,我们可以使用arcTo()方法来画一条弧线。
语法:

cxt.arcTo(cx,cy,x2,y2,radius);

 说明:

(cx , cy)表示控制点的坐标,(x2 , y2)表示结束点的坐标,radius表示圆弧的半径。

想要画一条弧线,我们需要提供3个点坐标:开始点、控制点和结束点。其中一般由moveTo()或lineTo()提供开始点,arcTo()提供控制点和结束点。 arcTo()方法就是利用“开始点”、“控制点”和“结束点”这3个点所形成的夹角,然后绘制一段与夹角的两边相切并且半径为radius的圆弧。其中,弧线的起点是“开始点所在边与圆的切点”,而弧线的终点是“结束点所在边与圆的切点”。

图片4.png

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id="canvas" width="300" height="300" style="border:1px dashed gray;"></canvas>
    <script>
      // 1. 找到画布
      let cnv=document.getElementById('canvas')
      // 2. 获取画笔,上下文对象
      let cxt=cnv.getContext('2d')
     //半圆
      cxt.beginPath();
      cxt.arc(80, 80, 50, 0, 180 * Math.PI / 180, true);
      cxt.closePath();
      //描边
      cxt.strokeStyle = "HotPink";
      cxt.stroke();
      //整圆
      cxt.beginPath();
      cxt.arc(120, 80, 50, 0, 360 * Math.PI / 180, true);
      cxt.closePath();
      //描边
      cxt.strokeStyle = "HotPink";
      cxt.stroke();

       //半圆
      cxt.beginPath();
      cxt.arc(200, 200, 50, 0, 180 * Math.PI / 180, true);
      cxt.closePath();
      //描边
      cxt.fillStyle = "HotPink";
      cxt.fill();
      //整圆
      cxt.beginPath();
      cxt.arc(240, 200, 50, 0, 360 * Math.PI / 180, true);
      cxt.closePath();
      //描边
      cxt.fillStyle = "#9966FF";
      cxt.fill();
      
      //用arcTo()画弧线cxt.arcTo(cx,cy,x2,y2,radius);
      cxt.moveTo(50,150);
      cxt.lineTo(100, 150);
      cxt.arcTo(150, 150, 150, 190, 50);
      cxt.lineTo(150, 220);
      cxt.stroke();
    </script>
</body>
</html>

运行结果如下:

image.png
注:如果想要使用arc()画圆形,需要使用cxt.closePath()来关闭路径;如果想要使用arc()画弧线,则不需要使用cxt.closePath()来关闭路径。closePath()方法的作用在于关闭路径、连接起点与终点。

三. 线条 文本 图片操作

线条操作

这一章,我们主要学习了Canvas的线条操作属性和方法,如下表所示:

线条操作属性

属性说明
lineWidth定义线条宽度
lineCap定义线帽样式
lineJoin定义两个线条交接处样式

线条操作方法

方法说明
setLineDash()定义线条的虚实样式

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id="canvas" width="300" height="400" style="border:1px dashed gray;"></canvas>
    <script>
      // 1. 找到画布
      let cnv=document.getElementById('canvas')
      // 2. 获取画笔,上下文对象
      let cxt=cnv.getContext('2d')
      //lineWidth值为5,lineCap值为默认值(butt)
      cxt.lineWidth = 5;
      cxt.moveTo(20, 20);
      cxt.lineTo(180, 20);
      cxt.stroke();

      //lineWidth值为10,lineCap值改为round
      cxt.beginPath();
      cxt.lineWidth = 10;
      cxt.lineCap = "round";
      cxt.moveTo(20, 70);
      cxt.lineTo(180, 70);
      cxt.stroke();

      //lineWidth值为15,lineCap值改为square
      cxt.beginPath();
      cxt.lineWidth = 15;
      cxt.lineCap = "square";
      cxt.moveTo(20, 120);
      cxt.lineTo(180, 120);
      cxt.stroke();

      cxt.moveTo(200, 200);
      cxt.lineTo(250, 200);
      cxt.lineTo(200, 250);
      cxt.lineTo(250, 250);
      cxt.lineWidth = 12;
      //cxt.lineJoin="miter"为尖角,默认值,cxt.lineJoin="round"为圆角,cxt.lineJoin="bevel"为斜角,
      cxt.lineJoin = "miter";
      cxt.stroke();
    </script>
</body>
</html>

运行结果如下:

image.png

文本操作

对于文本操作,Canvas为我们提供了不少方法和属性,具体使用方法请自行查阅api

文本操作“方法”

方法*说明*
fillText()绘制“填充”文本
strokeText()绘制“描边”文本
measureText()用于获取文本的长度

文本操作“属性”

属性*说明*
font定义文本字体样式(大小、粗细等)
textAlign定义文本水平对齐方式
textBaseline定义文本垂直对齐方式

图片操作

在canvas中,我们不仅可以绘制各种形状的图形,还可以将图片导入Canvas中进行各种操作,例如平铺切割等。无论开发的是应用程序还是游戏软件,都离不开图片,因为没有图片就无法让整个界面漂亮起来。在开发canvas游戏的时候,游戏中的地图、背景、人物、物品等都不是用Canvas绘制出来的,大多是用图片来实现的。

这里简单说一下3种图片操作方式:①绘制图片;②平铺图片;③切割图片。

绘制图片

在Canvas中,我们可以使用drawImage()方法来绘制图片。drawImage()方法共有3种调用方式:
(1)drawImage(image , dx , dy)
(2)drawImage(image , dx , dy , dw , dh)
(3)drawImage(image , sx , sy , sw , sh , dx , dy , dw , dh) 说明: 参数image,表示页面中的图片;参数dx,表示图片左上角的横坐标;参数dy,表示图片左上角的纵坐标。参数dw,定义图片的宽度;参数dh,定义图片的高度;参数sx,sy,sw,sh分别表示源图片被截取部分的横坐标,纵坐标,以及宽度和高度。     这3种方法在实际开发中的图片操作中经常用到,它们都有各自的优点和使用场合:
(1)第1种方法仅仅绘制一个图片;
(2)第2种方法可以绘制大小不一样的图片(常用于Canvas游戏开发);
(3)第3种方法可以将部分图像复制到画布。

平铺图片

在Canvas中,我们可以使用createPattern()方法来定义图片的平铺方式。

    语法:
var pattern = cxt.createPattern(image , type);
cxt.fillStyle = pattern;
cxt.fillRect();
说明:
想要定义图片的平铺方式,我们需要将createPattern()和fillRect()这两个方法配合使用。 参数image表示被平铺的图片对象,参数type表示图像平铺的方式。参数type有4种取值:no-repeat、repeat-x、repeat-y、repeat。
createPattern()方法type属性取值

属性值说明
repeat默认值,在水平方向和垂直方向同时平铺
repeat-x只在水平方向平铺
repeat-y只在垂直方向平铺
no-repeat只显示一次(不平铺)

此外,createPattern()方法不仅可以用于平铺图片,还可以用于平铺其他canvas元素或者平铺video元素(即视频)。

切割图片
在Canvas中,我们可以调用clip()方法切割Canvas中绘制的图片。
语法:  cxt.clip();
说明: 想要使用clip()方法切割一张图片,我们需要以下3步:   (1)绘制基本图形;
(2)使用clip()方法;
(3)绘制图片;
代码示例如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id="canvas" width="300" height="300" style="border:1px dashed gray;"></canvas>
    <script>
      // 1. 找到画布
      let cnv=document.getElementById('canvas')
      // 2. 获取画笔,上下文对象
      let cxt=cnv.getContext('2d')

      //第1步,绘制基本图形,用来切割
        cxt.beginPath();
        cxt.arc(150, 150, 100, 0, 360 * Math.PI / 180, true);
        cxt.closePath();
        cxt.stroke();

        //第2步,使用clip()方法,使得切割区域为上面绘制的基本图形
        cxt.clip();

        //第3步,绘制一张图片
        var image = new Image();
        image.src = "test.jpeg";
        image.onload = function () {
            cxt.drawImage(image, 30, 50);
        }
    </script>
</body>
</html>

运行结果如下:

image.png

四. 变换操作

Canvas为我们提供了以下变形操作的方法。这些变形操作,不仅可以用于图形,还可以用于图像和文字。
Canvas变形操作的方法

方法说明
translate()平移
scale()缩放
rotate()旋转
transform()、setTransform()变换矩阵
 其中,transform()和setTransform()这2个方法,可自行了解一下。

图形平移
在Canvas中,我们可以使用translate()方法来平移图形。

    语法: cxt.translate(x,y);

    说明: x表示图形在X轴方向移动的距离,默认单位为px。当x为正时,图形向X轴正方向移动;当x为负时,图形向X轴反方向移动。y表示图形在Y轴方向移动的距离,默认单位为px。当y为正时,图形向Y轴正方形移动;当y为负时,图形向Y轴反方向移动。

图形缩放
在Canvas中,我们可以使用scale()方法来对图形进行缩放操作。缩放,指的是“缩小”和“放大”的意思。
语法: cxt.scale(x,y);
说明:  x表示图形在X轴方向的缩放倍数。y表示图形在Y轴方向的缩放倍数。其中,x和y一般情况下都是正数。当x或y取值为0~1之间时,图形进行缩小;当x或y取值大于1时,图形进行放大。

图形旋转
在Canvas中,我们可以使用rotate()方法来旋转图形。
语法: cxt.rotate(angle);
说明: 参数angle表示图形旋转的角度,取值为-Math.PI2~Math.PI2。注意,rotate()方法的角度是也用弧度来表示的
如果我们想要改变图形的旋转中心,我们可以使用translate()方法来实现。 代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id="canvas" width="300" height="300" style="border:1px dashed gray;"></canvas>
    <script>
      // 1. 找到画布
      let cnv=document.getElementById('canvas')
      // 2. 获取画笔,上下文对象
      let cxt=cnv.getContext('2d')
      cxt.translate(170, 25);
      cxt.fillStyle = "rgba(195,39,43,0.8)";
      //cxt.fillRect(x,y,width,height)
      cxt.fillRect(0, 0, 100, 50);   
      let i=1;
      let intervalId = setInterval(function() {
        cxt.translate(25, 25);      //图形平移
        cxt.scale(0.95, 0.95);      //图形缩放
        cxt.rotate(Math.PI / 10);   //图形旋转
        cxt.fillRect(0, 0, 100, 50);
      i++;
      if (i > 50) {
        clearInterval(intervalId);
      }
    }, 400);
    </script>
</body>
</html>

运行结果如下:

image.png

五. 其他

可用canvas实现更多高级的实现,用户可以借助鼠标或键盘参与到canvas动画中去,去实现一些互动的效果,下面是一个简单的例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id="canvas" width="200" height="200" style="border:1px dashed gray;"></canvas>
    <script>
        window.onload = function () {
            let cnv = document.getElementById('canvas');
            let cxt = cnv.getContext("2d");

            //初始化一个圆形
            drawBall(cnv.width / 2, cnv.height / 2);
            //初始化变量
            let x = 100;
            let y = 75;
            //获取按键方向
            let key = getKey();

            //是否按下键盘
            let isMouseDown = false;

            //是否游戏结束
            isFinish=false;

             //随机产生-2~2之间的任意数,作为vx、vy的值
            let vx = (Math.random() * 2 - 1) * 2;
            let vy = (Math.random() * 2 - 1) * 2;

            //添加键盘松开事件
            window.addEventListener("keyup", function (e) {
                //清除整个Canvas,以便重绘新的圆形
                isMouseDown=false;
                console.log('进来了');
                automate()
            }, false);

            window.addEventListener("keydown", function (e) {
                //清除整个Canvas,以便重绘新的圆形
                isMouseDown=true;
                cxt.clearRect(0, 0, cnv.width, cnv.height);

                //根据key.direction的值,判断小球移动方向
                switch (key.direction) {
                    case "up":
                        y -= 2;
                        drawBall(x, y);
                        checkBorder();
                        break;
                    case "down":
                        y += 2;
                        drawBall(x, y);
                        checkBorder();
                        break;
                    case "left":
                        x -= 2;
                        drawBall(x, y);
                        checkBorder();
                        break;
                    case "right":
                        x += 2;
                        drawBall(x, y);
                        checkBorder();
                        break;
                        //default值
                    default:
                        drawBall(x, y);
                }
            }, false);
            //定义绘制小球的函数
            function drawBall(x, y) {
                cxt.beginPath();
                //cxt.arc(x,y,半径,开始角度,结束角度, anticlockwise);
                cxt.arc(x, y, 10, 0, 360 * Math.PI / 180, true);
                cxt.closePath();
                cxt.fillStyle = "#6699FF";
                cxt.fill();
            }

            //获取键盘控制方向
            function getKey () {
            let key = {};
            window.addEventListener("keydown", function (e) {
                if (e.keyCode == 38 || e.keyCode == 87) {
                    key.direction = "up";
                } else if (e.keyCode == 39 || e.keyCode == 68) {
                    key.direction = "right";
                } else if (e.keyCode == 40 || e.keyCode == 83) {
                    key.direction = "down";
                } else if (e.keyCode == 37 || e.keyCode == 65) {
                    key.direction = "left";
                } else {
                    key.direction = "";
                }
            }, false);
            return key;
            }

            //定义边界检测函数
            function checkBorder() {
                //当小球碰到上边界时
                if (y <= 10) {
                    isFinish=true;
                    alert('Game Over');
                    //当小球碰到下边界时
                } else if (y >= cnv.height - 10) {
                    isFinish=true;
                    alert('Game Over');
                }
                //当小球碰到左边界时
                if (x <= 10) {
                    isFinish=true;
                    alert('Game Over');
                } else if (x >= cnv.width - 10) {
                    isFinish=true;
                    alert('Game Over');
                }
            }
           
            function automate() {
              let intervalId = setInterval(function() {
              console.log(isMouseDown)
              if (!isMouseDown &&!isFinish) {
                    x += vx;
                    y += vy;
                    cxt.clearRect(0, 0, cnv.width, cnv.height);
                    drawBall(x, y);
                    checkBorder()                 
                }else{
                  clearInterval(intervalId);
                }
            }, 100);
            }
            automate()
        }
    </script>
</body>
</html>

通过以上代码可简单的控制一个小球的移动。