AMP
  • 网站

amp-script

简介

amp-script 组件允许您运行自定义 JavaScript。您的代码在 Web Worker 中运行,并有一些限制。

设置

首先,您需要导入 amp-script 扩展。

<script async custom-element="amp-script" src="https://cdn.ampproject.org/v0/amp-script-0.1.js"></script>

对于内联脚本,您需要生成脚本哈希。在开发期间,使用 data-ampdevmode 属性禁用此要求。访问文档了解更多信息。

<meta name="amp-script-src" content="sha384-iER2Cy-P1498h1B-1f3ngpVEa9NG1xIxKqg0rNkRX0e7p5s0GYdit1MRKsELIQe8 sha384-UPY0FmlOzIjSqWqMgbuaEbqIdvpGY_FzCuTAyoLdrFJb2NYf8cPWJlugA0rUbXjL

从 URL 加载脚本

要从 URL 加载您的脚本,请使用 src 属性。此示例加载并运行名为 hello.js 的脚本。有效的 AMP 要求所有 URL 都是绝对的,并使用 https

这是 hello-world.js 中的脚本

const button = document.getElementById('hello-url');

button.addEventListener('click', () => {
  const h1 = document.createElement('h1');
  h1.textContent = 'Hello World!';
  document.body.appendChild(h1);
});

这是 HTML

<amp-script layout="container" src="https://amp.org.cn/documentation/examples/components/amp-script/hello-world.js" class="sample">
  <button id="hello-url">Say hello!</button>
</amp-script>
在 Playground 中打开此代码片段

使用内联脚本

您还可以内联包含脚本并通过 id 引用它。请注意,在脚本中,您需要设置 type=text/plaintarget=amp-script

<amp-script layout="container" script="hello-world" class="sample">
  <button id="hello-inline">Say hello!</button>
</amp-script>

<script id="hello-world" type="text/plain" target="amp-script">
  const button = document.getElementById('hello-inline');

  button.addEventListener('click', () => {
    const h1 = document.createElement('h1');
    h1.textContent = 'Hello World!';
    document.body.appendChild(h1);
  });
</script>
在 Playground 中打开此代码片段

amp-script 将其子元素作为虚拟 DOM 传递给您的脚本 - 而不是整个 DOM。对于您的脚本,这些子元素就是 DOM。因此,document.body 指的是 amp-script 标签内的内容,而不是实际的 bodydocument.body.appendChild(...) 实际上是在 amp-script 元素内添加一个元素。

使用 fetch API

amp-script 支持 fetch API。如果 amp-script 知道脚本无法更改组件的高度,它允许我们在加载时更新页面。在这里,我们使用 fixed-height 布局,并在 HTML 属性中指定 height。有关详细信息,请参阅文档

页面加载时的时间是:
<amp-script layout="fixed-height" height="36" script="time-script" class="sample">
  <div>
    The time at page load was: <span id="time" class="answer-text"></span>
  </div>
</amp-script>

<script id="time-script" type="text/plain" target="amp-script">
  const fetchCurrentTime = async () => {
    const response = await fetch('https://amp.org.cn/documentation/examples/api/time');
    const data = await response.json();
    const span = document.getElementById('time');
    span.textContent = data.time;
  }

  fetchCurrentTime();
</script>
在 Playground 中打开此代码片段

多次获取

在大小可以更改的容器中,您的代码可以在最终 fetch() 完成后的 5 秒内进行 DOM 更改。此示例对一个慢速 API 进行多次调用。它在每次调用返回时显示结果。

<amp-script layout="container" script="multi-fetch-script" class="sample">
  <button id="multi-fetch">How slow is our API?</button>
</amp-script>
<script id="multi-fetch-script" type="text/plain" target="amp-script">
  const randomTime = () =>  Math.floor(Math.random() * 10) + 5;

  const button = document.getElementById('multi-fetch');

  function tripleFetch() {
    for (let i =0; i < 3; i++) {
      fetch('https://amp.org.cn/documentation/examples/api/slow-text?delay=' + randomTime())
        .then(response => response.text())
        .then(insertText);
    }
  }

  function insertText(text) {
    const div = document.createElement('div');
    div.textContent = text;
    document.body.appendChild(div);
  }

  button.addEventListener('click', tripleFetch);
</script>
在 Playground 中打开此代码片段

amp-list 的数据源

<amp-script> 函数可以用作 <amp-list> 的数据源。

  • 使用 exportFunction() 使该函数对 <amp-list> 可见。

  • <amp-list>src 属性中指定脚本和函数。使用 src="amp-script:{scriptID}:functionName" 的形式,其中 {scriptID}<amp-script>id,而 {functionName} 是导出的函数的名称。

  • 您可以在 <amp-script> 中使用 nodom 属性来指示 <amp-script> 不需要 DOM。这可以提高性能,因为 amp-script 不需要加载或执行其虚拟 DOM 实现。

<div class="sample">
  <amp-script id="dataFunctions" script="amp-list-source-script" nodom></amp-script>
  <script id="amp-list-source-script" type="text/plain" target="amp-script">
    function fetchData() {
      return fetch('https://amp.org.cn/static/samples/json/todo.json')
        .then(response => response.json())
        .then(transformData);
    }

    function transformData(json) {
      let newEntries =
        json.items.map(
          entry => (entry.done ? 'Already done: ' : 'To do: ') + entry.title
        );
      return { items: newEntries };
    }

    exportFunction('fetchData', fetchData);
  </script>

  <amp-list width="auto" height="70" layout="fixed-height" src="amp-script:dataFunctions.fetchData">
    <template type="amp-mustache">
      <div>{{.}}</div>
    </template>
  </amp-list>
</div>
在 Playground 中打开此代码片段

使用 WebSocket 进行实时更新

amp-script 支持 WebSockets。此示例模拟了一个实时博客。

<amp-script layout="fixed-height" height="200" script="live-blog-script" class="sample" sandbox="allow-forms">
  <button id="live-blog-start">Start live blog</button>
  <div id="live-blog-area"></div>
</amp-script>

<script id="live-blog-script" type="text/plain" target="amp-script">
  const button = document.getElementById('live-blog-start');
  const blogDiv = document.getElementById('live-blog-area');

  button.addEventListener("click", () => {
    button.setAttribute('disabled', '');
    button.textContent = 'Live blog begun';
    const socket = new WebSocket('wss://amp.org.cn/documentation/examples/api/socket/live-blog');

    socket.onmessage = event => {
      let newDiv = document.createElement('div');
      let time = new Date().toLocaleTimeString();
      newDiv.innerHTML = `<span class="time">${time}: </span><span>${event.data}</span>`;
      blogDiv.appendChild(newDiv);
    };
  });
</script>
在 Playground 中打开此代码片段

显示实时数据

您还可以使用 setInterval()setTimeout 获取最新数据。

当前时间是:
<amp-script layout="fixed-height" height="36" script="live-time-script" class="sample">
  <div>
    The current time is: <span id="live-time" class="answer-text"></span>
  </div>
</amp-script>

<script id="live-time-script" type="text/plain" target="amp-script">
  const span = document.getElementById('live-time');

  const fetchCurrentTime = async () => {
    const response = await fetch('https://amp.org.cn/documentation/examples/api/time');
    const data = await response.json();
    span.textContent = data.time;
  }

  setInterval(fetchCurrentTime, 1000);
</script>
在 Playground 中打开此代码片段

自定义表单验证

您还可以使用 amp-script 实现自定义表单验证。当输入字段仅包含大写字母时,此脚本会启用按钮。

<amp-script layout="container" script="form-validation-script" sandbox="allow-forms" class="sample">
  <input id="validated-input" placeholder="Only uppercase letters allowed...">
  <button id="validated-input-submit" disabled>Submit</button>
</amp-script>

<script id="form-validation-script" type="text/plain" target="amp-script">
  const submitButton = document.getElementById('validated-input-submit');
  const validatedInput = document.getElementById('validated-input');

  function allUpper() {
    let isValid = /^[A-Z]+$/.test(validatedInput.value);

    if (isValid) {
      submitButton.removeAttribute('disabled');
    } else {
      submitButton.setAttribute('disabled', '');
    }
  }

  validatedInput.addEventListener('input', allUpper);
</script>
在 Playground 中打开此代码片段

检测操作系统

您的脚本可以访问全局对象,如 navigator。此脚本使用此对象尝试猜测您设备的操作系统。

您的操作系统是:
<amp-script layout="fixed-height" height="36" script="user-agent-script" class="sample">
  <div>
    Your operating system is:
    <span id="operating-system" class="answer-text"></span>
  </div>
</amp-script>

<script id="user-agent-script" type="text/plain" target="amp-script">
  // Adapted with gratitude from https://stackoverflow.com/a/38241481

  function getOS() {
    const userAgent = navigator.userAgent,
          platform = navigator.platform,
          macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'],
          windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'],
          iosPlatforms = ['iPhone', 'iPad', 'iPod'];

    if (macosPlatforms.includes(platform)) {
      return 'Mac OS';
    } else if (iosPlatforms.includes(platform)) {
      return 'iOS';
    } else if (windowsPlatforms.includes(platform)) {
      return 'Windows';
    } else if (/Android/.test(userAgent)) {
      return 'Android';
    } else if (/Linux/.test(platform)) {
      return 'Linux';
    }

    return 'Unknown';
  }

  const span = document.getElementById('operating-system');
  span.textContent = getOS();
</script>
在 Playground 中打开此代码片段

个性化

同样,您可以使用 navigator 对象或其他方式来为您的用户个性化内容。以下脚本检测浏览器的语言并显示本地化的问候语。

<amp-script layout="fixed-height" height="40" script="translation-script" class="sample">
  <h2 id="translated-greeting"></h2>
</amp-script>

<script id="translation-script" type="text/plain" target="amp-script">
  const translationMap = {
    'en': 'Hello',
    'fr': 'Bonjour',
    'es': 'Hola',
    'hi': 'हैलो',
    'zh': '你好',
    'pr': 'Olá'
  };

  const lang = navigator.language.slice(0, 2);
  let translation = translationMap[lang];
  if (!translation) {
    translation = "Couldn't recognize your language. So: Saluton";
  }

  let greeting = document.getElementById('translated-greeting');
  greeting.innerHTML = translation + '!';
</script>
在 Playground 中打开此代码片段

<amp-state> 交互

您的脚本可以使用状态变量和绑定来影响 <amp-script> 组件外部的区域。在这里,当单击按钮时,我们将状态变量的值设置为图像 URL。该状态变量绑定到 <amp-img>src 属性。

<amp-state id="imgSrc">
  <script type="application/json">
    "product1_640x426.jpg"
  </script>
</amp-state>
<amp-img layout="responsive" height="426" width="640" src="https://amp.org.cn/static/samples/img/product1_640x426.jpg" [src]="'https://amp.org.cn/static/samples/img/' + imgSrc"></amp-img>
<amp-script layout="container" script="state-script" class="sample">
  <button id="apple-button" class="fruit-button">Apple</button>
  <button id="orange-button" class="fruit-button">Orange</button>
</amp-script>

<script id="state-script" type="text/plain" target="amp-script">
  const appleButton = document.getElementById('apple-button');
  const orangeButton = document.getElementById('orange-button');

  appleButton.addEventListener(
    'click',
    () => AMP.setState({imgSrc: 'product1_640x426.jpg'})
  );

  orangeButton.addEventListener(
    'click',
    () => AMP.setState({imgSrc: 'product2_640x426.jpg'})
  );
</script>
在 Playground 中打开此代码片段

与 AMP 组件交互

您的脚本可以使用状态变量和绑定来与 AMP 组件通信。将组件中的属性绑定到包含状态变量的表达式。当您的脚本修改该状态变量时,更改将传播到该组件。同样,如果 AMP 组件更改了状态变量的值,您的脚本可以获取新值。此脚本为一个按钮提供动力,该按钮以随机方向发送图像轮播。

<div id="carousel-sample">
  <amp-carousel type="slides" layout="responsive" width="450" height="300" controls loop [slide]="slideIndex" on="slideChange: AMP.setState({slideIndex: event.index})">
    <amp-img src="https://amp.org.cn/static/inline-examples/images/image1.jpg" layout="responsive" width="450" height="300"></amp-img>
    <amp-img src="https://amp.org.cn/static/inline-examples/images/image2.jpg" layout="responsive" width="450" height="300"></amp-img>
    <amp-img src="https://amp.org.cn/static/inline-examples/images/image3.jpg" layout="responsive" width="450" height="300"></amp-img>
  </amp-carousel>
  <p>Slide <span [text]="slideIndex + 1">1</span> of 3</p>

  <amp-script layout="container" script="carousel-script" class="sample">
    <button id="carousel-button" class="fruit-button">
      Random direction
    </button>
  </amp-script>

  <script id="carousel-script" type="text/plain" target="amp-script">
    const button = document.getElementById('carousel-button');

    async function randomSlideDirection() {
      let oldSlide = Number(await AMP.getState('slideIndex'));
      let addend = Math.ceil(Math.random() * 2);
      let newSlide = (oldSlide + addend) % 3;
      AMP.setState({slideIndex: newSlide});
    }

    button.addEventListener('click', randomSlideDirection);
  </script>
</div>
在 Playground 中打开此代码片段

本地存储

您的脚本可以访问 Web Storage API。这使您可以在浏览器会话之间持久保存用户信息。在这里,我们使用 localStorage 来跟踪用户名。如果您设置了用户名,然后重新加载此页面,则用户名将保持设置状态。

当前用户名:
<amp-script layout="fixed-height" script="local-storage-script" height="110" class="sample">
  <div>
    <span>Current username: </span>
    <b id="username-display"></b>
  </div>
  <div>
    <label>Enter new username:</label>
    <input id="username-input" type="text" maxlength="20">
  </div>
  <button id="username-submit">Submit</button>
</amp-script>
<script id="local-storage-script" type="text/plain" target="amp-script">
  const submit = document.getElementById('username-submit');
  const input = document.getElementById('username-input');
  const display = document.getElementById('username-display');

  const oldUsername = localStorage.getItem('username');
  display.textContent = oldUsername ? oldUsername : '(not set)';

  function setUsername() {
    const newUsername = input.value;
    localStorage.setItem('username', newUsername);
    display.textContent = newUsername;
  }

  submit.addEventListener('click', setUsername);
</script>
在 Playground 中打开此代码片段
需要进一步解释吗?

如果此页面上的说明没有涵盖您的所有问题,请随时与其他 AMP 用户联系以讨论您的确切用例。

转到 Stack Overflow
未解释的功能?

AMP 项目强烈鼓励您的参与和贡献!我们希望您成为我们开源社区的持续参与者,但我们也欢迎您为特别感兴趣的问题做出一次性贡献。

在 GitHub 上编辑示例