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 details
elemento nativo usando la API de animaciones web.
configuración HTML
Primero, veamos cómo estructuraremos el marcado necesario para esta animación.
El details
elemento necesita un summary
elemento. El resumen es el contenido visible cuando se cierra el acordeón.
Todos los demás elementos dentro details
del 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 Accordion
clase. Al hacer esto, podemos invocar new Accordion()
todos details
los 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 details
elemento tiene un atributo [open]
que le aplica el navegador cuando abrimos el elemento. Podemos obtener el valor de ese atributo comprobando la open
propiedad 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 details
elemento una vez cerrado. La altura actual se almacena en la startHeight
variable. La altura cerrada se almacena en la endHeight
variable 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 details
elemento 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: hidden
altura 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 shrink
funció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 Accordion
clase para cada details
elemento del HTML. Para hacerlo, usamos a querySelectorAll
en la details
etiqueta y creamos una nueva Accordion
instancia 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 summary
y 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