
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.