Menu fechado

Desbloqueie o Poder da Scroll-Driven Animation

scroll-driven

Introdução ao Projeto de Animação de Grid com Scroll

Desafio e Objetivos

O objetivo principal deste projeto é desenvolver uma animação de grid controlada pelo scroll, utilizando tecnologias como GSAP, ScrollTrigger e Lenis, proporcionando uma experiência de usuário agradável e imersiva.

Arquitetura do Projeto

A estrutura do projeto é composta por uma combinação de HTML, CSS e JavaScript, onde o HTML fornece a estrutura básica, o CSS define a aparência e a posição dos elementos, e o JavaScript é utilizado para criar a animação controlada pelo scroll.

Estrutura HTML

Sticky Grid Scroll

...

CSS Setup

html {
  font-size: calc(100vw / 1440);
}

body {
  font-size: 1.6rem;
}

.block.block--main {
  height: 425vh;
}

.block__wrapper {
  position: sticky;
  top: 0;
  padding: 0 24rem;
  overflow: hidden;
}

.content {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100vh;
  text-align: center;
  z-index: 1;
}

.gallery {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 73.6rem;
}

.gallery__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  column-gap: 3.2rem;
  row-gap: 4rem;
}

.gallery__item {
  width: 100%;
  aspect-ratio: 1;
}

.gallery__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

JavaScript + GSAP + Lenis

O JavaScript é responsável por criar a animação controlada pelo scroll, utilizando as bibliotecas GSAP, ScrollTrigger e Lenis. A implementação inclui:




  • Configuração do Lenis para controle suave do scroll.
  • Utilização do GSAP para animações e efeitos.
  • Integração do ScrollTrigger para animações baseadas no scroll.
import Lenis from "lenis";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { preloadImages } from "./utils.js";

gsap.registerPlugin(ScrollTrigger);

function initSmoothScrolling() {
  const lenis = new Lenis({
    lerp: 0.08,
    wheelMultiplier: 1.4,
  });
  lenis.on("scroll", ScrollTrigger.update);
  gsap.ticker.add((time) => {
    lenis.raf(time * 1000);
  });
  gsap.ticker.lagSmoothing(0);
}

preloadImages().then(() => {
  document.body.classList.remove("loading");
  initSmoothScrolling();
});

class StickyGridScroll {
  constructor() {
    this.getElements();
    this.initContent();
    this.groupItemsByColumn();
    this.addParallaxOnScroll();
    this.animateTitleOnScroll();
    this.animateGridOnScroll();
  }

  getElements() {
    this.block = document.querySelector(".block--main");
    if (this.block) {
      this.wrapper = this.block.querySelector(".block__wrapper");
      this.content = this.block.querySelector(".content");
      this.title = this.block.querySelector(".content__title");
      this.description = this.block.querySelector(".content__description");
      this.button = this.block.querySelector(".content__button");
      this.grid = this.block.querySelector(".gallery__grid");
      this.items = this.block.querySelectorAll(".gallery__item");
    }
  }

  initContent() {
    if (this.description && this.button) {
      gsap.set([this.description, this.button], {
        opacity: 0,
        pointerEvents: "none",
      });
    }
    if (this.content && this.title) {
      const dy = (this.content.offsetHeight - this.title.offsetHeight) / 2;
      this.titleOffsetY = (dy / this.content.offsetHeight) * 100;
      gsap.set(this.title, { yPercent: this.titleOffsetY });
    }
  }

  groupItemsByColumn() {
    this.numColumns = 3;
    this.columns = Array.from({ length: this.numColumns }, () => []);
    this.items.forEach((item, index) => {
      this.columns[index % this.numColumns].push(item);
    });
  }

  animateGridOnScroll() {
    const timeline = gsap.timeline({
      scrollTrigger: {
        trigger: this.block,
        start: "top 25%",
        end: "bottom bottom",
        scrub: true,
      },
    });
    timeline
      .add(this.gridRevealTimeline())
      .add(this.gridZoomTimeline(), "-=0.6")
      .add(() => this.toggleContent(timeline.scrollTrigger.direction === 1), "-=0.32");
  }

  gridRevealTimeline(columns = this.columns) {
    const timeline = gsap.timeline();
    const wh = window.innerHeight;
    const dy = wh - (wh - this.grid.offsetHeight) / 2;
    columns.forEach((column, colIndex) => {
      const fromTop = colIndex % 2 === 0;
      timeline.from(column, {
        y: dy * (fromTop ? -1 : 1),
        stagger: {
          each: 0.06,
          from: fromTop ? "end" : "start",
        },
        ease: "power1.inOut",
      }, "grid-reveal");
    });
    return timeline;
  }

  gridZoomTimeline(columns = this.columns) {
    const timeline = gsap.timeline({ defaults: { duration: 1, ease: "power3.inOut" } });
    timeline
      .to(this.grid, { scale: 2.05 })
      .to(columns[0], { xPercent: -40 }, "<")
      .to(columns[2], { xPercent: 40 }, "<")
      .to(columns[1], {
        yPercent: (index) => (index < Math.floor(columns[1].length / 2) ? -40 : 40),
        duration: 0.5,
        ease: "power1.inOut",
      }, "-=0.5");
    return timeline;
  }

  toggleContent(isVisible = true) {
    if (!this.title || !this.description || !this.button) {
      return;
    }
    gsap.timeline({ defaults: { overwrite: true } })
      .to(this.title, {
        yPercent: isVisible ? 0 : this.titleOffsetY,
        duration: 0.7,
        ease: "power2.inOut",
      })
      .to([this.description, this.button], {
        opacity: isVisible ? 1 : 0,
        duration: 0.4,
        ease: power1.${isVisible ? "inOut" : "out"},
        pointerEvents: isVisible ? "all" : "none",
      }, isVisible ? "-=0.9" : "-=0.4");
  }

  addParallaxOnScroll() {
    if (!this.block || !this.wrapper) {
      return;
    }
    gsap.from(this.wrapper, {
      yPercent: -100,
      ease: "none",
      scrollTrigger: {
        trigger: this.block,
        start: "top bottom",
        end: "top top",
        scrub: true,
      },
    });
  }

  animateTitleOnScroll() {
    if (!this.block || !this.title) {
      return;
    }
    gsap.from(this.title, {
      opacity: 0,
      duration: 0.7,
      ease: "power1.out",
      scrollTrigger: {
        trigger: this.block,
        start: "top 57%",
        toggleActions: "play none none reset",
      },
    });
  }
}

new StickyGridScroll();

Estrutura do Projeto

Classe StickyGridScroll

A classe StickyGridScroll gerencia a animação da grid, incluindo a revelação do grid, o zoom do grid e a troca de conteúdo.

class StickyGridScroll {
  constructor() {
    this.getElements();
    this.initContent();
    this.groupItemsByColumn();
    this.addParallaxOnScroll();
    this.animateTitleOnScroll();
    this.animateGridOnScroll();
  }
}

Fonte: Documentação do Projeto de Animação de Grid com Scroll.



Redação YTI&W-News

Redação Developers | Yassutaro TI & Web

Notícias do universo do Desenvolvimento Web, dicas e tutoriais para Webmasters.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Publicado em:Desenvolvimento Web,LiteSpeed,SEO
Fale Conosco
×

Inscreva-se em nossa Newsletter!


Receba nossos lançamentos e artigos em primera mão!