Scroll To Element com HTML, CSS e JavaScript Puro.
É 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:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scroll to Element</title>
</head>
<body>
</body>
</html>
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.
<header class="header">
<h1>Logo</h1>
<nav class="navbar">
<ul class="navbar__list">
<li class="navbar__item">
<button class="navbar__action active" data-scroll-to="services">Services</button>
</li>
<li class="navbar__item">
<button class="navbar__action" data-scroll-to="projects">Projects</button>
</li>
<li class="navbar__item">
<button class="navbar__action" data-scroll-to="contact">Contact</button>
</li>
</ul>
</nav>
</header>
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.
<main>
<section class="section services" data-scroll="services">Services</section>
<section class="section projects" data-scroll="projects">Projects</section>
<section class="section contact" data-scroll="contact">Contact</section>
</main>
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:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css" />
<title>Scroll to Element</title>
</head>
<body>
<header class="header">
<h1>Logo</h1>
<nav class="navbar">
<ul class="navbar__list">
<li class="navbar__item">
<button class="navbar__action active" data-scroll-to="services">Services</button>
</li>
<li class="navbar__item">
<button class="navbar__action" data-scroll-to="projects">Projects</button>
</li>
<li class="navbar__item">
<button class="navbar__action" data-scroll-to="contact">Contact</button>
</li>
</ul>
</nav>
</header>
<div class="spacer"></div>
<main>
<section class="section services" data-scroll="services">Services</section>
<section class="section projects" data-scroll="projects">Projects</section>
<section class="section contact" data-scroll="contact">Contact</section>
</main>
<footer class="footer">
<p>© SkyG0D - 2021</p>
</footer>
<script src="script.js"></script>
</body>
</html>
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:
/* Geral */
:root {
--header-height: 60px;
}
body {
padding: 0;
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
/* Header */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: #191919;
height: var(--header-height);
color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1rem;
}
.navbar__list {
list-style: none;
padding: 0;
display: flex;
}
.navbar__item {
padding: 0.5rem 1rem;
}
.navbar__action {
background-color: transparent;
border: none;
color: inherit;
font-size: 1.2rem;
text-decoration: none;
cursor: pointer;
}
.navbar__action:hover {
text-decoration: underline;
}
.navbar__action.active {
color: greenyellow;
}
.navbar__link:hover {
text-decoration: underline;
}
.spacer {
height: var(--header-height);
}
/* Main */
.section {
width: 100%;
height: 85vh;
font-size: 3rem;
display: flex;
align-items: center;
justify-content: center;
}
.services {
background-color: crimson;
color: #fff;
}
.projects {
background-color: rebeccapurple;
color: #fff;
}
.contact {
background-color: mediumaquamarine;
color: #fff;
}
/* Footer */
.footer {
background-color: #101010;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
height: 80px;
}
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:
function handleScrollTo(event) {
event.preventDefault();
const dataScroll = event.currentTarget.getAttribute('data-scroll-to');
const $section = document.querySelector(`[data-scroll="${dataScroll}"]`);
if (!$section) {
return;
}
$section.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
Pronto, a principal funcionalidade esta criada, basta adicioná-la aos botões:
const $linksToScroll = [...document.querySelectorAll('[data-scroll-to]')];
$linksToScroll.forEach(($element) => {
$element.addEventListener('click', handleScrollTo);
});
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.
const ACTIVE_SCROLL_CSS_CLASS = 'active';
function desactiveAllElements() {
$linksToScroll.forEach(($link) => (
$link.classList.remove(ACTIVE_SCROLL_CSS_CLASS)
));
}
function activeElement(dataScroll) {
const $linkFound = $linksToScroll
.find(($link) => $link.getAttribute('data-scroll-to') === dataScroll);
desactiveAllElements();
$linkFound.classList.add(ACTIVE_SCROLL_CSS_CLASS);
}
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.
const SCROLL_OFFSET = 200;
const $sections = [...document.querySelectorAll('[data-scroll]')];
function handleScroll() {
$sections.forEach(($section) => {
const sectionTop = $section.offsetTop - SCROLL_OFFSET;
if (scrollY >= sectionTop) {
const dataScroll = $section.getAttribute('data-scroll');
activeElement(dataScroll);
}
});
}
window.addEventListener('scroll', handleScroll);
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:
const SCROLL_OFFSET = 200;
const ACTIVE_SCROLL_CSS_CLASS = 'active';
const $sections = [...document.querySelectorAll('[data-scroll]')];
const $linksToScroll = [...document.querySelectorAll('[data-scroll-to]')];
function desactiveAllElements() {
$linksToScroll.forEach(($link) => (
$link.classList.remove(ACTIVE_SCROLL_CSS_CLASS)
));
}
function activeElement(dataScroll) {
const $linkFound = $linksToScroll
.find(($link) => $link.getAttribute('data-scroll-to') === dataScroll);
desactiveAllElements();
$linkFound.classList.add(ACTIVE_SCROLL_CSS_CLASS);
}
function handleScrollTo(event) {
event.preventDefault();
const dataScroll = event.currentTarget.getAttribute('data-scroll-to');
const $section = document.querySelector(`[data-scroll="${dataScroll}"]`);
if (!$section) {
return;
}
$section.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
function handleScroll() {
$sections.forEach(($section) => {
const sectionTop = $section.offsetTop - SCROLL_OFFSET;
if (scrollY >= sectionTop) {
const dataScroll = $section.getAttribute('data-scroll');
activeElement(dataScroll);
}
});
}
$linksToScroll.forEach(($element) => {
$element.addEventListener('click', handleScrollTo);
});
window.addEventListener('scroll', handleScroll);
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:
const HANDLE_SCROLL_DEBOUNCE_TIME = 100;
function debounce(fn, ms) {
let timerId;
return () => {
clearTimeout(timerId);
timerId = setTimeout(fn, ms);
};
}
const handleScroll = debounce(() => {
$sections.forEach(($section) => {
const sectionTop = $section.offsetTop - SCROLL_OFFSET;
if (scrollY >= sectionTop) {
const dataScroll = $section.getAttribute('data-scroll');
activeElement(dataScroll);
}
});
}, HANDLE_SCROLL_DEBOUNCE_TIME);
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.