aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/learn/javascript/client-side_web_apis
diff options
context:
space:
mode:
authorPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
committerPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
commit33058f2b292b3a581333bdfb21b8f671898c5060 (patch)
tree51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/learn/javascript/client-side_web_apis
parent8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff)
downloadtranslated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip
initial commit
Diffstat (limited to 'files/zh-cn/learn/javascript/client-side_web_apis')
-rw-r--r--files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.html798
-rw-r--r--files/zh-cn/learn/javascript/client-side_web_apis/drawing_graphics/index.html881
-rw-r--r--files/zh-cn/learn/javascript/client-side_web_apis/fetching_data/index.html395
-rw-r--r--files/zh-cn/learn/javascript/client-side_web_apis/index.html47
-rw-r--r--files/zh-cn/learn/javascript/client-side_web_apis/introduction/index.html275
-rw-r--r--files/zh-cn/learn/javascript/client-side_web_apis/manipulating_documents/index.html300
-rw-r--r--files/zh-cn/learn/javascript/client-side_web_apis/third_party_apis/index.html484
-rw-r--r--files/zh-cn/learn/javascript/client-side_web_apis/video_and_audio_apis/index.html499
8 files changed, 3679 insertions, 0 deletions
diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.html b/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.html
new file mode 100644
index 0000000000..8124baee4e
--- /dev/null
+++ b/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.html
@@ -0,0 +1,798 @@
+---
+title: 客户端存储
+slug: Learn/JavaScript/Client-side_web_APIs/Client-side_storage
+tags:
+ - API
+ - IndexedDB
+ - JavaScript
+ - 初学者
+ - 存储
+ - 文章
+translation_of: Learn/JavaScript/Client-side_web_APIs/Client-side_storage
+---
+<p>{{LearnSidebar}}</p>
+
+<div>{{PreviousMenu("Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</div>
+
+<div></div>
+
+<p class="summary">现代web浏览器提供了很多在用户电脑web客户端存放数据的方法 — 只要用户的允许 — 可以在它需要的时候被重新获得。这样能让你存留的数据长时间保存, 保存站点和文档在离线情况下使用, 保留你对其站点的个性化配置等等。本篇文章只解释它们工作的一些很基础的部分。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">Prerequisites:</th>
+ <td>JavaScript 基础 (查看 <a href="/en-US/docs/Learn/JavaScript/First_steps">第一步</a>, <a href="/en-US/docs/Learn/JavaScript/Building_blocks">构建的块</a>, <a href="/en-US/docs/Learn/JavaScript/Objects">JavaScript 对象</a>), <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction"> 基础的客户端 API</a></td>
+ </tr>
+ <tr>
+ <th scope="row">Objective:</th>
+ <td>学习如何使用客户端存储 API 来存储应用数据。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="客户端存储">客户端存储?</h2>
+
+<p>在其他的MDN学习中我们已经讨论过 静态网站(<a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview#Static_sites">static sites</a>) 和动态网站( <a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview#Dynamic_sites">dynamic sites</a>)的区别。 大多数现代的web站点是动态的— 它们在服务端使用各种类型的数据库来存储数据(服务端存储), 之后通过运行服务端( <a href="/en-US/docs/Learn/Server-side">server-side</a>) 代码来重新获取需要的数据,把其数据插入到静态页面的模板中,并且生成出HTML渲染到用户浏览上。</p>
+
+<p>客户端存储以相同的原理工作,但是在使用上有一些不同。它是由 JavaScript APIs 组成的因此允许你在客户端存储数据 (比如在用户的机器上),而且可以在需要的时候重新取得需要的数据。这有很多明显的用处,比如:</p>
+
+<ul>
+ <li>个性化网站偏好(比如显示一个用户选择的窗口小部件,颜色主题,或者字体)。</li>
+ <li>保存之前的站点行为 (比如从先前的session中获取购物车中的内容, 记住用户是否之前已经登陆过)。</li>
+ <li>本地化保存数据和静态资源可以使一个站点更快(至少让资源变少)的下载, 甚至可以在网络失去链接的时候变得暂时可用。</li>
+ <li>保存web已经生产的文档可以在离线状态下访问。</li>
+</ul>
+
+<p>通常客户端和服务端存储是结合在一起使用的。例如,你可以从数据库中下载一个由网络游戏或音乐播放器应用程序使用的音乐文件,将它们存储在客户端数据库中,并按需要播放它们。用户只需下载音乐文件一次——在随后的访问中,它们将从数据库中检索。</p>
+
+<div class="note">
+<p><strong>注意:</strong>使用客户端存储 API 可以存储的数据量是有限的(可能是每个API单独的和累积的总量);具体的数量限制取决于浏览器,也可能基于用户设置。有关更多信息,获取更多信息,请参考<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria">浏览器存储限制和清理标准</a>。</p>
+</div>
+
+<h3 id="传统方法:cookies">传统方法:cookies</h3>
+
+<p>客户端存储的概念已经存在很长一段时间了。从早期的网络时代开始,网站就使用 <a href="/zh-CN/docs/Web/HTTP/Cookies">cookies</a> 来存储信息,以在网站上提供个性化的用户体验。它们是网络上最早最常用的客户端存储形式。<br>
+ 因为在那个年代,有许多问题——无论是从技术上的还是用户体验的角度——都是困扰着 cookies 的问题。这些问题非常重要,以至于当第一次访问一个网站时,欧洲居民会收到消息,告诉他们是否会使用 cookies 来存储关于他们的数据,而这是由一项被称为<a href="/zh-CN/docs/Web/HTTP/Cookies#%E6%AC%A7%E7%9B%9FCookie%E6%8C%87%E4%BB%A4">欧盟 Cookie 条例</a>的欧盟法律导致的。</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/15734/cookies-notice.png" style="display: block; margin: 0 auto;"></p>
+
+<p>由于这些原因,我们不会在本文中教你如何使用cookie。毕竟它过时、存在各种<a href="/zh-CN/docs/Web/HTTP/Cookies#安全">安全问题</a>,而且无法存储复杂数据,而且有更好的、更现代的方法可以在用户的计算机上存储种类更广泛的数据。<br>
+ cookie的唯一优势是它们得到了非常旧的浏览器的支持,所以如果您的项目需要支持已经过时的浏览器(比如 Internet Explorer 8 或更早的浏览器),cookie可能仍然有用,但是对于大多数项目(很明显不包括本站)来说,您不需要再使用它们了。其实cookie也没什么好说的,<code><a href="/zh-CN/docs/Web/API/Document/cookie">document.cookie</a></code>一把梭就完事了。</p>
+
+<div class="note">
+<p>为什么仍然有新创建的站点使用 cookies?这主要是因为开发人员的习惯,使用了仍然使用cookies的旧库,以及存在许多web站点,提供了过时的参考和培训材料来学习如何存储数据。</p>
+</div>
+
+<h3 id="新流派:Web_Storage_和_IndexedDB">新流派:Web Storage 和 IndexedDB</h3>
+
+<p>现代浏览器有比使用 cookies 更简单、更有效的存储客户端数据的 API。</p>
+
+<ul>
+ <li><a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a> 提供了一种非常简单的语法,用于存储和检索较小的、由名称和相应值组成的数据项。当您只需要存储一些简单的数据时,比如用户的名字,用户是否登录,屏幕背景使用了什么颜色等等,这是非常有用的。</li>
+ <li><a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a> 为浏览器提供了一个完整的数据库系统来存储复杂的数据。这可以用于存储从完整的用户记录到甚至是复杂的数据类型,如音频或视频文件。</li>
+</ul>
+
+<p>您将在下面了解更多关于这些 API 的信息。</p>
+
+<h3 id="未来:Cache_API">未来:Cache API</h3>
+
+<p>一些现代浏览器支持新的 {{domxref("Cache")}} API。这个API是为存储特定HTTP请求的响应文件而设计的,它对于像存储离线网站文件这样的事情非常有用,这样网站就可以在没有网络连接的情况下使用。缓存通常与 <a href="/en-US/docs/Web/API/Service_Worker_API">Service Worker API</a> 组合使用,尽管不一定非要这么做。<br>
+ Cache 和 Service Workers 的使用是一个高级主题,我们不会在本文中详细讨论它,尽管我们将在下面的  {{anch("离线文件存储")}} 一节中展示一个简单的例子。</p>
+
+<h2 id="存储简单数据_—_web_storage">存储简单数据 — web storage</h2>
+
+<p><a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a> 非常容易使用 — 你只需存储简单的 键名/键值 对数据 (限制为字符串、数字等类型) 并在需要的时候检索其值。</p>
+
+<h3 id="基本语法">基本语法</h3>
+
+<p>让我们来告诉你怎么做:</p>
+
+<ol>
+ <li>
+ <p>第一步,访问 GitHub 上的 <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html">web storage blank template</a> (在新标签页打开此<a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html" rel="noopener"><font>模板</font></a>)。</p>
+ </li>
+ <li>
+ <p>打开你浏览器开发者工具的 JavaScript 控制台。</p>
+ </li>
+ <li>
+ <p>你所有的 web storage 数据都包含在浏览器内两个类似于对象的结构中: {{domxref("Window.sessionStorage", "sessionStorage")}} 和 {{domxref("Window.localStorage", "localStorage")}}。 第一种方法,只要浏览器开着,数据就会一直保存 (关闭浏览器时数据会丢失) ,而第二种会一直保存数据,甚至到浏览器关闭又开启后也是这样。我们将在本文中使用第二种方法,因为它通常更有用。</p>
+ </li>
+ <li>
+ <p>{{domxref("Storage.setItem()")}} 方法允许您在存储中保存一个数据项——它接受两个参数:数据项的名字及其值。试着把它输入到你的JavaScript控制台(如果你愿意的话,可以把它的值改为你自己的名字!)</p>
+
+ <pre class="brush: js notranslate">localStorage.setItem('name','Chris');</pre>
+ </li>
+ <li>
+ <p>{{domxref("Storage.getItem()")}} 方法接受一个参数——你想要检索的数据项的名称——并返回数据项的值。现在将这些代码输入到你的JavaScript控制台:</p>
+
+ <pre class="brush: js notranslate">var myName = localStorage.getItem('name');
+myName</pre>
+
+ <p>在输入第二行时,您应该会看到 <code>myName</code>变量现在包含<code>name</code>数据项的值。</p>
+ </li>
+ <li>
+ <p>{{domxref("Storage.removeItem()")}} 方法接受一个参数——你想要删除的数据项的名称——并从 web storage 中删除该数据项。在您的JavaScript控制台中输入以下几行:</p>
+
+ <pre class="brush: js notranslate">localStorage.removeItem('name');
+var myName = localStorage.getItem('name');
+myName</pre>
+
+ <p>第三行现在应该返回 <code>null</code> —  <code>name</code> 项已经不存在于 web storage 中。</p>
+ </li>
+</ol>
+
+<h3 id="数据会一直存在!">数据会一直存在!</h3>
+
+<p>web storage 的一个关键特性是,数据在不同页面加载时都存在(甚至是当浏览器关闭后,对localStorage的而言)。让我们来看看这个:</p>
+
+<ol>
+ <li>
+ <p>再次打开我们的 Web Storage 空白模板,但是这次你要在不同的浏览器中打开这个教程!这样可以更容易处理。</p>
+ </li>
+ <li>
+ <p>在浏览器的 JavaScript 控制台中输入这几行:</p>
+
+ <pre class="brush: js notranslate">localStorage.setItem('name','Chris');
+var myName = localStorage.getItem('name');
+myName</pre>
+
+ <p>你应该看到 name 数据项返回。</p>
+ </li>
+ <li>
+ <p>现在关掉浏览器再把它打开。</p>
+ </li>
+ <li>
+ <p>再次输入下面几行:</p>
+
+ <pre class="brush: js notranslate">var myName = localStorage.getItem('name');
+myName</pre>
+
+ <p>你应该看到,尽管浏览器已经关闭,然后再次打开,但仍然可以使用该值。</p>
+ </li>
+</ol>
+
+<h3 id="为每个域名分离储存">为每个域名分离储存</h3>
+
+<p>每个域都有一个单独的数据存储区(每个单独的网址都在浏览器中加载). 你 会看到,如果你加载两个网站(例如google.com和amazon.com)并尝试将某个项目存储在一个网站上,该数据项将无法从另一个网站获取。</p>
+
+<p>这是有道理的 - 你可以想象如果网站能够查看彼此的数据,就会出现安全问题!</p>
+
+<h3 id="更复杂的例子">更复杂的例子</h3>
+
+<p><font>让我们通过编写一个简单的工作示例来应用这些新发现的知识,让你了解如何使用网络存储。</font><font>我们的示例将允许你输入一个名称,然后该页面将刷新,以提供个性化问候。</font><font>这种状态也会页面/浏览器重新加载期间保持,因为这个名称存储在Web Storage 中。</font></p>
+
+<p>你可以在 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/web-storage/personal-greeting.html">personal-greeting.html</a> 中找到示例文件 —— 这包含一个具有标题,内容和页脚,以及用于输入您的姓名的表单的简单网站。</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/15735/web-storage-demo.png" style="display: block; margin: 0 auto;"></p>
+
+<p>让我们来构建示例,以便了解它的工作原理。</p>
+
+<ol>
+ <li>
+ <p>首先,在您的计算机上的新目录中创建一个 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/web-storage/personal-greeting.html">personal-greeting.html</a> 文件的副本。</p>
+ </li>
+ <li>
+ <p><font><font>接下来,请注意我们的HTML如何引用一个名为</font></font><code>index.js</code><font><font>的 JavaScript 文件</font></font><font><font>(请参见第40行)。</font><font>我们需要创建它并将 JavaScript 代码写入其中。</font></font><font><font>在与HTML文件相同的目录中</font><font>创建一个</font></font><code>index.js</code><font><font>文件。</font></font> </p>
+ </li>
+ <li>
+ <p><font>我们首先创建对所有需要在此示例中操作的HTML功能的引用 - 我们将它们全部创建为常量,因为这些引用在应用程序的生命周期中不需要更改。</font><font>将以下几行添加到你的 JavaScript 文件中:</font></p>
+
+ <pre class="brush: js notranslate">// 创建所需的常量
+const rememberDiv = document.querySelector('.remember');
+const forgetDiv = document.querySelector('.forget');
+const form = document.querySelector('form');
+const nameInput = document.querySelector('#entername');
+const submitBtn = document.querySelector('#submitname');
+const forgetBtn = document.querySelector('#forgetname');
+
+const h1 = document.querySelector('h1');
+const personalGreeting = document.querySelector('.personal-greeting');</pre>
+ </li>
+ <li>
+ <p><font>接下来,我们需要包含一个小小的事件监听器,以在按下提交按钮时阻止实际的提交表单动作自身,因为这不是我们想要的行为。</font><font>在您之前的代码下添加此代码段:</font> 在你之前的代码后添加这段代码:</p>
+
+ <pre class="brush: js notranslate">// 当按钮按下时阻止表单提交
+form.addEventListener('submit', function(e) {
+ e.preventDefault();
+});</pre>
+ </li>
+ <li>
+ <p><font><font>现在我们需要添加一个事件监听器,当单击“Say hello”按钮时,它的处理函数将会运行。</font><font>这些注释详细解释了每一处都做了什么,但实际上我们在这里获取用户输入到文本输入框中的名字并使用</font></font><code>setItem()</code><font><font>将它保存在网络存储中</font></font><font><font>,然后运行一个名为</font></font><code>nameDisplayCheck()</code><font><font>的函数</font></font><font><font>来处理实际的网站文本的更新。将此添加到代码的底部:</font></font> </p>
+
+ <pre class="brush: js notranslate">// run function when the 'Say hello' button is clicked
+submitBtn.addEventListener('click', function() {
+ // store the entered name in web storage
+ localStorage.setItem('name', nameInput.value);
+ // run nameDisplayCheck() to sort out displaying the
+ // personalized greetings and updating the form display
+ nameDisplayCheck();
+});</pre>
+ </li>
+ <li>
+ <p><font><font>此时,我们还需要一个事件处理程序,以便在单击“</font></font>Forget<font><font>”按钮时运行一个函数——且仅在单击“Say hello”按钮(两种表单状态来回切换)后才显示。在这个功能中,我们</font></font><font><font>使用</font></font><code>removeItem()</code><font><font>从网络存储中</font><font>删除</font><font>项目</font></font><code>name</code><font><font>,然后再次运行</font></font><code>nameDisplayCheck()</code><font><font>以更新显示。</font><font>将其添加到底部:</font></font> </p>
+
+ <pre class="brush: js notranslate">// run function when the 'Forget' button is clicked
+forgetBtn.addEventListener('click', function() {
+ // Remove the stored name from web storage
+ localStorage.removeItem('name');
+ // run nameDisplayCheck() to sort out displaying the
+ // generic greeting again and updating the form display
+ nameDisplayCheck();
+});</pre>
+ </li>
+ <li>
+ <p><font><font>现在是时候定义</font></font><code>nameDisplayCheck()</code><font><font>函数本身了。</font><font>在这里,我们通过使用</font></font><code>localStorage.getItem('name')</code><font><font>作为测试条件</font><font>来检查name 数据项是否已经存储在Web Storage 中</font><font>。</font><font>如果它已被存储,则该调用的返回值为</font></font><code>true</code><font><font>; </font><font>如果没有,它会是</font></font><code>false</code><font><font>。</font><font>如果是</font></font><code>true</code><font><font>,我们会显示个性化问候语,显示表格的“</font></font>forget<font><font>”部分,并隐藏表格的“</font></font>Say hello<font><font>”部分。</font><font>如果是</font></font><code>false</code><font><font>,我们会显示一个通用问候语,并做相反的事。</font><font>再次将下面的代码添到底部:</font></font></p>
+
+ <pre class="brush: js notranslate">// define the nameDisplayCheck() function
+function nameDisplayCheck() {
+ // check whether the 'name' data item is stored in web Storage
+ if(localStorage.getItem('name')) {
+ // If it is, display personalized greeting
+ let name = localStorage.getItem('name');
+ h1.textContent = 'Welcome, ' + name;
+ personalGreeting.textContent = 'Welcome to our website, ' + name + '! We hope you have fun while you are here.';
+ // hide the 'remember' part of the form and show the 'forget' part
+ forgetDiv.style.display = 'block';
+ rememberDiv.style.display = 'none';
+ } else {
+ // if not, display generic greeting
+ h1.textContent = 'Welcome to our website ';
+ personalGreeting.textContent = 'Welcome to our website. We hope you have fun while you are here.';
+ // hide the 'forget' part of the form and show the 'remember' part
+ forgetDiv.style.display = 'none';
+ rememberDiv.style.display = 'block';
+ }
+}</pre>
+ </li>
+ <li>
+ <p><font><font>最后但同样重要的是,我们需要在</font></font><font><font>每次加载页面时</font><font>运行</font></font><code>nameDisplayCheck()</code><font><font>函数。</font><font>如果我们不这样做,那么个性化问候不会在页面重新加载后保持。</font><font>将以下代码添加到代码的底部:</font></font></p>
+
+ <pre class="brush: js notranslate">document.body.onload = nameDisplayCheck;</pre>
+ </li>
+</ol>
+
+<p><font>你的例子完成了 - 做得好!</font><font>现在剩下的就是保存你的代码并在浏览器中测试你的HTML页面。你可以在这里看到我们的</font><a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/personal-greeting.html" rel="noopener"><font><font>完成版本并在线运行</font></font></a><font><font>。</font></font></p>
+
+<div class="note">
+<p><strong>注意</strong>: 在 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API">Using the Web Storage API</a> 中还有一个稍微复杂点儿的示例。</p>
+</div>
+
+<div class="note">
+<p><strong>注意</strong>: 在完成版本的源代码中, <code>&lt;script src="index.js" defer&gt;&lt;/script&gt;</code> 一行里, <code>defer</code> 属性指明<font>在页面加载完成之前,</font>{{htmlelement("script")}}<font>元素的内容</font><font>不会执行。</font></p>
+</div>
+
+<h2 id="存储复杂数据_—_IndexedDB">存储复杂数据 — IndexedDB</h2>
+
+<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API"><font><font>IndexedDB API</font></font></a><font><font>(有时简称 IDB )是可以在浏览器中访问的一个完整的数据库系统,在这里,你可以存储复杂的关系数据。其种类不限于像字符串和数字这样的简单值。你可以在一个</font></font>IndexedDB中<font><font>存储视频,图像和许多其他的内容。</font></font></p>
+
+<p><font>但是,这确实是有代价的:使用IndexedDB要比Web Storage API复杂得多。</font><font>在本节中,我们仅仅只能浅尝辄止地一提它的能力,不过我们会给你足够基础知识以帮助你开始。</font></p>
+
+<h3 id="通过一个笔记存储示例演示">通过一个笔记存储示例演示</h3>
+
+<p>在这里,我们将向您介绍一个示例,该示例允许您在浏览器中存储笔记并随时查看和删除它们,在我们进行时,我们将解释IDB的最基本部分并让您自己构建注释。</p>
+
+<p>这个应用看起来像这样:</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/15744/idb-demo.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p>
+
+<p>每个笔记都有一个标题和一些正文,每个都可以单独编辑。我们将在下面通过的JavaScript代码提供详细的注释,以帮助您了解正在发生的事情。</p>
+
+<h3 id="开始">开始</h3>
+
+<p>1、首先,将 <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/index.html">index.html</a></code>, <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/style.css">style.css</a></code>, 和 <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/index-start.js">index-start.js</a></code> 文件的本地副本放入本地计算机上的新目录中。 </p>
+
+<p>2、浏览这些文件。 您将看到HTML非常简单:具有页眉和页脚的网站,以及包含显示注释的位置的主内容区域,以及用于在数据库中输入新注释的表单。 CSS提供了一些简单的样式,使其更清晰。 JavaScript文件包含五个声明的常量,其中包含对将显示注释的 {{htmlelement("ul")}} 元素的引用,标题和正文  {{htmlelement("input")}} 元素,{{htmlelement("form")}}本身,以及{{htmlelement("button")}}。</p>
+
+<p>3、将您的JavaScript文件重命名为 <code>index.js</code> 。 您现在可以开始向其添加代码了。</p>
+
+<h3 id="数据库初始设置">数据库初始设置</h3>
+
+<p>现在让我们来看看为了建立数据库必须首先要做什么。</p>
+
+<ol>
+ <li>
+ <p>在常量声明下,加入这几行:</p>
+
+ <pre class="brush: js notranslate">// Create an instance of a db object for us to store the open database in
+let db;</pre>
+
+ <p>这里我们声明了一个叫 <code>db</code> 的变量 — 这将在之后被用来存储一个代表数据库的对象。我们将在几个地方使用它,所以我们为了方便使用而在这里把它声明为全局的。</p>
+ </li>
+ <li>
+ <p>接着,在你的代码最后添加如下代码:</p>
+
+ <pre class="brush: js notranslate">window.onload = function() {
+
+};</pre>
+
+ <p>我们将把所有的后续代码写在这个 <code>window.onload</code> 事件处理函数内,这个函数将在window的{{event("load")}}事件被触发时调用,为了确保我们没有在应用完整加载前试图使用IndexedDB功能(如果我们不这么做,它会失败)。</p>
+ </li>
+ <li>
+ <p><font><font>在</font></font><code>window.onload</code><font><font>处理程序内,添加以下内容:</font></font></p>
+
+ <pre class="brush: js notranslate">// Open our database; it is created if it doesn't already exist
+// (see onupgradeneeded below)
+let request = window.indexedDB.open('notes', 1);</pre>
+
+ <p><font><font>此行创建一个</font></font> <code>request</code> <font><font>变量,目的是打开</font></font> <code>notes</code><font><font>数据库的</font></font> <code>1</code><font><font>版本</font></font><font><font>。</font><font>如果<code>notes</code>数据库不存在,则后续代码将为您创建。</font><font>您将在IndexedDB中经常看到此请求模式。</font><font>数据库操作需要时间。</font><font>您不希望在等待结果时挂起浏览器,因此数据库操作是</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/asynchronous" title="异步:异步是指通信环境,其中每个方在方便或可能时而不是立即接收和处理消息。"><font><font>异步的</font></font></a><font><font>,这意味着它们不会立即发生,而是在将来的某个时刻发生,并且在完成后会收到通知。</font></font></p>
+
+ <p><font><font>要在IndexedDB中处理此问题,您需要创建一个请求对象(可以随意命名 - </font></font>命名为<code>request</code>,<font><font>可以表明它的用途)。</font><font>然后,在请求完成或者失败时,使用事件处理程序来运行代码,您将在下面看到这些代码。</font></font></p>
+
+ <div class="note">
+ <p><strong><font><font>注意</font></font></strong><font><font>:版本号很重要。</font><font>如果要升级数据库(例如:更改表结构),则必须使用增加的版本号或者</font></font><code>onupgradeneeded</code><font><font>处理程序</font><font>内指定的不同模式</font><font>(请参阅下文)等</font><font>再次运行代码</font><font>。在这个简单教程中,我们不讨论数据库升级。</font></font></p>
+ </div>
+
+ <ol>
+ <li>
+ <p><font><font>在之前添加的事件处理程序下方添加以下代码 - 在</font></font><code>window.onload</code><font><font>处理程序内:</font></font></p>
+
+ <pre class="brush: js notranslate">// onerror handler signifies that the database didn't open successfully
+request.onerror = function() {
+ console.log('Database failed to open');
+};
+
+// onsuccess handler signifies that the database opened successfully
+request.onsuccess = function() {
+ console.log('Database opened successfully');
+
+ // Store the opened database object in the db variable. This is used a lot below
+ db = request.result;
+
+ // Run the displayData() function to display the notes already in the IDB
+ displayData();
+};</pre>
+
+ <p><font><font>如果系统返回:请求失败,</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBRequest/onerror" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>request.onerror</code></a><font><font>将会运行。</font><font>这将允许你对这个问题做出响应。</font><font>在我们的简单示例中,只是将消息打印到JavaScript控制台。</font></font></p>
+
+ <p>如果系统返回:请求成功,<font><font>表明成功打开数据库</font></font> ,<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBRequest/onsuccess" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>request.onsuccess</code></a><font><font>将会运行。如果是这种情况,则表示已打开数据库的对象在</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBRequest/result" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>request.result</code></a><font><font>属性中</font><font>变为可用</font><font>,从而允许我们操作数据库。</font><font>我们将它存储在</font></font><code>db</code><font><font>我们之前创建</font><font>的</font><font>变量中供以后使用。</font><font>我们还运行一个名为</font></font><code>displayData()</code>的自定义函数<font><font>,它把数据库中的数据显示在</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/ul" title="The HTML &lt;ul> 元素 ( 或 HTML 无序列表元素) 代表多项的无序列表,即无数值排序项的集合,且它们在列表中的顺序是没有意义的。通常情况下,无序列表项的头部可以是几种形式,如一个点,一个圆形或方形。头部的风格并不是在页面的HTML描述定义, 但在其相关的CSS 可以用 list-style-type 属性。"><code>&lt;ul&gt;</code></a><font><font>。</font><font>我们现在运行它,以便在页面加载时显示数据库中已有的注释。</font><font>您将在稍后看到此定义。</font></font></p>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <p><font><font>最后,对于本节,我们可能会添加最重要的事件处理程序来设置数据库:</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBOpenDBRequest/onupgradeneeded" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>request.onupgradeneeded</code></a><font><font>。</font><font>如果尚未设置数据库,或者使用比现有存储数据库更大的版本号打开数据库(执行升级时),则运行此处理程序。</font><font>在上一个处理程序下面添加以下代码:</font></font></p>
+
+ <pre class="brush: js notranslate">// Setup the database tables if this has not already been done
+request.onupgradeneeded = function(e) {
+ // Grab a reference to the opened database
+ let db = e.target.result;
+
+ // Create an objectStore to store our notes in (basically like a single table)
+ // including a auto-incrementing key
+ let objectStore = db.createObjectStore('notes', { keyPath: 'id', autoIncrement:true });
+
+ // Define what data items the objectStore will contain
+ objectStore.createIndex('title', 'title', { unique: false });
+ objectStore.createIndex('body', 'body', { unique: false });
+
+ console.log('Database setup complete');
+};</pre>
+
+ <p><font><font>这是我们定义数据库的模式(结构)的地方; </font><font>也就是说,它包含的列(或字段)集。</font><font>这里我们首先从</font></font><code>e.target.result</code><font><font>(事件目标的</font></font><code>result</code><font><font>属性)中</font><font>获取对现有数据库的引用,该引用</font><font>是</font></font><code>request</code><font><font>对象。</font><font>这相当于</font><font>处理程序</font></font><code>db = request.result;</code><font><font>内部</font><font>的行</font></font><code>onsuccess</code><font><font>,但我们需要在此单独执行此操作,因为</font></font><code>onupgradeneeded</code><font><font>处理程序(如果需要)将在</font></font><code>onsuccess</code><font><font>处理程序</font><font>之前运行</font><font>,这意味着</font></font><code>db</code><font><font>如果我们不这样做</font><font>,该</font><font>值将不可用。</font></font></p>
+
+ <p><font><font>然后</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBDatabase/createObjectStore" title="此方法接受一个参数作为 store 的名称,也接受一个可选的参数对象让你可以定义重要的可选属性。你可以用这个属性唯一的标识此 store 中的每个对象。因为参数是一个标识符,所以 store 中的每一个对象都应有此属性并保证此属性唯一。"><code>IDBDatabase.createObjectStore()</code></a><font><font>,</font><font>我们使用</font><font>在打开的数据库中创建一个新的对象库。</font><font>这相当于传统数据库系统中的单个表。</font><font>我们给它起了名称注释,并且还指定了一个</font></font><code>autoIncrement</code><font><font>名为</font><font>的</font><font>关键字段</font></font><code>id</code><font><font>- 在每个新记录中,这将自动赋予增量值 - 开发人员不需要明确地设置它。</font><font>作为密钥,该</font></font><code>id</code><font><font>字段将用于唯一标识记录,例如删除或显示记录时。</font></font></p>
+
+ <p><font><font>我们还使用以下</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBObjectStore/createIndex" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>IDBObjectStore.createIndex()</code></a><font><font>方法</font><font>创建另外两个索引(字段)</font><font>:( </font></font><code>title</code><font><font>每个音符将包含一个标题),以及</font></font><code>body</code><font><font>(包含音符的正文)。</font></font></p>
+ </li>
+</ol>
+
+<p>因此,通过设置这个简单的数据库模式,当我们开始向数据库添加记录时,每个记录都会沿着这些行表示为一个对象:</p>
+
+<pre class="brush: js notranslate"><span class="message-body-wrapper"><span class="message-flex-body"><span class="devtools-monospace message-body"><span class="objectBox objectBox-object"><span class="objectLeftBrace">{
+ </span><span class="nodeName">title</span><span class="objectEqual">: </span><span class="objectBox objectBox-string">"Buy milk"</span>,
+ <span class="nodeName">body</span><span class="objectEqual">: </span><span class="objectBox objectBox-string">"Need both cows milk and soya."</span>,
+ <span class="nodeName">id</span><span class="objectEqual">: </span><span class="objectBox objectBox-number">8</span></span></span></span></span><span class="message-body-wrapper"><span class="message-flex-body"><span class="devtools-monospace message-body"><span class="objectBox objectBox-object"><span class="objectRightBrace">
+}</span></span></span></span></span></pre>
+
+<h3 id="添加数据到数据库">添加数据到数据库</h3>
+
+<p><font><font>现在让我们看一下如何将记录添加到数据库中。</font><font>这将使用我们页面上的表单完成。</font></font></p>
+
+<p><font><font>在您之前的事件处理程序下面(但仍在</font></font><code>window.onload</code><font><font>处理程序中),添加以下行,该行设置一个</font></font><code>onsubmit</code><font><font>处理程序,</font><font>该</font><font>处理程序运行</font></font><code>addData()</code><font><font>在提交表单时</font><font>调用的函数</font><font>(当</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button" title="HTML &lt;button> 元素表示一个可点击的按钮,可以用在表单或文档其它需要使用简单标准按钮的地方。"><code>&lt;button&gt;</code></a><font><font>按下</font><font>提交时</font><font>导致成功提交表单):</font></font></p>
+
+<pre class="brush: js notranslate">// Create an onsubmit handler so that when the form is submitted the addData() function is run
+form.onsubmit = addData;</pre>
+
+<p><font><font>现在让我们定义一下这个</font></font><code>addData()</code><font><font>功能。</font><font>在上一行下面添加:</font></font></p>
+
+<pre class="brush: js notranslate">// Define the addData() function
+function addData(e) {
+ // prevent default - we don't want the form to submit in the conventional way
+ e.preventDefault();
+
+ // grab the values entered into the form fields and store them in an object ready for being inserted into the DB
+ let newItem = { title: titleInput.value, body: bodyInput.value };
+
+ // open a read/write db transaction, ready for adding the data
+ let transaction = db.transaction(['notes'], 'readwrite');
+
+ // call an object store that's already been added to the database
+ let objectStore = transaction.objectStore('notes');
+
+ // Make a request to add our newItem object to the object store
+ var request = objectStore.add(newItem);
+ request.onsuccess = function() {
+ // Clear the form, ready for adding the next entry
+ titleInput.value = '';
+ bodyInput.value = '';
+ };
+
+ // Report on the success of the transaction completing, when everything is done
+ transaction.oncomplete = function() {
+ console.log('Transaction completed: database modification finished.');
+
+ // update the display of data to show the newly added item, by running displayData() again.
+ displayData();
+ };
+
+ transaction.onerror = function() {
+ console.log('Transaction not opened due to error');
+ };
+}</pre>
+
+<p><font>这很复杂; </font><font>打破它,我们:</font></p>
+
+<ul>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault" title="Event 接口的 preventDefault( ) 方法,告诉user agent:如果此事件没有需要显式处理,那么它默认的动作也不要做(因为默认是要做的)。此事件还是继续传播,除非碰到事件侦听器调用stopPropagation() 或stopImmediatePropagation(),才停止传播。"><code>Event.preventDefault()</code></a><font><font>在事件对象上</font><font>运行</font><font>以停止以传统方式实际提交的表单(这将导致页面刷新并破坏体验)。</font></font></li>
+ <li><font><font>创建一个表示要输入数据库的记录的对象,并使用表单输入中的值填充它。</font><font>请注意,我们不必明确包含一个</font></font><code>id</code><font><font>值 - 正如我们提前详细说明的那样,这是自动填充的。</font></font></li>
+ <li><font><font>使用该</font><font>方法</font><font>打开</font><font>对象存储</font><font>的</font></font><code>readwrite</code><font><font>事务</font><font>。</font><font>此事务对象允许我们访问对象存储,以便我们可以对其执行某些操作,例如添加新记录。</font></font><code>notes</code><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBDatabase/transaction" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>IDBDatabase.transaction()</code></a></li>
+ <li><font><font>使用该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBTransaction/objectStore" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>IDBTransaction.objectStore()</code></a><font><font>方法</font><font>访问对象库</font><font>,将结果保存在  </font></font><code>objectStore</code><font><font> 变量中。</font></font></li>
+ <li><font><font>使用添加新记录到数据库</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBObjectStore/add" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>IDBObjectStore.add()</code></a><font><font>。</font><font>这创建了一个请求对象,与我们之前看到的方式相同。</font></font></li>
+ <li><font><font>在生命周期的关键点</font><font>添加一堆事件处理程序</font></font><code>request</code><font><font>以及</font></font><code>transaction</code><font><font>运行代码。</font><font>请求成功后,我们会清除表单输入,以便输入下一个注释。</font><font>交易完成后,我们</font></font><code>displayData()</code><font><font>再次</font><font>运行该</font><font>功能以更新页面上的注释显示。</font></font></li>
+</ul>
+
+<h3 id="显示数据">显示数据</h3>
+
+<p><font><font>我们已经</font></font><code>displayData()</code><font><font>在代码中</font><font>引用了</font><font>两次,所以我们可能更好地定义它。</font><font>将其添加到您的代码中,位于上一个函数定义之下:</font></font></p>
+
+<pre class="brush: js notranslate">// Define the displayData() function
+function displayData() {
+ // Here we empty the contents of the list element each time the display is updated
+ // If you ddn't do this, you'd get duplicates listed each time a new note is added
+ while (list.firstChild) {
+ list.removeChild(list.firstChild);
+ }
+
+ // Open our object store and then get a cursor - which iterates through all the
+ // different data items in the store
+ let objectStore = db.transaction('notes').objectStore('notes');
+ objectStore.openCursor().onsuccess = function(e) {
+ // Get a reference to the cursor
+ let cursor = e.target.result;
+
+ // If there is still another data item to iterate through, keep running this code
+ if(cursor) {
+ // Create a list item, h3, and p to put each data item inside when displaying it
+ // structure the HTML fragment, and append it inside the list
+ let listItem = document.createElement('li');
+ let h3 = document.createElement('h3');
+ let para = document.createElement('p');
+
+ listItem.appendChild(h3);
+ listItem.appendChild(para);
+ list.appendChild(listItem);
+
+ // Put the data from the cursor inside the h3 and para
+ h3.textContent = cursor.value.title;
+ para.textContent = cursor.value.body;
+
+ // Store the ID of the data item inside an attribute on the listItem, so we know
+ // which item it corresponds to. This will be useful later when we want to delete items
+ listItem.setAttribute('data-note-id', cursor.value.id);
+
+ // Create a button and place it inside each listItem
+ let deleteBtn = document.createElement('button');
+ listItem.appendChild(deleteBtn);
+ deleteBtn.textContent = 'Delete';
+
+ // Set an event handler so that when the button is clicked, the deleteItem()
+ // function is run
+ <font face="Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace">deleteBtn.onclick = deleteItem;</font>
+
+ // Iterate to the next item in the cursor
+ cursor.continue();
+ } else {
+ // Again, if list item is empty, display a 'No notes stored' message
+ if(!list.firstChild) {
+ let listItem = document.createElement('li');
+ listItem.textContent = 'No notes stored.'
+ list.appendChild(listItem);
+ }
+ // if there are no more cursor items to iterate through, say so
+ console.log('Notes all displayed');
+ }
+ };
+}</pre>
+
+<p><font><font>再次,让我们打破这个:</font></font></p>
+
+<ul>
+ <li><font><font>首先,我们清空</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/ul" title="The HTML &lt;ul> 元素 ( 或 HTML 无序列表元素) 代表多项的无序列表,即无数值排序项的集合,且它们在列表中的顺序是没有意义的。通常情况下,无序列表项的头部可以是几种形式,如一个点,一个圆形或方形。头部的风格并不是在页面的HTML描述定义, 但在其相关的CSS 可以用 list-style-type 属性。"><code>&lt;ul&gt;</code></a><font><font>元素的内容,然后填充更新的内容。</font><font>如果您不这样做,那么每次更新时都会添加大量重复内容。</font></font></li>
+ <li><font><font>接下来,我们</font></font><code>notes</code><font><font>使用</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBDatabase/transaction" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>IDBDatabase.transaction()</code></a><font><font>和</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBTransaction/objectStore" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>IDBTransaction.objectStore()</code></a><font><font>我们一样</font><font>得到</font><font>对象存储</font><font>的引用</font></font><code>addData()</code><font><font>,除了这里我们将它们链接在一行中。</font></font></li>
+ <li><font><font>下一步是使用</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBObjectStore/openCursor" title="要确定添加操作是否已成功完成,请侦听结果的成功事件。"><code>IDBObjectStore.openCursor()</code></a><font><font>方法打开对游标的请求 - 这是一个可用于迭代对象存储中的记录的构造。</font><font>我们将一个</font></font><code>onsuccess</code><font><font>处理程序链接到该行的末尾以使代码更简洁 - 当成功返回游标时,运行处理程序。</font></font></li>
+ <li><font><font>我们</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBCursor" title="IndexedDB API的IDBCursor接口表示用于遍历或迭代数据库中的多个记录的游标。"><code>IDBCursor</code></a><font><font>使用let </font><font>获取对游标本身(</font><font>对象)</font><font>的引用</font></font><code>cursor = e.target.result</code><font><font>。</font></font></li>
+ <li><font><font>接下来,我们检查光标是否包含来自数据存储区(</font></font><code>if(cursor){ ... }</code><font><font>)</font><font>的记录</font><font>- 如果是这样,我们创建一个DOM片段,用记录中的数据填充它,然后将其插入页面(</font></font><code>&lt;ul&gt;</code><font><font>元素</font><font>内部</font><font>)。</font><font>我们还包括一个删除按钮,当单击该按钮时,将通过运行该</font></font><code>deleteItem()</code><font><font>功能</font><font>删除该注释</font><font>,我们将在下一节中查看。</font></font></li>
+ <li><font><font>在</font></font><code>if</code><font><font>块</font><font>结束时</font><font>,我们使用该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBCursor/continue" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>IDBCursor.continue()</code></a><font><font>方法将光标前进到数据存储区中的下</font><font>一条</font><font>记录,然后</font></font><code>if</code><font><font>再次</font><font>运行</font><font>块</font><font>的内容</font><font>。</font><font>如果有另一个要迭代的记录,这会导致它被插入到页面中,然后</font></font><code>continue()</code><font><font>再次运行,依此类推。</font></font></li>
+ <li><font><font>当没有更多记录要迭代时,</font></font><code>cursor</code><font><font>将返回</font></font><code>undefined</code><font><font>,因此</font></font><code>else</code><font><font>块将运行而不是</font></font><code>if</code><font><font>块。</font><font>此块检查是否有任何注释被插入</font></font><code>&lt;ul&gt;</code><font><font>- 如果没有,它会插入一条消息,说没有存储注释。</font></font></li>
+</ul>
+
+<h3 id="删除一条笔记">删除一条笔记</h3>
+
+<p><font><font>如上所述,当按下音符的删除按钮时,音符将被删除。</font><font>这是通过</font></font><code>deleteItem()</code><font><font>函数</font><font>实现的</font><font>,如下所示:</font></font></p>
+
+<pre class="brush: js notranslate">// Define the deleteItem() function
+function deleteItem(e) {
+ // retrieve the name of the task we want to delete. We need
+ // to convert it to a number before trying it use it with IDB; IDB key
+ // values are type-sensitive.
+ let noteId = Number(e.target.parentNode.getAttribute('data-note-id'));
+
+ // open a database transaction and delete the task, finding it using the id we retrieved above
+ let transaction = db.transaction(['notes'], 'readwrite');
+ let objectStore = transaction.objectStore('notes');
+ let request = objectStore.delete(noteId);
+
+ // report that the data item has been deleted
+ transaction.oncomplete = function() {
+ // delete the parent of the button
+ // which is the list item, so it is no longer displayed
+ e.target.parentNode.parentNode.removeChild(e.target.parentNode);
+ console.log('Note ' + noteId + ' deleted.');
+
+ // Again, if list item is empty, display a 'No notes stored' message
+ if(!list.firstChild) {
+ let listItem = document.createElement('li');
+ listItem.textContent = 'No notes stored.';
+ list.appendChild(listItem);
+ }
+ };
+}</pre>
+
+<ul>
+ <li><font><font>第一部分可以使用一些解释 - 我们检索要删除</font></font><code>Number(e.target.parentNode.getAttribute('data-note-id'))</code><font><font>的记录的ID - 回想一下记录的ID是在</font><font>第一次显示时</font><font>保存在</font></font><code>data-note-id</code><font><font>属性中的</font></font><code>&lt;li&gt;</code><font><font>。</font><font>但是,我们需要通过全局内置的</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number"><font><font>Number()</font></font></a><font><font>对象</font><font>传递属性</font><font>,因为它当前是一个字符串,否则将无法被数据库识别。</font></font></li>
+ <li><font><font>然后,我们使用我们之前看到的相同模式获取对对象存储的引用,并使用该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBObjectStore/delete" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>IDBObjectStore.delete()</code></a><font><font>方法从数据库中删除记录,并将ID传递给它。</font></font></li>
+ <li><font><font>当数据库事务完成后,我们</font></font><code>&lt;li&gt;</code><font><font>从DOM中</font><font>删除注释</font><font>,然后再次检查以查看它是否</font></font><code>&lt;ul&gt;</code><font><font>为空,并根据需要插入注释。</font></font></li>
+</ul>
+
+<p><font><font>就是这样了!</font><font>你的例子现在应该有效。</font></font></p>
+
+<p><font><font>如果您遇到问题,请随时</font></font><a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/notes/" rel="noopener"><font><font>查看我们的实例</font></font></a><font><font>(请参阅</font></font><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/index.js" rel="noopener"><font><font>源代码</font></font></a><font><font>)。</font></font></p>
+
+<h3 id="通过_IndexedDB_存储复杂数据">通过 IndexedDB 存储复杂数据</h3>
+
+<p><font><font>如上所述,IndexedDB可用于存储不仅仅是简单的文本字符串。</font><font>您可以存储任何您想要的东西,包括复杂的对象,如视频或图像blob。</font><font>并且它比任何其他类型的数据更难实现。</font></font></p>
+
+<p><font><font>为了演示如何操作,我们编写了另一个名为</font></font><a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/client-side-storage/indexeddb/video-store" rel="noopener"><font><font>IndexedDB视频存储的</font></font></a><font><font>示例</font><font>(请参阅</font></font><a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/video-store/" rel="noopener"><font><font>此处也可以在此处运行</font></font></a><font><font>)。</font><font>首次运行示例时,它会从网络下载所有视频,将它们存储在IndexedDB数据库中,然后在UI内部</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/video" title="HTML &lt;video> 元素 用于在HTML或者XHTML文档中嵌入视频内容。"><code>&lt;video&gt;</code></a><font><font>元素中</font><font>显示视频</font><font>。</font><font>第二次运行它时,它会在数据库中找到视频并从那里获取它们而不是显示它们 - 这使得后续加载更快,占用空间更少。</font></font></p>
+
+<p><font><font>让我们来看看这个例子中最有趣的部分。</font><font>我们不会全部看 - 它的很多内容与上一个示例类似,代码注释得很好。</font></font></p>
+
+<ol>
+ <li>
+ <p>对于这个简单的例子,我们已经存储了视频的名称以获取数组opf对象:</p>
+
+ <pre class="brush: js notranslate">const videos = [
+ { 'name' : 'crystal' },
+ { 'name' : 'elf' },
+ { 'name' : 'frog' },
+ { 'name' : 'monster' },
+ { 'name' : 'pig' },
+ { 'name' : 'rabbit' }
+];</pre>
+ </li>
+ <li>
+ <p><font><font>首先,一旦数据库成功打开,我们就运行一个</font></font><code>init()</code><font><font>函数。</font><font>这会遍历不同的视频名称,尝试加载由</font></font><code>videos</code><font><font>数据库中的</font><font>每个名称标识的记录</font><font>。</font></font></p>
+
+ <p><font><font>如果在数据库中找到每个视频(通过查看</font></font><code>request.result</code><font><font>评估</font><font>是否容易检查</font></font><code>true</code><font><font>- 如果记录不存在,那么</font></font><code>undefined</code><font><font>),视频文件(存储为blob)和视频名称将直接传递给</font></font><code>displayVideo()</code><font><font>函数以放置它们在用户界面中。</font><font>如果没有,视频名称将传递给</font></font><code>fetchVideoFromNetwork()</code><font><font>函数...你猜对了 - 从网络中获取视频。</font></font></p>
+
+ <pre class="brush: js notranslate">function init() {
+ // Loop through the video names one by one
+ for(let i = 0; i &lt; videos.length; i++) {
+ // Open transaction, get object store, and get() each video by name
+ let objectStore = db.transaction('videos').objectStore('videos');
+ let request = objectStore.get(videos[i].name);
+ request.onsuccess = function() {
+ // If the result exists in the database (is not undefined)
+ if(request.result) {
+ // Grab the videos from IDB and display them using displayVideo()
+ console.log('taking videos from IDB');
+ displayVideo(request.result.mp4, request.result.webm, request.result.name);
+ } else {
+ // Fetch the videos from the network
+ fetchVideoFromNetwork(videos[i]);
+ }
+ };
+ }
+}</pre>
+ </li>
+ <li>
+ <p><font><font>以下片段是从内部</font></font><code>fetchVideoFromNetwork()</code><font><font>获取的 - 这里我们使用两个单独的</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>WindowOrWorkerGlobalScope.fetch()</code></a><font><font>请求</font><font>获取视频的MP4和WebM版本</font><font>。</font><font>然后,我们使用该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Blob" title="Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。"><code>Body.blob()</code></a><font><font>方法将每个响应的主体提取为blob,为我们提供可以在以后存储和显示的视频的对象表示。</font></font></p>
+
+ <p><font><font>我们在这里遇到了一个问题 - 这两个请求都是异步的,但我们只想在两个promises都满足时尝试显示或存储视频。</font><font>幸运的是,有一种处理这种问题的内置方法 - </font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all" title="Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中  promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。"><code>Promise.all()</code></a><font><font>。</font><font>这需要一个参数 - 引用您要检查放置在数组中的履行的所有单个承诺 - 并且本身是基于承诺的。</font></font></p>
+
+ <p><font><font>当所有这些承诺都履行完毕时,</font></font><code>all()</code><font><font>承诺将通过包含所有个人履行价值的数组来实现。</font><font>在</font></font><code>all()</code><font><font>块中,您可以看到我们</font></font><code>displayVideo()</code><font><font>之前</font><font>调用</font><font>函数,就像在UI中显示视频一样,然后我们也调用</font></font><code>storeVideo()</code><font><font>函数将这些视频存储在数据库中。</font></font></p>
+
+ <pre class="brush: js notranslate">let mp4Blob = fetch('videos/' + video.name + '.mp4').then(response =&gt;
+ response.blob()
+);
+let webmBlob = fetch('videos/' + video.name + '.webm').then(response =&gt;
+ response.blob()
+);;
+
+// Only run the next code when both promises have fulfilled
+Promise.all([mp4Blob, webmBlob]).then(function(values) {
+ // display the video fetched from the network with displayVideo()
+ displayVideo(values[0], values[1], video.name);
+ // store it in the IDB using storeVideo()
+ storeVideo(values[0], values[1], video.name);
+});</pre>
+ </li>
+ <li>
+ <p><font><font>我们</font></font><code>storeVideo()</code><font><font>先</font><font>来看看吧</font><font>。</font><font>这与您在上一个示例中看到的用于向数据库添加数据的模式非常相似 - 我们打开一个</font></font><code>readwrite</code><font><font>事务并获取对象存储引用</font></font><code>videos</code><font><font>,创建一个表示要添加到数据库的记录的对象,然后使用它添加它</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/IDBObjectStore/add" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>IDBObjectStore.add()</code></a><font><font>。</font></font></p>
+
+ <pre class="brush: js notranslate">function storeVideo(mp4Blob, webmBlob, name) {
+ // Open transaction, get object store; make it a readwrite so we can write to the IDB
+ let objectStore = db.transaction(['videos'], 'readwrite').objectStore('videos');
+ // Create a record to add to the IDB
+ let record = {
+ mp4 : mp4Blob,
+ webm : webmBlob,
+ name : name
+ }
+
+ // Add the record to the IDB using add()
+ let request = objectStore.add(record);
+
+ ...
+
+};</pre>
+ </li>
+ <li>
+ <p><font><font>最后但并非最不重要的是,我们</font></font><code>displayVideo()</code><font><font>创建了在UI中插入视频然后将它们附加到页面所需的DOM元素。</font><font>最有趣的部分如下所示 - 要在</font></font><code>&lt;video&gt;</code><font><font>元素中</font><font>实际显示我们的视频blob </font><font>,我们需要使用该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL" title="URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。"><code>URL.createObjectURL()</code></a><font><font>方法</font><font>创建对象URL(指向存储在内存中的视频blob的内部URL)</font><font>。</font><font>完成后,我们可以将对象URL设置为</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/source" title="HTML &lt;source>元素为&lt;picture>,&lt;audio>或&lt;video>元素指定多个媒体资源。 这是一个空元素。 它通常用于以不同浏览器支持的多种格式提供相同的媒体内容。"><code>&lt;source&gt;</code></a><font><font>元素</font></font><code>src</code><font><font>属性的值,并且它可以正常工作。</font></font></p>
+
+ <pre class="brush: js notranslate">function displayVideo(mp4Blob, webmBlob, title) {
+ // Create object URLs out of the blobs
+ let mp4URL = URL.createObjectURL(mp4Blob);
+ let webmURL = URL.createObjectURL(webmBlob);
+
+ ...
+
+ let video = document.createElement('video');
+ video.controls = true;
+ let source1 = document.createElement('source');
+ source1.src = mp4URL;
+ source1.type = 'video/mp4';
+ let source2 = document.createElement('source');
+ source2.src = webmURL;
+ source2.type = 'video/webm';
+
+ ...
+}</pre>
+ </li>
+</ol>
+
+<h2 id="离线文件存储">离线文件存储</h2>
+
+<p><font>上面的示例已经说明了如何创建一个将大型资产存储在IndexedDB数据库中的应用程序,从而无需多次下载它们。</font><font>这已经是对用户体验的一个很大的改进,但仍然有一件事 - 每次访问网站时仍然需要下载主要的HTML,CSS和JavaScript文件,这意味着当没有网络连接时,它将无法工作。</font></p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/15759/ff-offline.png" style="border-style: solid; border-width: 1px; display: block; height: 307px; margin: 0px auto; width: 765px;"></p>
+
+<p><font><font>这就是</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API"><font><font>服务工作者</font></font></a><font><font>和密切相关的</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache"><font><font>Cache API的</font></font></a><font><font>用武之地。</font></font></p>
+
+<p><font><font>服务工作者是一个JavaScript文件,简单地说,它是在浏览器访问时针对特定来源(网站或某个域的网站的一部分)进行注册的。</font><font>注册后,它可以控制该来源的可用页面。</font><font>它通过坐在加载的页面和网络之间以及拦截针对该来源的网络请求来实现这一点。</font></font></p>
+
+<p><font><font>当它拦截一个请求时,它可以做任何你想做的事情(参见</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API#Other_use_case_ideas"><font><font>用例思路</font></font></a><font><font>),但经典的例子是离线保存网络响应,然后提供响应请求而不是来自网络的响应。</font><font>实际上,它允许您使网站完全脱机工作。</font></font></p>
+
+<p><font><font>Cache API是另一种客户端存储机制,略有不同 - 它旨在保存HTTP响应,因此与服务工作者一起工作得非常好。</font></font></p>
+
+<div class="note">
+<p><strong><font><font>注意</font></font></strong><font><font>:现在大多数现代浏览器都支持服务工作者和缓存。</font><font>在撰写本文时,Safari仍在忙着实施它,但它应该很快就会存在。</font></font></p>
+</div>
+
+<h3 id="一个_service_worker_例子">一个 service worker 例子</h3>
+
+<p><font><font>让我们看一个例子,让你对这可能是什么样子有所了解。</font><font>我们已经创建了另一个版本的视频存储示例,我们在上一节中看到了 - 这个功能完全相同,只是它还通过服务工作者将Cache,CSS和JavaScript保存在Cache API中,允许示例脱机运行!</font></font></p>
+
+<p><font><font>请参阅</font></font><a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/" rel="noopener"><font><font>IndexedDB视频存储,其中服务工作者正在运行</font></font></a><font><font>,并且还可以</font></font><a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/client-side-storage/cache-sw/video-store-offline" rel="noopener"><font><font>查看源代码</font></font></a><font><font>。</font></font></p>
+
+<h4 id="注册服务工作者"><font><font>注册服务工作者</font></font></h4>
+
+<p><font><font>首先要注意的是,在主JavaScript文件中放置了一些额外的代码(请参阅</font></font><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js" rel="noopener"><font><font>index.js</font></font></a><font><font>)。</font><font>首先,我们进行特征检测测试,以查看</font></font><code>serviceWorker</code><font><font>该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator" title="Navigator 接口表示用户代理的状态和标识。 它允许脚本查询它和注册自己进行一些活动。"><code>Navigator</code></a><font><font>对象中</font><font>是否有该</font><font>成员</font><font>。</font><font>如果返回true,那么我们知道至少支持服务工作者的基础知识。</font><font>在这里,我们使用该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/ServiceWorkerContainer/register" title="ServiceWorkerContainer 接口的 register() 方法创建或更新一个给定scriptURL的ServiceWorkerRegistration 。"><code>ServiceWorkerContainer.register()</code></a><font><font>方法将</font></font><code>sw.js</code><font><font>文件中</font><font>包含的服务工作者注册到</font><font>它所驻留的源,因此它可以控制与它或子目录相同的目录中的页面。</font><font>当其承诺履行时,服务人员被视为已注册。</font></font></p>
+
+<pre class="brush: js notranslate"> // Register service worker to control making site work offline
+
+ if('serviceWorker' in navigator) {
+ navigator.serviceWorker
+ .register('/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js')
+ .then(function() { console.log('Service Worker Registered'); });
+ }</pre>
+
+<div class="note">
+<p><strong><font><font>注意</font></font></strong><font><font>:</font></font><code>sw.js</code><font><font>文件</font><font>的给定路径</font><font>是相对于站点源的,而不是包含代码的JavaScript文件。</font><font>服务人员在</font></font><code>https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code><font><font>。</font><font>原点是</font></font><code>https://mdn.github.io</code><font><font>,因此给定的路径必须是</font></font><code>/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code><font><font>。</font><font>如果您想在自己的服务器上托管此示例,则必须相应地更改此示例。</font><font>这是相当令人困惑的,但出于安全原因,它必须以这种方式工作。</font></font></p>
+</div>
+
+<h4 id="安装_service_worker">安装 service worker</h4>
+
+<p><font><font>下次访问服务工作者控制下的任何页面时(例如,重新加载示例时),将针对该页面安装服务工作者,这意味着它将开始控制它。</font><font>发生这种情况时,</font></font><code>install</code><font><font>会向服务工作人员发起</font><font>一个</font><font>事件; </font><font>您可以在服务工作者本身内编写代码来响应安装。</font></font></p>
+
+<p><font><font>让我们看一下</font></font><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js" rel="noopener"><font><font>sw.js</font></font></a><font><font>文件(服务工作者)</font><font>中的一个例子</font><font>。</font><font>您将看到安装侦听器已注册</font></font><code>self</code><font><font>。</font><font>此</font></font><code>self</code><font><font>关键字是一种从服务工作文件内部引用服务工作者的全局范围的方法。</font></font></p>
+
+<p><font><font>在</font></font><code>install</code><font><font> 处理程序</font><font>内部,  </font><font>我们使用</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/ExtendableEvent/waitUntil" title="ExtendableEvent.waitUntil() 方法扩展了事件的生命周期。在服务工作线程中,延长事件的寿命从而阻止浏览器在事件中的异步操作完成之前终止服务工作线程。"><code>ExtendableEvent.waitUntil()</code></a><font><font>事件对象上可用</font><font>的</font><font>方法来表示浏览器不应该完成服务工作者的安装,直到其中的promise成功完成。</font></font></p>
+
+<p><font><font>这是我们在运行中看到Cache API的地方。</font><font>我们使用该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CacheStorage/open" title="CacheStorage open() 方法返回一个resolve为匹配 cacheName 的 Cache 对象的 Promise ."><code>CacheStorage.open()</code></a><font><font>方法打开一个可以存储响应的新缓存对象(类似于IndexedDB对象存储)。</font><font>此承诺通过</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Cache" title="Cache 接口为缓存的 Request / Response  对象对提供存储机制,例如,作为ServiceWorker 生命周期的一部分。请注意,Cache 接口像 workers 一样,是暴露在 window 作用域下的。尽管它被定义在 service worker 的标准中,  但是它不必一定要配合 service worker 使用."><code>Cache</code></a><font><font>表示</font></font><code>video-store</code><font><font>缓存</font><font>的</font><font>对象来</font><font>实现</font><font>。</font><font>然后,我们使用该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Cache/addAll" title="Cache 接口的 addAll() 方法接受一个URL数组,检索它们,并将生成的response对象添加到给定的缓存中。 在检索期间创建的request对象成为存储的response操作的key。"><code>Cache.addAll()</code></a><font><font>方法获取一系列资产并将其响应添加到缓存中。</font></font></p>
+
+<pre class="brush: js notranslate">self.addEventListener('install', function(e) {
+ e.waitUntil(
+ caches.open('video-store').then(function(cache) {
+ return cache.addAll([
+ '/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/',
+ '/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.html',
+ '/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js',
+ '/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/style.css'
+ ]);
+ })
+ );
+});</pre>
+
+<p>这就是现在,安装完成。</p>
+
+<h4 id="响应未来的请求">响应未来的请求</h4>
+
+<p><font><font>在我们的HTML页面上注册并安装了服务工作者,并且所有相关资产都添加到我们的缓存中,我们几乎准备好了。</font><font>还有一件事要做,写一些代码来响应进一步的网络请求。</font></font></p>
+
+<p><font><font>这就是第二位代码的</font></font><code>sw.js</code><font><font>作用。</font><font>我们向服务工作者全局范围添加另一个侦听器,该范围在</font></font><code>fetch</code><font><font>引发事件</font><font>时运行处理函数</font><font>。</font><font>只要浏览器在服务工作者注册的目录中请求资产,就会发生这种情况。</font></font></p>
+
+<p><font><font>在处理程序内部,我们首先记录所请求资产的URL。</font><font>然后,我们使用该</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/FetchEvent/respondWith" title="FetchEvent 接口的 respondWith() 方法旨在包裹代码,这些代码为来自受控页面的request生成自定义的response。这些代码通过返回一个 Response 、 network error 或者 Fetch的方式resolve。"><code>FetchEvent.respondWith()</code></a><font><font>方法</font><font>为请求提供自定义响应</font><font>。</font></font></p>
+
+<p><font><font>在这个块中,我们</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CacheStorage/match" title="CacheStorage 接口(可适用于全局性caches) 的 match() 方法检查给定的Request 对象或url字符串是否是一个已存储的 Response 对象的key. 这个方法针对 Response 返回一个 Promise ,如果没有匹配则返回 undefined 。"><code>CacheStorage.match()</code></a><font><font>用来检查是否可以在任何缓存中找到匹配的请求(即匹配URL)。</font><font>如果未找到匹配,或者</font></font><code>undefined</code><font><font>如果</font><font>未找到匹配,则此承诺将满足匹配的响应</font><font>。</font></font></p>
+
+<p><font><font>如果找到匹配项,我们只需将其作为自定义响应返回。</font><font>如果没有,我们</font><font>从网络中</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch"><font><font>获取()</font></font></a><font><font>响应并返回该响应。</font></font></p>
+
+<pre class="brush: js notranslate">self.addEventListener('fetch', function(e) {
+ console.log(e.request.url);
+ e.respondWith(
+ caches.match(e.request).then(function(response) {
+ return response || fetch(e.request);
+ })
+ );
+});</pre>
+
+<p><font><font>这就是我们简单的服务工作者。</font><font>您可以使用它们进行更多的负载 - 有关详细信息,请参阅</font></font><a href="https://serviceworke.rs/" rel="noopener"><font><font>服务工作者手册</font></font></a><font><font>。</font><font>感谢Paul Kinlan在他的文章中</font></font><a href="https://developers.google.com/web/fundamentals/codelabs/offline/" rel="noopener"><font><font>添加服务工作者和离线到您的Web应用程序</font></font></a><font><font>,这启发了这个简单的例子。</font></font></p>
+
+<h4 id="测试离线示例">测试离线示例</h4>
+
+<p><font><font>要测试我们的</font></font><a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/" rel="noopener"><font><font>服务工作者示例</font></font></a><font><font>,您需要加载它几次以确保它已安装。</font><font>完成后,您可以:</font></font></p>
+
+<ul>
+ <li><font><font>尝试拔掉网络连接/关闭Wifi。</font></font></li>
+ <li><font><font>如果您使用的是Firefox,请</font><font>选择</font></font><em><font><font>文件&gt;脱机工作</font></font></em><font><font>。</font></font></li>
+ <li><font><font>转到devtools,然后选择</font></font><em><font><font>Application&gt; Service Workers</font></font></em><font><font>,</font><font>如果您使用的是Chrome </font><font>,请选中</font></font><em><font><font>Offline</font></font></em><font><font>选中。</font></font></li>
+</ul>
+
+<p><font><font>如果再次刷新示例页面,您仍应该看到它加载得很好。</font><font>所有内容都是脱机存储的 - 缓存中的页面资源以及IndexedDB数据库中的视频。</font></font></p>
+
+<h2 id="总结">总结</h2>
+
+<p><font>现在就是这些。</font><font>我们希望你能感觉到我们对客户端存储技术的介绍很有用。</font></p>
+
+<h2 id="相关链接">相关链接</h2>
+
+<ul>
+ <li><a href="/en-US/docs/Web/API/Web_Storage_API">网页存储 API</a></li>
+ <li><a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a></li>
+ <li><a href="/en-US/docs/Web/HTTP/Cookies">Cookies</a></li>
+ <li><a href="/en-US/docs/Web/API/Service_Worker_API">Service worker API</a></li>
+</ul>
+
+<p>{{PreviousMenu("Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</p>
+
+<h2 id="在本单元中">在本单元中</h2>
+
+<ul>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">网页端 API介绍 </a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">Manipulating documents</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">从服务器获取数据(fetch)</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">第三方 API</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing graphics</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">视频和音频 API</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">客户端存储</a></li>
+</ul>
diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/drawing_graphics/index.html b/files/zh-cn/learn/javascript/client-side_web_apis/drawing_graphics/index.html
new file mode 100644
index 0000000000..e39694743d
--- /dev/null
+++ b/files/zh-cn/learn/javascript/client-side_web_apis/drawing_graphics/index.html
@@ -0,0 +1,881 @@
+---
+title: 绘图
+slug: Learn/JavaScript/Client-side_web_APIs/Drawing_graphics
+tags:
+ - API
+ - Canvas
+ - CodingScripting
+ - JavaScript
+ - WebGL
+ - 初学者
+ - 图形
+ - 学习
+ - 文章
+translation_of: Learn/JavaScript/Client-side_web_APIs/Drawing_graphics
+---
+<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Third_party_APIs", "Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</div>
+
+<p class="summary">浏览器包含一些非常强大的图形编程工具,从可缩放矢量图形(Scalable Vector Graphics,简称 <a href="https://developer.mozilla.org/zh-CN/docs/Web/SVG">SVG</a>)语言到用于在 HTML {{htmlelement("canvas")}} 元素上绘制图形的API(参阅 <a href="/zh-CN/docs/Web/API/Canvas_API">Canvas API</a> 和 <a href="/zh-CN/docs/Web/API/WebGL_API">WebGL</a>)。本文对 {{htmlelement("canvas")}} 进行介绍,并提供更多的学习资源。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">预备知识:</th>
+ <td>JavaScript基础(见 <a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript 第一步</a>,<a href="/zh-CN/docs/learn/JavaScript/Building_blocks">创建 JavaScript 代码块</a>,<a href="/zh-CN/docs/Learn/JavaScript/Objects">JavaScript 对象入门</a>),<a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API 基础知识</a>。</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>学习 JavaScript 在 <code>&lt;canvas&gt;</code> 元素中绘图的基础知识。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="网络图形">网络图形</h2>
+
+<p>我们来讨论 HTML 的 <a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding">多媒体和嵌入式</a> 模块,早先的网页只有单调的文字,后来才引入了图像,起初是通过 {{htmlelement("img")}} 元素的方式,后来出现了类似于 {{cssxref("background-image")}} 的 CSS 属性和 <a href="/zh-CN/docs/Web/SVG">SVG</a> 图像等方式。</p>
+
+<p>然而,这还不够好。当你能够使用 <a href="/zh-CN/docs/Learn/CSS">CSS</a> 和 <a href="/zh-CN/docs/Learn/JavaScript">JavaScript </a>让 SVG 矢量图动起来时,位图却依然没有相应的支持。同时 SVG 动画的可用工具也少得可怜。有效地生成动画、游戏画面、3D场景和其他的需求依然没有满足,而这些在诸如 C++ 或者 Java 等底层语言中却司空见惯。</p>
+
+<p>当浏览器开始支持 HTML 画布元素 {{htmlelement("canvas")}} 和相关的 <a href="/zh-CN/docs/Web/API/Canvas_API">Canvas API</a>(由苹果公司在 2004 年前后发明,后来其他的浏览器开始跟进)时,形势开始改善。下面你会看到,canvas 提供了许多有用的工具,特别是当捆绑了由网络平台提供的一些其他的 API 时。它们用来生成 2D 动画、游戏画面和数据分析图,以及其他类型的 app。</p>
+
+<p>下面的例子显示的是一个基于 canvas 的简单的 2D 弹跳球动画,前面我们在 <a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_building_practice">JavaScript 对象入门 </a>的章节中见到过。</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/oojs/bouncing-balls/index-finished.html", '100%', 500)}}</p>
+
+<p>大约在 2006 - 2007 年,Mozilla 开始测试 3D 画布。后来演化为 <a href="/zh-CN/docs/Web/API/WebGL_API">WebGL</a>,它获得了各大浏览器厂商的认可,于是大约在 2009 - 2010 年间得到了标准化。WebGL 可以让你在 web 浏览器中生成真正的 3D 图形。下面的例子显示了一个简单的旋转的 WebGL 立方体。</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/threejs-cube/index.html", '100%', 500)}}</p>
+
+<p>由于原生的 WebGL 代码非常复杂,本文主要针对 2D 画布。然而,你也可以通过 <a href="/zh-CN/docs/Web/API/WebGL_API">WebGL介绍页面</a> 找到 WebGL 原生代码的教程,来学习如何更容易地使用 WebGL 库来创建一个 3D 场景。</p>
+
+<div class="note">
+<p><strong>注:</strong>画布的基本功能有良好的跨浏览器支持。但存在例外:IE 8 及以下不支持 2D 画布,IE 11 及以下不支持WebGL。</p>
+</div>
+
+<h2 id="主动学习:开始使用_&lt;canvas>">主动学习:开始使用 &lt;canvas&gt;</h2>
+
+<p>要在网页中创建 2D 或者 3D 场景,必须在 HTML 文件中插入一个 {{htmlelement("canvas")}} 元素,以界定网页中的绘图区域。这很简单,如下所示:</p>
+
+<pre class="brush: html notranslate">&lt;canvas width="320" height="240"&gt;&lt;/canvas&gt;</pre>
+
+<p>网页中会生成一块 320 × 240 像素的画布。</p>
+
+<p>在 canvas 标签内,你可以放置一些反馈信息,如果用户的浏览器不支持画布功能,这些内容就会显示出来。</p>
+
+<pre class="brush: html notranslate">&lt;canvas width="320" height="240"&gt;
+ &lt;p&gt;卧槽你的浏览器竟然不支持 canvas!&lt;/p&gt;
+&lt;/canvas&gt;</pre>
+
+<p>当然这条信息实在是没什么用!在实际情况中最好提供与画布内容相关的反馈信息。比如,如果无法渲染实时更新的股价曲线图,就应提供一幅静态的、最近的股价曲线图,并用 alt 显示出价格信息。</p>
+
+<h3 id="创建画布并确定尺寸">创建画布并确定尺寸</h3>
+
+<p>让我们开始吧:创建画布,准备尝试绘制图形。</p>
+
+<ol>
+ <li>
+ <p>首先下载 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/0_canvas_start.html">0_canvas_start.html</a> 文件, 用文本编辑器打开。</p>
+ </li>
+ <li>
+ <p>在 {{htmlelement("body")}} 标签下面填加以下代码。</p>
+
+ <pre class="brush: html notranslate">&lt;canvas class="myCanvas"&gt;
+ &lt;p&gt;添加恰当的反馈信息。&lt;/p&gt;
+&lt;/canvas&gt;</pre>
+
+ <p>我们为 <code>&lt;canvas&gt;</code> 元素添加了一个 <code>class</code>,使得在网页中选择多个画布时会容易些。这里我们移除了 <code>width</code> 和 <code>height</code> 属性 (你可以随时添上。但是我们会在下方用 JavaScript 把它们添加回来)。不明确指定宽高时,画布默认尺寸为 300 × 150 像素。</p>
+ </li>
+ <li>
+ <p>现在,请在 {{htmlelement("script")}} 元素内添加以下 JavaScript 代码:</p>
+
+ <pre class="brush: js notranslate">var canvas = document.querySelector('.myCanvas');
+var width = canvas.width = window.innerWidth;
+var height = canvas.height = window.innerHeight;</pre>
+
+ <p>这里我们用 <code>canvas</code> 变量来存储画布的引用。第二行中我们将  {{domxref("Window.innerWidth")}}(可视区域宽度)赋值给一个新变量 <code>width</code> 和画布的 <code>canvas.width</code> 变量,(第三行同理)。然后我们就得到了一个充满窗口的画布。<br>
+ <br>
+ 你还可以看到我们使用了多个等号为多个变量进行连续赋值,这在 JavaScript 中是允许的,很适合为多个变量同时赋一个相同的值。后文中你会发现,使用 <code>width</code> 和 <code>height</code> 变量可以更快捷地访问画布的长宽(比如在画布正中央绘制一条垂直线)。 </p>
+ </li>
+ <li>
+ <p>如果现在保存文件,浏览器中什么也不会显示,这并没有问题,但是滚动条还是可见的,这就是问题了。原因是我们的“全窗尺寸画布”包含 {{htmlelement("body")}} 元素的外边距({{cssxref("margin")}}),使得文档比窗口略宽。 为使滚动条消失,需要删除 {{htmlelement("body")}} 元素的 {{cssxref("margin")}} 并将 {{cssxref("overflow")}} 设置为 <code>hidden</code>。在文档的 {{htmlelement("head")}} 中添加以下代码即可:</p>
+
+ <pre class="brush: html notranslate">&lt;style&gt;
+ body {
+ margin: 0;
+ overflow: hidden;
+ }
+&lt;/style&gt;</pre>
+
+ <p>此时滚动条就消失了。</p>
+ </li>
+</ol>
+
+<div class="note">
+<p><strong>注:</strong>如上文所讲,一般情况下图片的尺寸可以通过 HTML 属性或 DOM 属性来设定。你也可以使用 CSS,但问题是画布在渲染完毕后其尺寸就是固定的了,如果试图调整,就会与其他图象一样(其实渲染好的画布就是一副图片),所显示的内容将变得像素化或扭曲变形。</p>
+</div>
+
+<h3 id="获取画布上下文(canvas_context)并完成设置">获取画布上下文(canvas context)并完成设置</h3>
+
+<p>画布模板设置还有最后一步。我们需要获得一个对绘画区域的特殊的引用(称为“上下文”)用来在画布上绘图。可通过 {{domxref("HTMLCanvasElement.getContext()")}} 方法获得基础的绘画功能,需要提供一个字符串参数来表示所需上下文的类型。</p>
+
+<p>这里我们需要一个 2d 画布,在 <code>&lt;script&gt;</code> 元素内添加以下 JS 代码即可:</p>
+
+<pre class="brush: js notranslate">var ctx = canvas.getContext('2d');</pre>
+
+<div class="note">
+<p><strong>注:</strong>可选上下文还包括 WebGL(<code>webgl</code>)、WebGL 2(<code>webgl2</code>)等等,但本文暂不涉及。</p>
+</div>
+
+<p>好啦,现已万事具备!<code>ctx</code> 变量包含一个 {{domxref("CanvasRenderingContext2D")}} 对象,画布上所有绘画操作都会涉及到这个对象。</p>
+
+<p>开始前我们先初尝一下 canvas API。在 JS 代码中添加以下两行,将画布背景涂成黑色:</p>
+
+<pre class="brush: js notranslate">ctx.fillStyle = 'rgb(0, 0, 0)';
+ctx.fillRect(0, 0, width, height);</pre>
+
+<p>这里我们使用画布的 {{domxref("CanvasRenderingContext2D.fillStyle", "fillStyle")}} 属性(和CSS属性 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Values_and_units#Colors">色值</a> 一致)设置填充色,然后使用 {{domxref("CanvasRenderingContext2D.fillRect", "fillRect")}} 方法绘制一个覆盖整个区域的矩形(前两个参数是矩形左上顶点的坐标,后两个参数是矩形的长宽,现在你知道 <code>width</code> 和 <code>height</code> 的作用了吧)。</p>
+
+<p>好的,模板已经就位,我们要开始了。</p>
+
+<h2 id="2D_画布基础">2D 画布基础</h2>
+
+<p>如上文所讲,所有绘画操作都离不开 {{domxref("CanvasRenderingContext2D")}} 对象(这里叫做 <code>ctx</code>)。许多操作都需要提供坐标来指示绘图的确切位置。画布左上角的坐标是(0, 0),横坐标(x)轴向右延伸,纵坐标(y)轴向下延伸。</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/224/Canvas_default_grid.png" style="display: block; margin: 0 auto;"></p>
+
+<p>绘图操作可基于原始矩形模型实现,也可通过追踪一个特定路径后填充颜色实现。下面分别讲解。</p>
+
+<h3 id="简单矩形">简单矩形</h3>
+
+<p>让我们从简单矩形开始。</p>
+
+<ol>
+ <li>
+ <p>首先,复制一份刚才创建的画布模板 (如果你没有严格按上述步骤进行,请下载 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>)。</p>
+ </li>
+ <li>
+ <p>然后在 JS 代码末尾添加下面两行:</p>
+
+ <pre class="brush: js notranslate">ctx.fillStyle = 'rgb(255, 0, 0)';
+ctx.fillRect(50, 50, 100, 150);</pre>
+
+ <p>保存并刷新,画布上将出现一个红色的矩形。其左边和顶边与画布边缘距离均为 50 像素(由前两个参数指定),宽 100 像素、高 150 像素(由后两个参数指定)。</p>
+ </li>
+ <li>
+ <p>然后再添加一个绿色矩形。在 JS 代码末尾添加下面两行:</p>
+
+ <pre class="brush: js notranslate">ctx.fillStyle = 'rgb(0, 255, 0)';
+ctx.fillRect(75, 75, 100, 100);</pre>
+
+ <p>保存并刷新,新的矩形就会出现。这里引出了一个新问题:绘制矩形、线等操作按出现的顺序依次进行。就像粉刷墙面时,两层重叠时新层总会覆盖旧层。这一点是无法改变的,因此在绘制图形时一定要慎重考虑顺序问题。</p>
+ </li>
+ <li>
+ <p>你还可以通过指定半透明的颜色来绘制半透明的图形,比如使用 <code>rgba()</code>。 <code>a</code> 指定了“α 通道”的值,也就是颜色的透明度。值越高透明度越高,底层的内容就越清晰。在代码中添加以下两行:</p>
+
+ <pre class="brush: js notranslate"><code>ctx.fillStyle = 'rgba(255, 0, 255, 0.75)';
+ctx.fillRect(25, 100, 175, 50);</code></pre>
+ </li>
+ <li>
+ <p>现在你可以自己尝试绘制一些矩形了,玩得开心!</p>
+ </li>
+</ol>
+
+<h3 id="描边(stroke)和线条宽度">描边(stroke)和线条宽度</h3>
+
+<p>目前我们绘制的矩形都是填充颜色的,我们也可以绘制仅包含外部框线(图形设计中称为<strong>描边</strong>)的矩形。你可以使用 {{domxref("CanvasRenderingContext2D.strokeStyle", "strokeStyle")}} 属性来设置描边颜色,使用  {{domxref("CanvasRenderingContext2D.strokeRect", "strokeRect")}} 来绘制一个矩形的轮廓。</p>
+
+<ol>
+ <li>
+ <p>在上文的 JS 代码的末尾添加以下代码:</p>
+
+ <pre class="brush: js notranslate">ctx.strokeStyle = 'rgb(255, 255, 255)';
+ctx.strokeRect(25, 25, 175, 200);</pre>
+ </li>
+ <li>
+ <p>默认的描边宽度是 1 像素,可以通过调整 {{domxref("CanvasRenderingContext2D.lineWidth", "lineWidth")}} 属性(接受一个表示描边宽度像素值的数字)的值来修改。在上文两行后添加以下代码(读者注:此代码要放到两行代码中间才有效):</p>
+
+ <pre class="brush: js notranslate">ctx.lineWidth = 5;</pre>
+ </li>
+</ol>
+
+<p>现在可以看到白色的外边框变得更粗了。就这么简单,示例看上去像这样:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/getting-started/2_canvas_rectangles.html", '100%', 250)}}</p>
+
+<div class="note">
+<p><strong>注</strong>: 完整代码请访问 GitHub: <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/2_canvas_rectangles.html">2_canvas_rectangles.html</a>。</p>
+</div>
+
+<h3 id="绘制路径">绘制路径</h3>
+
+<p>可以通过绘制路径来绘制比矩形更复杂的图形。路径中至少要包含钢笔运行精确路径的代码以确定图形的形状。画布提供了许多函数用来绘制直线、圆、贝塞尔曲线,等等。</p>
+
+<p>重新复制一份(<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>),然后在其中绘制新的示例。</p>
+
+<p>一些通用的方法和属性将贯穿以下全部内容:</p>
+
+<ul>
+ <li>{{domxref("CanvasRenderingContext2D.beginPath", "beginPath()")}}:在钢笔当前所在位置开始绘制一条路径。在新的画布中,钢笔起始位置为 (0, 0)。</li>
+ <li>{{domxref("CanvasRenderingContext2D.moveTo", "moveTo()")}}:将钢笔移动至另一个坐标点,不记录、不留痕迹,只将钢笔“跳”至新位置。</li>
+ <li>{{domxref("CanvasRenderingContext2D.fill", "fill()")}}:通过为当前所绘制路径的区域填充颜色来绘制一个新的填充形状。</li>
+ <li>{{domxref("CanvasRenderingContext2D.stroke", "stroke()")}}:通过为当前绘制路径的区域描边,来绘制一个只有边框的形状。</li>
+ <li>路径也可和矩形一样使用 <code>lineWidth</code> 和 <code>fillStyle</code> / <code>strokeStyle</code> 等功能。</li>
+</ul>
+
+<p>以下是一个典型的简单路径绘制选项:</p>
+
+<pre class="brush: js notranslate">ctx.fillStyle = 'rgb(255, 0, 0)';
+ctx.beginPath();
+ctx.moveTo(50, 50);
+// 绘制路径
+ctx.fill();</pre>
+
+<h4 id="画线">画线</h4>
+
+<p>我们来在画布上绘制一个等边三角形。</p>
+
+<ol>
+ <li>
+ <p>首先,在代码底部添加下面的辅助函数。它可以将角度换算为弧度,在为 JavaScript 提供角度值时非常实用,JS 基本上只接受弧度值,而人类更习惯用角度值。</p>
+
+ <pre class="brush: js notranslate">function degToRad(degrees) {
+ return degrees * Math.PI / 180;
+};</pre>
+ </li>
+ <li>
+ <p>然后,在刚才复制好的文件中添加下面的内容,以开始路径的绘制。此处为我们为三角形设置了颜色,准备绘制,然后将钢笔移动至 (50, 50)(没有绘制任何内容)。然后准备在新的坐标开始绘制三角形。</p>
+
+ <pre class="brush: js notranslate">ctx.fillStyle = 'rgb(255, 0, 0)';
+ctx.beginPath();
+ctx.moveTo(50, 50);</pre>
+ </li>
+ <li>
+ <p>接下来在脚本中添加以下代码:</p>
+
+ <pre class="brush: js notranslate">ctx.lineTo(150, 50);
+var triHeight = 50 * Math.tan(degToRad(60));
+ctx.lineTo(100, 50+triHeight);
+ctx.lineTo(50, 50);
+ctx.fill();</pre>
+
+ <p>我们来逐行解释:</p>
+
+ <p>首先绘制一条直线,终点坐标为 (150, 50)。此时路径沿 x 轴向右行走 100 像素。</p>
+
+ <p>然后利用三角函数来计算等边三角形的高。这里我们要绘制的三角形是朝下的。等边三角形每个角均为 60°,为计算高的值,我们可以将三角形从正中心分割为两个直角三角形,每个直角三角形的三个角分别为 90°、60°、30°。对于边:</p>
+
+ <ul>
+ <li>最长的边称为 <strong>斜边</strong>。</li>
+ <li>紧挨 60° 角的边称为 <strong>临边</strong>,显然地,它的长度是刚才绘制的线的一半,即 50 像素。</li>
+ <li>60° 角对面的边称为 <strong>对边</strong>,即三角形的高,需要计算得到。</li>
+ </ul>
+
+ <p><img alt="" src="https://mdn.mozillademos.org/files/16289/%E4%B8%89%E8%A7%92%E5%87%BD%E6%95%B0.png" style="display: block; margin: 0 auto;"></p>
+
+ <p>通过基本三角函数可得:临边长度乘以角的正切等于对边长度。于是可得三角形的高为 <code>50 * Math.tan(degToRad(60))</code>。由于 {{jsxref("Math.tan()")}} 接受数值的单位为弧度,于是我们用刚才的 <code>degToRad()</code> 函数将 60° 换算为弧度。</p>
+ </li>
+ <li>
+ <p>有了三角形的高,我们来绘制另一条线,终点坐标为 <code>(100, 50+triHeight)</code>。X 坐标值很简单,应在刚才绘制的水平线两顶点正中间位置。Y 值应为 50 加上三角形的高,因为高即三角形底边到顶点的距离。</p>
+ </li>
+ <li>
+ <p>下一条线的终点坐标为绘制整个三角形的起点坐标。</p>
+ </li>
+ <li>
+ <p>最后,运行 <code>ctx.fill()</code> 来终止路径,并为图形填充颜色。</p>
+ </li>
+</ol>
+
+<h4 id="画圆">画圆</h4>
+
+<p>下面来看可在画布中绘制圆的方法—— {{domxref("CanvasRenderingContext2D.arc", "arc()")}} ,通过连续的点来绘制整个圆或者弧(arc,即局部的圆)。</p>
+
+<ol>
+ <li>
+ <p>在代码中添加以下几行,以向画布中添加一条弧。</p>
+
+ <pre class="notranslate">ctx.fillStyle = 'rgb(0, 0, 255)';
+ctx.beginPath();
+ctx.arc(150, 106, 50, degToRad(0), degToRad(360), false);
+ctx.fill();</pre>
+
+ <p><code>arc()</code> 函数有六个参数。前两个指定圆心的位置坐标,第三个是圆的半径,第四、五个是绘制弧的起、止角度(给定 0° 和 360° 便能绘制一个完整的圆),第六个是绘制方向(<code>false</code> 是顺时针,<code>true</code> 是逆时针)。</p>
+
+ <div class="note">
+ <p><strong>注:</strong>0° 设定为水平向右。</p>
+ </div>
+ </li>
+ <li>
+ <p>我们再来画一条弧:</p>
+
+ <pre class="notranslate">ctx.fillStyle = 'yellow';
+ctx.beginPath();
+ctx.arc(200, 106, 50, degToRad(-45), degToRad(45), true);
+ctx.lineTo(200, 106);
+ctx.fill();</pre>
+
+ <p>模式基本一样,但有两点不同:</p>
+
+ <ul>
+ <li>将 <code>arc()</code> 的最后一个参数设置为 <code>true</code>,意味着弧将逆时针绘制,也就意味着即使起、止角度分别设置为 -45°、45°,我们还是得到了区域外的一条 270° 的弧。如果把 <code>true</code> 改为<code>false</code> 重新运行,将得到 90° 的弧。</li>
+ <li>在调用 <code>fill()</code> 前,我们绘制了一条终点为圆心的直线。然后我们就渲染出一个惟妙惟肖的吃豆人模型。如果删除这条线(试试呗)再重新运行代码,你只能得到一个起止点间被砍掉一块的圆。这向我们展示了画布的另一个重要事项:如果要填充一个未完成(也就是没有首尾相接)的路径,浏览器将在起、止点件绘制一条直线,然后直接填充。</li>
+ </ul>
+ </li>
+</ol>
+
+<p>示例现在应该跟下图一致:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/getting-started/3_canvas_paths.html", '100%', 200)}}</p>
+
+<div class="note">
+<p><strong>注:</strong>完整代码可到 GitHub 下载:<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/3_canvas_paths.html">3_canvas_paths.html</a>。</p>
+</div>
+
+<div class="note">
+<p><strong>注:</strong>请访问我们的 <a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes">用画布绘图</a> 入门课程来学习更多高级的路径绘制功能,比如贝叶斯曲线。</p>
+</div>
+
+<h3 id="文本">文本</h3>
+
+<p>画布可用于绘制文本。我们简要学习一下。首先再次下载一份新的画布模板(<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.htm</a>),我们用它来绘制新的示例。</p>
+
+<p>以下两个函数用于绘制文本:</p>
+
+<ul>
+ <li>{{domxref("CanvasRenderingContext2D.fillText", "fillText()")}} :绘制有填充色的文本。</li>
+ <li>{{domxref("CanvasRenderingContext2D.strokeText", "strokeText()")}}:绘制文本外边框(描边)。</li>
+</ul>
+
+<p>这两个函数有三个基本的参数:需要绘制的文字、<strong>文本框</strong>(顾名思义,围绕着需要绘制文字的方框)左上顶点的X、Y坐标。</p>
+
+<p>还有一系列帮助控制文本渲染的属性:比如用于指定字体族、字号的 {{domxref("CanvasRenderingContext2D.font", "font")}},它的值和语法与 CSS 的 {{cssxref("font")}} 属性一致。</p>
+
+<p>在 JS 代码底部添加以下内容:</p>
+
+<pre class="brush: js notranslate">ctx.strokeStyle = 'white';
+ctx.lineWidth = 1;
+ctx.font = '36px arial';
+ctx.strokeText('Canvas text', 50, 50);
+
+ctx.fillStyle = 'red';
+ctx.font = '48px georgia';
+ctx.fillText('Canvas text', 50, 150);</pre>
+
+<p>将绘制两行文字,一行描边文字一行填充颜色的文字。示例最终如下所示:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/getting-started/4_canvas_text.html", '100%', 180)}}</p>
+
+<div class="note">
+<p><strong>注:</strong>完整代码可到 GitHub 下载:<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/4_canvas_text.html">4_canvas_text.html</a>.</p>
+</div>
+
+<p>可以自己尝试一下。访问 <a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Drawing_text">绘制文本</a> 获得关于画布文本选项的更多信息。</p>
+
+<h3 id="在画布上绘制图片">在画布上绘制图片</h3>
+
+<p>可在画布上渲染外部图片,简单图片文件、视频帧、其他画布内容都可以。这里我们只考虑简单图片文件的情况:</p>
+
+<ol>
+ <li>
+ <p>同上,下载画布模板(<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>)以绘制新的示例。这里还需要在同一目录下保存一个示例图片文件:<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/firefox.png">firefox.png</a>。</p>
+
+ <p>{{domxref("CanvasRenderingContext2D.drawImage", "drawImage()")}} 方法可将图片绘制在画布上。 最简单的版本需要三个参数:需要渲染的图片、图片左上角的X、Y坐标。</p>
+ </li>
+ <li>
+ <p>将图片源嵌入画布中,代码如下:</p>
+
+ <pre class="brush: js notranslate">var image = new Image();
+image.src = 'firefox.png';</pre>
+
+ <p>这里使用 {{domxref("HTMLImageElement.Image()", "Image()")}} 构造器创建了一个新的 {{domxref("HTMLImageElement")}} 对象。返回对象的类型与非空 {{htmlelement("img")}} 元素的引用是一致的。然后将它的 {{htmlattrxref("src", "img")}} 属性设置为 Firefox 的图标。此时浏览器将开始载入这张图片。</p>
+ </li>
+ <li>
+ <p>这次我们尝试用 <code>drawImage()</code> 函数来嵌入图片,应确保图片先载入完毕,否则运行会出错。可以通过 <code>onload</code> 事件处理器来达成,该函数只在图片调用完毕后才会调用。在上文代码末尾添加以下内容:</p>
+
+ <pre class="brush: js notranslate">image.onload = function() {
+ ctx.drawImage(image, 50, 50);
+}</pre>
+
+ <p>保存刷新,可以看到图片成功嵌入画布中。</p>
+ </li>
+ <li>
+ <p>还有更多方式。如果仅需要显示图片的某一部分,或者需要改变尺寸,该怎么做呢?复杂版本的 <code>drawImage()</code> 可解决这两个问题。请更新 <code>ctx.drawImage()</code> 一行代码为:</p>
+
+ <pre class="brush: js notranslate">ctx.drawImage(image, 20, 20, 185, 175, 50, 50, 185, 175);</pre>
+
+ <ul>
+ <li>第一个参数不变,为图片引用。</li>
+ <li>参数 2、3 表示裁切部分左上顶点的坐标,参考原点为原图片本身左上角的坐标。原图片在该坐标左、上的部分均不会绘制出来。</li>
+ <li>参数 4、5 表示裁切部分的长、宽。</li>
+ <li>参数 6、7 表示裁切部分左上顶点在画布中的位置坐标,参考原点为画布左上顶点。</li>
+ <li>参数 8、9 表示裁切部分在画布中绘制的长、宽。本例中绘制时与裁切时面积相同,你也可以定制绘制的尺寸。</li>
+ </ul>
+ </li>
+</ol>
+
+<p>最终结果如下所示:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/getting-started/5_canvas_images.html", '100%', 260)}}</p>
+
+<div class="note">
+<p><strong>注:</strong>完整代码可到 GitHub 下载:<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/5_canvas_images.html">5_canvas_images.html</a>.</p>
+</div>
+
+<h2 id="循环和动画">循环和动画</h2>
+
+<p>目前我们学习了关于 2D 画布一些非常基础的用法,但是不学习动画你就无法体会画布的强大。画布是提供可编程图形的。如果你的作品不需要改变,那么你就只能永远面对那些静态图片了。</p>
+
+<h3 id="创建一个循环">创建一个循环</h3>
+
+<p>在画布中使用循环是件有趣的事,你可以在 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/for">for</a></code> 循环中运行画布命令,和其他 JS 代码一样。</p>
+
+<p>我们来创建一个简单的示例。</p>
+
+<ol>
+ <li>
+ <p>继续复制一份画布模板(<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>)在代码编辑器中打开。</p>
+ </li>
+ <li>
+ <p>在 JS 代码末尾添加以下一行。这将创建一个新方法——{{domxref("CanvasRenderingContext2D.translate", "translate()")}},可用于移动画布的原点。</p>
+
+ <pre class="brush: js notranslate">ctx.translate(width/2, height/2);</pre>
+
+ <p>这会使原点 (0, 0) 从画布左上顶点移动至画布正中心。这个功能在许多场合非常实用,就像本示例,我们的绘制操作都是围绕着画布的中心点展开的。</p>
+ </li>
+ <li>
+ <p>在 JS 代码末尾添加以下内容:</p>
+
+ <pre class="brush: js notranslate">function degToRad(degrees) {
+ return degrees * Math.PI / 180;
+};
+
+function rand(min, max) {
+ return Math.floor(Math.random() * (max-min+1)) + (min);
+}
+
+var length = 250;
+var moveOffset = 20;
+
+for(var i = 0; i &lt; length; i++) {
+
+}</pre>
+
+ <p>这里我们实现了一个与上文三角形示例中相同的 <code>degToRad()</code> 函数、一个返回给定范围内随机数 <code>rand()</code> 函数、<code>length</code> 和 <code>moveOffset</code> 变量(见下文),以及一个空的 <code>for</code> 循环。</p>
+ </li>
+ <li>
+ <p>此处的理念是利用 <code>for</code> 循环在画布上循环迭代绘制好玩儿的内容。请将以下代码添加进 <code>for</code> 循环中:</p>
+
+ <pre class="brush: js notranslate">ctx.fillStyle = 'rgba(' + (255-length) + ', 0, ' + (255-length) + ', 0.9)';
+ctx.beginPath();
+ctx.moveTo(moveOffset, moveOffset);
+ctx.lineTo(moveOffset+length, moveOffset);
+var triHeight = length/2 * Math.tan(degToRad(60));
+ctx.lineTo(moveOffset+(length/2), moveOffset+triHeight);
+ctx.lineTo(moveOffset, moveOffset);
+ctx.fill();
+
+length--;
+moveOffset += 0.7;
+ctx.rotate(degToRad(5));</pre>
+
+ <p>在每次迭代中:</p>
+
+ <ul>
+ <li>设置 <code>fillStyle</code> 为略透明的紫色渐变色。渐变由每次迭代时 <code>length</code> 值的改变实现。随着循环的运行, <code>length</code> 值逐渐变小,从而使连续的三角形颜色逐渐变亮。</li>
+ <li>开始路径。</li>
+ <li>将钢笔移动至坐标 <code>(moveOffset, moveOffset)</code>;该变量定义了每次要绘制新三角形时需要移动的距离。</li>
+ <li>画一条直线,终点坐标为 <code>(moveOffset+length, moveOffset)</code>。即一条长度为 <code>length</code> 与 X 轴平行的线。</li>
+ <li>计算三角形的高,方法同上。</li>
+ <li>向三角形底部顶点方向绘制一条直线,然后向三角形的起始点绘制一条直线。</li>
+ <li>调用 <code>fill()</code> 为三角形填充颜色。</li>
+ <li>更新次序变量,准备绘制下一个三角形。<code>length</code> 的值减一,使三角形每次迭代都变小一些;小幅增加 <code>moveOffset</code> 的值,使得下一个三角形略微错位;用一个新函数 {{domxref("CanvasRenderingContext2D.rotate", "rotate()")}} 来旋转整块画布,在绘制下个三角形前画布旋转 5°。</li>
+ </ul>
+ </li>
+</ol>
+
+<p>好了,最终结果如下:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/6_canvas_for_loop.html", '100%', 550)}}</p>
+
+<p>现在,你可以尝试这个示例,可以加一些创新哦。比如:</p>
+
+<ul>
+ <li>把三角形换成矩形、弧,甚至内嵌的图片。</li>
+ <li>修改 <code>length</code> 和 <code>moveOffset</code> 的值。</li>
+ <li>我们引入了 <code>rand()</code> 函数但是没有使用,你可以试着用它引入一些随机数。</li>
+</ul>
+
+<div class="note">
+<p><strong>注:</strong>完整代码可到 GitHub 下载:<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/loops_animation/6_canvas_for_loop.html">6_canvas_for_loop.html</a>.</p>
+</div>
+
+<h3 id="动画">动画</h3>
+
+<p>上文的循环示例很有趣,其实在重度画布应用(比如游戏或实时可视化)中恒定循环是至关重要的支持组件。如果期望画布显示的内容像一部电影,屏幕最好能够以 60 帧每秒的刷新率实时更新,这样人眼看到的动作才更真实、更平滑。</p>
+
+<p>一些 JavaScript 函数可以让函数在一秒内重复运行多次,这里最适合的就是 {{domxref("window.requestAnimationFrame()")}}。它只取一个参数,即每帧要运行的函数名。下一次浏览器准备好更新屏幕时,将会调用你的函数。如果你的函数向动画中绘制了更新内容,则在函数结束前再次调用 <code>requestAnimationFrame()</code>,动画循环得以保留。只有在停止调用 <code>requestAnimationFrame()</code> 时,或 <code>requestAnimationFrame()</code> 调用后、帧调用前调用了 {{domxref("window.cancelAnimationFrame()")}} 时,循环才会停止。</p>
+
+<p><strong>注:</strong>动画结束后在主代码中调用 <code>cancelAnimationFrame()</code> 是良好习惯,可以确保不再有等待运行的更新。</p>
+
+<p>浏览器自行处理诸如“使动画匀速运行”、“避免在不可见的内容浪费资源”等复杂细节问题。</p>
+
+<p>我们简单回顾一下“弹球”示例(<a href="https://mdn.github.io/learning-area/javascript/oojs/bouncing-balls/index-finished.html">在线运行</a> 或查看 <a href="https://github.com/mdn/learning-area/tree/master/javascript/oojs/bouncing-balls">源代码</a>),来探究一下原理。以下是让弹球持续运行的循环代码:</p>
+
+<pre class="brush: js notranslate">function loop() {
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
+ ctx.fillRect(0, 0, width, height);
+
+ while(balls.length &lt; 25) {
+ var ball = new Ball();
+ balls.push(ball);
+ }
+
+ for(i = 0; i &lt; balls.length; i++) {
+ balls[i].draw();
+ balls[i].update();
+ balls[i].collisionDetect();
+ }
+
+ requestAnimationFrame(loop);
+}
+
+loop();</pre>
+
+<p>我们在代码底部运行了一次 <code>loop()</code> 函数,它启动了整个循环,绘制了第一帧动画。接着 <code>loop()</code> 函数接管了<code>requestAnimationFrame(loop)</code> 的调用工作,即运行下一帧、再下一帧……的动画。</p>
+
+<p>请注意每一帧我们都整体清除画布并重新渲染所有内容。(每帧创建一个新球(25 个封顶),然后绘制每个球,更新它们的位置,检查是否撞到了其它球。)向画布中绘制的新图形不能像DOM 元素那样单独操作。你无法再画布中单独操作某一个球,因为只要绘制完毕了,它就是画布的一部分,而不是一个单独的球。你需要擦除再重画,可以将整帧擦除再重画整个画面,也可通过编程选择最小的部分进行擦除和重画。</p>
+
+<p>优化图形动画是另一个编程主题,需要好多奇技淫巧。这超出我们的讨论范围啦。</p>
+
+<p>一般地,在画布上制作动画需要以下步骤:</p>
+
+<ol>
+ <li>清除画布内容(可用 {{domxref("CanvasRenderingContext2D.fillRect", "fillRect()")}} 或 {{domxref("CanvasRenderingContext2D.clearRect", "clearRect()")}})。</li>
+ <li>(在需要时)用 {{domxref("CanvasRenderingContext2D.save", "save()")}} 保存状态。(在进行下一步前保存所更新的设置,一般在复杂环境中用到)</li>
+ <li>绘制动画图形。</li>
+ <li>使用 {{domxref("CanvasRenderingContext2D.restore", "restore()")}} 恢复第 2 步中保存的状态。</li>
+ <li>调用 <code>requestAnimationFrame()</code> 准备下一帧动画。</li>
+</ol>
+
+<div class="note">
+<p><strong>注:</strong><code>save()<font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="background-color: #fff3d4;"> 和 </span></font></code><code>restore()</code> 这里暂不展开,可以访问 <a href="/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations">变形</a> 教程(及后续内容)来获取详细信息。</p>
+</div>
+
+<h3 id="一个简单的人物动画">一个简单的人物动画</h3>
+
+<p>现在我们来创建一个简单的动画,我们找来一个复古的电脑游戏的主角制作一个在屏幕上行走的动画。</p>
+
+<ol>
+ <li>
+ <p>继续复制一份画布模板(<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>)在代码编辑器中打开。下载 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/loops_animation/walk-right.png">walk-right.png</a> 并放在同一文件夹。</p>
+ </li>
+ <li>
+ <p>在 JS 代码末尾添加下面一行,再次将画布的原点设置为中心点。</p>
+
+ <pre class="brush: js notranslate">ctx.translate(width/2, height/2);</pre>
+ </li>
+ <li>
+ <p>创建一个新的 {{domxref("HTMLImageElement")}} 对象,把它的 {{htmlattrxref("src", "img")}} 设置为所需图片,添加一个 <code>onload</code> 事件处理器,使 <code>draw()</code> 函数在图片载入后触发。</p>
+
+ <pre class="brush: js notranslate">var image = new Image();
+image.src = 'walk-right.png';
+image.onload = draw;</pre>
+ </li>
+ <li>
+ <p>添加一些变量,来追踪精灵图在屏幕上的位置,以及当前需要显示的精灵图的序号。</p>
+
+ <pre class="brush: js notranslate">var sprite = 0;
+var posX = 0;</pre>
+
+ <p>我们来解释一下“精灵图序列”(我们借鉴了麦克托马斯的 <a href="http://atomicrobotdesign.com/blog/htmlcss/create-a-sprite-sheet-walk-cycle-using-using-css-animation/" rel="bookmark" title="Permanent Link to Create a sprite sheet walk cycle using using CSS animation">使用 CSS 动画创建人物行走的精灵图</a>)。图片如下:</p>
+
+ <p><img alt="" src="https://mdn.mozillademos.org/files/14847/walk-right.png" style="display: block; margin: 0 auto;"></p>
+
+ <p>图中包含六个精灵,它们组成了一趟完整的行走序列。每个精灵的尺寸为 102 × 148 像素。为了整齐的显示一个精灵,可以通过 <code>drawImage()</code> 来从序列中裁切出单独的精灵并隐藏其他部分,就像上文中操作 Firefox 图标的方法。切片的 X 坐标应为 102 的倍数,Y 坐标恒为 0。切片尺寸恒为 102 × 148 像素。</p>
+ </li>
+ <li>
+ <p>在代码末尾添加一个空的 <code>draw()</code> 函数,用来添加一些代码:</p>
+
+ <pre class="brush: js notranslate">function draw() {
+
+};</pre>
+ </li>
+ <li>
+ <p>本节剩余部分都在这个 <code>draw()</code> 中展开。首先,添加以下代码,清除画布,准备绘制新的帧。注意由于我们刚才将原点设置为 <code>width/2, height/2</code>,这里需要将矩形左上顶点的坐标设置为 <code>-(width/2), -(height/2)</code>。</p>
+
+ <pre class="brush: js notranslate">ctx.fillRect(-(width/2), -(height/2), width, height);</pre>
+ </li>
+ <li>
+ <p>下一步,我们使用 <code>drawImage()</code>(9参数版本)来绘制图形,添加以下代码:</p>
+
+ <pre class="brush: js notranslate">ctx.drawImage(image, (sprite*102), 0, 102, 148, 0+posX, -74, 102, 148);</pre>
+
+ <p>如你所见:</p>
+
+ <ul>
+ <li><code>image</code> 指定需要嵌入的图片。</li>
+ <li>参数 2、3 指定切片左上顶点在原图的位置坐标,X 值为 <code>sprite</code>(精灵序列 0 - 5)乘 102,Y 值恒为 0。</li>
+ <li>参数 4、5 指定切片尺寸:102 × 148 像素。</li>
+ <li>参数 6、7 指定切片在画布绘制区域的坐上顶点坐标。X 坐标位置为 0 + <code>posX</code>,意味着我们可以通过修改 <code>posX</code> 的值来修改绘制的位置。</li>
+ <li>参数 8、9 指定图片在画布中的尺寸。这里需要图片保持原始尺寸,因此我们指定宽、高值为 102、148。</li>
+ </ul>
+ </li>
+ <li>
+ <p>现在,我们在每帧绘制完毕(部分完毕)后修改 <code>sprite</code> 的值。在 <code>draw()</code> 函数底部添加以下内容:</p>
+
+ <pre class="brush: js notranslate"> if (posX % 13 === 0) {
+ if (sprite === 5) {
+ sprite = 0;
+ } else {
+ sprite++;
+ }
+ }</pre>
+
+ <p>将整个功能块放置在 <code>if (posX % 13 === 0) { ... }</code> 内。用“模(<code>%</code>)运算符”(即 <a href="/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Remainder_()">求余运算符</a>)来检测 <code>posX</code> 是否可以被 13 整除。如果整除,则通过增加 <code>sprite</code> 的值转至下一个精灵(到 5 号精灵时归零)。这实际上意味着每隔 13 帧才更新一次精灵,每秒大约更新 5 帧(<code>requestAnimationFrame()</code> 每秒最多调用 60 帧)。我们故意放慢了帧率,因为精灵图只有六个,且如果每秒显示 60 帧的话,这个角色就会快到起飞。</p>
+
+ <p>外部程序块中用一个 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/if...else">if ... else</a></code> 语句来检测 <code>sprite</code> 的值是否为 5(精灵序号在 0 - 5 间循环,因此 5 代表最后一个精灵)。 如果最后一个精灵已经显示,就把 <code>sprite</code> 重置为 0,否则加 1。</p>
+ </li>
+ <li>
+ <p>下一步要算出每帧 <code>posX</code> 的值,在上文代码末尾添加以下内容:</p>
+
+ <pre class="brush: js notranslate"> if(posX &gt; width/2) {
+ newStartPos = -((width/2) + 102);
+ posX = Math.ceil(newStartPos / 13) * 13;
+ console.log(posX);
+ } else {
+ posX += 2;
+ }</pre>
+
+ <p>用另一个 <code>if ... else</code> 来检测 <code>posX</code> 的值是否超出了 <code>width/2</code>,那意味着角色走到了屏幕右侧边缘。如果这样就计算出一个让角色出现在屏幕左侧边缘的 X 坐标,然后将 <code>posX</code> 设置为最接近这个数的 13 的倍数。这里必须限定 13 的倍数这个条件,这是因为 <code>posX</code> 不可能是 13 的倍数,若不限定的话上一段代码就不会运行了。</p>
+
+ <p>如果角色没有走到屏幕边缘,只需为 <code>posX</code> 加 2。这将让他在下次绘制时更靠右些。</p>
+ </li>
+ <li>
+ <p>最后,通过在 <code>draw()</code> 函数末尾添加 {{domxref("window.requestAnimationFrame", "requestAnimationFrame()")}} 调用以实现动画的循环。</p>
+
+ <pre class="brush: js notranslate">window.requestAnimationFrame(draw);</pre>
+ </li>
+</ol>
+
+<p>成功了!最终效果如下所示:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}</p>
+
+<div class="note">
+<p><strong>注:</strong>完整代码可到 GitHub 下载:<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html">7_canvas_walking_animation.html</a>.</p>
+</div>
+
+<h3 id="简单的绘图应用">简单的绘图应用</h3>
+
+<p>下面来演示一个简单的绘图应用,作为最后一个绘画示例,它将向你展示动画循环如果与用户输入(本例中为鼠标移动)结合起来。我们不会带你一步一步来实现本示例,只对代码中最有趣的部分进行探究。</p>
+
+<p>示例代码可到GitHub下载:<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/loops_animation/8_canvas_drawing_app.html">8_canvas_drawing_app.html</a>,也可在线试玩:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/8_canvas_drawing_app.html", '100%', 600)}}</p>
+
+<p>下面我们就来看看代码的精华部分。首先,用 <code>curX</code>、<code>curY</code> 和 <code>pressed</code> 这三个变量来跟踪鼠标的 X、Y 坐标和点击状态。当鼠标移动时,触发一个函数作为 <code>onmousemove</code> 事件处理器,其应捕获当前的 X 和 Y 值。再用 <code>onmousedown</code> 和 <code>onmouseup</code> 事件处理器来修改鼠标键按下时 <code>pressed</code> 的值(按下为 <code>true</code>,释放为  <code>false</code>)。</p>
+
+<pre class="brush: js notranslate">var curX;
+var curY;
+var pressed = false;
+
+document.onmousemove = function(e) {
+ curX = (window.Event) ? e.pageX : e.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
+ curY = (window.Event) ? e.pageY : e.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
+}
+
+canvas.onmousedown = function() {
+ pressed = true;
+};
+
+canvas.onmouseup = function() {
+ pressed = false;
+}</pre>
+
+<p>在按下“Clear canvas”(清除画布)按钮时,我们运行一个简单的函数来清除整个画布的内容至纯黑色,和刚才的方法一致:</p>
+
+<pre class="brush: js notranslate">clearBtn.onclick = function() {
+ ctx.fillStyle = 'rgb(0, 0, 0)';
+ ctx.fillRect(0, 0, width, height);
+}</pre>
+
+<p>这次的绘图循环非常简单,如果 <code>pressed</code> 为 <code>true</code>,则绘制一个圆,该圆以颜色选择器中设定的颜色为背景,以滑动选择器设定的数值为半径。</p>
+
+<pre class="brush: js notranslate">function draw() {
+ if(pressed) {
+ ctx.fillStyle = colorPicker.value;
+ ctx.beginPath();
+ ctx.arc(curX, curY-85, sizePicker.value, degToRad(0), degToRad(360), false);
+ ctx.fill();
+ }
+
+ requestAnimationFrame(draw);
+}
+
+draw();</pre>
+
+<div class="note">
+<p><strong>注:</strong><code>range</code> 和 <code>color</code> 两个 {{htmlelement("input")}} 的类型有良好的跨浏览器支持,但 Safari 暂不支持 <code>color</code>,IE 10 以下版本两者均不支持。如果你的浏览器不支持这些输入类型,它们将降格为简单文字输入区域,可以直接输入合法的数字来表示半径和颜色的值。</p>
+</div>
+
+<h2 id="WebGL">WebGL</h2>
+
+<p>2D 内容告一段落,现在简单了解一下 3D 画布。3D 画布内容可通过的 <a href="/zh-CN/docs/Web/API/WebGL_API">WebGL</a> API 实现,尽管它和 2D canvas API 都可在 {{htmlelement("canvas")}} 元素上进行渲染,但两者是彼此独立的。</p>
+
+<p>WebGL 基于 <a href="/en-US/docs/Glossary/OpenGL">OpenGL</a> 图形编程语言实现,可直接与 <a href="/en-US/docs/Glossary/GPU">GPU</a> 通信,基于此,编写纯 WebGL 代码与常规的 JavaScript 不尽相同,更像 C++ 那样的底层语言,更加复杂,但无比强大。</p>
+
+<h3 id="使用库">使用库</h3>
+
+<p>由于 3D 绘图的复杂性,大多数人写代码时会使用第三方 JavaScript 库(比如 <a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Three.js</a>、<a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_PlayCanvas">PlayCanvas</a> 或 <a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Babylon.js">Babylon.js</a>)。大多数库的原理都基本类似,提供创建基本的、自定义性状的功能、视图定位摄影和光效、表面纹理覆盖,等等。库负责 与 WebGL 通信,你只需完成更高阶工作。</p>
+
+<p>接触任何一个库都意味着要学一套全新的API(这里是第三方的版本),但与纯 WebGL 编程都大同小异。</p>
+
+<h3 id="创建魔方">创建魔方</h3>
+
+<p>我们来看一个简单的示例,用一套 WebGL 库(这里我们选择 <a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Three.js</a>,最流行的 3D 绘图库之一)来创建我们在本文开头看到的旋转魔方。</p>
+
+<ol>
+ <li>
+ <p>首先,下载 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/threejs-cube/index.html">index.html</a>、<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/threejs-cube/metal003.png">metal003.png</a> 并保存在同一个文件夹。图片将用于魔方的表面纹理。</p>
+ </li>
+ <li>
+ <p>然后,继续在同一个文件夹内创建 <code>main.js</code> 文件。</p>
+ </li>
+ <li>
+ <p>在编辑器中打开 <code>index.html</code> 可以看到其中有两个 {{htmlelement("script")}} 元素,第一个将 <code>three.min.js</code> 嵌入页面,第二个将我们的 <code>main.js</code> 嵌入页面。需要自行 <a href="https://raw.githubusercontent.com/mrdoob/three.js/dev/build/three.min.js">下载 three.min.js 库</a> 并保存到同一个文件夹中。</p>
+ </li>
+ <li>
+ <p>将 <code>three.js</code> 嵌入页面后,就可以在 <code>main.js</code> 中添加新的代码对其加以应用了。请添加下面一行:</p>
+
+ <pre class="brush: js notranslate">var scene = new THREE.Scene();</pre>
+
+ <p><code><a href="https://threejs.org/docs/index.html#Reference/Scenes/Scene">Scene()</a></code> 构造器创建一个新的场景,表示即将显示的整个 3D 世界。</p>
+ </li>
+ <li>
+ <p>下一步,我们需要一部<strong>摄影机</strong>来看到整个场景。在 3D 绘图语境中,摄影机表示观察者在世界里的位置,可通过下面代码创建一部摄影机:</p>
+
+ <pre class="brush: js notranslate">var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
+camera.position.z = 5;
+</pre>
+
+ <p><code><a href="https://threejs.org/docs/index.html#Reference/Cameras/PerspectiveCamera">PerspectiveCamera()</a></code> 构造器有四个参数:</p>
+
+ <ul>
+ <li>观察区域:镜头视角大小,用角度表示。</li>
+ <li>屏幕宽高比:一般情况下,宽高比等于屏幕的宽比上屏幕的高。使用其他的值会使场景扭曲(也许正是你需要的,但一般都不是)。</li>
+ <li>近裁切面:停止渲染前对象离摄影机的最近距离。设想一下,举起一个手指,逐渐移近双眼,某个点后就在也看不到这根手指了。</li>
+ <li>远裁切面:停止渲染前离摄像机最远的对象的距离。</li>
+ </ul>
+
+ <p>将摄像机的位置设定为距 Z 轴 5 个距离单位的位置。与 CSS 类似,在屏幕之外你(观察者)的位置。</p>
+ </li>
+ <li>
+ <p>第三个重要参数是渲染器。我们用它来渲染给定的场景,可通过给定位值得摄影机观察。现在我们使用 <code><a href="https://threejs.org/docs/index.html#Reference/Renderers/WebGLRenderer">WebGLRenderer()</a></code> 构造器创建一个渲染器供稍后使用。添加以下代码:</p>
+
+ <pre class="brush: js notranslate">var renderer = new THREE.WebGLRenderer();
+renderer.setSize(window.innerWidth, window.innerHeight);
+document.body.appendChild(renderer.domElement);</pre>
+
+ <p>第一行创建一个新的渲染器,第二行设定渲染器在当前摄影机视角下的尺寸,第三行将渲染好的 {{htmlelement("canvas")}} 对象加入HTML的 {{htmlelement("body")}} 中。现在渲染器绘制的内容将在窗口中显示出来。</p>
+ </li>
+ <li>
+ <p>下一步,在画布中创建魔方。把以下代码添加到 JS 文件中:</p>
+
+ <pre class="brush: js notranslate">var cube;
+
+var loader = new THREE.TextureLoader();
+
+loader.load( 'metal003.png', function (texture) {
+ texture.wrapS = THREE.RepeatWrapping;
+ texture.wrapT = THREE.RepeatWrapping;
+ texture.repeat.set(2, 2);
+
+ var geometry = new THREE.BoxGeometry(2.4, 2.4, 2.4);
+ var material = new THREE.MeshLambertMaterial( { map: texture, shading: THREE.FlatShading } );
+ cube = new THREE.Mesh(geometry, material);
+ scene.add(cube);
+
+ draw();
+});</pre>
+
+ <p>内容很多,我们来剥丝抽茧:</p>
+
+ <ul>
+ <li>首先,创建一个全局变量 <code>cube</code>,这样就可以在代码任意位置访问我们的魔方。</li>
+ <li>然后,创建一个 <code><a href="https://threejs.org/docs/index.html#Reference/Loaders/TextureLoader">TextureLoader</a></code> 对象,并调用 <code>load()</code>。 这里 <code>load()</code> 包含两个参数(其它情况可以有更多参数):需要调用的纹理图(PNG 文件)和纹理加载成功后调用的函数。</li>
+ <li>函数内部,我们用 <code><a href="https://threejs.org/docs/index.html#Reference/Textures/Texture">texture</a></code> 对象的属性指明我们要在魔方的每个面渲染 2 × 2 的图片,然后创建一个 <code><a href="https://threejs.org/docs/index.html#Reference/Geometries/BoxGeometry">BoxGeometry</a></code> 对象和一个 <code><a href="https://threejs.org/docs/index.html#Reference/Materials/MeshLambertMaterial">MeshLambertMaterial</a></code> 对象,将两者作为 <code><a href="https://threejs.org/docs/index.html#Reference/Objects/Mesh">Mesh</a></code> 的参数来创建我们的魔方。 <code><a href="https://threejs.org/docs/index.html#Reference/Objects/Mesh">Mesh</a></code> 一般就需要两个参数:一个几何(形状)和一个素材(形状表面外观)。</li>
+ <li>最后,将魔方添加进场景中,调用我们的 <code>draw()</code> 函数开始动画。</li>
+ </ul>
+ </li>
+ <li>
+ <p>定义 <code>draw()</code> 函数前,我们需要先为场景打光,以照亮场景中的物体。请添加以下代码:</p>
+
+ <pre class="brush: js notranslate">var light = new THREE.AmbientLight('rgb(255, 255, 255)'); // soft white light
+scene.add(light);
+
+var spotLight = new THREE.SpotLight('rgb(255, 255, 255)');
+spotLight.position.set( 100, 1000, 1000 );
+spotLight.castShadow = true;
+scene.add(spotLight);</pre>
+
+ <p><code><a href="https://threejs.org/docs/index.html#Reference/Lights/AmbientLight">AmbientLight</a></code> 对象是可以轻度照亮整个场景的柔光,就像户外的阳光。而 <code><a href="https://threejs.org/docs/index.html#Reference/Lights/SpotLight">SpotLight</a></code> 对象是直射的硬光,就像闪光灯和手电筒(或者它的英文字面意思——聚光灯)。</p>
+ </li>
+ <li>
+ <p>最后,在代码末尾添加我们的 <code>draw()</code> 函数:</p>
+
+ <pre class="brush: js notranslate">function draw() {
+ cube.rotation.x += 0.01;
+ cube.rotation.y += 0.01;
+ renderer.render(scene, camera);
+
+ requestAnimationFrame(draw);
+}</pre>
+
+ <p>这段代码很直观,每一帧我们都沿 X 轴 和 Y 轴将魔方轻微转动,然后按摄像机视角渲染场景,最后调用 <code>requestAnimationFrame()</code> 来准备下一帧。</p>
+ </li>
+</ol>
+
+<p>回顾一下最终效果:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/threejs-cube/index.html", '100%', 500)}}</p>
+
+<p>你可以 <a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/drawing-graphics/threejs-cube">到 Github 下载最终代码</a>。</p>
+
+<div class="note">
+<p><strong>注:</strong>在我们的 GitHub repo 还有另一个趣味 3D 魔方示例——<a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/drawing-graphics/threejs-video-cube">Three.js Video Cube</a>(<a>在线查看</a>)。其中通过 {{domxref("MediaDevices.getUserMedia", "getUserMedia()")}} 来从电脑摄像头获取一段视频,将其投影到魔方上作为纹理。</p>
+</div>
+
+<h2 id="小结">小结</h2>
+
+<p>此刻你以经了解了一些 Canvas 和 WebGL 图形编程的基本理念和简单应用,你一定产生了不少创作灵感,玩得开心!</p>
+
+<h2 id="另请参阅">另请参阅</h2>
+
+<p>本文我们只涉及到画布最为基本的内容,以下内容帮你探索更多:</p>
+
+<ul>
+ <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial">Canvas 教程</a>:一个详尽的教程系列,更细致深入地讲解了 2D 画布所需的知识。必读。</li>
+ <li><a href="/zh-CN/docs/Web/API/WebGL_API/Tutorial">WebGL 教程</a>:纯 WebGL 编程教程系列。</li>
+ <li><a href="/zh-CN/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">用 Three.js 创建一个简单的示例</a>:Three.js 基础教程。我们还提供 <a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_PlayCanvas">PlayCanvas</a> 和 <a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Babylon.js">Babylon.js</a> 的基础教程。</li>
+ <li><a href="/zh-CN/docs/Games">游戏开发</a>:MDN web 游戏开发目录页。提供与 2D、3D画布相关的实用教程和技术,可参考“技术”和“教程”菜单项。</li>
+</ul>
+
+<h2 id="示例">示例</h2>
+
+<ul>
+ <li><a href="https://github.com/mdn/violent-theremin">Violent theramin</a>:用 Web 音频 API 创建声音,用画布显示漂亮的视觉效果以配合音乐。</li>
+ <li><a href="https://github.com/mdn/voice-change-o-matic">Voice change-o-matic</a>:用画布为 Web 音频 API 产生的音效提供实时的视觉效果。</li>
+</ul>
+
+<p>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Third_party_APIs", "Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</p>
+
+<h2 id="本章目录">本章目录</h2>
+
+<ul>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">web API 简介</a></li>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">文档操作</a></li>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">从服务器获得数据</a></li>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">第三方 API</a></li>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">绘图</a></li>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">视频API 和 音频 API</a></li>
+ <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">客户端存储</a></li>
+</ul>
diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/fetching_data/index.html b/files/zh-cn/learn/javascript/client-side_web_apis/fetching_data/index.html
new file mode 100644
index 0000000000..5ea749c536
--- /dev/null
+++ b/files/zh-cn/learn/javascript/client-side_web_apis/fetching_data/index.html
@@ -0,0 +1,395 @@
+---
+title: 从服务器获取数据
+slug: Learn/JavaScript/Client-side_web_APIs/Fetching_data
+tags:
+ - API
+ - Fetch
+ - JSON
+ - JavaScript
+ - Promises
+ - XHR
+ - XML
+ - XMLHttpRequest
+ - 初学者
+ - 学习
+ - 数据
+ - 服务器
+ - 脚本编程
+ - 请求
+translation_of: Learn/JavaScript/Client-side_web_APIs/Fetching_data
+---
+<div>{{LearnSidebar}}</div>
+
+<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Manipulating_documents", "Learn/JavaScript/Client-side_web_APIs/Third_party_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</div>
+
+<p class="summary">在现代网站和应用中另一个常见的任务是从服务端获取个别数据来更新部分网页而不用加载整个页面。 这看起来是小细节却对网站性能和行为产生巨大的影响。所以我们将在这篇文章介绍概念和技术使它成为可能,例如: XMLHttpRequest 和 Fetch API.</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">先决条件:</th>
+ <td>JavaScript 基础(查看 <a href="/zh-CN/docs/Learn/JavaScript/First_steps">第一步</a>, <a href="/zh-CN/docs/Learn/JavaScript/Building_blocks">基础要件</a>, <a href="/zh-CN/docs/Learn/JavaScript/Objects">JavaScript对象</a>), 和<a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">客户端API基础</a></td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>学会如何从服务器获取数据并使用它更新网页内容.</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="这里有什么问题">这里有什么问题?</h2>
+
+<p>最初加载页面很简单  -- 你为网站发送一个请求到服务器, 只要没有出错你将会获取资源并显示网页到你的电脑上。</p>
+
+<p><img alt="A basic representation of a web site architecture" src="https://mdn.mozillademos.org/files/6475/web-site-architechture@2x.png" style="display: block; height: 134px; margin: 0px auto; width: 484px;"></p>
+
+<p>这个模型的问题是当你想更新网页的任何部分,例如显示一套新的产品或者加载一个新的页面,你需要再一次加载整个页面。这是非常浪费的并且导致了差的用户体验尤其是现在的页面越来越大且越来越复杂。</p>
+
+<h3 id="Ajax开始">Ajax开始</h3>
+
+<p>这导致了创建允许网页请求小块数据(例如 <a href="/en-US/docs/Web/HTML">HTML</a>, {{glossary("XML")}}, <a href="/en-US/docs/Learn/JavaScript/Objects/JSON">JSON</a>, 或纯文本) 和 仅在需要时显示它们的技术,从而帮助解决上述问题。</p>
+
+<p>这是通过使用诸如 {{domxref("XMLHttpRequest")}} 之类的API或者 — 最近以来的 <a href="/en-US/docs/Web/API/Fetch_API">Fetch API</a> 来实现. 这些技术允许网页直接处理对服务器上可用的特定资源的 <a href="/en-US/docs/Web/HTTP">HTTP</a> 请求,并在显示之前根据需要对结果数据进行格式化。</p>
+
+<div class="note">
+<p><strong>注意:在早期,这种通用技术被称为</strong>Asynchronous JavaScript and XML<strong>(Ajax),</strong> 因为它倾向于使用{{domxref("XMLHttpRequest")}} 来请求XML数据。 但通常不是这种情况 (你更有可能使用 <code>XMLHttpRequest</code> 或 Fetch 来请求JSON), 但结果仍然是一样的,术语“Ajax”仍然常用于描述这种技术。</p>
+</div>
+
+<p><img alt="A simple modern architecture for web sites" src="https://mdn.mozillademos.org/files/6477/moderne-web-site-architechture@2x.png" style="display: block; height: 235px; margin: 0px auto; width: 559px;"></p>
+
+<p>Ajax模型包括使用Web API作为代理来更智能地请求数据,而不仅仅是让浏览器重新加载整个页面。让我们来思考这个意义:</p>
+
+<ol>
+ <li><font><font>去你最喜欢的信息丰富的网站之一,如亚马逊,油管,CNN等,并加载它。</font></font></li>
+ <li>现在搜索一些东西,比如一个新产品。 主要内容将会改变,但大部分周围的信息,如页眉,页脚,导航菜单等都将保持不变。</li>
+</ol>
+
+<p>这是一件非常好的事情,因为:</p>
+
+<ul>
+ <li><font><font>页面更新速度更快,您不必等待页面刷新,这意味着该网站体验感觉更快,响应更快。</font></font></li>
+ <li><font><font>每次更新都会下载更少的数据,这意味着更少地浪费带宽。</font><font>在宽带连接的桌面上这可能不是一个大问题,但是在移动设备和发展中国家没有无处不在的快速互联网服务是一个大问题。</font></font></li>
+</ul>
+
+<p>为了进一步提高速度,有些网站还会在首次请求时将资产和数据存储在用户的计算机上,这意味着在后续访问中,他们将使用本地版本,而不是在首次加载页面时下载新副本。 内容仅在更新后从服务器重新加载。</p>
+
+<p><img alt="A basic web app data flow architecture" src="https://mdn.mozillademos.org/files/6479/web-app-architecture@2x.png" style="display: block; height: 383px; margin: 0px auto; width: 562px;"></p>
+
+<p><font>本文不会涉及这种存储技术。</font><font>我们稍后会在模块中讨论它。</font></p>
+
+<h2 id="基本的Ajax请求">基本的Ajax请求</h2>
+
+<p>让我们看看使用{{domxref("XMLHttpRequest")}} 和 <a href="/en-US/docs/Web/API/Fetch_API">Fetch</a>如何处理这样的请求. 对于这些例子,我们将从几个不同的文本文件中请求数据,并使用它们来填充内容区域。</p>
+
+<p>这一系列文件将作为我们的假数据库; 在真正的应用程序中,我们更可能使用服务器端语言(如PHP,Python或Node)从数据库请求我们的数据。 不过,我们要保持简单,并专注于客户端部分。</p>
+
+<h3 id="XMLHttpRequest">XMLHttpRequest</h3>
+
+<p><code>XMLHttpRequest</code> (通常缩写为XHR)现在是一个相当古老的技术 - 它是在20世纪90年代后期由微软发明的,并且已经在相当长的时间内跨浏览器进行了标准化。</p>
+
+<ol>
+ <li>
+ <p>为例子做些准备, 将 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/ajax-start.html">ajax-start.html</a> 和四个文本文件 — <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse1.txt">verse1.txt</a>, <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse2.txt">verse2.txt</a>, <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse3.txt">verse3.txt</a>,  <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse4.txt">verse4.txt</a> — 复制到你计算机上的一个新目录. 在这个例子中,我们将通过XHR在下拉菜单中选择一首诗(您可能会认识 — "如果谷歌翻译可以翻译的话")加载另一首诗。</p>
+ </li>
+ <li>
+ <p>在 {{htmlelement("script")}} 的内部, 添加下面的代码. 将 {{htmlelement("select")}} 和 {{htmlelement("pre")}} 元素的引用存储到变量中, 并定义一个 {{domxref("GlobalEventHandlers.onchange","onchange")}} 事件处理函数,可以在select的值改变时, 将其值传递给 <code>updateDisplay()</code> 函数作为参数。 </p>
+
+ <pre class="brush: js">const verseChoose = document.querySelector('select');
+const poemDisplay = document.querySelector('pre');
+
+verseChoose.onchange = function() {
+ const verse = verseChoose.value;
+ updateDisplay(verse);
+};</pre>
+ </li>
+ <li>
+ <p>定义 <code>updateDisplay()</code> 函数. 首先,将下面的代码块放在之前代码块的下面 - 这是函数的空壳:</p>
+
+ <pre class="brush: js">function updateDisplay(verse) {
+
+}</pre>
+ </li>
+ <li>
+ <p>我们将通过构造一个  指向我们要加载的文本文件的相对URL  来启动我们的函数, 因为我们稍后需要它. 任何时候 {{htmlelement("select")}} 元素的值都与所选的 {{htmlelement("option")}} 内的文本相同 (除非在值属性中指定了不同的值) — 例如 "Verse 1". 相应的诗歌文本文件是 "verse1.txt", 并与HTML文件位于同一目录中, 因此只需要文件名即可。</p>
+
+ <p>但是,Web服务器往往是区分大小写的,文件名没有空格。 要将“Verse 1”转换为“verse1.txt”,我们需要将V转换为小写,删除空格,并在末尾添加.txt。  这可以通过 {{jsxref("String.replace", "replace()")}}, {{jsxref("String.toLowerCase", "toLowerCase()")}}, 和 简单的 <a href="/en-US/docs/Learn/JavaScript/First_steps/Strings#Concatenating_strings">string concatenation</a> 来完成. 在 <code>updateDisplay()</code> 函数中添加以下代码:</p>
+
+ <pre class="brush: js">verse = verse.replace(" ", "");
+verse = verse.toLowerCase();
+let url = verse + '.txt';</pre>
+ </li>
+ <li>
+ <p>要开始创建XHR请求,您需要使用 {{domxref("XMLHttpRequest.XMLHttpRequest", "XMLHttpRequest()")}} 的构造函数创建一个新的请求对象。 你可以把这个对象叫做你喜欢的任何东西, 但是我们会把它叫做 <code>request</code> 来保持简单. 在之前的代码中添加以下内容:</p>
+
+ <pre class="brush: js">let request = new XMLHttpRequest();</pre>
+ </li>
+ <li>
+ <p>接下来,您需要使用 {{domxref("XMLHttpRequest.open","open()")}} 方法来指定用于从网络请求资源的 <a href="/en-US/docs/Web/HTTP/Methods">HTTP request method</a> , 以及它的URL是什么。我们将在这里使用 <code><a href="/en-US/docs/Web/HTTP/Methods/GET">GET</a></code> 方法, 并将URL设置为我们的 <code>url</code> 变量. 在你上面的代码中添加以下代码:</p>
+
+ <pre class="brush: js">request.open('GET', url);</pre>
+ </li>
+ <li>
+ <p>接下来,我们将设置我们期待的响应类型 —  这是由请求的 {{domxref("XMLHttpRequest.responseType", "responseType")}} 属性定义的 — 作为 <code>text</code>.  这并不是绝对必要的 — XHR默认返回文本 —但如果你想在以后获取其他类型的数据,养成这样的习惯是一个好习惯. 接下来添加:</p>
+
+ <pre class="brush: js">request.responseType = 'text';</pre>
+ </li>
+ <li>
+ <p>从网络获取资源是一个 {{glossary("asynchronous")}} "异步" 操作, 这意味着您必须等待该操作完成(例如,资源从网络返回),然后才能对该响应执行任何操作,否则会出错,将被抛出错误。 XHR允许你使用它的 {{domxref("XMLHttpRequest.onload", "onload")}} 事件处理器来处理这个事件 — 当{{event("onload")}} 事件触发时(当响应已经返回时)这个事件会被运行。 发生这种情况时, <code>response</code> 数据将在XHR请求对象的响应属性中可用。</p>
+
+ <p>在后面添加以下内容.  你会看到,在 <code>onload</code> 事件处理程序中,我们将 <code>poemDisplay</code>  ( {{htmlelement("pre")}} 元素 ) 的  <code><a href="/en-US/docs/Web/API/Node/textContent">textContent</a></code> 设置为 {{domxref("XMLHttpRequest.response", "request.response")}} 属性的值。</p>
+
+ <pre class="brush: js">request.onload = function() {
+ poemDisplay.textContent = request.response;
+};</pre>
+ </li>
+ <li>
+ <p>以上都是XHR请求的设置 — 在我们告诉它之前,它不会真正运行,这是通过 {{domxref("XMLHttpRequest.send","send()")}} 完成的. 在你之前的代码下添加以下内容完成该函数:</p>
+
+ <pre class="brush: js">request.send();</pre>
+ </li>
+ <li>
+ <p>这个例子中的一个问题就是它首次加载时不会显示任何诗。 为了解决这个问题,在代码的底部添加以下两行 (正好在关闭的 <code>&lt;/script&gt;</code> 标签之上) 默认加载第1节,并确保 {{htmlelement("select")}} 元素始终显示正确的值:</p>
+
+ <pre class="brush: js">updateDisplay('Verse 1');
+verseChoose.value = 'Verse 1';</pre>
+ </li>
+</ol>
+
+<h3 id="在server端运行例子">在server端运行例子</h3>
+
+<p>如果只是从本地文件运行示例,一些浏览器(包括Chrome)将不会运行XHR请求。这是因为安全限制(更多关于web安全性的限制,请参阅<a href="/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a>)。</p>
+
+<p>为了解决这个问题,我们需要通过在本地web服务器上运行它来测试这个示例。要了解如何实现这一点,请阅读<a href="/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server">How do you set up a local testing server?</a></p>
+
+<h3 id="Fetch">Fetch</h3>
+
+<p>Fetch API基本上是XHR的一个现代替代品——它是最近在浏览器中引入的,它使异步HTTP请求在JavaScript中更容易实现,对于开发人员和在Fetch之上构建的其他API来说都是如此。</p>
+
+<p>让我们将最后一个示例转换为使用Fetch !</p>
+
+<ol>
+ <li>
+ <p>复制您之前完成的示例目录. (如果您没有通过以前的练习,创建一个新的目录。, 然后复制 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/xhr-basic.html">xhr-basic.html</a>和这四个文件 — <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse1.txt">verse1.txt</a>, <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse2.txt">verse2.txt</a>, <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse3.txt">verse3.txt</a>, and <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse4.txt">verse4.txt</a>.)</p>
+ </li>
+ <li>
+ <p>在 <code>updateDisplay()</code> 里找到 XHR 那段代码:</p>
+
+ <pre class="brush: js">let request = new XMLHttpRequest();
+request.open('GET', url);
+request.responseType = 'text';
+
+request.onload = function() {
+ poemDisplay.textContent = request.response;
+};
+
+request.send();</pre>
+ </li>
+ <li>
+ <p>像这样替换掉所有关于XHR的代码:</p>
+
+ <pre class="brush: js">fetch(url).then(function(response) {
+ response.text().then(function(text) {
+ poemDisplay.textContent = text;
+ });
+});</pre>
+ </li>
+ <li>
+ <p>在浏览器中加载示例(通过web服务器运行),它应该与XHR版本相同,前提是您运行的是一个现代浏览器。</p>
+ </li>
+</ol>
+
+<h4 id="那么Fetch代码中发生了什么呢">那么Fetch代码中发生了什么呢?</h4>
+
+<p>首先,我们调用了<code><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/WorkerOrWindowGlobalScope/fetch" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!">fetch()</a></code>方法,将我们要获取的资源的URL传递给它。这相当于现代版的XHR中的<code>request.open()</code>,另外,您不需要任何等效的<code>send()</code>方法。</p>
+
+<p>然后,你可以看到<code>.then()</code>方法连接到了<code>fetch()</code>末尾-这个方法是<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise" title="Promise 对象用于一个异步操作的最终完成(或失败)及其结果值的表示。(简单点说就是处理异步请求。我们经常会做些承诺,如果我赢了你就嫁给我,如果输了我就嫁给你之类的诺言。这就是promise的中文含义:诺言,一个成功,一个失败。)"><code>Promises</code></a>的一部分,是一个用于执行异步操作的现代JavaScript特性。<code>fetch()</code>返回一个promise,它将解析从服务器发回的响应。我们使用<code>then()</code>来运行一些后续代码,这是我们在其内部定义的函数。这相当于XHR版本中的<code>onload</code>事件处理程序。</p>
+
+<p>当<code>fetch()</code> promise 解析时,这个函数会自动将响应从服务器传递给参数。在函数内部,我们获取响应并运行其<code>text()</code>方法。这基本上将响应作为原始文本返回,这相当于在XHR版本中的<code>responseType = 'text'</code>。</p>
+
+<p>你会看到 <code>text()</code> 也返回了一个 promise, 所以我们连接另外一个 <code>.then()</code> 到它上面, 在其中我们定义了一个函数来接收 <code>text()</code> promise解析的生文本。</p>
+
+<p>在promise的函数内部,我们做的和在XHR版本中差不多— 设置 <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre" title="The HTML &lt;pre> element represents preformatted text which is to be presented exactly as written in the HTML file."><code>&lt;pre&gt;</code></a> 元素的文本内容为text的值。</p>
+
+<h3 id="关于promises">关于promises </h3>
+
+<p>当你第一次见到它们的时候,promises会让你有点困惑,但现在不要太担心这个。在一段时间之后,您将会习惯它们,特别是当您了解更多关于现代JavaScript api的时候——大多数现代的JavaScript api都是基于promises的。</p>
+
+<p>让我们再看看上面的promises结构,看看我们是否能更清楚地理解它:</p>
+
+<pre class="brush: js">fetch(url).then(function(response) {
+ response.text().then(function(text) {
+ poemDisplay.textContent = text;
+ });
+});</pre>
+
+<p>第一行是说‘’获取位于url里的资源(<code>fetch(url)</code>)‘’和“然后当promise解析后运行指定的函数(<code>.then(function() { ... })</code>)”。"解析"的意思是"在将来某一时刻完成指定的操作"。在本例中,指定的操作是从指定的URL(使用HTTP请求)获取资源,并返回对我们执行某些操作的响应。</p>
+
+<p>实际上,传递给 <code>then()</code> 是一段不会立即执行的代码 — 而是当返回响应时代码会被运行。注意,你还可以选择把你的 promise 保存到一个变量里, 链接 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then" title="The then() method returns a Promise. It takes up to two arguments: callback functions for the success and failure cases of the Promise."><code>.then()</code></a> 在相同的位置。下面的代码会做相同的事情。</p>
+
+<pre class="brush: js">let myFetch = fetch(url);
+
+myFetch.then(function(response) {
+ response.text().then(function(text) {
+ poemDisplay.textContent = text;
+ });
+});</pre>
+
+<p>因为方法 fetch() 返回一个解析HTTP响应的promise, 你在 .then() 中定义的任何函数会被自动给与一个响应作为一个参数。你可以给这个参数取任何名字,以下的例子依然可以实现:(例子里把response参数叫做狗饼干---'dogBiscuits'=狗饼干)</p>
+
+<pre class="brush: js">fetch(url).then(function(dogBiscuits) {
+ dogBiscuits.text().then(function(text) {
+ poemDisplay.textContent = text;
+ });
+});</pre>
+
+<p>但是把参数叫做描述其内容的名字更有意义。</p>
+
+<p>现在让我们来单独看一下函数:</p>
+
+<pre class="brush: js">function(response) {
+ response.text().then(function(text) {
+ poemDisplay.textContent = text;
+ });
+}</pre>
+
+<p>response 对象有个 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Body/text" title="The text() method of the Body mixin takes a Response stream and reads it to completion. It returns a promise that resolves with a USVString object (text). The response is always decoded using UTF-8."><code>text()</code></a>方法, 获取响应主体中的原始数据a并把它转换成纯文本, 那是我们想要的格式。它也返回一个promise (解析结果文本字符串), 所以这里我们再使用 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then" title="The then() method returns a Promise. It takes up to two arguments: callback functions for the success and failure cases of the Promise."><code>.then()</code></a>, 在里面我们再定义一个操作文本字符串的函数。我们设置诗歌的 <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre" title="The HTML &lt;pre> element represents preformatted text which is to be presented exactly as written in the HTML file."><code>&lt;pre&gt;</code></a> 元素的 <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent">textContent</a></code> 属性和这个文本字符串相同, 这样就非常简单地解决了。</p>
+
+<p>值得注意的是你可以直接将promise块 (<code>.then()</code>块, 但也有其他类型) 链接到另一个的尾部, 顺着链条将每个块的结果传到下一个块。 这使得promises非常强大。</p>
+
+<p>下面的代码块和我们原始的例子做的是相同的事, 但它是不同的写法:</p>
+
+<pre class="brush: js">fetch(url).then(function(response) {
+ return response.text()
+}).then(function(text) {
+ poemDisplay.textContent = text;
+});</pre>
+
+<p>很多开发者更喜欢这种样式, 因为它更扁平并且按理说对于更长的promise链它更容易读 — 每一个promise(承诺)接续上一个promise,而不是在上一个promise的里面(会使得整个代码笨重起来,难以理解)。以上两种写法还有一个不同的地方是我们在<code>response.text()</code>  语句之前得包含一个 <code><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Return_values">return</a></code>  语句, 用来把这一部分的结果传向promise链的下一段。</p>
+
+<h3 id="你应该用哪种方法呢">你应该用哪种方法呢?</h3>
+
+<p>  这完全取决于你正在干的项目是啥样。XHR已经面世非常之久,现在已经有了相当棒的跨浏览器支持。然而对于网页平台来说,Fetch和Promise是新近的产物,除了IE和Safari浏览器不支持,别的浏览器大多提供了支持。(现在Safari也即将为fetch和promise提供支持)。</p>
+
+<p>  如果你的项目需要支持年代久远的浏览器,那么使用XHR可能会更爽一些。如果你的项目比较激进而且你根本不管老版的浏览器吃不吃这套,那就选择Fetch吧老铁。</p>
+
+<p>  话说回来,咱倒真应该两者都学学——因为使用IE浏览器的人们在变少,Fetch会变得越来越流行(事实上IE已经没人管了,因为微软Edge浏览器的受宠),但在所有浏览器彻底支持Fetch之前,你可能还得和XHR纠缠一阵子。</p>
+
+<h2 id="一个更复杂的示例">一个更复杂的示例</h2>
+
+<p>为了使本文更详尽,我们将看一个稍微复杂一点的示例,它展示了Fetch的一些更有趣的用法。我们创建了一个名为Can Store的示例站点——它是一个虚构的超市,只出售罐头食品。你可以找到 <a href="https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/">example live on GitHub</a>,并且 <a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/fetching-data/can-store">see the source code</a> 。</p>
+
+<p><img alt="A fake ecommerce site showing search options in the left hand column, and product search results in the right hand column." src="https://mdn.mozillademos.org/files/14779/can-store.png" style="display: block; margin: 0 auto;"></p>
+
+<p>默认情况下,站点会显示所有产品,但您可以使用左手列中的表单控件按类别或搜索词或两者进行筛选。</p>
+
+<p>有很多复杂的代码处理按类别和搜索词过滤产品、操作字符串以便数据在UI中正确显示等等。我们不会在本文中讨论所有这些,但是您可以在代码中找到大量的注释 (see <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/can-store/can-script.js">can-script.js</a>)。</p>
+
+<p>但是,我们会解释Fetch代码的含义。</p>
+
+<p>第一个使用Fetch的块可以在JavaScript的开头找到:</p>
+
+<pre class="brush: js">fetch('products.json').then(function(response) {
+ if(response.ok) {
+ response.json().then(function(json) {
+ products = json;
+ initialize();
+ });
+ } else {
+ console.log('Network request for products.json failed with response ' + response.status + ': ' + response.statusText);
+ }
+});</pre>
+
+<p>这看起来和我们之前看到的相似,只是第二个promise是在一个条件语句中。在条件下,我们检查返回的response是否成功 —  {{domxref("response.ok")}} 属性包含一个布尔变量,如果response是成功的 (e.g. <a href="/en-US/docs/Web/HTTP/Status/200">200 meaning "OK"</a>),则是<code>true</code>;如果失败了,则是<code>false</code>。</p>
+
+<p>如果response成功,我们运行第二个promise — 然而,这次我们使用 {{domxref("Body.json","json()")}},而不是{{domxref("Body.text","text()")}}, 因为我们想要response返回的是一个结构化的JSON数据,而不是纯文本。</p>
+
+<p>如果response失败,我们将输出一个错误到控制台,指出网络请求失败,该控制台将报告响应的网络状态和描述性消息(分别包含在{{domxref("response.status")}}和{{domxref("response.statusText")}}属性中)。 当然,一个完整的web站点可以通过在用户的屏幕上显示一条消息来更优雅地处理这个错误,也许还可以提供一些选项来补救这种情况。</p>
+
+<p>您可以自己测试失败案例:</p>
+
+<ol>
+ <li>制作示例文件的本地副本(下载并解压<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/can-store/can-store.zip?raw=true">the can-store ZIP file</a>)</li>
+ <li>通过web服务器运行代码(如上所述,在 {{anch("Serving your example from a server")}})</li>
+ <li>修改要获取的文件的路径,比如“produc.json'(确保你拼写的是错误的)</li>
+ <li>现在在你的浏览器上加载索引文件 (通过 <code>localhost:8000</code>) 然后查看你的开发者控制台。 你将看到一条类似于“网络请求products.json失败,404:找不到文件”的消息</li>
+</ol>
+
+<p>第二个Fetch块可以在<code>fetchBlob()</code> 找到:</p>
+
+<pre class="brush: js">fetch(url).then(function(response) {
+ if(response.ok) {
+ response.blob().then(function(blob) {
+ objectURL = URL.createObjectURL(blob);
+ showProduct(objectURL, product);
+ });
+ } else {
+ console.log('Network request for "' + product.name + '" image failed with response ' + response.status + ': ' + response.statusText);
+ }
+});</pre>
+
+<p>它的工作原理和前一个差不多, 除了我们放弃{{domxref("Body.json","json()")}}而使用{{domxref("Body.blob","blob()")}} — 在本例中,我们希望以图像文件的形式返回响应,为此使用的数据格式是<a href="/en-US/docs/Web/API/Blob">Blob</a> — 这个词是“二进制大对象”的缩写,基本上可以用来表示大型文件类对象——比如图像或视频文件。</p>
+
+<p>一旦我们成功地接收到我们的blob,我们就会使用它创建一个对象URL {{domxref("URL.createObjectURL()", "createObjectURL()")}}. 它返回一个指向浏览器中引用的对象的临时内部URL。这些不是很容易读懂,但是你可以通过打开Can Store看到,按Ctrl-/右键单击一个图像,然后选择“View Image(查看图像)”选项(根据您使用的浏览器可能略有不同)。 对象URL将在地址栏中可见,应该是这样的:</p>
+
+<pre>blob:http://localhost:7800/9b75250e-5279-e249-884f-d03eb1fd84f4</pre>
+
+<h3 id="挑战:一个XHR版本的Can_Store">挑战:一个XHR版本的Can Store</h3>
+
+<p>我们希望您能尝试将Fetch版本转换为使用XHR,作为一个有用的实践。下载一份 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/can-store/can-store.zip?raw=true">copy of the ZIP file</a>, 并适当修改下JavaScript。</p>
+
+<p>一些有用的提示:</p>
+
+<ul>
+ <li>你可能会发现 {{domxref("XMLHttpRequest")}} 作参考材料非常有用。</li>
+ <li>你基本上需要使用和你在前面的文章中看到的<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/xhr-basic.html">XHR-basic.html</a>例子相同的模式。</li>
+ <li>但是,您将需要添加我们在Can Store的Fetch版本中显示的错误处理:
+ <ul>
+ <li>在<code>load</code>事件完毕后,response被用于<code>request.response</code>而不是promise的<code>then()</code>。</li>
+ <li>Fetch的 <code>response.ok</code>在XHR中的最佳等效为检查 {{domxref("XMLHttpRequest.status","request.status")}} 是否等于200或者 {{domxref("XMLHttpRequest.readyState","request.readyState")}} 是否等于4。</li>
+ <li>获取状态和状态信息的内容是一样的, 但是它们在 <code>request</code> (XHR) 对象,而不是<code>response</code> 对象。</li>
+ </ul>
+ </li>
+</ul>
+
+<div class="note">
+<p><strong>注意</strong>: 如果您在这方面有问题,请到Github将您的代码和完成的版本进行对比 ( <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/can-store-xhr/can-script.js">see the source here</a>, see also <a href="https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store-xhr/">see it running live</a> )。</p>
+</div>
+
+<h2 id="概述">概述</h2>
+
+<p>我们这篇关于从服务器那儿抓取数据的文章就到此为止了。现在你应该对如何使用XHR和Fetch有一些了解了吧?</p>
+
+<h2 id="请参阅">请参阅</h2>
+
+<p>虽然本文中讨论了许多不同的主题,但是这些主题仅仅只是触及了表面。有关这些主题的更多详细信息,请尝试以下文章:</p>
+
+<ul>
+ <li><a href="/en-US/docs/AJAX/Getting_Started">Ajax — Getting started</a></li>
+ <li><a href="/en-US/docs/Web/API/Fetch_API/Using_Fetch">Using Fetch</a></li>
+ <li><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Objects/JSON">Working with JSON data</a></li>
+ <li><a href="/en-US/docs/Web/HTTP/Overview">An overview of HTTP</a></li>
+ <li><a href="/en-US/docs/Learn/Server-side">Server-side website programming</a></li>
+ <li>
+ <p><a href="https://jsperf.com/xhr-vs-jquery-ajax-vs-get-vs-fetch">https://jsperf.com/xhr-vs-jquery-ajax-vs-get-vs-fetch</a></p>
+ </li>
+ <li>
+ <p><a href="https://xhr.spec.whatwg.org/#example-xhr">https://xhr.spec.whatwg.org/#example-xhr</a></p>
+ </li>
+</ul>
+
+<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Manipulating_documents", "Learn/JavaScript/Client-side_web_APIs/Third_party_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</div>
+
+<div></div>
+
+<h2 id="模块大纲">模块大纲</h2>
+
+<ul>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">介绍web APIs</a></li>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">操作DOM documents</a></li>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">从服务器获取数据</a></li>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">第三方APIs</a></li>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">绘图</a></li>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">视频音频APIs</a></li>
+ <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">客户端存储</a></li>
+</ul>
diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/index.html b/files/zh-cn/learn/javascript/client-side_web_apis/index.html
new file mode 100644
index 0000000000..30bec1c50f
--- /dev/null
+++ b/files/zh-cn/learn/javascript/client-side_web_apis/index.html
@@ -0,0 +1,47 @@
+---
+title: 客户端 Web API
+slug: Learn/JavaScript/Client-side_web_APIs
+tags:
+ - API(应用程序编程接口)
+ - DOM(文档对象模型)
+ - JavaScript
+ - WebAPI(网页接口)
+ - 初学者
+ - 图形学
+ - 数据
+ - 文章
+ - 脚本编程
+translation_of: Learn/JavaScript/Client-side_web_APIs
+---
+<div>{{LearnSidebar}}</div>
+
+<p class="summary">当你给网页或者网页应用编写客户端的JavaScript时, 你很快会遇上应用程序接口(API )—— 这些编程特性可用来操控网站所基于的浏览器与操作系统的不同方面,或是操控由其他网站或服务端传来的数据。在这个单元里,我们将一同探索什么是API,以及如何使用一些在你开发中将经常遇见的API。</p>
+
+<h2 id="预备知识">预备知识</h2>
+
+<p>若想深入理解这个单元的内容, 你必须能够以自己的能力较好地学完之前的几个章节 (<a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript第一步</a>, <a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript</a><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks">基础要件</a>, 和<a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript</a><a href="/zh-CN/docs/Learn/JavaScript/Objects">对象介绍</a>). 这几部分涉及到了许多简单的API的使用, 如果没有它们我们将很难做一些实际的事情。在这个教程中,我们会认为你懂得JavaScript的核心知识,而且我们将更深入地探索常见的网页API。</p>
+
+<p>若你知道 <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML">HTML</a> 和 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS">CSS</a> 的基本知识,也会对理解这个单元的内容大有裨益。</p>
+
+<div class="note">
+<p>注意:如果你正在使用一台无法创建你自身文件的电脑、平板或其他设备,你可以尝试使用一些在线网页编辑器如<a href="http://jsbin.com/">JSBin</a>或者<a href="https://thimble.mozilla.org/">Thimble</a>,来尝试编辑一些代码示例。</p>
+</div>
+
+<h2 id="向导">向导</h2>
+
+<dl>
+ <dt><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API简介</a></dt>
+ <dd>首先, 我们将从一个更高的角度来看这些API —它们是什么,它们怎么起作用的,你该怎么在自己的代码中使用它们以及他们是怎么构成的? 我们依旧会再来看一看这些API有哪些主要的种类和他们会有哪些用处。</dd>
+ <dt><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">操作文档</a></dt>
+ <dd>当你在制作WEB页面和APP时,一个你最经常想要做的事就是通过一些方法来操作WEB文档。这其中最常见的方法就是使用文档对象模型Document Object Model (DOM),它是一系列大量使用了 {{domxref("Document")}} object的API来控制HTML和样式信息。通过这篇文章,我们来看看使用DOM方面的一些细节, 以及其他一些有趣的API能够通过一些有趣的方式改变你的环境。</dd>
+ <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">从服务器获取数据</a></dt>
+ <dd>在现代网页及其APP中另外一个很常见的任务就是与服务器进行数据交互时不再刷新整个页面,这看起来微不足道,但却对一个网页的展现和交互上起到了很大的作用,在这篇文章里,我们将阐述这个概念,然后来了解实现这个功能的技术,例如 {{domxref("XMLHttpRequest")}} 和 <a href="/en-US/docs/Web/API/Fetch_API">Fetch API</a>.(抓取API)。</dd>
+ <dt><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">第三方 API</a></dt>
+ <dd>到目前为止我们所涉及的API都是浏览器内置的,但并不代表所有。许多大网站如Google Maps, Twitter, Facebook, PayPal等,都提供他们的API给开发者们去使用他们的数据(比如在你的博客里展示你分享的推特内容)或者服务(如在你的网页里展示定制的谷歌地图或接入Facebook登录功能)。这篇文章介绍了浏览器API和第三方API 的差别以及一些最新的典型应用。</dd>
+ <dt><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">绘制图形</a></dt>
+ <dd>浏览器包含多种强大的图形编程工具,从可缩放矢量图形语言Scalable Vector Graphics (<a href="/zh-CN/docs/Web/SVG">SVG</a>) language,到HTML绘制元素 {{htmlelement("canvas")}} 元素(<a href="/zh-CN/docs/Web/API/Canvas_API">The Canvas API</a> and <a href="/zh-CN/docs/Web/API/WebGL_API">WebGL</a>). 这篇文章提供了部分canvas的简介,以及让你更深入学习的资源。</dd>
+ <dt><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">视频和音频 API</a></dt>
+ <dd>HTML5能够通过元素标签嵌入富媒体——{{htmlelement("video")}} and {{htmlelement("audio")}}——而将有自己的API来控制回放,搜索等功能。本文向您展示了如何创建自定义播放控制等常见的任务。</dd>
+ <dt><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">客户端存储</a></dt>
+ <dd>现代web浏览器拥有很多不同的技术,能够让你存储与网站相关的数据,并在需要时调用它们,能够让你长期保存数据、保存离线网站及其他实现其他功能。本文解释了这些功能的基本原理。</dd>
+</dl>
diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/introduction/index.html b/files/zh-cn/learn/javascript/client-side_web_apis/introduction/index.html
new file mode 100644
index 0000000000..f549f9919d
--- /dev/null
+++ b/files/zh-cn/learn/javascript/client-side_web_apis/introduction/index.html
@@ -0,0 +1,275 @@
+---
+title: Web API简介
+slug: Learn/JavaScript/Client-side_web_APIs/Introduction
+tags:
+ - API
+ - CodingScripting
+ - WebAPI
+ - 初学者
+ - 学习
+ - 客户机端
+ - 对象
+ - 文章
+ - 浏览器
+ - 第三方
+translation_of: Learn/JavaScript/Client-side_web_APIs/Introduction
+---
+<div>{{LearnSidebar}}</div>
+
+<div>{{NextMenu("Learn/JavaScript/Client-side_Web_APIs/Manipulating_documents", "Learn/JavaScript/Client-side_Web_API")}}</div>
+
+<p class="summary">首先,我们将从一个高层次看看API - 它们是什么;他们如何工作;如何在代码中使用它们,以及它们是如何组织的。我们也将看看不同主要类别的API以及它们的用途。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">预备知识</th>
+ <td>基本计算机知识,对于HTML和CSS的基本理解(见<a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript 第一步</a>,<a href="/zh-CN/docs/learn/JavaScript/Building_blocks">创建JavaScript代码块</a>,<a href="/zh-CN/docs/Learn/JavaScript/Objects">JavaScript 对象入门</a>)。</td>
+ </tr>
+ <tr>
+ <th scope="row">目标</th>
+ <td>熟悉API,他们可以做什么,以及如何在代码中使用它们。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="什么是API">什么是API?</h2>
+
+<p>应用程序接口(API)是基于编程语言构建的结构,使开发人员更容易地创建复杂的功能。它们抽象了复杂的代码,并提供一些简单的接口规则直接使用。</p>
+
+<p>来看一个现实中的例子:想想您的房子、公寓或其他住宅的供电方式,如果您想在您的房子里用电,只要把电器的插头插入插座就可以,而不是直接把它连接到电线上——这样做非常低效,而且对于不是电工的人会是困难和危险的。</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/14317/plug-socket.png" style="display: block; height: 472px; margin: 0px auto; width: 700px;"></p>
+
+<p><em>图片来自:<a href="https://www.flickr.com/photos/easy-pics/9518184890/in/photostream/lightbox/">Overloaded plug socket</a> 提供者: <a href="https://www.flickr.com/photos/easy-pics/">The Clear Communication People</a>于Flickr。</em></p>
+
+<p>同样,比如说,编程来显示一些3D图形,使用以更高级语言编写的API(例如JavaScript或Python)将会比直接编写直接控制计算机的GPU或其他图形功能的低级代码(比如C或C++)来执行操作要容易得多。</p>
+
+<div class="note">
+<p><strong>注:</strong>详细说明请见<a href="https://developer.mozilla.org/en-US/docs/Glossary/API">API - Glossary</a>。</p>
+</div>
+
+<h3 id="客户端JavaScript中的API">客户端JavaScript中的API</h3>
+
+<p>客户端JavaScript中有很多可用的API — 他们本身并不是JavaScript语言的一部分,却建立在JavaScript语言核心的顶部,为使用JavaScript代码提供额外的超强能力。他们通常分为两类:</p>
+
+<ul>
+ <li><strong>浏览器API</strong>内置于Web浏览器中,能从浏览器和电脑周边环境中提取数据,并用来做有用的复杂的事情 。例如<a href="/en-US/docs/Web/API/Geolocation/Using_geolocation">Geolocation API</a>提供了一些简单的JavaScript结构以获得位置数据,因此您可以在Google地图上标示您的位置。在后台,浏览器确实使用一些复杂的低级代码(例如C++)与设备的GPS硬件(或可以决定位置数据的任何设施)通信来获取位置数据并把这些数据返回给您的代码中使用浏览器环境;但是,这种复杂性通过API抽象出来,因而与您无关。</li>
+ <li><strong>第三方API</strong>缺省情况下不会内置于浏览器中,通常必须在Web中的某个地方获取代码和信息。例如<a href="https://dev.twitter.com/overview/documentation">Twitter API</a> 使您能做一些显示最新推文这样的事情,它提供一系列特殊的结构,可以用来请求Twitter服务并返回特殊的信息。</li>
+</ul>
+
+
+
+
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/13508/browser.png" style="display: block; height: 511px; margin: 0px auto; width: 815px;"></p>
+
+
+
+<h3 id="JavaScript,API和其他JavaScript工具之间的关系">JavaScript,API和其他JavaScript工具之间的关系</h3>
+
+<p>如上所述,我们讨论了什么是客户端JavaScript API,以及它们与JavaScript语言的关系。让我们回顾一下,使其更清晰,并提及其他JavaScript工具的适用位置:</p>
+
+<ul>
+ <li>JavaScript — 一种内置于浏览器的高级脚本语言,您可以用来实现Web页面/应用中的功能。注意JavaScript也可用于其他象<a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Node</a>这样的的编程环境。但现在您不必考虑这些。</li>
+ <li>客户端API — 内置于浏览器的结构程序,位于JavaScript语言顶部,使您可以更容易的实现功能。</li>
+ <li>第三方API — 置于第三方普通的结构程序(例如Twitter,Facebook),使您可以在自己的Web页面中使用那些平台的某些功能(例如在您的Web页面显示最新的Tweets)。</li>
+ <li>JavaScript库 — 通常是包含具有<a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Custom_functions">特定功能</a>的一个或多个JavaScript文件,把这些文件关联到您的Web页以快速或授权编写常见的功能。例如包含jQuery和Mootools</li>
+ <li>JavaScript框架 — 从库开始的下一步,JavaScript框架视图把HTML、CSS、JavaScript和其他安装的技术打包在一起,然后用来从头编写一个完整的Web应用。</li>
+</ul>
+
+<h2 id="API可以做什么?">API可以做什么?</h2>
+
+<p>在主流浏览器中有大量的可用API,您可以在代码中做许多的事情,对此可以查看<a href="https://developer.mozilla.org/en-US/docs/Web/API">MDN API index page</a>。</p>
+
+<h3 id="常见浏览器API">常见浏览器API</h3>
+
+<p>特别地,您将使用的最常见的浏览器API类别(以及我们将更详细地介绍的)是:</p>
+
+<ul>
+ <li><strong>操作文档的API</strong>内置于浏览器中。最明显的例子是<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model">DOM(文档对象模型)</a>API,它允许您操作HTML和CSS — 创建、移除以及修改HTML,动态地将新样式应用到您的页面,等等。每当您看到一个弹出窗口出现在一个页面上,或者显示一些新的内容时,这都是DOM的行为。 您可以在在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">Manipulating documents</a>中找到关于这些类型的API的更多信息。</li>
+ <li><strong>从服务器获取数据的API </strong>用于更新网页的一小部分是相当好用的。这个看似很小的细节能对网站的性能和行为产生巨大的影响 — 如果您只是更新一个股票列表或者一些可用的新故事而不需要从服务器重新加载整个页面将使网站或应用程序感觉更加敏感和“活泼”。使这成为可能的API包括<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest" title="XMLHttpRequest is an API that provides client functionality for transferring data between a client and a server. It provides an easy way to retrieve data from a URL without having to do a full page refresh. This enables a Web page to update just a part of the page without disrupting what the user is doing."><code>XMLHttpRequest</code></a>和<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API">Fetch API</a>。您也可能会遇到描述这种技术的术语<strong>Ajax</strong>。您可以在<a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">Fetching data from the server</a>找到关于类似的API的更多信息。</li>
+ <li><strong>用于绘制和操作图形的API</strong>目前已被浏览器广泛支持 — 最流行的是允许您以编程方式更新包含在HTML {{htmlelement("canvas")}} 元素中的像素数据以创建2D和3D场景的<a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">Canvas</a>和<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API">WebGL</a>。例如,您可以绘制矩形或圆形等形状,将图像导入到画布上,然后使用Canvas API对其应用滤镜(如棕褐色滤镜或灰度滤镜),或使用WebGL创建具有光照和纹理的复杂3D场景。这些API经常与用于创建动画循环的API(例如{{domxref("window.requestAnimationFrame()")}})和其他API一起不断更新诸如动画和游戏之类的场景。</li>
+ <li><strong>音频和视频API</strong>例如{{domxref("HTMLMediaElement")}},<a href="/zh-CN/docs/Web/API/Web_Audio_API">Web Audio API</a>和<a href="/zh-CN/docs/MDN/Doc_status/API/WebRTC">WebRTC</a>允许您使用多媒体来做一些非常有趣的事情,比如创建用于播放音频和视频的自定义UI控件,显示字幕字幕和您的视频,从网络摄像机抓取视频,通过画布操纵(见上),或在网络会议中显示在别人的电脑上,或者添加效果到音轨(如增益,失真,平移等) 。</li>
+ <li><strong>设备API</strong>基本上是以对网络应用程序有用的方式操作和检索现代设备硬件中的数据的API。我们已经讨论过访问设备位置数据的地理定位API,因此您可以在地图上标注您的位置。其他示例还包括通过系统通知(参见<a href="/zh-CN/docs/Web/API/Notifications_API">Notifications API</a>)或振动硬件(参见<a href="/zh-CN/docs/Web/API/Vibration_API">Vibration API</a>)告诉用户Web应用程序有用的更新可用。</li>
+ <li><strong>客户端存储API</strong>在Web浏览器中的使用变得越来越普遍 - 如果您想创建一个应用程序来保存页面加载之间的状态,甚至让设备在处于脱机状态时可用,那么在客户端存储数据将会是非常有用的。例如使用<a href="/zh-CN/docs/Web/API/Web_Storage_API">Web Storage API</a>的简单的键 - 值存储以及使用<a href="/zh-CN/docs/Web/API/IndexedDB_API">IndexedDB API</a>的更复杂的表格数据存储。</li>
+</ul>
+
+<h3 id="常见第三方API">常见第三方API</h3>
+
+<p>第三方API种类繁多; 下列是一些比较流行的你可能迟早会用到的第三方API:</p>
+
+<ul>
+ <li>The <a href="https://dev.twitter.com/overview/documentation">Twitter API</a>, 允许您在您的网站上展示您最近的推文等。</li>
+ <li>The <a href="https://developers.google.com/maps/">Google Maps API</a> 允许你在网页上对地图进行很多操作(这很有趣,它也是Google地图的驱动器)。现在它是一整套完整的,能够胜任广泛任务的API。其能力已经被<a href="https://developers.google.com/maps/documentation/api-picker">Google Maps API Picker</a>见证。</li>
+ <li>The <a href="https://developers.facebook.com/docs/">Facebook suite of API</a> 允许你将很多Facebook生态系统中的功能应用到你的app,使之受益,比如说它提供了通过Facebook账户登录、接受应用内支付、推送有针对性的广告活动等功能。</li>
+ <li>The <a href="https://developers.google.com/youtube/">YouTube API</a>, 允许你将Youtube上的视频嵌入到网站中去,同时提供搜索Youtube,创建播放列表等众多功能。</li>
+ <li>The <a href="https://www.twilio.com/">Twilio API</a>, 其为您的app提供了针对语音通话和视频聊天的框架,以及从您的app发送短信息或多媒体信息等诸多功能。</li>
+</ul>
+
+<div class="note">
+<p><strong>注</strong>: 你可以在 <a href="http://www.programmableweb.com/category/all/apis">Programmable Web API directory</a>.上发现更多关于第三方API的信息。</p>
+</div>
+
+<h2 id="API如何工作?">API如何工作?</h2>
+
+<p>不同的JavaScript API以稍微不同的方式工作,但通常它们具有共同的特征和相似的主题。</p>
+
+<h3 id="它们是基于对象的">它们是基于对象的</h3>
+
+<p> API使用一个或多个 <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects">JavaScript objects</a> 在您的代码中进行交互,这些对象用作API使用的数据(包含在对象属性中)的容器以及API提供的功能(包含在对象方法中)。</p>
+
+<div class="note">
+<p> 注意:如果您不熟悉对象如何工作,则应继续学习 <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects">JavaScript objects</a> 模块。</p>
+</div>
+
+<p>让我们回到Geolocation API的例子 - 这是一个非常简单的API,由几个简单的对象组成:</p>
+
+<ul>
+ <li>{{domxref("Geolocation")}}, 其中包含三种控制地理数据检索的方法</li>
+ <li>{{domxref("Position")}}, 表示在给定的时间的相关设备的位置。 — 它包含一个当前位置的 {{domxref("Coordinates")}} 对象。还包含了一个时间戳,这个时间戳表示获取到位置的时间。</li>
+ <li>{{domxref("Coordinates")}}, 其中包含有关设备位置的大量有用数据,包括经纬度,高度,运动速度和运动方向等。</li>
+</ul>
+
+<pre class="brush: js notranslate">navigator.geolocation.getCurrentPosition(function(position) {
+ var latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
+ var myOptions = {
+ zoom: 8,
+ center: latlng,
+ mapTypeId: google.maps.MapTypeId.TERRAIN,
+ disableDefaultUI: true
+ }
+ var map = new google.maps.Map(document.querySelector("#map_canvas"), myOptions);
+});</pre>
+
+<div class="note">
+<p><strong>Note</strong>: 当您第一次加载上述实例,应当出现一个对话框询问您是否乐意对此应用共享位置信息(参见 {{anch("They have additional security mechanisms where appropriate")}} 这一稍后将会提到的部分)。 您需要同意这项询问以将您的位置于地图上绘制。如果您始终无法看见地图,您可能需要手动修改许可项。修改许可项的方法取决于您使用何种浏览器,对于Firefox浏览器来说,在页面信息 &gt; 权限 中修改位置权限,在Chrome浏览器中则进入 设置 &gt; 隐私 &gt; 显示高级设置 &gt; 内容设置,其后修改位置设定。</p>
+</div>
+
+<p>我们首先要使用 {{domxref("Geolocation.getCurrentPosition()")}} <font>方法返回设备的当前位置。</font><font>浏览器的 </font>{{domxref("Geolocation")}} 对象通过调用 {{domxref("Navigator.geolocation")}} <font>属性</font><font>来访问.</font></p>
+
+<pre class="brush: js notranslate">navigator.geolocation.getCurrentPosition(function(position) { ... });</pre>
+
+<p>这相当于做同样的事情</p>
+
+<pre class="brush: js notranslate">var myGeo = navigator.geolocation;
+myGeo.getCurrentPosition(function(position) { ... });</pre>
+
+<p>但是我们可以使用 "点运算符" 将我们的属性和方法的访问链接在一起,减少了我们必须写的行数。</p>
+
+<p>{{domxref("Geolocation.getCurrentPosition()")}} 方法只有一个必须的参数,这个参数是一个匿名函数,当设备的当前位置被成功取到时,这个函数会运行。  这个函数本身有一个参数,它包含一个表示当前位置数据的 {{domxref("Position")}} 对象。</p>
+
+<div class="note">
+<p><strong>注意:由另一个函数作为参数的函数称为</strong> (<a href="https://developer.mozilla.org/en-US/docs/Glossary/Callback_function">callback function</a> "回调函数").</p>
+</div>
+
+<p>仅在操作完成时调用函数的模式在JavaScript API中非常常见 - 确保一个操作已经完成,然后在另一个操作中尝试使用该操作返回的数据。这些被称为 <strong><a href="https://developer.mozilla.org/en-US/docs/Glossary/Asynchronous">asynchronous</a> </strong> “异步”操作。由于获取设备的当前位置依赖于外部组件(设备的GPS或其他地理定位硬件), 我们不能保证会立即使用返回的数据。 因此,这样子是行不通的:</p>
+
+<pre class="brush: js notranslate">var position = navigator.geolocation.getCurrentPosition();
+var myLatitude = position.coords.latitude;</pre>
+
+<p>如果第一行还没有返回结果,则第二行将会出现错误,因为位置数据还不可用。 出于这个原因,涉及同步操作的API被设计为使用 {{glossary("callback function")}}s “回调函数”,或更现代的 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> 系统,这些系统在ECMAScript 6中可用,并被广泛用于较新的API。</p>
+
+<p>我们将Geolocation API与第三方API(Google Maps API)相结合, — 我们正在使用它来绘制Google地图上由 <code>getCurrentPosition()</code>返回的位置。 我们通过链接到页面上使这个API可用。 — 你会在HTML中找到这一行:</p>
+
+<pre class="brush: html notranslate">&lt;script type="text/javascript" src="https://maps.google.com/maps/API/js?key=AIzaSyDDuGt0E5IEGkcE6ZfrKfUtE9Ko_de66pA"&gt;&lt;/script&gt;</pre>
+
+<p>要使用该API, 我们首先使用<code>google.maps.LatLng()</code>构造函数创建一个<code>LatLng</code>对象实例, 该构造函数需要我们的地理定位 {{domxref("Coordinates.latitude")}} 和 {{domxref("Coordinates.longitude")}}值作为参数:</p>
+
+<pre class="brush: js notranslate">var latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);</pre>
+
+<p>该对象实例被设置为 <code>myOptions</code>对象的<code>center</code>属性的值。然后我们通过调用<code>google.maps.Map()</code>构造函数创建一个对象实例来表示我们的地图, 并传递它两个参数 — 一个参数是我们要渲染地图的 {{htmlelement("div")}} 元素的引用 (ID为 <code>map_canvas</code>), 以及另一个参数是我们在上面定义的<code>myOptions</code>对象</p>
+
+<pre class="brush: js notranslate">var myOptions = {
+ zoom: 8,
+ center: latlng,
+ mapTypeId: google.maps.MapTypeId.TERRAIN,
+ disableDefaultUI: true
+}
+
+var map = new google.maps.Map(document.querySelector("#map_canvas"), myOptions);</pre>
+
+<p>这样做一来,我们的地图呈现了。</p>
+
+<p>最后一块代码突出显示了您将在许多API中看到的两种常见模式。 首先,API对象通常包含构造函数,可以调用这些构造函数来创建用于编写程序的对象的实例。 其次,API对象通常有几个可用的options(如上面的<code>myOptions</code>对象),可以调整以获得您的程序所需的确切环境(根据不同的环境,编写不同的<code>Options</code>对象)。 API构造函数通常接受options对象作为参数,这是您设置这些options的地方。</p>
+
+<div class="note">
+<p><strong>注意:如果您不能立即理解这个例子的细节,请不要担心。 我们将在未来的文章中详细介绍第三方API。</strong></p>
+</div>
+
+<h3 id="它们有可识别的入口点">它们有可识别的入口点</h3>
+
+<p>使用API时,应确保知道API入口点的位置。 在Geolocation API中,这非常简单 - 它是 {{domxref("Navigator.geolocation")}} 属性, 它返回浏览器的 {{domxref("Geolocation")}} 对象,所有有用的地理定位方法都可用。</p>
+
+<p>文档对象模型 (DOM) API有一个更简单的入口点 —它的功能往往被发现挂在 {{domxref("Document")}} 对象, 或任何你想影响的HTML元素的实例,例如:</p>
+
+<pre class="brush: js notranslate">var em = document.createElement('em'); // create a new em element
+var para = document.querySelector('p'); // reference an existing p element
+em.textContent = 'Hello there!'; // give em some text content
+para.appendChild(em); // embed em inside para</pre>
+
+<p>其他API具有稍微复杂的入口点,通常涉及为要编写的API代码创建特定的上下文。例如,Canvas API的上下文对象是通过获取要绘制的 {{htmlelement("canvas")}} 元素的引用来创建的,然后调用它的{{domxref("HTMLCanvasElement.getContext()")}}方法:</p>
+
+<pre class="brush: js notranslate">var canvas = document.querySelector('canvas');
+var ctx = canvas.getContext('2d');</pre>
+
+<p>然后,我们想通过调用内容对象 (它是{{domxref("CanvasRenderingContext2D")}}的一个实例)的属性和方法来实现我们想要对画布进行的任何操作, 例如:</p>
+
+<pre class="brush: js notranslate">Ball.prototype.draw = function() {
+ ctx.beginPath();
+ ctx.fillStyle = this.color;
+ ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
+ ctx.fill();
+};</pre>
+
+<div class="note">
+<p><strong><font><font>注意</font></font></strong><font><font>:您可以在我们的</font></font><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/introduction/bouncing-balls.html" rel="noopener"><font><font>弹跳球演示中</font></font></a><font><font>看到此代码的实际  </font></font><a href="http://mdn.github.io/learning-area/javascript/apis/introduction/bouncing-balls.html" rel="noopener"><font><font>运行情况</font></font></a><font><font>  (</font><font> 也可以</font><font>参阅它  </font><a href="http://mdn.github.io/learning-area/javascript/apis/introduction/bouncing-balls.html" rel="noopener"><font>现场运行</font></a><font>)。</font></font></p>
+</div>
+
+<h3 id="它们使用事件来处理状态的变化">它们使用事件来处理状态的变化</h3>
+
+<p><font><font>我们之前已经在课程中讨论了事件,在我们的  </font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events"><font><font>事件介绍</font></font></a><font><font>文章中 - 详细介绍了客户端Web事件是什么以及它们在代码中的用法。</font><font>如果您还不熟悉客户端Web API事件的工作方式,则应继续阅读。</font></font></p>
+
+<p><font><font>一些Web API不包含事件,但有些包含一些事件。</font><font>当事件触发时,允许我们运行函数的处理程序属性通常在单独的 “</font></font>Event handlers<font><font>”(事件处理程序) 部分的参考资料中列出。作为一个简单的例子,</font></font><code><a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a></code><font><font> 对象的</font><font>实例  </font><font>(每一个实例都代表一个到服务器的HTTP请求,来取得某种新的资源)都有很多事件可用,例如  </font></font><code>onload</code><font><font> 事件在成功返回时就触发包含请求的资源,并且现在就可用。</font></font></p>
+
+<p>下面的代码提供了一个简单的例子来说明如何使用它:</p>
+
+<pre class="brush: js notranslate">var requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json';
+var request = new XMLHttpRequest();
+request.open('GET', requestURL);
+request.responseType = 'json';
+request.send();
+
+request.onload = function() {
+ var superHeroes = request.response;
+ populateHeader(superHeroes);
+ showHeroes(superHeroes);
+}</pre>
+
+<div class="note">
+<p><strong>注意:您可以在我们的</strong><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/introduction/ajax.html">ajax.html</a><strong>示例中看到此代码</strong>  (或者 在线运行版本 <a href="http://mdn.github.io/learning-area/javascript/apis/introduction/ajax.html">see it live</a> also).</p>
+</div>
+
+<p><font><font>前五行指定了我们要获取的资源的位置,使用</font></font><code>XMLHttpRequest()</code><font><font> 构造函数</font><font>创建请求对象的新实例  </font><font>,打开HTTP 的 </font></font><code>GET</code><font><font> 请求以取得指定资源,指定响应以JSON格式发送,然后发送请求。</font></font></p>
+
+<p><font><font>然后 </font></font><code>onload</code> 处理函数指定我们如何处理响应。 我们知道请求会成功返回,并在需要加载事件(如<code>onload</code><font><font> 事件</font></font>)之后可用(除非发生错误),所以我们将包含返回的JSON的响应保存在<code>superHeroes</code>变量中,然后将其传递给两个不同的函数以供进一步处理。</p>
+
+<h3 id="它们在适当的地方有额外的安全机制">它们在适当的地方有额外的安全机制</h3>
+
+<p><font><font>WebAPI功能受到与JavaScript和其他Web技术(例如</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy"><font><font>同源政策</font></font></a><font><font>)</font><font>相同的安全考虑</font></font> 但是他们有时会有额外的安全机制。<font><font>例如,一些更现代的WebAPI将只能在通过HTTPS提供的页面上工作,因为它们正在传输潜在的敏感数据(例如  </font></font><a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API"><font><font>服务工作者</font></font></a><font><font>  和  </font></font><a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API"><font><font>推送</font></font></a><font><font>)。</font></font></p>
+
+<p><font><font>另外,一旦调用WebAPI请求,用户就可以在您的代码中启用一些WebAPI请求权限。</font><font>作为一个例子,在加载我们之前的</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation"><font><font>Geolocation</font></font></a><font><font>  示例</font><font>时,您可能注意到了类似下面的对话框  </font><font>:</font></font></p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/14313/location-permission.png"></p>
+
+<p><font><font>该  </font></font><a href="https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API"><font><font>通知API</font></font></a><font><font>  请求以类似的方式许可:</font></font></p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/14315/notification-permission.png"></p>
+
+<p>这些许可提示会被提供给用户以确保安全 - 如果这些提示不在适当位置,那么网站可能会在您不知情的情况下开始秘密跟踪您的位置,或者通过大量恼人的通知向您发送垃圾邮件。</p>
+
+<h2 id="概要">概要</h2>
+
+<p>在这一点上,您应该对API是什么,它们是如何工作的以及在JavaScript代码中可以对它们做什么有一个很好的了解。你可能很兴奋开始用特定的API来做一些有趣的事情,so let's go!  接下来,我们将看到使用文档对象模型(DOM)处理文档。</p>
+
+<p>{{NextMenu("Learn/JavaScript/Client-side_Web_APIs/Manipulating_documents", "Learn/JavaScript/Client-side_Web_API")}}</p>
diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/manipulating_documents/index.html b/files/zh-cn/learn/javascript/client-side_web_apis/manipulating_documents/index.html
new file mode 100644
index 0000000000..ceca6b3b57
--- /dev/null
+++ b/files/zh-cn/learn/javascript/client-side_web_apis/manipulating_documents/index.html
@@ -0,0 +1,300 @@
+---
+title: 操作文档
+slug: Learn/JavaScript/Client-side_web_APIs/Manipulating_documents
+translation_of: Learn/JavaScript/Client-side_web_APIs/Manipulating_documents
+---
+<div>{{LearnSidebar}}</div>
+
+<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Introduction", "Learn/JavaScript/Client-side_web_APIs/Fetching_data", "Learn/JavaScript/Client-side_web_APIs")}}</div>
+
+<p class="summary">在编写web页面或应用时,你最想做的事情之一就是以某种方式操作文档结构。这通常使用一套大量使用{{domxref("Document")}}对象来控制HTML和样式信息的文档对象模型(DOM)来实现,在本文中,我们可以更详细的看到怎样使用DOM,连同一些其他有趣的API以有趣的方式改变你的环境</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">前提:</th>
+ <td>基础的计算机常识,基本了解HTML、CSS和JavaScript - 包括JavaScript对象。</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>熟悉核心DOM API, 以及其他和DOM与文档操作相关的常见API。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="web浏览器的重要部分">web浏览器的重要部分</h2>
+
+<p>web浏览器的软件中有很多活动的程序片段,而许多片段web开发人员无法使用JavaScript来控制或操作,因此Web浏览器是一个很复杂的软件组合。你可能认为这样的限制是不是好事,但是浏览器被锁定是有充分理由的,主要集中在安全方面。如果一个网站可以访问您存储的密码或其他敏感信息,犹如你一样登录到网站,试想会发生什么?</p>
+
+<p>尽管有局限性,Web API仍然允许我们访问许多的功能,使我们用web页做很多事情。有几个在代码中经常引用的非常明显的部位 - 下面的图表表示了直接出现在web页面视图中的浏览器的主要部分:</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/14557/document-window-navigator.png" style="display: block; margin: 0 auto;"></p>
+
+<ul>
+ <li>window是载入浏览器的标签,在JavaScript中用{{domxref("Window")}}对象来表示,使用这个对象的可用方法,你可以返回窗口的大小(参见{{domxref("Window.innerWidth")}}和{{domxref("Window.innerHeight")}}),操作载入窗口的文档,存储客户端上文档的特殊数据(例如使用本地数据库或其他存储设备),为当前窗口绑定<a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events#A_series_of_fortunate_events">event handler</a>,等等。</li>
+ <li>navigator表示浏览器存在于web上的状态和标识(即用户代理)。在JavaScript中,用{{domxref("Navigator")}}来表示。你可以用这个对象获取一些信息,比如来自用户摄像头的地理信息、用户偏爱的语言、多媒体流等等。</li>
+ <li>document(在浏览器中用DOM表示)是载入窗口的实际页面,在JavaScript中用{{domxref("Document")}} 对象表示,你可以用这个对象来返回和操作文档中HTML和CSS上的信息。例如获取DOM中一个元素的引用,修改其文本内容,并应用新的样式,创建新的元素并添加为当前元素的子元素,甚至把他们一起删除。</li>
+</ul>
+
+<p>在本文中,我们主要关注操作文档,但是也会稍微关注一下其他有用的部位。</p>
+
+<h2 id="文档对象模型">文档对象模型</h2>
+
+<p>在浏览器标签中当前载入的文档用文档对象模型来表示。这是一个由浏览器生成的“树结构”,使编程语言可以很容易的访问HTML结构 — 例如浏览器自己在呈现页面时,使用它将样式和其他信息应用于正确的元素,而页面呈现完成以后,开发人员可以用JavaScript操作DOM。</p>
+
+<p>我们已经创建一个简单的例子<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/dom-example.html">dom-example.html</a> (<a href="http://mdn.github.io/learning-area/javascript/apis/document-manipulation/dom-example.html">see it live also</a>). 在你的浏览器中打开它 — 这是一个很简单的页面,包含了一个{{htmlelement("section")}} 元素,里面有一个图像和有链接的段落。HTML源码如下:</p>
+
+<pre class="brush: html">&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+ &lt;head&gt;
+ &lt;meta charset="utf-8"&gt;
+ &lt;title&gt;Simple DOM example&lt;/title&gt;
+ &lt;/head&gt;
+ &lt;body&gt;
+ &lt;section&gt;
+ &lt;img src="dinosaur.png" alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth."&gt;
+ &lt;p&gt;Here we will add a link to the &lt;a href="https://www.mozilla.org/"&gt;Mozilla homepage&lt;/a&gt;&lt;/p&gt;
+ &lt;/section&gt;
+ &lt;/body&gt;
+&lt;/html&gt;</pre>
+
+<p>另一方面,DOM树如下所示:</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/14559/dom-screenshot.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p>
+
+<div class="note">
+<p><strong>注意</strong>: 这个DOM树状图是用Ian Hickson的<a href="https://software.hixie.ch/utilities/js/live-dom-viewer/">Live DOM viewer</a>生成的</p>
+</div>
+
+<p>这里你可以看到,文档中每个元素和文本在树中都有它们自己的入口 — 称之为<strong>节点</strong>。你将用不同的术语来描述节点的类型和它们相对于其他节点的位置:</p>
+
+<ul>
+ <li><strong>元素节点</strong>: 一个元素,存在于DOM中。</li>
+ <li><strong>根节点</strong>: 树中顶层节点,在HTML的情况下,总是一个<code>HTML</code>节点(其他标记词汇,如SVG和定制XML将有不同的根元素)。</li>
+ <li><strong>子节点</strong>: <em>直接</em>位于另一个节点内的节点。例如上面例子中,<code>IMG</code>是<code>SECTION</code>的子节点。</li>
+ <li><strong>后代节点</strong>: 位于另一个节点内<em>任意位置</em>的节点。例如 上面例子中,<code>IMG</code>是<code>SECTION</code>的子节点,也是一个后代节点。<code>IMG</code>不是<code>BODY</code>的子节点,因为它在树中低了<code>BODY</code>两级,但它是<code>BODY</code>的后代之一。</li>
+ <li><strong>父节点</strong>: 里面有另一个节点的节点。例如上面的例子中<code>BODY</code>是<code>SECTION</code>的父节点。</li>
+ <li><strong>兄弟节点</strong>: DOM树中位于同一等级的节点。例如上面例子中,<code>IMG</code>和<code>P</code>是兄弟。</li>
+ <li><strong>文本节点</strong>: 包含文字串的节点</li>
+</ul>
+
+<p>在用DOM工作之前,熟悉这些术语是很有用的,因为你将会遇到大量代码术语的使用。你在研究CSS时也会遇到这些术语(例如后代选择器、子选择器)</p>
+
+<h2 id="主动学习_基本的DOM_操作">主动学习: 基本的DOM 操作</h2>
+
+<p>要开始学习DOM操作,我们先做一个实际的例子。</p>
+
+<ol>
+ <li>本地备份<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/dom-example.html">dom-example.html page</a>和与之相关的<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/dinosaur.png">image</a>。</li>
+ <li>在闭合的<code>&lt;/body&gt;</code>标签上面加入<code>&lt;script&gt;&lt;/script&gt;</code>元素。</li>
+ <li>要操作DOM内的元素,首先需要选择它,并将它的引用存储在一个变量中。在script元素中,添加下列代码行:
+ <pre class="brush: js">var link = document.querySelector('a');</pre>
+ </li>
+ <li>现在你有了一个存储在变量中的元素引用,你可以使用它的可用属性和方法来操作它(在{{htmlelement("a")}}元素的情况下定义为接口{{domxref("HTMLAnchorElement")}},它更常常用的父接口是{{domxref("HTMLElement")}}和表示DOM中所有节点的{{domxref("Node")}})。首先,更新 {{domxref("Node.textContent")}}属性的值来修改链接中的文字。在上面的代码后面加入一行代码:
+ <pre class="brush: js">link.textContent = 'Mozilla Developer Network';</pre>
+ </li>
+ <li>我们也能修改链接指向的URL,使得它被点击时不会走向错误的位置。在底部再加入下列代码:
+ <pre class="brush: js">link.href = 'https://developer.mozilla.org';</pre>
+ </li>
+</ol>
+
+<div>
+<p>注意,和JavaScript中的许多事情一样,有很多方法可以选择一个元素,并在一个变量中存储一个引用。{{domxref("Document.querySelector()")}}是推荐的主流方法,它允许你使用CSS选择器选择元素,使用很方便。上面的<code>querySelector()</code>调用会匹配它在文档中遇到的第一个{{htmlelement("a")}}元素。如果想对多个元素进行匹配和操作,你可以使用{{domxref("Document.querySelectorAll()")}},这个方法匹配文档中每个匹配选择器的元素,并把它们的引用存储在一个<a href="/en-US/docs/Learn/JavaScript/First_steps/Arrays">array</a>中。</p>
+
+<p>对于获取元素引用,还有一些更旧的方法,如:</p>
+
+<ul>
+ <li>{{domxref("Document.getElementById()")}},选择一个<code>id</code>属性值已知的元素,例如<code>&lt;p id="myId"&gt;My paragraph&lt;/p&gt;</code>。ID作为参数传递给函数,即 <code>var elementRef = document.getElementById('myId')</code>。</li>
+ <li>{{domxref("Document.getElementsByTagName()")}},返回页面中包含的所有已知类型元素的数组。如<code>&lt;p&gt;</code>s, <code>&lt;a&gt;</code>。元素类型作为参数传递给函数,即<code>var elementRefArray = document.getElementsByTagName('p')</code>.</li>
+</ul>
+
+<p>在较老的浏览器中使用这两种方法而不是流行的<code>querySelector()</code>方法,但这样并不方便。看一看你还可以发现别的什么!</p>
+</div>
+
+<h3 id="创建并放置新的节点">创建并放置新的节点</h3>
+
+<p>以上只是让你稍微尝试一下你可以做的事情,让我们进一步看看我们可以怎样来创建新的元素。</p>
+
+<ol>
+ <li>回到当前的例子,我们先获取到{{htmlelement("section")}}元素的引用 — 在已有script中添加下列代码(其他代码也同样处理):
+ <pre class="brush: js">var sect = document.querySelector('section');</pre>
+ </li>
+ <li>现在用{{domxref("Document.createElement()")}}创建一个新的段落,用与之前相同的方法赋予相同的文本:
+ <pre class="brush: js">var para = document.createElement('p');
+para.textContent = 'We hope you enjoyed the ride.';</pre>
+ </li>
+ <li>现在可以用{{domxref("Node.appendChild()")}}方法在后面追加新的段落:
+ <pre class="brush: js">sect.appendChild(para);</pre>
+ </li>
+ <li>最后,在内部链接的段落中添加文本节点,完美的结束句子。首先我们要使用{{domxref("Document.createTextNode()")}}创建一个文本节点:
+ <pre class="brush: js">var text = document.createTextNode(' — the premier source for web development knowledge.');</pre>
+ </li>
+ <li>现在获取内部连接的段落的引用,并把文本节点绑定到这个节点上:
+ <pre class="brush: js">var linkPara = document.querySelector('p');
+linkPara.appendChild(text);</pre>
+ </li>
+</ol>
+
+<p>这是给DOM添加节点要做的大部分工作 — 在构建动态接口时,你将做大量使用这些方法(我们在后面可以看到一些例子)。</p>
+
+<h3 id="移动和删除元素">移动和删除元素</h3>
+
+<p>也许有时候你想移动或从DOM中删除节点,这是完全可能的。</p>
+
+<p>如果你想把具有内部链接的段落移到sectioin的底部,简单的做法是:</p>
+
+<pre class="brush: js">sect.appendChild(linkPara);</pre>
+
+<p>这样可以把段落下移到section的底部。你可能想过要做第二个副本,但是情况并非如此 — <code>linkPara</code>是指向该段落唯一副本的引用。如果你想做一个副本并也把它添加进去,只能用{{domxref("Node.cloneNode()")}} 方法来替代。</p>
+
+<p>删除节点也非常的简单,至少,你拥有要删除的节点和其父节点的引用。在当前情况下,我们只要使用{{domxref("Node.removeChild()")}}即可,如下:</p>
+
+<pre>sect.removeChild(linkPara);</pre>
+
+<p>要删除一个仅基于自身引用的节点可能稍微有点复杂,这也是很常见的。没有方法会告诉节点删除自己,所以你必须像下面这样操作。</p>
+
+<pre class="brush: js">linkPara.parentNode.removeChild(linkPara);</pre>
+
+<p>把上述代码行加到你的代码中去</p>
+
+<h3 id="操作样式">操作样式</h3>
+
+<p>通过JavaScript以不同的方式来操作CSS样式是可能的。</p>
+
+<p>首先,使用 {{domxref("Document.stylesheets")}}返回{{domxref("CSSStyleSheet")}}数组,获取绑定到文档的所有样式表的序列。然后添加/删除想要的样式。然而,我们并不想扩展这些特性,因此它们在操作样式方面有点陈旧和困难,而现在有了更容易的方法。</p>
+
+<p>第一种方法是直接在想要动态设置样式的元素内部添加内联样式。这是用{{domxref("HTMLElement.style")}}属性来实现。这个属性包含了文档中每个元素的内联样式信息。你可以设置这个对象的属性直接修改元素样式。</p>
+
+<ol>
+ <li>要做个例子,把下面的代码行加到我们的例子中:
+ <pre class="brush: js">para.style.color = 'white';
+para.style.backgroundColor = 'black';
+para.style.padding = '10px';
+para.style.width = '250px';
+para.style.textAlign = 'center';</pre>
+ </li>
+ <li>重新载入页面,你将看到样式已经应用到段落中。如果在浏览器的<a href="/en-US/docs/Tools/Page_Inspector">Page Inspector/DOM inspector</a>中查看段落,你会看到这些代码的确为文档添加了内联样式:
+ <pre class="brush: html">&lt;p style="color: white; background-color: black; padding: 10px; width: 250px; text-align: center;"&gt;We hope you enjoyed the ride.&lt;/p&gt;</pre>
+ </li>
+</ol>
+
+<div class="note">
+<p><strong>注意</strong>: CSS样式的JavaSript属性版本以小驼峰式命名法书写,而CSS版本带连接符号(<code>backgroundColor</code> 对 <code>background-color</code>)。确保你不会混淆,否则就不能工作。</p>
+</div>
+
+<p>现在我们来看看另一个操作文档样式的常用方法。</p>
+
+<ol>
+ <li>删除之前添加到JavaScript中的五行代码。</li>
+ <li>在HTML的{{htmlelement("head")}}中添加下列代码 :
+ <pre>&lt;style&gt;
+.highlight {
+ color: white;
+ background-color: black;
+ padding: 10px;
+ width: 250px;
+ text-align: center;
+}
+&lt;/style&gt;</pre>
+ </li>
+ <li>现在我们改为使用HTML操作的常用方法 — {{domxref("Element.setAttribute()")}} — 这里有两个参数,你想在元素上设置的属性,你要为它设置的值。在这种情况下,我们在段落中设置类名为highlight:
+ <pre class="brush: js">para.setAttribute('class', 'highlight');</pre>
+ </li>
+ <li>刷新页面,看不到改变 — CSS仍然应用到段落,但是这次给出CSS规则选择的类不是内联CSS样式。</li>
+</ol>
+
+<p>两种方式各有优缺点,选择哪种取决于你自己。第一种方式无需安装,适合简单应用,第二种方式更加正统(没有CSS和JavaScript的混合,没有内联样式,而这些被认为是不好的体验)。当你开始构建更大更具吸引力的应用时,你可能会更多地使用第二种方法,但这完全取决于你自己。</p>
+
+<p>在这一点上,我们还没有做任何有用的事!使用JavaScript创建静态内容是毫无意义的 — 最好将其写入HTML,而不使用JavaScript。用JavaScript创建内容也有其他问题(如不能被搜索引擎读取),比HTML复杂得多。</p>
+
+<p>在接下来的几节中我们将看看DOM API一些更实际的用途。</p>
+
+<div class="note">
+<p><strong>注意</strong>: 你可以在GitHub上找到演示程序<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/dom-example-manipulated.html">finished version of the dom-example.html</a> (<a href="http://mdn.github.io/learning-area/javascript/apis/document-manipulation/dom-example-manipulated.html">see it live also</a>).</p>
+</div>
+
+<h2 id="主动学习:从Window对象中获取有用的信息">主动学习:从Window对象中获取有用的信息</h2>
+
+<p>到目前为止,我们只真正看到使用{{domxref("Node")}}和{{domxref("Document")}} 特性来操纵文档,但是没有理由不能从其他来源获取数据并在UI中使用它。想想我们最新文件中的演示例子 <a href="http://mdn.github.io/learning-area/javascript/apis/introduction/maps-example.html">maps-example.html</a> — 那里我们获取一些位置数据并用来显示你所在区域的地图。你只要确保你的数据格式正确;使用JavaScript比其他许多语言更容易,因为它是弱类型的——例如,当你想把它们打印到屏幕上时,数字会自动转换成字符串。</p>
+
+<p>在这个例子中,我们解决了一个常见的问题 — 不管窗口的大小是多少,确保应用程序和它所在的窗口视图一样大。在玩游戏的情况下,想在游戏中尽可能多地使用屏幕区域,这种方法是很有用的。</p>
+
+<p>一开始,要做一个<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/window-resize-example.html">window-resize-example.html</a>和<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/bgtile.png">bgtile.png</a>文件的本地拷贝。打开文件看一看 — 你可以看到我们用一个{{htmlelement("div")}}元素包裹屏幕的小部分,用来获得应用的background tile。我们也用它来表示应用的UI区域。</p>
+
+<ol>
+ <li>首先,获取这个div的引用,然后获取视窗(显示文档的内部窗口)的宽度和高度, 并存入变量中 — 这两个值包含在{{domxref("Window.innerWidth")}} 和 {{domxref("Window.innerHeight")}}属性中。在已存在的{{htmlelement("script")}}元素中加入下列代码:
+ <pre class="brush: js">var div = document.querySelector('div');
+var WIDTH = window.innerWidth;
+var HEIGHT = window.innerHeight;</pre>
+ </li>
+ <li>接下来,我们将动态地改变div的宽度和高度,使其等于视窗的宽度和高度。 在您的代码下面添加以下两行:
+ <pre class="brush: js">div.style.width = WIDTH + 'px';
+div.style.height = HEIGHT + 'px';</pre>
+ </li>
+ <li>保存并刷新浏览器 — 现在可以看到不管你使用什么大小的屏幕,div变得和视窗一样大。如果要调整窗口大小使其更大,你可以看到div会保持相同大小 — 因为我们只能设置一次。</li>
+ <li>在我们调整窗口时,我们怎样用事件来调整div的大小? {{domxref("Window")}}对象有一个称为resize的可用事件。每次窗口调整大小时都会触发该事件 — 我们可以通过{{domxref("Window.onresize")}} 事件处理程序来访问它,并返回每次改变大小的代码。在代码底部添加下列程序:
+ <pre class="brush: js">window.onresize = function() {
+ WIDTH = window.innerWidth;
+ HEIGHT = window.innerHeight;
+ div.style.width = WIDTH + 'px';
+ div.style.height = HEIGHT + 'px';
+}</pre>
+ </li>
+</ol>
+
+<div class="note">
+<p><strong>注意</strong>: 如果你被卡住了,可查看<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/window-resize-example-finished.html">finished window resize example</a> (<a href="http://mdn.github.io/learning-area/javascript/apis/document-manipulation/window-resize-example-finished.html">see it live also</a>).</p>
+</div>
+
+<h2 id="主动学习_一个动态的购物单">主动学习: 一个动态的购物单</h2>
+
+<p>作为文章的收尾,我们想给你一个小小的挑战 — 我们要做一个简单的购物单的例子,使用表单的输入框和按钮动态的向购物单中添加购物项。在输入中添加项,然后按下按钮:</p>
+
+<ul>
+ <li>购物项应该出现在清单中。</li>
+ <li>每个购物项都应该给出一个按钮,可以按下按钮从清单中删除该项。</li>
+ <li>输入框应该是空白的并已聚焦,为你准备好输入另一个项。</li>
+</ul>
+
+<p>完成后的演示程序看起来有点像这样的:</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/14563/shopping-list.png" style="border-style: solid; border-width: 1px; display: block; height: 225px; margin: 0px auto; width: 369px;"></p>
+
+<p>要完成实验,要按照下面的步骤,确保购物单的行为如上所述。</p>
+
+<ol>
+ <li>首先,下载<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/shopping-list.html">shopping-list.html</a>文件,并存入本地。你会看到它有一些极小的CSS,一个带有label、input和button的list和一个空的list以及{{htmlelement("script")}} 元素。要添加的所有程序都在script里面。</li>
+ <li>创建三个变量来保存list({{htmlelement("ul")}})、{{htmlelement("input")}}和{{htmlelement("button")}}元素的引用。</li>
+ <li>创建一个<a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions">函数</a>响应点击按钮。</li>
+ <li>在函数体内,开始要在一个变量中存储输入框的当前<a href="/en-US/docs/Web/API/HTMLInputElement#Properties">值</a>。</li>
+ <li>然后,为输入框元素设置空字符 - <code>''</code>使其为空</li>
+ <li>创建三个新元素 — 一个list项({{htmlelement('li')}}),{{htmlelement('span')}}和 {{htmlelement('button')}},并把它们存入变量之中。</li>
+ <li>把span和button作为list项的子节点。</li>
+ <li>把之前保存的输入框元素的值设置为span的文本内容,按钮的文本内容设置为'Delete'</li>
+ <li>把list项设置为list的子节点。</li>
+ <li>为删除按钮绑定事件处理程序。当点击按钮时,删除它所在的整个list项。</li>
+ <li>最后,使用<code><a href="/en-US/docs/Web/API/HTMLElement/focus">focus()</a></code>方法聚焦输入框准备输入下一个购物项。</li>
+</ol>
+
+<div class="note">
+<p><strong>注意</strong>: 如果你卡住了,请查看<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/shopping-list-finished.html">finished shopping list</a> (<a href="http://mdn.github.io/learning-area/javascript/apis/document-manipulation/shopping-list-finished.html">see it running live also</a>.)</p>
+</div>
+
+<h2 id="总结">总结</h2>
+
+<p>我们已经结束了对文档和DOM操作的研究。在这一点上,你应该了解Web浏览器在控制文档和用户Web体验的其他方面方面的重要部分。更重要的是,你应该了解什么是文档对象模型,怎样操作它来创建有用的功能。</p>
+
+<h2 id="另见">另见</h2>
+
+<p>你还可以使用更多的特性来操作文档,检查一些参考,看看你能发现些什么?</p>
+
+<ul>
+ <li>{{domxref("Document")}}</li>
+ <li>{{domxref("Window")}}</li>
+ <li>{{domxref("Node")}}</li>
+ <li>{{domxref("HTMLElement")}}, {{domxref("HTMLInputElement")}}, {{domxref("HTMLImageElement")}}, etc.</li>
+</ul>
+
+<p>(MDN上有完整的Web API 列表,参见<a href="https://developer.mozilla.org/zh-CN/docs/Web/API">Web API接口参考</a> !)</p>
+
+<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Introduction", "Learn/JavaScript/Client-side_web_APIs/Fetching_data", "Learn/JavaScript/Client-side_web_APIs")}}</div>
diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/third_party_apis/index.html b/files/zh-cn/learn/javascript/client-side_web_apis/third_party_apis/index.html
new file mode 100644
index 0000000000..edaeb3acde
--- /dev/null
+++ b/files/zh-cn/learn/javascript/client-side_web_apis/third_party_apis/index.html
@@ -0,0 +1,484 @@
+---
+title: 第三方 API
+slug: Learn/JavaScript/Client-side_web_APIs/Third_party_APIs
+tags:
+ - API
+ - youtube
+ - 初学者
+ - 第三方
+ - 谷歌地图
+translation_of: Learn/JavaScript/Client-side_web_APIs/Third_party_APIs
+---
+<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Fetching_data", "Learn/JavaScript/Client-side_web_APIs/Drawing_graphics", "Learn/JavaScript/Client-side_web_APIs")}}</div>
+
+<p class="summary">到目前为止我们已经介绍的API是内置在浏览器中的,但并不是所有的API都是。许多大型网站和服务(例如Google地图,Twitter,Facebook,PayPal等)提供的API允许开发者使用他们的数据(例如在博客上显示您的Twitter流)或服务(例如在您的网站上显示自定义Google地图,或者使用Facebook登录来登录你的用户)。本文着眼于浏览器API和第三方API的区别,并展示了后者的一些典型用途。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">先决条件:</th>
+ <td>JavaScript 基础知识 (see <a href="/en-US/docs/Learn/JavaScript/First_steps">first steps</a>, <a href="/en-US/docs/Learn/JavaScript/Building_blocks">building blocks</a>, <a href="/en-US/docs/Learn/JavaScript/Objects">JavaScript objects</a>), the <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">basics of Client-side APIs</a></td>
+ </tr>
+ <tr>
+ <th scope="row">目的:</th>
+ <td> 学习了解第三方API的运作方式,以及如何运用它们来提高你的网站性能</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="什么是第三方API">什么是第三方API?</h2>
+
+<p>第三方API是由第三方(通常是Facebook,Twitter或Google等公司)提供的API,允许您通过JavaScript访问其功能,并在您自己的站点上使用它。 正如我们在 <a href="/ch-ZN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">API介绍章节</a> 所展示的, 最显著的例子就是运用 <a href="https://developers.google.com/maps/">Google Maps APIs</a> 在你的网页上展示自定义地图。</p>
+
+<p>让我们再来瞧一眼这个地图的例子 (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/introduction/maps-example.html">source code on GitHub</a>; <a href="https://mdn.github.io/learning-area/javascript/apis/introduction/maps-example.html">see it live also</a>), 从这里可以知道第三方API和浏览器API的区别是怎么样的。</p>
+
+<div class="note">
+<p><strong>Note</strong>: 您可能想要一次获得所有的代码示例,在这种情况下,您可以搜索repo来获取每个部分中需要的示例文件。</p>
+</div>
+
+<h3 id="它们植根于第三方服务器">它们植根于第三方服务器</h3>
+
+<p>浏览器API在浏览器构建之初就存在 — 用JavaScript就可以立即访问它们。例如, 例子中所使用的 <a href="/en-US/docs/Web/API/Geolocation/Using_geolocation">Geolocation API</a> 就是通过使用 <code><a href="/en-US/docs/Web/API/Navigator">Navigator</a></code> 对象的属性 geolocation 来访问的, 它返回一个名为 <code><a href="/en-US/docs/Web/API/Geolocation">Geolocation</a></code> 的对象。 这个例子使用了这个对象的方法 <code><a href="/en-US/docs/Web/API/Geolocation/getCurrentPosition">getCurrentPosition()</a></code> 来请求当前设备所处的地点:</p>
+
+<pre class="brush: js notranslate">navigator.geolocation.getCurrentPosition(function(position) { ... });</pre>
+
+<p>第三方API,从某种角度讲,植根于第三方服务器上。要通过 JavaScript获取它们,您首先需要链接到其功能接口上并使其在您的页面上生效。通常来说,这首先需要您通过一个 {{htmlelement("script")}} 元素连接到第三方服务器所开放的JavaScript库,如下所示:</p>
+
+<pre class="brush: js notranslate">&lt;script type="text/javascript" src="https://maps.google.com/maps/api/js?key=AIzaSyDDuGt0E5IEGkcE6ZfrKfUtE9Ko_de66pA"&gt;&lt;/script&gt;</pre>
+
+<p>然后您便可使用该库中可用的对象了,如:</p>
+
+<pre class="brush: js notranslate">var latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
+var myOptions = {
+ zoom: 8,
+ center: latlng,
+ mapTypeId: google.maps.MapTypeId.TERRAIN,
+ disableDefaultUI: true
+}
+
+var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);</pre>
+
+<p>代码中我们用 <code>google.maps.LatLng()</code> 构造器创建了一个新的 <code>LatLng</code> 对象,它包含了我们想展示的地址的纬度和经度,作为一个Geolocation API返回。然后,我们创建了包含这个对象,和其他有关地图显示信息的选项对象(<code>myOptions</code>) 。最后,用 <code>google.maps.Map()</code> 构造器创建了<code>map</code>对象,它接受网页元素(地图展示处)和选项对象两个参数。</p>
+
+<p>以上就是用 Google Maps API 建立一个简单地图所需要的所有信息。所有复杂的工作都全由你所连接的第三方服务器处理,包括展示正确地理位置的地图块,等等。</p>
+
+<div class="note">
+<p><strong>Note</strong>: 一些api处理对其功能的访问略有不同,相反,它们要求开发人员向特定的URL模式发出HTTP请求(参见从服务器获取数据),以检索特定的数据。这些被称为RESTful api,稍后我们将在文章中展示这个示例。</p>
+</div>
+
+<h3 id="权限的不同处理方式">权限的不同处理方式</h3>
+
+<p>正如我们在第一篇文章中所讨论的,浏览器api的安全性通常是通过权限提示。这样做的目的是为了让用户知道他们访问的网站上发生了什么,并且不太可能成为恶意使用API的人的受害者。</p>
+
+<p>第三方API有一个稍微不同的权限系统——它们倾向于使用关键代码来允许开发人员访问API功能。再次查看我们链接到的谷歌地图API库的URL:</p>
+
+<pre class="notranslate">https://maps.google.com/maps/api/js?key=AIzaSyDDuGt0E5IEGkcE6ZfrKfUtE9Ko_de66pA</pre>
+
+<p>URL末尾提供的URL参数是一个开发人员密钥—应用程序的开发人员必须应用它来获取一个密钥,然后以一种特定的方式将其包含在代码中,才能允许访问API的功能。对于谷歌映射(以及其他谷歌API),可以在谷歌云平台上申请一个密钥。</p>
+
+<p>其他API的包含方式稍微不同,但是大多数API的模式都非常相似。</p>
+
+<p>需要密钥的原因是:所用使用API功能的人都需要承担责任。当开发者注册一个密钥之后,如果他们开始恶意使用API(例如跟踪位置,试图用大量的垃圾邮件干扰用户正常工作),此时API的提供者可以采取措施。最简单的措施是撤销开发者的API的使用权。</p>
+
+<h2 id="扩展the_Google_Maps示例">扩展the Google Maps示例</h2>
+
+<p>现在我们已经检验了Google Maps API示例以及它的运作方式,让我们添加一些更多的功能来展示如何使用API的其他特性。</p>
+
+<ol>
+ <li>
+ <p>要开始这个部分, 确保你已经在一个新的目录复制 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/google-maps/maps_start.html">Google Maps启动文件</a>。 如果你已经 <a href="/en-US/docs/Learn#Getting_our_code_examples">克隆了示例存储库</a>,那么你已经拥有了一个这个文件的拷贝,你可以在<em>javascript/apis/third-party-apis/google-maps目录中找到该文件。</em></p>
+ </li>
+ <li>
+ <p>接下来,用以下步骤获取你自己的开发者密钥:</p>
+
+ <ol>
+ <li>跳转到 <a href="https://console.cloud.google.com/apis/dashboard">Google Cloud Platform API Manager dashboard</a>.</li>
+ <li>如果你还没有新项目,请创建一个新项目。</li>
+ <li>单击“启用 API”按钮。</li>
+ <li>选择<em>Google Maps JavaScript API</em>.</li>
+ <li>单击“启用”按钮。</li>
+ <li>单击创建凭据,然后选择API密钥。</li>
+ <li>复制你的API密钥并将示例中的第一个{{htmlelement("script")}}元素中的现有密钥替换为你自己的密钥。(位于<code>?key=</code>和属性结束引号标记 (<code>"</code>)之间的位置。)</li>
+ </ol>
+
+ <div class="note">
+ <p><strong>注意:获取</strong>Google相关API密钥可能会有一点困难——Google Cloud Platform API Manager有许多不同的屏幕 ,并且工作流程可能因您是否设置账户而变得细微的不同。如果您在执行此步骤时遇到了困难,我们将很乐意为您提供帮助——<a href="/en-US/docs/Learn#Contact_us">联系我们</a>。</p>
+ </div>
+ </li>
+ <li>打开你的Google Maps起始文件,找到<code>INSERT-YOUR-API-KEY-HERE</code>字符串,然后将其替换为你从Google Cloud Platform API Manager dashboard获取的实际API密钥。</li>
+</ol>
+
+<h3 id="添加自定义标记">添加自定义标记</h3>
+
+<p>添加一个标记在地图上(icon)在某种程度上是很容易的,你只需要创建一个新的标记使用google.maps.Marker()构造函数,传递给它一个包含位置显示标记的选择对象(如LatLng对象),和Map对象来显示它。</p>
+
+<ol>
+ <li>
+ <p>在 <code>var map ...</code> 行下面添加下列代码:</p>
+
+ <pre class="brush: js notranslate">var marker = new google.maps.Marker({
+ position: latlng,
+ map: map
+});</pre>
+
+ <p>现在如果你刷新你的页面, 你会看到地图中间弹出了一个小小的漂亮标记。这很酷, 但是这并不是一个定制的标记图标 — 它使用了默认的标记图标。</p>
+ </li>
+ <li>
+ <p>如果要使用定制化的图标,我们需要在创建标记时通过URL来明确说明。首先,在刚才添加的代码块之后添加下面的代码:</p>
+
+ <pre class="brush: js notranslate">var iconBase = 'https://maps.google.com/mapfiles/kml/shapes/';</pre>
+
+ <p>这定义了所有Google Maps官方图标存储的URL(如果你想的话你也可以使用你自己的图标存储位置)。</p>
+ </li>
+ <li>
+ <p>图标的位置应当在选项对象的<code>icon</code> 属性中说明。更新Constructor并添加icon属性,如下:</p>
+
+ <pre class="brush: js notranslate">var marker = new google.maps.Marker({
+ position: latlng,
+ icon: iconBase + 'flag_maps.png',
+ map: map
+});</pre>
+
+ <p>这里我们用<code>iconBase</code> 加上图标的文件名,从而创建完整URL的方式阐明了icon属性。现在尝试重新加载你的例子,你会看到你的地图上显示了一个定制的标记!</p>
+ </li>
+</ol>
+
+<div class="note">
+<p><strong>Note</strong>: 访问 <a href="https://developers.google.com/maps/documentation/javascript/custom-markers">Customizing a Google Map: Custom Markers</a> 以查看更多信息。</p>
+</div>
+
+<div class="note">
+<p><strong>Note</strong>: 访问 <a href="https://fusiontables.google.com/DataSource?dsrcid=308519#map:id=3">Map marker or Icon names</a> 以找出还有哪些可以的图标,并查看它们的引用名称是什么。它们的文件名应当是当你点击它们时显示出的图标名,最后带有".png"。</p>
+</div>
+
+<h3 id="单击标记时显示弹出窗口"><span class="tlid-translation translation" lang="zh-CN"><span title="">单击标记时显示弹出窗口</span></span></h3>
+
+<p><span class="tlid-translation translation" lang="zh-CN"><span title="">Google地图的另一个常见用例是在点击其名称或标记时显示有关某个地点的更多信息(弹出式广告在Google Maps API中称为信息窗口)。</span> <span title="">这也很容易实现,所以让我们来看看它。</span></span></p>
+
+<ol>
+ <li>
+ <p><span class="tlid-translation translation" lang="zh-CN"><span title="">首先,您需要指定一个包含HTML的JavaScript字符串,该字符串将定义弹出窗口的内容。</span> <span title="">这将由API注入弹出窗口,并且可以包含您想要的任何内容。</span> <span title="">在<code>google.maps.Marker()</code>构造函数定义下面添加以下行:</span></span></p>
+
+ <pre class="brush: js notranslate">var contentString = '&lt;div id="content"&gt;&lt;h2 id="firstHeading" class="firstHeading"&gt;Custom info window&lt;/h2&gt;&lt;p&gt;This is a cool custom info window.&lt;/p&gt;&lt;/div&gt;';</pre>
+ </li>
+ <li>
+ <p>然后,你需要使用<code>google.maps.InfoWindow()</code> 构造器,创建一个新的info window object。在之前的代码下面,添加以下代码:</p>
+
+ <pre class="brush: js notranslate">var infowindow = new google.maps.InfoWindow({
+ content: contentString
+});</pre>
+
+ <p>还有其他可用的属性 (查看 <a href="https://developers.google.com/maps/documentation/javascript/infowindows">Info Windows</a>), 但是在这里我们只具体说明指向内容源的<code>content</code> 属性。</p>
+ </li>
+ <li>
+ <p>最后,为了在单击标记(marker)时显示弹出窗口,我们使用了一个简单的点击事件处理器。在<code>google.maps.InfoWindow()</code>构造器代码下面,添加以下代码:</p>
+
+ <pre class="brush: js notranslate">marker.addListener('click', function() {
+ infowindow.open(map, marker);
+});</pre>
+
+ <p>在函数中,我们只需调用 infowindow 的 <code>open()</code> 函数,该函数将要显示它的地图和希望它显示在旁边的标记作为参数。</p>
+ </li>
+ <li>
+ <p>现在尝试重新加载示例,然后单击标记!</p>
+ </li>
+</ol>
+
+<h3 id="Controlling_what_map_controls_are_displayed">Controlling what map controls are displayed</h3>
+
+<p>在原始 <code>google.maps.Map()</code>构造函数中,将看到 <code>disableDefaultUI: true</code> 。这将禁用您通常在 Google 地图上获得的所有标准 UI 控件。</p>
+
+<ol>
+ <li>
+ <p>将其值设置为 <code>false</code> (或完全删除此属性),重新加载示例,将看到地图缩放按钮、 scale indicator等等。</p>
+ </li>
+ <li>
+ <p>现在撤销上一次更改。</p>
+ </li>
+ <li>
+ <p>通过使用指定单个 UI 功能的其他属性,可以更精细地显示或隐藏控件。尝试在 <code>disableDefaultUI: true</code> 的下面添加代码(请记住在 <code>disableDefaultUI: true</code> 之后输入逗号,否则将收到错误):</p>
+
+ <pre class="brush: js notranslate">zoomControl: true,
+mapTypeControl: true,
+scaleControl: true,</pre>
+ </li>
+ <li>
+ <p>现在尝试重新加载示例以查看这些属性的效果。您可以在 <a href="https://developers.google.com/maps/documentation/javascript/3.exp/reference#MapOptions">MapOptions object reference page</a>找到更多属性。</p>
+ </li>
+</ol>
+
+<p>就是现在 - 看看 <a href="https://developers.google.com/maps/documentation/javascript/">Google Maps APIs documentation</a>,发现更多乐趣!</p>
+
+<h2 id="一个RESTful_API_—_NYTimes">一个RESTful API — NYTimes</h2>
+
+<p><span class="tlid-translation translation" lang="zh-CN"><span title="">现在让我们看看另一个API示例</span></span> - <a href="https://developer.nytimes.com">New York Times API</a><span class="tlid-translation translation" lang="zh-CN"><span title="">。</span> <span title="">此API允许您检索纽约时报的新闻故事信息并将其显示在您的网站上。</span> <span title="">这种类型的API称为RESTful API - 我们不像使用Google地图那样使用JavaScript库的功能获取数据,而是通过向特定网址发出HTTP请求来获取数据,其中包含搜索术语和其他属性编码的数据</span> <span title="">URL(通常作为URL参数)。</span> <span title="">这是您在API中遇到的常见模式。</span></span></p>
+
+<h2 id="使用第三方API的方法">使用第三方API的方法</h2>
+
+<p><span class="tlid-translation translation" lang="zh-CN"><span title="">下面我们将带您完成练习,向您展示如何使用NYTimes API,它还提供了一组更为通用的步骤,您可以将其用作处理新API的方法。</span></span></p>
+
+<h3 id="查找文档"><span class="tlid-translation translation" lang="zh-CN"><span title="">查找文档</span></span></h3>
+
+<p><span class="tlid-translation translation" lang="zh-CN"><span title="">当您想要使用第三方API时,必须找出文档的位置,以便了解API具有哪些功能,以及如何使用它们等等。NYTimes API文档位于</span></span> <a href="https://developer.nytimes.com/">https://developer.nytimes.com/</a>。</p>
+
+<h3 id="获取一个开发者密钥">获取一个开发者密钥</h3>
+
+<p><span class="tlid-translation translation" lang="zh-CN"><span title="">出于安全性和问责制的原因,大多数API都要求您使用某种开发人员密钥。</span> <span title="">要注册NYTimes API密钥,您需要访问</span></span> <a href="https://developer.nytimes.com/signup">https://developer.nytimes.com/signup</a>。</p>
+
+<ol>
+ <li>
+ <p>申请 "Article Search API" 的 API key ——新建一个应用,选择这个 API ,(填写名称和描述,打开 "Article Search API" 下面的开关,然后点击 “创建(Create)”)</p>
+ </li>
+ <li>
+ <p>从结果页面获取 API 。</p>
+ </li>
+ <li>
+ <p>现在开始构建这个应用,下载 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/nytimes/nytimes_start.html">nytimes_start.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/nytimes/nytimes.css">nytimes.css</a> 到一个新的本地目录。如果已经克隆过这个仓库 <a href="/en-US/docs/Learn#Getting_our_code_examples">cloned the examples repository</a>, 里面就已经有这 2 个文件了,它们存放在 <em>javascript/apis/third-party-apis/nytimes</em> 目录下。HTML 文件里的 <code>&lt;script&gt;</code> 标签下已经包含了构建这个应用需要用到的变量;下面我们来填写函数。</p>
+ </li>
+</ol>
+
+<p>下面是这个应用最终的样子,可以在搜索框里填写条目、起始日期和结束日期,作为参数向 Article Search API 接口发起查询,然后显示查询结果。</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/14821/nytimes-search.png" style="border-style: solid; border-width: 1px; display: block; height: 374px; margin: 0px auto; width: 700px;"></p>
+
+<h3 id="将API连接到应用">将API连接到应用</h3>
+
+<p>首先,建立 API 和本地应用的连接。对于这个 API ,每次向服务器对应的 URL 发起 get 请求,都需要把 API key 作为 get 请求的参数。</p>
+
+<ol>
+ <li>
+ <p>在代码中找到下面这行:</p>
+
+ <pre class="brush: js notranslate">var key = 'INSERT-YOUR-API-KEY-HERE';</pre>
+
+ <p>把 <code>INSERT-YOUR-API-KEY-HERE</code> 替换为在上一节中获得的 API key.</p>
+ </li>
+ <li>
+ <p>添加下面这行代码到 JavaScript 代码中。添加到注释 "<code>// Event listeners to control the functionality</code>" 的下面。当表单提交时 (按钮按下时) 运行这个函数  <code>submitSearch()</code> .</p>
+
+ <pre class="brush: js notranslate">searchForm.addEventListener('submit', <code>submitSearch</code>);</pre>
+ </li>
+ <li>
+ <p>添加 <code>submitSearch()</code> 和 <code>fetchResults()</code> 函数如下:</p>
+
+ <pre class="notranslate">function submitSearch(e) {
+ pageNumber = 0;
+ fetchResults(e);
+}
+
+function fetchResults(e) {
+ // Use preventDefault() to stop the form submitting
+ e.preventDefault();
+
+ // Assemble the full URL
+ url = baseURL + '?api-key=' + key + '&amp;page=' + pageNumber + '&amp;q=' + searchTerm.value + '&amp;fq=document_type:("article")';
+
+ if(startDate.value !== '') {
+ url += '&amp;begin_date=' + startDate.value;
+ };
+
+ if(endDate.value !== '') {
+ url += '&amp;end_date=' + endDate.value;
+ };
+
+}</pre>
+ </li>
+</ol>
+
+<p><code>submitSearch()</code> 设置起始页码为 0 ,然后调用 <code>fetchResults()</code> 函数。其中,先调用事件对象的 <code><a href="/en-US/docs/Web/API/Event/preventDefault">preventDefault()</a></code> 函数,阻止实际的表单提交事件 (会破坏应用,至于为什么可以自己试一试)。然后,处理字符串,构建完整的请求 URL 。以下是几个必要的步骤:</p>
+
+<ul>
+ <li>基本的 URL ( <code>baseURL</code> 变量).</li>
+ <li> API key ,需要放到 <code>api-key</code> URL 参数里面去 ( <code>key</code> 变量).</li>
+ <li>页码,需要放到 <code>page</code> URL 参数里面去 ( <code>pageNumber</code> 变量).</li>
+ <li>搜索项,需要放到 <code>q</code> URL 参数里面去 ( <code>searchTerm</code> 输入框的文字 {{htmlelement("input")}}).</li>
+</ul>
+
+<p>然后,用几个 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/if...else">if()</a></code> 代码块检测 <code>startDate</code> 和 <code>endDate</code> <code>&lt;input&gt;</code> 输入框里面是否有文字内容。如果有,再把输入框里的内容分别填写到 <code>begin_date</code> 和 <code>end_date</code> URL 参数里面去。</p>
+
+<p>最后,完整的 URL 如下所示:</p>
+
+<pre class="notranslate">https://api.nytimes.com/svc/search/v2/articlesearch.json?api-key=4f3c267e125943d79b0a3e679f608a78&amp;page=0&amp;q=cats
+&amp;begin_date=20170301&amp;end_date=20170312</pre>
+
+<div class="note">
+<p><strong>Note</strong>: 更多 URL 参数的说明参考 <a href="https://developer.nytimes.com/">NYTimes developer docs</a>.</p>
+</div>
+
+<div class="note">
+<p><strong>Note</strong>: 示例包含了基本的表单数据验证操作 — 表项提交之前必须有内容 (用 <code>required</code> 属性实现),此外,日期字段有确定的 <code>pattern</code> 属性,它们的值必须由 8 个数字组成 (<code>pattern="[0-9]{8}"</code>),否则不能提交。更多细节参考 <a href="/en-US/docs/Learn/HTML/Forms/Form_validation">Form data validation</a> </p>
+</div>
+
+<h3 id="从api请求数据">从api请求数据</h3>
+
+<p>现在 URL 已经构造好了,下面向它发起请求。本例使用 <a href="/ch-CN/docs/Web/API/Fetch_API/Using_Fetch">Fetch API </a></p>
+
+<p>把下面的代码块添加到 <code>fetchResults()</code> 函数末尾的大括号里面:</p>
+
+<pre class="brush: js notranslate">// Use fetch() to make the request to the API
+fetch(url).then(function(result) {
+ return result.json();
+}).then(function(json) {
+ displayResults(json);
+});</pre>
+
+<p>这段代码用于发起请求,把变量 <code>url</code> 作为 <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch">fetch()</a></code> 函数的参数,用 <code><a href="/en-US/docs/Web/API/Body/json">json()</a></code> 函数把响应的结果转换为 JSON 格式,然后把 JSON 数据传递给 <code>displayResults()</code> 函数,数据就可以在 UI 中显示出来了。</p>
+
+<h3 id="显示数据">显示数据</h3>
+
+<p>OK,下面是显示数据的部分。把下面的函数添加到 <code>fetchResults()</code> 函数下面。</p>
+
+<pre class="brush: js notranslate">function displayResults(json) {
+ while (section.firstChild) {
+ section.removeChild(section.firstChild);
+ }
+
+ var articles = json.response.docs;
+
+ if(articles.length === 10) {
+ nav.style.display = 'block';
+ } else {
+ nav.style.display = 'none';
+ }
+
+ if(articles.length === 0) {
+ var para = document.createElement('p');
+ para.textContent = 'No results returned.'
+ section.appendChild(para);
+ } else {
+ for(var i = 0; i &lt; articles.length; i++) {
+ var article = document.createElement('article');
+ var heading = document.createElement('h2');
+ var link = document.createElement('a');
+ var img = document.createElement('img');
+ var para1 = document.createElement('p');
+ var para2 = document.createElement('p');
+ var clearfix = document.createElement('div');
+
+ var current = articles[i];
+ console.log(current);
+
+ link.href = current.web_url;
+ link.textContent = current.headline.main;
+ para1.textContent = current.lead_paragraph;
+ para2.textContent = 'Keywords: ';
+ for(var j = 0; j &lt; current.keywords.length; j++) {
+ var span = document.createElement('span');
+ span.textContent += current.keywords[j].value + ' ';
+ para2.appendChild(span);
+ }
+
+ if(current.multimedia.length &gt; 0) {
+ img.src = 'http://www.nytimes.com/' + current.multimedia[0].url;
+ img.alt = current.headline.main;
+ }
+
+ clearfix.setAttribute('class','clearfix');
+
+ article.appendChild(heading);
+ heading.appendChild(link);
+ article.appendChild(img);
+ article.appendChild(para1);
+ article.appendChild(para2);
+ article.appendChild(clearfix);
+ section.appendChild(article);
+ }
+ }
+};</pre>
+
+<p>这一段有好多代码;下面一一解释:</p>
+
+<ul>
+ <li>第一个 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/while">while</a></code> 循环是通用的操作,用于删除 DOM 结点里面的所有内容,在本例中是 {{htmlelement("section")}} 元素。不断查询 <code>&lt;section&gt;</code> 结点是否有子节点,如果有,就删除。当 <code>&lt;section&gt;</code> 没有子节点时退出循环。</li>
+ <li>然后,把 <code>articles</code> 变量赋值为 <code>json.response.docs</code> — 它来自查询结果,是所有文章对象构成的数组。单独写出这一行来,下面的代码会清晰一些。</li>
+ <li>第一个 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/if...else">if()</a></code> 检测查询到的文章数量是否是 10  (该 API 一次最多返回 10 篇文章) 如果是,就把 {{htmlelement("nav")}} 显示出来,里面包含有 <em>前 10/后 10(Previous 10/Next 10) </em>翻页按钮。如果返回的文章数量不足 10,就直接显示在 1 页里,不需要显示翻页按钮。在下一节中将展示翻页功能的实现。</li>
+ <li>下一个 <code>if()</code> 检测是否没有文章返回。如果是,就什么都不显示 — 创建一个 {{htmlelement("p")}} 结点,填写提示 "无结果。" 然后把该结点插入到 <code>&lt;section&gt;</code> 结点中。</li>
+ <li>如果有一些文章返回,第一步,创建所有用于展示新闻信息的结点,依次填写对应的文章内容,再把这些结点插入到合适的 DOM 结点下。至于文章对象里面的哪个属性包含需要展示的信息,查询 <a href="https://developer.nytimes.com/article_search_v2.json">Article Search API reference</a>. 大部分操作都很简单,但是个别需要提示一下:
+ <ul>
+ <li>使用 <a href="/en-US/docs/Web/JavaScript/Reference/Statements/for">for loop</a> (<code>for(var j = 0; j &lt; current.keywords.length; j++) { ... }</code> ) 遍历每篇文章关联的关键字,再把它们插入到各自的 {{htmlelement("span")}} 标签,在 <code>&lt;p&gt;</code> 标签里面,这样做可以方便样式表的编辑。</li>
+ <li>使用 <code>if()</code> 代码块 (<code>if(current.multimedia.length &gt; 0) { ... }</code>) 检测每篇文章是否有对应的图片 (有些没有) 。只有当检测到有时,显示首张图片 (否则抛出异常)。</li>
+ <li>把 &lt;div&gt; 结点设置一个 class 属性 "clearfix",这样清理操作 (apply clearing to it) 就很容易了。</li>
+ </ul>
+ </li>
+</ul>
+
+<h3 id="添加分页按钮">添加分页按钮</h3>
+
+<p>为了使分页按钮工作,我们将增加(或减少)<code>pageNumber</code> 变量的值,然后用页面 url 参数中包含的新值重新运行 fetch 请求。这么做是因为 NYTimes API 每次最多只返回 10 篇文章 — 如果查询结果超过 10 篇,而 <code>page</code> URL 参数设为 0 (或者忽略这个参数 — 0 是默认值),只返回前 10 篇 (0-9) ,后续的 10 篇 (10-19) 对应参数值 1 ,以此类推。</p>
+
+<p>根据这个特性就可以轻松实现一个简单的翻页函数。</p>
+
+<ol>
+ <li>
+ <p>下面的代码中,在 <code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code> 函数中添加这 2 个新事件,即把 <code>nextPage()</code> 和 <code>previousPage()</code> 函数链接到相应的按键点击事件上:</p>
+
+ <pre class="brush: js notranslate">nextBtn.addEventListener('click', nextPage);
+previousBtn.addEventListener('click', previousPage);</pre>
+ </li>
+ <li>
+ <p>在上边代码的基础上,定义这 2 个函数 — 添加下面代码:</p>
+
+ <pre class="brush: js notranslate">function nextPage(e) {
+ pageNumber++;
+ fetchResults(e);
+};
+
+function previousPage(e) {
+ if(pageNumber &gt; 0) {
+ pageNumber--;
+ } else {
+ return;
+ }
+ fetchResults(e);
+};</pre>
+
+ <p>第一个函数很简单 — 增加 <code>pageNumber</code> 变量,然后再次运行 <code>fetchResults()</code> 函数 展示下一页的结果。</p>
+
+ <p>第二个函数基本上执行相反的操作,不过有个额外的步骤是检测 <code>pageNumber</code> 在 -1 之前是否已经是 0 — 如果 fetch 请求的 <code>page</code> URL 参数是负数,会导致错误。如果 <code>pageNumber</code> 已经是 0 ,则直接执行 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/return">return</a></code> 退出函数,避免多余的计算。 (如果当前页面已经是首页,就不需要重新加载)。</p>
+ </li>
+</ol>
+
+<div class="blockIndicator note">
+<p><strong>Note</strong>: 完整代码参考 <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/nytimes/index.html">finished nytimes API example code on GitHub</a> (实例参考 <a href="https://mdn.github.io/learning-area/javascript/apis/third-party-apis/nytimes/">see it running live here</a>).</p>
+</div>
+
+<h2 id="YouTube示例">YouTube示例</h2>
+
+<p>我们还做了另外一个示例用来学习 — 参考 <a href="https://mdn.github.io/learning-area/javascript/apis/third-party-apis/youtube/">YouTube video search example</a>. 这个示例用了下面 2 个 API:</p>
+
+<ul>
+ <li><a href="https://developers.google.com/youtube/v3/docs/">YouTube Data API</a> ,搜索 YouTube 视频并返回结果。</li>
+ <li><a href="https://developers.google.com/youtube/iframe_api_reference">YouTube IFrame Player API</a> ,把返回的视频查询结果展示到 IFrame 视频播放器里,然后就可以播放了。</li>
+</ul>
+
+<p>这个示例的有趣之处在于,它把两个第三方 API 结合起来做了一个应用。第一个 API 是 RESTful API ,而第二个 API 更像是 Mapquest (有 API 专用方法 (API-specific methods), etc.)。但是,值得注意的是,这两个 API 均需要将 JavaScript 库应用于该页面。RESTful API 自己有用于处理 HTTP 请求并返回结果的函数。</p>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/14823/youtube-example.png" style="border-style: solid; border-width: 1px; display: block; height: 389px; margin: 0px auto; width: 700px;"></p>
+
+<p>本文不会对该示例做过多的叙述 — <a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/third-party-apis/youtube">源码</a> 中有详细的注释。</p>
+
+<p>运行源码需要:</p>
+
+<ul>
+ <li>从 <a href="https://cloud.google.com/">Google Cloud</a> 获取 API key.</li>
+ <li>找到源码中的 <code>ENTER-API-KEY-HERE</code> 字符串,替换为 API key.</li>
+ <li>把示例运行在 web 服务器上。只是用浏览器打开文件是不会工作的 (例如,通过 <code>file://</code> URL 打开).</li>
+</ul>
+
+<h2 id="小结">小结</h2>
+
+<p>本文介绍了如何使用第三方 API 给网页添加功能。</p>
+
+<p>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Fetching_data", "Learn/JavaScript/Client-side_web_APIs/Drawing_graphics", "Learn/JavaScript/Client-side_web_APIs")}}</p>
+
+<h2 id="本章目录">本章目录</h2>
+
+<ul>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Introduction to web APIs</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">Manipulating documents</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">Fetching data from the server</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">Third party APIs</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing graphics</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">Video and audio APIs</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">Client-side storage</a></li>
+</ul>
diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/video_and_audio_apis/index.html b/files/zh-cn/learn/javascript/client-side_web_apis/video_and_audio_apis/index.html
new file mode 100644
index 0000000000..45d379f4b4
--- /dev/null
+++ b/files/zh-cn/learn/javascript/client-side_web_apis/video_and_audio_apis/index.html
@@ -0,0 +1,499 @@
+---
+title: 视频和音频API
+slug: Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs
+translation_of: Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs
+---
+<div>{{LearnSidebar}}</div>
+
+<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Drawing_graphics", "Learn/JavaScript/Client-side_web_APIs/Client-side_storage", "Learn/JavaScript/Client-side_web_APIs")}}</div>
+
+<p class="summary">HTML5提供了用于在文档中嵌入富媒体的元素 — {{htmlelement("video")}}和{{htmlelement("audio")}} — 这些元素通过自带的API来控制视频或音频的播放,定位进度等。本文将向你展示如何执行一些常见的任务,如创建自定义播放控件。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">前提:</th>
+ <td>JavaScript基础(见<a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript第一步</a>,<a href="/zh-CN/docs/learn/JavaScript/Building_blocks">创建JavaScript代码块</a>,<a href="/zh-CN/docs/Learn/JavaScript/Objects">JavaScript对象入门</a>),<a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API简介</a></td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>学习如何通过浏览器API来控制视频和音频的播放。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="HTML5视频和音频">HTML5视频和音频</h2>
+
+<p>{{htmlelement("video")}}和{{htmlelement("audio")}}元素允许我们把视频和音频嵌入到网页当中。就像我们在<a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">音频和视频内容</a>文中展示的一样,一个典型的实现如下所示:</p>
+
+<pre class="brush: html">&lt;video controls&gt;
+ &lt;source src="rabbit320.mp4" type="video/mp4"&gt;
+ &lt;source src="rabbit320.webm" type="video/webm"&gt;
+ &lt;p&gt;Your browser doesn't support HTML5 video. Here is a &lt;a href="rabbit320.mp4"&gt;link to the video&lt;/a&gt; instead.&lt;/p&gt;
+&lt;/video&gt;</pre>
+
+<p>上述代码将会在浏览器内部创建一个如下图所示的视频播放器:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/html/multimedia-and-embedding/video-and-audio-content/multiple-video-formats.html", '100%', 380)}}</p>
+
+<p>你可以点击上面的文章链接来查看相关HTML元素的所有特性;但在这篇文章中,主要目的是学习我们最感兴趣的{{htmlattrxref("controls", "video")}}属性,它会启用默认的播放设置。如果没有指定该属性,则播放器中不会显示相关控件:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/html/multimedia-and-embedding/video-and-audio-content/multiple-video-formats-no-controls.html", '100%', 380)}}</p>
+
+<p>至此,你可能会觉得这个属性作用不大,但是它确实很有优势。使用原生浏览器控件的一个很大的问题在于,它们在各个浏览器中都不相同 — 对于跨浏览器的支持并不是很好!另一个问题是,在大多数浏览器中原生控件难以通过键盘来操作。</p>
+
+<p>你可以通过隐藏本地控件(通过删除controls属性),然后使用HTML,CSS和JavaScript编写自己的代码来解决这两个问题。 在下一节中,我们将看到如何通过一些可用的工具来实现。</p>
+
+<h2 id="HTMLMediaElement_API">HTMLMediaElement API</h2>
+
+<p>作为HTML5规范的一部分,{{domxref("HTMLMediaElement")}} API提供允许你以编程方式来控制视频和音频播放的功能—例如 {{domxref("HTMLMediaElement.play()")}}, {{domxref("HTMLMediaElement.pause()")}},等。该接口对{{htmlelement("audio")}}和{{htmlelement("video")}}两个元素都是可用的,因为在这两个元素中要实现的功能几乎是相同的。让我们通过一个例子来一步步演示一些功能。</p>
+
+<p>我们最终的示例(和功能)将会如下所示:</p>
+
+<p>{{EmbedGHLiveSample("learning-area/javascript/apis/video-audio/finished/", '100%', 360)}}</p>
+
+<h3 id="入门">入门</h3>
+
+<p>想要使用这个示例的代码来入门,请下载<a href="https://github.com/mdn/learning-area/raw/master/javascript/apis/video-audio/start/media-player-start.zip">media-player-start.zip</a> 并解压到您的硬盘上的一个新建目录里。如果想要下载<a href="">examples repo</a>,它位于<code>javascript/apis/video-audio/start/ </code>路径下。</p>
+
+<p>下载并解压之后,如果您加载这个HTML,你将会看到一个通过浏览器原生播放控件渲染的非常一般的HTML5视频播放器。</p>
+
+<h4 id="探索_HTML">探索 HTML</h4>
+
+<p>打开HTML index文件。你将看到一些功能;HTML由视频播放器和它的控件所控制:</p>
+
+<pre>&lt;div class="player"&gt;
+  &lt;video controls&gt;
+    &lt;source src="video/sintel-short.mp4" type="video/mp4"&gt;
+    &lt;source src="video/sintel-short.mp4" type="video/webm"&gt;
+    &lt;!-- fallback content here --&gt;
+  &lt;/video&gt;
+  &lt;div class="controls"&gt;
+    &lt;button class="play" data-icon="P" aria-label="play pause toggle"&gt;&lt;/button&gt;
+    &lt;button class="stop" data-icon="S" aria-label="stop"&gt;&lt;/button&gt;
+    &lt;div class="timer"&gt;
+ &lt;div&gt;&lt;/div&gt;
+ &lt;span aria-label="timer"&gt;00:00&lt;/span&gt;
+ &lt;/div&gt;
+    &lt;button class="rwd" data-icon="B" aria-label="rewind"&gt;&lt;/button&gt;
+    &lt;button class="fwd" data-icon="F" aria-label="fast forward"&gt;&lt;/button&gt;
+  &lt;/div&gt;
+&lt;/div&gt;
+</pre>
+
+<ul>
+ <li>整个播放器被包装在一个{{htmlelement("div")}}元素之中,所以如果有必要的话,可以把它作为一个单元整体来设置其样式。</li>
+ <li>{{htmlelement("video")}}元素层包含两个{{htmlelement("source")}}元素,这样可以根据浏览器来加载其所支持的不同视频格式。</li>
+ <li>控件 HTML 大概是最有趣的:
+ <ul>
+ <li>我们有四个 {{htmlelement("button")}} — play/pause, stop, rewind, and fast forward.</li>
+ <li>每个<code>&lt;button&gt;</code> 都有一个<code>class名</code> , 一个<code>data-icon</code> 属性来决定在每个按钮上显示什么图标 (在下一节讲述它是如何工作的), 和一个<code>aria-label</code> 属性为每一个按钮提供容易理解的描述, 即使我们没有在tags内提供可读的标签。当用户关注这些元素时含有<code>aria-label</code> 属性的内容也会被讲述人读出来。</li>
+ <li>有一个设定的计时器 {{htmlelement("div")}}用来报告已经播放的时长。为了好玩,我们提供了两种报告机制 — 一个 {{htmlelement("span")}} 包含了流逝时间的分钟和秒,和一个额外的<code>&lt;div&gt;</code> 用来创建一个水平的随着时间增加而增长的进度条。要想了解完成版本看上去是咋样的, <a href="https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/">点击查看完成版本</a>.</li>
+ </ul>
+ </li>
+</ul>
+
+<h4 id="探索_CSS">探索 CSS</h4>
+
+<p>现在打开CSS文件来查看里面的内容。例子中的CSS样式并不是很复杂, 我们突出了最主要的一部分。首先注意<code>.controls</code> 的样式:</p>
+
+<pre class="brush: css">.controls {
+ visibility: hidden;
+ opacity: 0.5;
+ width: 400px;
+ border-radius: 10px;
+ position: absolute;
+ bottom: 20px;
+ left: 50%;
+ margin-left: -200px;
+ background-color: black;
+ box-shadow: 3px 3px 5px black;
+ transition: 1s all;
+ display: flex;
+}
+
+.player:hover .controls, player:focus .controls {
+ opacity: 1;
+}
+</pre>
+
+<ul>
+ <li>我们从设置为<code>hidden</code>的自定义控件{{cssxref("visibility")}}开始。稍后在我们的JavaScript中, 我们将控件设置为 <code>visible</code>, 并且从<code>&lt;video&gt;</code> 元素中移除<code>controls</code> 属性。这是因为, 如果JavaScript由于某种原因没有加载, 用户依然可以使用原生的控件播放视频。</li>
+ <li>默认情况下,我们将控件的<code>opacity</code>设置为0.5 {{cssxref("opacity")}},这样当您尝试观看视频时,它们就不会分散注意力。 只有当您将鼠标悬停/聚焦在播放器上时,控件才会完全不透明。</li>
+ <li>我们使用Flexbox({{cssxref("display")}}: flex)布置控制栏内的按钮,以简化操作。</li>
+</ul>
+
+<p>接下来,让我们看看我们的按钮图标:</p>
+
+<pre class="brush: css">@font-face {
+ font-family: 'HeydingsControlsRegular';
+ src: url('fonts/heydings_controls-webfont.eot');
+ src: url('fonts/heydings_controls-webfont.eot?#iefix') format('embedded-opentype'),
+ url('fonts/heydings_controls-webfont.woff') format('woff'),
+ url('fonts/heydings_controls-webfont.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+button:before {
+ font-family: HeydingsControlsRegular;
+ font-size: 20px;
+ position: relative;
+ content: attr(data-icon);
+ color: #aaa;
+ text-shadow: 1px 1px 0px black;
+}</pre>
+
+<p>首先在CSS的最上方我们使用 {{cssxref("@font-face")}} 块来导入自定义Web字体。这是一种图标字体 —— 字母表中的所有字符都是各种常用图标,你可以尝试在程序中调用。</p>
+
+<p>接下来,我们使用这些内容来显示每个按钮上的图标:</p>
+
+<ul>
+ <li>我们使用 {{cssxref("::before")}} 选择器在每个 {{htmlelement("button")}} 元素之前显示内容。</li>
+ <li>我们使用 {{cssxref("content")}} 属性将各情况下要显示的内容设置为 <code><a href="/en-US/docs/Learn/HTML/Howto/Use_data_attributes">data-icon</a></code> 属性的内容。例如在播放按钮的情况下,<code><a href="/en-US/docs/Learn/HTML/Howto/Use_data_attributes">data-icon </a></code>包含大写的“P”。</li>
+ <li>我们使用 {{cssxref("font-family")}} 将自定义Web字体应用于我们的按钮上。在该字体中“P”对应的是“播放”图标,因此播放按钮上显示“播放”图标。</li>
+</ul>
+
+<p>图标字体非常酷有很多原因 —— 减少HTTP请求,因为您不需要将这些图标作为图像文件下载。同时具有出色的可扩展性,以及您可以使用文本属性来设置它们的样式 —— 例如 {{cssxref("color")}} 和 {{cssxref("text-shadow")}}。</p>
+
+<p>最后让我们来看看进度条的 CSS:</p>
+
+<pre class="brush: css">.timer {
+ line-height: 38px;
+ font-size: 10px;
+ font-family: monospace;
+ text-shadow: 1px 1px 0px black;
+ color: white;
+ flex: 5;
+ position: relative;
+}
+
+.timer div {
+ position: absolute;
+ background-color: rgba(255,255,255,0.2);
+ left: 0;
+ top: 0;
+ width: 0;
+ height: 38px;
+ z-index: 2;
+}
+
+.timer span {
+ position: absolute;
+ z-index: 3;
+ left: 19px;
+}</pre>
+
+<ul>
+ <li>我们将外部 <code>.timer</code>  <code>&lt;div&gt;</code> 设为flex:5,这样它占据了控件栏的大部分宽度。 我们还设置 {{cssxref("position")}}<code>: relative</code>,这样我们就可以根据它的边界方便地定位元素,而不是{{htmlelement("body")}} 元素的边界。</li>
+ <li>内部<code> &lt;div&gt; </code> <code>position:absolute</code> 绝对定位于外部 <code>&lt;div&gt;</code> 的顶部。 它的初始宽度为0,因此根本无法看到它。随着视频的播放,JavaScript将动态的增加其宽度。</li>
+ <li><code>&lt;span&gt;</code> 也绝对位于计时器/进度条 <code>timer</code> 栏的左侧附近。</li>
+ <li>我们还对内部 <code>&lt;div&gt;</code> 和 <code>&lt;span&gt;</code> 定义适当数值的 {{cssxref("z-index")}} ,以便进度条显示在最上层,内部<code> &lt;div&gt;</code> 显示在下层。 这样,我们确保我们可以看到所有信息 —— 一个box不会遮挡另一个。</li>
+</ul>
+
+<h3 id="实现_JavaScript">实现 JavaScript</h3>
+
+<p>我们已经有了一个相当完整的 HTML 和CSS 接口;现在我们只需要调通所有按钮以使控件正常工作。</p>
+
+<ol>
+ <li>
+ <p>在与 index.html 文件相同的目录下创建新的JavaScript文件。命名为 <code>custom-player.js</code>。</p>
+ </li>
+ <li>
+ <p>在此文件的顶部,插入以下代码:</p>
+
+ <pre class="brush: js">var media = document.querySelector('video');
+var controls = document.querySelector('.controls');
+
+var play = document.querySelector('.play');
+var stop = document.querySelector('.stop');
+var rwd = document.querySelector('.rwd');
+var fwd = document.querySelector('.fwd');
+
+var timerWrapper = document.querySelector('.timer');
+var timer = document.querySelector('.timer span');
+var timerBar = document.querySelector('.timer div');
+</pre>
+
+ <p>这里我们创建变量来保存对我们想要操作的所有对象的引用。有如下三组:</p>
+
+ <ul>
+ <li> <code>&lt;video&gt;</code> 元素,和控制栏。</li>
+ <li>播放/暂停,停止,快退,和快进按钮。</li>
+ <li>进度条外面的 <code>&lt;div&gt;</code>,数字计时器的 <code>&lt;span&gt;</code>,以及内部的 <code>&lt;div&gt;</code> 会随着视频播放逐渐变宽。</li>
+ </ul>
+ </li>
+ <li>
+ <p>接下来,在代码的底部插入以下内容:</p>
+
+ <pre class="brush: js">media.removeAttribute('controls');
+controls.style.visibility = 'visible';</pre>
+
+ <p>这两行从视频中删除默认浏览器控件,并使自定义控件可见。</p>
+ </li>
+</ol>
+
+<h4 id="播放和暂停视频">播放和暂停视频</h4>
+
+<p>让我们实现或许是最重要的控制——播放/暂停按钮。</p>
+
+<ol>
+ <li>
+ <p>首先,将以下内容添加到您代码的底部,以便于在单击播放按钮时调用 <code>playPauseMedia()</code>函数:</p>
+
+ <pre class="brush: js">play.addEventListener('click', playPauseMedia);
+</pre>
+ </li>
+ <li>
+ <p>现在定义 <code>playPauseMedia()</code> 函数——再次添加以下内容到您代码底部:</p>
+
+ <pre class="brush: js">function playPauseMedia() {
+ if(media.paused) {
+ play.setAttribute('data-icon','u');
+ media.play();
+ } else {
+ play.setAttribute('data-icon','P');
+ media.pause();
+ }
+}</pre>
+
+ <p><span class="tlid-translation translation" lang="zh-CN"><span title="">我们使用if语句来检查</span></span>视频<span class="tlid-translation translation" lang="zh-CN"><span title="">是否暂停。如果</span></span>视频<span class="tlid-translation translation" lang="zh-CN"><span title="">已暂停,</span></span>{{domxref("HTMLMediaElement.paused")}} 属性将返回 true,任何视频没有播放的时间,包括第一次加载时处于0的时间段都是视频暂停状态。如果已暂停,我们把play按钮的 <code>data-icon</code> 属性值设置成"u", 用以表示 "暂停" 按钮图标,并且调用{{domxref("HTMLMediaElement.play()")}} 函数播放视频。</p>
+
+ <p>点击第二次,按钮将会切换回去——"播放"按钮图标将会再次显示,并且视频将会被{{domxref("HTMLMediaElement.paused()")}} 函数暂停。</p>
+ </li>
+</ol>
+
+<h4 id="暂停视频">暂停视频</h4>
+
+<ol>
+ <li>
+ <p>接下来,让我们添加处理视频停止的方法。添加以下的 <code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener() </a></code>行在你之前添加的内容的下面:</p>
+
+ <pre class="brush: js">stop.addEventListener('click', stopMedia);
+media.addEventListener('ended', stopMedia);
+</pre>
+
+ <p>{{event("click")}} 事件很明显——我们想要在点击停止按钮的时候停止视频通过运行我们的 <code>stopMedia()</code> 函数。然而我们也希望停止视频当视频播放完成时——由{{event("ended")}} 事件标记,所以我们也会设置一个监听器在此事件触发时运行函数。</p>
+ </li>
+ <li>
+ <p>接下来,让我们定义 <code>stopMedia()</code>—— 在 <code>playPauseMedia() 后面</code>添加以下函数:</p>
+
+ <pre>function stopMedia() {
+ media.pause();
+ media.currentTime = 0;
+ play.setAttribute('data-icon','P');
+}
+</pre>
+
+ <p>在 HTMLMediaElement API 中没有 <code>stop()</code> 方法——等效的办法是先用 <code>pause()</code> 暂停视频,然后设置{{domxref("HTMLMediaElement.currentTime","currentTime")}} 属性为0。设置 <code>currentTime</code> 的值(单位:秒)将会立刻使视频跳到该位置。</p>
+
+ <p>之后要做的事是把显示的图标设置成“播放”图标。无论视频使暂停还是正在播放,您都希望它随后可以播放。</p>
+ </li>
+</ol>
+
+<h4 id="探索快进和快退">探索快进和快退</h4>
+
+<p>有许多方法可以实现快退和快进功能;在这里,我们向您展示了一种相对复杂的方式,当按意外顺序按下不同的按钮时,它不会中断。</p>
+
+<ol>
+ <li>
+ <p>首先,在前面的代码之下添加以下两个<code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code>:</p>
+
+ <pre class="brush: js">rwd.addEventListener('click', mediaBackward);
+fwd.addEventListener('click', mediaForward);
+</pre>
+ </li>
+ <li>
+ <p>现在转到事件处理函数 - 在以前的函数下面添加以下代码来定义<code>mediaBackward()</code>和<code>mediaForward()</code>:</p>
+
+ <pre class="brush: js">var intervalFwd;
+var intervalRwd;
+
+function mediaBackward() {
+ clearInterval(intervalFwd);
+ fwd.classList.remove('active');
+
+ if(rwd.classList.contains('active')) {
+ rwd.classList.remove('active');
+ clearInterval(intervalRwd);
+ media.play();
+ } else {
+ rwd.classList.add('active');
+ media.pause();
+ intervalRwd = setInterval(windBackward, 200);
+ }
+}
+
+function mediaForward() {
+ clearInterval(intervalRwd);
+ rwd.classList.remove('active');
+
+ if(fwd.classList.contains('active')) {
+ fwd.classList.remove('active');
+ clearInterval(intervalFwd);
+ media.play();
+ } else {
+ fwd.classList.add('active');
+ media.pause();
+ intervalFwd = setInterval(windForward, 200);
+ }
+}
+</pre>
+
+ <p>您会注意到,首先我们初始化两个变量 -  intervalFwd和intervalRwd  - 您将在后面发现它们的用途。</p>
+
+ <p>让我们逐步浏览<code>mediaBackward()</code>(<code>mediaForward()</code>的功能性完全相同,但效果相反):</p>
+
+ <ol>
+ <li>我们清除在快进功能上设置的所有classes和intervals ––这样做是因为如果我们在按下<code>fwd</code>(快进)按钮后再按下<code>rwd</code>(快退)按钮,就可以取消任何快进的功能并将其替换为快退功能。如果我们试图同时做到这两点,播放器就会暂停。</li>
+ <li>使用<code>if</code>语句检查是否已在<code>rwd</code>按钮上设置了用来指示它已被按下的<code>active</code>类。{{domxref("classList")}}是一个非常方便的属性,存在于每个元素上 ––它包含元素上设置的所有类的列表,以及添加/删除类的方法等。使用<code>classList.contains()</code>方法检查列表是否包含<code>active</code>类。这将返回布尔值<code>true/false</code>结果。</li>
+ <li>如果在<code>rwd</code>按钮上设置了<code>active</code>,我们使用<code>classList.remove()</code>删除它,清除第一次按下按钮时设置的间隔(参见下面的更多解释),并使用{{domxref("HTMLMediaElement.play()")}}取消快退并开始正常播放视频。</li>
+ <li>如果尚未设置,使用<code>classList.add()</code>将<code>active</code>类添加到<code>rwd</code>按钮,使用{{domxref("HTMLMediaElement.pause()")}}暂停视频,然后设置<code>intervalRwd</code>变量为{{domxref("WindowOrWorkerGlobalScope.setInterval", "setInterval()")}}的调用。调用时,<code>setInterval()</code>会创建一个活动间隔,这意味着它每隔x毫秒运行一个作为第一个参数给出的函数,其中x是第二个参数的值。 所以这里我们每200毫秒运行一次<code>windBackward()</code>函数 ––我们将使用此函数不断向后滚动(快退动作)视频。要停止{{domxref("WindowOrWorkerGlobalScope.setInterval", "setInterval()")}}运行,你必须调用{{domxref("WindowOrWorkerGlobalScope.clearInterval", "clearInterval()")}},给它识别要清除的间隔的名称,在本例中是变量名称intervalRwd(请参阅函数中较早的clearInterval()调用)。</li>
+ </ol>
+ </li>
+ <li>
+ <p>最后,对于本节,定义在setInterval()调用中需要调用的windBackward()和windForward()函数。在以上两个函数下面添加以下内容:</p>
+
+ <pre class="brush: js">function windBackward() {
+ if(media.currentTime &lt;= 3) {
+ rwd.classList.remove('active');
+ clearInterval(intervalRwd);
+ stopMedia();
+ } else {
+ media.currentTime -= 3;
+ }
+}
+
+function windForward() {
+ if(media.currentTime &gt;= media.duration - 3) {
+ fwd.classList.remove('active');
+ clearInterval(intervalFwd);
+ stopMedia();
+ } else {
+ media.currentTime += 3;
+ }
+}</pre>
+
+ <p>同样,我们将完成这些功能中的第一个,因为它们几乎完全相同,但彼此相反。在<code>windBackward()</code>中,我们执行以下操作 ––请记住,当间隔处于活动状态时,此函数每200毫秒运行一次。</p>
+
+ <ol>
+ <li>我们从一个<code>if</code>语句开始,该语句检查当前时间是否小于3秒,即,如果再倒退三秒将使其超过视频的开始。这会导致奇怪的行为,所以如果是这种情况,我们通过调用<code>stopMedia()</code>来停止视频播放,从倒带按钮中删除<code>active</code>类,并清除<code>intervalRwd</code>间隔以停止快退功能。如果我们没有做到最后一步,视频将永远保持快退。</li>
+ <li>如果当前时间不在视频开始的3秒内,我们只需通过执行<code>media.currentTime-=3</code>从当前时间中删除三秒。因此,实际上,我们将视频快退3秒,每200毫秒一次。</li>
+ </ol>
+ </li>
+</ol>
+
+<h4 id="更新已用时间">更新已用时间</h4>
+
+<p>我们要实施的媒体播放器的最后一块是显示的时间。为此,我们将运行一个函数,以便每次在&lt;video&gt;元素上触发 {{event("timeupdate")}}事件时更新时间显示。此事件触发的频率取决于您的浏览器,CPU电源等(<a href="http://stackoverflow.com/questions/9678177/how-often-does-the-timeupdate-event-fire-for-an-html5-video">see this stackoverflow post</a>)。</p>
+
+<p>在代码下方添加<code>addEventListener()</code>行:</p>
+
+<pre class="brush: js">media.addEventListener('timeupdate', setTime);</pre>
+
+<p>现在定义<code>setTime()</code>函数。在文件底部添加以下内容:</p>
+
+<pre class="brush: js">function setTime() {
+ var minutes = Math.floor(media.currentTime / 60);
+ var seconds = Math.floor(media.currentTime - minutes * 60);
+ var minuteValue;
+ var secondValue;
+
+ if (minutes &lt; 10) {
+ minuteValue = '0' + minutes;
+ } else {
+ minuteValue = minutes;
+ }
+
+ if (seconds &lt; 10) {
+ secondValue = '0' + seconds;
+ } else {
+ secondValue = seconds;
+ }
+
+ var mediaTime = minuteValue + ':' + secondValue;
+ timer.textContent = mediaTime;
+
+ var barLength = timerWrapper.clientWidth * (media.currentTime/media.duration);
+ timerBar.style.width = barLength + 'px';
+}
+</pre>
+
+<p>这是一个相当长的函数,所以让我们一步一步地完成它:</p>
+
+<ol>
+ <li>首先,我们计算{{domxref("HTMLMediaElement.currentTime")}} 值中的分钟数和秒数。</li>
+ <li>然后我们初始化另外两个变量 ––<code>minuteValue</code>和<code>secondValue</code>。</li>
+ <li>两个<code>if</code>语句计算出分钟数和秒数是否小于10.如果是这样,它们会在类似数值时钟显示工作的方式上为值添加前置的零。</li>
+ <li>要显示的实际时间值设置为<code>minuteValue</code>加上冒号字符加<code>secondValue</code>。</li>
+ <li>计时器的{{domxref("Node.textContent")}}值设置为时间值,因此它显示在UI中。</li>
+ <li>我们应该设置内部<code>&lt;div&gt;</code>的长度是通过首先计算外部<code>&lt;div&gt;</code>的宽度来计算出来的(任何元素的{{domxref("HTMLElement.clientWidth", "clientWidth")}} 属性将包含它的长度),然后乘以{{domxref("HTMLMediaElement.currentTime")}}除以媒体的总{{domxref("HTMLMediaElement.duration")}}。</li>
+ <li>我们将内部<code>&lt;div&gt;</code>的宽度设置为等于计算的条形长度加上“px”,因此它将设置为该像素数</li>
+</ol>
+
+<h4 id="修复播放和暂停">修复播放和暂停</h4>
+
+<p>还有一个问题需要修复。如果在快退或快进功能激活时按下播放/暂停或停止按钮,它们就不起作用。我们如何修复它以便取消<code>rwd / fwd</code>按钮功能并按照您的预期播放/停止视频?这很容易解决。</p>
+
+<p>首先,在<code>stopMedia()</code>函数中添加以下行 ––任何地方都可以:</p>
+
+<pre class="brush: js">rwd.classList.remove('active');
+fwd.classList.remove('active');
+clearInterval(intervalRwd);
+clearInterval(intervalFwd);
+</pre>
+
+<p>现在再次添加相同的行(上面的四行)到<code>playPauseMedia()</code>函数的最开头(就在<code>if</code>语句的开始之前)。</p>
+
+<p>此时,您可以删除<code>windBackward()</code>和<code>windForward()</code>函数中的等效行,因为该函数已在<code>stopMedia()</code>函数中实现。</p>
+
+<div class="blockIndicator note">
+<p><strong>注意</strong>:您还可以通过创建运行这些行的单独函数来进一步提高代码的效率,然后在需要的任何地方调用它,而不是在代码中多次重复这些行。但是我们会把这些留给你自己。</p>
+</div>
+
+<h2 id="小结">小结</h2>
+
+<p><span class="tlid-translation translation" lang="zh-CN"><span title="">我想我们已经在这篇文章中教过你足够多了。使用</span></span>{{domxref("HTMLMediaElement")}} <span class="tlid-translation translation" lang="zh-CN"><span title="">API可以为创建简单的视频和音频播放器提供丰富的可用功能,然而这只是冰山一角。</span> <span title="">有关更复杂和有趣功能的链接,请参阅下面的“另请参阅”部分。</span></span></p>
+
+<p><span class="tlid-translation translation" lang="zh-CN"><span title="">以下是一些有关如何增强我们构建的现有示例的建议:</span></span></p>
+
+<ol>
+ <li><span class="tlid-translation translation" lang="zh-CN"><span title="">如果视频是一小时或更长时间(嗯,它不会显示小时;只有几分钟和几秒),当前显示时间会中断。</span> <span title="">你能弄清楚如何更改示例以使其显示小时数吗?</span></span></li>
+ <li>
+ <p><span class="tlid-translation translation" lang="zh-CN"><span title="">由于 <code>&lt;audio&gt; </code>元素具有相同的{{domxref("HTMLMediaElement")}}功能,因此您可以轻松地将此播放器用于<code> &lt;audio&gt; </code>元素。</span> <span title="">试着这样做。</span></span></p>
+ </li>
+ <li>
+ <p><span class="tlid-translation translation" lang="zh-CN"><span title="">你能找到一种方法将计时器内部的<code> &lt;div&gt; </code>元素转换为真正的搜索条/ 滑动条- 也就是说,当你点击条形图上的某个位置时,它会跳转到视频播放中的相对位置吗?</span></span> <span class="tlid-translation translation" lang="zh-CN"><span title="">作为提示,您可以通过</span></span><code><a href="/ch-ZN/docs/Web/API/Element/getBoundingClientRect">getBoundingClientRect()</a></code> <span class="tlid-translation translation" lang="zh-CN"><span title="">方法找出元素左/右和上/下侧的X和Y值</span></span> , 而且你可以通过 {{domxref("Document")}} 对象调用的click事件的事件对象找到鼠标单击的坐标。举个栗子:</p>
+
+ <pre class="brush: js">document.onclick = function(e) {
+ console.log(e.x) + ',' + console.log(e.y)
+}</pre>
+ </li>
+</ol>
+
+<h2 id="另请参阅">另请参阅</h2>
+
+<ul>
+ <li>{{domxref("HTMLMediaElement")}}</li>
+ <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">视频和音频内容</a>— &lt;video&gt;和&lt;audio&gt;的简单指南.</li>
+ <li><a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_delivery">Audio and video delivery</a> — detailed guide to delivering media inside the browser, with many tips, tricks, and links to further more advanced tutorials.</li>
+ <li><a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_manipulation">Audio 与 video 操作</a> — 操作audio 和 video 的详细指南,例如:使用 <a href="/en-US/docs/Web/API/Canvas_API">Canvas API</a>, <a href="/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a>, 等等。</li>
+ <li>{{htmlelement("video")}} and {{htmlelement("audio")}} reference pages.</li>
+ <li>
+ <p><a href="/en-US/docs/Web/HTML/Supported_media_formats">Media formats supported by the HTML audio and video elements</a>.</p>
+ </li>
+</ul>
+
+<p>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Drawing_graphics", "Learn/JavaScript/Client-side_web_APIs/Client-side_storage", "Learn/JavaScript/Client-side_web_APIs")}}</p>
+
+<h2 id="本章目录">本章目录</h2>
+
+<ul>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Introduction to web APIs</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">Manipulating documents</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">Fetching data from the server</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">Third party APIs</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing graphics</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">Video and audio APIs</a></li>
+ <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">Client-side storage</a></li>
+</ul>