Interactividad avanzada en AMP

Accelerated Mobile Pages (AMP) es una iniciativa de código abierto que permite crear sitios web y anuncios rápidos, atractivos y de alto rendimiento.

Si es la primera vez que usas AMP, te brindamos una descripción general con los siguientes recursos:

Motivación

AMP admite contenido enriquecido y dinámico con componentes de la IU, como carruseles de imágenes y lightboxes. AMP también admite algunas maneras simples en que un componente puede activar una acción en otro mediante acciones de AMP.

Sin embargo, ¿qué sucede si quiero hacer lo siguiente?

  • ¿Quieres personalizar un componente de AMP?
  • Por ejemplo, muestra una etiqueta personalizada que muestra la diapositiva actual y la cantidad total de diapositivas en un carrusel de imágenes.
  • ¿Deseas agregar comportamientos con estado?
  • Por ejemplo, inhabilita el botón “Agregar al carrito” si la cantidad de productos seleccionados por el usuario supera su disponibilidad actual.

Anteriormente, implementar estas funciones era difícil en AMP debido a la falta de un canal de comunicación sólido entre los componentes de la IU y la incapacidad de tener un estado compartido mutable. Creamos un nuevo componente potente en AMP para resolver estos casos de uso.

<amp-bind>

<amp-bind> es un nuevo componente de AMP que ofrece interactividad personalizada a través de la vinculación de datos y expresiones similares a JS. En este codelab, te guiaremos por el uso de <amp-bind> para compilar una página de AMP con interactividad personalizada y enriquecida.

Qué compilarás

En este codelab, crearás una página de detalles del producto de comercio electrónico:

  • Usa HTML de AMP y componentes de AMP para crear una página web con una experiencia del usuario rápida y enriquecida
  • Usa <amp-bind> para agregar interactividad entre elementos
  • Use <amp-state> para recuperar datos de productos adicionales a pedido

Qué aprenderás

  • Cómo usar expresiones y vinculación de datos para compilar increíbles páginas interactivas de AMP con <amp-bind>

Requisitos

  • Navegador que elijas
  • Editor de texto que prefieras
  • Node.js y NPM
  • El código de muestra
  • Conocimientos básicos de HTML, CSS y JavaScript

Descarga el código

Primero, obtén el código de inicio para el codelab, como un archivo ZIP:

Descargar

O bien, mediante git:

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

Instala dependencias

Descomprime el archivo (si es necesario) y navega hasta el directorio. Ejecuta npm install para instalar las dependencias.

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

Ejecute el servidor de desarrollo

Inicia el servidor de desarrollo con node.js:

node app.js

Navega a http://localhost:3000 en tu navegador web para ver la página de AMP en ejecución.

Plantilla de AMP

Una página de AMP es una página HTML con algunas restricciones para un rendimiento confiable. Las páginas de AMP tienen un lenguaje de marcado especial que lo identifican como una página de AMP en la Búsqueda de Google.

Una página de AMP básica se ve de la siguiente manera:

<!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>

Componentes de AMP

Nuestro código de inicio (static/index.html) se basa en la página de AMP básica con su contenido de página (imágenes, texto, etc.) y algunos componentes de 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>

Los componentes de AMP ofrecen funciones adicionales y componentes de la IU que agregan interactividad enriquecida a las páginas de AMP. El código de inicio usa los siguientes componentes de AMP:

  • <amp-carousel>
  • Un carrusel de imágenes que muestra varias vistas del producto
  • <amp-mustache>
  • Un sistema de plantillas para renderizar las respuestas del servidor desde un formato de AMP
  • <amp-form>
  • Agrega una funcionalidad especial para los elementos <form> que son necesarios para las páginas de AMP.
  • <amp-selector>
  • Ofrece una manera semántica para seleccionar uno o más elementos de un grupo de elementos. Se puede usar como una fuente de entrada para AMP.

Interactividad básica

El código de inicio ofrece cierta interactividad básica:

  • El carrusel de imágenes (un <amp-carousel>) muestra varias vistas del producto.
  • Para agregar el producto al carrito del usuario (mediante <amp-form>), presiona el botón Agregar al carrito en la parte inferior de la página.

Intenta deslizar el carrusel de imágenes y presionar el botón "Agregar al carrito".

Mejora la experiencia

El código de inicio proporciona una experiencia del usuario bastante simple. Hay diferentes formas de mejorarlo:

  • Agrega un indicador que muestre la diapositiva actual y la cantidad total de diapositivas.
  • Cuando un usuario seleccione un color de camisa diferente, cambie el carrusel de imágenes para mostrar imágenes de camisas en el color seleccionado.

Antes de la introducción del componente <amp-bind>, no era posible agregar funciones como estas. Obtén una experiencia práctica con <amp-bind> y agrega estas funciones nuevas a nuestro código de muestra.

Instala la extensión <amp-bind>

<amp-bind> es un nuevo componente de AMP que ofrece interactividad personalizada a través de la vinculación de datos y expresiones similares a JS. Para usar <amp-bind>, debes instalarlo en la página.

Abre el archivo static/index.html y agrega la siguiente secuencia de comandos a la lista de componentes de AMP en la sección <head> de la página:

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

Cómo agregar un indicador de diapositiva

<amp-bind> funciona mediante la vinculación de atributos de elementos con expresiones personalizadas. Estas expresiones pueden hacer referencia al "estado" (datos JSON mutables). Podemos inicializar este estado mediante el componente <amp-state> incluido con <amp-bind>.

Iniciemos una variable de estado para hacer un seguimiento del índice de la diapositiva que se muestra actualmente en el carrusel de imágenes. Abre static/index.html y agrega lo siguiente en la parte superior de la página <body> (antes del encabezado):

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

Se puede acceder a los datos dentro de los elementos <amp-state> por su ID asociado. Por ejemplo, podemos referirnos a esta variable por el siguiente fragmento de expresión:

selected.slide // Evaluates to 0.

A continuación, actualizaremos esta variable cuando el usuario cambie las diapositivas del carrusel. Para ello, agregarámos una acción al elemento existente:

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

Cuando la diapositiva que se muestra <amp-carousel> cambie, se llamará a la acción AMP.setState con el siguiente argumento:

{
  selected: {
    slide: event.index
  }
}

La expresión event.index evalúa el índice de diapositivas nuevo, y la acción AMP.setState() combina este literal de objeto en el estado actual. Esto reemplaza el valor actual de selected.slide con el valor de event.index.

A continuación, usaremos esta variable de estado que realiza un seguimiento de la diapositiva que se muestra actualmente y creará un indicador de diapositiva. Busca el elemento indicador de diapositiva (busca <!-- TODO: "Add a slide indicator" -->) y agrega las siguientes vinculaciones a sus elementos secundarios:

<!-- 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] es una vinculación que cambia el atributo class, y puedes usarla para agregar o quitar clases de CSS de cualquier elemento.

Ahora, actualiza la página y pruébala. Si cambias la diapositiva en el carrusel, sucederá lo siguiente:

  1. Activa el evento slideChange...
  2. ¿Qué llama a la acción AMP.setState?
  3. que actualiza la variable de estado selected.slide...
  4. Esto actualiza la vinculación [class] en los elementos <span> del indicador.

¡Muy bien! Ahora tenemos un indicador de diapositiva que funciona.

Sería bueno poder ver imágenes de diferentes colores de camisas cuando cambiemos el color seleccionado. Con amp-bind, podemos hacerlo mediante la vinculación de [src] en los elementos <amp-img> dentro de <amp-carousel>.

Sin embargo, primero debemos inicializar los datos de estado con las URL de origen de imagen de cada camisa de color. Veamos cómo hacerlo con un elemento <amp-state> nuevo:

<!-- 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>

Este elemento <amp-state> contiene un objeto JSON que asigna una string de identificador de camisa (es decir, un SKU) al color y la URL de imagen de la camisa correspondiente. Un array JSON también sirve aquí, pero con un objeto podemos hacer otras tareas interesantes que pronto verás.

Ahora podemos acceder a la URL de la imagen mediante el identificador de una camisa. Por ejemplo, shirts['10014'].color evalúa "dark green", y shirts['10030'].image muestra la URL de la imagen del color de la camisa.

Si agregamos otra variable de estado que realice un seguimiento del SKU seleccionado, podremos vincular una expresión a los elementos <amp-img> a fin de actualizar sus atributos src cuando cambie el SKU seleccionado. Agrega una clave sku nueva al elemento amp-state#selected existente:

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

Agrega una acción "on" a <amp-selector> para actualizar la variable selected.sku cada vez que se seleccione un color nuevo:

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

Luego, agrega vinculaciones a los elementos <amp-img> dentro de <amp-carousel> (busca <!-- 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>

Nota: En la práctica, es probable que cada imagen del carrusel tenga un src diferente. Para ello, puede reemplazar la imagen única por un array de imágenes. Para simplificar, en este codelab, se usa una sola imagen con diferentes magnificaciones.

Ahora actualice la página y seleccione un color diferente para una camisa. Cuando lo haces, las imágenes del carrusel se actualizan para mostrar las camisas del color seleccionado.

¿Qué ocurre si los datos vinculados son demasiado grandes o complejos y no se pueden recuperar al cargar la página? ¿O si cada SKU tiene un precio que demora mucho tiempo en buscarse? Buscar precios de SKU para artículos no vistos es una pérdida de tiempo.

Recuperando tamaños disponibles para una camisa

Usemos la capacidad de recuperar datos remotos para buscar precios de SKU en nuestro ejemplo de codelab. Nuestro servidor de desarrollo de Express.js en app.js ya tiene un extremo /shirts/sizes?shirt=<sku>, que, con un SKU de camisa, muestra los tamaños y precios disponibles para cada tamaño. Envía la respuesta con un retraso artificial de un segundo para simular la latencia de la red.

Solicitud

Respuesta

GET /shirts/sizesAndPrices?sku=1001

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

Al igual que con los datos de JSON dentro de los elementos <amp-state>, los datos remotos que se muestran de estas recuperaciones se combinan y están disponibles en el atributo id del elemento. Por ejemplo, se puede acceder a los datos que se muestran en la respuesta anterior de ejemplo en una expresión:

Expresión

Resultado

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

8.99

Ahora apliquemos esto a nuestro ejemplo de comercio electrónico. Primero, recuperemos los datos de esta camisa cuando se seleccione un SKU nuevo. Agrega una vinculación [src] a nuestro elemento amp-state#shirts:

<!-- 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">

A continuación, marque claramente los tamaños no disponibles para un SKU determinado. La clase de CSS "unavailable" agrega una línea diagonal a través de un elemento. Podemos agregarla a los elementos dentro de amp-selector[name="size"] correspondientes a los tamaños no disponibles:

<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>

Ahora vuelve a cargar la página y pruébala. Si seleccionas un nuevo SKU (color de camisa), los tamaños no disponibles se tacharán (después de una breve demora).

Sin embargo, hay un pequeño problema: ¿qué ocurre con la camisa negra, el color seleccionado previamente? Debemos agregar los datos sobre el tamaño y el precio de la camisa negra a 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
        }
      },
<!-- ... -->

además de actualizar el estado predeterminado de los elementos relevantes.

<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>

Precios variables para camisetas

Ahora que mostramos correctamente los tamaños disponibles, asegurémonos de que también se muestre el precio correcto.

Nuestra tienda de AMPPAREL es peculiar porque el precio de la camisa es específico del color Y el tamaño. Eso significa que necesitamos una nueva variable para hacer un seguimiento del tamaño que seleccionan los usuarios. Agrega una nueva acción a nuestro elemento <amp-selector> de tamaño:

<!-- 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})">

Ten en cuenta que no inicializaremos el valor de selectedSize a través del elemento amp-state#selected. Esto se debe a que intencionalmente no proporcionamos un tamaño seleccionado predeterminado y, en su lugar, queremos forzar al usuario a elegir un tamaño.

Agrega un nuevo elemento <span> que una la etiqueta de precio y cambia el texto predeterminado a &----" dado que no hay una selección de tamaño predeterminada.

<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>

¡Tenemos los precios correctos! Pruébalo.

Botón habilitado condicionalmente

Ya casi terminamos. Ahora, inhabilitemos el botón Agregar al carrito cuando el tamaño seleccionado no esté disponible:

<!-- 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]">

Tenemos una página interactiva de detalles de productos de comercio electrónico con tamaños y precios variables para cada SKU, obtenidos a pedido desde un extremo JSON remoto.

Si no puede avanzar, consulte static/final.html para obtener la solución completa.

Esperamos que este codelab te muestre la potencia y la flexibilidad de compilar páginas AMP interactivas con <amp-bind>. Para obtener más información, consulta la <amp-bind> document.

Nos encanta recibir comentarios. Envíanos tus solicitudes de funciones, sugerencias o informes de errores. Si te interesa probar <amp-bind> con usuarios reales, evalúa solicitar una prueba de origen.

Estamos ansiosos por lanzar <amp-bind> pronto y ver qué puedes crear con ella.