Eu tenho tentado dissociar meus projetos de dependências e bibliotecas o máximo que posso ultimamente. Isso não apenas me dá uma boa oportunidade de exercitar algumas habilidades que tenho negligenciado, mas também ter um controle extra sobre bugs e erros.
Minha mais recente aventura foi a necessidade de criar um botão Voltar ao topo que só aparecesse depois que um elemento específico estivesse na página e desaparecesse quando rolasse além de um contêiner de limite. Aqui estão os critérios básicos de aceitação:
- Um contêiner superior aciona o estado de visibilidade do botão para verdadeiro quando cruza a viewport
- Um contêiner inferior remove o estado visível do botão
- Apenas baunilha JS
- O botão Voltar ao Início apresenta uma transição sutil de opacidade ao alterar os estados
- O botão Voltar ao topo não pode ser clicado quando oculto
Com essas condições básicas, vamos esticar nossos músculos de codificação e iniciar essa solução!
Compreendendo o problema
Considerando que nossa marcação é composta de um botão Voltar ao topo #back-to-top
, um contêiner superior #section-top
e um contêiner inferior #section-bottom
, vamos começar armazenando referências de ponteiro para esses elementos DOM:
const btn = document.querySelector("#back-to-top");
const topSection = document.querySelector("#section-top");
const bottomSection = document.querySelector("#section-bottom");
Nossa primeira etapa é descobrir onde os dois elementos da seção cruzarão o fluxo de rolagem da página. Parece uma tarefa simples, pois você pode indicá-la, basta encontrar o valor da posição de rolagem em relação ao espaço vertical disponível na página! . Bem, esta é a solução em si, mas o JavasScript não fornece um método específico para encontrar esses valores imediatamente. Em vez disso, fornece muitos métodos auxiliares disponíveis no nível do objeto para lidar com esses tipos de cálculos.
const topScrollTreshold = topSection.getBoundingClientRect().top + window.pageYOffset
Uma coisa importante a considerar é que as propriedades de posição ( top
, left
e assim por diante) retornadas por esse método são relativas à viewport e não estão cientes da altura total da página, o que é fundamental para entendermos quando o botão Voltar ao topo deve mostrar-se. Para contornar esse problema, adicionamos o top
valor do nosso elemento à posição atual de rolagem acessando window.PageYOffset
. Os nomes de propriedades também são importantes aqui: window.pageYOffset
é o mesmo que window.scrollY
, mas possui suporte nativo no IE.
Usar box-sizing: border-box
é uma maneira de se livrar desse comportamento, mas, para o nosso caso, basta estar atento a essa regra.
Observando se já passamos dos contêineres
Como uma maneira de otimizar esse processo, adicionaremos uma lógica condicional à nossa, topScrollTreshold
para que ela retorne um valor booleano. Agora, estamos planejando tornar essas variáveis vivas dentro de um scroll
evento de janela; assim, toda vez que os usuários rolarem as variáveis serão verificadas e retornarão um novo conjunto de valores:
window.addEventListener("scroll", function (e) {
const topScrollTreshold = topSection.getBoundingClientRect().top + window.pageYOffset < window.pageYOffset;
}
Estamos adicionando nossa .top
propriedade de contêiner superior pageYOffset
para encontrar sua posição em relação à rolagem da página inteira e depois verificando se esse valor é menor que a posição de rolagem da página ( pageYOffset
). Para o contêiner inferior, a lógica tem uma pequena ressalva:
const bottomScrollTreshold = btn.getBoundingClientRect().top + window.pageYOffset > bottomSection.getBoundingClientRect().top + window.pageYOffset;
De volta aos nossos requisitos:
- Um contêiner inferior remove o estado visível do botão
Há uma chance de que nada fique abaixo do contêiner Inferior e nunca chegue ao topo da página . De acordo com a MDN:
A página de propriedade da janela somente leituraYOffset (…) retorna o número de pixels em que o documento está rolado no eixo vertical (ou seja, para cima ou para baixo) com um valor de 0,0, indicando que a borda superior do documento está alinhada com a borda superior da área de conteúdo da janela .
Esse é o problema de medir sua posição sozinho, como fizemos no contêiner Top. Verificar sua interseção com o botão Voltar ao topo é uma opção mais à prova de falhas nesse caso.
Um pouco de CSS
O CSS extra para fazer esse botão funcionar é bastante simples: em seu estado padrão (oculto), nenhum pointer-events
será observado e o valor opacity
será definido como zero. Também o posicionamos em fixed
relação à viewport:
.back-to-top {
position: fixed;
bottom: 5vh;
right: 5vw;
opacity: 0;
transition: opacity 0.5s;
pointer-events: none;
}
.back-to-top.visible {
opacity: 1;
pointer-events: all;
}
A .visible
classe descreve seu estado ativo quando é clicável e totalmente opaco.
Juntando tudo
As etapas finais estão adicionando uma lógica mais simples para acionar o estado do botão com base no resultado final dos verificadores de limite:
//setting visibility based on scroll pos
if (topScrollTreshold) {
btn.classList.add("visible");
} else if (btn.classList.contains("visible")) {
btn.classList.remove("visible");
}
if (bottomScrollTreshold) {
btn.classList.remove("visible");
}
Veja o código abaixo: