Executando verificação de segurança...
1

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>&copy; 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.