Interaktivitas Lanjutan di AMP

Accelerated Mobile Pages (AMP) adalah inisiatif open source yang memungkinkan pembuatan situs dan iklan secara cepat, indah, dan berperforma tinggi.

Jika Anda baru mengenal AMP, sebaiknya lihat ringkasan singkat dengan referensi berikut:

Motivasi

AMP mendukung konten dinamis dan kaya dengan komponen UI seperti carousel gambar dan Lightbox. AMP juga mendukung beberapa cara sederhana untuk satu komponen untuk memicu tindakan di komponen lain melalui Tindakan AMP.

Namun, bagaimana jika saya ingin:

  • Sesuaikan komponen AMP?
  • Misalnya, tampilkan label kustom yang menampilkan slide saat ini dan jumlah total slide dalam carousel gambar.
  • Tambahkan perilaku stateful?
  • Misalnya, nonaktifkan tombol "Tambahkan ke keranjang" jika jumlah produk yang dipilih pengguna melebihi ketersediaannya saat ini.

Sebelumnya, menerapkan fitur seperti itu sulit dilakukan di AMP karena kurangnya saluran komunikasi yang kuat antara komponen UI dan ketidakmampuan untuk memiliki status bersama yang dapat berubah. Kami telah membuat komponen baru yang canggih di AMP untuk menyelesaikan kasus penggunaan ini.

<amp-bind>

<amp-bind> adalah komponen AMP baru yang menawarkan interaktivitas kustom melalui data binding dan ekspresi yang mirip dengan JS. Codelab ini akan memandu Anda dalam menggunakan <amp-bind> untuk membuat halaman AMP dengan interaktivitas kustom yang kaya.

Yang akan Anda buat

Dalam codelab ini, Anda akan membuat halaman detail produk e-commerce:

  • Menggunakan komponen AMP dan HTML AMP untuk membuat halaman web yang memberikan pengalaman pengguna yang lengkap dan cepat
  • Menggunakan <amp-bind> untuk menambahkan interaktivitas lintas elemen
  • Gunakan <amp-state> untuk mengambil data produk tambahan on demand

Yang akan Anda pelajari

  • Cara menggunakan data binding dan ekspresi untuk membuat halaman AMP yang menarik dan interaktif dengan <amp-bind>.

Yang Anda butuhkan

  • Browser pilihan Anda
  • Editor teks pilihan Anda
  • Node.js dan NPM
  • Kode contoh
  • Pengetahuan dasar tentang HTML, CSS, dan JavaScript

Mendownload kode

Pertama-tama, download kode awal untuk codelab sebagai file ZIP:

Download

Atau melalui git:

git clone https://github.com/googlecodelabs/advanced-interactivity-in-amp.git

Menginstal dependensi

Ekstrak file arsip (jika perlu) lalu buka direktori. Instal dependensi dengan menjalankan npm install.

cd advanced-interactivity-in-amp-codelab
npm install

Menjalankan server pengembangan

Mulai server pengembangan dengan node.js:

node app.js

Buka http://localhost:3000 di browser web Anda untuk melihat halaman AMP yang berjalan.

Boilerplate AMP

Halaman AMP adalah halaman HTML yang memiliki beberapa batasan untuk memberikan performa yang andal. Halaman AMP memiliki sedikit markup khusus yang mengidentifikasinya sebagai halaman AMP untuk Google Penelusuran.

Halaman AMP barebone akan terlihat seperti ini:

<!doctype html>
<html amp>
 <head>
   <meta charset="utf-8">
   <link rel="canonical" href="hello-world.html">
   <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
   <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
   <script async src="https://cdn.ampproject.org/v0.js"></script>
 </head>
 <body>Hello World!</body>
</html>

Komponen AMP

Kode awal kami (static/index.html) dibuat dari halaman AMP barebone dengan konten halamannya (gambar, teks, dll.) serta penyertaan beberapa komponen AMP:

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

Komponen AMP menawarkan komponen UI dan fungsi tambahan yang menambahkan interaktivitas yang kaya ke halaman AMP. Kode pembuka menggunakan komponen AMP berikut:

  • <amp-carousel>
  • Carousel gambar yang menampilkan beberapa tampilan produk.
  • <amp-mustache>
  • Sistem pembuatan template untuk merender respons server dari formulir amp.
  • <amp-form>
  • Menambahkan fungsi khusus untuk elemen <form> yang diperlukan untuk halaman AMP.
  • <amp-selector>
  • Menawarkan cara semantik untuk memilih satu atau beberapa elemen dari sekelompok elemen. Dapat digunakan sebagai sumber masukan untuk amp-form.

Interaktivitas dasar

Kode pembuka menawarkan beberapa interaktivitas dasar:

  • Carousel gambar (<amp-carousel>) menampilkan beberapa tampilan produk.
  • Produk tersebut dapat ditambahkan ke keranjang pengguna (melalui <amp-form>) dengan mengetuk tombol "Tambahkan ke keranjang" di bagian bawah halaman.

Coba geser carousel gambar, lalu ketuk tombol "Tambahkan ke keranjang".

Meningkatkan pengalaman

Kode pembuka memberikan pengalaman pengguna yang cukup sederhana. Ada beberapa cara yang dapat kita lakukan untuk meningkatkannya:

  • Menambahkan indikator yang menampilkan slide saat ini dan jumlah total slide.
  • Jika pengguna memilih warna kemeja yang berbeda, ubah carousel gambar untuk menampilkan gambar kemeja dengan warna yang dipilih.

Sebelum adanya komponen <amp-bind>, penambahan fitur seperti ini tidak mungkin dilakukan. Mari kita dapatkan pengalaman langsung dengan <amp-bind> dan tambahkan fitur baru ini ke kode contoh kami.

Menginstal ekstensi <amp-bind>

<amp-bind> adalah komponen AMP baru yang menyediakan interaktivitas kustom melalui data binding dan ekspresi yang mirip dengan JS. Untuk menggunakan <amp-bind>, Anda harus menginstalnya di halaman.

Buka file static/index.html dan tambahkan skrip berikut ke daftar komponen AMP di bagian <head> halaman:

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

Menambahkan indikator slide

<amp-bind> berfungsi dengan mengikat atribut elemen ke ekspresi kustom. Ekspresi ini dapat mereferensikan "state" (data JSON yang dapat diubah). Kita dapat melakukan inisialisasi status ini melalui komponen <amp-state> yang disertakan dengan <amp-bind>.

Lakukan inisialiasi variabel status untuk melacak indeks dari slide yang ditampilkan saat ini di carousel gambar. Buka static/index.html dan tambahkan kode berikut ke bagian atas <body> halaman (sebelum header):

<amp-state id="selected">
  <script type="application/json">
    {
      "slide": 0
    }
  </script>
</amp-state>

Data dalam elemen <amp-state> dapat diakses dengan ID terkaitnya. Misalnya, kita dapat merujuk ke variabel ini berdasarkan fragmen ekspresi berikut:

selected.slide // Evaluates to 0.

Berikutnya, mari kita perbarui variabel ini saat pengguna mengubah slide di carousel dengan menambahkan tindakan "on" ke elemen <amp-carousel> yang ada:

<amp-carousel type="slides" layout="fixed-height" height=250 id="carousel"
    on="slideChange:AMP.setState({selected: {slide: event.index}})">

Sekarang, setiap kali slide <amp-carousel> yang ditampilkan berubah, tindakan AMP.setState akan dipanggil dengan argumen berikut:

{
  selected: {
    slide: event.index
  }
}

Ekspresi event.index mengevaluasi indeks slide baru, dan tindakan AMP.setState() menggabungkan objek literal ini ke dalam status saat ini. Ini menggantikan nilai selected.slide saat ini dengan nilai event.index.

Berikutnya, gunakan variabel status ini yang melacak slide yang saat ini ditampilkan, dan buat indikator slide. Temukan elemen indikator slide (cari <!-- TODO: "Add a slide indicator" -->) dan tambahkan binding berikut ke turunannya:

<!-- TODO: "Add a slide indicator" -->
<p class="dots">
  <!-- The <span> element corresponding to the current displayed slide
       will have the 'current' CSS class. -->
  <span [class]="selected.slide == 0 ? 'current' : ''" class="current"></span>
  <span [class]="selected.slide == 1 ? 'current' : ''"></span>
  <span [class]="selected.slide == 2 ? 'current' : ''"></span>
</p>

[class] adalah binding yang mengubah atribut class dan Anda dapat menggunakannya untuk menambahkan atau menghapus class CSS dari elemen apa pun.

Sekarang, muat ulang halaman dan coba. Dengan mengubah slide di carousel, perubahan ini:

  1. Memicu peristiwa slideChange...
  2. Yang memanggil tindakan AMP.setState...
  3. Yang mengupdate variabel status selected.slide ...
  4. yang mengupdate binding [class] pada elemen <span> indikator.

Bagus! Sekarang kita memiliki indikator slide yang dapat dijalankan.

Sangatlah bagus jika kita dapat melihat berbagai warna kemeja saat mengubah warna yang dipilih. Dengan amp-bind, kita dapat melakukannya dengan mengikat [src] pada elemen <amp-img> dalam <amp-carousel>.

Namun, pertama-tama, kita perlu melakukan inisialisasi data status dengan URL sumber gambar dari setiap warna kemeja. Mari kita lakukan ini dengan elemen <amp-state> baru:

<!-- Available shirts. Maps unique string identifier to color and image URL string. -->
<amp-state id="shirts">
  <script type="application/json">
    {
      "1001": {
        "color": "black",
        "image": "./shirts/black.jpg"
      },
      "1002": {
        "color": "blue",
        "image": "./shirts/blue.jpg"
      },
      "1010": {
        "color": "brown",
        "image": "./shirts/brown.jpg"
      },
      "1014": {
        "color": "dark green",
        "image": "./shirts/dark-green.jpg"
      },
      "1015": {
        "color": "gray",
        "image": "./shirts/gray.jpg"
      },
      "1016": {
        "color": "light gray",
        "image": "./shirts/light-gray.jpg"
      },
      "1021": {
        "color": "navy",
        "image": "./shirts/navy.jpg"
      },
      "1030": {
        "color": "wine",
        "image": "./shirts/wine.jpg"
      }
    }
  </script>
</amp-state>

Elemen <amp-state> ini berisi objek JSON yang memetakan string ID kemeja (yaitu, SKU) ke URL warna dan gambar kemeja yang sesuai. Array JSON juga akan berfungsi di sini, namun penggunaan objek memungkinkan kita melakukan beberapa penyempurnaan yang menarik, yang akan segera Anda lihat.

Kini kami dapat mengakses URL gambar melalui ID kaus. Misalnya, shirts['10014'].color mengevaluasi ke "dark green" dan shirts['10030'].image menampilkan URL gambar untuk warna kemeja "wine".

Jika menambahkan variabel status lain yang melacak SKU yang dipilih, kita dapat mengikat ekspresi ke elemen <amp-img> untuk memperbarui atribut src ketika SKU yang dipilih berubah. Tambahkan kunci sku baru ke elemen amp-state#selected yang ada:

<amp-state id="selected">
  <script type="application/json">
    {
      "slide": 0,
      "sku": "1001"
    }
  </script>
</amp-state>

Tambahkan tindakan "on" ke <amp-selector> yang memperbarui variabel selected.sku setiap kali warna baru dipilih:

<amp-selector name="color" 
    on="select:AMP.setState({selected: {sku: event.targetOption}})">

Kemudian, tambahkan binding ke elemen <amp-img> di dalam <amp-carousel> (cari <!-- TODO: "Changing images in amp-carousel-->"):

<!-- Update the `src` of each <amp-img> when the `selected.sku` variable changes. -->
<amp-img width=200 height=250 src="./shirts/black.jpg"
    [src]="shirts[selected.sku].image"></amp-img>
<amp-img width=300 height=375 src="./shirts/black.jpg"
    [src]="shirts[selected.sku].image"></amp-img>
<amp-img width=400 height=500 src="./shirts/black.jpg"
    [src]="shirts[selected.sku].image"></amp-img>

Catatan: Dalam praktiknya, setiap gambar di carousel kemungkinan memiliki src yang berbeda. Hal ini dapat dilakukan dengan mengganti 1 gambar dengan deretan gambar. Demi kemudahan, codelab ini menggunakan satu gambar dengan berbagai pembesaran.

Sekarang, muat ulang halaman dan pilih warna kemeja yang berbeda. Saat Anda melakukannya, gambar carousel akan diupdate untuk menampilkan kemeja dengan warna yang dipilih.

Bagaimana jika data Anda yang dapat diikat terlalu besar atau kompleks untuk diambil saat pemuatan halaman? Atau, bagaimana jika setiap SKU memiliki harga yang butuh waktu lama untuk dicari? Mencari harga SKU untuk item yang tidak dilihat adalah pekerjaan yang sia-sia.

Mengambil ukuran kemeja yang tersedia

Mari kita manfaatkan kemampuan pengambilan data jarak jauh untuk mencari harga SKU di sampel codelab kami. Server pengembangan Express.js kami di app.js sudah memiliki endpoint /shirts/sizes?shirt=<sku> yang, jika diberi SKU kaus, menampilkan ukuran dan harga yang tersedia untuk setiap ukuran. Server pengembangan ini mengirimkan respons dengan penundaan buatan selama 1 detik untuk menyimulasikan latensi jaringan.

Permintaan

Tanggapan

GET /shirts/sizesAndPrices?sku=1001

{"1001: {"sizes": {"XS": 8.99, "S" 9.99}}}

Serupa dengan data JSON dalam elemen <amp-state>, data jarak jauh yang ditampilkan dari pengambilan ini digabungkan menjadi dan tersedia pada atribut id elemen. Misalnya, data yang ditampilkan dari contoh respons di atas dapat diakses di ekspresi:

Ekspresi

Hasil

shirts['1001'].sizes['XS']

8.99

Sekarang, mari kita terapkan hal ini ke contoh e-commerce. Pertama-tama, ambil data kemeja ini ketika SKU baru dipilih. Tambahkan binding [src] ke elemen amp-state#shirts kita:

<!-- When `selected.sku` changes, update the `src` attribute and fetch
     JSON at the new URL. Then, merge that data under `id` ("shirts"). -->
<amp-state id="shirts" [src]="'/shirts/sizesAndPrices?sku=' + selected.sku">

Berikutnya, tandai dengan jelas ukuran yang tidak tersedia untuk SKU tertentu. Class CSS "unavailable" menambahkan garis diagonal melalui elemen -- kita dapat menambahkannya ke elemen dalam amp-selector[name="size"] sesuai dengan ukuran yang tidak tersedia:

<amp-selector name="size">
  <table>
    <tr>
      <!-- If 'XS' size is available for selected SKU, return empty string.
           Otherwise, return 'unavailable'. -->
      <td [class]="shirts[selected.sku].sizes['XS'] ? '' : 'unavailable'">
        <div option="XS">XS</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['S'] ? '' : 'unavailable'">
        <div option="S">S</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['M'] ? '' : 'unavailable'">
        <div option="M">M</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['L'] ? '' : 'unavailable'">
        <div option="L">L</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['XL'] ? '' : 'unavailable'">
        <div option="XL">XL</div>
      </td>
    </tr>
  </table>
</amp-selector>

Sekarang muat ulang halaman dan cobalah. Memilih SKU baru (warna kemeja) akan menyebabkan ukuran yang tidak tersedia dicoret (setelah penundaan singkat).

Namun ada sedikit masalah -- bagaimana dengan kemeja hitam, yaitu warna default yang dipilih? Kita perlu menambahkan data ukuran dan harga kemeja hitam ke amp-state#shirts...

<amp-state id="shirts" [src]="'/shirts/sizesAndPrices?sku=' + selected.sku">
  <script type="application/json">
    {
      "1001": {
        "color": "black",
        "image": "./shirts/black.jpg",
        "sizes": {
          "XS": 8.99,
          "S": 9.99
        }
      },
<!-- ... -->

...serta memperbarui status default elemen yang relevan.

<amp-selector name="size">
  <table>
    <tr>
      <!-- If 'XS' size is available for selected SKU, return empty string.
           Otherwise, return 'unavailable'. -->
      <td [class]="shirts[selected.sku].sizes['XS'] ? '' : 'unavailable'">
        <div option="XS">XS</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['S'] ? '' : 'unavailable'">
        <div option="S">S</div>
      </td>
      <!-- Add the ‘unavailable' class to the next three <td> elements
           to be consistent with the available sizes of the default SKU. -->
      <td class="unavailable" 
          [class]="shirts[selected.sku].sizes['M'] ? '' : 'unavailable'">
        <div option="M">M</div>
      </td>
      <td class="unavailable" 
          [class]="shirts[selected.sku].sizes['L'] ? '' : 'unavailable'">
        <div option="L">L</div>
      </td>
      <td class="unavailable" 
          [class]="shirts[selected.sku].sizes['XL'] ? '' : 'unavailable'">
        <div option="XL">XL</div>
      </td>
    </tr>
  </table>
</amp-selector>

Harga kemeja variabel

Setelah kita menampilkan ukuran yang tersedia dengan benar, mari kita pastikan harga yang benar juga ditampilkan.

Toko AMPPAREL kita tidak biasa karena harga kemeja berbeda berdasarkan warna maupun ukuran. Artinya kita perlu variabel baru untuk melacak ukuran yang dipilih pengguna. Menambahkan tindakan baru ke elemen <amp-selector> ukuran kita:

<!-- When an element is selected, set the `selectedSize` variable to the
     value of the "option" attribute of the selected element.  -->
<amp-selector name="size" 
    on="select:AMP.setState({selectedSize: event.targetOption})">

Perlu diketahui bahwa kita tidak menginisialisasi nilai selectedSize melalui elemen amp-state#selected. Hal ini karena kita sengaja tidak memberikan ukuran default yang dipilih, dan ingin mendorong pengguna untuk memilih ukuran.

Tambahkan elemen <span> baru yang menggabungkan label harga dan ubah teks default menjadi "---" karena tidak ada pilihan ukuran default.

<h6>PRICE :
  <!-- Display the price of the selected shirt in the selected size if available.
       Otherwise, display the placeholder text '---'. -->
  <span [text]="shirts[selected.sku].sizes[selectedSize] || '---'">---</span>
</h6>

Dan kita memiliki harga yang benar! Cobalah.

Tombol yang diaktifkan secara bersyarat

Kita hampir selesai. Sekarang mari kita nonaktifkan tombol "Tambahkan ke troli" saat ukuran yang dipilih tidak tersedia:

<!-- Disable the "ADD TO CART" button when:
     1. There is no selected size, OR
     2. The available sizes for the selected SKU haven't been fetched yet
-->
<input type="submit" value="ADD TO CART" disabled
    class="mdl-button mdl-button--raised mdl-button--accent"
    [disabled]="!selectedSize || !shirts[selected.sku].sizes[selectedSize]">

Kami memiliki halaman detail produk e-commerce interaktif dengan ukuran variabel dan harga untuk setiap SKU, yang diambil on demand dari endpoint JSON jarak jauh.

Jika Anda mengalami kesulitan, lihat static/final.html untuk mendapatkan solusi lengkap.

Kami harap codelab ini menunjukkan kecanggihan dan fleksibilitas dalam membuat halaman AMP interaktif dengan <amp-bind>. Untuk informasi selengkapnya, lihat <amp-bind> dokumentasi.

Kami senang menerima masukan -- harap kirimkan permintaan fitur, saran, atau laporan bug Anda kepada kami. Jika Anda tertarik untuk menguji <amp-bind> dengan pengguna sungguhan, sebaiknya ajukan uji coba awal.

Kami sangat senang dapat meluncurkan <amp-bind> segera dan melihat apa yang dapat Anda build dengannya!