AMP

Important: this component does not support your currently selected format stories!

amp-bind

通过数据绑定和表达式添加自定义互动方式。

必需的脚本
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
示例
教程 制作交互式 AMP 网页

概述

借助 amp-bind 组件,您可以通过数据绑定以及类似于 JS 的表达式,为 AMP 网页添加自定义的有状态互动方式。

观看此视频,简要了解 amp-bind。

一个简单示例

在下面的示例中,点按相应按钮可将 <p> 元素的文本从“Hello World”更改为“Hello amp-bind”。

<p [text]="'Hello ' + foo">Hello World</p>

<button on="tap:AMP.setState({foo: 'amp-bind'})">Say "Hello amp-bind"</button>

为了确保性能并避免内容意外跳转,amp-bind 不会在网页加载时对表达式求值。这意味着,应该为视觉元素指定默认状态,而不是依赖 amp-bind 进行初始呈现。

运作方式

amp-bind 包含三个主要组件:

  1. 状态:涵盖整个文档的可变 JSON 状态。在上面的示例中,在点按相应按钮之前,状态为空。在点按相应按钮之后,状态为 {foo: 'amp-bind'}
  2. 表达式:类似于 JavaScript 的表达式,可引用状态。上面的示例中包含单个表达式,即 'Hello ' + foo,该表达式用于将字符串字面量 'Hello ' 和状态变量 foo 连接在一起。一个表达式中最多可以使用 100 个操作数。
  3. 绑定:一些采用 [property] 形式的特殊属性,用于将元素的属性关联到表达式。上面的示例中包含单个绑定,即 [text],该绑定用于在表达式的值每次发生更改时更新 <p> 元素的文本。

amp-bind 会特别注意确保在 AMP 网页上实现出色的速度、安全性和性能。

一个稍微复杂的示例

<!-- 将复杂的嵌套 JSON 数据存储在 <amp-state> 元素中。-->
<amp-state id="myAnimals">
  <script type="application/json">
    {
      "dog": {
        "imageUrl": "/img/dog.jpg",
        "style": "greenBackground"
      },
      "cat": {
        "imageUrl": "/img/cat.jpg",
        "style": "redBackground"
      }
    }
  </script>
</amp-state>

<p [text]="'This is a ' + currentAnimal + '.'">This is a dog.</p>

<!-- 也可以使用 [class] 添加或移除 CSS 类。 -->
<p class="greenBackground" [class]="myAnimals[currentAnimal].style">
  Each animal has a different background color.
</p>

<!-- 或通过 [src] 绑定更改图片的 src。-->
<amp-img width="300" height="200" src="/img/dog.jpg" [src]="myAnimals[currentAnimal].imageUrl">
</amp-img>

<button on="tap:AMP.setState({currentAnimal: 'cat'})">Set to Cat</button>

按下相应按钮后:

  1. 系统会根据定义为 'cat'currentAnimal状态进行更新。
  2. 系统会对依赖于 currentAnimal表达式进行求值:

    • 'This is a ' + currentAnimal + '.' => 'This is a cat.'
    • myAnimals[currentAnimal].style => 'redBackground'
    • myAnimals[currentAnimal].imageUrl => /img/cat.jpg
  3. 系统会对依赖于更改后的表达式的绑定进行更新:

    • 第一个 <p> 元素的文本将显示为“This is a cat.”
    • 第二个 <p> 元素的 class 属性将为“redBackground”。
    • amp-img 元素将显示一只猫的图片。

如需查看此示例的带代码注释版本,请观看在线演示

详细说明

状态

每个使用 amp-bind 的 AMP 文档都包含涵盖整个文档的可变 JSON 数据(即状态)。

通过 amp-state 对状态进行初始化

可通过 amp-state 组件对 amp-bind 的状态进行初始化:

<amp-state id="myState">
  <script type="application/json">
    {
      "foo": "bar"
      }
  </script>
</amp-state>

表达式可通过点语法引用状态变量。在此示例中,myState.foo 的求解结果为 "bar"

  • <amp-state> 元素的子级 JSON 不能超过 100KB。
  • <amp-state> 元素还可以指定 CORS 网址,而不是子级 JSON 脚本。有关详情,请参阅附录

刷新状态

此组件支持 refresh 操作,该操作可用于刷新状态内容。

<amp-state id="amp-state" ...></amp-state>
<!-- 点击该按钮将刷新并重新获取 amp-state 中的 json。 -->
<button on="tap:amp-state.refresh"></button>

通过 AMP.setState() 更新状态

AMP.setState() 操作可将对象字面量合并到状态中。例如,当用户按下方的按钮后,AMP.setState() 会将对象字面量与状态进行深度合并

<!-- 与 JavaScript 类似,您可以在
       对象字面量的值中引用现有变量。 -->
<button on="tap:AMP.setState({foo: 'bar', baz: myAmpState.someVariable})"></button>

一般来说,嵌套对象的合并深度上限为 10。所有变量(包括由 amp-state 引入的变量)都可以被覆盖。

被特定事件触发后,AMP.setState() 还可以访问 event 属性的事件相关数据。

<!-- 此 <input> 元素的“change”事件包含
     可通过“event.value”引用的“value”变量。 -->
<input type="range" on="change:AMP.setState({myRangeValue: event.value})">

通过 AMP.pushState() 修改历史记录

AMP.pushState() 操作与 AMP.setState() 类似,只不过它还会将新条目推送到浏览记录堆栈。弹出此浏览记录条目(例如,通过执行返回操作)将会恢复由 AMP.pushState() 设置的变量的上一个值。

例如:

<button on="tap:AMP.pushState({foo: '123'})">Set 'foo' to 123</button>
  • 点按相应按钮会将变量 foo 设为 123,并推送新的历史记录条目。
  • 执行返回操作会将 foo 恢复到之前的值“bar”(相当于调用 AMP.setState({foo: 'bar'}))。

表达式

表达式与 JavaScript 类似,但两者存在一些重要区别。

与 JavaScript 的区别

  • 表达式只能访问所在文档的状态
  • 表达式无权访问 windowdocument 等全局属性。
  • 只能使用列入白名单的函数和运算符。
  • 一般不允许使用自定义函数、类和循环。允许将箭头函数用作参数,如 Array.prototype.map
  • 未定义的变量和 array-index-out-of-bound 会返回 null,而不是 undefined,也不会引发错误。
  • 为了确保性能,单个表达式中目前最多可以使用 50 个操作数。如果这无法满足您的使用需求,请与我们联系

如需查看完整的表达式语法和实现,请参阅 bind-expr-impl.jisonbind-expression.js

示例

以下都是有效的表达式:

1 + '1'           // 11
1 + (+'1')        // 2
!0                // true
null || 'default' // 'default'

列入白名单的函数

对象类型 函数 示例
Array1 concat
filter
includes
indexOf
join
lastIndexOf
map
reduce
slice
some
sort (not-in-place)
splice (not-in-place)
// 返回 [1, 2, 3]。
          [3, 2, 1].sort()
// 返回 [1, 3, 5]。
            [1, 2, 3].map((x, i) => x + i)
// 返回 6。
              [1, 2, 3].reduce((x, y) => x + y)
Number toExponential
toFixed
toPrecision
toString
// 返回 3。
                (3.14).toFixed()
// 返回“3.14”。
                  (3.14).toString()
String charAt
charCodeAt
concat
indexOf
lastIndexOf
slice
split
substr
substring
toLowerCase
toUpperCase
// 返回“abcdef”。
                      abc'.concat('def')
Math2 abs
ceil
floor
max
min
random
round
sign
// 返回 1。
                          abs(-1)
Object2 keys
values
// 返回 ['a', 'b']。
                            keys({a: 1, b: 2})
// 返回 [1, 2]。
                              values({a: 1, b: 2}
Global2 encodeURI
encodeURIComponent
// 返回 'Hello%20world'。
                                encodeURIComponent('Hello world')

1包含单个参数的箭头函数不能包含英文括号,例如,应使用 x => x + 1,而不是 (x) =>; x + 1。此外,sort()splice() 会返回修改后的副本,而不是原地操作。

2静态函数没有命名空间,例如,应使用 abs(-1),而不是 Math.abs(-1)

通过 amp-bind-macro 定义宏

您可以通过定义 amp-bind-macro 重复使用 amp-bind 表达式片段。借助 amp-bind-macro 元素,您可以定义一个采用零个或多个参数并引用当前状态的表达式。您可以像调用函数一样调用宏,只需从文档中的任意位置引用宏的 id 属性值即可。

<amp-bind-macro id="circleArea" arguments="radius" expression="3.14 * radius * radius"></amp-bind-macro>

<div>
  The circle has an area of <span [text]="circleArea(myCircle.radius)">0</span>.
</div>

宏还可以调用在其之前定义的其他宏,但无法以递归方式调用自身。

绑定

绑定 是一种采用 [property] 形式的特殊属性,用于将元素的属性关联到表达式。您还可以通过 data-amp-bind-property 形式使用另一种与 XML 兼容的语法。

状态发生变化时,系统会对表达式重新求值,并根据新的表达式结果更新绑定元素的属性。

amp-bind 支持对以下四种元素状态进行数据绑定:

类型 属性 详细说明
Node.textContent [text] 大多数文本元素都支持该类型。
CSS 类 [class] 表达式结果必须是以空格分隔的字符串。
hidden 属性 [hidden] 应为布尔表达式。
AMP 元素大小 [width]
[height]
用于更改 AMP 元素的宽度和/或高度。
特定于元素的属性 各种

关于绑定的注意事项:

  • 为了安全起见,不允许绑定到 innerHTML
  • 对于不安全的值(如 javascript:),系统会对所有属性绑定进行净化处理。
  • 系统会根据布尔表达式的结果切换布尔值属性。例如:<amp-video [controls]="expr"...>。当 expr 的求解结果为 true 时,<amp-video> 元素具有 controls 属性。当 expr 的求解结果为 false 时,系统会移除 controls 属性。
  • 编写 XML(如 XHTML、JSX)或通过 DOM API 编写属性时,属性名称中的括号字符 [] 可能会带来问题。在这些情况下,请使用替代语法 data-amp-bind-x="foo",而不是 [x]="foo"

特定于元素的属性

仅允许绑定到以下组件和属性:

组件 属性 行为
<amp-brightcove> [data-account]
[data-embed]
[data-player]
[data-player-id]
[data-playlist-id]
[data-video-id]
更改显示的 Brightcove 视频。
<amp-carousel type=slides> [slide] 更改当前显示的幻灯片索引。查看示例
<amp-date-picker> [min]
[max]
设置可选择的最早日期
设置可选择的最晚日期
<amp-google-document-embed> [src]
[title]
在更新后的网址上显示文档。
更改文档的标题。
<amp-iframe> [src] 更改 iframe 的来源网址。
<amp-img> [alt]
[attribution]
[src]
[srcset]
绑定到 [src] 时,请务必同时绑定到 [srcset],以便绑定在缓存中正常发挥作用。
请参阅相应的 amp-img 属性
<amp-lightbox> [open] 切换灯箱的显示。提示:在灯箱关闭时,使用 on="lightboxClose: AMP.setState(...)" 更新变量。
<amp-list> [src] 如果表达式为字符串,则从字符串网址获取并呈现 JSON。 如果表达式为对象或数组,则呈现表达式数据。
<amp-selector> [selected]*
[disabled]
更改当前所选的子元素,
这些元素由其 option 属性值标识。支持多个选择项对应的值列表(以英文逗号分隔)。查看示例
<amp-state> [src] 从新网址获取 JSON,并将其合并到现有状态。请注意,以下更新将忽略 <amp-state> 元素,以防止出现循环。
<amp-video> [alt]
[attribution]
[controls]
[loop]
[poster]
[preload]
[src]
请参阅相应的 amp-video 属性
<amp-youtube> [data-videoid] 更改显示的 YouTube 视频。
<a> [href] 更改链接。
<button> [disabled]
[type]
[value]
请参阅相应的 button 属性
<details> [open] 请参阅相应的 details 属性
<fieldset> [disabled] 启用或停用字段集。
<image> [xlink:href]
请参阅相应的 image 属性
<input> [accept]
[accessKey]
[autocomplete]
[checked]
[disabled]
[height]
[inputmode]
[max]
[maxlength]
[min]
[minlength]
[multiple]
[pattern]
[placeholder]
[readonly]
[required]
[selectiondirection]
[size]
[spellcheck]
[step]
[type]
[value]
[width]
请参阅相应的 input 属性
<option> [disabled]
[label]
[selected]
[value]
请参阅相应的 option 属性
<optgroup> [disabled]
[label]
请参阅相应的 optgroup 属性
<select> [autofocus]
[disabled]
[multiple]
[required]
[size]
请参阅相应的 select 属性
<source> [src]
[type]
请参阅相应的 source 属性
<track> [label]
[src]
[srclang]
请参阅相应的 track 属性
<textarea> [autocomplete]
[autofocus]
[cols]
[disabled]
[maxlength]
[minlength]
[placeholder]
[readonly]
[required]
[rows]
[selectiondirection]
[selectionend]
[selectionstart]
[spellcheck]
[wrap]
请参阅相应的 textarea 属性
<sup>*</sup>表示可绑定的属性,而且没有不可绑定的对应项。

调试

在开发模式下进行测试(使用网址片段 #development=1),以便在开发过程中发现警告和错误,并可以使用特殊的调试函数。

警告

在开发模式下,如果绑定属性的默认值与相应表达式的初始结果不一致,amp-bind 会发出警告。这有助于防止出现因其他状态变量发生变化而导致的意外变化。例如:

<!-- 该元素的默认类值 ('def') 与 [class] ('abc') 的表达式结果不一致,因此在开发模式下将发出警告。-->

<p class="def" [class]="'abc'"></p>

在开发模式下,当解除对未定义变量或属性的引用时,amp-bind 也会发出警告。这同样有助于防止出现因 null 表达式结果而导致的意外变化。例如:

<amp-state id="myAmpState">
  <script type="application/json">
    { "foo": 123 }
</script>
</amp-state></p>

<!-- amp-state#myAmpState 没有 `bar` 变量,因此在开发模式下将发出警告。-->
<p [text]="myAmpState.bar">Some placeholder text.</p>

错误

使用 amp-bind 时,可能会遇到以下几种运行时错误。

类型 消息 建议
无效绑定 不允许绑定到 <P> 上的 [someBogusAttribute]。 仅使用列入白名单的绑定
语法错误 …中存在表达式编译错误 确认表达式是否存在拼写错误。
函数未列入白名单 alert 不是受支持的函数。 仅使用列入白名单的函数
结果已经过净化处理 对于 [href],“javascript:alert(1)”不是有效的结果。 避免使用加入黑名单的网址协议或表达式,否则会导致无法通过 AMP 验证工具的验证。
CSP 违规 被拒绝通过“blob:...”创建工作器,因为它违反了《内容安全政策》的以下指令… default-src blob: 添加到来源的《内容安全政策》。amp-bind 会将耗用资源较多的工作委派给专门的网络工作器,以确保实现良好的性能。

调试状态

利用 AMP.printState() 将当前状态输出到控制台。

附录

<amp-state> 规范

amp-state 元素可以包含子级 <script> 元素,也可以 包含 src 属性(其中包含指向远程 JSON 端点的 CORS 网址),但不能同时包含这两者。

<amp-state id="myLocalState">
  <script type="application/json">
    {
      "foo": "bar"
      }
  </script>
</amp-state>

<amp-state id="myRemoteState" src="https://data.com/articles.json">
</amp-state>

XHR 批处理

AMP 会对向 JSON 端点发出的 XMLHttpRequest (XHR) 进行批处理,也就是说,您可以在 AMP 网页上将单个 JSON 数据请求用作多个使用方(如多个 amp-state 元素)的数据源。例如,如果您的 amp-state 元素向某个端点发出 XHR,那么在该 XHR 传输期间,向同一端点发送的所有后续 XHR 都不会触发,系统将只返回第一个 XHR 的结果。

属性

src 远程端点的网址,该端点将返回 JSON,以便更新此 amp-state。这必须是 CORS HTTP 服务。 src 属性支持所有标准网址变量替换。如需了解详情,请参阅替换指南

该端点必须符合 AMP 中的 CORS 请求规范中规定的要求。

credentials(可选) credentials 选项定义为通过 Fetch API 指定的值。
  • 支持的值:omitinclude
  • 默认值:omit
要发送凭据,请传递 include 的值。如果此值已设置,响应必须遵循 AMP CORS 安全指南

通过 AMP.setState() 进行深度合并

调用 AMP.setState() 时,amp-bind 会将所提供的对象字面量与当前状态进行深度合并。除了以递归方式合并的嵌套对象之外,对象字面量的所有变量都会直接写入到状态。状态中的基元和数组始终会被对象字面量中的同名变量覆盖。

请参考以下示例:

{
  <!-- 状态为空 -->
  }
<button on="tap:AMP.setState({employee: {name: 'John Smith', age: 47, vehicle: 'Car'}})"...></button>
<button on="tap:AMP.setState({employee: {age: 64}})"...></button>

按下第一个按钮后,状态会更改为:

{
  employee: {
    name: 'John Smith',
    age: 47,
    vehicle: 'Car',
    }
  }

按下第二个按钮后,amp-bind 会以递归方式将对象字面量参数 {employee: {age: 64}} 合并到现有状态。

{
  employee: {
    name: 'John Smith',
    age: 64,
    vehicle: 'Car',
    }
  }

employee.age 已更新,但 employee.nameemployee.vehicle 键未发生变化。

请注意,如果通过包含循环引用的对象字面量调用 AMP.setState()amp-bind 会抛出错误。

移除变量

AMP.setState() 中将现有状态变量的值设为 null 可移除该变量。从上一个示例中的状态开始,按下:

<button on="tap:AMP.setState({employee: {vehicle: null}})"...></button>

会将状态更改为:

{
  employee: {
    name: 'John Smith',
    age: 48,
    }
  }

同样,按下:

<button on="tap:AMP.setState({employee: null})"...></button>

会将状态更改为:

{
  <!-- 状态为空 -->
  }

表达式语法

amp-bind 表达式的语法,与 BNF 语法类似:

expr:
    operation
  | invocation
  | member_access
  | '(' expr ')'
  | variable
  | literal

operation:
    '!' expr
  | '-' expr
  | '+' expr
  | expr '+' expr
  | expr '-' expr
  | expr '*' expr
  | expr '/' expr
  | expr '%' expr
  | expr '&&' expr
  | expr '||' expr
  | expr '<=' expr
  | expr '<' expr
  | expr '>=' expr
  | expr '>' expr
  | expr '!=' expr
  | expr '==' expr
  | expr '?' expr ':' expr

invocation:
    expr '.' NAME args

args:
    '(' ')'
  | '(' array ')'
  ;

member_access:
    expr member
  ;

member:
    '.' NAME
  | '[' expr ']'

variable:
    NAME
  ;

literal:
    STRING
  | NUMBER
  | TRUE
  | FALSE
  | NULL
  | object_literal
  | array_literal

array_literal:
    '[' ']'
  | '[' array ']'

array:
    expr
  | array ',' expr

object_literal:
    '{' '}'
  | '{' object '}'

object:
    key_value
  | object ',' key_value

key_value:
  expr ':' expr
需要更多帮助?

You've read this document a dozen times but it doesn't really cover all of your questions? Maybe other people felt the same: reach out to them on Stack Overflow.

Go to Stack Overflow
Found a bug or missing a feature?

The AMP project strongly encourages your participation and contributions! We hope you'll become an ongoing participant in our open source community but we also welcome one-off contributions for the issues you're particularly passionate about.

Go to GitHub