1
0
mirror of https://github.com/apachecn/eloquent-js-3e-zh.git synced 2025-05-23 20:02:20 +00:00
This commit is contained in:
wizardforcel 2018-05-12 22:51:27 +08:00
parent 0a64725152
commit ab359949e0

130
17.md
View File

@ -8,13 +8,13 @@
但是在一些场景中使用DOM并不符合我们的设计初衷。比如我们很难使用普通的HTML元素画出任意两点之间的线段这类图形。 但是在一些场景中使用DOM并不符合我们的设计初衷。比如我们很难使用普通的HTML元素画出任意两点之间的线段这类图形。
这里有两种解决办法。第一种方法基于 DOM但使用可缩放矢量图形SVGScalable Vector Graphics代替 HTML。我们可以将 SVG 看成文档标记方言专用于描述图形而非文字。你可以在HTML文档中嵌入SVG还可以在&lt;img&gt;标签中引用它。 这里有两种解决办法。第一种方法基于 DOM但使用可缩放矢量图形SVGScalable Vector Graphics代替 HTML。我们可以将 SVG 看成文档标记方言专用于描述图形而非文字。你可以在HTML文档中嵌入SVG还可以在<img>标签中引用它。
我们将第二种方法称为画布canvas。画布是一个能够封装图片的DOM元素。它提供了在空白的html节点上绘制图形的编程接口。SVG与画布的最主要区别在于SVG保存了对于图像的基本信息的描述我们可以随时移动或修改图像。 我们将第二种方法称为画布canvas。画布是一个能够封装图片的DOM元素。它提供了在空白的html节点上绘制图形的编程接口。SVG与画布的最主要区别在于SVG保存了对于图像的基本信息的描述我们可以随时移动或修改图像。
另外,画布在绘制图像的同时会把图像转换成像素(在栅格中的具有颜色的点)并且不会保存这些像素表示的内容。唯一的移动图形的方法就是清空画布(或者围绕着图形的部分画布)并在新的位置重画图形。 另外,画布在绘制图像的同时会把图像转换成像素(在栅格中的具有颜色的点)并且不会保存这些像素表示的内容。唯一的移动图形的方法就是清空画布(或者围绕着图形的部分画布)并在新的位置重画图形。
### 16.1 SVG ## 16.1 SVG
本书不会深入研究SVG的细节但是我会简单地解释其工作原理。在本章的结尾我会再次来讨论对于某个具体的应用来说我们应该如何权衡利弊选择一种绘图方式。 本书不会深入研究SVG的细节但是我会简单地解释其工作原理。在本章的结尾我会再次来讨论对于某个具体的应用来说我们应该如何权衡利弊选择一种绘图方式。
@ -29,22 +29,22 @@
</svg> </svg>
``` ```
xmlns属性把一个元素以及他的子元素切换到一个不同的XML命名空间。这个由url定义的命名空间规定了我们当前使用的语言。在HTML中不存在的&lt;circle&gt;&lt;rect&gt;标签但这些标签在SVG中是有意义的你可以通过这些标签的属性来绘制图像并指定样式与位置。 xmlns属性把一个元素以及他的子元素切换到一个不同的XML命名空间。这个由url定义的命名空间规定了我们当前使用的语言。在HTML中不存在的<circle><rect>标签但这些标签在SVG中是有意义的你可以通过这些标签的属性来绘制图像并指定样式与位置。
和HTML标签一样这些标签会创建DOM元素脚本可以和它们交互。例如下面的代码可以把&lt;circle&gt;元素的颜色替换为青色。 和HTML标签一样这些标签会创建DOM元素脚本可以和它们交互。例如下面的代码可以把<circle>元素的颜色替换为青色。
```html ```html
let circle = document.querySelector("circle"); let circle = document.querySelector("circle");
circle.setAttribute("fill", "cyan"); circle.setAttribute("fill", "cyan");
``` ```
### 16.2 canvas元素 ## 16.2 canvas元素
我们可以在&lt;canvas&gt;元素中绘制画布图形。你可以通过设置width与height属性来确定画布尺寸单位为像素 我们可以在<canvas>元素中绘制画布图形。你可以通过设置width与height属性来确定画布尺寸单位为像素
新的画布是空的,意味着它是完全透明的,看起来就像文档中的空白区域一样。 新的画布是空的,意味着它是完全透明的,看起来就像文档中的空白区域一样。
&lt;canvas&gt;标签允许多种不同风格的绘图。要获取真正的绘图接口首先我们要创建一个能够提供绘图接口的方法的上下文context。目前有两种得到广泛支持的绘图接口用于绘制二维图形的“2d”与通过openGL接口绘制三维图形的“webgl”。 <canvas>标签允许多种不同风格的绘图。要获取真正的绘图接口首先我们要创建一个能够提供绘图接口的方法的上下文context。目前有两种得到广泛支持的绘图接口用于绘制二维图形的“2d”与通过openGL接口绘制三维图形的“webgl”。
本书只讨论二维图形而不讨论WebGL。但是如果你对三维图形感兴趣我强烈建议大家自行深入研究WebGL。它提供了非常简单的现代图形硬件接口同时你也可以使用JavaScript来高效地渲染非常复杂的场景。 本书只讨论二维图形而不讨论WebGL。但是如果你对三维图形感兴趣我强烈建议大家自行深入研究WebGL。它提供了非常简单的现代图形硬件接口同时你也可以使用JavaScript来高效地渲染非常复杂的场景。
@ -66,7 +66,7 @@ circle.setAttribute("fill", "cyan");
与HTML或者SVG相同画布使用的坐标系统将00放置在左上角并且y轴向下增长。所以1010是相对于左上角向下并向右各偏移10像素的位置。 与HTML或者SVG相同画布使用的坐标系统将00放置在左上角并且y轴向下增长。所以1010是相对于左上角向下并向右各偏移10像素的位置。
### 直线和平面 ## 直线和平面
我们可以使用画布接口填充图形也就是赋予某个区域一个固定的填充颜色或填充模式。我们也可以描边也就是沿着图形的边沿画出线段。SVG也使用了相同的技术。 我们可以使用画布接口填充图形也就是赋予某个区域一个固定的填充颜色或填充模式。我们也可以描边也就是沿着图形的边沿画出线段。SVG也使用了相同的技术。
@ -91,7 +91,7 @@ strokeStyle属性的作用很相似但是它用于规定轮廓线的颜色。
当没有设置width或者height参数时正如示例一样画布元素的默认宽度为300像素默认高度为150像素。 当没有设置width或者height参数时正如示例一样画布元素的默认宽度为300像素默认高度为150像素。
### 16.4 路径 ## 16.4 路径
路径是线段的序列。2D canvas接口使用一种奇特的方式来描述这样的路径。Path的绘制都是间接完成的。我们无法将路径保存为可以后续修改并传递的值。如果你想修改路径必须要调用多个方法来描述他的形状。 路径是线段的序列。2D canvas接口使用一种奇特的方式来描述这样的路径。Path的绘制都是间接完成的。我们无法将路径保存为可以后续修改并传递的值。如果你想修改路径必须要调用多个方法来描述他的形状。
@ -128,7 +128,7 @@ strokeStyle属性的作用很相似但是它用于规定轮廓线的颜色。
你也可以使用closePath方法显示地通过增加一条回到路径起始节点的线段来封闭一个路径。这条线段在勾勒路径的时候将被显示地画出。 你也可以使用closePath方法显示地通过增加一条回到路径起始节点的线段来封闭一个路径。这条线段在勾勒路径的时候将被显示地画出。
### 16.5 曲线 ## 16.5 曲线
路径也可能会包含曲线。绘制曲线更加复杂。 路径也可能会包含曲线。绘制曲线更加复杂。
@ -191,7 +191,7 @@ bezierCurve贝塞尔曲线方法可以绘制一种类似的曲线。不同
上面这段代码绘制出的图形包含了一条从完整圆第一次调用arc的右侧到四分之一圆第二次调用arc的左侧的直线。arc与其他绘制路径的方法一样会自动连接到上一个路径上。你可以调用`moveTo`或者开启一个新的路径来避免这种情况。 上面这段代码绘制出的图形包含了一条从完整圆第一次调用arc的右侧到四分之一圆第二次调用arc的左侧的直线。arc与其他绘制路径的方法一样会自动连接到上一个路径上。你可以调用`moveTo`或者开启一个新的路径来避免这种情况。
### 16.6 绘制饼状图 ## 16.6 绘制饼状图
设想你刚刚从EconomiCorp获得了一份工作并且你的第一个任务是画出一个描述其用户满意度调查结果的饼状图。results绑定包含了一个表示调查结果的对象的数组。 设想你刚刚从EconomiCorp获得了一份工作并且你的第一个任务是画出一个描述其用户满意度调查结果的饼状图。results绑定包含了一个表示调查结果的对象的数组。
@ -231,7 +231,7 @@ const results = [
但表格并没有告诉我们切片代表的含义,它毫无用处。因此我们需要将文字画在画布上。 但表格并没有告诉我们切片代表的含义,它毫无用处。因此我们需要将文字画在画布上。
### 16.7 文本 ## 16.7 文本
2D画布的context对象提供了fillText方法和strokeText方法。第二个方法可以用于绘制字母轮廓但通常情况下我们需要的是fillText方法。该方法使用当前的fillColor来填充特定文字的轮廓。 2D画布的context对象提供了fillText方法和strokeText方法。第二个方法可以用于绘制字母轮廓但通常情况下我们需要的是fillText方法。该方法使用当前的fillColor来填充特定文字的轮廓。
@ -251,11 +251,11 @@ const results = [
在本章末尾的练习中,我们会回顾饼状图,并解决给饼状图分片标注的问题。 在本章末尾的练习中,我们会回顾饼状图,并解决给饼状图分片标注的问题。
### 16.8 图像 ## 16.8 图像
计算机图形学领域经常将矢量图形和位图图形分开来讨论。本章一直在讨论第一种图形,即通过对图形的逻辑描述来绘图。而位图则相反,不需要设置实际图形,而是通过处理像素数据来绘制图像(光栅化的着色点)。 计算机图形学领域经常将矢量图形和位图图形分开来讨论。本章一直在讨论第一种图形,即通过对图形的逻辑描述来绘图。而位图则相反,不需要设置实际图形,而是通过处理像素数据来绘制图像(光栅化的着色点)。
我们可以使用drawImage方法在画布上绘制像素值。此处的像素数值可以来自&lt;img&gt;元素,或者来自其他的画布。下例创建了一个独立的&lt;img&gt;元素并且加载了一张图像文件。但我们无法马上使用该图片进行绘制因为浏览器可能还没有完成图片的获取操作。为了处理这个问题我们在图像元素上注册一个“load”事件处理程序并且在图片加载完之后开始绘制。 我们可以使用drawImage方法在画布上绘制像素值。此处的像素数值可以来自<img>元素,或者来自其他的画布。下例创建了一个独立的<img>元素并且加载了一张图像文件。但我们无法马上使用该图片进行绘制因为浏览器可能还没有完成图片的获取操作。为了处理这个问题我们在图像元素上注册一个“load”事件处理程序并且在图片加载完之后开始绘制。
```html ```html
<canvas></canvas> <canvas></canvas>
@ -309,7 +309,7 @@ clearRect方法可以帮助我们在画布上绘制动画。该方法类似于fi
cycle绑定用于记录角色在动画图像中的位置。每显示一帧我们都要将cycle加1并通过取余数确保cycle的值在0~7这个范围内。我们随后使用该绑定计算精灵当前形象在图片中的x坐标。 cycle绑定用于记录角色在动画图像中的位置。每显示一帧我们都要将cycle加1并通过取余数确保cycle的值在0~7这个范围内。我们随后使用该绑定计算精灵当前形象在图片中的x坐标。
### 16.9 变换 ## 16.9 变换
但是,如果我们希望角色可以向左走而不是向右走该怎么办?诚然,我们可以绘制另一组精灵,但我们也可以使用另一种方式在画布上绘图。 但是,如果我们希望角色可以向左走而不是向右走该怎么办?诚然,我们可以绘制另一组精灵,但我们也可以使用另一种方式在画布上绘图。
@ -372,7 +372,7 @@ function flipHorizontally(context, around) {
</script> </script>
``` ```
### 16.10 存储与清除图像的变换状态 ## 16.10 存储与清除图像的变换状态
图像变换的效果会保留下来。我们绘制出一次镜像特征后,绘制其他特征时都会产生镜像效果,这可能并不方便。 图像变换的效果会保留下来。我们绘制出一次镜像特征后,绘制其他特征时都会产生镜像效果,这可能并不方便。
@ -406,11 +406,11 @@ function flipHorizontally(context, around) {
如果没有调用save与restore方法第二次递归调用branch将会在第一次调用的位置结束。它不会与当前的分支相连接而是更加靠近中心偏右第一次调用所画出的分支。结果图像会很有趣但是它肯定不是一棵树。 如果没有调用save与restore方法第二次递归调用branch将会在第一次调用的位置结束。它不会与当前的分支相连接而是更加靠近中心偏右第一次调用所画出的分支。结果图像会很有趣但是它肯定不是一棵树。
### 16.11 回到游戏 ## 16.11 回到游戏
我们现在已经了解了足够多的画布绘图知识我们已经可以使用基于画布的显示系统来改造前面几章中开发的游戏了。新的界面不会再是一个个色块而使用drawImage来绘制游戏中元素对应的图片。 我们现在已经了解了足够多的画布绘图知识我们已经可以使用基于画布的显示系统来改造前面几章中开发的游戏了。新的界面不会再是一个个色块而使用drawImage来绘制游戏中元素对应的图片。
我们定义了一种对象类型叫做CanvasDisplay支持与第15章中的DOMDisplay相同的接口也就是setState方法与clear方法。 我们定义了一种对象类型叫做CanvasDisplay支持与第14章中的DOMDisplay相同的接口也就是setState方法与clear方法。
这个对象需要比DOMDisplay多保存一些信息。该对象不仅需要使用DOM元素的滚动位置还需要追踪自己的视口viewport。视口会告诉我们目前处于哪个关卡。最后该对象会保存一个filpPlayer属性确保即便玩家站立不动时它面朝的方向也会与上次移动所面向的方向一致。 这个对象需要比DOMDisplay多保存一些信息。该对象不仅需要使用DOM元素的滚动位置还需要追踪自己的视口viewport。视口会告诉我们目前处于哪个关卡。最后该对象会保存一个filpPlayer属性确保即便玩家站立不动时它面朝的方向也会与上次移动所面向的方向一致。
@ -566,21 +566,21 @@ CanvasDisplay.prototype.drawPlayer = function(player, x, y,
drawPlayer方法被drawActors方法调用该方法负责画出游戏中的所有角色。 drawPlayer方法被drawActors方法调用该方法负责画出游戏中的所有角色。
```js ```js
CanvasDisplay.prototype.drawActors = function() { CanvasDisplay.prototype.drawActors = function(actors) {
this.level.actors.forEach(function(actor) { for (let actor of actors) {
var width = actor.size.x * scale; let width = actor.size.x * scale;
var height = actor.size.y * scale; let height = actor.size.y * scale;
var x = (actor.pos.x - this.viewport.left) * scale; let x = (actor.pos.x - this.viewport.left) * scale;
var y = (actor.pos.y - this.viewport.top) * scale; let y = (actor.pos.y - this.viewport.top) * scale;
if (actor.type == "player") { if (actor.type == "player") {
this.drawPlayer(x, y, width, height); this.drawPlayer(actor, x, y, width, height);
} else { } else {
var tileX = (actor.type == "coin" ? 2 : 1) * scale; let tileX = (actor.type == "coin" ? 2 : 1) * scale;
this.cx.drawImage(otherSprites, this.cx.drawImage(otherSprites,
tileX, 0, width, height, tileX, 0, width, height,
x, y, width, height); x, y, width, height);
} }
}, this); }
}; };
``` ```
@ -588,7 +588,7 @@ CanvasDisplay.prototype.drawActors = function() {
当计算角色的位置时我们需要减掉视口的位置因为00在我们的画布坐标系中代表着视口层面的左上角而不是该关卡的左上角。我们也可以使用translate方法这样可以作用于所有元素。 当计算角色的位置时我们需要减掉视口的位置因为00在我们的画布坐标系中代表着视口层面的左上角而不是该关卡的左上角。我们也可以使用translate方法这样可以作用于所有元素。
就形成了新的展示系统。最后的游戏界面如下所示。 个文档将新的显示屏插入`runGame`中:
```html ```html
<body> <body>
@ -598,25 +598,25 @@ CanvasDisplay.prototype.drawActors = function() {
</body> </body>
``` ```
### 16.12 选择图像接口 ## 16.12 选择图像接口
无论何时,只要在浏览器中绘图时你都可以选择纯粹的HTML、SVG或画布。没有唯一的最适合的且在所有动画中都是最好的方法。每个选择都有它的利与弊。 所以当你需要在浏览器中绘图时你都可以选择纯粹的HTML、SVG或画布。没有唯一的最适合的且在所有动画中都是最好的方法。每个选择都有它的利与弊。
单纯的HTML的优点是简单。它也可以很好地与文字集成使用。SVG与画布都可以允许你绘制文字但是它们不会只通过一行代码来帮助你放置text或者包装它在一个基于HTML的图像中包含一篇文字是非常方便的 单纯的HTML的优点是简单。它也可以很好地与文字集成使用。SVG与画布都可以允许你绘制文字但是它们不会只通过一行代码来帮助你放置text或者包装它在一个基于HTML的图像中包含文本块更加简单
SVG可以被用来制造可以任意缩放而仍然清晰的图像。它比单纯的HTML更加难以使用但是它更加强大 SVG可以被用来制造可以任意缩放而仍然清晰的图像。与HTML相反它实际上是为绘图而设计的因此更适合于此目的
SVG与HTML都会构建一个新的数据结构DOM来代表图片对象。这使得在绘制元素之后对其进行修改更为可能。如果你需要重复的修改在一张大图片中的一小部分来对用户的动作进行响应或者作为动画的一部分时在画布里做这件事情将会极其的昂贵。DOM也可以允许我们在图片上的每一个元素甚至在SVG画出的图形上注册鼠标事件的处理器。在画布里则实现不了。 SVG与HTML都会构建一个新的数据结构DOM,它表示你的图片。这使得在绘制元素之后对其进行修改更为可能。如果你需要重复的修改在一张大图片中的一小部分来对用户的动作进行响应或者作为动画的一部分时在画布里做这件事情将会极其的昂贵。DOM也可以允许我们在图片上的每一个元素甚至在SVG画出的图形上注册鼠标事件的处理器。在画布里则实现不了。
但是画布的基于像素的方法在需要绘制大量的微小元素时会有优势。它不会构建新的数据结构而是仅仅重复的在同一个像素上绘制,这使得画布在每个图形上拥有更低的消耗。 但是画布的基于像素的方法在需要绘制大量的微小元素时会有优势。它不会构建新的数据结构而是仅仅重复的在同一个像素上绘制,这使得画布在每个图形上拥有更低的消耗。
有一些效果像在逐像素的渲染一个场景比如使用光线追踪或者使用javaScript对一张图片进行后加工虚化或者扭曲只能通过基于像素的技术来进行真实的处理。在某些情况下你可能想要将这些技术整合起来使用。比如你可能用SVG或者画布画出一个图形但是通过将一个HTML元素放在图片的顶端来展示像素信息。 有一些效果像在逐像素的渲染一个场景比如使用光线追踪或者使用javaScript对一张图片进行后加工虚化或者扭曲只能通过基于像素的技术来进行真实的处理。在某些情况下你可能想要将这些技术整合起来使用。比如你可能用SVG或者画布画出一个图形但是通过将一个HTML元素放在图片的顶端来展示像素信息。
对于一些要求低的程序来说,选择哪个接口并没有什么太大的区别。因为不需要绘制文字,处理鼠标交互或者与大量的外母元素交互。我们在本章游戏构建的第二个显示屏可以通过使用三种图像技术中的任意一种来实现。 对于一些要求低的程序来说,选择哪个接口并没有什么太大的区别。因为不需要绘制文字,处理鼠标交互或者与大量的外母元素交互。我们在本章游戏构建的显示屏可以通过使用三种图像技术中的任意一种来实现。
### 16.13 本章小结 ## 16.13 本章小结
在本章中,我们讨论了在浏览器中绘制图形的技术,重点关注了&lt;canvas&gt;元素。 在本章中,我们讨论了在浏览器中绘制图形的技术,重点关注了<canvas>元素。
一个canvas节点代表了我们的程序可以绘制在文档中的一片区域。这个绘图动作是通过一个由getContext方法创建的绘图上下文对象完成的。 一个canvas节点代表了我们的程序可以绘制在文档中的一片区域。这个绘图动作是通过一个由getContext方法创建的绘图上下文对象完成的。
@ -630,81 +630,83 @@ SVG与HTML都会构建一个新的数据结构DOM来代表图片对象。
图形变换允许你向多个方向绘制图片。2D绘制上下文拥有一个当前的可以通过translate、scale与rotate进行变换。这些会影响所有的后续的绘制操作。一个变换的状态可以通过save方法来保存通过restore方法来恢复。 图形变换允许你向多个方向绘制图片。2D绘制上下文拥有一个当前的可以通过translate、scale与rotate进行变换。这些会影响所有的后续的绘制操作。一个变换的状态可以通过save方法来保存通过restore方法来恢复。
当在一个画布上绘制动画时clearRect方法可以用来在重绘之前清除画布的某一部分。 在一个画布上展示动画时clearRect方法可以用来在重绘之前清除画布的某一部分。
### 16.14 习题 ## 16.14 习题
#### 16.14.1 形状 ### 16.14.1 形状
编写一个程序,在画布上画出下面的图形。 编写一个程序,在画布上画出下面的图形。
1.一个不规则四边形(一个在一边比较长的矩形) 1. 一个不规则四边形(一个在一边比较长的矩形)
2.一个红色的钻石一个矩形旋转45度角 2. 一个红色的钻石一个矩形旋转45度角
3.zigzagging——一个2字形折线 3. zigzagging——一个2字形折线
4.一个由100条直线线段构成的螺旋 4. 一个由100条直线线段构成的螺旋
5.一个黄色的星星 5. 一个黄色的星星
![](../Images/00527.jpeg) ![](../Images/00527.jpeg)
当绘制最后两个图形时你可以参考在第13章中的Math.cos和Math.sin的定义这两个函数定义了如何在一个圆上的坐标。 当绘制最后两个图形时你可以参考在第14章中的Math.cos和Math.sin的定义这两个函数定义了如何在一个圆上的坐标。
建议你为每一个图形创建一个方法,传入坐标信息,以及其他的一些参数,比如大小或者点的数量。另一种方法,可以在你的代码中硬编码,会使得你的代码变得难以阅读和修改。 建议你为每一个图形创建一个方法,传入坐标信息,以及其他的一些参数,比如大小或者点的数量。另一种方法,可以在你的代码中硬编码,会使得你的代码变得难以阅读和修改。
```html ```html
<canvas width="600" height="200"></canvas> <canvas width="600" height="200"></canvas>
<script> <script>
var cx = document.querySelector("canvas").getContext("2d"); let cx = document.querySelector("canvas").getContext("2d");
// Your code here. // Your code here.
</script> </script>
``` ```
#### 16.14.2 饼状图 ### 16.14.2 饼状图
在本章的前部分我们看到一个绘制饼状图的样例程序。修改这个程序使得每个部分的名字可以被显示在相应的切片旁边。试着找到一个合适的方法来自动放置这些文字同时也可以适用于其他数据。你可以假设这些部分不会小于5%这意味着不会有大量的非常微小的相邻的部分。你可能还会需要Math.sin和Math.cos方法像上面的练习中描述的一样。 在本章的前部分,我们看到一个绘制饼状图的样例程序。修改这个程序,使得每个部分的名字可以被显示在相应的切片旁边。试着找到一个合适的方法来自动放置这些文字,同时也可以适用于其他数据。你可以假设分类大到足以为标签留出空间。
你可能还会需要Math.sin和Math.cos方法像第 14 章描述的一样。
```html ```html
<canvas width="600" height="300"></canvas> <canvas width="600" height="300"></canvas>
<script> <script>
var cx = document.querySelector("canvas").getContext("2d"); let cx = document.querySelector("canvas").getContext("2d");
var total = results.reduce(function(sum, choice) { let total = results
return sum + choice.count; .reduce((sum, {count}) => sum + count, 0);
}, 0); let currentAngle = -0.5 * Math.PI;
let centerX = 300, centerY = 150;
var currentAngle = -0.5 * Math.PI;
var centerX = 300, centerY = 150;
// Add code to draw the slice labels in this loop. // Add code to draw the slice labels in this loop.
results.forEach(function(result) { results.forEach(function(result) {
var sliceAngle = (result.count / total) * 2 * Math.PI; for (let result of results) {
cx.beginPath(); let sliceAngle = (result.count / total) * 2 * Math.PI;
cx.arc(centerX, centerY, 100, cx.arc(centerX, centerY, 100,
currentAngle, currentAngle + sliceAngle); currentAngle, currentAngle + sliceAngle);
currentAngle += sliceAngle; currentAngle += sliceAngle;
cx.lineTo(centerX, centerY); cx.lineTo(centerX, centerY);
cx.fillStyle = result.color; cx.fillStyle = result.color;
cx.fill(); cx.fill();
}); }
</script> </script>
``` ```
#### 16.14.3 弹力球 ### 16.14.3 弹力球
使用在第13章和第15章出现的requestAnimationFrame方法画出一个装有弹力球的盒子。这个球以匀速运动并且当撞到盒子的边缘的时候反弹。 使用在第 14 章和第 16 章出现的requestAnimationFrame方法画出一个装有弹力球的盒子。这个球以匀速运动并且当撞到盒子的边缘的时候反弹。
```html ```html
<canvas width="400" height="400"></canvas> <canvas width="400" height="400"></canvas>
<script> <script>
var cx = document.querySelector("canvas").getContext("2d"); let cx = document.querySelector("canvas").getContext("2d");
var lastTime = null; let lastTime = null;
function frame(time) { function frame(time) {
if (lastTime != null) if (lastTime != null) {
updateAnimation(Math.min(100, time - lastTime) / 1000); updateAnimation(Math.min(100, time - lastTime) / 1000);
}
lastTime = time; lastTime = time;
requestAnimationFrame(frame); requestAnimationFrame(frame);
} }
@ -716,9 +718,9 @@ SVG与HTML都会构建一个新的数据结构DOM来代表图片对象。
</script> </script>
``` ```
#### 16.14.4 预处理镜像 ### 16.14.4 预处理镜像
当进行图形变换时,绘制位图图像会很慢。对于矢量图形,这种效果并不明显,因为只有一小部分点(比如,圆的中心)需要被变换,之后绘制图形可以跟正常情况一样。对一个位图图像,每个像素的位置必须被变换,并且即便浏览器可以在未来以一种更加高效科学的方法绘制,目前绘制位图会产生一种在时间上可度量的复杂度提升 当进行图形变换时,绘制位图图像会很慢。每个像素的位置和大小都必须进行变换,尽管将来浏览器可能会更加聪明,但这会导致绘制位图所需的时间显着增加
在一个像我们这样的只绘制一个简单的子画面图像变换的游戏中,这个不是问题。但是如果我们需要绘制成百上千的角色或者爆炸产生的旋转粒子时,这将会成为一个问题。 在一个像我们这样的只绘制一个简单的子画面图像变换的游戏中,这个不是问题。但是如果我们需要绘制成百上千的角色或者爆炸产生的旋转粒子时,这将会成为一个问题。