Cómo animar el elemento "detalles" usando WAAPI





¡Buen dia amigos!



En este artículo, le mostraré cómo puede animar el elemento de detalles nativos utilizando la API de animaciones web .



Comencemos con el marcado.



El elemento "detalles" debe contener un elemento "resumen". resumen es la parte visible del contenido cuando se cierra el acordeón.



Cualquier otro elemento es parte del contenido interno del acordeón. Para facilitar nuestra tarea, envolveremos este contenido en un div con la clase "content".



<details>
  <summary>Summary of the accordion</summary>
  <div class="content">
    <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



Necesitamos una clase Accordion para poder reutilizar nuestro código. Con tal clase, podemos instanciar cualquier número de detalles en la página.



class Accordion {
  constructor() {}

  // ,     summary
  onClick() {}

  // ,     
  shrink() {}

  // ,      
  open() {}

  // ,     
  expand() {}

  // ,    shrink  expand
  onAnimationFinish() {}
}


constructor ()



El constructor se utiliza para almacenar los datos necesarios para el acordeón.



constructor(el) {
  //  details
  this.el = el
  //  summary
  this.summary = el.querySelector('summary')
  //  div   "content"
  this.content = el.querySelector('.content')

  //    (    )
  this.animation = null
  //      ?
  this.isClosing = false
  //      ?
  this.isExpanding = false
  //    summary
  this.summary.addEventListener('click', (e) => this.onClick(e))
}


al hacer clic ()



En la función "onClick", comprobamos si el elemento está en proceso de animación (cerrarse o expandirse). Necesitamos hacer esto en el caso de que el usuario haga clic en el acordeón antes de que finalice la animación. No queremos que el acordeón salte de completamente abierto a completamente cerrado.



El elemento "detalles" tiene un atributo "abierto" agregado por el navegador cuando se abre el elemento. Podemos obtener el valor de este atributo a través de this.el.open.



onClick(e) {
  //    
  e.preventDefault()
  //   details  "overflow"   "hidden"    
  this.el.style.overflow = 'hidden'
  // ,         
  if (this.isClosing || !this.el.open) {
    this.open()
    // ,         
  } else if (this.isExpanding || this.el.open) {
    this.shrink()
  }
}


encoger ()



La función de encogimiento utiliza la función "animar" WAAPI. Puede leer sobre esta función aquí . WAAPI es muy similar a la declaración de "fotogramas clave" de CSS en que necesitamos definir fotogramas clave para la animación. En este caso, solo necesitamos dos de estos marcos: el primero es la altura actual del elemento de detalles (abierto), el segundo es la altura de los detalles cerrados (altura de resumen).



shrink() {
  //    
  this.isClosing = true

  //    
  const startHeight = `${this.el.offsetHeight}px`
  //   summary
  const endHeight = `${this.summary.offsetHeight}px`

  //    
  if (this.animation) {
    //  
    this.animation.cancel()
  }

  //  WAAPI 
  this.animation = this.el.animate({
    //   
    height: [startHeight, endHeight]
  }, {
    //         ,        (duration - )
    duration: 400,
    //        (easing (animation-timing-function) -  )
    easing: 'ease-out'
  })

  //     onAnimationFinish()
  this.animation.onfinish = () => this.onAnimationFinish(false)
  //   ,   "isClosing"  "false"
  this.animation.oncancel = () => this.isClosing = false
}


abierto ()



La función "abrir" se llama cuando queremos abrir el acordeón. Esta función no controla la animación del acordeón. Primero, calculamos la altura del elemento "detalles" y le agregamos los estilos en línea apropiados. Una vez hecho esto, podemos agregarle un atributo "abierto" para que el contenido sea visible, pero al mismo tiempo oculto gracias al overflow: hidden y la altura fija del elemento. A continuación, esperamos al siguiente cuadro para llamar a la función de expansión y animar el elemento.



open() {
  //    
  this.el.style.height = `${this.el.offsetHeight}px`
  //  details  "open"
  this.el.open = true
  //       "expand"
  requestAnimationFrame(() => this.expand())
}


expandir ()



La función expandir es similar a la función encoger, pero en lugar de animar desde la altura actual del elemento hasta su altura cerrada, animamos desde la altura del elemento hasta su altura completa. La altura total es la altura de resumen más la altura del contenido interior.



expand() {
  //    
  this.isExpanding = true
  //    
  const startHeight = `${this.el.offsetHeight}px`
  //     ( summary +  )
  const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`

  //    
  if (this.animation) {
    //  
    this.animation.cancel()
  }

  //  WAAPI 
  this.animation = this.el.animate({
    height: [startHeight, endHeight]
  }, {
    duration: 400,
    easing: 'ease-out'
  })

  this.animation.onfinish = () => this.onAnimationFinish(true)
  this.animation.oncancel = () => this.isClosing = false
}


onAnimationFinish ()



Esta función se llama al final de los detalles de la animación de apertura y cierre. Se necesita un parámetro, un valor booleano para el atributo "abierto", que ya no es procesado por el navegador (si lo recuerda, cancelamos el comportamiento predeterminado del navegador en la función "onClick").



onAnimationFinish(open) {
  //    "open"
  this.el.open = open
  //  ,  
  this.animation = null
  //  
  this.isClosing = false
  this.isExpanding = false
  //  overflow   
  this.el.style.height = this.el.style.overflow = ''
}


Inicializando acordeones



¡Fuh! Ya casi hemos terminado.



Todo lo que queda por hacer es crear una instancia de la clase Accordion para cada elemento de detalles en la página.



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


Observaciones



Para calcular correctamente la altura de un elemento en los estados abierto y cerrado, el resumen y el contenido deben tener la misma altura en toda la animación.



No agregue espacios en blanco internos para el resumen abierto, ya que esto puede dar lugar a saltos bruscos. Lo mismo ocurre con el contenido interno: debe tener una altura fija y debe evitar cambiar su altura al abrir detalles.



Además, no agregue espacios en blanco externos entre el resumen y el contenido, ya que no se tendrán en cuenta al calcular la altura en fotogramas clave. En su lugar, use relleno en su contenido para agregar algo de espacio.



Conclusión



Así es como, fácil y simplemente, logramos crear un acordeón en JavaScript puro.







Espero que hayas encontrado algo interesante para ti. Gracias por su atención.



All Articles