跳至主要內容

01.canvas之学习之路

pinia原创大约 21 分钟CANVAScanvas

初识canvas

canvasMDN文档open in new window

<canvas>元素用于生成图像。它本身就像一个画布,JavaScript 通过操作它的 API,在上面生成图像。它的底层是一个个像素,基本上<canvas>是一个可以用 JavaScript 操作的位图(bitmap)。

它与 SVG 图像的区别在于,<canvas>是脚本调用各种方法生成图像,SVG 则是一个 XML 文件,通过各种子元素生成图像。

使用 Canvas API 之前,需要在网页里面新建一个<canvas>元素。

浏览器不支持 Canvas

  • 如果浏览器不支持这个 API,就会显示<canvas>标签中间的文字:“您的浏览器不支持 Canvas”。
<canvas id="myCanvas" width="400" height="250">
  您的浏览器不支持 Canvas,请下载最新版浏览器
  <a href="https://www.google.cn/intl/zh-CN/chrome/">立即下载</a>
</canvas>
<body>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
	style:一般是网页的大小
 -->
<canvas id="canvas" width="500" height="500"></canvas>
<script>
    //1.找到画布
    const canvas = document.getElementById('canvas');
    //2.获取画布的上下文
    const ctx = canvas.getContext('2d');
</script>
</body>
  • Canvas API 需要getContext方法指定参数2d,表示该<canvas>节点生成 2D 的平面图像。如果参数是webgl,就表示用于生成 3D 的立体图案,这部分属于 WebGL API。
  • 按照用途,Canvas API 分成两大部分:绘制图形和图像处理。
  • canvas上下文的兼容性问题
//浏览器兼容性问题
if(!canvas.getContext){
   alert('您的浏览器不支持canvas');
}

canvas绘制基本图形

  • Canvas 画布提供了一个作图的平面空间,该空间的每个点都有自己的坐标。原点(0, 0)位于图像左上角,x轴的正向是原点向右,y轴的正向是原点向下。

1.绘制路径的属性或方法

  • CanvasRenderingContext2D.beginPath():开始绘制路径。
  • CanvasRenderingContext2D.closePath():结束路径,返回到当前路径的起始点,会从当前点到起始点绘制一条直线。如果图形已经封闭,或者只有一个点,那么此方法不会产生任何效果。
  • CanvasRenderingContext2D.moveTo():设置路径的起点,即将一个新路径的起始点移动到(x,y)坐标。
  • CanvasRenderingContext2D.lineTo():使用直线从当前点连接到(x, y)坐标。
  • CanvasRenderingContext2D.fill():在路径内部填充颜色(默认为黑色)。
  • CanvasRenderingContext2D.stroke():路径线条着色(默认为黑色)。
  • CanvasRenderingContext2D.fillStyle:指定路径填充的颜色和样式(默认为黑色)。
  • CanvasRenderingContext2D.strokeStyle:指定路径线条的颜色和样式(默认为黑色)。

绘制一个三角形

    ctx.beginPath()
    ctx.moveTo(100, 100);
    ctx.lineTo(200, 200);
    ctx.lineTo(100, 200);
    ctx.closePath()
    ctx.strokeStyle ='red'
    ctx.stroke()

注意

  • 必须先确定样式,然后再填充或描边

2.绘制弧线

以下方法用于绘制弧形。

  • CanvasRenderingContext2D.arc():通过指定圆心和半径绘制弧形。
  • CanvasRenderingContext2D.arcTo():通过指定两根切线和半径绘制弧形。

语法

ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
//x和y参数是圆心坐标
//radius是半径
//startAngle和endAngle则是扇形的起始角度和终止角度(以弧度表示)
//anticlockwise表示做图时应该逆时针画(true)还是顺时针画(false),这个参数用来控制扇形的方向(比如上半圆还是下半圆)。
ctx.arcTo(cp1x,cp2y,endx,endy,raduis)
//cp1x,cp2y是控制点的坐标,
//endx,endy是结束点的坐标,
//raduis是半径。

绘制一个笑脸

    //笑脸
    //head
    ctx.beginPath()
    ctx.arc(75, 75, 50, 0, Math.PI * 2);
    ctx.stroke()
    ctx.closePath()
    //mouth
    ctx.beginPath()
    ctx.arc(75, 75, 35, 0, Math.PI);
    ctx.stroke()
    ctx.closePath()
    //lefteye
    ctx.beginPath()
    ctx.arc(60, 65, 5, 0, Math.PI * 2);
    ctx.stroke()
    ctx.closePath()
    //righteye
    ctx.beginPath()
    ctx.arc(90, 65, 5, 0, Math.PI * 2);
    ctx.stroke();
    ctx.closePath()

    //笑脸
    ctx.beginPath()
    ctx.arc(175, 175, 50, 0, Math.PI * 2);
    ctx.moveTo(210, 175);
    ctx.arc(175, 175, 35, 0, Math.PI);
    ctx.moveTo(165, 165);
    ctx.arc(160, 165, 5, 0, Math.PI * 2);
    ctx.moveTo(195, 165);
    ctx.arc(190, 165, 5, 0, Math.PI * 2);
    ctx.stroke();
    ctx.closePath()

3.绘制矩形

以下方法用来绘制矩形。

  • CanvasRenderingContext2D.rect():绘制矩形路径。
  • CanvasRenderingContext2D.fillRect():填充一个矩形。
  • CanvasRenderingContext2D.strokeRect():绘制矩形边框。
  • CanvasRenderingContext2D.clearRect():指定矩形区域的像素都变成透明。

绘制一个矩形

//绘制矩形-填充-坐标(x,y),宽度,高度
ctx.fillRect(0,0,100,100);
//fillReact拆分写法
ctx.rect(100,100,100,100);
ctx.fill();  
//绘制矩形-描边-坐标(x,y),宽度,高度
ctx.strokeRect(200,200,300,300);
//strokeReact拆分写法
ctx.rect(200,200,100,100);
ctx.stroke();
//绘制矩形-清除-坐标(x,y),宽度,高度
ctx.clearRect(50,50,100,100);

线性清除动画设计

let height = 0;
let interval = setInterval(()=>{
    height += 10;
    ctx.clearRect(0,0,canvas.clientWidth,height);
    if (height >= canvas.clientHeight){
         clearInterval(interval)
    }
},100)

4.二次贝塞尔曲线及三次贝塞尔曲线

绘制二次贝塞尔曲线

  • 语法
ctx.quadraticCurveTo(cp1x,cp1y,endx,endy)
//cp1x,cp1y为第一个控制点的坐标
//endx,endy为结束点坐标
  • 公式:B(t)=(1-t)2P0+2t(1-t)P1+t2P2,t∈[0,1]

绘制三次贝塞尔曲线

  • 语法
ctx.bezierCureTO(cp1x,cp1y,cp2x,cp2y,endx,enxy)
//cp1x,cp1y为第一个控制点的坐标
//cp2x,cp2y为第二个控制点的坐标
//endx,endy为结束点坐标
  • 公式:B(t)=P0(1-t)3+3P1t(1-t)2+3P2t2(1-t)+P3t3,t∈[0,1]

利用二次贝塞尔曲线绘制一个消息气泡

    ctx.beginPath()
    ctx.moveTo(300,400)
    ctx.quadraticCurveTo(200,400,200,300)
    ctx.quadraticCurveTo(200,200,300,200)
    ctx.lineTo(600,200)
    ctx.quadraticCurveTo(700,200,700,300)
    ctx.quadraticCurveTo(700,400,600,400)
    ctx.lineTo(350,400)
    ctx.quadraticCurveTo(350,480,260,480)
    ctx.quadraticCurveTo(300,480,300,400)
    ctx.closePath()
    ctx.stroke()

利用三次贝塞尔曲线绘制一个心形

    //绘制心形
    ctx.beginPath()
    ctx.moveTo(500,350)
    ctx.quadraticCurveTo(500,200,350,200)
    ctx.bezierCurveTo(150,185,100,400,500,700)
    ctx.bezierCurveTo(900,400,850,185,650,200)
    ctx.quadraticCurveTo(500,200,500,350)
    ctx.stroke()

5.Path2D对象

  • 本质就是路径的封装
let message = new Path2D()  //创建一个路径
    message.moveTo(300,400)
    message.quadraticCurveTo(200,400,200,300)
    message.quadraticCurveTo(200,200,300,200)
    message.lineTo(600,200)
    message.quadraticCurveTo(700,200,700,300)
    message.quadraticCurveTo(700,400,600,400)
    message.lineTo(350,400)
    message.quadraticCurveTo(350,480,260,480)
    message.quadraticCurveTo(300,480,300,400)
ctx.stroke(message)

//绘制正方形
let square = new Path2D('M10 10 h 80 v 80 h -80 Z')  //svg字符串写法
ctx.stroke(square)

canvas样式控制

1.色彩colors

以下方法用来给图形上色。

  • CanvasRenderingContext2D.fillStyle:设置填充的颜色。
  • CanvasRenderingContext2D.strokeStyle:设置轮廓的颜色。
    ctx.strokeStyle = 'red'
    ctx.fillStyle = 'blue'
	ctx.strokeStyle = '#ffffff'
	ctx.fillStyle = '#ffffff'
	ctx.strokeStyle = 'rgb(255,255,255)'
	ctx.fillStyle = 'rgb(255,255,255)'
	ctx.strokeStyle = 'rgba(255,255,255,0.5)'
	ctx.fillStyle = 'rgba(255,255,255,0.5)'

2.透明度globalAlpha

ctx.globalAlpha = 0.5

3.渐变色

以下方法用于设置渐变效果和图像填充效果。

  • CanvasRenderingContext2D.createLinearGradient():定义线性渐变样式。
  • CanvasRenderingContext2D.createRadialGradient():定义径向渐变样式。
  • CanvasRenderingContext2D.createRadialGradient():定义圆锥渐变样式。
  • CanvasRenderingContext2D.createPattern():定义图像填充样式。

画一个线性渐变的动画

    let index = 0;
    const render =()=>{
        ctx.clearRect(0,0,1200,1000)//清除画布(起始点x,y,宽度,高度
        index += 0.005
        if(index>1){
            index = 0
        }
        let linearGradient = ctx.createLinearGradient(10,10,210,10)//线性渐变(起始点x,y,结束点x,y)
        linearGradient.addColorStop(0,'red')//添加渐变颜色
        linearGradient.addColorStop(index,'skyblue')
        linearGradient.addColorStop(1,'blue')
        ctx.fillStyle = linearGradient
        ctx.fillRect(10,10,200,200)//填充矩形(起始点x,y,宽度,高度)
        requestAnimationFrame(render)//动画
    }
    render()

画一个径向渐变的模拟的3D球

    ctx.arc(200, 200, 100, 0, 2 * Math.PI);
    let radialGradient = ctx.createRadialGradient(150, 150, 1, 200, 200, 100);//创建一个渐变对象(渐变开始的圆心坐标,渐变开始的半径,渐变结束的圆心坐标,渐变结束的半径)
    radialGradient.addColorStop(0, "#ffcccc");//添加渐变颜色
    radialGradient.addColorStop(1, 'red');
    ctx.fillStyle = radialGradient;//设置填充样式
    ctx.fill();//填充

画一个锥形渐变模拟的时钟分针

    let angle = 0;
    let time = setInterval(()=>{
        if(angle>60){
            angle = 0;
        }
        angle++
        ctx.arc(200, 200, 100, 0, 2 * Math.PI);
        let conicGradient = ctx.createConicGradient((angle*(Math.PI/30)),200,200) //圆锥渐变(角度,原心x,y)
        conicGradient.addColorStop(0, "blue");//添加渐变颜色
        conicGradient.addColorStop(0.5,"yellow")
        conicGradient.addColorStop(1, 'red');
        ctx.fillStyle = conicGradient;//设置填充样式
        ctx.fill();//填充
    },1000)

画一个图片

    ctx.arc(200, 200, 100, 0, 2 * Math.PI);
    let image = new Image();
    image.src = './assets/DreamShaper_v5_O_ultimo_ser_humano_vivo_no_planeta_terra_2.jpg';
    image.width=200;
    image.height =200
    image.onload = function () {
        ctx.fillStyle = ctx.createPattern(image, 'no-repeat');//创建图片对象(图片对象,重复方式:repeat,no-repeat,repeat-x,repeat-y)
        ctx.fill();
    }

4.线条样式Line Style

以下的方法和属性控制线条的视觉特征。

  • CanvasRenderingContext2D.lineWidth:指定线条的宽度,默认为1.0。
  • CanvasRenderingContext2D.lineCap:指定线条端点的样式,有三个可能的值:butt(默认值,末端为矩形)、round(末端为圆形)、square(末端为突出的矩形,矩形宽度不变,高度为线条宽度的一半)。
  • CanvasRenderingContext2D.lineJoin:指定线段交点的样式,有三个可能的值:round(交点为扇形)、bevel(交点为三角形底边)、miter(默认值,交点为菱形)。
  • CanvasRenderingContext2D.miterLimit:指定交点菱形的长度,默认为10。该属性只在lineJoin属性的值等于miter时有效。
  • CanvasRenderingContext2D.getLineDash():返回一个数组,表示虚线里面线段和间距的长度。
  • CanvasRenderingContext2D.setLineDash():数组,用于指定虚线里面线段和间距的长度。
	ctx.moveTo(100, 100);
    ctx.lineTo(200, 200);
    ctx.lineTo(300, 100);
    ctx.lineTo(400, 200);
    ctx.lineTo(500, 100);
    ctx.lineWidth = 40;//线条宽度
    ctx.lineCap = 'round';//线条末端 butt(默认,矩形) round(圆角) square(正方形)
    ctx.lineJoin = 'round';//线条连接处 bevel(三变形底边) round(圆角) miter(默认,尖)
	ctx.miterLimit = 1;//对miter斜接面长度的限制
	ctx.setLineDash([10,5]);//设置虚线([实线长度,虚线长度])
	ctx.lineDashOffset=10;//设置虚线偏移量
    ctx.stroke();

画一条移动的虚线

	let index = 0
    const render = () => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        index += 1
        if(index>40){
            index=0
        }
        ctx.moveTo(100, 100);
        ctx.lineTo(500,100);
        ctx.linewidth= 5;
        ctx.setLineDash([40,20]);//设置虚线([实线长度,虚线长度])
        ctx.lineDashOffset=index;//设置虚线偏移量
        ctx.stroke();
        requestAnimationFrame(render);
    }
    render()

5.阴影Shadows

以下属性用于设置阴影。

  • CanvasRenderingContext2D.shadowBlur:阴影的模糊程度,默认为0
  • CanvasRenderingContext2D.shadowColor:阴影的颜色,默认为black
  • CanvasRenderingContext2D.shadowOffsetX:阴影的水平位移,默认为0
  • CanvasRenderingContext2D.shadowOffsetY:阴影的垂直位移,默认为0
    ctx.fillStyle = 'skyblue';
    ctx.shadowOffsetX = 10;//阴影的水平偏移量
    ctx.shadowOffsetY = 10;//阴影的垂直偏移量
    ctx.shadowBlur = 10;//阴影的模糊程度
    ctx.shadowColor = 'rgba(255,200,200,0.5)';//阴影的颜色
    ctx.fillRect(100,100,200,200)

canvas绘制文字

以下方法和属性用于绘制文本。

  • CanvasRenderingContext2D.fillText():在指定位置绘制实心字符。
  • CanvasRenderingContext2D.strokeText():在指定位置绘制空心字符。
  • CanvasRenderingContext2D.measureText():返回一个 TextMetrics 对象。
  • CanvasRenderingContext2D.font:指定字型大小和字体,默认值为10px sans-serif
  • CanvasRenderingContext2D.textAlign:文本的对齐方式,默认值为start
  • CanvasRenderingContext2D.direction:文本的方向,默认值为inherit
  • CanvasRenderingContext2D.textBaseline:文本的垂直位置,默认值为alphabetic
    ctx.font = '100px Arial';//字体
    ctx.textAlign = 'center';//文本对齐方式 start(默认) end center left right
    ctx.textBaseline='ideographic';//基线对齐方式 top(上) bottom(下) middle(居中) alphabetic(默认,字母基线) hanging(悬挂对齐) ideographic(下沿对齐)
    ctx.direction = 'inherit';//文本方向 inherit(默认,继承父级的设置),ltr(从左到右),rtl(从右到左)。
    ctx.fillText('hello world', 400, 100);//填充文字('text',文本起点x,y, ?maxWidth)
    ctx.strokeText('hello world', 400, 200);//描边文字

    let text= ctx.measureText('hello world')// 测量文本宽度
    console.log(text)

1.fillText()填充字符

CanvasRenderingContext2D.fillText(text, x, y [?maxWidth])

该方法接受四个参数。

  • text:所要填充的字符串。
  • x:文字起点的横坐标,单位像素。
  • y:文字起点的纵坐标,单位像素。
  • maxWidth:文本的最大像素宽度。该参数可选,如果省略,则表示宽度没有限制。如果文本实际长度超过这个参数指定的值,那么浏览器将尝试用较小的字体填充。

注意

注意,fillText()方法不支持文本断行,所有文本一定出现在一行内。如果要生成多行文本,只有调用多次fillText()方法。

2.strokeText()描边字符

strokeText()方法用来添加空心字符,它的参数与fillText()一致。

3.textAlign对齐方式

textAlign属性用来指定文本的对齐方式。它可以取以下几个值。

  • left:左对齐
  • right:右对齐
  • center:居中
  • start:默认值,起点对齐(从左到右的文本为左对齐,从右到左的文本为右对齐)。
  • end:结尾对齐(从左到右的文本为右对齐,从右到左的文本为左对齐)。

4.direction文本的方向

direction属性指定文本的方向,默认值为inherit,表示继承<canvas>document的设置。其他值包括ltr(从左到右)和rtl(从右到左)。

5.textBaseLine文本的基线对齐方式

textBaseline属性指定文本的垂直位置,可以取以下值。

  • top:上部对齐(字母的基线是整体上移)。
  • hanging:悬挂对齐(字母的上沿在一根直线上),适用于印度文和藏文。
  • middle:中部对齐(字母的中线在一根直线上)。
  • alphabetic:默认值,表示字母位于字母表的正常位置(四线格的第三根线)。
  • ideographic:下沿对齐(字母的下沿在一根直线上),使用于东亚文字。
  • bottom:底部对齐(字母的基线下移)。对于英文字母,这个设置与ideographic没有差异。

6.measureText()获取参数信息

measureText()方法接受一个字符串作为参数,返回一个 TextMetrics 对象,可以从这个对象上面获取参数字符串的信息,目前主要是文本渲染后的宽度(width)。

var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');

var text1 = ctx.measureText('Hello world');
text1.width // 55.14

ctx.font = 'Bold 20px Arial';
var text2 = ctx.measureText('Hello world');
text2.width // 107.78

上面代码中,10px大小的字符串Hello world,渲染后宽度为49.46。放大到20px以后,宽度为107.78

canvas绘制图片

1.drawImage()

Canvas API 允许将图像文件写入画布,做法是读取图片后,使用drawImage()方法将这张图片放上画布。

CanvasRenderingContext2D.drawImage()有三种使用格式。

ctx.drawImage(image, dx, dy);
ctx.drawImage(image, dx, dy, dWidth, dHeight);
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

各个参数的含义如下。

  • image:图像元素
  • sx:图像内部的横坐标,用于映射到画布的放置点上。
  • sy:图像内部的纵坐标,用于映射到画布的放置点上。
  • sWidth:图像在画布上的宽度,会产生缩放效果。如果未指定,则图像不会缩放,按照实际大小占据画布的宽度。
  • sHeight:图像在画布上的高度,会产生缩放效果。如果未指定,则图像不会缩放,按照实际大小占据画布的高度。
  • dx:画布内部的横坐标,用于放置图像的左上角
  • dy:画布内部的纵坐标,用于放置图像的右上角
  • dWidth:图像在画布内部的宽度,会产生缩放效果。
  • dHeight:图像在画布内部的高度,会产生缩放效果。

写一个小案例

const image = new Image()//创建一个图片对象
image.src = './assets/DreamShaper_v5_O_ultimo_ser_humano_vivo_no_planeta_terra_2.jpg'//设置图片的路径
image.onload=()=>{ //图片加载完成后执行
        // ctx.drawImage(image,0,0)//绘制图片(图片对象,x坐标,y坐标)
        // ctx.drawImage(image,0,0,100,100)//绘制图片(图片对象,x坐标,y坐标,宽度,高度)
        ctx.drawImage(image,200,200,300,300,0,0,200,200)//绘制图片(图片对象,原图x坐标,原图y坐标,原图宽度,原图高度,裁剪后画布x坐标,裁剪后画布y坐标,渲染图片宽度,渲染图片高度)
}

2.ImageData像素操作

ImageData对象中存储着canvas对象真实的像素数据,它包含以下几个只读属性

  • width:图片宽度,单位是像素

  • height:图片高度,单位是像素

  • data:Unit8ClampedArray类型的一维数组,里面包含着RGBA格式的整数类型,范围在0至255之间

<script>
    let canvas = document.getElementById('canvas')
    let ctx = canvas.getContext('2d')

    let img = new Image()
    img.src = './assets/123.jpg'
    img.onload = function () {
        ctx.drawImage(img, 0, 0, 600, 600)
        let imageData = ctx.getImageData(0, 0, 600, 600)//获取画布上的像素点 getImageData(x, y, width, height)
        for (let i = 0; i < imageData.data.length; i += 4) {
            // let gray = (imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]) / 3
            imageData.data[i] = 255 - imageData.data[i]
            imageData.data[i + 1] = 255 - imageData.data[i + 1]
            imageData.data[i + 2] = 255 - imageData.data[i + 2]
            imageData.data[i + 3] = 255

        }
        ctx.putImageData(imageData, 0, 0,0,0,600,600)//将像素点放回画布 putImageData(imageData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
    }
</script>

canvas图像变换

以下方法用于图像变换。

  • CanvasRenderingContext2D.rotate():图像旋转
  • CanvasRenderingContext2D.scale():图像缩放
  • CanvasRenderingContext2D.translate():图像平移
  • CanvasRenderingContext2D.transform():通过一个变换矩阵完成图像变换
  • CanvasRenderingContext2D.setTransform():取消前面的图像变换

1.rotate()

CanvasRenderingContext2D.rotate()方法用于图像旋转。它接受一个弧度值作为参数,表示顺时针旋转的度数。

注意

rotate是旋转的坐标系

// ctx.translate(100,200)
    ctx.rotate(45 * Math.PI / 180)
    // ctx.translate(100,200)
    ctx.fillRect(0, 0, 200, 20)

2.scale()

CanvasRenderingContext2D.scale()方法用于缩放图像。它接受两个参数,分别是x轴方向的缩放因子和y轴方向的缩放因子。默认情况下,一个单位就是一个像素,缩放因子可以缩放单位,比如缩放因子0.5表示将大小缩小为原来的50%,缩放因子10表示放大十倍。

ctx.scale(5,2) //scale 缩放(x,y)
ctx.fillRect(0,0,200,20)

注意

如果缩放因子为1,就表示图像没有任何缩放。如果为-1,则表示方向翻转。ctx.scale(-1, 1)为水平翻转,ctx.scale(1, -1)表示垂直翻转。

注意,负向缩放本质是坐标翻转,所针对的坐标轴就是画布左上角原点的坐标轴。

3.translate()

CanvasRenderingContext2D.translate()方法用于平移图像。它接受两个参数,分别是 x 轴和 y 轴移动的距离(单位像素)。

注意

translate是移动的坐标系

ctx.translate(100,100)//位移(水平位移,垂直位移)
ctx.fillRect(0,0,200,20)

4.transform()

CanvasRenderingContext2D.transform()方法接受一个变换矩阵的六个元素作为参数,完成缩放、旋转、移动和倾斜等变形。

它的使用格式如下。

ctx.transform(a, b, c, d, e, f);
/*
a:水平缩放(默认值1,单位倍数)
b:水平倾斜(默认值0,单位弧度)
c:垂直倾斜(默认值0,单位弧度)
d:垂直缩放(默认值1,单位倍数)
e:水平位移(默认值0,单位像素)
f:垂直位移(默认值0,单位像素)
*/
ctx.transform(1, -1, 0, 1, 100, 100);//transform(a,b,c,d,e,f)  a水平缩放,b水平倾斜,c垂直倾斜,d垂直缩放,e水平位移,f垂直位移
ctx.fillRect(0, 0, 200, 20)

注意

注意,多个transform()方法具有叠加效果。

5.setTransform()

CanvasRenderingContext2D.setTransform()方法取消前面的图形变换,将画布恢复到该方法指定的状态。该方法的参数与transform()方法完全一致。

6.clip()

CanvasRenderingContext2D.clip()方法展示路径里面的内容,路径外面的透明

<script>
    let canvas = document.getElementById('canvas')
    let ctx = canvas.getContext('2d')

    let path = new Path2D()
    path.moveTo(200, 200)
    path.lineTo(290, 200)
    path.arc(300, 200, 10, Math.PI, 0)
    path.lineTo(400, 200)
    path.lineTo(400, 400)
    path.lineTo(200, 400)
    path.lineTo(200, 200)
    ctx.clip(path)
    let img = new Image()
    img.src = './assets/123.jpg'
    img.onload = function () {
        ctx.drawImage(img, 0, 0, 600, 600)
    }
</script>

canvas图像的合成

CanvasRenderingContext2D.globalCompositeOperation = 'source-over'

    ctx.fillStyle = 'yellowgreen';
    ctx.fillRect(100,100,300,100)
    
    /**
     * 设置合成模式
     * source-in:只显示源图像 + 目标图像重叠的部分
     * source-out:只显示目标图像 + 源图像重叠的部分
     * source-atop:在目标图像顶部显示源图像 + 目标图像重叠的部分
     * destination-over:在目标图像下方显示源图像    默认
     * destination-in:只显示目标图像   重叠的部分
     * destination-out:只显示目标图像  不重叠的部分
     * destination-atop:在源图像顶部显示目标图像 + 源图像重叠的部分
     * lighter:显示源图像 + 目标图像 重叠部分的颜色值相加
     * multiply:显示源图像 * 目标图像 重叠部分的颜色值相乘
     * screen:显示源图像 + 目标图像的反色  重叠部分的颜色值相加
     * xor:使用异或操作对源图像与目标图像进行组合 重叠部分的颜色值进行异或操作
     * copy:只显示源图像 不重叠的部分
     * @type {string}
     */
    ctx.globalCompositeOperation = 'source-in';/

    ctx.fillStyle = 'skyblue';
    ctx.fillRect(150,150,300,100)
source-over默认。在目标图像上显示源图像。
source-atop在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。
source-in在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。
source-out在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。
destination-over在源图像上方显示目标图像。
destination-atop在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。
destination-in在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。
destination-out在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。
lighter显示源图像 + 目标图像。
copy显示源图像。忽略目标图像。
xor使用异或操作对源图像与目标图像进行组合。

具体每个值对应的描述,可以点击这里查阅open in new window

具体效果可以看下面的实现效果:

https://jsrun.net/tw3Kp/edit

做一个刮刮乐的小案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>刮刮乐</title>
</head>
<body>
<div id="ggk">谢谢惠顾</div>
<canvas id="canvas" width="600" height="200"></canvas>
</body>
<style>
    * {
        margin: 0;
        padding: 0;
    }

    #ggk {
        position: relative;
        width: 600px;
        height: 200px;
        background-color: black;
        font-size: 30px;
        font-weight: 900;
        color: white;
        text-align: center;
        line-height: 200px;

    }

    #canvas {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 2;
    }
</style>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = 'gray'
    ctx.fillRect(0, 0, 600, 200)
    let isDraw = false
    canvas.onmousedown = () => {
        isDraw = true
    }
    canvas.onmouseup = () => {
        isDraw = false
    }
    canvas.onmousemove = (e) => {
        if (isDraw) {
            let x = e.pageX
            let y = e.pageY
            ctx.globalCompositeOperation = 'destination-out'
            ctx.arc(x, y, 20, 0, Math.PI * 2)
            ctx.fill()

        }
    }

    let random = Math.random()
    let ggk = document.getElementById('ggk')
    if (random <= 0.1) {
        ggk.innerHTML = '恭喜你中奖了'
    } else {
        ggk.innerHTML = '谢谢惠顾'
    }
</script>
</html>

canvas绘制视频

// 获取logo图片对象
let img = new Image()
img.src = './assets/DreamShaper_v5_O_ultimo_ser_humano_vivo_no_planeta_terra_2.jpg'//设置图片的路径
//获取视屏
const video = document.querySelector('video');
let btn = document.getElementById('play');
btn.onclick = function () {
	video.paused ? video.play() : video.pause();//判断视屏是否暂停
	render()
}
const render = () => {
	ctx.drawImage(video, 0, 0, 1200, 1000);
	ctx.drawImage(img, 1100, 900, 100, 100)
	requestAnimationFrame(render)
}

状态的保存和恢复

  1. save()

    • 保存画布(canvas)的所有状态
  2. restore()

    • 恢复至前一次的状态
  3. save和restore方法是用来保存和恢复canvas状态的,都没有参数,canvas的状态就是当前画面应用的所有样式和变形的一次快照

  4. canvas所有的状态存储在栈中,每当save()方法被调用后,所有的状态就会被推送到栈中保存,一个绘画状态包。

    ctx.fillStyle = 'gray'
    ctx.fillRect(0, 0, 600, 600)
    ctx.save()
    ctx.beginPath()
    ctx.moveTo(250,250)
    ctx.lineTo(350,250)
    ctx.lineTo(350,350)
    ctx.lineTo(250,350)
    ctx.closePath()
    ctx.clip()
    ctx.fillStyle = 'skyblue'
    ctx.fillRect(0, 0, 600, 600)
    ctx.restore()
    ctx.fillRect(700,700,50,50)

高级封装绘制元素和实现元素交互

<script>
    let canvas = document.getElementById('canvas')
    let ctx = canvas.getContext('2d')

    class box {
        constructor(x, y) {
            //初始位置
            this.x = x
            this.y = y
            //初始颜色
            this.color = ''
            //是否在路径内
            this.isIn = false
            //事件列表
            this.eventMapList = {
                onhover: [],
                onleave: []
            }
            //path2D对象
            this.path = new Path2D()
            //绘制路径
            this.path.moveTo(this.x, this.y)
            this.path.bezierCurveTo(this.x + 50, this.y - 50, this.x + 100, this.y, this.x, this.y + 50)
            this.path.bezierCurveTo(this.x - 100, this.y, this.x - 50, this.y - 50, this.x, this.y)
            //绑定事件
            canvas.addEventListener('mousemove', (e) => {
                let x = e.offsetX
                let y = e.offsetY
                this.isIn = ctx.isPointInPath(this.path, x, y)
                if (this.isIn) {
                    this.eventMapList.onhover.forEach(fn => fn())
                } else {
                    this.eventMapList.onleave.forEach(fn => fn())
                }
            })
        }

        //鼠标移入事件
        onHover(fn) {
            this.eventMapList.onhover.push(fn)
        }

        //鼠标移出事件
        onLeave(fn) {
            this.eventMapList.onleave.push(fn)
        }

        //绘制
        draw() {
            ctx.save()
            ctx.fillStyle = this.color
            ctx.fill(this.path)
            ctx.restore()
        }
    }

    //实例化
    let box1 = new box(100, 100)
    box1.onHover(() => {
        box1.color = 'skyblue'
    })
    box1.onLeave(() => {
        box1.color = 'orangered'
    })
    //渲染
    const render = () => {
        ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight)//清空画布
        box1.draw()
        requestAnimationFrame(render)
    }
    render()

</script>

精彩案例

1.制作一个小画板

小画板
<canvas id="canvas" width="600" height="300" style="border: black solid 2px"></canvas>
<hr>
<label for="inputColor">画笔颜色<input type='color' name="" id="inputColor" value=""/></label>
<button id="boldBtn" type="button">粗线条</button>
<button id="thinBtn" type="button">细线条</button>
<button id="eraserBtn" type="button">橡皮擦</button>
<button id="claerBtn" type="button">清空</button>
<button id="saveBtn" type="button">保存</button>
button.active {
	background-color: skyblue;
	color: white;
	border: #444 solid 1px;
}
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
let btn = {
    inputColor: document.getElementById('inputColor'),
    boldBtn: document.getElementById('boldBtn'),
    thinBtn: document.getElementById('thinBtn'),
    eraserBtn: document.getElementById('eraserBtn'),
    claerBtn: document.getElementById('claerBtn'),
    saveBtn: document.getElementById('saveBtn'),
}
//允许画画
let isDraw = false
let linewidth = 1
//连接处圆润
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
//鼠标按下去事件
canvas.onmousedown = (e) => {
    isDraw = true
    let x = e.pageX - canvas.offsetLeft
    let y = e.pageY - canvas.offsetTop
    ctx.beginPath()
    ctx.moveTo(x, y)
}
//鼠标移动事件
canvas.onmousemove = (e) => {
    if (isDraw) {
        let x = e.pageX - canvas.offsetLeft
        let y = e.pageY - canvas.offsetTop
        ctx.lineTo(x, y)
        ctx.stroke()
    }
}
//鼠标t抬起事件
canvas.onmouseup = () => {
    isDraw = false
    ctx.closePath()
}
canvas.onmouseleave = () => {
    isDraw = false
    ctx.closePath()
}
btn.boldBtn.onclick = () => {
    for (let item in btn) {
        btn[item].classList.remove('active')
    }
    btn.boldBtn.classList.add('active')
    ctx.globalCompositeOperation = 'source-over'

    if (linewidth >= 40) return
    linewidth += 1
    ctx.lineWidth = linewidth
}
btn.thinBtn.onclick = () => {
    for (let item in btn) {
        btn[item].classList.remove('active')
    }
    btn.thinBtn.classList.add('active')
    ctx.globalCompositeOperation = 'source-over'

    if (linewidth <= 1) return
    linewidth -= 1
    ctx.lineWidth = linewidth
}
btn.eraserBtn.onclick = () => {
    for (let item in btn) {
        btn[item].classList.remove('active')
    }
    btn.eraserBtn.classList.add('active')
    ctx.globalCompositeOperation = 'destination-out'
    ctx.lineWidth = 30
}
btn.claerBtn.onclick = () => {
    for (let item in btn) {
        btn[item].classList.remove('active')
    }
    btn.claerBtn.classList.add('active')
    ctx.clearRect(0, 0, canvas.width, canvas.height)
}
btn.saveBtn.onclick=()=>{
    let imageData = canvas.toDataURL()//获取图片的base64
    let aaa = document.createElement('a')
    aaa.setAttribute('download','canvas签名下载')///设置a标签的属性
    aaa.href = imageData
    aaa.click()
}
btn.inputColor.onchange=()=>{
    ctx.strokeStyle=btn.inputColor.value
}

2.制作一个小时钟

小时钟
<canvas id="canvas" width="600" height="300"></canvas>
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')


const render = () => {
    ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight)
    // 画表盘
    ctx.save()// 保存当前状态
    ctx.translate(300, 150)
    ctx.rotate(-Math.PI / 2)

    //时钟
    ctx.save()
    for (let i = 0; i < 12; i++) {
        ctx.beginPath()
        ctx.moveTo(100, 0)
        ctx.lineTo(120, 0)
        ctx.lineWidth = 4
        ctx.strokeStyle = 'gray'
        ctx.stroke()
        ctx.closePath()
        ctx.rotate(Math.PI / 6)
    }
    // 分钟
    ctx.restore()
    ctx.save()
    for (let i = 0; i < 60; i++) {
        ctx.beginPath()
        ctx.moveTo(110, 0)
        ctx.lineTo(120, 0)
        ctx.lineWidth = 2
        ctx.strokeStyle = 'gray'
        ctx.stroke()
        ctx.closePath()
        ctx.rotate(Math.PI / 30)
    }
    ctx.restore()
    ctx.save()

    //获取时间
    let date = new Date()
    let hour = date.getHours() % 12
    let minute = date.getMinutes()
    let second = date.getSeconds()

    //秒钟
    ctx.rotate(Math.PI / 30 * second )
    ctx.beginPath()
    ctx.moveTo(-15, 0)
    ctx.lineTo(95, 0)
    ctx.lineWidth = 2
    ctx.strokeStyle = 'red'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()

    //分针
    ctx.rotate(Math.PI / 30 * minute + Math.PI / 1800 * second)
    ctx.beginPath()
    ctx.moveTo(-12, 0)
    ctx.lineTo(80, 0)
    ctx.lineWidth = 3
    ctx.strokeStyle = '#999'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()

    //时针
    ctx.rotate(Math.PI / 6 * hour + Math.PI / 360 * minute + Math.PI / 21600 * second)
    ctx.beginPath()
    ctx.moveTo(-9, 0)
    ctx.lineTo(50, 0)
    ctx.lineWidth = 4
    ctx.strokeStyle = '#888'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()

    ctx.restore()// 恢复到保存的状态
    requestAnimationFrame(render)
}
render()
上次编辑于:
贡献者: 林深不见鹿