Como animar o elemento "details" usando WAAPI





Bom dia amigos!



Neste artigo, mostrarei como você pode animar o elemento de detalhes nativos usando a API Web Animations .



Vamos começar com a marcação.



O elemento "detalhes" deve conter um elemento "resumo". o resumo é a parte visível do conteúdo quando o acordeão é fechado.



Quaisquer outros elementos fazem parte do conteúdo interno do acordeão. Para tornar nossa tarefa mais fácil, envolveremos esse conteúdo em uma div com a classe "conteúdo".



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


Aula de acordeão



Precisamos de uma classe Accordion para poder reutilizar nosso código. Com essa classe, podemos instanciar qualquer número de detalhes na página.



class Accordion {
  constructor() {}

  // ,     summary
  onClick() {}

  // ,     
  shrink() {}

  // ,      
  open() {}

  // ,     
  expand() {}

  // ,    shrink  expand
  onAnimationFinish() {}
}


construtor ()



O construtor é usado para armazenar os dados necessários para o acordeão.



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


onClick ()



Na função "onClick" verificamos se o elemento está em processo de animação (fechando ou expandindo). Precisamos fazer isso para o caso em que o usuário clica no acordeão antes de a animação terminar. Não queremos que o acordeão salte de totalmente aberto para totalmente fechado.



O elemento "details" tem um atributo "open" adicionado pelo navegador quando o elemento é aberto. Podemos obter o valor desse atributo por meio 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()
  }
}


encolher ()



A função de redução usa a função WAAPI "animar". Você pode ler sobre esse recurso aqui . WAAPI é muito semelhante à instrução CSS "quadros-chave" em que precisamos definir quadros-chave para a animação. Nesse caso, precisamos apenas de dois desses quadros: o primeiro é a altura atual do elemento de detalhes (aberto), o segundo é a altura dos detalhes fechados (altura de resumo).



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
}


abrir ()



A função "abrir" é chamada quando queremos abrir o acordeão. Esta função não controla a animação do acordeão. Primeiro, calculamos a altura do elemento "detalhes" e adicionamos os estilos embutidos apropriados a ele. Feito isso, podemos adicionar um atributo "aberto" a ele para tornar o conteúdo visível, mas ao mesmo tempo oculto graças ao overflow: hidden e a altura fixa do elemento. Em seguida, esperamos que o próximo quadro chame a função de expansão e anime o elemento.



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


expandir ()



A função de expansão é semelhante à função de redução, mas em vez de animar da altura atual do elemento para sua altura fechada, nós animamos da altura do elemento até sua altura total. A altura total é a altura do resumo mais a altura do conteúdo interno.



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 função é chamada no final dos detalhes da animação de abertura e fechamento. Leva um parâmetro - um valor booleano para o atributo "open", que não é mais processado pelo navegador (se você se lembra, cancelamos o comportamento padrão do navegador na função "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 acordeões



Fuh! Estamos quase terminando.



Tudo o que resta a fazer é criar uma instância da classe Accordion para cada elemento de detalhes na página.



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


Observações



Para calcular corretamente a altura de um elemento nos estados aberto e fechado, o resumo e o conteúdo devem ter a mesma altura em toda a animação.



Não adicione espaços em branco internos para o resumo aberto, pois isso pode causar saltos repentinos. O mesmo é verdadeiro para o conteúdo interno - ele deve ter uma altura fixa e você deve evitar alterar sua altura ao abrir os detalhes.



Além disso, não adicione espaços em branco externos entre o resumo e o conteúdo, pois eles não serão levados em consideração ao calcular a altura em quadros-chave. Em vez disso, use preenchimento em seu conteúdo para adicionar algum espaço.



Conclusão



Foi assim que, de forma fácil e simples, conseguimos criar um acordeão em JavaScript puro.







Espero que você tenha encontrado algo interessante para você. Obrigado pela atenção.



All Articles