01.canvas之学习之路
初识canvas
<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 | 使用异或操作对源图像与目标图像进行组合。 |
具体每个值对应的描述,可以点击这里查阅。
具体效果可以看下面的实现效果:
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)
}
状态的保存和恢复
save()
- 保存画布(canvas)的所有状态
restore()
- 恢复至前一次的状态
save和restore方法是用来保存和恢复canvas状态的,都没有参数,canvas的状态就是当前画面应用的所有样式和变形的一次快照
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()