一、前言
动画这方面不太熟悉,自己用的也很少,只能在用到和看到相关书籍学习的时候将经验写下来了...
二、requestAnimationFrame
1.早期定时动画
说明: 早期动画使用setInterval
来控制动画的运行,不过存在两个问题,一是
定时的间隔必须足够短,这样才能让不同的动画类型运行流畅,但是又需要足够长,以便浏览器可以将变化渲染出来,大部分屏幕的刷新率都是60hz,意味着每秒重绘60次,那么最佳的时间间隔就是17ms了,二是
定时器的第二个参数表示隔多久将代码添加到任务队列中去,并不能保证动画立即执行,因此也就不能保证动画时间的精度
js
(function() {
function updateAnimations() {
doAnimation1();
doAnimation2();
// 其他任务
}
setInterval(updateAnimations, 100);
})();
浏览器计时器的精度:
IE8及更早:
15.625毫秒IE9及更晚:
4毫秒Firefox 和 Safari:
约为10毫秒Chrome:
4毫秒
2.requestAnimationFrame
说明: 参数是一个回调函数,用于告诉浏览器在下次重绘之前调用指定的回调函数更新动画,不过它只会调用一次
传入的函数,如果需要动画循环下去,可以把多个 requestAnimationFrame()调用串联起来
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Progress Bar Animation</title>
<style>
#status {
width: 10%;
height: 20px;
background-color: #4caf50;
}
</style>
</head>
<body>
<div id="status"></div>
<script>
function updateProgress(e) {
// 回调函数接受一个参数,这个参数表示执行回调函数的时刻
console.log(e);
var div = document.getElementById("status");
div.style.width = div.offsetWidth + 5 + "px";
if (div.offsetWidth < window.innerWidth) {
requestAnimationFrame(updateProgress);
}
}
requestAnimationFrame(updateProgress);
</script>
</body>
</html>
3.cancelAnimationFrame
说明: 上面那个方法的返回值是一个整数ID,可以通过这个ID像定时器那样取消
已经注册的动画,取消时使用的是最后一个ID
js
let requestID = window.requestAnimationFrame(() => {
// 不会执行
console.log('Repaint!');
});
window.cancelAnimationFrame(requestID);
三、canvas
说明: 创建<canvas>
元素时至少要设置其width
和height
属性,这样才能告诉浏览器在多大面积上绘图。出现在开始和结束标签之间的内容是后备数据,会在浏览器不支持<canvas>
元素时显示,对于样式来说,可以像其它元素那样,通过css添加,也可以通过JavaScript来添加,在画布上面绘制图形,需要获取绘图上下文
,可以通过getContext()
来获取,对于平面图形,参数传"2d"
,在图形绘制完毕后,可以通过toDataURL()
方法将画布上面的图形导出来
html
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
<script>
let drawing = document.getElementById("drawing");
// 检测元素是否存在getContext,确保浏览器支持<canvas>
if (drawing.getContext) {
// 取得图像的数据 URI
let imgURI = drawing.toDataURL("image/png");
// 显示图片
let image = document.createElement("img");
image.src = imgURI;
document.body.appendChild(image);
}
</script>
四、context
说明: context用来表示绘图上下文,这里说的是2d绘图上下文
,它提供了绘制 2D 图形的方法,包括矩形、弧形和路径。2D 上下文的坐标原点(0, 0)在 元素的左上角。所有坐标值都相对于该点计算,因此 x 坐标向右增长,y 坐标向下增长。默认情况下,width 和 height 表示两个方向上像素的最大值。
1.填充和描边
说明: 填充(fillStyle)
以指定样式自动填充形状,而描边(strokeStyle)
只为图形边界着色,这两个属性可以是字符串、渐变对象或图案对象,默认值为"#000000"。字符串表示颜色值,可以是CSS支持的任意格式:名称、十六进制代码、rgb、rgba、hsl 或 hsla
相关的方法:
fill():
会根据当前的样式进行填充stroke():
会根据当前的线条进行描边
js
let drawing = document.getElementById("drawing");
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 设置描边
context.strokeStyle = "red";
// 设置填充
context.fillStyle = "#0000ff";
}
2.绘制矩形
说明: 矩形是可以在直接在绘图上下文中绘制的图形,与其相关的方法有三个:fillRect()
、strokeRect()
和clearRect()
,它们的参数都是:左上顶点的x坐标
、左上顶点的y坐标
、矩形的宽度
、矩形的高度
,单位是像素
,需要注意,如果需要填充颜色需要先填充再绘制
,否则填充无法生效
fillRect():
以指定颜色在画布上绘制并填充矩形
strokeRect():
以指定颜色在画布上绘制矩形的轮廓
clearRect():
在画布上擦出一个矩形,先绘制后擦除
常用属性:
lineWidth:
整数,表示描边的宽度
lineCap:
线条端点的形状,取值如下
- "butt:平头
- "round":圆头
- "square":方头
lineJoin:
线条交点的形状,取值如下
- "round:圆的
- "bevel":平的
- "miter":尖的
html
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
<script>
let drawing = document.getElementById("drawing");
if (drawing.getContext) {
let context = drawing.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>
html
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
<script>
let drawing = document.getElementById("drawing");
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 绘制红色轮廓的矩形
context.strokeStyle = "#ff0000";
context.strokeRect(10, 10, 50, 50);
// 绘制半透明蓝色轮廓的矩形
context.strokeStyle = "rgba(0,0,255,0.5)";
context.strokeRect(30, 30, 50, 50);
}
</script>
html
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
<script>
let drawing = document.getElementById("drawing");
if (drawing.getContext) {
let context = drawing.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);
// 在前两个矩形重叠的区域擦除一个矩形区域
context.clearRect(40, 40, 10, 10);
}
</script>
3.绘制路径
说明: 通过路径可以创建复杂的形状和线条。要绘制路径,必须首先调用 beginPath()
方法以表示要开始绘制新路径,然后可以使用下面这些方法来进行绘制,绘制完成,使用closePath()
方法结束绘制,对于判断一个点是否被一段路径所使用,可以使用isPointInPath(x,y)
方法来判定,它的返回值是布尔值
arc(x, y, radius, startAngle, endAngle, counterclockwise):
以坐标(x, y)为圆 心,以 radius 为半径绘制一条弧线,起始角度为 startAngle,结束角度为 endAngle(都是 弧度)。最后一个参数 counterclockwise 表示是否逆时针计算起始角度和结束角度(默认为 顺时针)。arcTo(x1, y1, x2, y2, radius):
以给定半径 radius,经由(x1, y1)绘制一条从上一点 到(x2, y2)的弧线。lineTo(x, y):
绘制一条从上一点到(x, y)的直线。moveTo(x, y):
不绘制线条,只把绘制光标移动到(x, y),表示当前绘制的路径是一条新路径,跟之前的路径没有关系quadraticCurveTo(cx, cy, x, y):
以(cx, cy)为控制点,绘制一条从上一点到(x, y) 的弧线(二次贝塞尔曲线)。bezierCurveTo(c1x, c1y, c2x, c2y, x, y):
以(c1x, c1y)和(c2x, c2y)为控制点, 绘制一条从上一点到(x, y)的弧线(三次贝塞尔曲线)。rect(x, y, width, height):
以给定宽度和高度在坐标点(x, y)绘制一个矩形。这个方法 与 strokeRect()和 fillRect()的区别在于,它创建的是一条路径,而不是独立的图形。
html
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
<script>
let drawing = document.getElementById("drawing");
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 获取当前时间
let now = new Date();
let hour = now.getHours();
let minute = now.getMinutes();
let second = now.getSeconds();
// 清除画布
context.clearRect(0, 0, drawing.width, drawing.height);
// 创建路径
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.moveTo(100, 100);
context.lineTo(
100 +
60 * Math.cos((Math.PI / 30) * (minute + second / 60) - Math.PI / 2),
100 + 60 * Math.sin((Math.PI / 30) * (minute + second / 60) - Math.PI / 2)
);
// 绘制时针
context.moveTo(100, 100);
context.lineTo(
100 +
50 *
Math.cos(
(Math.PI / 6) * ((hour % 12) + minute / 60 + second / 3600) -
Math.PI / 2
),
100 +
50 *
Math.sin(
(Math.PI / 6) * ((hour % 12) + minute / 60 + second / 3600) -
Math.PI / 2
)
);
// 描画路径
context.stroke();
}
</script>
4.绘制文本
说明: 绘图上下文提供了两个方法:即fillText()
和strokeText()
。这两个方法都接收4个参数:要绘制的字符串
、x坐标
、y坐标
和最大像素宽度(可选)
,前者绘制的文本是填充
的,后者是描边
的,绘制的结果取决于下面三个属性的设定,先设定后绘制
,其次对于文本大小的确定,可以使用measureText("需要测量的文本")
方法,如果使用了第四个可选参数限制文本的宽度,绘制的字符串超出了最大宽度限制,则文本会以正确的字符高度绘制,这时字符会被水平压缩
,以达到限定宽度
font:
以 CSS 语法指定的字体样式textAlign:
文本的对齐方式的属性textBaseLine:
指定文本的基线
js
// 在前面的时钟绘制前加上下面这些就可以绘制时刻文本了
// 正常
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);
// 与开头对齐
context.textAlign = "start";
context.fillText("12", 100, 40);
// 与末尾对齐
context.textAlign = "end";
context.fillText("12", 100, 60);
5.变换
说明: 2D绘图上下文支持所有常见的绘制变换。在创建绘制上下文时,会以默认值初始化变换矩阵,从而让绘制操作如实应用到绘制结果上,可以使用下面这些方法:
rotate(angle):
围绕原点把图像旋转 angle 弧度(约为57.3度),先旋转再绘制,不然会失效scale(scaleX, scaleY):
通过在 x 轴乘以 scaleX、在 y 轴乘以 scaleY 来缩放图像。scaleX 和 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 1setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):
把矩阵重置为默认值,再以传入的参数调用 transform()。save():
将当前绘图上下文的设置(如颜色、线条样式等)和变换(如平移、旋转等)保存在一个栈中。这些设置和变换的保存并不包括已经绘制的图形内容,只是记录了当前的状态,状态可以保存多次的时,恢复状态会就近恢复restore():
恢复上一次保存的状态
html
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
<script>
let drawing = document.getElementById("drawing");
if (drawing.getContext) {
let 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);
// 移动原点到表盘中心,此时(100,100)这个点就是原点(0,0)了
context.translate(100, 100);
// 旋转表针,上面已经改变了原点的坐标,所以此时旋转是围绕钟的中心点旋转的
context.rotate(1);
// 绘制分针
context.moveTo(0, 0);
context.lineTo(0, -85);
// 绘制时针
context.moveTo(0, 0);
context.lineTo(-65, 0);
// 描画路径
context.stroke();
}
</script>
html
<canvas id="drawing" width="1000" height="1000">A drawing of something.</canvas>
<script>
let drawing = document.getElementById("drawing");
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 设置绘图上下文的填充颜色为红色。
context.fillStyle = "#ff0000";
// 将当前绘图状态保存到绘图状态栈中。
context.save();
// 设置绘图上下文的填充颜色为绿色。
context.fillStyle = "#00ff00";
// 平移绘图上下文的原点位置到 (100, 100)。
context.translate(100, 100);
// 再次将当前绘图状态保存到绘图状态栈中。
context.save();
// 设置绘图上下文的填充颜色为蓝色。
context.fillStyle = "#0000ff";
// 在当前绘图上下文的原点位置 (100, 100) 绘制宽度为 100、高度为 200 的蓝色矩形。
context.fillRect(0, 0, 100, 200);
// 从绘图状态栈中取出上一个保存的绘图状态,恢复为之前的状态。此时填充颜色为绿色。
context.restore();
// 在当前绘图上下文的原点位置 (100, 100) 绘制宽度为 100、高度为 200 的绿色矩形。
context.fillRect(10, 10, 100, 200);
// 再次从绘图状态栈中取出上一个保存的绘图状态,恢复为最初的状态。此时填充颜色为红色。
context.restore();
// 在当前绘图上下文的原点位置 (0, 0) 绘制宽度为 100、高度为 200 的红色矩形。
context.fillRect(0, 0, 100, 200);
}
</script>
save()保存状态之后,在没有恢复状态之前,都可以基于这个状态上进行操作,恢复之后,后续操作只能基于这个保存的状态进行操作了,如果保存的状态存在多个,
恢复状态只能就近恢复
,只能恢复上一次的状态
6.绘制图像
说明: 如果想把现有图像绘制到画布上,可以使用drawImage()
方法,它存在如下的传参方式:
drawImage(<img>元素, 绘制的x, 绘制的y):
绘制出来的图形与原来一样大drawImage(<img>元素, 绘制的x, 绘制的y, 目标宽度,目标高度):
绘制出来的图形的大小会缩放/扩大至指定的高度和宽度drawImage(<img>元素, 截取的x, 截取的y, 截取宽度,截取高度,绘制的x,绘制的y,绘制宽度,绘制高度):
会从图像的(x,y)处截取指定宽度和高度,并将其渲染在画布(x,y)处,大小为指定的宽高
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas Image Example</title>
</head>
<body>
<img src="./1.jpg" width="400" alt="" style="display: none" />
<canvas id="drawing" width="400" height="400"
>A drawing of something.</canvas
>
<script>
const drawing = document.getElementById("drawing");
const image = document.querySelector("img");
if (drawing.getContext) {
// 图片加载过程中的存在异步获取
image.onload = function () {
const context = drawing.getContext("2d");
context.drawImage(image, 10, 10);
};
}
</script>
</body>
</html>
7.阴影
说明: 可以使用以下属性为已有的路径和形状生成阴影,这些属性都可以通过 context对象读写。只要在绘制图形或路径前给这些属性设置好适当的值,阴影就会自动生成
shadowColor:
CSS 颜色值,表示要绘制的阴影颜色,默认为黑色。shadowOffsetX:
阴影相对于形状或路径的 x 坐标的偏移量,默认为 0。shadowOffsetY:
阴影相对于形状或路径的 y 坐标的偏移量,默认为 0。shadowBlur:
像素,表示阴影的模糊量。默认值为 0,表示不模糊。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas Image Example</title>
</head>
<body>
<img src="./1.jpg" width="400" alt="" style="display: none" />
<canvas id="drawing" width="400" height="400"
>A drawing of something.</canvas
>
<script>
const drawing = document.getElementById("drawing");
if (drawing.getContext) {
const context = drawing.getContext("2d");
// 设置阴影
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = "rgba(0, 0, 0, 0.5)";
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制蓝色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);
}
</script>
</body>
</html>
8.渐变
说明: 创建一个线性渐变
需要使用createLinearGradient(起点x,起点y,终点x,终点y)
方法,如果需要径向渐变
需要使用createRadialGradient(起点圆心x,起点圆心y,起点圆半径,终点圆心x,终点圆心y,终点圆半径)
方法,它们都会返回一个CanvasGradient对象实例
,实例上可以用下面的方法,
addColorStop(色标位置,CSS颜色字符串):
指定渐变的颜色,色标位置通过 0~1 范围内的值表示,0 是第一种颜色,1 是最后 一种颜色
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas Image Example</title>
</head>
<body>
<img src="./1.jpg" width="400" alt="" style="display: none" />
<canvas id="drawing" width="400" height="400"
>A drawing of something.</canvas
>
<script>
const drawing = document.getElementById("drawing");
if (drawing.getContext) {
const context = drawing.getContext("2d");
// 渐变对象
let gradient = context.createLinearGradient(30, 30, 70, 70);
// 渐变起始色
gradient.addColorStop(0, "white");
// 渐变终止色
gradient.addColorStop(1, "black");
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 渐变色对象可以给fillStyle或strokeStyle属性赋值
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
}
</script>
</body>
</html>
如果绘制的矩形在有部分不在渐变的范围内,则只有在渐变范围内的部分存在渐变
js
// 如果上面渐变的矩形这样写,渐变就只有一点点了
context.fillStyle = gradient;
context.fillRect(50, 50, 50, 50);
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas Image Example</title>
</head>
<body>
<img src="./1.jpg" width="400" alt="" style="display: none" />
<canvas id="drawing" width="400" height="400"
>A drawing of something.</canvas
>
<script>
const drawing = document.getElementById("drawing");
if (drawing.getContext) {
const context = drawing.getContext("2d");
let gradient = context.createRadialGradient(55, 55, 10, 55, 55, 30);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
}
</script>
</body>
</html>
9.图案
说明: 这个可以理解为背景图,用于填充和描画图形的重复图像,创建一个图案使用createPattern(<img>元素,如何重复)
完成,第二个参数的值可选择的值为repeat、repeat-x、repeat-y、no-repeat,一样是先创建后填充
js
// 举个栗子
let image = document.images[0],
pattern = context.createPattern(image, "repeat");
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150);
10.图像数据
说明: 获取图像数据可以使用getImageData(第一个像素的x,第一个像素的y,取取得的宽度,取得的高度)
方法,返回值是一个ImageData实例,里面data属性包含所有区域内像素的信息,每个像素都包含四个值,分别对应r、g、b、a
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas Image Example</title>
</head>
<body>
<img src="./1.jpg" width="400" alt="" style="display: none" />
<canvas id="drawing" width="400" height="400"
>A drawing of something.</canvas
>
<script>
const drawing = document.getElementById("drawing");
if (drawing.getContext) {
const context = drawing.getContext("2d");
let gradient = context.createRadialGradient(55, 55, 10, 55, 55, 30);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
// 获取图像坐标为(10,10)这个点的像素信息
let imageData = context.getImageData(10, 10, 1, 1);
console.log(imageData);
console.log(imageData.data);
}
</script>
</body>
</html>
11.合成
说明: 关于合成有两个属性,分别是globalAlpha
和globalComposition Operation
,前者是设置绘制的透明度,后者类似PS中的布尔运算,其取值超级多,详细可以看MDN具体的属性值
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas Image Example</title>
</head>
<body>
<img src="./1.jpg" width="400" alt="" style="display: none" />
<canvas id="drawing" width="400" height="400"
>A drawing of something.</canvas
>
<script>
const drawing = document.getElementById("drawing");
if (drawing.getContext) {
const context = drawing.getContext("2d");
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 修改全局透明度
context.globalAlpha = 0.5;
// 绘制蓝色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);
// 重置
context.globalAlpha = 0;
}
</script>
</body>
</html>