1.使用requestAnimationFrame
使用requestAnimationFrame用来替代计时器与定时执行.
1.1 早期定时动画
使用setImterval()来控制动画的执行.
function updateAnimations(){
doAnimation1();
doAnimation2();
//其他任务
}
setInterval(updateAnimations,100);
1.2 requestAnimationFrame
requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,如果系统绘制率是 60Hz,那么回调函数就会16.7ms再 被执行一次,如果绘制频率是75Hz,那么这个间隔时间就变成了 1000/75=13.3ms。换句话说就是,requestAnimationFrame的执行步伐跟着系统的绘制频率走。它能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
requestAniamtionFrame():
参数:在重绘屏幕前调用的函数.
<div id="aa">弹幕</div>
wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
<br>
wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
<div id="bb">弹幕</div>
//requestAnimationFrame绘制动画
let div=document.getElementById("aa");
div.style.backgroundColor = "transparent";
div.style.border="1px solid red"
div.style.position = "absolute";
div.style.left='0%';
div.style.top='20px'
function cr(){
div.style.left=parseFloat(div.style.left,10)+0.1+'%';
if(parseInt(div.style.left,10)<100){
requestAnimationFrame(cr)
}
}
requestAnimationFrame(cr);
//setInterval绘制动画
let dib=document.getElementById("bb");
dib.style.position='absolute'
dib.style.left='0%';
function br(){
dib.style.left=parseFloat(dib.style.left,10)+0.1+'%';
if(parseInt(dib.style.left,10)>100){
clearInterval(a)
}
}
let a=setInterval(function(){br()},15)
跟setTimeout和setInterva的对比
setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔,参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行
requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果
虽然requestAnimationFrame和setInterval看起来很相似,但它们之间有一些重要的区别:
- requestAnimationFrame会在浏览器的下一次重绘之前执行回调函数,而setInterval会按照指定的时间间隔重复执行回调函数。
- requestAnimationFrame会自动考虑浏览器的重绘,避免不必要的重绘,提供更流畅的动画效果。而setInterval则不会考虑浏览器的重绘,可能会导致不必要的重绘,影响性能。
- requestAnimationFrame会在浏览器的后台标签页中暂停,避免不必要的计算资源占用。而setInterval则会一直执行,可能会导致浏览器卡顿或者耗尽电池。
1.3 cancelAnimationFrame
requestAnimationFrame()返回一个请求ID,可以通过cancelAnimationFrame()来取消重绘任务.
let b=requestAnimationFrame(cr);
cancelAnimationFrame(b)
1.4通过requestAnimationFrame节流
function scrol(){
console.log('1')
}
let a=0;
addEventListener('scroll', function(){
if(!a){
a=1;
requestAnimationFrame(scrol);
setTimeout(() => {
a=0
}, 1000);
}
})
2.基本的画布功能
创建<canvas>元素时,至少要设置其width与height属性.
<canvas id='canvas' height="100px" width="100px"></canvas>
<img src="" alt=""/>
let canvas= document.getElementById('canvas');
//确保浏览器支持<canvas>
if(canvas.getContext){
// 获取绘图上下文getContext
let context=drawing.getContext('2d')
}
//也可以导出画布图像toDataURL
//取得图像数据URL
let iageURL=canvas.toDataURL('image/png')
//显示图片
let imge=document.getElementsByTagName('img')[0]
imge.src=iageURL;
3. 2D上下文 convas
3.1 填充和描边
填充(fillStyle)指定样式自动填充形状,而描边(strokeStyle)只为图片边界着色.
值为:字符串表示颜色值.
let canvas= document.getElementById('canvas');
//确保浏览器支持<canvas>
if(canvas.getContext){
// 获取绘图上下文getContext
let context=drawing.getContext('2d')
}
context.fillStyle='red'
context.strokeStyle='yellow'
3.2 绘制矩形
矩形是唯一一个可以在2D绘图上下文中绘制的形状.
fillRect() 在画布上绘制并填充矩形
strokeRect() 在画布上绘制并描边矩形
clearRect() 擦除画布中的某个区域
都有4个参数为:起点x坐标,起点y坐标,矩形宽度,矩形高度
let canvas= document.getElementById('canvas');
//确保浏览器支持<canvas>
if(canvas.getContext){
// 获取绘图上下文getContext
let context=drawing.getContext('2d')
}
context.fillStyle='red'
context.fillRect(0,0,100,100)
context.strokeStyle='yellow'
context.StrokeRect(50,50,150,150)
context.clearRect(75,75,125,125)
3.3绘制路径
要绘制路径,必须调用beginPath()方法以表示要开始绘制新路径.
- arc(x,y,半径,起始角度,结束角度,旋转方向): 用于绘制圆形路径,以(x,y)为圆心,旋转方向为一个bool值表示是否以逆时针方向绘制,起始角度以及结束角度的单位都是弧度
- arcTo(x1,y1,x2,y2,半径): 从(x1,x2)为起点绘制一条到(x2,y2)的弧线,且该圆弧的半径为指定值
- bezierCurveTo(c1x,c1y,c2x,c2y,x,y): 从上一个点为起点绘制一条到(x,y)点的曲线,并且以(c1x,c1y)和(c2x,c2y)两点为控制点(即绘制贝塞尔曲线)
- lineTo(x,y): 从上一点绘制一条到(x,y)点的直线
- moveTo(x,y): 起点移到(x,y)点,不画线
- quadraticCurveTo(cx,cy,x,y): 从上一点绘制一条到(x,y)点的二次曲线,以(cx,cy)为控制点
- rect(x,y,width,height): 从点(x,y)开始绘制一个矩形,宽度为 width 高度为 height,这里绘制的是矩形路径,而不是一个独立的形状
当路径绘制完成后有几种不同的操作:
- closePath(): 如果希望绘制一条连接到路径起点的线条则可以调用该方法
- fill(): 用fillStyle来填充图形
- stroke(): 对路径描边
- clip(): 在路径上创建一个剪切区域
let canvas=document.getElementById("canvas");
if (canvas.getContext('2d')){
let context=canvas.getContext('2d');
context.beginPath();
//将光标移动到(100,100)
context.moveTo(100,100)
//绘制一条到(200,200)的直线
context.lineTo(200, 200);
//再到(300,300)
context.lineTo(300,100);
//以(300,100)为圆心画半圆
context.arc(300, 100, 100, Math.PI / 180 * 0, Math.PI / 180 * 200, false);
//首尾连接
context.closePath();
//绘制一个正方形
context.rect(200, 200, 100, 100);
context.fillStyle='black';
context.fill()
context.strokeStyle='red';
context.stroke();
}
isPointInpath() 接受两个参数即x,y坐标,用于判断(x,y)是否位于路径上,在路径关闭前有效
3.4绘制文本
canvas也为绘制文本提供了相应的方法.
2D上下文提供的文本绘制方法主要有两个:
- fillText()
- strokeText()
这两个方法都接受四个参数
- 要绘制的文本字符串
- 绘制文本的X坐标
- 绘制文本的Y坐标
- 最大像素宽度
这些方法都以以下属性为基础:
- font: 表示文本样式,大小,字体, 该属性以 CSS 中指定字体的格式来指定
- textAlign: 表示文本对齐方式,可能的值有 "start" "end" "left" "right" "center"
- textBaseline:表示文本基线
上述属性都有默认值,所以没有必要每次使用都为上述属性重新赋值
一般来说使用 fillText 的情况比较多,因为该方法显示的文字和网页中正常显示文字的区别不大,而 strokeText 方法则会对文本进行描边
context.fillStyle='black';
context.font='20px sans-serif';
context.textAlign='center';
context.textBaseline='middle';
context.fillText('文本',300,50)
由于绘制文本比较复杂,尤其是当我们需要将文本控制在某一区域中的时候
2D上下文为此提供了一个辅助确定文本大小的方法 measureText()
该方法接收一个参数,即要绘制的文本
返回一个 TextMetrics 对象,返回对象目前只有一个 width 属性,通过该属性可以获得传入的文本显示在页面中的大小
如果我们希望绘制的文本宽度小于某个值则可以使用该方法来判断文本大小
let fontSize = 100;
context.font = fontSize + "px Arial";
while(context.measureText("Hello World!").width > 140){
fontSize--;
context.font = fontSize + "px Arial";
}
context.fillText("Hello World",300,200);
3.5变换
canvas 上下文为我们提供了以下方法来修改变换矩阵:
- ratate(angle): 围绕原点将绘制的图像旋转 angle 弧度
- scale(scaleX, scaleY): 缩放图像,在x方向乘以 scaleX , y方向乘以 scaleY, 默认值为1.0
- translate(x ,y ): 将坐标原点放到 (x,y),执行该方法后,坐标(0,0)会变为(x,y)
- transform(m1_1, m1_2, m2_1, m2_2, dx, dy): 直接修改变换矩阵,方式是在原有变换矩阵的基础上乘以如下矩阵
- m1_1 m1_2 dx
- m2_1 m2_2 dy
- 0 0 1
- 以上矩阵的参数代表的含义如下:
m1_1 水平缩放
m2_1 水平倾斜
m1_2 垂直倾斜
m2_2 垂直缩放
dx 水平偏移
dy 垂直偏移
- setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy): 先将变换矩阵设置为默认值,在对其调用 transform 方法
var drawing = document.getElementById("drawing");
// 确定浏览器对canvas的支持
if(drawing.getContext){
var context = drawing.getContext("2d");
// 开始路径
context.beginPath();
// 绘制外圆
context.arc(100,100,99,0,2*Math.PI,false);
// 绘制内圆
context.moveTo(194,100);
context.arc(100,100,94,0,2*Math.PI,false);
// 变换原点
context.translate(100,100);
// 绘制分针
context.moveTo(0,0);
context.lineTo(0,-85);
// 绘制时针
context.moveTo(0,0);
context.lineTo(-65,0);
// 描边路径
context.stroke();
}
此外,canvas 中没有什么方法将其重置回初始值,但是提供了以下方法,以完成类似功能
- save() : 保存当前上下文的信息,并将其存入一个堆栈结构
- restore() : 将当前上下文恢复为上一个 save 保存的状态
需要注意的是save只会保存上下文中的设置与变换,并不会保存绘制的内容
3.6绘制图像
2D绘图上下文内置了对图像的支持
如果希望将一幅图绘制到画布上,可以使用 drawImage() 的方法
该方法有三种不同的参数数组合以对应不同的应用场景
- 将<img>绘制到画布的(x,y)点 context.drawImage(img, x, y)
- 以宽 width 高 height 将<img>绘制到(x, y)点 context.drawImage(img, x, y, width, height)
- 绘制目标图像的一部分到画布 context.drawImage(img, 源图像的x, 源图像的y, 源图像的宽, 源图像的高, 目标图像的x, 目标图像的y, 目标图像的宽, 目标图像的高)
//绘制图像
//获取图像信息
let iageURL=canvas.toDataURL('image/png')
let imge=document.getElementsByTagName('img')[0]
//传入img
imge.src=iageURL;
context.restore();
//绘制到画布上
context.drawImage(imge, 300,10,1000 ,500);
context.drawImage(imge, 0,0,500 ,500,100,100,600,600);
注意:等图片加载成功后才可以绘制,当图片未加载成功时使用drawImage(),不会被调用,绘制会失败。
1.当图像使用img标签时:使用window.onload监听图像是否加载成功,加载成功后才调用drawImage()
window.onload=function(){
var image=document.getElementById("image");
var drawing=document.getElementById("drawing");
var context=drawing.getContext("2d");
context.drawImage(image,0,0);
}
2.当使用new Image()创建图像对象时,监听图像对象本身是否加载成功。
var drawing=document.getElementById("drawing");var context=drawing.getContext("2d");var image=new Image();image.src="mc.png";image.onload=function(){ context.drawImage(image,0,0);}var drawing=document.getElementById("drawing");
var context=drawing.getContext("2d");
var image=new Image();
image.src="mc.png";
image.onload=function(){
context.drawImage(image,0,0);
}
3.7 阴影
2D上下文将会根据以下属性为形状或路径绘制阴影
- shadowColor: 用于设置阴影颜色,默认为黑色
- shadowOffsetX: 形状或路径X方向的阴影偏移量,默认为0
- shadowOffsetY: 形状或路径y轴方向的阴影偏移量,默认为0
- shadowBlur: 模糊的像素数,默认为0, 即不模糊
这些属性都可以通过 context 对象来修改,只要在绘制之前为其设置值,就能自动产生阴影
let canvas=document.getElementById("canvas");
let img=document.getElementById("img");
let context=canvas.getContext('2d');
context.beginPath();
context.shadowColor='blue';
context.shadowOffsetX=5;
context.shadowOffsetY=5;
context.shadowBlur=4;
context.moveTo(100,100)
context.lineTo(200, 100);
context.strokeStyle='black';
context.stroke()
3.8渐变
渐变由 canvasGradient 实例表示
要创建一个渐变对象需要调用 createLinearGradient() 方法
该方法接收四个参数:
- 起点的x坐标
- 起点的y坐标
- 终点的x坐标
- 终点的y坐标
调用该方法后会创建一个指定大小的实例,并返回一个 CanvasGradient 实例
创建渐变对象后需要指定色标,通过 addColorStop()
该方法接收两个参数:
- 色标位置
- CSS颜色值
//绘制一个渐变区域
let linear=context.createLinearGradient(0, 0, 50, 50);
//添加渐变颜色
linear.addColorStop(0, 'blue');
linear.addColorStop(1,'red')
context.fillStyle=linear;
context.fillRect(0,0,100,100);
而如果需要使用径向渐变,则需要使用 createRadialGradient() 方法
该方法接收6个参数:
- 起点圆的圆心x坐标
- 起点圆的圆心y坐标
- 起点圆的圆半径
- 终点圆的圆心x坐标
- 终点圆的圆心y坐标
- 终点圆的圆半径
let radia=context.createRadialGradient(250, 50, 30, 250, 50,60);
radia.addColorStop(0,'blue')
radia.addColorStop(1, 'red');
context.strokeStyle=radia;
context.strokeRect(200, 0,100,100);
context.fillStyle=radia;
context.fillRect(200,0,100,100);
3.9图案
模式其实就是重复的图像,用来填充或描边图形
要创建一个新模式,可以调用 createPattern()并传入两个参数
- 一个HTML img元素
- 用于表示如何重复的字符串
- "repeat" 重复
- "repeat-x" 水平方向重复
- "repeat-y" 垂直方向重复
- "no-repeat" 不重复
需要注意的是,模式与渐变一样,都从画布的(0,0)开始
将fillStyle设置为模式对象,只表示在特定区域内显示重复的图像,而不是从某个区域开始绘制重复的图像
createPattern() 方法的第一个参数也可以是 <video> 或者另外一个 <canvas>元素
let canvas = document.getElementById("canvas");
let context=canvas.getContext('2d')
context.beginPath();
context.arc(50, 50, 40, Math.PI / 180 * 0, Math.PI / 180 * 360, false);
context.shadowColor='black';
context.shadowOffsetX=0;
context.shadowOffsetY=0;
context.shadowBlur=2;
context.stroke();
let imgurl=canvas.toDataURL('image/png')
let canvas1 = document.getElementById("canvas1");
let context1=canvas1.getContext('2d')
context1.fillStyle='red';
context1.fillRect(0,0,100,100);
let img=document.images[0]
img.src=imgurl;
context1.drawImage(img, 100, 100,100,100);
let pattern= context.createPattern(img, 'repeat');
context1.fillStyle=pattern;
context1.fillRect(200,200,200,200);
3.10 图像数据
2D上下文一个优势在于,可以通过 getImageData 方法取得原始的图像数据
该方法接收四个参数:
- 画面区域的x坐标
- 画面区域的y坐标
- 画面宽度
- 画面高度
该方法会返回一个 ImageData 实例
每个ImageData有以下三个属性:
- width
- height
- data
data属性是一个数组,保存着每一个像素的数据
每一个像素用四个值来保存,分别表示红/绿/蓝/透明度,这些值每一个都介于 0~255 之间
window.onload=function(){
let drawing = document.getElementById("drawing");
if(drawing.getContext){
let ctx = drawing.getContext("2d"),
image = document.images[0],
imageData,data,len,average,red,green,blue,alpha;
// 绘制图像数据
ctx.drawImage(image, 0, 0);
// 取得图像数据
imageData = ctx.getImageData(0,0,image.width,image.height);
data = imageData.data;
for(let i=0,len = data.length; i<len;i+=4){
red = data[i];
green = data[i+1];
blue = data[i+2];
alpha = data[i+3];
// 求rgb平均值
average = Math.floor((red+green+blue)/3);
// 设置颜色值
data[i] = average;
data[i+1] = average;
data[i+2] = average;
}
// 回写图像数据
imageData.data = data;
ctx.putImageData(imageData,0,0);
}
}
putImageData()图像数据(从指定的 ImageData 对象)放回画布上。
context.putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight);
参数 | 描述 |
imgData | 规定要放回画布的 ImageData 对象。 |
x | ImageData 对象左上角的 x 坐标,以像素计。 |
y | ImageData 对象左上角的 y 坐标,以像素计。 |
dirtyX | 可选。水平值(x),以像素计,在画布上放置图像的位置。 |
dirtyY | 可选。水平值(y),以像素计,在画布上放置图像的位置。 |
dirtyWidth | 可选。在画布上绘制图像所使用的宽度。 |
dirtyHeight | 可选。在画布上绘制图像所使用的高度。 |
3.11 合成
- globalAlpha 用于指定所有绘制的透明度
- globalComposition 用于表示后绘制的图形怎样与先绘制的图形进行结合
- 该属性的值是字符串,可能的值如下
- source-over(默认) 后绘制的图形位于先绘制的图形的上方
- source-in 后绘制的图形与先绘制的图形重叠部分可见,其它部分透明
- source-out 后绘制的图形与先绘制的图形不重叠部分可见,先绘制的图形完全透明
- source-atop 后绘制的图形与先绘制的图形重叠部分可见,先绘制的图形不受影响
- destination-over 后绘制的图形位于先绘制图形的下方,只有之前透明的部分可见
- destination-in 后绘制的图形位于先绘制图形的下方,两者不重叠部分完全透明
- destination-out 后绘制的图形擦除与先绘制图形的重叠部分
- destination-atop 后绘制的图形位于先绘制图形的下方,在两者不重叠的地方,先绘制的图形会透明
- lighter 后绘制的图形与先绘制的图形重叠部分的值相加,使该部分的值变亮
- copy 后绘制的图形完全替代与之重叠的先绘制图形
- xor 后绘制图形与先绘制图形的重叠部分执行异或操作
- 该属性的值是字符串,可能的值如下
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Canvas合成</title>
<meta name="Description" content=""/>
<meta name="Author" content="lhy"/>
<style>
body{
background-color: #75727c;
}
</style>
</head>
<body>
<canvas id="canvas" width="800px" height="400px" style="margin: 200px 200px"></canvas>
<script>
let canvas = document.getElementById('canvas');
let context = canvas.getContext('2d');
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10,10,50,50);
// 绘制半透明蓝色矩形
context.fillStyle = 'rgba(0,0,255,0.5)';
context.fillRect (30,30,50,50);
</script>
</body>
</html>