É muito comum ter em nossos sites botões e links que ao serem clicados rolem até uma determinada seção da nossa página, aqui vou ensinar como implementar essa funcionalide com HTML, CSS e JavaScript Puro.
A qualidade ficou péssima mas a ideia é essa.
HTML
Primeiro vamos criar nosso arquivo HTML:
1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8"> 6 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 7 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 8 <title>Scroll to Element</title> 9</head> 10 11<body> 12 13</body> 14 15</html> 16
Agora vamos inserir nosso header com os botões para fazer a rolagem, a estrutura que você vai utilizar aqui é o de menos, fique atento apenas com o atributo personalidado que vamos utilizar, neste caso o data-scroll-to
, ele que vai dizer para qual elemento vamos realizar a rolagem quando o botão for clicado.
1<header class="header"> 2 <h1>Logo</h1> 3 <nav class="navbar"> 4 <ul class="navbar__list"> 5 <li class="navbar__item"> 6 <button class="navbar__action active" data-scroll-to="services">Services</button> 7 </li> 8 <li class="navbar__item"> 9 <button class="navbar__action" data-scroll-to="projects">Projects</button> 10 </li> 11 <li class="navbar__item"> 12 <button class="navbar__action" data-scroll-to="contact">Contact</button> 13 </li> 14 </ul> 15 </nav> 16</header> 17
Em seguida vamos inserir as seções da nossa página, novamente foque apenas no atributo personalizado, nesse caso o data-scroll
, ele vai funcionar como um identificador para quando a rolagem acontecer.
1<main> 2 <section class="section services" data-scroll="services">Services</section> 3 <section class="section projects" data-scroll="projects">Projects</section> 4 <section class="section contact" data-scroll="contact">Contact</section> 5</main> 6
Adicione também um link para sua folha de estilos e no final da página um script
.
No meu caso, o HTML final ficou assim:
1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8"> 6 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 7 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 8 <link rel="stylesheet" href="style.css" /> 9 <title>Scroll to Element</title> 10</head> 11 12<body> 13 14 <header class="header"> 15 <h1>Logo</h1> 16 17 <nav class="navbar"> 18 <ul class="navbar__list"> 19 <li class="navbar__item"> 20 <button class="navbar__action active" data-scroll-to="services">Services</button> 21 </li> 22 <li class="navbar__item"> 23 <button class="navbar__action" data-scroll-to="projects">Projects</button> 24 </li> 25 <li class="navbar__item"> 26 <button class="navbar__action" data-scroll-to="contact">Contact</button> 27 </li> 28 </ul> 29 </nav> 30 </header> 31 32 <div class="spacer"></div> 33 34 <main> 35 <section class="section services" data-scroll="services">Services</section> 36 <section class="section projects" data-scroll="projects">Projects</section> 37 <section class="section contact" data-scroll="contact">Contact</section> 38 </main> 39 40 <footer class="footer"> 41 <p>© SkyG0D - 2021</p> 42 </footer> 43 44 <script src="script.js"></script> 45</body> 46 47</html> 48
Alguns elementos são apenas para estilização, como o footer
e a div
com a classe spacer.
CSS
A estilização é o que menos importa, então vou pular os detalhes sobre ela, mas minha estilização final ficou assim:
1/* Geral */ 2 3:root { 4 --header-height: 60px; 5} 6 7body { 8 padding: 0; 9 margin: 0; 10 font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 11 Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 12} 13 14/* Header */ 15 16.header { 17 position: fixed; 18 top: 0; 19 left: 0; 20 right: 0; 21 background-color: #191919; 22 height: var(--header-height); 23 color: #fff; 24 display: flex; 25 align-items: center; 26 justify-content: space-between; 27 padding: 0 1rem; 28} 29 30.navbar__list { 31 list-style: none; 32 padding: 0; 33 display: flex; 34} 35 36.navbar__item { 37 padding: 0.5rem 1rem; 38} 39 40.navbar__action { 41 background-color: transparent; 42 border: none; 43 color: inherit; 44 font-size: 1.2rem; 45 text-decoration: none; 46 cursor: pointer; 47} 48 49.navbar__action:hover { 50 text-decoration: underline; 51} 52 53.navbar__action.active { 54 color: greenyellow; 55} 56 57.navbar__link:hover { 58 text-decoration: underline; 59} 60 61.spacer { 62 height: var(--header-height); 63} 64 65/* Main */ 66 67.section { 68 width: 100%; 69 height: 85vh; 70 font-size: 3rem; 71 display: flex; 72 align-items: center; 73 justify-content: center; 74} 75 76.services { 77 background-color: crimson; 78 color: #fff; 79} 80 81.projects { 82 background-color: rebeccapurple; 83 color: #fff; 84} 85 86.contact { 87 background-color: mediumaquamarine; 88 color: #fff; 89} 90 91/* Footer */ 92 93.footer { 94 background-color: #101010; 95 color: #fff; 96 display: flex; 97 align-items: center; 98 justify-content: center; 99 height: 80px; 100} 101
JavaScript
A parte mais legal vem agora, vamos criar a função que realiza a rolagem para o elemento e alguns outros detalhes.
Vamos criar a função principal, ela vai receber um event
como atributo, pois vai ser acionada quando um botão for clicado, então vamos previnir o funcionamento padrão e em seguida pegar o atributo que contém a referência para onde queremos realizar a rolagem, no nosso caso o data-scroll-to
, então vamos procurar aquela aquela referência em nossas seções, caso não exista uma seção a função não faz nada, e quando existe utilizamos o método scrollIntoView para rolar até o elemento desejado:
1function handleScrollTo(event) { 2 event.preventDefault(); 3 4 const dataScroll = event.currentTarget.getAttribute('data-scroll-to'); 5 const $section = document.querySelector(`[data-scroll="${dataScroll}"]`); 6 7 if (!$section) { 8 return; 9 } 10 11 $section.scrollIntoView({ behavior: 'smooth', block: 'center' }); 12} 13
Pronto, a principal funcionalidade esta criada, basta adicioná-la aos botões:
1const $linksToScroll = [...document.querySelectorAll('[data-scroll-to]')]; 2 3$linksToScroll.forEach(($element) => { 4 $element.addEventListener('click', handleScrollTo); 5}); 6
Porém, também queremos indicar qual seção esta atualmente ativa, para isso, primeiro vamos criar funções para alterar dinamicamente a estilização de nossos botões.
1const ACTIVE_SCROLL_CSS_CLASS = 'active'; 2 3function desactiveAllElements() { 4 $linksToScroll.forEach(($link) => ( 5 $link.classList.remove(ACTIVE_SCROLL_CSS_CLASS) 6 )); 7} 8 9function activeElement(dataScroll) { 10 const $linkFound = $linksToScroll 11 .find(($link) => $link.getAttribute('data-scroll-to') === dataScroll); 12 13 desactiveAllElements(); 14 15 $linkFound.classList.add(ACTIVE_SCROLL_CSS_CLASS); 16} 17
A função desactiveAllElements
é bem simples, ela apenas remove a classe active de todos os nossos botões, já a activeElement
procura um determinado botão, chama a função desactiveAllElements
e adiciona a classe active para o botão encontrado, assim conseguimos adicionar dinamicamente estilos para nossos botões.
Para encerrar, vamos adicionar uma função que escuta sempre que acontecer uma rolagem na página, e então verifica qual das seções esta atualmente na tela.
1const SCROLL_OFFSET = 200; 2 3const $sections = [...document.querySelectorAll('[data-scroll]')]; 4 5function handleScroll() { 6 $sections.forEach(($section) => { 7 const sectionTop = $section.offsetTop - SCROLL_OFFSET; 8 9 if (scrollY >= sectionTop) { 10 const dataScroll = $section.getAttribute('data-scroll'); 11 12 activeElement(dataScroll); 13 } 14 }); 15} 16 17window.addEventListener('scroll', handleScroll); 18
A função itera por cada seção sempre que há uma rolagem, e verifica se a rolagem no eixo y é maior ou igual ao topo da nossa seção menos um deslocamento personalizado(esse valor pode ser alterado), e então chama nossa função activeElement
para a referência daquela seção.
O código final se parece com isso:
1const SCROLL_OFFSET = 200; 2const ACTIVE_SCROLL_CSS_CLASS = 'active'; 3 4const $sections = [...document.querySelectorAll('[data-scroll]')]; 5const $linksToScroll = [...document.querySelectorAll('[data-scroll-to]')]; 6 7function desactiveAllElements() { 8 $linksToScroll.forEach(($link) => ( 9 $link.classList.remove(ACTIVE_SCROLL_CSS_CLASS) 10 )); 11} 12 13function activeElement(dataScroll) { 14 const $linkFound = $linksToScroll 15 .find(($link) => $link.getAttribute('data-scroll-to') === dataScroll); 16 17 desactiveAllElements(); 18 19 $linkFound.classList.add(ACTIVE_SCROLL_CSS_CLASS); 20} 21 22function handleScrollTo(event) { 23 event.preventDefault(); 24 25 const dataScroll = event.currentTarget.getAttribute('data-scroll-to'); 26 const $section = document.querySelector(`[data-scroll="${dataScroll}"]`); 27 28 if (!$section) { 29 return; 30 } 31 32 $section.scrollIntoView({ behavior: 'smooth', block: 'center' }); 33} 34 35function handleScroll() { 36 $sections.forEach(($section) => { 37 const sectionTop = $section.offsetTop - SCROLL_OFFSET; 38 39 if (scrollY >= sectionTop) { 40 const dataScroll = $section.getAttribute('data-scroll'); 41 42 activeElement(dataScroll); 43 } 44 }); 45} 46 47$linksToScroll.forEach(($element) => { 48 $element.addEventListener('click', handleScrollTo); 49}); 50 51window.addEventListener('scroll', handleScroll); 52
Caso queira diminuir a quantidade de chamadas a handleScroll
sabendo que rolar é uma ação que acontece muitas vezes, podemos utilizar uma função para realizar um debounce:
1const HANDLE_SCROLL_DEBOUNCE_TIME = 100; 2 3function debounce(fn, ms) { 4 let timerId; 5 6 return () => { 7 clearTimeout(timerId); 8 timerId = setTimeout(fn, ms); 9 }; 10} 11 12const handleScroll = debounce(() => { 13 $sections.forEach(($section) => { 14 const sectionTop = $section.offsetTop - SCROLL_OFFSET; 15 16 if (scrollY >= sectionTop) { 17 const dataScroll = $section.getAttribute('data-scroll'); 18 19 activeElement(dataScroll); 20 } 21 }); 22}, HANDLE_SCROLL_DEBOUNCE_TIME); 23
Você deve encontrar um valor interessante para o intervalo do seu debounce já que quanto maior o tempo do debounce, mais tempo a mudança no layout vai demorar pra ocorrer.
Conclusão
Aqui aprendemos a realizar uma rolagem para um determinado elemento com o JavaScript puro.
Caso você utilize apenas âncoras para elementos em seu site também é possível realizar a rolagem suave sem utilizar nenhum Javascript.