Crie um componente de acordeão JavaScript animado, com painéis sobrepostos

Neste novo tutorial, aprenderemos como criar um componente acordeão JavaScript animado com painéis sobrepostos.


Não focaremos tanto na acessibilidade neste tutorial, então explorar como tornar esse componente mais acessível seria um próximo passo válido.

Nosso componente de acordeão

Aqui está o que vamos criar (clique em um painel para testar o comportamento):

1. Comece com a marcação da página

Dentro de um contêiner, colocaremos uma lista de painéis.

Cada painel terá um título e conteúdo. Dentro do título, adicionaremos um Fechar botão de onde podemos fechar o painel ativo.

Aqui está a estrutura necessária:

1
 class="accordion-wrapper">
2
  
3
    
  • 4
           class="accordion-title">...
    
    5
           class="accordion-content">
    
    6
             class="inner">...
    
    7
          
    
    8
        
    
    9
        
  • 10
           class="accordion-title">...
    
    11
           class="accordion-content">
    
    12
             class="inner">...
    
    13
          
    
    14
        
    
    15
        
  • 16
           class="accordion-title">...
    
    17
           class="accordion-content">
    
    18
             class="inner">...
    
    19
          
    
    20
        
    
    21
        
  • 22
           class="accordion-title">...
    
    23
           class="accordion-content">
    
    24
             class="inner">...
    
    25
          
    
    26
        
    
    27
      
    
    28
    
    

    Estado inicial do acordeão/itens ativos

    Por padrão, todos os painéis serão recolhidos.

    Nosso acordeão com painéis recolhidosNosso acordeão com painéis recolhidosNosso acordeão com painéis recolhidos

    Para evitar esse comportamento, temos que atribuir o active classe para um ou mais painéis como este:

    1
    2
       class="active">...
    
    3
    
    
    Nosso acordeão com painel expandidoNosso acordeão com painel expandidoNosso acordeão com painel expandido

    Vários painéis abertos

    Também há a opção de ter mais de um painel aberto simultaneamente sem que um se feche quando o outro estiver aberto. Para habilitar isso, devemos adicionar o data-multiple="true" atribuir ao wrapper do acordeão assim:

    1
     class="accordion-wrapper" data-multiple="true">...
    
    Nosso acordeão com vários painéis abertos ao mesmo tempoNosso acordeão com vários painéis abertos ao mesmo tempoNosso acordeão com vários painéis abertos ao mesmo tempo

    2. Adicione o CSS

    Vamos agora nos concentrar nos estilos principais — a maioria dos outros estilos não tem nada de especial, então vamos deixá-los por enquanto:

    • Para fazer os painéis se sobreporem e criar um layout de acordeão diferente em comparação aos padrões, daremos a eles uma margem superior negativa e um preenchimento inferior igual. Apenas para o primeiro e o último itens, cancelaremos a margem superior e o preenchimento inferior, respectivamente.
    • Para ocultar o conteúdo de cada painel, daremos a eles height: 0 e overflow: hidden. Então, como veremos mais tarde, por meio do JavaScript, recalcularemos sua altura e as revelaremos suavemente. Apenas observe que também usaremos height: 0 !important para redefinir a altura para 0 e substituir os estilos JavaScript de um painel ativo anteriormente.
    • Para abrir o modal, toda a área do painel será clicável. Para deixar claro, atribuiremos cursor: pointer para todos os painéis. Ao contrário, quando um painel está aberto, podemos fechá-lo somente através do botão fechar. Neste momento, somente este botão terá cursor: pointer enquanto o painel terá cursor: default.

    Aqui está uma parte dos estilos necessários:

    1
    /*CUSTOM STYLES HERE*/
    
    2
    
    
    3
    .accordion-wrapper li {
    
    4
      padding: 0 20px 100px;
    
    5
      cursor: pointer;
    
    6
      border-top-left-radius: var(--accordion-radius);
    
    7
      border-top-right-radius: var(--accordion-radius);
    
    8
      background: var(--accordion-bg-color);
    
    9
      transition: all 0.2s ease-out;
    
    10
    }
    
    11
    
    
    12
    .accordion-wrapper li:not(:first-child) {
    
    13
      margin-top: -100px;
    
    14
      border-top: 2px solid var(--light-cyan);
    
    15
    }
    
    16
    
    
    17
    .accordion-wrapper li:nth-last-child(2),
    
    18
    .accordion-wrapper li:last-child {
    
    19
      border-bottom-left-radius: var(--accordion-radius);
    
    20
      border-bottom-right-radius: var(--accordion-radius);
    
    21
    }
    
    22
    
    
    23
    .accordion-wrapper li:last-child {
    
    24
      padding-bottom: 0;
    
    25
    }
    
    26
    
    
    27
    .accordion-wrapper:not([data-multiple="true"]) li.active {
    
    28
      border-top-color: var(--accordion-active-bg-color);
    
    29
    }
    
    30
    
    
    31
    .accordion-wrapper li.active {
    
    32
      cursor: default;
    
    33
      color: var(--white);
    
    34
      background: var(--accordion-active-bg-color);
    
    35
    }
    
    36
    
    
    37
    .accordion-wrapper li:not(.active) .accordion-content {
    
    38
      height: 0 !important;
    
    39
    }
    
    40
    
    
    41
    .accordion-wrapper .accordion-content {
    
    42
      height: 0;
    
    43
      overflow: hidden;
    
    44
      transition: height 0.3s;
    
    45
    }
    
    46
    
    
    47
    .accordion-wrapper .inner {
    
    48
      padding-bottom: 40px;
    
    49
    }
    
    50
    
    
    51
    @media (min-width: 700px) {
    
    52
      .accordion-wrapper li {
    
    53
        padding-left: 60px;
    
    54
        padding-right: 60px;
    
    55
      }
    
    56
    
    
    57
      .accordion-wrapper .inner {
    
    58
        max-width: 85%;
    
    59
      }
    
    60
    }
    

    3. Adicione o JavaScript

    A maneira como animaremos cada painel e obteremos um efeito de slide semelhante ao do jQuery slideToggle() a função é aproveitar a scrollHeight propriedade.

    Esta propriedade mede a altura do conteúdo de um elemento, incluindo conteúdo não visível na tela devido a estouro. No nosso caso, precisaremos calcular esse valor para o .accordion-content elementos que têm height: 0 e overflow: hidden por padrão.

    Quando DOM estiver pronto

    Como primeira ação, quando o DOM estiver pronto, verificaremos se há algum painel ativo e, em caso positivo, definiremos a altura para o mesmo. .accordion-content elemento de cada painel ativo igual ao seu scrollHeight valor da propriedade.

    Aqui está o código JavaScript relacionado:

    1
    const activeItems = accordionWrapper.querySelectorAll("li.active");
    
    2
    
    
    3
    if (activeItems) {
    
    4
      activeItems.forEach(function (item) {
    
    5
        const content = item.querySelector(".accordion-content");
    
    6
        content.style.height = `${content.scrollHeight}px`;
    
    7
      });
    
    8
    }
    

    Alternar painéis de acordeão

    Em seguida, cada vez que clicarmos em um painel, faremos o seguinte:

    1. Verifique se clicamos no botão fechar. Se isso acontecer e o painel estiver aberto, fecharemos removendo o active classe e ignorando todos os próximos passos.
    2. Verifique se definimos a opção de ter vários painéis abertos juntos. Se não for o caso e houver um painel ativo, nós o fecharemos.
    3. Adicione o active classe para esse painel.
    4. Defina a altura para o .accordion-content elemento deste painel igual ao seu scrollHeight valor da propriedade.

    Aqui está o código JavaScript que implementa todo esse comportamento:

    1
    const accordionWrapper = document.querySelector(".accordion-wrapper");
    
    2
    const items = accordionWrapper.querySelectorAll("li");
    
    3
    const multiple_open = accordionWrapper.dataset.multiple;
    
    4
    const ACTIVE_CLASS = "active";
    
    5
    
    
    6
    items.forEach(function (item) {
    
    7
      item.addEventListener("click", function (e) {
    
    8
        // 1
    
    
    9
        const target = e.target;
    
    10
        if (
    
    11
          (target.tagName.toLowerCase() === "button" || target.closest("button")) &&
    
    12
          item.classList.contains(ACTIVE_CLASS)
    
    13
        ) {
    
    14
          item.classList.remove(ACTIVE_CLASS);
    
    15
          return;
    
    16
        }
    
    17
        
    
    18
        // 2
    
    
    19
        if (
    
    20
          "true" !== multiple_open &&
    
    21
          document.querySelector(".accordion-wrapper li.active")
    
    22
        ) {
    
    23
          document
    
    24
            .querySelector(".accordion-wrapper li.active")
    
    25
            .classList.remove(ACTIVE_CLASS);
    
    26
        }
    
    27
        
    
    28
        // 3
    
    
    29
        item.classList.add(ACTIVE_CLASS);
    
    30
    
    
    31
        // 4
    
    
    32
        const content = item.querySelector(".accordion-content");
    
    33
        content.style.height = `${content.scrollHeight}px`;
    
    34
      });
    
    35
    });
    

    Conclusão

    Pronto! Espero que você tenha gostado do acordeão JavaScript que construímos e tenha aprendido uma ou duas coisas novas.

    Antes de encerrar, vamos relembrar nossa principal criação de hoje:

    Como sempre, muito obrigado pela leitura!

    Deixe uma resposta