amp-bind
说明
允许元素通过数据绑定和类似 JS 的简单表达式响应用户操作或数据更改而发生变化。
必需脚本
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
示例
用法
amp-bind
组件为 AMP 页面启用了自定义状态交互。
为了提高性能并避免意外内容跳动的风险,amp-bind
不会在页面加载时计算表达式。这意味着可视元素应具有默认状态,而不应依赖 amp-bind
进行初始渲染。
amp-bind
有三个主要概念
- 状态:文档范围的可变 JSON 状态。状态变量会根据用户操作进行更新。
amp-bind
不会在页面加载时计算表达式。可视元素应定义其默认“状态”,而不应依赖amp-bind
进行初始渲染。 - 表达式:可引用状态的类似 JavaScript 的表达式。
- 绑定:通过表达式将元素的属性链接到状态的特殊属性。通过以
[property]
的形式将其括在方括号内来绑定属性。
未声明状态的示例
Hello World
<p [text]="'Hello ' + foo">Hello World</p> <button on="tap:AMP.setState({foo: 'Interactivity'})"> Say "Hello Interactivity" </button>
在上面的示例中
- 状态最初为空。
- 它在
<p>
元素上对[text]
(节点的文本内容)有一个单一的绑定。 [text]
值包含表达式'Hello ' + foo
。此表达式连接字符串“Hello ”和状态变量 foo 的值。
当用户点击/单击按钮时
- 它会触发
tap
事件。 tap
事件调用AMP.setState()
方法。AMP.setState()
方法将foo
状态变量设置为Interactivity
的值。- 状态不再为空,因此页面会将绑定的属性更新为其状态。
AMP.setState()
可能会设置或更改页面上其他示例的状态。刷新此页面以查看 AMP.setState()
之前的示例。 声明状态的示例
<amp-state>
规范
状态
每个使用 amp-bind
的 AMP 文档都具有文档范围的可变 JSON 数据,或状态。
大小
<amp-state>
元素的 JSON 数据的最大大小为 100KB。
使用 <amp-state>
定义和初始化状态
表达式不会在页面加载时计算,但你可以定义一个初始状态。<amp-state>
组件包含不同的状态及其状态变量。虽然这定义了一个状态,但它不会在用户交互后反映在页面上。
<amp-state id="myDefinedState"> <script type="application/json"> { "foo": "bar" } </script> </amp-state> <p [text]="myDefinedState.foo"></p> <button on="tap:AMP.setState({})">See value of initialized state</button>
使用 表达式 来引用状态变量。如果 JSON 数据未嵌套在 <amp-state>
组件中,则通过点语法引用状态。在上面的示例中,myState.foo
的值为“bar”。
<amp-state>
元素还可以指定 CORS URL,而不是子 JSON 脚本。有关详细信息,请参阅 <amp-state>
规范。
<amp-state id="myRemoteState" src="/static/samples/json/websites.json"> </amp-state>
使用 AMP.setState()
更新状态变量
AMP.setState()
操作将对象字面量合并到状态中。这意味着您可以更新已定义状态变量的值。
<amp-state id="myUpdateState"> <script type="application/json"> { "foo": "bar", "baz": "hello" } </script> </amp-state> <p [text]="myUpdateState.foo"></p> <p [text]="myUpdateState.baz"></p> <button on="tap:AMP.setState({})">See value of set state</button> <!-- Like JavaScript, you can reference existing variables in the values of the object literal. --> <button on="tap:AMP.setState({myUpdateState:{baz: myUpdateState.foo}})"> Set value of baz to value of foo </button> <button on="tap:AMP.setState({myUpdateState:{baz: 'world'}})"> Set value of baz to "world" </button>
在上面的示例中,对第一个按钮触发 AMP.setState({})
操作会计算 [text]
绑定表达式。然后,它将已定义的状态变量值插入到 <p>
标记中。
当单击第二个按钮时,已定义 AMP.setState({myState:{baz: myState.foo}})
操作,它会将“baz”状态变量值与“foo”状态变量值 深度合并。两个 <p>
标记都显示“bar”。
状态变量值可以更新为未在初始状态中定义的值。当单击第三个按钮时,已定义 "tap:AMP.setState({myState:{baz: 'world'}})"
操作,它会深度合并“baz”状态变量值,并将其覆盖为“world”。
在其他两个按钮之后单击第一个按钮,会设置当前状态。不会有任何变化。
在页面刷新时,状态变量会恢复到 <amp-state>
中定义的 JSON。
事件触发和数据
当由某些事件触发时,AMP.setState()
可以访问 event
属性上的事件相关数据。
<!-- The "change" event of this <input> element contains a "value" variable that can be referenced via "event.value". --> <select on="change:AMP.setState({ option: event.value })"> <option value="0">No selection</option> <option value="1">Option 1</option> <option value="2">Option 2</option> </select> <div hidden [hidden]="option != 1"> Option 1 </div> <div hidden [hidden]="option != 2"> Option 2 </div>
更新嵌套变量
嵌套对象通常会合并到最大深度 10。所有变量(包括在 <amp-state>
中定义的变量)都可以被覆盖。
<amp-state id="myState"> <script type="application/json"> { "foo": "bar", "first": { "a": "nested once", "ab": { "b": "nested twice", "bc": { "c": "nested three times", "cd": { "d": "nested four times", "de": { "e": "nested five times", "ef": { "f": "nested six times", "fg": { "g": "nested seven times", "gh": { "h": "nested nine times", "hi": { "i": "nested ten times" } } } } } } } } } } </script> </amp-state> <p [text]="myState.foo"></p> <p [text]="myState.first.ab.bc.cd.de.ef.fg.gh.hi.i"></p> <button on="tap:AMP.setState({})">See value of set state</button> <button on="tap:AMP.setState({ myState: {first: {ab: {bc: {cd: {de: {ef: {fg: {gh: {hi: {i:'this is as far as you should merge nested values'} } } } } } } } } } })" > Merge 10th nested object </button>
循环引用
如果 object
包含循环引用,AMP.setState(object)
会引发错误。
移除变量
通过在 AMP.setState()
中将其值设置为 null
来删除现有状态变量。
<button on="tap:AMP.setState({removeMe: null})"></button>
使用 AMP.setState()
深度合并
调用 AMP.setState()
会将提供的对象字面量与当前状态深度合并。amp-bind
将所有字面量直接写入状态,但嵌套对象除外,这些对象会递归合并。状态中的基本类型和数组始终会被对象字面量中同名变量覆盖。
姓名
年龄
车辆
<p [text]="employee.name">Name</p> <p [text]="employee.age">Age</p> <p [text]="employee.vehicle">Vehicle</p> <!-- Pressing this button changes state to: --> <button on="tap:AMP.setState({ employee: { name: 'John Smith', age: 47, vehicle: 'Car' } })" > Set employee to John Smith </button> <!-- Pressing this button recursively merges the object literal argument, --> <!-- `{employee: {age: 64}}`, into the existing state. --> <button on="tap:AMP.setState({ employee: { age: 64 } })" > Set employee age to 64 </button> <!-- The value updates from 47 to 64 at employee.age. --> <!-- No other values change. -->
表达式
amp-bind
使用可引用状态的类似 JavaScript 的表达式。
与 JavaScript 的差异
- 表达式只能访问包含文档的 状态。
- 表达式无法访问
window
或document
。global
引用顶级状态。 - 只能使用 已列入白名单的函数 和运算符。可以使用箭头函数作为函数参数,例如
[1, 2, 3].map(x => x + 1)
。- 不允许使用自定义函数、类和循环。
- 未定义变量和数组索引超出范围会返回
null
,而不是undefined
或引发错误。 - 出于性能考虑,单个表达式当前最多限制为 250 个操作数。如果这不足以满足您的用例,请 联系我们。
以下都是有效的表达式
<p [text]="myExpressionsState.foo"></p> <!-- 1 + '1'; // 11 --> <button on="tap:AMP.setState({myExpressionsState: {foo: 1 + '1'}})"> foo: 1 + "1" </button> <!-- 1 + +'1'; // 2 --> <button on="tap:AMP.setState({myExpressionsState: {foo: 1 + + '1'}})"> foo: 1 + + "1" </button> <!-- !0; // true --> <button on="tap:AMP.setState({myExpressionsState: {foo: !0}})">foo: !0</button> <!-- null || 'default'; // 'default' --> <button on="tap:AMP.setState({myExpressionsState: {foo: null || 'default'}})"> null || "default" </button> <!-- [1, 2, 3].map(x => x + 1); // 2,3,4 --> <button on="tap:AMP.setState({myExpressionsState: {foo: [1, 2, 3].map(x => x + 1)}})" > [1, 2, 3].map(x => x + 1) </button>
在 bind-expr-impl.jison 和 bind-expression.js 中查找完整的表达式语法和实现。
允许列出的函数
数组
单参数箭头函数不能带括号,例如使用 x => x + 1
而不是 (x) => x + 1
。sort()
和 splice()
返回修改后的副本,而不是就地操作。
concat: 1, 2, 3
filter: 字母少于三个的单词
includes: "hello" 或 "world"
indexOf: "world"
join: 用破折号连接所有单词
lastIndexOf: "amp-bind"
map: 将每个数字添加到前一个数字
reduce: 将数组中的所有数字相加
slice:返回索引 1 和 3 处的单词
some:一些数字小于 2
sort:按字母顺序排列单词
splice:在索引 2 处放置“amp-bind”
<amp-state id="myArrayState"> <script type="application/json"> { "foo": [1, 2, 3], "bar": ["hello", "world", "bar", "baz"], "baz": "Hello world, welcome to amp-bind" } </script> </amp-state> <p [text]="'concat: ' + myArrayState.foo.concat(4)">concat: 1, 2, 3</p> <p [text]="'filter: ' + myArrayState.bar.filter(word => word.length > 3)"> filter: words with less than three letter </p> <p [text]="'includes: ' + myArrayState.bar.includes('hello' || 'world')"> includes: "hello" or "world" </p> <p [text]="'indexOf: ' + myArrayState.bar.indexOf('world')">indexOf: "world"</p> <p [text]="'join: ' + myArrayState.bar.join('-')"> join: all words with a dash </p> <p [text]="'lastIndexOf: ' + myArrayState.baz.lastIndexOf('amp-bind')"> lastIndexOf: "amp-bind" </p> <p [text]="'map: ' + myArrayState.foo.map((x, i) => x + i)"> map: add each number to previous number </p> <p [text]="'reduce: ' + myArrayState.foo.reduce((x, i) => x + i)"> reduce: add all numbers in array together </p> <p [text]="'slice: ' + myArrayState.bar.slice(1,3)"> slice: return words at index 1 and 3 </p> <p [text]="'some: ' + myArrayState.foo.some(x => x < 2)"> some: some numbers are less than 2 </p> <p [text]="'sort: ' + myArrayState.bar.sort()"> sort: place words in alphabetical order </p> <p [text]="'splice: ' + myArrayState.bar.splice(2, 0, 'amp-bind')"> splice: place "amp-bind" at index 2 </p> <button on="tap:AMP.setState({})">Evaluate</button>
数字
toExponential:100 的指数为 5
toFixed:1.99 四舍五入并固定到小数点后一位
toPrecision:1.234567 以字符串形式返回,保留到小数点后第三位
toString:3.14 以字符串形式返回
<p [text]="'toExponential: ' + (100).toExponential(5)"> toExponential: 100 to the exponent of 5 </p> <p [text]="'toFixed: ' + (1.99).toFixed(1)"> toFixed: 1.99 rounded and fixed to first decimal </p> <p [text]="'toPrecision: ' + (1.234567).toPrecision(3)"> toPrecision: 1.234567 returned as a string to the third digit </p> <p [text]="'toString ' + (3.14).toString()"> toString: 3.14 returned as a string </p> <button on="tap:AMP.setState({})">Evaluate</button>
字符串
charAt:索引 6 处的字符
charCodeAt:索引 6 处字符的 UTF-16 代码单元
concat:合并 foo 和 bar
lastIndexOf:字母“w”的索引
replace:用“amp-bind”替换“world”
slice:提取前 5 个字符
split:以空格为分隔符拆分单词并以数组形式返回
toLowerCase:将所有字母转换为小写
toUpperCase:将所有字母转换为大写
<amp-state id="myStringState"> <script type="application/json"> { "foo": "Hello world", "bar": ", welcome to amp-bind" } </script> </amp-state> <p [text]="'charAt: ' + myStringState.foo.charAt(6)"> charAt: The character at index 6 </p> <p [text]="'charCodeAt: ' + myStringState.foo.charCodeAt(6)"> charCodeAt: The UTF-16 code unit of the character at index 6 </p> <p [text]="'concat: ' + myStringState.foo.concat(myState.bar)"> concat: Combine foo and bar </p> <p [text]="'lastIndexOf: ' + myStringState.foo.lastIndexOf('w')"> lastIndexOf: The index of "w" </p> <p [text]="'replace: ' + myStringState.foo.replace('world', 'amp-bind')"> replace: Replace "world" with "amp-bind" </p> <p [text]="'slice: ' + myStringState.foo.slice(5)"> slice: Extract the first 5 characters </p> <p [text]="'split: ' + myStringState.foo.split(' ')"> split: Split words at space and return as array </p> <p [text]="'toLowerCase: ' + myStringState.foo.toLowerCase()"> toLowerCase: Make all letters lower case </p> <p [text]="'toUpperCase: ' + myStringState.foo.toUpperCase()"> toUpperCase: Make all letters upper case </p> <button on="tap:AMP.setState({})">Evaluate</button>
数学
静态函数没有命名空间,例如,使用abs(-1)
代替Math.abs(-1)
abs:5 - 9 的绝对值
abs:将 1.01 四舍五入到下一个最大的整数
floor:将 1.99 向下舍入到一个整数
max:返回最大的数字
min:返回最小的数字
pow:返回 5 的 3 次方
random:返回一个大于 0 且小于 1 的数字
round:将 1.51 四舍五入
sign:评估是正数还是负数
<p [text]="'abs: ' + abs(5 - 9)">abs: absolute number of 5 - 9</p> <p [text]="'ceil: ' + ceil(1.01)"> abs: round 1.01 up to the next largest whole number </p> <p [text]="'floor: ' + floor(1.99)">floor: round 1.99 down to a whole number</p> <p [text]="'max: ' + max(100, 4, 98)">max: return largest number</p> <p [text]="'min: ' + min(100, 4, 98)">min: return smalled number</p> <p [text]="'pow: ' + pow(5, 3)">pow: return 5 to the power of 3</p> <p [text]="'random: ' + random()"> random: return a number greater than 0 and less than 1 </p> <p [text]="'round: ' + round(1.51)">round: round 1.51</p> <p [text]="'sign: ' + sign(-9)">sign: evaluate if positive or negative</p> <button on="tap:AMP.setState({})">Evaluate</button>
对象
静态函数没有命名空间,例如,使用keys(Object)
代替Object.abs(Object)
keys:myObjectState JSON 对象键
values:myObjectState JSON 对象值
<amp-state id="myObjectState"> <script type="application/json"> { "hello": "world", "foo": "bar" } </script> </amp-state> <p [text]="'keys: ' + keys(myObjectState)"> keys: myObjectState JSON object keys </p> <p [text]="'values: ' + values(myObjectState)"> values: myObjectState JSON object values </p> <button on="tap:AMP.setState({})">Evaluate</button>
全局
encodeURI:对 URI 编码并忽略协议前缀
encodeURIComponent:对 URI 编码
<p [text]="'encodeURI: ' + encodeURI('https://amp.org.cn/😉')"> encodeURI: Encode a URI and ignore protocol prefix </p> <p [text]="'encodeURIComponent: ' + encodeURIComponent('https://amp.org.cn/😉')"> encodeURIComponent: Encode a URI </p> <button on="tap:AMP.setState({})">Evaluate</button>
使用 amp-bind-macro
定义宏
通过定义 amp-bind-macro
重用 amp-bind
表达式片段。amp-bind-macro
元素允许一个表达式,该表达式可以采用零个或多个参数并引用当前状态。像调用函数一样调用 amp-bind-macros
,引用文档中任何位置的 id
属性值。
输入半径值
圆的面积为 0。
<amp-bind-macro id="circleArea" arguments="radius" expression="3.14 * radius * radius" ></amp-bind-macro> <p> Input a radius value </p> <input type="number" min="0" max="100" value="0" on="input-throttled:AMP.setState({myCircle:{radius: event.value}})" /> <p> The circle has an area of <span [text]="circleArea(myCircle.radius)">0</span>. </p>
宏还可以调用其他宏在宏自身之前定义。宏无法递归调用自身。
绑定
绑定是一种特殊属性,形式为 [property]
,它将元素的属性链接到表达式。如果在 XML 中开发,请使用备用与 XML 兼容的语法。
当状态更改时,将计算与该状态绑定的表达式。与状态绑定的元素属性将使用新的表达式结果进行更新。
布尔表达式结果切换布尔属性。例如:<amp-video [controls]="expr"...>
。当 expr
计算为 true
时,<amp-video>
元素具有 controls
属性。当 expr
计算为 false
时,controls
属性将被移除。
<amp-video [controls]="controls" width="640" height="360" layout="responsive" poster="/static/inline-examples/images/kitten-playing.png" > <source src="/static/inline-examples/videos/kitten-playing.webm" type="video/webm" /> <source src="/static/inline-examples/videos/kitten-playing.mp4" type="video/mp4" /> <div fallback> <p>This browser does not support the video element.</p> </div> </amp-video> <button on="tap:AMP.setState({ controls: true })"> Controls </button> <button on="tap:AMP.setState({ controls: false })"> No Controls </button>
React 和 XML 兼容性
如果使用 React 或 XML 进行开发,请使用备用 data-amp-bind-property
语法。属性名称中的 [
和 ]
字符在 XML 中无效,因此无法使用 [property]
语法。
将 property
字段替换为要在 data-amp-bind-property
中定义的属性的名称。
例如,[text]="myState.foo"
将变为 data-amp-bind-text="myState.foo"
。
绑定类型
amp-bind
支持对五种类型的元素状态进行数据绑定。
使用 [text]
属性绑定 Node.textContent
。大多数文本元素都支持 [text]
属性。
<p [text]="'Hello ' + myState.foo">Hello World</p> <p></p>
CSS 类
使用 [class]
属性绑定元素的 class
。[class]
表达式必须生成一个空格分隔的字符串。这意味着,如果你要绑定多个类,请在名称之间使用空格。逗号或破折号将被计算为类名称。
<head> <style amp-custom> .background-green { background: green; } .background-red { background: red; } .border-red { border-color: red; border-width: 5px; border-style: solid; } </style> </head> <body> <div class="background-red" [class]="myClass">Hello World</div> <!-- This button adds both classes --> <button on="tap:AMP.setState({ myClass: 'background-green border-red' })"> Working: Change Class </button> <!-- String arrays also work --> <button on="tap:AMP.setState({ myClass: ['background-green', 'border-red'] })" > Working string array: Change Class </button> <!-- This expression evaluates to class="background-green,border-red" --> <button on="tap:AMP.setState({ myClass: 'background-green,border-red' })"> Broken: Change Class </button> </body>
使用 [hidden]
属性隐藏和显示元素。[hidden]
表达式应为布尔表达式。
你好!
<p [hidden]="hiddenState">Hello there!</p> <button on="tap:AMP.setState({hiddenState: true})">Hide</button> <button on="tap:AMP.setState({hiddenState: false})">Show</button>
AMP 组件 的大小
使用 [width]
和 [height]
属性更改 width
和 height
。
<amp-img src="https://unsplash.it/400/200" width="200" [width]="myImageDimension.width" height="100" [height]="myImageDimension.height" > </amp-img> <button on="tap:AMP.setState({ myImageDimension: { width: 400, height: 200 } })" > Change size </button>
辅助功能状态和属性
用于动态更新辅助技术(例如屏幕阅读器)可用的信息。所有 [aria-*]
属性 都可绑定。
AMP 组件特定和 HTML 属性
一些 AMP 组件和 HTML 元素具有特定的可绑定属性。它们如下所示。
特定于 AMP 组件的属性
<amp-carousel type=slides>
[slide]
更改当前显示的幻灯片索引。
查看示例.
<amp-lightbox>
[open]
切换灯箱显示。
on="lightboxClose: AMP.setState(...)"
在关闭灯箱时更新变量。
<details>
[open]
请参阅相应的 details 属性。
<fieldset>
[disabled]
启用或禁用 fieldset。
<image>
[xlink:href]
请参阅相应的 image 属性。
<option>
[disabled]
[label]
[selected]
[value]
请参阅相应的 option 属性。
<optgroup>
[disabled]
[label]
请参阅相应的 optgroup 属性。
<section>
[data-expand]
更改section
在amp-accordion
中的展开。
不允许的绑定
出于安全原因,不允许绑定到 innerHTML
。
所有属性绑定都已针对不安全值(例如 javascript:
)进行了清理。
调试
警告
在开发模式下,当绑定的属性的默认值与其对应的表达式的初始结果不匹配时,amp-bind
会发出警告。这有助于防止由其他状态变量的更改引起的意外变异。例如
<!-- The element's default class value ('def') doesn't match the expression result for [class] ('abc'), so a warning will be issued in development mode. --> <p [class]="'abc'" class="def"></p>
在开发模式下,当解除对未定义变量或属性的引用时,amp-bind
也会发出警告。这也有助于防止因 null
表达式结果而导致意外变异。例如
<amp-state id="myAmpState"> <script type="application/json"> {"foo": 123} </script> </amp-state> <!-- The amp-state#myAmpState does not have a `bar` variable, so a warning will be issued in development mode. --> <p [text]="myAmpState.bar">Some placeholder text.</p>
错误
以下概述了使用 amp-bind
时可能出现的错误类型。
类型 | 消息 | 建议 |
---|---|---|
无效绑定 | 不允许在 <P> 上绑定到 [foo]. | 仅使用 允许绑定的。 |
语法错误 | 表达式编译错误... | 检查表达式是否有错别字。 |
未允许列出的函数 | alert 不是受支持的函数。 | 仅使用 允许列出的函数。 |
已清理的结果 | “javascript:alert(1)” 不是 [href] 的有效结果。 | 避免使用被禁止的 URL 协议或表达式,这些协议或表达式会导致 AMP 验证器失败。 |
CSP 违规 | 拒绝创建来自“blob:...”的 worker,因为它违反了以下内容安全策略指令... | 将 default-src blob: 添加到来源的内容安全策略中。amp-bind 将繁重的任务委托给 专用 Web Worker 以确保良好的性能。 |
调试状态
使用 AMP.printState()
将当前状态打印到控制台。要使此功能正常工作,您需要启用 开发模式。
表达式语法
amp-bind
表达式的 BNF 类语法
expr:
operation
| invocation
| member_access
| '(' expr ')'
| variable
| literal
;
operation:
'!' expr
| '-' expr %prec UMINUS
| '+' expr %prec UPLUS
| 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:
NAME args
| expr '.' NAME args
| expr '.' NAME '(' arrow_function ')'
| expr '.' NAME '(' arrow_function ',' expr ')'
;
arrow_function:
'(' ')' '=>' expr
| NAME '=>' expr
| '(' params ')' '=>' expr
;
params:
NAME ',' NAME
| params ',' NAME
;
args:
'(' ')'
| '(' array ')'
;
member_access:
expr member
;
member:
'.' NAME
| '[' expr ']'
;
variable:
NAME
;
literal:
primitive
| object_literal
| array_literal
;
primitive:
STRING
| NUMBER
| TRUE
| FALSE
| NULL
;
array_literal:
'[' ']'
| '[' array ']'
| '[' array ',' ']'
;
array:
expr
| array ',' expr
;
object_literal:
'{' '}'
| '{' object '}'
| '{' object ',' '}'
;
object:
key_value
| object ',' key_value
;
key_value:
key ':' expr
;
key:
NAME
| primitive
| '[' expr ']'
;
您已经阅读过本文档十几次了,但它并没有真正涵盖您所有的问题?也许其他人也有同感:在 Stack Overflow 上联系他们。
转到 Stack Overflow 发现了一个 bug 或缺少某个功能?AMP 项目强烈鼓励您参与和做出贡献!我们希望您成为我们开源社区的持续参与者,但我们也欢迎您为特别热衷的问题做出一次性贡献。
转到 GitHub