canvas入门

132 阅读10分钟

canvas

使用canvas标签,然后通过设置width和height,可以创建一块指定宽高的画布:

<canvas id="canvas" width="500px" height="500px"></canvas>

如果要在画布上绘制图形,首先要获取绘图上下文,通过canvas的getContext方法获取,并传入参数”2d“

let canvas=document.getElementById("canvas")
let ctx=canvas.getContext("2d")

绘制好的canvas,可以通过canvas上的toDataURL()导出canvas上的图像

let url=canvas.toDataURL("image/jpg")
//toDataURL(type,quality)
//type是导出图片格式,默认是image/png
//quality是导出图片质量,可以设置0~1之间的数值

注意:如果绘制图像是从其他域绘制过来的,使用toDataURL()、getImageData()这些方法会报错

填充和描边

2d上下文有两个基本绘制操作:填充和描边。填充是以指定的样式填充形状。描边是以指定的样式为便捷着色。可以通过fillStyle和strokeStyle这两个属性指定填充和描边样式,这两个属性可以是字符串、渐变、图案

ctx.fillStyle="#000"
ctx.strokeStyle="red"

绘制矩形

矩形是唯一可以直接在2d上下文绘制的图形,其他图形都是通过路径绘制出来的。

绘制矩形相关方法有3个:fillRect(),strokeRect(),clearRect()。都接收4个参数:矩形起点x坐标,矩形起点y坐标,矩形宽度,矩形高度。

可以在绘制之前先指定填充或描边样式。

ctx.fillStyle="#000"
ctx.fillRect(0,0,50,50)
ctx.strokeStyle="red"
ctx.fillRect(0,0,50,50)

和线条相关的三个属性:

lineWidth:线条宽度,可以是任意整数值

lineJoin:线条交点形状,可以是round(出圆头),butt(平头),square(出方头)

lineCap:线条末端形状,可以是round(出圆头),bevel(取平),miter(出尖)

绘制路径

要绘制路径,首先要调用beginPath(),表示开始绘制路径,然后再调用下列方法:

  • lineTo(x,y):绘制一条从上一点到(x,y)的路径

  • moveTo(x,y):将光标移动到(x,y)

  • arc(x,y,radius,startAngle,endAngle,boolean):以(x,y)为圆心绘制一个半径为radius的圆,可以指定开始角度startAngle和结束角度endAngle,还可以指定是以顺时针还是逆时针方式绘制的布尔值,默认是顺时针(false),需要注意的是:开始角度0是圆的最右端,逆时针不会影响圆的开始角度和结束角度位置,只会按反方向绘制

  • arcTo(x1,y1,x2,y2,radius):指定半径,经由(x1,y1)绘制一条到(x2,y2)的弧线

  • rect(x,y,width,height):指定起点绘制一个width和height的矩形路径

调用这些方法绘制好路径后,可以指定fillstyle,然后调用fill()方法填充路径,也可以指定strokeStyle,然后调用stroke()方法开始描画路径

最后可以调用closePath()表示结束绘制路径。

绘制文本

canvas也可以指定绘制文本样式,主要是通过三个属性:

  • font:以css样式指定字体样式、大小、字体族,如”10px Arial“

  • textAlign:文本对齐方式,可以是start、end、center、right、left

  • textBaseLine:文本基线,可以是top、middle、bottom、hanging、alphabetic、idegraphic

这三个属性都有默认值,因此没必要每次绘制得时候都设置它们。fillText()方法使用fillStyle属性绘制文本,strokeText()方法使用strokeStyle属性绘制文本。一般fillText()方法用的比较多一点,因为它是模拟了在网页渲染文本,语法:

ctx.fillText(text, x, y, [maxWidth])
text:绘制文本
x:绘制文本起点x坐标
y:绘制文本起点y坐标
maxWidth:文本最大宽度,如果超出这个宽度,回缩小字体适应最大宽度

canvas提供了用于确定文本大小的measureText()方法,measureText()方法接收一个字符串,即要绘制的文本,然后返回一个TextMetrics对象,该对象目前只有一个width属性,表示当前绘制文本的宽度

绘制图像

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

如果只是想将图片按原来大小绘制到画布上(注意会按图片原尺寸,使用img.width设置的尺寸没用),可以给drawImage()方法传递三个参数:

ctx.drawImage(img, dx, dy)
//img是图片元素
//dx是绘制起点x坐标
//dy是绘制起点y坐标

如果想将图片绘制到画布上,并指定绘制图片宽高尺寸,可以给drawImage()方法传递5个参数:

ctx.drawImage(img,dx,dy,dwidth,dheight)
//前面三个参数同上
//dwidth是绘制的图片宽度
//dheight是绘制的图片高度

如果想将图片裁剪一部分绘制到画布上(注意的是:裁剪的是原图像),可以给drawImage()方法传递9个参数:

ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
//sx 源图片要裁剪的起点x坐标
//sy 源图片要裁剪的起点y坐标
//sWidth 源图片要裁剪宽度
//sHeight 源图片要裁剪高度

想将绘制的图片导出可以使用toDataUrl()方法,也可以使用getImageData()方法,不同的是,一个是canvas上的方法,一个是CanvasRenderingContext2D上的方法。语法:

ctx.getImageData(x, y, width, height);
//sx 要提取图片的起点x坐标
//sy 要提取图片的起点y坐标
//width 要提取图片的宽度
//height 要提取图片的高度

变换

我们可以通过变换绘图上下文改变画布上的图像。需要区分一下画布和绘图上下文的关系,画布是我们通过canvas标签定义的,我们可以看见的一个窗口,而绘图上下文是我们绘制的区域,只是初始的时候绘图上下文的原点(0,0)和画布的左上角是重叠,绘图上下文的范围可能比画布大得多,关系大概是这样:

image1.png

可以变换绘图上下文的方法有:

方法参数说明
rotate()(angle)旋转绘图上下文,angle是旋转角度,默认是顺时针,值可以是Math.PI
scale()(scaleX, scaleY)按比例缩放绘图上下文,scaleX和scaleY是x轴和y轴的缩放数值,默认1.0
translate()(x, y)移动原点,参数x和y是要移动到的位置坐标

渐变

还记得前面说过fillStyle和strokeStyle支持字符串、渐变、图案,创建一个渐变首先需要创建渐变实例,通常我们使用ctx.createLinearGradient()创建一个线性渐变实例,这个方法接收4个参数,分别是:渐变起点x坐标、渐变起点y坐标、渐变终点x坐标、渐变终点y坐标,例如:

const liner=ctx.createLinearGradient(0,0,50,50)

上面代码就创建了一个坐标(0, 0)到坐标(50, 50)的线性渐变。创建渐变实例之后,我们可以通过实例上的addColorStop()方法指定渐变色标,这个方法接收两个参数,分别是:色标位置和css颜色字符串,色标位置可以取0~1的数值,0代表开始,1代表终点,例如给上面创建的渐变实例添加色标:

liner.addColorStop(0,"#007acc")
liner.addColorStop(1,"#e5e510")

添加完色标之后还需要赋值给fillStyle或者strokeStyle都可以,例如分别用填充和描边绘制矩形,并给他们添加渐变:

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

const liner1=ctx.createLinearGradient(10,10,60,60)
liner1.addColorStop(0,"#007acc")
liner1.addColorStop(1,"#e5e510")
const liner2=ctx.createLinearGradient(70,10,120,60)
liner2.addColorStop(0,"#007acc")
liner2.addColorStop(1,"#e5e510")
//以描边的方式绘制矩形
ctx.strokeStyle=liner1
ctx.strokeRect(10,10,50,50)
//以填充的方式绘制矩形
ctx.fillStyle=liner2
ctx.fillRect(70,10,50,50)

效果:

image2.png

除了线性渐变,我们还可以创建径向渐变,通过ctx.createRadialGradient()方法创建一个径向渐变实例,这个方法接收6个参数,分别对应两个圆形的圆心和半径,前三个参数是第一个圆的圆心x坐标、圆心y坐标、圆半径,后三个参数是第二个圆的圆心x坐标、圆心y坐标、圆半径。

创建好的径向渐变实例也是通过addColorStop()方法添加渐变色标,与线性渐变用法一样,下面同样分别用填充和描边绘制圆形,并给他们添加渐变:

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

const radial1=ctx.createRadialGradient(50,50,10,50,50,40)
radial1.addColorStop(0,"#007acc")
radial1.addColorStop(1,"#e5e510")
const radial2=ctx.createRadialGradient(150,50,10,150,50,40)
radial2.addColorStop(0,"#007acc")
radial2.addColorStop(1,"#e5e510")
//以描边的方式绘制圆形
ctx.beginPath()
ctx.arc(50, 50, 40, 0, 2*Math.PI)
ctx.strokeStyle=radial1
ctx.stroke()
//以填充的方式绘制圆形
ctx.beginPath()
ctx.arc(150, 50, 40, 0, 2*Math.PI)
ctx.fillStyle=radial2
ctx.fill()

效果:

image3.png

需要注意的是:渐变实例有渐变范围,如果绘制的图形只有一部分在渐变范围,则只会有一部分的渐变反映在图形中

图案

同样fillStyle也持支持图案,如果要获得一个图案实例,我们可以通过ctx.createPattern()方法创建图案实例,这个方法接收两个参数,分别是:一个HTML元素、如何重复图像的字符串。第二个参数的值和background-repeat属性一样,有repeat、repeat-x、repeat-y、no-repeat这几个值。比如:

import grid from "./images/grid.png"//引入图片
const canvas=document.getElementById("canvas")
const ctx=canvas.getContext("2d")

const img=new Image()
img.src=grid
img.onload=()=>{
  const pattern=ctx.createPattern(img,"repeat")
  ctx.fillStyle=pattern
  ctx.fillRect(1,1,canvas.width,canvas.height)
}

呈现的效果:

image4.png

图像数据

ctx.getImageData()会获取画布上一个矩形范围的图像数据,语法:

ctx.getImageData(x, y, width, height);
//x:矩形起点x坐标
//y:矩形起点y坐标
//width:矩形宽度
//height:矩形高度

getImageData()方法会返回一个imageData对象,这个对象上面有3个属性,分别是data、width、height。其中width和height是获取矩形图像的宽度和高度,data是获取矩形图像的数据,它是一个数组,图像的每一个像素都由4个值表示,分别代表红、绿、蓝、透明度,在data数组中,第0到第3个值代表的就是第一个像素的信息。

利用图像数据,我们可以修改图像的透明度,假如现在画布上有一张图片:

image5.png

遍历图像数据,修改图像数据中透明度,然后再通过putImageData()方法将图像数据再绘制到画布上:

import tree from "./images/tree.jpg"//引入图片
const canvas=document.getElementById("canvas")
const ctx=canvas.getContext("2d")

const img=new Image()
img.src=tree
img.onload=()=>{
  ctx.drawImage(img,0,0,100,80)
  const imgData=ctx.getImageData(0,0,100,80)
  const data=imgData.data
  for(let i=3;i<data.length;i+=4){
    data[i]=125
  }
  ctx.putImageData(imgData,0,0)
}

效果:

image6.png

合成

使图像透明也可以使用绘制上下文对象的全局属性:globalAlpha 。globalAlpha 可以取0~1之间的数值,默认为0,例如可以绘制一个透明的矩形,绘制完之后将globalAlpha 的值重新设置为默认值:

ctx.fillStyle="red"
ctx.fillRect(1,1,30,15)

ctx.globalAlpha=0.5

ctx.fillStyle="blue"
ctx.fillRect(16,8,30,15)

ctx.globalAlpha=0

这样就会看见画布上有一个半透明的蓝色矩形和一个红色矩形,并且能透过蓝色矩形看到下面的红色矩形

类似globalAlpha 这样的全局属性还有globalCompositeOperation,globalCompositeOperation表示新绘制的图形与原有图形怎么融合。这个属性是一个字符串,取值如下:

属性名属性说明案例
source-over默认值。新图形绘制在原有图形上面source-over.png
source-in新图形只会绘制出与原有图形重叠的部分,其余内容透明source-in.png
source-out新图形只会绘制出与原有图形不重叠的部分,其余内容透明source-out.png
source-atop新图形只会绘制出与原有图形重叠的部分,原有图形不受影响source-atop.png
destination-over新图形绘制在原有图形下面destination-over.png
destination-in新图形绘制在原有图形下面,画布上只剩下重叠部分,其余图形透明destination-in.png
destination-out新图形以及和原有图形重叠的部分都透明,其余不受影响destination-out.png
destination-atop新图形绘制在原有图形下面,原有图形与新图形不重叠的部分透明destination-atop.png
lighter新图形和原有图形重叠的部分像素相加,使该部分变亮lighter.png
copy新图形将擦除并完全取代原有图形copy.png
xor新图形与原有图形重叠部分像素执行“异或”操作xor.png