JavaScript高级程序设计(4)
第18章 动画与 Canvas 图形
### 18.1 使用requestAnimationFrame 549 ##### 18.1.1早期定时动画 - > 以前,在 JavaScript 中创建动画基本上就是使用 setInterval()来控制动画的执行。这种定时动画的问题在于无法准确知晓循环之间的延时。定时间隔必须足够短,这样才能让不同的动画类型都能平滑顺畅,但又要足够长,以便产生浏览器可以渲染出来的变化。 ##### 18.1.2 时间间隔的问题 - > 随着<canvas>的流行和 HTML5 游戏的兴起,开发者发现 setInterval()和setTimeout()的不精确是个大问题。浏览器自身计时器的精度让这个问题雪上加霜。浏览器的计时器精度不足毫秒。 ##### 18.1.3 requestAnimationFrame - > requestAnimationFrame()方法接收一个参数,此参数是一个要在重绘屏幕前调用的函数。这个函数就是修改 DOM 样式以反映下一次重绘有什么变化的地方。为了实现动画循环,可以把多个requestAnimationFrame()调用串联起来,就像以前使用 setTimeout()时一样。 > > 传给 requestAnimationFrame()的函数实际上可以接收一个参数,此参数是一个 DOMHighResTimeStamp 的实例(比如 performance.now()返回的值),表示下次重绘的时间。这一点非常重要:requestAnimationFrame()实际上把重绘任务安排在了未来一个已知的时间点上,而且通过这个参数告诉了开发者。基于这个参数,就可以更好地决定如何调优动画了。 > > ```javascript > 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 - > ```javascript > let requestID = window.requestAnimationFrame(() => { > console.log('Repaint!'); > }); > window.cancelAnimationFrame(requestID); //消重绘任务 > ``` ##### 18.1.5 通过requestAnimationFrame节流 - > 通过 requestAnimationFrame()递归地向队列中加入回调函数,可以保证每次重绘最多只调用一次回调函数。在频繁执行影响页面外观的代码时(比如滚动事件监听器),可以利用这个回调队列进行节流。 > > ```javascript > //滚动事件监听器每次触发都会调用名为 expensiveOperation()(耗时操作)的函数。当向下滚动网页时,这个事件很快就会被触发并执行成百上千次。如果想把事件处理程序的调用限制在每次重绘前发生,可以把它封装到 requestAnimationFrame()调用中。把所有回调的执行集中在重绘钩子,但不会过滤掉每次重绘的多余调用。此时,定义一个标志变量,由回调设置其开关状态,就可以将多余的调用屏蔽 > > let enqueued = false; > function expensiveOperation() { > console.log('Invoked at', Date.now()); > enqueued = false; > } > window.addEventListener('scroll', () => { > if (!enqueued) { > enqueued = true; > window.requestAnimationFrame(expensiveOperation); > } > }); > ``` > > 因为重绘是非常频繁的操作,所以这还算不上真正的节流。更好的办法是配合使用一个计时器来限制操作执行的频率。这样,计时器可以限制实际的操作执行间隔,而 requestAnimationFrame 控制在浏览器的哪个渲染周期中执行。下面的例子可以将回调限制为不超过 50 毫秒执行一次: > > ```javascript > 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 基本的画布功能 552 - 创建<canvas>元素时至少要设置其 width 和 height 属性 ```html <canvas id="drawing" width="200" height="200">A drawing of something.</canvas> ``` - getContext():以获取对绘图上下文的引用。要在画布上绘制图形,首先要取得绘图上下文。 ```javascript let drawing = document.getElementById("drawing"); // 确保浏览器支持<canvas> if (drawing.getContext) { //对于平面图形,需要给这个方法传入参数"2d",表示要获取 2D 上下文对象 let context = drawing.getContext("2d"); // 其他代码 } ``` - toDataURL()方法:导出<canvas>元素上的图像 ```javascript 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绘图上下文 553 ##### 18.3.1 填充和描边 - > 大多数 2D 上下文操作有填充和描边的变体,显示效果取决于两个属性,这两个属性可以是字符串、渐变对象或图案对象,默认值都为"#000000"。 > > - fillStyle:填充以指定样式(颜色、渐变或图像)自动填充形状。 > > - strokeStyle:描边只为图形边界着色。 > > ```javascript > let drawing = document.getElementById("drawing"); > // 确保浏览器支持<canvas> > if (drawing.getContext) { > let context = drawing.getContext("2d"); > context.strokeStyle = "red"; > context.fillStyle = "#0000ff"; > } > ``` ##### 18.3.2 绘制矩形 - > - fillRect(x,y,width,height):用于以指定颜色在画布上绘制并填充矩形。填充的颜色使用 fillStyle 属性指定。 > - strokeRect(x,y,width,height):使用通过 strokeStyle 属性指定的颜色绘制矩形轮廓。 > - clearRect(x,y,width,height):擦除画布中某个区域。该方法用于把绘图上下文中的某个区域变透明。 > > ```javascript > 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():创建路径之后,可以使用 closePath()方法绘制一条返回起点的线。如果路径已经完成,则既可以指定 fillStyle 属性并调用 fill()方法来填充路径,也可以指定 strokeStyle 属性并调用stroke()方法来描画路径,还可以调用 clip()方法基于已有路径创建一个新剪切区域。 > > - isPointInPath(x, y):于确定指定的点是否在路径上,可以在关闭路径前随时调用。 > > ```javascript > let drawing = document.getElementById("drawing"); > // 确保浏览器支持<canvas> > 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); > > // 绘制分针 > context.moveTo(100, 100); > context.lineTo(100, 15); > > // 绘制时针 > context.moveTo(100, 100); > context.lineTo(35, 100); > > // 描画路径 > context.stroke(); > } > ``` ##### 18.3.4 绘制文本 - > - fillText(str, x, y, [width]):使用fillStyle 属性绘制文本。是使用最多,因为它模拟了在网页中渲染文本。 > - strokeText(str, x, y, [width]):使用 strokeStyle 属性。 > > 这两个方法最终绘制的结果都取决于以下 3 个属性 > > - font:以 CSS 语法指定的字体样式、大小、字体族等,比如"10px Arial"。 textAlign:指定文本的对齐方式,可能的值包括"start"、"end"、"left"、"right"和"center"。推荐使用"start"和"end",不使用"left"和"right",因为前者无论在从左到右书写的语言还是从右到左书写的语言中含义都更明确。 > > - textBaseLine :指定文本的基线,可能的值包括 "top" 、 "hanging" 、 "middle" 、"alphabetic"、 "ideographic" 和 "bottom"。 > > ```javascript > context.font = "bold 14px Arial"; > context.textAlign = "center"; > context.textBaseline = "middle"; > context.fillText("12", 100, 20); > ``` ##### 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):像下面这样通过矩阵乘法直接修改矩阵。 > - setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):把矩阵重置为默认值,再以传入的参数调用 transform()。 > - restore():会从暂存栈中取出并恢复之前保存的设置。 > - save():只保存应用到绘图上下文的设置和变换,不保存绘图上下文的内容。 > > ```javascript > //先将 fillStyle 设置为红色,然后调用 save()。 > context.fillStyle = "#ff0000"; > context.save(); > > //接着,将 fillStyle 修改为绿色,坐标移动到(100, 100),并再次调用 save(),保存设置。 > context.fillStyle = "#00ff00"; > context.translate(100, 100); > context.save(); > > //随后,将 fillStyle 属性设置为蓝色并绘制一个矩形。因为此时坐标被移动了,所以绘制矩形的坐标实际上是(100, 100)。 > context.fillStyle = "#0000ff"; > context.fillRect(0, 0, 100, 200); // 在(100, 100)绘制蓝色矩形 > > //在调用 restore()之后,fillStyle 恢复为绿色,因此这一次绘制的矩形是绿色的。而绘制矩形的坐标是(110, 110),因为变换仍在起作用。 > context.restore(); > context.fillRect(10, 10, 100, 200); // 在(100, 100)绘制绿色矩形 > > //再次调用 restore()之后,变换被移除,fillStyle 也恢复为红色。绘制最后一个矩形的坐标变成了(0, 0)。 > context.restore(); > context.fillRect(0, 0, 100, 200); // 在(0, 0)绘制红色矩形 > ``` ##### 18.3.6 绘制图像 - >- context.drawImage(img,x,y); 在画布上定位图像: >- context.drawImage(img,x,y,width,height); 在画布上定位图像,并规定图像的宽度和高度: >- context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height); 剪切图像,并在画布上定位被剪切的部分 > >```javascript >let image = document.images[0]; >context.drawImage(image, 10, 10); >context.drawImage(image, 50, 10, 20, 30); >context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60); >``` ##### 18.3.7 阴影 - > - shadowColor:CSS 颜色值,表示要绘制的阴影颜色,默认为黑色。 > - shadowOffsetX:阴影相对于形状或路径的 x 坐标的偏移量,默认为 0。 > - shadowOffsetY:阴影相对于形状或路径的 y 坐标的偏移量,默认为 0。 > - shadowBlur:像素,表示阴影的模糊量。默认值为 0,表示不模糊。 > > ```javascript > 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 > ``` ##### 18.3.8 渐变 - > - createLinearGradient(起点 *x* 坐标, 起点 *y* 坐标, 终点 *x* 坐标, 终点 *y* 坐标):会以指定大小创建一个新的 CanvasGradient对象并返回实例。 > - addColorStop(色标位置, CSS 颜色字符串):为渐变指定色标。色标位置通过 0~1 范围内的值表示,0 是第一种颜色,1 是最后一种颜色。 > - createRadialGradient(圆形 *x*,圆形*y* ,圆形半径,圆形中心x,圆形中心y,圆形中心半径):径向渐变(或放射性渐变)。 > > ```javascript > //在画布上从(30, 30)到(70, 70)绘制一个渐变。渐变的起点颜色为白色,终点颜色为黑色。 > let gradient = context.createLinearGradient(30, 30, 70, 70); > gradient.addColorStop(0, "white"); > gradient.addColorStop(1, "black"); > > //可以把这个对象赋给 fillStyle 或 strokeStyle 属性,从而以渐变填充或描画绘制的图形: > // 绘制红色矩形 > context.fillStyle = "#ff0000"; > context.fillRect(10, 10, 50, 50); > > // 绘制渐变矩形 > context.fillStyle = gradient; //******* > context.fillRect(30, 30, 50, 50); > > //要创建起点圆心在形状中心并向外扩散的径向渐变,需要将两个圆形设置为同心圆。比如,要在前面例子中矩形的中心创建径向渐变,则渐变的两个圆形的圆心都必须设置为(55, 55)。这是因为矩形的起点是(30, 30),终点是(80, 80)。代码如下: > 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 图案 - > - createPattern():创建新图案,用于填充和描画图形。跟渐变一样,图案的起点实际上是画布的原点(0, 0)。将填充样式设置为图案,表示在指定位置而不是开始绘制的位置显示图案。 > > ```javascript > let image = document.images[0], > pattern = context.createPattern(image, "repeat"); > // 绘制矩形 > context.fillStyle = pattern; > context.fillRect(10, 10, 150, 150); > ``` ##### 18.3.10 图像数据 - > - getImageData(左上角x坐标,左上角y坐标,宽度,高度):获取原始图像数据。 > > ```javascript > //要从(10, 5)开始取得 50 像素宽、50 像素高的区域对应的数据 > let imageData = context.getImageData(10, 5, 50, 50); > ``` > > - 只有在画布没有加载跨域内容时才可以获取图像数据。如果画布上绘制的是跨域内容,则尝试获取图像数据会导致 JavaScript 报错。 ##### 18.3.11 合成 - > - globalAlpha属性:是一个范围在 0~1 的值,用于指定所有绘制内容的透明度,默认值为 0。 > > ```javascript > // 绘制红色矩形 > 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; > ``` > > - globalCompositionOperation属性:表示新绘制的形状如何与上下文中已有的形状融合。 > > ```javascript > // 绘制红色矩形 > context.fillStyle = "#ff0000"; > context.fillRect(10, 10, 50, 50); > // 设置合成方式 > context.globalCompositeOperation = "destination-over"; > // 绘制蓝色矩形 > context.fillStyle = "rgba(0,0,255,1)"; > context.fillRect(30, 30, 50, 50); > ``` ### 18.4 WebGL 569 ##### 18.4.1 WebGL上下文 - >```javascript >let drawing = document.getElementById("drawing"); >// 确保浏览器支持<canvas> >if (drawing.getContext) { >//在使用上下文之前,应该先检测返回值是否存在: >let gl = drawing.getContext("webgl"); >if (gl){ > // 使用 WebGL >} >} >``` ##### 18.4.2 WebGL基础 - >- getContext()选项: > - alpha:布尔值,表示是否为上下文创建透明通道缓冲区,默认为 true。 > - depth:布尔值,表示是否使用 16 位深缓冲区,默认为 true。 > - stencil:布尔值,表示是否使用 8 位模板缓冲区,默认为 false。 > - antialias:布尔值,表示是否使用默认机制执行抗锯齿操作,默认为 true。 > - premultipliedAlpha:布尔值,表示绘图缓冲区是否预乘透明度值,默认为 true。 > - preserveDrawingBuffer:布尔值,表示绘图完成后是否保留绘图缓冲区,默认为 false。 > >```javascript >let drawing = document.getElementById("drawing"); >// 确保浏览器支持<canvas> >if (drawing.getContext) { >let gl = drawing.getContext("webgl", { alpha: false }); >if (gl) { >// 使用 WebGL >} >} >``` > >- 常量 > > > OpenGL常量以 GL_开头,在 WebGL 中,context 对象上的常量则不包含 GL_前缀。例如,L_COLOR_BUFFER_BIT 常量在WebGL 中要这样访问 gl.COLOR_BUFFER_BIT。 > >- 方法命名 > >- 准备绘图 > >- 视口与坐标 > >- 缓冲区 > >- 错误 > >- 着色器 > >- 绘图 > >- 纹理 > >- 读取像素
顶部
收展
底部
[TOC]
目录
第1章 JavaScript简介
第2章 在 HTML中使用JavaScript
第3章 语言基础(1)语法变量
第3章 语言基础(2)数据类型
第3章 语言基础(3)操作符
第3章 语言基础(4)语句
第4章 变量、作用域与内存
第5章 基本引用类型
第6章 集合引用类型
第7章 迭代器与生成器
第8 章对象、类与面向对象编程
第9章 代理与反射
第10章 函数
第11章 期约与异步函数
第12章 BOM
第13章 客户端检测
第14章 DOM
第15章 DOM 扩展
第16章 DOM2 和 DOM3
第17章 事件
第18章 动画与 Canvas 图形
第19章 表单脚本
第20章 JavaScript API
第21章 错误处理与调试
第22章 处理 XML
第23章 JSON
第24章 网络请求与远程资源
第25章 客户端存储
第26章 模块
第27章 工作者线程
第28章 最佳实践
相关推荐
WebSocket