编者按:以下客座文章由 Jung von Matt 的开发人员 Thorsten Harders 和 Sebil Satici 撰写。
Amp.dev 提供了许多资源,使入门和使用 AMP 更容易:案例研究、每个组件的工作原理、最佳实践指南、分步教程以及大量可执行代码示例。事实上,它提供了如此多的资源,以至于我们需要一种方法让开发人员能够快速发现并访问所有现有内容。我们希望通过让所有内容都可以在网站上直接搜索来实现这一点。本文将介绍我们在 amp.dev 上实施搜索所采取的步骤。
TL;DR:新的 amp.dev 搜索是使用 <amp-lightbox>、<amp-list>、<amp-autocomplete>、<amp-mustache> 以及当然还有 <amp-bind> 来将它们粘合在一起构建的。它使用一个自定义 API,该 API 由 Google 自定义搜索 支持,并且使用 Service Worker 功能 来缓存最新的搜索查询和结果。
使用 AMP 组件构建
我们在构建 amp.dev 搜索功能时有三个目标
- 搜索应该可以在所有页面上的单独层中访问。
- 搜索应该针对查找 AMP 组件进行优化,因为这些组件是 AMP 开发人员体验的核心。
- 搜索应该使用有效的 AMP 功能实施。
隐藏和显示搜索层(使用 amp-lightbox)
在网络上四处查看,您会看到许多不同类型的搜索功能。有些只是简单的输入字段,而另一些则带有花哨的动画展开和折叠。有些会触发页面加载以转到分页结果页面,而另一些则会异步显示结果。有些会自动建议搜索词,而另一些则不会。对于 amp.dev,我们希望将这些方法的优点结合起来,找到一种解决方案,既能最大程度地避免打扰用户,又能快速访问。因此,我们决定将整个搜索封装在一个全屏层中,以
- 向用户建议有趣的文章(例如,新发布的指南或组件),无论用户在哪个页面上
- 避免使用页面上的其他元素分散用户的注意力
- 快速内联显示搜索结果,无需加载额外的结果页面
为了满足我们的需求,我们决定继续使用 <amp-lightbox>。它使我们能够轻松地隐藏和显示搜索层,同时为我们提供有用的操作和事件,我们可以将其用于 AMP 前端的集成。
添加无缝交互
从用户体验的角度来看,对我们来说尤其重要的是,用户能够在输入搜索查询和查看结果之间实现无缝过渡。当用户打开搜索层时,输入字段会自动获得焦点,用户可以立即开始输入。当搜索层关闭时,我们会再次将焦点放在搜索切换按钮上,以便键盘用户可以从之前的位置继续操作。
我们使用 <amp-lightbox> 的打开和关闭事件以及全局焦点操作来实现此功能
<amp-lightbox layout="nodisplay"
on="lightboxOpen:searchInput.focus;
lightboxClose:searchTriggerOpen.focus"
scrollable>
Code language: HTML, XML (xml)
列出搜索结果(使用 amp-list 和 amp-mustache)
为了在页面上显示搜索结果,我们使用 <amp-list> 组件,因为它内置了分页和无限滚动功能 - 这正是您列出搜索结果时所需的。实际搜索是在服务器端实施的,可以通过 API 端点 /search/do
访问,该端点返回类似于以下内容的 JSON 对象
{
"result": {
"pageCount": 10,
"pages": [
{
"title": "Some title",
"description": "Description",
"url": "https://amp.org.cn/some/link"
},
...
]
},
"nextUrl": "/search/do?q=amp&page=2"
}
Code language: JSON / JSON with Comments (json)
我们使用 amp-bind 来更新我们 <amp-list>
中的完整搜索 URL,方法是将 [src]
属性绑定到输入查询。为了启用无限滚动,我们设置了 load-more 属性,为了在触发后续搜索时获得更清晰的重新加载体验,我们设置了 reset-on-refresh 属性。在 <amp-mustache>
模板中,我们使用结果对象中的数据来动态渲染列表。以下是代码
<amp-list id="searchList"
src="/search/initial-items"
[src]="query ? '/search/initial-items : '/search/do?q=' +
encodeURIComponent(query)"
binding="no"
items="."
height="80vh"
layout="fixed-height"
load-more="auto"
load-more-bookmark="nextUrl"
reset-on-refresh
single-item>
<template type="amp-mustache">
<div class="search-result-list">
{{#result.pages}}
<a href="{{url}}">
<h4>{{title}}</h4>
<p>{{description}}</p>
</a>
{{/result.pages}}
</div>
</template>
</amp-list>
Code language: HTML, XML (xml)
建议重要内容(使用 amp-autocomplete)
根据我们的分析数据,开发人员最常访问 amp.dev 以访问 AMP 组件文档和示例。这就是为什么我们希望尽可能轻松地发现它们。使用 <amp-autocomplete>
,AMP 提供了一种开箱即用的解决方案来实施自动建议。目标是自动建议所有可用的 AMP 组件。静态数据源由 /search/autosuggest
端点提供,自动完成使用 filter="substring"
指定在客户端生成建议。
<amp-autocomplete filter="substring"
min-characters="1"
on="select:AMP.setState({ query: event.value })"
submit-on-enter="false"
src="/search/autosuggest"
>
<input placeholder="What are you looking for?">
</amp-autocomplete>
Code language: HTML, XML (xml)
执行搜索(使用 amp-form)
用户可以通过选择一个自动建议的选项或提交表单来触发搜索。为了执行实际的搜索请求,我们使用状态 query
作为 URL 参数。
<amp-list [src]="'/search/do?q=' + encodeURIComponent(query) + '&locale=en'">
Code language: HTML, XML (xml)
根据 amp-autocomplete
中的 on
操作,query
会在表单提交和自动完成发出 select
事件时更新(以及 amp-list
重新渲染)。
<form action-xhr="/search/echo"
on="submit:
AMP.setState({ query: queryInput }),
searchResult.focus,
searchList.changeToLayoutContainer"
method="POST" target="_top">
<amp-autocomplete filter="substring"
min-characters="1"
on="select:AMP.setState({ query: event.value })"
submit-on-enter="false"
src="/search/autosuggest"
>
<input id="searchInput"
placeholder="What are you looking for?"
on="input-throttled:AMP.setState({ queryInput: event.value })"
>
<button disabled [disabled]="!queryInput">Search</button>
</amp-autocomplete>
</form>
Code language: HTML, XML (xml)
当表单提交时,我们还会将焦点放在搜索结果容器上,以使键盘导航直接从第一个条目开始。此外,我们调用 <amp-list>
的 changeToLayoutContainer
以确保列表的高度会根据内容进行更改,并且可以变小。因为在我们的案例中,表单 submit
事件的结果不需要,所以我们只需指向一个回声操作即可。旁注:这在将来将不再需要,因为很快就可以在没有表单的情况下使用 amp-autocomplete
。
通过 Service Worker 缓存以前的搜索结果
在我们发布搜索的第一个版本后,我们很快就收到了一个相关的功能请求:即使用户导航到另一个页面,也要继续显示最新搜索查询的结果。
为了实现这一点,我们基于 AMP 的单行 Service Worker amp-sw,该 Service Worker 提供了基本的 PWA 功能,如缓存和离线页面。我们将其扩展为存储最新的搜索查询以及相应的搜索结果。
当开始搜索时,我们会显示以前的搜索查询及其结果。否则,我们会显示一个建议文章列表。在页面加载时,我们从服务器端点 /search/latest-query
初始化一个 amp-state
对象,该对象会填充搜索输入字段和搜索结果
// search.html
<amp-state id="query" src="/search/latest-query"></amp-state>
<amp-list src="/search/initial-items"
[src]="query ? '/search/initial-items : '/search/do?q=' + encodeURIComponent(query)"
...
</amp-list>
Code language: PHP (php)
诀窍在于:这个服务器端点不存在。神奇之处在于 Service Worker,它会拦截路由并使用用户上次搜索请求中缓存的搜索查询和搜索结果创建一个新的响应,并将其发送回页面,而不是从网络加载原始响应。
为了保存最新的搜索查询,我们使用正则表达式从请求的 URL 中获取 query
参数,并将其存储在缓存中新创建的响应对象中。
然后,路由处理程序检查缓存中是否存在与搜索请求匹配的条目。如果存在,则会立即从缓存中返回结果。否则,请求会传递到服务器,然后缓存以供后续调用。
// serviceworker.js
async function searchDoRequestHandler(url, request) {
const searchQuery = decodeURIComponent(url.search.match(/q=([^&]+)/)[1]);
const cache = await caches.open(SEARCH_CACHE_NAME);
cache.put(SEARCH_LATEST_QUERY_PATH, new Response(`"${searchQuery}"`));
let response = await cache.match(request);
if (response) return response;
response = await fetch(request);
if (response.status == 200) {
cache.delete(request, {
ignoreSearch: true,
});
cache.put(request, response.clone());
}
return response;
}
Code language: JavaScript (javascript)
这样,当用户在另一个页面上打开搜索层时,他们会自动收到之前的搜索结果,并可以从上次离开的地方继续操作。
您可以在这里看到如何注册处理程序函数
// serviceworker.js
self.addEventListener('fetch', (event) => {
const requestUrl = new URL(event.request.url);
if (requestUrl.pathname === '/search/do') {
event.respondWith(searchDoRequestHandler(requestUrl, event.request));
}
});
Code language: PHP (php)
使用 Service Worker API 拦截和动态更改请求是一种巧妙的方法,无论何时您想要个性化由 AMP 组件使用的数据,这些组件从远程端点(如 <amp-state>
或 <amp-list>
等)加载数据。就像它帮助我们通过缓存用户的最新搜索查询来增强搜索的用户体验一样。
结论
通过在 amp.dev 中实施搜索功能,我们实现了让用户能够以直观高效的方式精确导航网站内容的目标。为了获得更好的用户体验,我们还使用 Service Worker 功能缓存以前的搜索结果。
最酷的事情是,我们无需编写一行 JavaScript 代码(除了 Service Worker 部分)就集成了搜索。只需利用 AMP 现有的组件,我们就可以集成自动建议和无限滚动等有用功能,否则实施起来会非常困难!
作者:Jung von Matt 的开发人员 Thorsten Harders 和 Sebil Satici