AMP

加载和显示服务器内容

关闭专家模式

专家模式

使用专家模式隐藏为初学者准备的 Web 开发指南。

向我们的网站添加产品

现在是时候回去向我们的 Chico's Cheese Bikes 页面添加一些产品了!我们的目标是在我们的网站上添加一个新页面,使我们能够

  • 显示每个产品的产品名称、图像、价格和评分。

  • 过滤产品,仅显示某些类别的产品(例如,自行车、头盔、手套等)。

  • 按价格从高到低或从低到高对产品列表进行排序。

哪种方法可能是最佳的?我们可以使用 HTML、CSS 和 AMP 组件来表示单个产品,然后我们可以手动复制它以添加我们想要添加的每个产品。但是,这会带来一些重大问题。

首先,每次我们更新我们的产品之一时,我们都必须更新我们 AMP 网站的代码并将其再次发布到 Web 上。如果产品缺货或我们进行促销,那么我们的开发人员将不得不更新我们所有的产品页面。这是一种漫长且可能容易出错的方式,可以使我们的网站与我们的产品目录保持同步。

其次,我们需要能够仅显示一部分产品或更改产品出现的顺序。在 AMP 中,没有办法重新组织或过滤页面上已经呈现的内容。尝试使用绑定和状态变量来实现这一点将需要大量的代码,并且随着产品和类别的增加,代码会变得难以管理。

相反,我们希望将产品信息维护在独立于我们网站的服务器上。当我们的网站加载时,我们希望连接到该服务器,下载最新的产品数据,并以一致的方式显示该数据。我们可以定义一个由 HTML、CSS 和 AMP 组件组成的模板,该模板描述单个产品在我们的网站上的外观。稍后,当来自服务器的产品数据到达时,每个产品都应用于该模板并添加到页面中。然后,我们只需从服务器获取一组新的过滤或重新排序的产品数据,将结果再次应用于模板,最后在屏幕上显示这些结果,即可过滤或重新排序屏幕上的元素。

我们用来检索和显示此类服务器数据的组件称为 <amp-list>。我们还将使用 <amp-mustache> 模板,我们在讨论表单时在中间培训中首次提到过。

加载和显示服务器内容

在我们了解如何使用 AMP 检索服务器数据之前,让我们讨论服务器如何提供数据。

您可以将提供数据的服务器视为文件柜。服务器具有 API(应用程序编程接口),该 API 由一个或多个端点构成。可以通过唯一的 URL 访问每个端点,并返回不同的数据集合。在我们的类比中,API 将是文件柜内部的文件夹集合,端点将是文件柜中的各个文件夹,URL 地址将是贴在每个文件夹顶部以方便查找的标签。

我们将使用 <amp-list> 组件检索和显示服务器数据。<amp-list> 组件连接到给定 URL 上的远程 JSON API 端点以检索数据。<amp-list> 组件还包含一个 <amp-mustache> 模板。来自服务器的数据中的每个条目都单独应用于 <amp-mustache> 模板,并将结果添加到页面中。例如,以下代码将检索名称列表并在页面上将其显示为 <p> 标签的集合

<amp-list width="auto" height="100" layout="fixed-height" src="https://some.url/data.json" binding="refresh">
    <template type="amp-mustache">
        <p>{{name}}</p>
    </template>
</amp-list>

<amp-list> 的 src 属性包含服务器端点的 URL,该端点提供要在页面上显示的数据。默认情况下,<amp-list> 希望服务器使用包含名为 items 的属性的 JSON 对象进行响应,该属性包含要在屏幕上显示的对象的数组。对于前面的示例,从服务器返回的数据可能如下所示

{
    "items": [
        {
            "name": "Alice",
            "age": 42
        },
        {
            "name": "Bob",
            "Age": 55
        },
        {
            "name": "Carol",
            "Age": 28
        },
        {
            "name": "Dan",
            "age": 22
        }
    ]
}

我们与 <amp-list> 一起使用的模板是 <amp-mustache> 模板。这意味着我们不使用属性绑定括号语法来显示文本。相反,我们可以使用双花括号(或 mustache)语法将值嵌入到我们的模板中。mustache 模板中包含的变量名位于服务器返回的每个元素的上下文中。因此,在上面的示例中,{{name}} 具体指返回的 items 数组中每个对象的 name 属性。我们可以使用 mustache 语法将变量插入到我们的标签和属性中(例如,<amp-img> 组件的 src 属性)。

我们在中间课程中详细讨论了 <amp-mustache> 模板。如有必要,请参阅 <amp-mustache>文档或之前的课程来复习这种模板样式。

<amp-list> 模板的输出并非不受 AMP 的布局优化的影响。这意味着在甚至请求任何服务器数据之前,AMP 会在页面上保留特定数量的空间来放置结果。如果从服务器返回的数据无法在可用空间中显示,AMP 将尝试分配额外的空间。为了使 AMP 更可能允许 <amp-list> 扩展,请使 <amp-list> 组件成为页面上的最后一项。

由于对服务器的请求需要时间并且不能保证成功,我们可能希望检测我们的请求何时正在进行或何时失败。这将允许我们显示我们正在加载数据的通知或显示错误消息。为此,AMP 提供了 placeholderfallback 属性。在以下示例中,在 <amp-list> 向服务器请求数据但尚未收到回复后,它会显示标有 placeholder 属性的元素。如果对服务器的请求未及时返回或返回错误代码,则将显示带有 fallback 属性的元素

<amp-list src="https://foo.com/list.json" binding="refresh">
    <div placeholder>Loading ...</div>
    <div fallback>Failed to load data.</div>
</amp-list>

由于动态加载和显示数据对于现代 Web 开发非常重要,因此 <amp-list> 具有许多功能。值得花一些时间查看 <amp-list>文档,以了解一些其他配置选项。

案例研究:构建视频网站

为了更好地了解如何使用来自服务器的动态内容构建网站,让我们考虑一个视频网站。视频网站的每个访问者都看到相同的布局,但几乎所有填充该布局的视频对于用户来说都是唯一的。因此,例如,所有用户都可能会看到推荐的视频部分,但该部分中的视频对于每个用户都是不同的,由服务器提供。

视频网站布局示例

视频网站通常由匹配特定主题的视频组组成。一组通常用于推荐的视频的组合,而其他组可能是与热门话题相关的视频或由特定内容创建者发布的视频。这些组中的每一个都包含固定数量的视频。该组有一个标题,可能是一个图标,可能还有一个号召性用语(如订阅按钮或关闭按钮)。每个视频都显示一个缩略图、视频长度、标题、创建者、观看次数和发布日期。无论我们从服务器获取哪个视频,都会显示关于这些视频的相同数据。这是数据的模板。

视频网站上的骨架加载

当视频网站首次加载时,我们可以更清楚地看到该模板。请注意,所有视频行都缺少标题。另请注意,所有视频都有空的缩略图和实心框,而不是标题、创建者姓名或任何其他信息。

这种策略称为“骨架加载”。它的目的是预览网站结构,并指示最终将加载内容的位置,同时网站与服务器联系以获取信息。一旦服务器发送了填充每个组的组和视频的名称,网站就会更新以使用从服务器下载的真实数据替换骨架加载块。

那么,从我们的视频网站示例中得出的主要结论是什么?在开发严重依赖动态内容的网站时,目标是关注所有用户都相同的网站结构。这包括页面布局、导航和菜单系统,以及将容纳动态内容的容器的外观。在 AMP 中,一旦我们布置好页面的静态元素,我们就使用 <amp-list> 将动态内容加载到我们设置的插槽中。

练习 3:重新创建视频网站

要开始使用 <amp-list>,让我们重现一个视频网站的小部分:推荐视频集合。我们的推荐视频集合将包含由我们的服务器为用户选择的六个视频。我们将使用 <amp-youtube> 作为我们网站上的视频。当我们的网站联系服务器以检索视频信息时,我们将使用骨架加载向用户展示结构并指示正在发生某些事情。

我们不会在我们的 Chico's Cheese Bikes 项目中构建此产品页面。相反,您可以使用 这个 Glitch 作为此练习的起点。注意:不要忘记混音它以便您可以编辑!Glitch 包含

  • 一些基本的 CSS 和 HTML 来布局推荐视频页面。

  • JSON 文件中的示例视频数据。

  • 预构建的服务器,其中包含视频数据 API 端点,以及预先配置为查看该端点的 <amp-list> 组件。

注意:虽然您不必创建服务器来完成此练习,但您必须遵循链接的 Glitch 示例中包含的 README 上的说明。README 将引导您完成如何更新服务器在其 CORS 配置中使用的环境配置。如果您发现您的练习解决方案不起作用,即使其他一切似乎都正确,您可能需要按照 README 的指示更新环境变量中的地址。如果您有兴趣了解更多关于 CORS 是什么以及为什么它很重要,请阅读文档。

让我们讨论一下我们从服务器检索的视频数据的结构

{
    "items": [
        {
            "id": "xEnifYNnDCA",
            "img": "https://...02.png",
            "title": "How to make Cheddar Cheese (Cloth Banded)",
            "creator": "Gavin Webber",
            "duration": "14:50",
            "date": "Jul 24, 2016"
        },
        ...
    ]
}

id 字段是指此视频的 YouTube 视频 ID。img 字段是指向缩略图的链接,可以在 YouTube 视频初始化时用作占位符。titlecreatorduration 是关于视频本身的详细信息。最后,date 是视频首次发布的日期。

使用 <amp-list><amp-youtube> 的文档以及上述描述,更新推荐视频页面以满足以下要求

  • 推荐视频的模板应包括一个 <amp-youtube> 组件、一个标题、创建者、视频时长和视频的发布日期。

  • <amp-youtube> 视频正在初始化时,它应该有一个占位符 <amp-img> 组件,其 src 来自服务器数据。

  • <amp-list> 组件应该有一个占位符 <div>,其中包含六个骨架加载视频容器。

推荐的样式指南

  • <amp-list> 组件的 <amp-list> 模板内部包裹在一个带有分配类 video<div> 中。

  • <amp-youtube>width 应为 470px,height 应为 280px,layout 应为 responsive

  • 推荐视频的模板可以使用 <h2> 作为视频标题;<p> 标签用于视频日期、创建者和时长;以及一个额外的 <strong> 标签用于创建者。

  • <amp-list> 占位符应包含一个带有分配类 placeholder-container<div>

  • placeholder-container <div> 应包含六个 <div> 标签,每个标签都带有分配类 placeholder-vid

  • 每个 placeholder-vid <div> 应包含三个 <div> 标签。第一个应带有分配类 vid-pl,另外两个应带有分配类 title-pl

完成后,您的页面应如下所示

结果

解决方案

该解决方案可以在 此 Glitch 示例中找到。包含更改的页面部分应如下所示

<main>
    <h2>Recommended</h2>
    <amp-list width="auto" height="600" layout="fixed-height" src="videos" binding="refresh">
        <template type="amp-mustache">
            <div class="video">
                <amp-youtube data-videoid="{{id}}" layout="responsive" width="480" height="270">
                    <amp-img src="{{img}}" placeholder layout="fill"></amp-img>
                </amp-youtube>
                <h2>{{title}}</h2>
                <p>Published on {{date}}</p>
                <p>
                    <strong>{{creator}}</strong>
                </p>
                <p>Duration: {{duration}}</p>
            </div>
        </template>
    </amp-list>
</main>

如果您的解决方案添加了骨架加载,它应该如下所示

<main>
    <h2>Recommended</h2>
    <amp-list width="auto" height="600" layout="fixed-height" src="videos" binding="refresh">
        <div placeholder>
            <div class="placeholder-container">
                <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
        </div>
        <template type="amp-mustache">
            <div class="video">
                <amp-youtube data-videoid="{{id}}" layout="responsive" width="480" height="270">
                    <amp-img src="{{img}}" placeholder layout="fill"></amp-img>
                </amp-youtube>
                <h2>{{title}}</h2>
                <p>Published on {{date}}</p>
                <p>
                    <strong>{{creator}}</strong>
                </p>
                <p>Duration: {{duration}}</p>
            </div>
        </template>
    </amp-list>
</main>

过滤和排序服务器数据

到目前为止,我们已经练习了使用 <amp-list> 将动态内容添加到我们网站的基础知识,但在我们将产品页面添加到我们的 Chico's Cheese Bikes 网站之前,还有一项功能需要讨论。我们需要一种方法来过滤和排序 <amp-list> 组件内的内容。我们将依靠服务器来帮助我们。

根据我们到目前为止所学的内容,检索我们所有产品信息的合理 API 端点地址可能类似于

https://pathto.ourserver.com/api/v1/products

我们需要一种方法来要求服务器仅返回特定产品类别。为此,我们可以在 API 端点的末尾添加一个查询字符串。这不会改变我们请求信息的位置,但它会传递额外的信息供服务器处理。这样的地址可能类似于

https://pathto.ourserver.com/api/v1/products?category=bikes

有关 URL 中包含的内容的更多信息,请查看 文档。

接下来,我们需要一种方法来告诉服务器对返回给我们的结果进行排序。我们可以通过扩展我们用于过滤服务器结果的查询字符串来实现这一点。添加一个排序参数,该参数告诉服务器使用哪种类型的排序,以及是从高到低(降序)排序还是从低到高(升序)排序。这样的地址可能类似于

https://pathto.ourserver.com/api/v1/products?category=bikes&sort=price-asc

注意:您不必在查询字符串中使用这两个参数即可使用排序。类别或排序可以单独出现。当它们一起使用时,必须用 & 符号分隔。

现在我们知道如何从服务器获取经过过滤和/或排序的产品列表,但仍然不清楚应该将什么放入 <amp-list> 组件的 src 属性中。当用户更改过滤和排序设置时,他们希望页面自动更新。我们需要更新 src 属性以响应用户操作。这听起来像是状态变量和属性绑定的完美工作!

以下示例显示了如何将 <amp-list> 与属性绑定和状态变量相结合来完成上述示例


<amp-state id="productSettings">
    <script type="application/json">
        {
            "category": "bikes",
            "sort": "price-asc"
        }
    </script>
</amp-state>
<amp-list src="https://pathto.ourserver.com/api/v1/products"
          [src]="'https://pathto.ourserver.com/api/v1/products' +
                 ‘?category=' + productSettings.category +
                 ‘&sort=' + productSettings.sort"
          binding="refresh">
    <template type="amp-mustache">
        ...
    </template>
</amp-list>

首次加载页面时,它将调用服务器以获取所有产品。但是,当用户选择不同的过滤和排序选项时,productSettings 中的状态变量会更新,并且 src 绑定将评估并生成新的 src 值。接下来,<amp-list> 组件将访问更新的 src 地址以下载新数据。收到新数据后,它将像页面首次加载时初始数据一样应用于模板。新内容将替换屏幕上的旧内容。

练习 4:创建可过滤的产品列表

现在是时候将产品页面添加到我们的 Chico's Cheese Bikes 示例中了!如果您使用我们之前课程中链接的任何 Chico's Cheese Bikes Glitch 示例,您已经拥有完成此练习所需的服务器代码。您所需要做的就是确保您已按照 README 说明在环境变量中设置地址。如果您尚未执行此操作,那么我们构建的网站将无法从服务器下载信息。

我们需要做的第一件事是使我们的产品页面可以从我们的主页访问。我们将在滑出式菜单中添加一个链接。在 index.html 上的导航中,“我们的故事”下方,添加以下代码

<li class="nav-item">
    <a href="/products.html">Our Products</a>
</li>

检查 products.html 页面。产品页面已包含

  • 完成此练习所需的 CSS。

  • 产品页面和索引页面之间通用的布局部分,例如页眉和滑出式菜单。

  • <amp-state> 和选项下拉菜单,以包含服务器将期望的所有属性和选项值。

  • 您应完成练习所需的 AMP 组件脚本。

要完成此练习,我们必须将选择输入连接到其对应的状态变量,将状态变量绑定到 <amp-list> 组件,并开发我们产品的模板。现在,该模板将包含产品图像、产品名称、产品的客户评分和价格。

让我们看一下我们从服务器检索的产品数据的结构

{
    "items": [
        {
            "id": "cheddar-chaser",
            "type": "bicycle",
            "url": "/pages/cheddar-chaser.html",
            "name": "Cheddar Chaser",
            "img": "https://...cheddar-chaser.jpg",
            "stars": "5.0",
            "price": 599,
            "description": "Lorem ipsum dolor sit amet, ..."
        },
        ...
    ]
}

idtype 字段仅由服务器真正使用,因此您可以忽略它们进行此练习。url 字段表示此产品的产品页面的地址。我们不会在此练习中实现这些页面。img 字段包含指向此产品图片的 URL。stars 字段表示该产品的星级用户评分。price 字段是以美元计的此产品的价格。最后,description 字段是营销文案,我们不会在此练习中使用它,但在本培训的可选练习之一中使用。

使用 <amp-bind><amp-list> 的文档以及上述所有描述,创建一个满足以下要求的产品列表页面

  • 当产品类型选择输入更新时,它应将其新的选定值存储到 ID 为 products<amp-state> 组件中的 category 状态变量中。

  • 当“排序方式”选择输入更新时,它应将其新的选定值存储到 sort 状态变量中。

  • 每当 categorysort 状态变量更新时,<amp-list> 组件都应从服务器检索更新的产品列表。注意:应发送到服务器的查询参数与状态变量(categorysort)同名。

  • 产品的模板应包括产品图像、产品名称、产品的用户评分和价格。

推荐的样式指南

  • 模板的内容应包裹在一个带有分配类 product-card<div> 中。

  • 每个产品图像的大小应为 200 x 150 像素。

  • 有关产品的文本详细信息应包裹在一个带有分配类 product-details<div> 中。

  • 产品名称、评分和价格可以分别放置在带有分配类 product-namestar-rankproduct-price<p> 标签中。

完成后,您的页面应如下所示

结果

解决方案

包含产品列表的页面部分现在应如下所示

<main>
    <div class="main-content">
        <h2 class="main-heading">Our Products</h2>
        <div class="filter-sort-selectors">
            <p>Product Type:</p>
            <select
                class="product-selector"
                on="change:AMP.setState({
                        products: {
                            category: event.value
                        }
                    })">
                <option value="all">All</option>
                <option value="bicycle">Bikes</option>
                <option value="helmet">Helmets</option>
                <option value="gloves">Gloves</option>
                <option value="basket">Baskets</option>
                <option value="bottle">Water Bottles</option>
            </select>
            <p class="sort-by">Sort By:</p>
            <select
                class="order-selector"
                on="change:AMP.setState({
                        products: {
                            sort: event.value
                        }
                    })">
                <option value="price-desc">Price: High-Low</option>
                <option value="price-asc">Price: Low-High</option>
            </select>
        </div>
        <amp-list id="amp-list-bikes" class="product-list" width="auto"
            height="600" layout="fixed-height" src="/products/filter"
            [src]="'/products/filter?sort=' + products.sort +
                   '&category=' + products.category"
            binding="refresh">
            <template type="amp-mustache">
                <div class="product-card">
                    <amp-img
                        width="200"
                        height="150"
                        layout="responsive"
                        alt="{{name}}"
                        src="{{img}}">
                    </amp-img>
                    <div class="product-details">
                        <p class="product-name">{{name}}</p>
                        <p class="star-rank">{{stars}} ★</p>
                        <p class="product-price">
                            ${{price}}
                        </p>
                    </div>
                </div>
            </template>
        </amp-list>
    </div>
</main>