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

從網址載入指令碼

若要從網址載入指令碼,請使用 src 屬性。此範例會載入並執行名為 hello.js 的指令碼。有效的 AMP 要求所有網址都必須是絕對網址,並使用 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.dev.org.tw/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.dev.org.tw/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.dev.org.tw/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.dev.org.tw/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 支援 WebSocket。此範例模擬即時部落格。

<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.dev.org.tw/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.dev.org.tw/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> 元件外部的區域。在這裡,當按鈕被點擊時,我們會將狀態變數的值設定為圖片網址。該狀態變數會繫結至 <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.dev.org.tw/static/samples/img/product1_640x426.jpg" [src]="'https://amp.dev.org.tw/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.dev.org.tw/static/inline-examples/images/image1.jpg" layout="responsive" width="450" height="300"></amp-img>
    <amp-img src="https://amp.dev.org.tw/static/inline-examples/images/image2.jpg" layout="responsive" width="450" height="300"></amp-img>
    <amp-img src="https://amp.dev.org.tw/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 上編輯範例