在 AMP 页面中使用自定义 JavaScript
重要提示:此文档不适用于您当前选择的格式 email!
amp-script
允许您编写和运行自己的 JavaScript,并保持 AMP 的性能保证。大多数 AMP 组件通过自己的逻辑实现常见的 Web 交互,使您可以快速构建页面,而无需编写 JavaScript 或导入第三方库。通过使用 amp-script
,您可以针对特定用例或独特需求采用自定义逻辑,而不会失去 AMP 的优势。
本指南提供了有关此组件的背景信息及其使用最佳实践。
Web Workers
过多的 JavaScript 会使网站运行缓慢且无响应。为了控制 AMP 页面加载的 JavaScript 以及何时执行,AMP 的验证规则禁止开发人员通过 <script>
标记在网页中运行 JavaScript。
Web Workers 提供了一种更安全地运行 JavaScript 的方法。通常,所有 JavaScript 都在单个线程中运行,但每个 worker 都在自己的线程中运行。这是可行的,因为它们无法访问 DOM 或 window
对象,并且每个 worker 都在自己的全局范围内运行。因此,它们不会干扰彼此的工作或主线程中代码引起的突变。它们只能通过包含对象的 message 与主线程和彼此通信。Workers 为多线程 Web 提供了一条途径,一种将 JavaScript 封装在沙箱中的方法,使其不会阻塞 UI。
Workers 不具备访问 DOM 的权限。为了弥补这一缺陷,AMP 团队创建了一个名为 WorkerDOM 的开源库。WorkerDOM 将 DOM 复制到虚拟 DOM,并使该副本可供 worker 使用。WorkerDOM 还重新创建了标准 DOM API 的子集。当 worker 对虚拟 DOM 进行更改时,WorkerDOM 会在实际 DOM 中重新创建这些更改。这允许 worker 操作 DOM 并使用标准技术在页面上进行更改。DOM 同步仅在一个方向上进行。如果主线程修改了 DOM,则不存在让 worker 知晓的机制。
amp-script
是 WorkerDOM 的包装器,使 WorkerDOM 可以在 AMP 中使用,提供与 AMP 功能的连接,建立开发者 API,并引入保护用户体验的限制。WorkerDOM 提供了 amp-script
功能的核心。
amp-script
概述
JavaScript 语言在 worker 中与其他地方在浏览器中是相同的。因此,在 amp-script
中,您可以使用 JavaScript 提供的所有常用构造。WorkerDOM 还重新创建了许多常用的 DOM API,并使它们可供您使用。它支持常见的 Web API,如 Fetch
和 Canvas
,并为您提供选定的全局对象,如 navigator
和 localStorage
。您可以像往常一样为浏览器事件分配处理程序。
但是,amp-script
不支持完整的 DOM API 或 Web API,因为这会使其自身的 JavaScript 太大且笨重。有关详细信息,请参阅文档,并参阅这些示例以查看 amp-script
的使用。
amp-script
使用返回 Promise 的替代方法替换了一些同步 DOM API 方法。例如,您可以使用 getBoundingClientRectAsync()
而不是 getBoundingClientRect()
。有时,这对于提供对计算布局进行同步访问的 DOM API 是必需的,有时是为了使 amp-script
可以调用本地浏览器方法并等待响应。
为了保持 AMP 对性能和布局稳定性的保证,amp-script
有一些限制。如果 amp-script
容器的大小未固定,则只有在用户交互触发的情况下,您的代码才能进行突变。您不能将样式表或其他脚本添加到 DOM,并且不支持 importScripts()
。有关详细信息,请参阅文档。
使用 JavaScript 框架
由于 DOM API 未完全支持,因此在 amp-script
中操作 DOM 的最佳方法是什么?以下是两种可能性。
1) 了解支持的内容。 了解丰富的支持的 API 集。您需要以不同的方式考虑 DOM API - 不要将其视为多年来开发的一大组属性和方法,而应将其视为一组简洁的工具。
2) 使用 Preact。 React 不仅是构建网站的流行方式,而且它使用 DOM API 的子集来修改 DOM。amp-script
可以完全支持 API 的这一部分,从而完全支持 React。也就是说,React 包的大小通常会超出 amp-script
的 150K 限制。因此,建议您使用 Preact,它是 React 的轻量级替代方案。Preact 旨在从 React 进行直接迁移。使用 Preact,您应该能够构建复杂的交互,而无需过多担心支持的内容。
该团队已经使用 Vue、Angular、Aurelia 和 lit-html 等框架测试了 amp-script
,但测试程度较低。如果您发现任何遗漏,请提出问题 - 或者,更好的是,提交拉取请求。
由于 amp-script
无法完全支持 DOM API,因此简单地将 jQuery 等库复制到 <amp-script>
组件中将不起作用。
用例
amp-script
扩展了 AMP 网页的功能范围。由于它支持 DOM 和 Web API 的子集,并且由于其使用附带限制,因此它不是通用的 JavaScript 解决方案。任何大量的现有 JavaScript 可能都需要修改才能在 amp-script
上下文中工作。
但是,amp-script
提供了一种很好的方法来处理现有 AMP 组件未提供的逻辑和交互。以下是一些出色的用例。
创建新的交互
amp-script
为您提供了 JavaScript 和 DOM API 的强大功能。它使您可以创建其他 AMP 组件无法实现的交互,从而为 Web 的全部创造力打开了大门。
也就是说,在转而使用 amp-script
来创建新的交互之前,请检查是否可以使用 AMP 组件或组件组合来完成相同的操作。利用现有的 AMP 组件最终将使您的代码更小且更易于维护。如果组件可以实现与您想要的效果类似的效果,则可以使用 amp-script
自定义其行为。
如果您使用 amp-script
创建了其他开发人员可能感兴趣的交互,请考虑建议或贡献新组件。
添加高级逻辑
当你的逻辑无法简洁地表达为一个紧凑的表达式时,amp-script
是最佳选择。amp-bind
允许你将逻辑引入用户交互,但你的 JavaScript 代码需要适合单个表达式。由于代码被封装在 HTML 属性中,你将无法在 IDE 中获得语法高亮的好处,无法设置断点,并且调试可能具有挑战性。在这种情况下,amp-script
允许你遵循最佳编程实践。
增强 AMP 组件
amp-script
可以访问 AMP 状态变量和 AMP.getState()
和 AMP.setState()
方法。这提供了一条使用你自己的逻辑增强现有 AMP 组件的途径。它还使得可以影响 <amp-script>
组件之外的 DOM。请参考 这里 和 这里 的示例。
替换 amp-bind 和 amp-list
如果你是 AMP 新手并且熟悉 JavaScript,你可能会很想对每个浏览器交互都使用 amp-script
。但是,对于较简单的交互,你可能更需要学习使用 amp-bind
和 AMP 的 动作和事件 系统。对于更复杂的交互,或者需要更多逻辑和复杂状态变量操作的情况,amp-script
可能会更容易。
处理服务器数据
AMP 允许你使用 amp-list
检索服务器数据,并使用 amp-mustache
格式化数据。在 mustache 模板不足以满足需求的情况下,amp-script
可以获取数据、格式化数据,并将格式化后的数据注入到 DOM 中。如果需要在将服务器数据发送到 amp-mustache
之前对其进行处理,则 amp-script
函数可以作为 amp-list
的数据源。请参考文档了解详情和代码示例。
引入新功能
你可以使用 amp-script
来利用 Web API 和 DOM API 中当前 AMP 组件无法访问的区域,或者以 AMP 组件不支持的方式使用这些 API。例如,amp-script
支持 WebSockets
( 示例 )、localStorage
和 Canvas
。它支持各种浏览器事件,因此你可以监听超出 AMP 传递给传统组件的事件。并且由于 amp-script
提供了对 navigator
对象的访问,你可以检索有关 用户浏览器 或 首选语言 的信息。
何时替换 amp-bind 和 amp-list
对于熟悉 JavaScript 的 AMP 新开发人员来说,使用 amp-script
似乎总是比学习 amp-bind
和 amp-list
更容易。但是,在某些情况下,amp-bind
和 amp-list
更适合。
对于基本交互,amp-bind
通常更直接,它与 HTML 标签的紧密集成非常吸引人。在此示例中,按下按钮会更改文本片段。AMP 的数据绑定使此操作简单易懂
<p [text]="name">Rajesh</p>
<button on="tap:AMP.setState({name: 'Priya'})">I am Priya</button>
同样,当使用你控制输出的 API 时,你可以在服务器上实现业务逻辑。你可以格式化 API 输出的数据,使其平滑地适合 amp-mustache
模板。amp-list
在这种情况下非常合适。
amp-bind
还提供了一种直接的机制来在 AMP 组件之间进行通信。在此示例中,点击 <amp-selector>
中的图像会将状态变量 selectedSlide
设置为 0
,这反过来使 <amp-carousel>
移动到其第一个幻灯片。
<amp-carousel slide="selectedSlide">
...
</amp-carousel>
<amp-selector>
<amp-img on="tap:AMP.setState({selectedSlide: 0})"/>
</amp-selector>
传统的交互式 AMP 组件可能更适合跨越网页大部分区域的交互 - 因为你可能不希望将 DOM 的大部分内容都包裹在 <amp-script>
中。amp-list
和 amp-bind
与 AMP 的其余部分紧密集成,因此可以在网页的任何位置方便地使用绑定。
但是,在涉及更复杂的状态变量或多次交互的页面上,使用 amp-script
将产生更简单且更易于维护的代码。以 AMP Camp 电子商务演示站点中的这个示例为例
<amp-selector
name="color"
layout="container"
[selected]="product.selectedColor"
on="select:AMP.setState({
product:
{
selectedSlide: product[event.targetOption].option - 1,
selectedColor: event.targetOption,
selectedSize: product[event.targetOption].sizes[product.selectedSize] != null ?
product.selectedSize :
product[event.targetOption].defaultSize,
selectedQuantity: 1
}
})"
></amp-selector>
此演示是在发布 amp-script
之前创建的。但是,这种逻辑在 JavaScript 中会更容易编写和调试。对于具有更多业务逻辑的页面,使用 amp-script
可以帮助你避免混淆并遵循更好的编程实践。
在许多情况下,你希望在同一页面上同时使用 amp-script
和 amp-bind
。对于简单的交互,请部署 amp-bind
,当需要更多逻辑或结构时,请转向 amp-script
。此外,尽管 amp-script
只能对其 DOM 子元素进行更改,但如上所述,它可以通过改变状态变量来影响页面的其余部分。 amp-bind
完成其余的工作,如 此示例 中所示。
尽管当你的代码更改它有权访问的虚拟 DOM 时,WorkerDOM 会更改真实的 DOM,但不存在反向同步的机制。因此,不建议使用 amp-bind
或其他方式来修改 <amp-script>
的子元素。将页面的该区域保留给你的 amp-script
JavaScript。
为 amp-script
做贡献
amp-script
始终在不断发展,就像 AMP 一样。你可以通过参与来帮助改进它。考虑其他开发人员可能也需要的新功能,提交问题,当然也可以 建议并贡献新功能!
-
由 @morss 编写
由 @CrystalOnScript 和 @fstanis 贡献