第18章 动画与canvas图形
18.1 requestAnimationFrame
18.1.1 早期定时动画
使用 setInterval()来控制动画的执行,但是这种没有办法确保何时能让浏览器把下一帧绘制出来
18.1.3 requestAnimationFrame
此方法接收一个参数(一个要在重绘屏幕前调用的函数)
function updateProgress() {
var div = document.getElementById("status");
div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
if (div.style.left != "100%") {
requestAnimationFrame(updateProgress);
}
}
requestAnimationFrame(updateProgress);
18.1.4 cancelAnimationFrame(取消任务)
let requestID = window.requestAnimationFrame(() => {
console.log('Repaint!');
});
window.cancelAnimationFrame(requestID);
18.1.5 通过 requestAnimationFrame 节流
1、通过 requestAnimationFrame()递归地向队列中加入回调函数,可以保证每次重绘最多只调用一次回调函数
2、配合使用一个计时器来限制操作执行的频率。
//将回调限制为不超过50毫秒执行一次
let enabled = true;
function expensiveOperation() {
console.log('Invoked at', Date.now());
}
window.addEventListener('scroll', () => {
if (enabled) {
enabled = false;
window.requestAnimationFrame(expensiveOperation);
window.setTimeout(() => enabled = true, 50);
}
});
18.2 画布(canvas)
1、创建
创建元素时至少要设置其 width 和 height 属性,
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
与其他元素一样,width 和 height 属性也可以在 DOM 节点上设置,因此可以随时修改。整个元素还可以通过 CSS 添加样式,并且元素在添加样式或实际绘制内容前是不可见的。
要在画布上绘制图形,首先要通过getContext()取得绘图上下文,对于平面图形,需要给这个方法传入参数"2d"
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas> ,有些浏览器对html规范中没有的元素会创建默认httml元素对象。
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 其他代码
}
2、导出
可以使用toDataURL()方法导出canvas元素上的图像这个方法接收一个参数:要生成图像的 MIME 类型(与用来创建图形的上下文无关)。例如,要从画布上导出一张 PNG 格式的图片,可以这样做:
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
// 取得图像的数据 URI
let imgURI = drawing.toDataURL("image/png");
// 显示图片
let image = document.createElement("img");
image.src = imgURI;
document.body.appendChild(image);
}
18.3 2D绘图上下文
18.3.1 填充和描边(fillStyle、strokeStyle)
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
context.strokeStyle = "red";
context.fillStyle = "#0000ff";
}
18.3.2 绘制矩形
方法:fillRect()、strokeRect()和 clearRect(),这些方法都接收4个参数:矩形x坐标、矩形y坐标、矩形宽度和矩形高度。参数的单位都是像素
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
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);
}
18.3.3 绘制路径
绘制路径,必须首先调用beginPath()方法以表示要开始绘制新路径,然后通过以下方法来绘制路径。
-
arc(x, y, radius, startAngle, endAngle, counterclockwise):以坐标(x, y)为圆心,以 radius 为半径绘制一条弧线,起始角度为 startAngle,结束角度为 endAngle(都是弧度)。最后一个参数 counterclockwise 表示是否逆时针计算起始角度和结束角度(默认为顺时针)。
-
arcTo(x1, y1, x2, y2, radius):以给定半径 radius,经由(x1, y1)绘制一条从上一点到(x2, y2)的弧线。
-
bezierCurveTo(c1x, c1y, c2x, c2y, x, y):以(c1x, c1y)和(c2x, c2y)为控制点,绘制一条从上一点到(x, y)的弧线(三次贝塞尔曲线)。
-
lineTo(x, y):绘制一条从上一点到(x, y)的直线。
-
moveTo(x, y):不绘制线条,只把绘制光标移动到(x, y)。
-
quadraticCurveTo(cx, cy, x, y):以(cx, cy)为控制点,绘制一条从上一点到(x, y)的弧线(二次贝塞尔曲线)。
-
rect(x, y, width, height):以给定宽度和高度在坐标点(x, y)绘制一个矩形。这个方法与 strokeRect()和fillRect()的区别在于,它创建的是一条路径,而不是独立的图形。
创建路径之后,可以调用closePath()方法绘制一条返回起点的线。如果路径已经完成,则可以指定fillStyle属性并调用fill()方法来填充路径,也可以指定strokeStyle属性并调用stroke()方法来描画路径,还可以调用 clip()方法基于已有路径创建一个新剪切区域。
let drawing = document.getElementById("drawing"); // 确保浏览器支持<canvas> if (drawing.getContext) { let context = drawing.getContext("2d"); // 创建路径 context.beginPath(); // 绘制外圆 context.arc(200, 200, 199, 0, 2 * Math.PI, false); // 绘制内圆 context.moveTo(390, 200); context.arc(200, 200, 189, 0, 2 * Math.PI, false); // 绘制分针 context.moveTo(200, 200); context.lineTo(200, 35); // 绘制时针 context.moveTo(200, 200); context.lineTo(370, 200); // 描画路径 context.stroke(); }
在这里插入图片描述
18.3.4 绘制文本
方法:fillText()、strokeText()
接收的参数:要绘制的字符串、x 坐标、y 坐标和可选的最大像素宽度。
属性:
-
font:以 CSS 语法指定的字体样式、大小、字体族等,比如"10px Arial"。
-
textAlign:指定文本的对齐方式,可能的值包括"start"、“end”、“left”、“right"和"center”。推荐使用"start"和"end",不使用"left"和"right",因为前者无论在从左到右书写的语言还是从右到左书写的语言中含义都更明确。
-
textBaseLine :指定文本的基线,可能的值包括 “top” 、 “hanging” 、 “middle” 、“alphabetic”、“ideographic"和"bottom”。
context.font = "bold 14px Arial"; context.textAlign = "center"; context.textBaseline = "middle"; context.fillText("12", 200, 20); // 与开头对齐 context.fillText("3", 380, 200);
18.3.5 变换
方法:
- rotate(angle):围绕原点把图像旋转 angle 弧度。
- 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 1
-
setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):把矩阵重置为默认值,再以传入的参数调用 transform()。
// 移动原点到表盘中心 context.translate(100, 100);
18.3.6 绘制图像
1、把图像绘制到画布上
接收3个参数:图像、x坐标、y坐标
<body>
<img id="scream" src="https://www.runoob.com/try/demo_source/img_the_scream.jpg">
<p>画布:</p>
<canvas id="myCanvas" width="250" height="300" style="border:1px solid #d3d3d3;">
</canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = document.getElementById("scream");
img.onload = function () {
ctx.drawImage(img, 10, 10);
}
</script>
</body>
如果想要改变所绘制图像的大小,可以再传入另外两个参数:目标宽度和目标高度
context.drawImage(image, 50, 10, 20, 30);
如果只把图像绘制到上下文中的一个区域。则要提供9个参数:要绘制的图像、源图像 x 坐标、源图像 y 坐标、源图像宽度、源图像高度、目标区域 x 坐标、目标区域 y 坐标、目标区域宽度和目标区域高度。
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);
原始图像中只有一部分绘制到画布上
18.3.7 阴影
2D 上下文可以根据以下属性的值自动为已有形状或路径生成阴影。
shadowColor:CSS 颜色值,表示要绘制的阴影颜色,默认为黑色。
shadowOffsetX:阴影相对于形状或路径的 x 坐标的偏移量,默认为 0。
shadowOffsetY:阴影相对于形状或路径的 y 坐标的偏移量,默认为 0。
shadowBlur:像素,表示阴影的模糊量。默认值为 0,表示不模糊。
这些属性都可以通过 context 对象读写。只要在绘制图形或路径前给这些属性设置好适当的值,阴影就会自动生成。
let 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);
18.3.8 渐变(CanvasGradient)
createLinearGradient:线性渐变
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);
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
createRadialGradient:径向渐变
接收6个参数:起点x坐标、y坐标、半径、终点x坐标、y坐标、半径
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);
18.3.9 图案
let image = document.images[0],
pattern = context.createPattern(image, "repeat");
// 绘制矩形
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150);
18.3.10 图案数据
可以使用getImageData()方法获取原始图像数据,接收4个参数,例如:
let imageData = context.getImageData(10, 5, 50, 50);
要从(10,5)开始取得50像素宽、50像素高的区域对应的数据
返回的对象时一个ImaheData的实例,包含3个属性:width、height、data(表红、绿、蓝和透明度值)
18.4 webGL
画布的3D上下文
在调用getContext()取得webGL上下文时指定一些选项,可通过参数对象传入
alpha:布尔值,表示是否为上下文创建透明通道缓冲区,默认为 true。
depth:布尔值,表示是否使用 16 位深缓冲区,默认为 true。
stencil:布尔值,表示是否使用 8 位模板缓冲区,默认为 false。
antialias:布尔值,表示是否使用默认机制执行抗锯齿操作,默认为 true。
premultipliedAlpha:布尔值,表示绘图缓冲区是否预乘透明度值,默认为 true。
preserveDrawingBuffer:布尔值,表示绘图完成后是否保留绘图缓冲区,默认为 false。 建议在充分了解这个选项的作用后再自行修改,因为这可能会影响性能。
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let gl = drawing.getContext("webgl", { alpha: false });
if (gl) {
// 使用 WebGL
}
}