Cómo animar el elemento de detalles usando WAAPI

Animar acordeones en JavaScript ha sido una de las animaciones más solicitadas en los sitios web. Dato curioso: slideDown()la función de jQuery ya estaba disponible en la primera versión en 2006.

En este artículo, veremos cómo puedes animar el detailselemento nativo usando la API de animaciones web.

configuración HTML

Primero, veamos cómo estructuraremos el marcado necesario para esta animación.

El detailselemento necesita un summaryelemento. El resumen es el contenido visible cuando se cierra el acordeón.
Todos los demás elementos dentro detailsdel acordeón son parte del contenido interno. Para que resulte más fácil animar ese contenido, lo envolvemos dentro de un archivo div.

details  summarySummary of the accordion/summary  div    p      Lorem, ipsum dolor sit amet consectetur adipisicing elit.      Modi unde, ex rem voluptates autem aliquid veniam quis temporibus repudiandae illo, nostrum, pariatur quae!      At animi modi dignissimos corrupti placeat voluptatum!    /p  /div/details

clase de acordeón

Para que nuestro código sea más reutilizable, deberíamos crear una Accordionclase. Al hacer esto, podemos invocar new Accordion()todos detailslos elementos de la página.

class Accordion {  // The default constructor for each accordion  constructor() {}  // Function called when user clicks on the summary  onClick() {}  // Function called to close the content with an animation  shrink() {}  // Function called to open the element after click  open() {}  // Function called to expand the content with an animation  expand() {}  // Callback when the shrink or expand animations are done  onAnimationFinish() {}}

Constructor()

El constructor es el lugar donde guardamos todos los datos necesarios por acordeón.

constructor(el) {  // Store the details element  this.el = el;  // Store the summary element  this.summary = el.querySelector('summary');  // Store the div element  this.content = el.querySelector('.content');  // Store the animation object (so we can cancel it, if needed)  this.animation = null;  // Store if the element is closing  this.isClosing = false;  // Store if the element is expanding  this.isExpanding = false;  // Detect user clicks on the summary element  this.summary.addEventListener('click', (e) = this.onClick(e));}

onClick()

En la onClick()función, notará que estamos verificando si el elemento se está animando (cerrándose o expandiéndose). Necesitamos hacer esto en caso de que los usuarios hagan clic en el acordeón mientras se está animando. En caso de clics rápidos, no queremos que el acordeón salte de estar completamente abierto a completamente cerrado.

El detailselemento tiene un atributo [open]que le aplica el navegador cuando abrimos el elemento. Podemos obtener el valor de ese atributo comprobando la openpropiedad de nuestro elemento usando this.el.open.

onClick(e) {  // Stop default behaviour from the browser  e.preventDefault();  // Add an overflow on the details to avoid content overflowing  this.el.style.overflow = 'hidden';  // Check if the element is being closed or is already closed  if (this.isClosing || !this.el.open) {    this.open();  // Check if the element is being openned or is already open  } else if (this.isExpanding || this.el.open) {    this.shrink();  }}

shrink()

Esta función de reducción utiliza la .animate()función WAAPI. Puede leer más sobre esto en los documentos de MDN. WAAPI es muy similar a CSS @keyframes. Necesitamos definir los fotogramas clave de inicio y fin de la animación. En este caso, solo necesitamos dos fotogramas clave, el primero es la altura actual del elemento y el segundo es la altura del detailselemento una vez cerrado. La altura actual se almacena en la startHeightvariable. La altura cerrada se almacena en la endHeightvariable y es igual a la altura del archivo summary.

shrink() {  // Set the element as "being closed"  this.isClosing = true;  // Store the current height of the element  const startHeight = `${this.el.offsetHeight}px`;  // Calculate the height of the summary  const endHeight = `${this.summary.offsetHeight}px`;  // If there is already an animation running  if (this.animation) {    // Cancel the current animation    this.animation.cancel();  }  // Start a WAAPI animation  this.animation = this.el.animate({    // Set the keyframes from the startHeight to endHeight    height: [startHeight, endHeight]  }, {    // If the duration is too slow or fast, you can change it here    duration: 400,    // You can also change the ease of the animation    easing: 'ease-out'  });  // When the animation is complete, call onAnimationFinish()  this.animation.onfinish = () = this.onAnimationFinish(false);  // If the animation is cancelled, isClosing variable is set to false  this.animation.oncancel = () = this.isClosing = false;}

open()

La open función se llama cuando queremos expandir el acordeón. Esta función aún no controla la animación del acordeón. Primero, calculamos la altura del detailselemento y le aplicamos esta altura con estilos en línea. Una vez hecho esto, podemos configurar el atributo de apertura para que el contenido sea visible pero oculto, ya que tenemos una overflow: hiddenaltura fija en el elemento. Luego esperamos a que el siguiente fotograma llame a la función de expansión y anime el elemento.

open() {  // Apply a fixed height on the element  this.el.style.height = `${this.el.offsetHeight}px`;  // Force the [open] attribute on the details element  this.el.open = true;  // Wait for the next frame to call the expand function  window.requestAnimationFrame(() = this.expand());}

expand()

La función expandir es similar a la shrinkfunción, pero en lugar de animar desde la altura actual hasta la altura cercana, animamos desde la altura del elemento hasta la altura final. Esa altura final es igual a la altura del resumen más la altura del contenido interno.

expand() {  // Set the element as "being expanding"  this.isExpanding = true;  // Get the current fixed height of the element  const startHeight = `${this.el.offsetHeight}px`;  // Calculate the open height of the element (summary height + content height)  const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`;  // If there is already an animation running  if (this.animation) {    // Cancel the current animation    this.animation.cancel();  }  // Start a WAAPI animation  this.animation = this.el.animate({    // Set the keyframes from the startHeight to endHeight    height: [startHeight, endHeight]  }, {    // If the duration is too slow of fast, you can change it here    duration: 400,    // You can also change the ease of the animation    easing: 'ease-out'  });  // When the animation is complete, call onAnimationFinish()  this.animation.onfinish = () = this.onAnimationFinish(true);  // If the animation is cancelled, isExpanding variable is set to false  this.animation.oncancel = () = this.isExpanding = false;}

onAnimationFinish()

Esta función se llama al final de la animación de reducción o expansión. Como puede ver, hay un parámetro, [open]que se establece en verdadero cuando el acordeón está abierto, lo que nos permite establecer el [open]atributo HTML en el elemento, ya que ya no lo maneja el navegador.

onAnimationFinish(open) {  // Set the open attribute based on the parameter  this.el.open = open;  // Clear the stored animation  this.animation = null;  // Reset isClosing  isExpanding  this.isClosing = false;  this.isExpanding = false;  // Remove the overflow hidden and the fixed height  this.el.style.height = this.el.style.overflow = '';}

Configurar los acordeones

¡Uf, hemos terminado con la mayor parte del código!

Todo lo que queda es usar nuestra Accordionclase para cada detailselemento del HTML. Para hacerlo, usamos a querySelectorAllen la detailsetiqueta y creamos una nueva Accordioninstancia para cada una.

document.querySelectorAll('details').forEach((el) = {  new Accordion(el);});

Notas

Para realizar los cálculos de la altura cerrada y la altura abierta, debemos asegurarnos de que summaryy el contenido siempre tengan la misma altura.

Por ejemplo, no intente agregar un relleno en el resumen cuando esté abierto porque podría provocar saltos durante la animación. Lo mismo ocurre con el contenido interno: debe tener una altura fija y debemos evitar tener contenido que pueda cambiar de altura durante la animación de apertura.

Además, no agregue un margen entre el resumen y el contenido, ya que no se calculará para los fotogramas clave de altura. En su lugar, utilice un relleno directamente sobre el contenido para agregar algo de espacio.

el fin

¡Y listo, tenemos un bonito acordeón animado en JavaScript sin biblioteca!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir