Canvas入门篇

588 阅读8分钟

初识canvas

什么是canvas

<canvas>是一个可以使用javascript来绘制2D图形的HTML元素。<canvas>标签只有两个属性width和height,用来设置画布的宽度和高度。下面代码构建了一个300*300的画布。

<canvas id="canvas" width="300" height="300"></canvas>

渲染上下文

canvas初始是空白的。要绘制图形,需要获取canvas元素的渲染上下文。

下面代码,通过getElementById获取canvas的DOM对象,然后通过canvas对象的getContext()方法获取渲染上下文。此时就可以通过context绘制图形了。

const canvas = document.getElementById("canvas")
const context = canvas.getContext("2d")

一个简单的例子

绘制图形的步骤

  1. 创建一个canvas元素
  2. 获取canvas元素的渲染上下文
  3. 绘制图形

一个简单的例子,绘制三角形。

绘制图形

canvas坐标空间

canvas坐标空间起点为左上角(坐标原点(0,0)),Y轴坐标是向下的。

image.png

绘制矩形

canvas支持两种形式的图形绘制:矩形和路径。其他所有的图形都通过一条或者多条路径组合而成。

canvas提供了三种方法绘制矩形:

  1. context.fillRect(x, y, width, height)绘制一个填充矩形
  2. context.strokeRect(x, y, width, height)绘制一个矩形边框
  3. context.clearRect(x, y, widht, height)清除指定矩形 上面提供的方法,x和y指定了在canvas画布上所绘制的矩形的左上角的坐标。width和height表示矩形的宽高。

绘制路径

通过路径绘制图形,需要用到的函数:

  1. beiginPath():表示开启新的路径。
  2. closePath():关闭路径,作用是连接起点和终点,主要用于实现封闭图形。
  3. stroke(): 通过线条来绘制图形轮廓。
  4. fill(): 通过填充路径的内容区域生成实心的图形。
  5. moveTo(): 将笔触移动到指定的坐标(x,y) 上,通常会使用moveTo()函数设置起点。

直线 lineTo()

绘制直线,需要用到的方法lineTo(x, y)。x,y代表坐标系中直线结束的点。开始点是之前绘制路径的结束点,也可通过moveTo(x,y)设置开始点。

圆弧 arc()

  1. arc(x, y, radius, startAngle, endAngle, anticlockwise):画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针)来生成。

image.png

  1. arcTo(cx, cy, x2, y2, radius):arcTo方法是利用开始点/控制点/结束点/所形成的夹角,绘制一段与夹角两边相切并且半径为radius的圆弧。(cx,cy)表示控制点,(x2,y2)表示结束点坐标,radius表示圆弧半径。

image.png

arc()函数中表示角度的单位是弧度,弧度=(Math.PI/180)*角度。

贝塞尔曲线

  1. quadraticCurveTo(cp1x, cp1y, x, y):绘制二次贝塞尔曲线,cp1x,cp1y 为一个控制点,x,y 为结束点。
  2. bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y):绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。

image.png

给图形设置样式

颜色

给图形设置颜色,使用fillStyle和strokeStyle属性。

  1. fillStyle = color 设置图形的填充颜色。
  2. strokeStyle = color 设置图形的轮廓颜色。

颜色值可以是如下格式:

ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)";

渐变样式

我们可以用线性或者径向的渐变来填充或描边,我们可以创建一个渐变对象,然后赋给图形的fillStyle和strokeStyle属性。

  1. createLinearGradient(x1, y1, x2, y2)创建线性渐变对象。createLinearGradient 方法接受 4 个参数,表示渐变的起点 (x1,y1) 与终点 (x2,y2)。
  2. createRadialGradient(x1, y1, r1, x2, y2, r2)创建镜像渐变对象。createRadialGradient 方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。
  3. gradient.addColorStop(position, color)创建好渐变对象之后,我们就可以用 addColorStop 方法给它上色了。addColorStop 方法接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。

图案样式

图案的应用跟渐变很类似的,创建出一个 pattern 之后,赋给 fillStyle 或 strokeStyle 属性即可。

createPattern(image, type)该方法接受两个参数。Image 可以是一个 Image 对象的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeatrepeat-xrepeat-y 和 no-repeat

<body>
  <canvas id="canvas" width="500" height="500"></canvas>
  <script>
    const canvas = document.getElementById('canvas');
    const context = canvas.getContext("2d");

    const image = new Image()
    image.src= "//iconfont.alicdn.com/p/illus/preview_image/m5Ae1H3sEQPu/55288624-8d1a-452f-a0fa-83cfca6430aa.png"
    image.onload = function(){
      const parttern = context.createPattern(image, 'no-repeat')
      context.fillStyle = parttern;
      context.arc(200, 200, 200, 0, 360*Math.PI/180)
      context.fill()
    }
  </script>
</body>

image.png

线条样式

  1. lineWidth=value设置线条宽度
  2. lineCap = value 设置线条末端样式,value取值为butt, round, square
  3. lineJoin = type 设置线条与线条间结合处的样式,value取值: roundbevel , miter。默认值是 miter
  4. miterLimit = value 设置线条交接处的最大长度
  5. setLineDash(arr) 设置虚线样式
  6. lineDashOffset = value 设置虚线的起始偏移

阴影样式

  1. shadowOffsetX = float 设定阴影在 X 轴的延伸距离, 正值表示向右,负值表示向左。
  2. shadowOffsetY = float 设定阴影在 Y 轴的延伸距离,正值表示向下,负值表示向上。
  3. shadowBlur = float 设定阴影模糊程度
  4. shadowColor = color 设定阴影颜色
<body>
  <canvas id="canvas" width="500" height="500"></canvas>
  <script>
    const canvas = document.getElementById('canvas');
    const context = canvas.getContext("2d");

    context.shadowOffsetX = 10
    context.shadowOffsetY = 10
    context.shadowBlur = 5
    context.shadowColor = 'red';
    context.arc(50, 50, 50, 0, 360*Math.PI/180)
    context.stroke()
  </script>
</body>

image.png

绘制文本

canvas提供了两种方法来渲染文本:

  1. fillText(text, x, y)在指定的 (x,y) 位置填充指定的文本
  2. strokeText(text, x, y)在指定的 (x,y) 位置绘制文本边框 设置文本样式:
  3. font = value 和css font属性语法相同 font="10px serif"
  4. textAlign = value 文本对齐选项,包括start end left right center
  5. textBaseline = value 基线对齐选项。 top, middle, bottom
  6. direction = value 文本方向。可能的值包括:ltr, rtl, inherit
<body>
  <canvas id="canvas" width="500" height="500"></canvas>
  <script>
    const canvas = document.getElementById('canvas');
    const context = canvas.getContext("2d");
    context.font = "30px serif";
    context.fillStyle = "red"
    context.fillText('I LOVE YOU', 10, 50)
    context.strokeStyle = "blue"
    context.strokeText('I LOVE YOU', 10, 50)
  </script>
</body>

image.png

图像操作

canvas 具备图像操作能力。可以用于动态的图像合成或者作为图形的背景。

获取图像源

要将图像绘制到canvas,需要获取图像。以下几种方式都可以用到canvas中。

  1. 由 Image() 函数构造出来的,或者任何的 <img>元素
  2. 用一个 HTML 的 <video>元素作为你的图片源,可以从视频中抓取当前帧作为一个图像
  3. 可以使用另一个 <canvas> 元素作为图片源。

绘制图像

  1. drawImage(image, dx, dy):其中 image 是 image 或者 canvas 对象,dx 和 dy 是其在目标 canvas 里的起始坐标。
  2. drawImage(image, dx, dy, dwidth, dheight) 这个方法多了 2 个参数:dwidth 和 dheight,这两个参数用来控制 当向 canvas 画入时应该缩放的大小
  3. drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)前 4 个是定义图像源的切片位置和大小,后 4 个则是定义切片的目标显示位置和大小

像素操作

  1. canvas提供了getImageData方法和putImageData方法配合使用,先用getImageData方法获取ImageData对象,然后利用一定的算法进行像素操作,最后使用putImageData方法输出像素数据。

  2. ImageData对象中存储着 canvas 对象真实的像素数据。它包含3个属性:

    width图片宽度 height图片高度 data是一个一维数组。包含着RGBA格式的整数数据,数组取值如[r1,g1,b1,a1,r2,g2,b2,a2,...],数组中每四个数存储着一个像素的RGBA颜色值。

  3. 翻转效果:颜色反转是值图片颜色颠倒的效果,实现算法是:将红、绿、蓝3个通道像素取各自的相反值。

<body>
  <canvas id="canvas" width="500" height="500"></canvas>
  <script>    
    const canvas = document.getElementById('canvas')
    const cxt = canvas.getContext("2d")
    
    const image = new Image()
     image.src = "./1.png"
     image.onload = () => {
       cxt.drawImage(image, 10, 10)
       var imageData = cxt.getImageData(10,10,120,120)
      for(let i=0; i<imageData.data.length; i=i+4){
        imageData.data[i+0] = 255 - imageData.data[i+0];
        imageData.data[i+1] = 255 - imageData.data[i+1];
        imageData.data[i+2] = 255 - imageData.data[i+2];        
      }
      cxt.putImageData(imageData, 140, 10)
     }
  </script>
  </body>

image.png

  1. 黑白效果:也叫灰度图,将彩色图片转换成黑白图片,实现算法是,取红绿蓝3个通道的平均值,然后将三个通道全部保存为这个平均值。
<body>
  <canvas id="canvas" width="500" height="500"></canvas>
  <script>    
    const canvas = document.getElementById('canvas')
    const cxt = canvas.getContext("2d")
    
    const image = new Image()
     image.src = "./1.png"
     image.onload = () => {
       cxt.drawImage(image, 10, 10)
       var imageData = cxt.getImageData(10,10,120,120)
       console.log(imageData)
       console.log(imageData.data)
      for(let i=0; i<imageData.data.length; i=i+4){
        let avg = (imageData.data[i+0] + imageData.data[i+1] + imageData.data[i+2])/3
        imageData.data[i+0] = avg;
        imageData.data[i+1] = avg;
        imageData.data[i+2] = avg;        
      }
      cxt.putImageData(imageData, 140, 10)
     }
  </script>
  </body>

image.png

  1. 复古效果:是将红蓝绿三个通道的值,进行加权平均达到的效果
  2. 红色蒙版:是将红色通道的值赋值为红、绿、蓝三个通道的平均值,并且将绿色通道、蓝色通道都赋值为0
  3. 透明处理:将数组中每一个像素的透明度乘以n,然后保存像素数组。

变形操作

变形是一种更强大的方法,可以将原点移动到另一点、对canvas进行旋转和缩放。

状态保存和恢复

在了解变形之前,我先介绍两个在你开始绘制复杂图形时必不可少的方法。

  1. save(): 保存画布 (canvas) 的所有状态。
  2. restore(): 恢复canvas状态的。

Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。Canvas 状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。每一次调用 restore() 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。

一个绘画状态包括:

  1. 当前应用的变形(即移动,旋转和缩放)
  2. 以及下面这些属性:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled
  3. 当前的裁切路径(clipping path)

移动translate

translate(x, y):移动 canvas 和它的原点到一个不同的位置。x是左右偏移量,y是上下偏移量。

旋转rotate

rotate(angle):以原点为中心旋转 canvas。angle>0坐标系顺时针旋转,angle<0坐标系逆时针旋转。

缩放scale

scale(x, y):缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比 1 小,会缩小图形,如果比 1 大会放大图形。

变形transform

transform(a, b, c, d, e, f):

  1. translate(e,f)等价于transform(1,0,0,1,e,f)
  2. scale(a,d)等价于transform(a,0,0,d,0,0)
  3. rotate(angle)等价于transform(cos, sin, -sin, cos, 0,0)

用canvas制作一个时钟

参考:

  1. developer.mozilla.org/zh-CN/docs/…
  2. 从0到1 HTML5 Canvas动画开发