L o a d i n g . . .
SHIWIVI-文章

//sunny forever
while(life<end){
love++;
beAwesome :)}

    <
  • 主题:
  • + -
  • 清除背景
  • 禁用背景

Canvas

字数:29495 写于:2023-05-03
最新更新:2023-05-03 阅读本文预计花费您85分钟

引入

关于

canvas是HTML5新增的标签,用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面,canvas不支持IE8及IE8以下浏览器

具体的兼容性可以参考MDN文档: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/canvas

创建画布

通过HTML标签

可以通过直接添加HTML标签的方式创建canvas,并设置画布的宽高,然后通过getContext()方法获得画布的 2D 渲染上下文对象,需要为该方法提供‘2d’作为参数,该对象提供了用于在画布上绘图的方法和属性

<body>
    <canvas width="600" height="400" id="canvas"></canvas>
    <script>
        const canvas=document.getElementById('canvas');
        const context = canvas.getContext('2d');
    </script>
</body>
通过JavaScript动态创建

也可以通过JavaScript动态添加canvas并指定宽高,tips:通过JavaScript创建的canvas在写代码时IDE会有代码补全提示

 const canvas=document.createElement("canvas");
    canvas.width=600;
    canvas.height=400;
    document.body.appendChild(canvas);
 const context = canvas.getContext('2d');
画布尺寸的说明
  • canvas需要通过属性来设置宽度、高度,且不需要单位
  • 通过CSS设置canvas的width和height属性,仅用于缩放图像,无法改变画布尺寸,当CSS指定的宽高与画布的宽高比例不一致时,图像会出现扭曲
  • 默认画布大小为300px * 150px,宽高比为2:1,如果不指定画布的宽高,或者设置了无效值(如负数),则会使用默认值
通过CSS设置的Canvas宽高样式只会修改浏览器渲染后所显示图像的宽高,不会修改画布本身及其图像本身的宽高。浏览器会在渲染期间根据CSS对源图像进行伸缩,以适应指定的CSS样式大小。如果 CSS 的指定的宽高尺寸与画布的宽高比例不一致,图像有可能出现扭曲。而通过属性修改的画布宽度和高度,实际会修改Canvas DOM对象的属性值,即画布本身的宽度和高度,并将该DOM对象传递给canvas内部的2d渲染上下文环境对象(CanvasRenderingContext2D)

兼容性检查

IE9之前的浏览器不支持Canvas,如果不兼容canvas,canvas 标签会被浏览器解析为自定义标签,显示标签内的提示信息

<canvas id="canvas">抱歉,您的浏览器不支持 canvas 标签</canvas>
同时要做好JavaScript兼容性检查
const canvas = document.getElementById('canvas'); if (canvas.getContext){ const ctx = canvas.getContext('2d'); }

Canvas 2D渲染API

所有API都是基于canvas的2d渲染上下文对象,文中以context或ctx为对象名

绘制

路径

  • moveTo(x,y) 移动画笔到指定坐标
    指定画笔最先开始绘制的点,此后Canvas的绘制方法都是基于上一次的路径终点进行的,仍可以使用moveTo(x,y)修改画笔的坐标到路径终点以外

  • context.beginPath() 创建一个新路径

在同一个画布中绘制多个独立的图像,应当为每个图像创建不同的子路径,如:为不同的线段设置不同的颜色、线宽等样式,应当为每个线段创建独立的子路径,每个路径内部的样式、属性单独声明,否则后声明的样式会覆盖前面的样式,并且应当为每个独立的路径指定画笔初始点,执行绘制方法

const canvasPath=document.getElementById("canvasPath"); const ctxPath = canvasPath.getContext('2d'); ctxPath.lineWidth=5; ctxPath.beginPath(); ctxPath.strokeStyle="red"; ctxPath.moveTo(150,50); ctxPath.lineTo(50,200); ctxPath.stroke(); ctxPath.beginPath(); ctxPath.strokeStyle="blue"; ctxPath.moveTo(50,200); ctxPath.lineTo(250,200); ctxPath.stroke(); ctxPath.beginPath(); ctxPath.strokeStyle="yellow"; ctxPath.moveTo(250,200); ctxPath.lineTo(150,50); ctxPath.stroke();

在使用循环定时器创建动画时,往往需要在每次定时器执行时创建一个新路径,避免上次的路径影响到本次路径的绘制

const canvas=document.createElement("canvas"); canvas.width=300; canvas.height=300; document.body.appendChild(canvas); const ctx = canvas.getContext('2d'); let cpx=10; let timer=setInterval(()=>{ ctx.beginPath();//每次计时器开始时,开始新路径 ctx.clearRect(0,0,300,300);//清空画布 ctx.moveTo(50,50); ctx.quadraticCurveTo(cpx+=10,100,50,250);//动态绘制贝塞尔曲线 ctx.stroke(); if(cpx>=250){ clearInterval(timer) } },50)
  • context.closePath(); 闭合当前子路径
    将画笔坐标移回到当前子路径起始点,该方法会尝试从当前点到起始点绘制一条直线,如果图形已经是封闭的或者只有一个点,该方法不会做任何操作,可用于闭合图形
const canvasLine=document.getElementById("canvasLine"); const ctxLine = canvasLine.getContext('2d'); ctxLine.lineWidth=3; ctxLine.moveTo(150,50); ctxLine.lineTo(100,150); ctxLine.lineTo(200,150); ctxLine.closePath(); ctxLine.stroke();

描边

  • context.stroke() 绘制当前路径
  • context.lineWidth=值; 修改描边线条的宽度
  • context.strokeStyle=”颜色/渐变对象/Pattern对象”; 设置描边样式

填充

  • context.fill() 填充已闭合的路径
  • context.fillStyle=”颜色/渐变对象/Pattern对象”; 设置填充的样式

裁剪

context.clip() 根据当前路径进行裁剪
沿着路径进行裁剪,裁剪路径外的图形将不再显示在Canvas中

清除

context.clearRect(x,y,width,height) 清除指定区域内的画布内容

context.clearRect(0,0,canvas.width,canvas.height)//清空整个画布

封装路径-Path2D对象

let myPath2D=new Path2D();
返回一个Path2D对象,之后可以将路径添加到该对象中,并直接重用对象中的所有路径,stroke()、fill()、clip()均接收该对象作为参数

const canvas=document.getElementById("canvas"); const context = canvas.getContext('2d'); let myDesign=new Path2D(); //将路径封装到Path2D对象中而非context对象上 myDesign.moveTo(50,50); myDesign.lineTo(100,50); myDesign.lineTo(100,100); myDesign.lineTo(50,100); myDesign.lineTo(50,50); //之后可以直接重用封装好的路径 context.stroke(myDesign);//描边 context.fill(myDesign);//填充 context.clip(myDesign);//裁剪

位置判断

  • context.isPointInStroke(x, y) 返回boolean值,判断(x,y)点是否在当前路径上
  • context.isPointInStroke(path2D对象, x, y) 返回boolean值,判断(x,y)点是否在封装的path2D路径上
  • context.isPointInPath(x, y) 返回boolean值,判断(x,y)点是否在当前路径内
  • context.isPointInPath(path2D对象, x, y) 返回boolean值,判断(x,y)点是否在封装的path2D路径内

线段

实线线段

线段路径

context.lineTo(x,y) 连接直线路径到指定坐标

线段折点

context.lineJoin=”miter/round/bevel”;

设置或返回两条线交汇时,线段折线处的样式

  • miter 尖锐折线(默认)
  • round 圆角折线
  • bevel 切角折线

ctx.miterLimit = value;
设置或返回边角斜切面的限制长度(默认为10),下图为miterLimit=2(左)以及miterLimit=10(右)的区别

线段末端

context.lineCap=”butt/round/square;”

设置或返回线段末端线帽的样式,”round” 和 “square” 值会使线条略微变长

  • butt 末端以方形结束(默认)
  • round末端添加圆形线帽
  • square末端添加一个宽度相同,长度为宽度一半的矩形线帽

虚线线段

  • context.setLineDash([数组]); 传递一个数组来指定虚线线段和间隙的交替长度,空数组将设置为实线
  • context.getLineDash(); 返回一个数组,获取当前线段的样式
context.setLineDash([2,10,5,10]) context.moveTo(0,0) context.lineTo(100,200); context.stroke();

context.lineDashOffset = value;
设置虚线偏移值,可实现蚂蚁线效果

const canvasDash=document.getElementById("canvasDash"); const ctxDash = canvasDash.getContext('2d'); let offset=0; ctxDash.lineWidth=5; ctxDash.setLineDash([30,20]) ctxDash.setLineDash([30,20]) ctxDash.moveTo(0,100) ctxDash.lineTo(300,100); function render(){ offset++; if(offset>50){ offset=0 } ctxDash.clearRect(0,0,300,300) ctxDash.lineDashOffset=offset; ctxDash.stroke(); requestAnimationFrame(render) } render();

矩形

矩形路径

context.rect(x,y,width,height) 创建矩形路径

  • x, y为矩形坐标
  • width为矩形的宽度,正值矩形位于x坐标右侧,负值则位于左侧
  • height为矩形的高度,正值矩形位于y坐标下方,负值则在上方

仅创建矩形路径,不会显示在画布中,可以使用stroke()方法或fill()方法进行描边绘制或者填充绘制

描边矩形

context.strokeRect(x, y, width, height) 绘制描边矩形

创建矩形路径并描边,参数同上,相当于rect()方法和stroke()方法同时执行,可以使用lineWidth修改线宽,使用strokeStyle修改线条样式

填充矩形

context.fillRect(x, y, width, height) 填充一个矩形

创建矩形路径并填充,参数同上,相当于rect()方法和fill()方法同时执行,可以使用fillStyle修改填充样式

渐变对象

添加渐变色

渐变对象名.addColorStop(偏移量,”颜色”)

  • 对象名为以下三种渐变对象创建的实例
  • 偏移量为0~1之间的值,代表渐变开始到渐变终止的位置,等同于CSS中的百分比位置
  • 颜色取值同CSS

线性渐变对象

context.createLinearGradient(x1,y1,x2,y2)
在(x1,y1)到(x2,y2)矢量方向上创建径向渐变对象,并返回该对象

const ctx = canvas.getContext('2d'); const gradient=ctx.createLinearGradient(0,0,600,400); gradient.addColorStop(0,"red"); gradient.addColorStop(.3,"#df0"); gradient.addColorStop(1,"blue"); ctx.strokeStyle=gradient;

径向渐变对象

context.createRadialGradient(x0, y0, r0, x1, y1, r1);
以(x0,y0)为圆心,r0为半径确定一圆,以(x1,y1)为圆心,r1为半径确定另一圆,

根据参数确定两个圆的坐标,绘制放射性渐变的方法

const canvas1=document.getElementById("canvas1"); const ctx1 = canvas1.getContext('2d'); const radial=ctx1.createRadialGradient(50,50,50,250,250,100); radial.addColorStop(0,"red"); radial.addColorStop(.25,"yellow"); radial.addColorStop(.5,"green"); radial.addColorStop(.75,"#ff00fb"); radial.addColorStop(1,"blue"); ctx1.fillStyle=radial; ctx1.fillRect(0,0,300,300)

锥形渐变对象

context.createConicGradient(弧度值,x,y)
以(x,y)为锥形中心创建锥形渐变对象,弧度值角度为渐变开始的位置,角度通过Math.PI*角度/180运算为弧度值,值可正可负

const canvas=document.getElementById("canvas"); const ctx = canvas.getContext('2d'); const cg=ctx.createConicGradient(Math.PI*45/180,150,150); cg.addColorStop(0,"red"); cg.addColorStop(.25,"yellow"); cg.addColorStop(.5,"green"); cg.addColorStop(.75,"#ff00fb"); cg.addColorStop(1,"blue"); ctx.fillStyle=cg; ctx.fillRect(0,0,300,300);

锥形渐变兼容性远比线性渐变、径向渐变差,对浏览器内核版本要求较高,详见MDN文档: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createConicGradient

Pattern对象

创建模板对象,

createPattern(image,重复模式)
image为图像源,可以是<img>图像<video>视频<canvas>另外一个canvas对象canvas的2d上下文对象(CanvasRenderingContext2D)

图像的重复模式可以为repeatrepeat-xrepeat-yno-repeat,用法同CSS

const canvas=document.createElement("canvas"); canvas.width=1000; canvas.height=1000; document.body.appendChild(canvas); const ctx=canvas.getContext("2d"); let img=new Image(); img.src="./dog.jpg"; img.onload=function(){ const p=ctx.createPattern(img,'repeat-y') ctx.fillStyle=p; ctx.fillRect(0,0,1000,1000) }

曲线

圆与圆弧线绘制

context.arc(x,y,r,startAngle, endAngle, anticlockwise)

  • x,y为圆弧中心
  • r为圆弧半径
  • startAngle, endAngle为圆弧起始点和终点角度,弧度表示,角度为css坐标x轴与y轴夹角
  • anticlockwise可选,true为逆时针绘制圆弧,false为顺时针绘制
    const canvasArc=document.getElementById("canvasArc"); const ctxArc = canvasArc.getContext('2d'); ctxArc.lineWidth=5; ctxArc.arc(150,150,100,0,90/180*Math.PI,true); ctxArc.stroke();

圆弧线绘制方法2

context.arcTo(x1, y1, x2, y2, radius)

  • x1, y1为第一个控制点坐标
  • x2, y2为第二个控制点坐标
  • radius 为圆弧半径

将当前路径终点与控制点 1 连接的直线,和控制点 1 与控制点 2 连接的直线,作为使用指定半径的圆的切线,画出两条切线之间的弧线路径

const canvasArcTo=document.getElementById("canvasArcTo"); const ctxArcTo = canvasArcTo.getContext('2d'); ctxArcTo.beginPath(); ctxArcTo.moveTo(100,50) ctxArcTo.strokeStyle="#000"; ctxArcTo.lineWidth=5; ctxArcTo.arcTo(250,50,250,250,150); ctxArcTo.stroke() ctxArcTo.beginPath(); ctxArcTo.moveTo(50,50) ctxArcTo.setLineDash([5,10]); ctxArcTo.lineWidth=5; ctxArcTo.strokeStyle="blue"; ctxArcTo.lineTo(250,50) ctxArcTo.lineTo(250,250) ctxArcTo.stroke()

椭圆

context.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);

  • x,y为椭圆圆心的坐标
  • radiusX 为椭圆长轴的半径,radiusY 为椭圆短轴半径
  • rotation 为椭圆的旋转角度,以弧度表示
  • startAngle, endAngle为椭圆圆弧起始点和终点角度,弧度表示
  • anticlockwise可选,true为逆时针绘制圆弧,false为顺时针绘制
const canvasEllipse=document.getElementById("canvasEllipse"); const ctxEll = canvasEllipse.getContext('2d'); ctxEll.lineWidth=5; ctxEll.ellipse(150,150,100,80,0,0,2*Math.PI) ctxEll.stroke();

贝塞尔曲线

二阶贝塞尔曲线

context.quadraticCurveTo(cpx, cpy, x, y)

  • cpx, cpy为控制点的坐标
  • x, y为曲线终点坐标
  • 起始点坐标为当前路径所在终点,或者可以使用moveTo()控制
const canvasBC=document.getElementById("canvasBezierCurve"); const ctxBC = canvasBC.getContext('2d'); ctxBC.lineWidth=2; ctxBC.moveTo(50,50); let cpx=50; let timerBC=setInterval(()=>{ //想绘制为动画还需再添加beginPath()和clearRect()方法 ctxBC.moveTo(50,50) ctxBC.quadraticCurveTo(cpx+=50,100,50,250) ctxBC.stroke() if(cpx>=350){ clearInterval(timerBC) } },100)
三阶贝塞尔曲线

context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

  • cp1x, cp1y为第一个控制点的坐标
  • cp2x, cp2y为第二个控制点的坐标
  • x, y为曲线终点坐标
  • 起始点坐标为当前路径所在终点,或者可以使用moveTo()控制
const canvasBC2=document.getElementById("canvasBezierCurve2"); const ctxBC2 = canvasBC2.getContext('2d'); ctxBC2.lineWidth=2; ctxBC2.moveTo(50,50); ctxBC2.bezierCurveTo(300, 50, 50, 300,250, 290); ctx2BC.stroke()

文字绘制

绘制文字

context.fillText(text, x, y, [maxWidth]); 对文字进行填充

context.strokeText(text, x, y, [maxWidth]); 对文字进行描边

  • text指定文本内容
  • x, y为文本左下角在画布中开始绘制的坐标(因此坐标不应该为0,0)
  • maxWidth(可选),指定绘制的最大宽度,会对文本进行水平缩放

获取文本宽度

context.measureText(“文本”);

返回文本的TextMetrics 对象,一般会从该对象中获得文本宽度,以判断文字是否需要在Canva中进行换行

<canvas width="300" height="700" id="canvasText"></canvas> <script> const canvasText=document.getElementById("canvasText"); const ctxText = canvasText.getContext('2d'); let string="微风需要竹林,溪流需要蜻蜓,乡愁般的离开,需要片片浮萍,青春属于表白,阳光属于窗台,而我想我属于一个,拥有你的未来"; ctxText.font=" 30px Serif"; let rowNum=1;//行号 let lastIndex=0;//上一行文字的截取索引位置 let rowWidth=0;//每一行的文本长度 for(let i=0;i<string.length;i++){ rowWidth+=ctxText.measureText(string[i]).width; if(rowWidth>canvasText.width){ ctxText.fillText(string.substring(lastIndex,i),0,50*rowNum) lastIndex=i--;//substring()截左不截右 rowNum++; rowWidth=0; } if(i==string.length-1){ ctxText.fillText(string.substring(lastIndex,i+1),0,50*rowNum) }} </script>

文本属性

context.font = “value”;
指定文本属性,默认为 10px sans-serif,value为CSS中的font简写属性,可以按顺序设置[font-style]  [font-variant] [font-weight]  font-size[/line-height]  font-family

  • 必须包含font-size和font-family
  • font-style:字体样式,常用取值normal(正常)、italic(斜体)、oblique(倾斜)
  • font-variant:设置小型大写字母,将字母写为大写,但除首字母外的文本将缩小字号,默认为normal,可以修改为small-caps(小型大写字母)
  • font-weight:设置文本的粗细,常用值:normal(正常),bold(粗体),bolder(再加粗),lighter(细体),以及100-900的整百数值
  • font-size必须,line-height为非必须,有line-height时要写为如:16px/20px 的形式
  • font-family:字体族,5个通用字体:Serif(衬线字体)、Sans-serif(无衬线字体)、Monospace(等宽字体)、Cursive(草书字体)、Fantasy(幻想字体)
const canvasText=document.getElementById("canvasText"); const ctxText = canvasText.getContext('2d'); ctxText.font=" 60px Serif"; ctxText.fillText("hello world",50,60) //对文字进行填充 ctxText.strokeText("hello world",50,180)//对文字进行描边

文本方向

context.direction =”ltr/rtl/inherit”; 设置当前文本方向

  • ltr ,从左往右
  • rtl ,从右往左(部分国家读写习惯)
  • inherit(默认),从父元素继承

context.textAlign = “left/right/center/start/end”; 定义文本水平方向上的对齐方式

  • center以绘制文本时的x坐标为基准,一半位于x左边,一半位于右边
  • start与end属性以direction定义的文本方向为基准

context.textBaseline = “tophanging/middle/alphabetic/ideographic/bottom”; 定义文本垂直方向上的对齐方式

  • 与CSS相同,以文本基线为基准,详见MDN文档

图像与视频绘制

  • drawImage(image, dx, dy)

  • drawImage(image, dx, dy, dWidth, dHeight)

  • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

    • image为图像源,可以是图片、SVG矢量图、视频、canvas等
    • dx, dy为图片左上角在画布中开始绘制的位置
    • dWidth, dHeight为图像在画布上绘制出来的尺寸,会对其进行缩 放、拉抻
    • sx, sy为裁剪时,距离图像左上角的开始裁剪的位置
    • sWidth, sHeight为裁剪的宽度和高度,省略该参数则默认裁剪到>右下角,sHeight为负值将从sy反向裁剪
const canvasImg=document.getElementById("canvasImg"); const ctxImg = canvasImg.getContext('2d'); let img=new Image(); img.src="./dog.jpg"; img.onload=function(){ //仅指定图片在画布中的位置 ctxImg.drawImage(img, 50, 50); //指定图片在画布中的位置及缩放尺寸 ctxImg.drawImage(img, 50, 50,300,200); //裁剪图片,并指定图片在画布中的位置及缩放尺寸 ctxImg.drawImage(img, 50, 50,300,200,0,0,300,300); }

移动、旋转、缩放

context.translate(x, y); 进行水平和垂直位移
修改坐标系的原点,默认原点位于(0,0),由此移动图像在Canvas中的相对位置,可以在上一次translate(x, y)的基础上再次移动坐标系,多次修改坐标原点

context.rotate(弧度值); 进行旋转变换
修改坐标系的旋转角度,参数为弧度值

context.scale(x, y); 进行水平和垂直缩放
对坐标系x轴和y轴进行伸缩,0-1进行缩小,大于1进行放大,负值则进行水平/垂直翻转后进行缩放

const canvas=document.createElement('canvas'); canvas.width=600; canvas.height=600; document.body.append(canvas); const ctx=canvas.getContext('2d'); ctx.translate(50,50); ctx.fillRect(0,0,50,100) ctx.translate(100,100); ctx.scale(1,-2) ctx.rotate(180*Math.PI/180) ctx.fillRect(0,0,50,100)

阴影

类似于CSS的阴影效果,注意,阴影应当设置在图形绘画之前,如果设置阴影效果之前已经有图形和文字存在,则阴影不会对这些图形和文字生效

  • context.shadowOffsetX = value; 阴影的水平偏移距离
  • context.shadowOffsetY = value; 阴影的垂直偏移距离
  • context.shadowBlur = value; 阴影的模糊值
  • context.shadowColor = “颜色”; 阴影颜色
    const ctx=canvas.getContext('2d'); ctx.shadowOffsetX=16; ctx.shadowOffsetY=8; ctx.shadowBlur=5; ctx.shadowColor="#575656"; ctx.moveTo(50,50); ctx.bezierCurveTo(300, 50, 50, 300,250, 290); ctx.stroke()

滤镜

类似于CSS3中的效果
context.filter = “一个或多个值”

  • blur(值px):高斯模糊
  • brightness(百分比):亮度
  • contrast(百分比):对比度
  • grayscale(百分比):灰度滤镜
  • hue-rotate(角度deg):对图像进行色彩旋转的处理
  • invert(百分比):反色(呈现出照片底片的效果)
  • opacity(百分比):不透明度
  • sepia(百分比):褐色处理(怀旧风格)
  • drop-shadow(x, y, 模糊值, 阴影扩张/收缩, 阴影色):阴影效果

图像合成模式

context.globalCompositeOperation = “type”;

  • source-over 图像叠加显示(默认)
  • source-in 只显示图像重叠部分
  • source-out 只显示图像不重叠的部分
  • source-atop 后叠加的图像只显示与原图像重叠的部分
  • destination-over 将后叠加的图像置于原图像之下
  • destination-in 只显示图像重叠部分,并且只显示原图像部分
  • destination-out 将原图像抠去与后图像叠加部分显示,并且后图像不显示
  • destination-atop将后图像重叠部分替换为原图像并显示
  • lighter两图像重叠部分进行颜色相加
  • copy去除原图像,只显示新图像
  • xor重叠部分透明,其他正常显示
  • multiply将重叠部分的顶层像素与底层像素相乘,重叠部分显示为暗黑色
  • screen将重叠部分像素倒转,相乘,再倒转,重叠部分显示为亮色
  • overlaymultiply 和 screen 的结合,原本暗的地方更暗,原本亮的地方更亮
  • darken保留两个图层中最暗的像素
  • lighten保留两个图层中最亮的像素
  • color-dodge将底层除以顶层的反置
  • color-burn将反置的底层除以顶层,然后将结果反过来
  • hard-light类似于叠加,上下图层互换
  • soft-light用顶层减去底层或者相反来得到一个正值
  • difference一个柔和版本的强光(hard-light),纯黑或纯白不会导致纯黑或纯白
  • exclusion和 difference 相似,但对比度较低
  • hue保留底层的亮度和色度,同时采用顶层的色调
  • saturation保留底层的亮度和色调,同时采用顶层的色度
  • color保留了底层的亮度,同时采用了顶层的色调和色度
  • luminosity保持底层的色调和色度,同时采用顶层的亮度

状态保存与恢复

  • context.save() 将当前状态推入栈中
  • context.restore() 读取栈顶的状态

将保存当前的裁剪区域、虚线列表、以及各属性值压入栈中,之后可以直接依次读取栈顶存储的状态并直接绘制,会保存的属性值包括:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled

const canvas=document.getElementById("canvas"); const ctx=canvas.getContext('2d'); ctx.fillStyle="red"; ctx.save(); //依次压栈 ctx.fillStyle="yellow"; ctx.save(); //依次压栈 ctx.fillStyle="green"; ctx.restore(); //弹栈 ctx.fillRect(50,50,100,100);//将绘制为黄色而非绿色

像素操作-ImageData对象

ImageData对象保存了Canvas图像的底层实际像素,可以直接进行读取和写入。其中,像素被保存在Uint8ClampedArray类型的一维数组中,每个数组元素为0-255之间的数据,每4个数组元素为一组代表了一个像素点的RGBA值。如:索引为0-3的数组元素,存储了第一个像素点的红、绿、蓝、不透明度对应的0-255十进制数值,以此类推

context.getImageData(x,y,width,height)
返回ImageData对象,获取(x,y)坐标开始,width为宽,height为高的矩形区域中的像素。返回的对象中包含width、height、data数组(Uint8ClampedArray类型)三个属性

context.putImageData(imagedata对象, dx偏移, dy偏移)

getImageData()方法可以从(x,y)坐标开始获取一个矩形区域内的像素数据,将该部分数据修改后,可以通过putImageData()方法将数据绘制到canvas中。其中,dx,dy为在(x,y)的基础上进行的偏移量,之前获取到的(x,y)到(x+width,y+height)矩形区域内的图形,修改像素数据后将被绘制到(x+dx,y+dy)开始的同大小矩形区域内

context.putImageData(imagedata对象,dx偏移, dy偏移, dirtyX, dirtyY,dirtyWidth, dirtyHeight)

  • dx偏移, dy偏移作用同上
  • (dirtyX, dirtyY)为进行修改像素操作开始的位置坐标
  • dirtyWidth, dirtyHeight为将进行修改像素操作的矩形区域的长宽
<canvas width="300" height="300" id="canvas"></canvas> <script> const canvas=document.getElementById("canvas"); const ctx = canvas.getContext('2d'); let img=new Image(); img.src="./dog.jpg"; img.onload=function(){ ctx.drawImage(img,0,0,300,300); let imageDate=ctx.getImageData(0, 0, 100, 100); //每4个索引为一组,代表一个像素的RGBA值 for(let i=0;i<imageDate.data.length;i+=4){ //计算每个像素点的灰度值 let avg=(imageDate.data[i]+imageDate.data[i+1]+imageDate.data[i+2])/3; imageDate.data[i]=avg; //修改像素点的R imageDate.data[i+1]=avg;//修改像素点的G imageDate.data[i+2]=avg;//修改像素点的B imageDate.data[i+3]=255;//修改像素点不透明度A为1 } // 将(10,10)开始,长200,宽200的区域内的像素调为灰色 ctxImg.putImageData(imageDate,0,0,10,10,200,200) } </script>

案例

使用图像合成模式的“destination-out”属性值制作刮刮卡

再刮一次
再刮一次
<div class="card"></div>//底层卡片,显示是否中奖
<canvas width="300" height="150" id="canvas1"></canvas>//刮刮乐灰色图层,监听刮开区域
<div class="tryAgain" onclick="tryAgain()">再刮一次</div>//重新开始按钮</div>
<script>
       const canvas=document.getElementById("canvas1");
       const context=canvas.getContext('2d');
       const p=context.createPattern(cover(),"repeat");//创建模板对象,设置图像重复模式
        context.fillStyle=p;
        function tryAgain(){
            document.querySelector(".card").innerText=Math.random()>.8?"恭喜中奖":"再刮一次";
            context.clearRect(0,0,300,150);
            context.globalCompositeOperation="copy";
            context.fillRect(0,0,300,150);
        }
        tryAgain();
        /*
        *制作刮刮乐封面,返回canvas DOM
        */
        function cover(){
            const canvasCover=document.createElement('canvas');
            const coverCtx=canvasCover.getContext('2d');
            canvasCover.width=60;
            canvasCover.height=50;
            coverCtx.fillStyle="#6f6d6d"
            coverCtx.fillRect(0,0,60,50)
            coverCtx.rotate(45*Math.PI/180);
            coverCtx.font="300 15px Serif";
            coverCtx.fillStyle="#333333"
            coverCtx.fillText("发大财",20,0)
            return canvasCover;
        }
        let allowedDraw=false;
        canvas.addEventListener('mousedown',function(){
            allowedDraw=true;
        }) 
        canvas.addEventListener('mouseup',function(){
            allowedDraw=false;
        })
        canvas.addEventListener('mousemove',function(event){
            var event = event || window.event;
            if(allowedDraw){
             if (event.offsetX || event.offsetY) {  //非Mozilla浏览器
              var  x = event.offsetX;
              var y = event.offsetY;
            } else if (event.layerX || event.layerY) {  //兼容Mozilla浏览器
              var x = event.layerX;
              var y = event.layerY;
            }
            context.globalCompositeOperation="destination-out";
            context.beginPath();
            context.arc(x,y,20,0,2*Math.PI);
            context.fill();
            }})
            //移动端
            canvas.addEventListener('touchstart',function(){
            let canvasRect=canvas.getBoundingClientRect();
            canvas.addEventListener('touchmove',function(e){
                e.preventDefault();
            var x=e.targetTouches[0].pageX-canvasRect.left;
            var y=e.targetTouches[0].pageY-canvasRect.top;
            context.globalCompositeOperation="destination-out";
            context.beginPath();
            context.arc(x,y,20,0,2*Math.PI);
            context.fill();
            },{passive:false})    
        },{passive:false})
    </script>
上一篇:SVG矢量图
下一篇:Web的发展旅程
z z z z z