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-11 18:44:09 +08:00
parent 4862c9cb9c
commit 261ea02194

155
14.md
View File

@ -8,7 +8,7 @@
JavaScript在其沙箱中提供了将文本转换成文档对象模型的功能。它是你可以读取或者修改的数据结构。模型是一个所见即所得的数据结构改变模型会使得屏幕上的页面产生相应变化。
### 13.1 文档结构
## 文档结构
你可以将HTML文件想象成一系列嵌套的箱子。诸如<body></body>之类的标签会将其他标签包围起来,而包含在内部的标签也可以包含其他的标签和文本。这里给出上一章中已经介绍过的示例文件。
@ -35,7 +35,7 @@ JavaScript在其沙箱中提供了将文本转换成文档对象模型的功能
我们可以通过全局绑定document来访问这些对象。该对象的documentElement属性引用了表示<html>标签的对象。由于每个 HTML 文档都有一个头部和一个主体,它还具有`head``body`属性,指向这些元素。
### 13.2 
## 树
回想一下第12章中提到的语法树。其结构与浏览器文档的结构极为相似。每个节点可以使用children引用其他节点而每个子节点又有各自的children。其形状是一种典型的嵌套结构每个元素可以包含与其自身相似的子元素。
@ -55,7 +55,7 @@ DOM中也是一样。元素表示 HTML 标签)的节点用于确定文档
叶子节点是文本节点,而箭头则指出了节点之间的父子关系。
### 13.3 标准
## 标准
并非只有JavaScript会使用数字代码来表示节点类型。本章随后将会展示其他的DOM接口你可能会觉得这些接口有些奇怪。这是因为DOM并不是为JavaScript而设计的它尝试成为一组语言中立的接口确保也可用于其他系统中不只是HTML还有XML。XML是一种通用数据格式语法与HTML相近。
@ -67,7 +67,7 @@ DOM中也是一样。元素表示 HTML 标签)的节点用于确定文档
但这些问题并非无法改善。因为JavaScript允许我们构建自己的抽象可以设计改进方式来表达您正在执行的操作。 许多用于浏览器编程的库都附带这些工具。
### 13.4 通过树结构访问节点
## 沿着树移动
DOM节点包含了许多指向相邻节点的链接。下面的图表展示了这一点。
@ -103,7 +103,7 @@ console.log(talksAbout(document.body, "book"));
文本节点的`nodeValue`属性保存它所表示的文本字符串。
### 13.5 查找元素
## 查找元素
使用父节点、子节点和兄弟节点之间的连接遍历节点确实非常实用。但是如果我们只想查找文档中的特定节点那么从document.body开始盲目沿着硬编码的链接路径查找节点并非良策。如果程序通过树结构定位节点就需要依赖于文档的具体结构而文档结构随后可能发生变化。另一个复杂的因素是DOM会为不同节点之间的空白字符创建对应的文本节点。例如示例文档中的body标签不止包含3个孩子<h1>和两个<p>元素其实包含7个孩子这三个节点、三个节点前后的空格、以及元素之间的空格。
@ -130,7 +130,7 @@ console.log(link.href);
第三个类似的方法是getElementsByClassName它与getElementsByTagName类似会搜索元素节点的内容并获取所有包含特定class属性的元素。
### 13.6 修改文档
## 修改文档
几乎所有DOM数据结构中的元素都可以被修改。文档树的形状可以通过改变父子关系来修改。 节点的`remove`方法将它们从当前父节点中移除。appendChild方法可以添加子节点并将其放置在子节点列表末尾而insertBefore则将第一个参数表示的节点插入到第二个参数表示的节点前面。
@ -149,7 +149,7 @@ console.log(link.href);
replaceChild方法用于将一个子节点替换为另一个子节点。该方法接受两个参数第一个参数是新节点第二个参数是待替换的节点。待替换的节点必须是该方法调用者的子节点。这里需要注意replaceChild和insertBefore都将新节点作为第一个参数。
### 13.7 创建节点
## 创建节点
假设我们要编写一个脚本,将文档中的所有图像(`<img>`标签)替换为其`alt`属性中的文本,该文本指定了图像的文字替代表示。
@ -220,7 +220,7 @@ console.log(array.map(s => s.toUpperCase()));
</script>
```
### 13.8 属性
## 属性
我们可以通过元素的DOM对象的同名属性去访问元素的某些属性比如链接的href属性。这仅限于最常用的标准属性。
@ -244,7 +244,7 @@ HTML允许你在节点上设定任何属性。这一特性非常有用因为
这里有一个常用的属性class。该属性是JavaScript中的保留字。因为某些历史原因某些旧版本的JavaScript实现无法处理和关键字或保留字同名的属性访问class的属性名为className。你也可以使用getAttribute和setAttribute方法使用其实际名称“class”来访问该属性。
### 13.9 布局
## 布局
你可能已经注意到不同类型的元素有不同的布局。某些元素,比如段落(&lt;p&gt;)和标题(&lt;h1&gt;会占据整个文档的宽度并且在独立的一行中渲染。这些元素被称为块Block元素。其他的元素比如链接&lt;a&gt;&lt;strong&gt;元素则与周围文本在同一行中渲染。这类元素我们称之为内联Inline元素。
@ -303,9 +303,9 @@ getBoundingClientRect方法是获取屏幕中某个元素精确位置的最有
</script>
```
### 13.10 样式
## 样式
我们看到了不同的HTML元素会有不同的显示效果。一些元素显示为块,一些则是以内联方式显示。我们还可以添加一些样式,比如使用&lt;strong&gt;加粗内容,或使用&lt;a&gt;使内容变成蓝色,并添加下划线。
我们看到了不同的HTML元素的绘制是不同的。一些元素显示为块,一些则是以内联方式显示。我们还可以添加一些样式,比如使用&lt;strong&gt;加粗内容,或使用&lt;a&gt;使内容变成蓝色,并添加下划线。
&lt;img&gt;标签显示图片的方式或点击标签&lt;a&gt;时跳转的链接都和元素类型紧密相关。但元素的默认样式比如文本的颜色、是否有下划线都是可以改变的。这里给出使用style属性的示例。
@ -316,7 +316,7 @@ getBoundingClientRect方法是获取屏幕中某个元素精确位置的最有
样式属性可以包含一个或多个声明格式为属性比如color后跟着一个冒号和一个值比如green。当包含更多声明时不同属性之间必须使用分号分隔比如“colorredbordernone”。
样式会受到很多因素的影响。例如display属性控制一个元素是否显示为块元素或内联元素。
文档的很多方面会受到样式的影响。例如display属性控制一个元素是否显示为块元素或内联元素。
```html
This text is displayed <strong>inline</strong>,
@ -324,17 +324,17 @@ This text is displayed <strong>inline</strong>,
<strong style="display: none">not at all</strong>.
```
标签block会结束其所在的那一行因为块元素是不会和周围文本内联显示的。最后一个标签完全不会显示出来因为displaynone会阻止一个元素呈现在屏幕上。这是隐藏元素的一种方式。更好的方式是将其从文档中完全移除因为后将其放回去是一件很简单的事情。
标签block会结束其所在的那一行因为块元素是不会和周围文本内联显示的。最后一个标签完全不会显示出来因为displaynone会阻止一个元素呈现在屏幕上。这是隐藏元素的一种方式。更好的方式是将其从文档中完全移除因为后将其放回去是一件很简单的事情。
JavaScript代码可以通过节点的style属性操作元素的样式。该属性保存了一个对象对象中存储了所有可能的样式属性这些属性的值是字符串我们可以把字符串写入属性修改某些方面的元素样式。
JavaScript代码可以通过元素的style属性操作元素的样式。该属性保存了一个对象对象中存储了所有可能的样式属性这些属性的值是字符串我们可以把字符串写入属性修改某些方面的元素样式。
```html
<p id="para" style="color: purple">
Pretty text
Nice text
</p>
<script>
var para = document.getElementById("para");
let para = document.getElementById("para");
console.log(para.style.color);
para.style.color = "magenta";
</script>
@ -342,7 +342,7 @@ JavaScript代码可以通过节点的style属性操作元素的样式。该属
一些样式属性名包含破折号比如font-family。由于这些属性的命名不适合在JavaScript中使用你必须写成style[“font-family”]因此在JavaScript中样式对象中的属性名都移除了破折号并将破折号之后的字母大写style.fontFamily
### 13.11 层叠样式
## 层叠样式
我们把HTML的样式化系统称为CSS即层叠样式表Cascading Style Sheets。样式表是一系列规则指出如何为文档中元素添加样式。可以在&lt;style&gt;标签中写入CSS。
@ -356,9 +356,9 @@ JavaScript代码可以通过节点的style属性操作元素的样式。该属
<p>Now <strong>strong text</strong> is italic and gray.</p>
```
所谓层叠指的是将多条规则组合起来产生元素的最终样式。在上面的示例中,&lt;strong&gt;标签的默认样式font-weightbold会被&lt;style&gt;标签中的规则覆盖,并为&lt;strong&gt;标签样式添加font-style和color属性。
所谓层叠指的是将多条规则组合起来产生元素的最终样式。在示例中,&lt;strong&gt;标签的默认样式font-weightbold会被&lt;style&gt;标签中的规则覆盖,并为&lt;strong&gt;标签样式添加font-style和color属性。
当多条规则重复定义同一属性时,最近的规则会拥有最高的优先级。因此如果&lt;style&gt;标签中的规则包含font-weightnormal与默认的font-weight规则冲突那么文本将会显示为普通样式而非粗体。属性style中的样式会直接作用于节点而且往往拥有最高优先级。
当多条规则重复定义同一属性时,最近的规则会拥有最高的优先级。因此如果&lt;style&gt;标签中的规则包含font-weightnormal违背了默认的font-weight规则那么文本将会显示为普通样式而非粗体。属性style中的样式会直接作用于节点而且往往拥有最高优先级。
我们可以在CSS规则中使用标签名来定位标签。规则.abc指的是所有class属性中包含“abc”的元素。规则#xyz作用于id属性为“xyz”应当在文档中唯一存在的元素。
@ -371,17 +371,17 @@ JavaScript代码可以通过节点的style属性操作元素的样式。该属
background: blue;
color: white;
}
/* p elements, with classes a and b, and id main */
p.a.b#main {
/* p elements with id main and with classes a and b */
p#main.a.b {
margin-bottom: 20px;
}
```
优先级规则(偏向于最近定义的规则)只有在规则特殊性相同的情况下有效。规则的特殊性用于衡量该规则描述匹配元素时的准确性。特殊性取决于规则中的元素数量和类型tag、class或id。例如目标规则p.a比目标规则p或.a更具体因此有更高优先级。
优先级规则偏向于最近定义的规则,仅在规则特殊性相同时适用。规则的特殊性用于衡量该规则描述匹配元素时的准确性。特殊性取决于规则中的元素数量和类型tag、class或id。例如目标规则p.a比目标规则p或.a更具体因此有更高优先级。
p&gt;a{….}这种写法将样式作用于&lt;p&gt;标签的直系孩子。类似的p a{…}应用于所有的&lt;p&gt;标签中的&lt;a&gt;标签,无论是否是直系孩子。
### 13.12 查询选择器
## 查询选择器
本书不会使用太多样式表。尽管理解样式表对浏览器程序设计至关重要,想要正确解释所有浏览器支持的属性及其使用方式,可能需要两到三本书才行。
@ -412,13 +412,13 @@ document对象和元素节点中都定义了querySelectorAll方法该方法
</script>
```
与getElementsByTagName这类方法不同由querySelectorAll返回的对象不是动态变更的。修改文档时其内容不会被修改。
`getElementsByTagName`这类方法不同,由`querySelectorAll`返回的对象不是动态变更的。修改文档时其内容不会被修改。但它仍然不是一个真正的数组,所以如果你打算将其看做真的数组,你仍然需要调用`Array.from`
querySelector方法没有All与querySelectorAll作用相似。如果只想寻找某一个特殊元素该方法非常有用。该方法只返回第一个匹配元素,如果不存在则返回null。
querySelector方法没有All与querySelectorAll作用相似。如果只想寻找某一个特殊元素该方法非常有用。该方法只返回第一个匹配的元素,如果没有匹配的元素则返回null。
### 13.13 位置与动画
## 位置与动画
position样式属性是一种强大的布局方法。默认情况下该属性值为static表示元素处于文档中的默认位置。若该属性设置为relative该元素在文档中依然占据空间但此时其top和left样式属性则是相对于默认位置的偏移。若position设置为absolute会将元素从默认文档流中移除该元素将不再占据空间而会与其他元素重叠。其top和left属性则是相对其最近的封闭元素的偏移其中position属性的值不是static。如果没有任何封闭元素存在则是相对于整个文档的偏移。
position样式属性是一种强大的布局方法。默认情况下该属性值为static表示元素处于文档中的默认位置。若该属性设置为relative该元素在文档中依然占据空间但此时其top和left样式属性则是相对于常规位置的偏移。若position设置为absolute会将元素从默认文档流中移除该元素将不再占据空间而会与其他元素重叠。其top和left属性则是相对其最近的封闭元素的偏移其中position属性的值不是static。如果没有任何封闭元素存在则是相对于整个文档的偏移。
我们可以使用该属性创建一个动画。下面的文档用于显示一幅猫的图片,该图片会沿着椭圆轨迹移动。
@ -427,59 +427,62 @@ position样式属性是一种强大的布局方法。默认情况下该属性
<img src="img/cat.png" style="position: relative">
</p>
<script>
var cat = document.querySelector("img");
var angle = 0, lastTime = null;
function animate(time) {
if (lastTime != null)
let cat = document.querySelector("img");
let angle = Math.PI / 2;
function animate(time, lastTime) {
if (lastTime != null) {
angle += (time - lastTime) * 0.001;
}
lastTime = time;
cat.style.top = (Math.sin(angle) * 20) + "px";
cat.style.left = (Math.cos(angle) * 200) + "px";
requestAnimationFrame(animate);
requestAnimationFrame(newTime => animate(newTime, time));
}
requestAnimationFrame(animate);
</script>
```
图像在页面中央position为relative。为了移动这只猫我们需要不断更新图像的top和left样式。
我们的图像在页面中央position为relative。为了移动这只猫我们需要不断更新图像的top和left样式。
脚本使用request-AnimationFrame在每次浏览器准备重绘屏幕时调用animate函数。animate函数再次调用requestAnimationFrame以准备下一次更新。当浏览器窗口或标签激活时更新频率大概为60次每秒这种频率可以生成美观的动画。
若我们只是在循环中更新DOM页面会静止不动页面上也不会显示任何东西。浏览器不会在执行JavaScript程序时刷新显示内容也不允许页面上的任何交互。这就是我们需要requestAnimationFrame的原因该函数用于告知浏览器JavaScript程序目前已经完成工作因此浏览器可以继续执行其他任务比如刷新屏幕响应用户动作。
我们将动画生成函数作为参数传递给requestAnimationFrame。该函数比较现在的时间和上一次时间lastTime绑定确保每一毫秒猫的移动是稳定的,而且动画是圆滑的。如果仅仅每次走几步,猫的动作可能略显迟钝,例如,另一个在相同电脑上的繁重任务可能使得该函数零点几秒之后才会运行一次。
我们将动画生成函数作为参数传递给requestAnimationFrame。为了确保每一毫秒猫的移动是稳定的,而且动画是圆滑的,它基于一个速度,角度以这个速度改变这一次与上一次函数运行的差。如果仅仅每次走几步,猫的动作可能略显迟钝,例如,另一个在相同电脑上的繁重任务可能使得该函数零点几秒之后才会运行一次。
我们使用三角函数Math.cos和Math.sin来使猫沿着圆弧移动。你可能不太熟悉这些计算这些计算是本书第一次提及,因此我在这里对这几个计算进行一个大致的介绍
我们使用三角函数Math.cos和Math.sin来使猫沿着圆弧移动。你可能不太熟悉这些计算我在这里简要介绍它们,因为你会在这本书中偶尔遇到
Math.cos和Math.sin非常实用我们可以利用一个1单元的弧度计算出以点00为圆心的圆上特定点的位置。两个函数都将参数解释为圆上的一个位置0表示圆上最右侧那个点一直逆时针递增到2π大概是6.28刚刚走过整个圆。Math.cos可以计算出圆上某一点对应的x坐标而Math.sin则计算出y坐标。超过2π或小于0的位置或角度都是合法的。因为弧度是循环重复的a+2π与a的角度相同。
Math.cos和Math.sin非常实用我们可以利用一个1个弧度计算出以点00为圆心的圆上特定点的位置。两个函数都将参数解释为圆上的一个位置0表示圆上最右侧那个点一直逆时针递增到2π大概是6.28刚刚走过整个圆。Math.cos可以计算出圆上某一点对应的x坐标而Math.sin则计算出y坐标。超过2π或小于0的位置或角度都是合法的。因为弧度是循环重复的a+2π与a的角度相同。
用于测量角度的单位称为弧度 - 一个完整的圆弧是2π个弧度类似于以角度度量时的360度。 常量π在JavaScript中为`Math.PI`
![](../Images/00407.jpeg)
猫的动画代码保存了一个名为angle的计数器该绑定记录猫在圆上的角度而且每当调用animate函数时该计数器的值与流逝的时间成比例递增。我们接着使用这个角度来计算图像元素的当前位置。top样式是Math.sin的结果乘以20表示圆中的垂直弧度。left样式是Math.cos的结果与200的乘积因此圆的宽度大于其高度导致最后猫会沿着椭圆轨迹移动。
猫的动画代码保存了一个名为angle的计数器该绑定记录猫在圆上的角度而且每当调用animate函数时增加该计数器的值。我们接着使用这个角度来计算图像元素的当前位置。top样式是Math.sin的结果乘以20表示圆中的垂直弧度。left样式是Math.cos的结果与200的乘积因此圆的宽度大于其高度导致最后猫会沿着椭圆轨迹移动。
这里需要注意的是样式的值一般需要指定单位。本例中我们在数字后添加“px”来告知浏览器以像素为计算单位而非厘米“ems”或其他单位。我们很容易遗漏这个单位。如果我们没有为样式中的数字加上单位浏览器最后会忽略掉该样式除非数字是0在这种情况下使用什么单位其结果都是一样的。
### 13.14 本章小结
## 本章小结
JavaScript程序可以通过名为DOM的数据结构查看并修改浏览器中显示的文档。该数据结构描述了浏览器文档模型而JavaScript程序可以通过修改该数据结构来修改浏览器展示的文档。
JavaScript程序可以通过名为DOM的数据结构查看并修改浏览器中显示的文档。该数据结构描述了浏览器文档模型而JavaScript程序可以通过修改该数据结构来修改浏览器展示的文档。
DOM的组织就像树一样DOM根据文档结构来层次化地排布元素。描述元素的对象包含很多属性比如parentNode和childNodes这两个属性可以用来遍历DOM树。
我们可以通过样式来改变文档的显示方式可以直接在节点上附上样式也可以编写匹配节点的规则。样式包含许多不同的属性比如color和display。JavaScript可以直接通过节点的style属性操作元素的样式。
我们可以通过样式来改变文档的显示方式可以直接在节点上附上样式也可以编写匹配节点的规则。样式包含许多不同的属性比如color和display。JavaScript代码可以直接通过节点的style属性操作元素的样式。
### 13.15 习题
## 习题
#### 13.15.1 创建一张表
### 创建一张表
我们在第6章中使用纯文本来构建表格。HTML使得表格排版更加容易。我们可以使用下面的标签结构来构建HTML表格。
HTML 表格使用以下标签结构构建:
```html
<table>
<tr>
<th>name</th>
<th>height</th>
<th>country</th>
<th>place</th>
</tr>
<tr>
<td>Kilimanjaro</td>
@ -491,36 +494,39 @@ DOM的组织就像树一样DOM根据文档结构来层次化地排布元素
&lt;table&gt;标签中,每一行包含一个&lt;tr&gt;标签。&lt;tr&gt;标签内部则是单元格元素,分为表头(&lt;th&gt;)和常规单元格(&lt;td&gt;)。
我们这里使用第6章中已经使用过的源数据源数据存储在沙箱的MOUNTAINS绑定中。你也可以从网站上下载[http://eloquentjavascript.net/code/](http://eloquentjavascript.net/code/))数据
给定一个山的数据集,一个包含`name``height``place`属性的对象数组为枚举对象的表格生成DOM结构。 每个键应该有一列,每个对象有一行,外加一个顶部带有`<th>`元素的标题行,列出列名
编写一个函数buildTable调用者指定一个对象数组数组中每个对象都包含相同的一组属性该函数根据数组构建出表示表格的DOM结构。表格应该以属性名称作为表头表头使用&lt;th&gt;元素包围,每一行代表数组中的一个对象,其属性值存放在&lt;td&gt;元素中
编写这个程序,以便通过获取数据中第一个对象的属性名称,从对象自动产生列
Object.keys函数返回某个对象所有属性名称的数组读者可能会在程序中用到
将所得表格添加到`id`属性为`"mountains"`的元素,以便它在文档中可见
当你完成基本功能将元素的style.textAlign属性设置为right将单元格中的数字右对齐。
当你完成后将元素的style.textAlign属性设置为right包含数值的单元格右对齐。
```html
<style>
/* Defines a cleaner look for tables */
table { border-collapse: collapse; }
td, th { border: 1px solid black; padding: 3px 8px; }
th { text-align: left; }
</style>
<h1>Mountains</h1>
<div id="mountains"></div>
<script>
function buildTable(data) {
// Your code here.
}
const MOUNTAINS = [
{name: "Kilimanjaro", height: 5895, place: "Tanzania"},
{name: "Everest", height: 8848, place: "Nepal"},
{name: "Mount Fuji", height: 3776, place: "Japan"},
{name: "Vaalserberg", height: 323, place: "Netherlands"},
{name: "Denali", height: 6168, place: "United States"},
{name: "Popocatepetl", height: 5465, place: "Mexico"},
{name: "Mont Blanc", height: 4808, place: "Italy/France"}
];
document.body.appendChild(buildTable(MOUNTAINS));
// Your code here
</script>
```
#### 13.15.2 通过标签名获取元素
### 通过标签名获取元素
方法getElementsByTagName返回带有特定标签名称的所有子元素。实现该函数,这里注意是函数不是方法。该函数的参数是一个节点和字符串(标签名称),并返回一个数组,该数组包含所有带有特定标签名称的所有后代元素节点。
document.getElementsByTagName方法返回带有特定标签名称的所有子元素。实现该函数,这里注意是函数不是方法。该函数的参数是一个节点和字符串(标签名称),并返回一个数组,该数组包含所有带有特定标签名称的所有后代元素节点。
你可以使用tagName属性从DOM元素中获取标签名称。但这里需要注意使用tagName获取的标签名称是全大写形式。可以使用字符串的toLowerCase或toUpperCase来解决这个问题。
你可以使用nodeName属性从DOM元素中获取标签名称。但这里需要注意使用tagName获取的标签名称是全大写形式。可以使用字符串的toLowerCase或toUpperCase来解决这个问题。
```html
<h1>Heading with a <span>span</span> element.</h1>
@ -536,27 +542,42 @@ Object.keys函数返回某个对象所有属性名称的数组读者可能会
// → 1
console.log(byTagName(document.body, "span").length);
// → 3
var para = document.querySelector("p");
let para = document.querySelector("p");
console.log(byTagName(para, "span").length);
// → 2
</script>
```
#### 13.15.3 猫的帽子
### 猫的帽子
扩展一下之前定义的用来绘制猫的动画函数,让猫和它的帽子沿着椭圆形轨道边(帽子永远在猫的对面)移动。
你也可以尝试让帽子环绕着猫移动,或修改成其他有趣的动画。
为了便于定位多个对象一个比较好的方法是使用绝对absolute定位。这就意味着top和left属性是相对于文档左上角的坐标。你可以简单地在坐标上加上一个固定数字以避免出现负的坐标。
为了便于定位多个对象一个比较好的方法是使用绝对absolute定位。这就意味着top和left属性是相对于文档左上角的坐标。你可以简单地在坐标上加上一个固定数字以避免出现负的坐标,它会使图像移出可见页面
```html
<style>body { min-height: 200px }</style>
<img src="img/cat.png" id="cat" style="position: absolute">
<img src="img/hat.png" id="hat" style="position: absolute">
<script>
var cat = document.querySelector("#cat");
var hat = document.querySelector("#hat");
// Your code here.
let cat = document.querySelector("#cat");
let hat = document.querySelector("#hat");
let angle = 0;
let lastTime = null;
function animate(time) {
if (lastTime != null) angle += (time - lastTime) * 0.001;
lastTime = time;
cat.style.top = (Math.sin(angle) * 40 + 40) + "px";
cat.style.left = (Math.cos(angle) * 200 + 230) + "px";
// Your extensions here.
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
</script>
```