Blog

Postado em em 3 de junho de 2024

Criando Popovers com HTML, CSS e JavaScript

Aprenda como criar popovers com HTML, CSS e JavaScript! Entenda o que é um popover, como criá-lo e utilizá-lo em seu projeto.

Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!

Para receber por e-mail o(s) arquivo(s) utilizados na aula, preencha:

Criando Popovers com HTML, CSS e JavaScript

Na aula de hoje, vou te mostrar como criar popovers com HTML, CSS e JavaScript. Você irá entender o que é um popover, como criar um e como aplicá-lo em seu projeto.

Para esta aula, vamos utilizar a réplica da página do Google, um projeto que desenvolvemos e construímos ao longo do nosso Curso de Introdução ao HTML e CSS, e que você pode acessar aqui.

Caso você queira compreender todos os elementos que compõem essa página, não deixe de conferir esse curso gratuito, onde explico detalhadamente, passo a passo, como cada elemento funciona.

O objetivo desta aula será complementar essa réplica, adicionando o menu do Google Apps, a janela que é aberta quando clicamos no ícone correspondente e exibe todos os aplicativos do Google.

Dessa forma, ao final da aula, você terá compreendido o que é um popover, como criar o seu e formatá-lo para utilizar neste e em outros projetos que desejar.

Então, faça o download do material disponível e venha comigo aprender a criar popovers com HTML, CSS e JavaScript!

Apresentação do Projeto – Arquivos Iniciais

No material disponível para download, você encontrará os arquivos iniciais para esta aula dentro de uma pasta chamada Do zero.

Apresentação do Projeto – Arquivos Iniciais

Dentro dessa pasta estarão os arquivos index.html e style.css que foram criados ao final do nosso Curso de Introdução ao HTML e CSS, no qual desenvolvemos uma réplica da página do Google.

réplica da página do Google

Popover – O que é

Antes de partirmos para a criação do nosso popover, vamos entender exatamente o que ele é. Esse componente de interface do usuário exibe uma pequena janela flutuante quando o usuário interage com algum elemento específico.

No caso do nosso projeto, quando o usuário interagir com a caixa de ícones, o menu de aplicativos do Google deverá abrir uma janela exibindo todos os aplicativos disponíveis.

Popover – O que é

Ou seja, essa janela que é aberta ao clicarmos no menu é o popover que iremos criar nesta aula.

Para isso, precisaremos definir o posicionamento dele em relação ao elemento que o aciona, a interatividade (os links e aplicativos) que teremos dentro dele e o fechamento, ou seja, como esse popover será encerrado.

Criando o Popover no HTML

Para começar a nossa aula, vamos abrir o arquivo index.html e localizar a div com a classe caixa-icone. Dentro dela, vamos criar uma nova div com a classe caixa-icone__popover.

<div class="caixa-icone">
        <svg class="icone-produtos" focusable="false" viewBox="0 0 24 24">
          <path
            d="M6,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM16,6c0,1.1 0.9,2 2,2s2,-0.9 2,-2 -0.9,-2 -2,-2 -2,0.9 -2,2zM12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"
          ></path>
          <image
            src="https://ssl.gstatic.com/gb/images/bar/al-icon.png"
            alt=""
            height="24"
            width="24"
            style="border: none; display: none \9"
          ></image>
        </svg>
        <div class="caixa-icone__popover"></div>
      </div>

Formatação do Popover

Com o popover definido no HTML, vamos criar um seletor para ele no nosso arquivo style.css para formatá-lo e editá-lo.

Para facilitar nosso trabalho, podemos começar copiando as propriedades que estão dentro do nosso seletor .caixa-icone::after e colando-as dentro do seletor para o popover.

.caixa-icone__popover {
  display: none;
  background-color: #4f5355;
  font-family: Google Sans, Roboto, Helvetica, Arial, sans-serif;
  color: white;
  padding: 5px 10px;
  border-radius: 5px;
  font-weight: 500;
  font-size: 13px;
  position: absolute;
  width: max-content;
  left: 50%;
  bottom: 0;
  transform: translate(-50%, 30px);
}

Agora, vamos remover as propriedades que não utilizaremos no momento, como display, font-family, color, font-weight, font-size e width. Além disso, vamos alterar o backgroundcolor para #e9eef6 e o border-radius para 25px.

.caixa-icone__popover {
  background-color: #e9eef6;
  padding: 5px 10px;
  border-radius: 25px;
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translate(-50%, 30px);
}

Em seguida, precisamos remover os estilos aplicados pelo seletor .caixa-icone::after para que não interfiram com o nosso popover. Vamos fazer as seguintes modificações no style.css:

O conjunto de seletores:

.caixa-teclado:hover::after,
.caixa-microfone:hover::after,
.caixa-camera:hover::after,
.caixa-icone:hover::after {
  display: block;
}

Será alterado para:

.caixa-teclado:hover::after,
.caixa-microfone:hover::after,
.caixa-camera:hover::after {
  display: block;
}

E o conjunto de seletores:

.caixa-teclado::after,
.caixa-microfone::after,
.caixa-camera::after,
.caixa-icone::after {
  display: none;
  background-color: #4f5355;
  font-family: Google Sans, Roboto, Helvetica, Arial, sans-serif;
  color: white;
  padding: 5px 10px;
  border-radius: 5px;
  font-weight: 500;
  font-size: 13px;
  position: absolute;
  width: max-content;
  left: 50%;
  bottom: 0;
  transform: translate(-50%, 30px);
}

Será alterado para:

.caixa-teclado::after,
.caixa-microfone::after,
.caixa-camera::after {
  display: none;
  background-color: #4f5355;
  font-family: Google Sans, Roboto, Helvetica, Arial, sans-serif;
  color: white;
  padding: 5px 10px;
  border-radius: 5px;
  font-weight: 500;
  font-size: 13px;
  position: absolute;
  width: max-content;
  left: 50%;
  bottom: 0;
  transform: translate(-50%, 30px);
}

Finalmente, podemos deletar completamente o seguinte seletor:

.caixa-icone::after {
  content: "Google Apps";
}

Com isso, nosso popover estará criado e visível na réplica da página do Google.

popover criado

O próximo passo será definir um tamanho e um posicionamento para o popover. Vamos adicionar as propriedades width e height com o valor de 300px para definir seu tamanho.

Para posicionar o popover, vamos substituir a propriedade left por right com o valor 0. Isso faz com que o popover seja posicionado no extremo direito do seu elemento pai, a div caixa-icone (o ícone para acessar o popover).

Além disso, vamos modificar a propriedade transform para translate(0, calc(100% + 10px)). Essa mudança é importante porque move o popover para baixo, garantindo que ele apareça completamente abaixo do elemento pai, com um pequeno espaçamento de 10 pixels

Como queremos mover o popover para baixo em 100% do seu tamanho e mais 10 pixels, utilizamos a função calc para somar os 10px no deslocamento vertical do popover.

.caixa-icone__popover {
  background-color: #e9eef6;
  padding: 5px 10px;
  border-radius: 25px;
  position: absolute;
  right: 0;
  bottom: 0;
  transform: translate(0, calc(100% + 10px));
  width: 300px;
  height: 300px;
}

tamanho pro popover

Criando o Container – Metodologia BEM

Dentro do popover, precisamos criar um container onde ficarão os ícones e os links de acesso aos aplicativos do Google. No arquivo HTML, dentro da div caixa-icone__popover criaremos a div caixa-icone__container.

<div class="caixa-icone__popover">
          <div class="caixa-icone__container"></div>
        </div>
      </div>

Observe que tanto a classe caixa-icone__popover quanto a caixa-icone__container possuem uma sintaxe semelhante. Isso porque elas estão sendo declaradas seguindo a convenção de nomenclatura chamada BEM (Block Element Modifier).

A BEM é uma metodologia usada para estruturar e nomear classes em CSS de forma modular e reutilizável. Vamos pegar a caixa-icone__container como exemplo.

O caixa-icone representa o bloco (block), um componente independente na página web. E o __container é o elemento (element) desse bloco, uma parte que possui uma função específica, o elemento é separado do bloco por dois underlines.

E por fim podemos ter um modificador (modifier), uma variação ou estado do bloco ou do elemento e é separado do restante por dois traços, podendo ser algo como –vermelho.

Com o container definido no HTML, vamos formatá-lo dentro do CSS.

.caixa-icone__container {
  background-color: #f8fafd;
}

Por enquanto, nosso container está vazio, então ele não será exibido na nossa página. Para isso, precisamos adicionar os elementos dentro dele. Voltando ao arquivo HTML, criaremos uma nova div, dentro do container, chamada caixa-icone__item.

Dentro dessa div, teremos uma imagem (img) e um texto (p). O primeiro ícone e texto que deveremos adicionar são referentes à Conta.

<div class="caixa-icone__popover">
          <div class="caixa-icone__container">
            <div class="caixa-icone__item">
              <img src="" alt="" class="caixa-icone__imagem" />
              <p class="caixa-icone__texto">Conta</p>
            </div>
          </div>
        </div>
      </div>

Para determinar como é definida a estrutura da imagem desses ícones dentro do HTML, podemos inspecionar a página oficial do Google utilizando a ferramenta do programador.

Essa ferramenta permite visualizar o HTML e CSS de uma página, assim como outras informações relacionadas à sua estrutura, acessando a página pelo navegador e pressionando a tecla F12.

No entanto, um ponto importante a se observar é que, ao tentar inspecionar o popover na página do Google, você verá que o elemento irá sumir da página. Isso acontece porque esse conteúdo está dentro de um iframe.

O iframe incorpora outras páginas e documentos HTML dentro do documento principal. Então, para localizar a estrutura que o Google utiliza dentro do popover, precisamos primeiro identificar e encontrar esse iframe dentro do código da página.

Inspecionando cada elemento, veremos que o Google não utiliza uma tag img, mas sim uma div com um atributo background-imagem dentro do CSS.

Então, faremos essa alteração dentro do HTML:

<div class="caixa-icone__popover">
          <div class="caixa-icone__container">
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Conta</p>
            </div>
          </div>
        </div>
      </div>

E no arquivo CSS criaremos um seletor para a classe caixa-icone__imagem.

.caixa-icone__imagem {
  width: 50px;
  height: 50px;
  background-image: url("https://lh3.googleusercontent.com/a/ACg8ocLwdCNV5lX9OUKoGTosyhzmV36fyDZIEQDqdTfYErnU22oTfQ=s128-b16-cc-rp-mo");
  background-size: cover;
}

Com isso, teremos o nosso primeiro ícone sendo exibido dentro do popover.

primeiro ícone

Porém, se observarmos a página do Google, veremos que esse popover precisa ser preenchido por diversos ícones. Então, vamos adicionar novas divs dentro da nossa caixa-icone__container, uma para cada item.

<div class="caixa-icone__popover">
          <div class="caixa-icone__container">
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Conta</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Gmail</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Drive</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Documentos</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Planilhas</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Apresentações</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Agenda</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Chat</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Meet</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Formulários</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Sites</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Contatos</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Grupos</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">YouTube</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Maps</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Notícias</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Google Ads</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Fotos</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Tradutor</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Vault</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Administrador</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Keep</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Jamboard</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Cloud Search</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Google Earth</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Salvo</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Viagens</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Gerenciador de senhas</p>
            </div>
            <div class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Google Analytics</p>
            </div>
          </div>
        </div>
      </div>

Se observarmos nosso popover, veremos que ele ficou todo esquisito com um item embaixo do outro, ultrapassando até mesmo o tamanho definido.

item embaixo do outro

Layout dos Itens e Formatação

Para ajustar a disposição dos itens, vamos criar uma grade de ícones dentro do nosso container usando Grid Layout, que nos permite organizar os elementos visualmente.

Nosso grid layout terá três colunas. Para isso, utilizaremos a propriedade grid-template-columns com a função repeat. Assim, definimos três colunas, cada uma ocupando uma fração do espaço disponível (1fr).

O container deverá ter bordas arredondadas como o popover, então configuramos o border-radius para 25px. Adicionamos também um padding de 25px, criando um espaço interno entre o container e os elementos.

Para garantir espaçamento entre os ícones, utilizamos a propriedade gap definida em 10px.

Para evitar que o container ultrapasse o limite do popover, definimos dentro do seletor .caixa-icone__popover a propriedade overflow: auto. Dessa forma, se a quantidade de elementos ultrapassar o tamanho do elemento pai, uma barra de rolagem será adicionada automaticamente.

caixa-icone__popover {
  background-color: #e9eef6;
  padding: 5px 10px;
  border-radius: 25px;
  position: absolute;
  right: 0;
  bottom: 0;
  transform: translate(0, calc(100% + 10px));
  width: 300px;
  height: 300px;
  overflow: auto;
}

.caixa-icone__container {
  display: grid;
  padding: 25px;
  border-radius: 25px;
  gap: 10px;
  grid-template-columns: repeat(3, 1fr);
  background-color: #f8fafd;
}

Layout dos Itens e Formatação

Observe que, apesar de termos definido exatamente o tamanho que cada coluna deve ter, alguns elementos com textos maiores acabam não se encaixando no espaço disponível, bagunçando a disposição do layout.

Para impedir que isso aconteça e garantir a disposição correta dos elementos, vamos adicionar ao seletor .caixa-icone__texto a propriedade overflow com o valor hidden. Isso fará com que, toda vez que o texto ultrapasse o limite de espaço disponível, a parte que ultrapassar fique oculta.

Além disso, vamos definir a propriedade text-overflow com o valor ellipsis. Dessa forma, o texto ficará com três pontinhos (“…”) ao final quando ele estiver ultrapassando o espaço disponível.

Também usaremos a propriedade white-space com o valor nowrap. Esta propriedade impede que o texto quebre em várias linhas.

Finalmente, vamos passar a largura máxima do texto (max-width) como 100%, para que ele só possa ocupar 100% do espaço disponível pelo elemento pai.

Isso significa que todo o texto ficará em uma única linha, mesmo que seja longo, e só será cortado se ultrapassar o espaço disponível.

.caixa-icone__texto {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

Tambem precisamos definir a largura total de cada item. Então, criaremos um seletor para o .caixa-icone__item com a largura máxima (max-width) de 84px. E, como queremos que a altura seja proporcional a largura, vamos definir o aspect-ratio como 1.

.caixa-icone__item {
  max-width: 84px;
  aspect-ratio: 1;
}

Layout dos Itens e Formatação

Para que os elementos fiquem centralizados, podemos utilizar o display flex.

.caixa-icone__item {
  display: flex;
  flex-direction: column;
  max-width: 84px;
  aspect-ratio: 1;
  justify-content: center;
  align-items: center;
}

Elementos no display flex

Para finalizar essa etapa, agora que já definimos os elementos e seus tamanhos, vamos ajustar a largura e altura do popover, removendo a propriedade de width e aumentando a altura (height) para 400px.

.caixa-icone__popover {
  background-color: #e9eef6;
  padding: 5px 10px;
  border-radius: 25px;
  position: absolute;
  right: 0;
  bottom: 0;
  transform: translate(0, calc(100% + 10px));
  height: 400px;
  overflow: auto;
}

Popover ajustado

Hover no Itens – CSS Aninhado

Agora, precisamos modificar os itens quando passamos o mouse por cima deles. Essa mudança de estado de um elemento é o que chamamos de hover.

O hover é uma pseudoclasse CSS que nos permite definir estilos específicos quando o usuário passa o mouse sobre algum elemento da página. Vimos isso na aula 6, quando construímos a réplica da página do Google.

Nessa aula, aprendemos como definir o hover utilizando CSS padrão:

.caixa-icone__item:hover {
    /*estilos aqui */
}

Porém, outra forma de definir esse estilo é utilizando o que chamamos de CSS Aninhado.

No CSS aninhado, utilizamos o seletor &:hover dentro do elemento que desejamos modificar. Isso permite uma melhor organização do código, pois o estado hover ficará contido dentro do próprio elemento.

Dessa forma, podemos definir o pseudo-seletor hover da seguinte maneira:

&:hover {
    /*estilos aqui */
}

O símbolo & representa o elemento pai no contexto do CSS aninhado. Então, &:hover é o mesmo que .caixa-icone__item:hover.

Vamos aplicar isso ao nosso projeto. Queremos que, ao passar o mouse sobre os itens, a cor de fundo deles seja alterada. Então, dentro do seletor .caixa-icone__item, adicionaremos &:hover com a propriedade background-color.

Além disso, para ajustarmos os cantos e o espaçamento dos nossos itens, podemos adicionar as propriedades border-radius e padding ao elemento pai .caixa-icone__item.

O seletor .caixa-icone__item ficará da seguinte forma:

.caixa-icone__item {
  display: flex;
  flex-direction: column;
  max-width: 84px;
  aspect-ratio: 1;
  justify-content: center;
  align-items: center;
  border-radius: 10px;
  padding: 5px;
  &:hover {
    background-color: #e9eef6;
  }
}

Ajustando Tamanhos – Box Sizing – Seletor Universal

Outro ajuste importante que precisamos fazer é quanto ao Box Sizing. O box-sizing é uma propriedade que define como o tamanho total dos elementos é calculado, e geralmente a definimos dentro do seletor universal (*).

Por padrão, o box-sizing tem o valor content-box, em que o padding e a border são adicionados ao tamanho do elemento.

Isso significa que, ao definir a largura e a altura de um elemento, o padding e a border são somados a esses valores, aumentando o tamanho total do elemento.

Como queremos definir o tamanho total do elemento, incluindo o padding e a border no cálculo, precisamos alterar o box-sizing para border-box.

Dessa forma, o padding e a border serão incluídos dentro da largura e altura definidas, facilitando o controle do layout.

Além de adicionar essa propriedade ao seletor universal, podemos passar as propriedades font-family, font-size e color para dentro do seletor body.

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
  display: flex;
  flex-direction: column;
  font-family: arial, sans-serif;
  font-size: 14px;
  color: #202124;
}

Personalização da Barra de Rolagem

Com esse ajuste feito, podemos personalizar a barra de rolagem do nosso popover. Sempre que preciso ajustar uma barra de rolagem, eu confiro os exemplos e modelos no site W³ Schools.

Acessando esse site, podemos copiar o modelo de barra de rolagem fornecido e adicionar dentro do seletor do nosso elemento popover, fazendo os ajustes necessários.

Além de ajustar as medidas e valores do código fornecido, como estamos utilizando um CSS aninhado, vamos adicionar o símbolo & antes de cada um dos pseudo-elementos referentes à barra de rolagem.

Por fim, vamos ajustar o padding do nosso popover. O objetivo é que ele tenha um padding de 8px de cada lado.

No entanto, a barra de rolagem já ocupa 8px do espaço. Então, podemos definir um padding geral de 8px e, em seguida, definir um padding-right de 0px para o lado direito do popover.

.caixa-icone__popover {
  background-color: #e9eef6;
  padding: 8px;
  padding-right: 0px;
  border-radius: 25px;
  position: absolute;
  right: 0;
  bottom: 0;
  transform: translate(0, calc(100% + 10px));
  height: 400px;
  overflow: auto;
  /* width */
  &::-webkit-scrollbar {
    width: 8px;
  }
  /* Track */
  &::-webkit-scrollbar-track {
    background: transparent;
    border-radius: 8px;
    margin: 34px 0;
  }
  /* Handle */
  &::-webkit-scrollbar-thumb {
    border-radius: 8px;
    background: #aaa;
  }
  /* Handle on hover */
  &::-webkit-scrollbar-thumb:hover {
    background: #888;
  }
}

Barra de Rolagem

Centralizando o Texto dos Elementos

Para centralizar o texto dos nossos elementos, basta adicionarmos a propriedade text-align: center ao seletor .caixa-icone__texto.

.caixa-icone__texto {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
  text-align: center;
}

Mostrar Todo o Texto do Item

Ao passar o cursor do mouse sobre algum item (fazer hover), precisamos exibir todo o texto correspondente. Para isso, vamos editar o seletor &:hover dentro do .caixa-icone__item.

Queremos modificar um elemento que está dentro de .caixa-icone__item, especificamente o .caixa-icone__texto, quando o hover ocorre.

Assim, definimos o seletor .caixa-icone__texto dentro de &:hover. Isso garante que as alterações feitas no .caixa-icone__texto ocorrerão apenas quando fizermos hover em .caixa-icone__item.

Dentro desse seletor, vamos alterar a propriedade white-space para initial, permitindo que o texto se comporte de acordo com as regras normais de quebra de linha, facilitando a leitura.

Em seguida, definimos o text-overflow como normal, evitando qualquer corte e exibição de reticências no texto.

Por fim, definimos a propriedade word-break como break-all. Ela permite que palavras longas sejam quebradas e continuem na linha seguinte.

.caixa-icone__item {
  display: flex;
  flex-direction: column;
  max-width: 84px;
  aspect-ratio: 1;
  justify-content: center;
  align-items: center;
  border-radius: 10px;
  padding: 5px;
  &:hover {
    background-color: #e9eef6;
    .caixa-icone__texto {
      white-space: initial;
      text-overflow: normal;
      word-break: break-all;
    }
  }
}

Posicionando o mouse sobre Apresentações por exemplo, a exibição fiará dessa forma:

Centralizando o Texto dos Elementos

Imagem do Ícones

Ao inspecionar a página do Google, podemos observar que eles utilizam uma única imagem de fundo para todos os ícones, alterando apenas a posição dela para cada um. O único item que possui uma imagem diferente é o primeiro, referente à conta do usuário.

Nosso primeiro passo será alterar a propriedade background-image dentro do seletor .caixa-icone__imagem para essa imagem comum que será utilizada pela maioria dos itens.

.caixa-icone__imagem {
  width: 50px;
  height: 50px;
  background-image: url(https://ssl.gstatic.com/gb/images/sprites/p_2x_387ed93f4280.png);
  background-size: cover;
}

Feito isso, para personalizar a imagem de fundo de cada ícone dentro do seletor .caixa-icone__item, utilizaremos a pseudo-classe :nth-child.

Essa pseudo-classe permite selecionar elementos com base em sua posição em uma lista de elementos irmãos, o que é útil para aplicar estilos específicos a cada elemento individualmente.

Vamos utilizá-la dentro do seletor .caixa-icone__item para estilizar cada ícone com uma imagem de fundo diferente.

Como queremos modificar um elemento que está dentro do .caixa-icone__item, especificamente a imagem, vamos definir então dentro de cada seletor &:nth-child o seletor .caixa-icone__imagem.

Como mencionado, o primeiro filho usa uma imagem diferente dos demais. Para ele, definimos a propriedade background-image com o link específico (o que tínhamos anteriormente dentro do .caixa-icone__imagem).

&:nth-child(1) {
    .caixa-icone__imagem {
      background-image: url(https://lh3.googleusercontent.com/a/ACg8ocLwdCNV5lX9OUKoGTosyhzmV36fyDZIEQDqdTfYErnU22oTfQ=s128-b16-cc-rp-mo);
    }
  }

Para os demais filhos, utilizamos a imagem comum definida anteriormente. A única coisa que precisamos fazer é modificar a posição da imagem de fundo usando a propriedade background-position.

Essa propriedade recebe dois valores: o primeiro correspondente ao eixo X e o segundo ao eixo Y.

Como não desejamos alterar a posição horizontalmente, o primeiro valor será 0. O segundo valor será calculado usando a função calc, multiplicando o valor de -54.612px por um número inteiro.

Eu já deixei esses cálculos prontos para você utilizar, mas vou explicar como chegamos neles.

A imagem de fundo usada pelo Google é uma imagem de 106px de largura por 5210px de altura com 45 ícones dispostos um abaixo do outro.

Imagem do Ícones

Para calcular o deslocamento da imagem de fundo, primeiro precisamos calcular a proporção de altura da imagem em relação aos nossos ícones.

Os ícones do popover têm as dimensões de 50px por 50px. Dividindo 50px pela largura total de 106px, obtemos aproximadamente 0,4717.

Multiplicando esse resultado pela altura total de 5210px, obtemos 2457,55px. Dividindo esse valor pelo número de ícones (45), obtemos aproximadamente 54,612px.

Ou seja, cada ícone na imagem de fundo ocupa um espaço de 54,612px de altura. Como desejamos deslocar a imagem para baixo, utilizamos esse valor negativo, multiplicado pela posição que o ícone desejado está.

Dessa forma, nosso seletor .caixa-icone__item ficará assim:

.caixa-icone__item {
  display: flex;
  flex-direction: column;
  max-width: 84px;
  aspect-ratio: 1;
  justify-content: center;
  align-items: center;
  border-radius: 10px;
  padding: 5px;
  &:hover {
    background-color: #e9eef6;
    .caixa-icone__texto {
      white-space: initial;
      text-overflow: normal;
      word-break: break-all;
    }
  }
  &:nth-child(1) {
    .caixa-icone__imagem {
      background-image: url(https://lh3.googleusercontent.com/a/ACg8ocLwdCNV5lX9OUKoGTosyhzmV36fyDZIEQDqdTfYErnU22oTfQ=s128-b16-cc-rp-mo);
    }
  }
  &:nth-child(2) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 31);
    }
  }
  &:nth-child(3) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 18);
    }
  }
  &:nth-child(4) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 25);
    }
  }
  &:nth-child(5) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 16);
    }
  }
  &:nth-child(6) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 12);
    }
  }
  &:nth-child(7) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 34);
    }
  }
  &:nth-child(8) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 43);
    }
  }
  &:nth-child(9) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 7);
    }
  }
  &:nth-child(10) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 20);
    }
  }
  &:nth-child(11) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 33);
    }
  }
  &:nth-child(12) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 15);
    }
  }
  &:nth-child(13) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 39);
    }
  }
  &:nth-child(14) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 38);
    }
  }
  &:nth-child(15) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 17);
    }
  }
  &:nth-child(16) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 41);
    }
  }
  &:nth-child(17) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 6);
    }
  }
  &:nth-child(18) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 8);
    }
  }
  &:nth-child(19) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 44);
    }
  }
  &:nth-child(20) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 30);
    }
  }
  &:nth-child(21) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 11);
    }
  }
  &:nth-child(22) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 10);
    }
  }
  &:nth-child(23) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 14);
    }
  }
  &:nth-child(24) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 28);
    }
  }
  &:nth-child(25) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 36);
    }
  }
  &:nth-child(26) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 32);
    }
  }
  &:nth-child(27) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 26);
    }
  }
  &:nth-child(28) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 3);
    }
  }
  &:nth-child(29) {
    .caixa-icone__imagem {
      background-position: 0 calc(-54.612px * 0);
    }
  }
}

Dessa forma, cada ícone é estilizado de acordo com sua posição, garantindo que cada um tenha a imagem correta exibida.

Ícones com imagens

Ajustando o Cursor

Vamos fazer uma pequena alteração para que o nosso cursor só mude quando posicionarmos o mouse em cima dos itens e não dentro do container todo.

Dentro do .caixa-icone__item, vamos adicionar a propriedade cursor:pointer.

.caixa-icone__item {
  display: flex;
  flex-direction: column;
  max-width: 84px;
  aspect-ratio: 1;
  justify-content: center;
  align-items: center;
  border-radius: 10px;
  padding: 5px;
  cursor: pointer;

E no .caixa-item__container vamos definir como initial, o padrão.

.caixa-icone__container {
  display: grid;
  padding: 25px;
  border-radius: 25px;
  gap: 10px;
  grid-template-columns: repeat(3, 1fr);
  background-color: #f8fafd;
  cursor: initial;
}

Transformando as divs em âncoras

Na página do Google, cada um dos ícones presentes dentro do menu de aplicativos é um link que leva para um dos serviços oferecidos pelo Google.

Então, podemos substituir as divs onde criamos nossos ícones por tags de âncora (<a>).

<div class="caixa-icone__popover">
          <div class="caixa-icone__container">
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Conta</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Gmail</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Drive</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Documentos</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Planilhas</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Apresentações</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Agenda</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Chat</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Meet</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Formulários</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Sites</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Contatos</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Grupos</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">YouTube</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Maps</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Notícias</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Google Ads</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Fotos</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Tradutor</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Vault</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Administrador</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Keep</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Jamboard</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Cloud Search</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Google Earth</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Salvo</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Viagens</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Gerenciador de senhas</p>
            </a>
            <a href="#" class="caixa-icone__item">
              <div class="caixa-icone__imagem"></div>
              <p class="caixa-icone__texto">Google Analytics</p>
            </a>
          </div>
        </div>
      </div>

Ao alterarmos de div para anchor tag, precisamos ajustar a estilização, porque os nomes de cada item passarão a se comportar como links também.

Para remover o estilo padrão das tags âncoras e garantir que elas se comportem como desejado, vamos fazer algumas modificações no CSS.

Primeiro, vamos fazer com que os links herdem a cor do texto anterior. Para isso, dentro do seletor .caixa-icone__item, vamos definir a propriedade color como inherit.

Além disso, precisaremos modificar todas as pseudoclasses relacionadas aos links: :link (representa um link que ainda não foi visitado), :visited (representa um link que já foi visitado), :hover (representa o estado do link quando o cursor está sobre ele) e :active (representa o estado do link enquanto está sendo clicado).

Para todos esses seletores, vamos definir text-decoration: none para remover o sublinhado padrão dos links.

.caixa-icone__item {
  display: flex;
  flex-direction: column;
  max-width: 84px;
  aspect-ratio: 1;
  justify-content: center;
  align-items: center;
  border-radius: 10px;
  padding: 5px;
  cursor: pointer;
  color: inherit;

  &:link,
  &:visited,
  &:hover,
  &:active {
    text-decoration: none;
  }

Transformando as divs em âncoras

Visibilidade da Caixa de Ícones – JavaScript

Nosso popover está praticamente pronto, agora só precisamos adicionar duas funcionalidades a nossa caixa de ícones. A primeira é de exibi-la quando clicarmos no ícone dela, e a segunda é de fechá-la quando clicarmos fora dela.

Vamos começar definindo dentro do CSS o comportamento padrão para o nosso popover como sendo display:none.

.caixa-icone__popover {
  display: none;

Agora criaremos um segundo seletor para a classe caixa-icone–aberto que modificará o nosso .caixa-icone__popover para display: block.

.caixa-icone--aberto {
  .caixa-icone__popover {
    display: block;
  }
}

Essa classe .caixa-icone–aberto será adicionada à nossa div .caixa-icone quando clicarmos sobre o ícone do menu de aplicativos.

Para adicionar essa funcionalidade de, ao clicarmos no ícone do menu, alterarmos a classe de caixa-icone para caixa-icone–aberto, precisaremos trabalhar com JavaScript.

Então, vamos criar um arquivo chamado scripts.js. Além disso, precisamos linkar o nosso arquivo JS com o arquivo HTML. Então, antes de finalizar o corpo (body) do nosso documento HTML, vamos definir o link entre o nosso código JavaScript com o HTML.

<script src="scripts.js"></script>
  </body>
</html>

Dentro do nosso arquivo JavaScript começaremos declarando uma variável chamada elemCaixaIcone. Essa variável armazenará o primeiro elemento do documento (document) com base em um seletor que será definido pelo método querySelector.

let elemCaixaIcone = document.querySelector(".caixa-icone");

Dessa forma temos uma variável armazenando o seletor .caixa-icone.

Feito isso, vamos utilizar o método addEventListener ao elemento que acabamos de armazenar na variável. Esse método dará a ele a capacidade de “escutar” um evento, que será o clique sobre ele.

Ao receber esse clique, nosso elemento chamará uma função que listará todas as classes presentes nele. Se a classe caixa-icone–aberto estiver presente ele irá retirá-la do nosso elemento, caso contrário, irá adicioná-la.

Esse controle de adicionar e remover pode ser feito através do método toggle.

let elemCaixaIcone = document.querySelector(".caixa-icone");

elemCaixaIcone.addEventListener("click", function () {
  elemCaixaIcone.classList.toggle("caixa-icone--aberto");
});

Com isso já será possível abrir e fechar o nosso popover ao clicar sobre o ícone dele.

Agora, dentro do arquivo CSS, vamos adicionar o seletor .caixa-icone–aberto junto ao seletor .caixa-icone:hover, para que quando o popover estiver aberto o ícone ele fique com a mesma aparência de quando posicionamos o mouse em cima.

.caixa-icone:hover,
.caixa-icone--aberto {
  background-color: rgba(60, 64, 67, 0.08);
  border-radius: 50%;
}

Popover funcionando

Fechar a Caixa de Ícone ao Clicar Fora

Agora precisamos definir a lógica para fechar nosso popover ao clicar fora dele. Vamos começar definindo a variável que armazenará nosso elemento com o seletor caixa-icone__popover.

let elemCaixaIcone = document.querySelector(".caixa-icone");
let elemCaixaPopover = document.querySelector(".caixa-icone__popover");

A lógica será muito parecida com a que definimos para abrir, mas agora, dentro da nossa função definiremos o parâmetro elemento, que representa o evento de clique.

Dentro dessa função vamos verificar se o elemento clicado (elemento.target) não está dentro do popover (!elemCaixaPopover) e não está dentro do caixa ícone (!elemCaixaIcone).

Se o clique acontecer fora desses dois locais, a classe caixa-icone–aberto será removida do elemCaixaIcone, fechando assim nosso popover.

let elemCaixaIcone = document.querySelector(".caixa-icone");
let elemCaixaPopover = document.querySelector(".caixa-icone__popover");

elemCaixaIcone.addEventListener("click", function () {
  elemCaixaIcone.classList.toggle("caixa-icone--aberto");
});

document.addEventListener("click", function (elemento) {
  if (
    !elemCaixaPopover.contains(elemento.target) &&
    !elemCaixaIcone.contains(elemento.target)
  ) {
    elemCaixaIcone.classList.remove("caixa-icone--aberto");
  }
});

Porém, você irá reparar que, ao clicar entre os ícones dentro do nosso popover, a caixa do menu está sendo fechada também.

Para resolver isso, utilizaremos uma regra semelhante à anterior para o nosso primeiro evento em que, o método toggle só será chamado se o clique ocorrer fora do popover.

let elemCaixaIcone = document.querySelector(".caixa-icone");
let elemCaixaPopover = document.querySelector(".caixa-icone__popover");

elemCaixaIcone.addEventListener("click", function (elemento) {
    if (!elemCaixaPopover.contains(elemento.target)) {
      elemCaixaIcone.classList.toggle("caixa-icone--aberto");
    }
  });

document.addEventListener("click", function (elemento) {
  if (
    !elemCaixaPopover.contains(elemento.target) &&
    !elemCaixaIcone.contains(elemento.target)
  ) {
    elemCaixaIcone.classList.remove("caixa-icone--aberto");
  }
});

Dessa forma concluímos a parte de JavaScript do nosso projeto e encerramos a implementação do Popover na nossa réplica da página do Google.

Conclusão – Criando Popovers com HTML, CSS e JavaScript

Na aula de hoje eu te mostrei o que são popover e como criar um do zero, totalmente responsivo utilizando HTML, CSS e JavaScript.

Com essa parte, complementamos o projeto da nossa réplica da página do Google, um projeto completo que você pode acompanhar nessas aulas aqui.

Além de poder completar o projeto da página do Google, agora você também sabe o que é um popover, como criar um e utilizá-lo para diferentes projetos que você quiser criar.

Hashtag Treinamentos

Para acessar publicações de HTML e CSS, clique aqui!


Quer aprender mais sobre HTML e CSS com um minicurso básico gratuito?