Skip to content

feat: estrutura de animação (motion base)#73

Open
Luana-lua wants to merge 10 commits intomainfrom
feature/motion-base
Open

feat: estrutura de animação (motion base)#73
Luana-lua wants to merge 10 commits intomainfrom
feature/motion-base

Conversation

@Luana-lua
Copy link
Copy Markdown

@Luana-lua Luana-lua commented Mar 15, 2026

📝 Resumo das Mudanças
Este PR estabelece a base de animação (Motion Base) do projeto, criando uma camada de abstração sobre o GSAP. O objetivo é padronizar a implementação de animações, garantindo que sejam reutilizáveis e livres de vazamento de memória.

🛠 Detalhes Técnicos & Impacto

  1. Hooks de Abstração (Pasta '/hooks')
  • useScrollTrigger: Hook base para animações de entrada simples (Fade/Translate). Abstrai a configuração repetitiva do ScrollTrigger.
  • useStaggerScroll: Implementa animações em cascata para listas de elementos, permitindo controlar o stagger via props.
  • useScrollTimeline: Fornece uma instância de Timeline orquestrada, permitindo que o componente defina sequências complexas sem poluir o ciclo de vida do React.
  • useScrollPin: Hook especializado para fixação de elementos (Pinning) e sincronização com o progresso do scroll (Scrubbing).
  1. Ciclo de Vida e Performance
  • Utilização do hook @gsap/react (useGSAP) para garantir o cleanup automático. Todas as instâncias de ScrollTrigger são invalidadas ao desmontar o componente, prevenindo degradação de performance em SPAs.
  1. Protótipos de Validação (Pasta '/components')
  • MotionDemo: Centraliza os testes visuais das animações de entrada e stagger.
  • LaptopSection: Um "stress test" de performance utilizando Pin + Scrub para simular uma abertura de device em 3D (CSS perspective).

🧪 Como Validar (Passo a Passo)
Para validar estas alterações, siga os passos abaixo:

  1. Instalação: Execute npm install para instalar as dependências (caso não estejam instaladas).
  2. Execução: Inicie o servidor de desenvolvimento com 'npm run dev'.
  3. Teste de Interação:
    • Nas primeiras linhas do arquivo App.tsx, importe os componentes MotionDemo e LaptopSection dessa forma:
      import MotionDemo from './components/MotionDemo';
      import LaptopSection from './components/LaptopSection';
    • localize o bloco de retorno e certifique-se de que os componentes MotionDemo e LaptopSection estão inseridos dentro de uma tag div comum, um logo abaixo do outro. O resultado final deve mostrar o componente MotionDemo seguido do componente LaptopSection, ambos envolvidos por um elemento pai.
    • Scroll Simples: Verifique se os elementos do MotionDemo surgem com suavidade ao entrar na viewport.
    • Scroll Stagger: Note se os cards aparecem um após o outro (efeito cascata).
    • Teste de Pin: Na seção do Laptop, confirme se a seção "trava" na tela enquanto faz scroll e se o laptop abre/fecha proporcionalmente à velocidade do seu movimento de scroll.
  4. Verificação de Performance: Ao navegar para fora ou recarregar, verifique no console se não existem erros de instâncias órfãs do GSAP.

@Luana-lua Luana-lua requested a review from oEnzoRibas March 19, 2026 00:44
@oEnzoRibas oEnzoRibas linked an issue Mar 19, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Member

@oEnzoRibas oEnzoRibas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oi @Luana-lua ,
Você poderia melhorar a descrição deste PR, incluindo mais detalhes sobre as mudanças e o impacto delas no projeto? Além disso, seria importante adicionar uma seção explicando como testar as alterações, passo a passo, para que os revisores consigam validar o funcionamento corretamente.

Quando adicionar, só solicitar minha revisão novamente.

Obrigado!

@oEnzoRibas oEnzoRibas requested review from Copilot and oEnzoRibas and removed request for oEnzoRibas March 21, 2026 20:57
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR cria a “Motion Base” do projeto: uma camada de hooks para padronizar animações de scroll com GSAP/ScrollTrigger, além de componentes de demo para validação visual e stress test.

Changes:

  • Adiciona hooks de animação com ScrollTrigger (useScrollTrigger, useStaggerScroll, useScrollTimeline, useScrollPin).
  • Adiciona componentes de demonstração (MotionDemo, LaptopSection) para validar efeitos (fade/translate, stagger, timeline, pin/scrub).
  • Atualiza App.tsx para importar os componentes de demo (com bloco comentado) e atualiza package-lock.json.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
lp-code/src/hooks/scrollHooks.ts Introduz hooks de abstração para animações com ScrollTrigger e timeline/pin.
lp-code/src/components/MotionDemo.tsx Implementa página de demo para validar animações simples, stagger e timeline.
lp-code/src/components/LaptopSection.tsx Implementa demo de pin + scrub com timeline (stress test).
lp-code/src/App.tsx Importa componentes de demo e adiciona bloco de instruções/comentários (atualmente dentro do JSX).
lp-code/package-lock.json Ajustes no lockfile (metadados como peer).
Files not reviewed (1)
  • lp-code/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Member

@oEnzoRibas oEnzoRibas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Os hooks funcionaram bem, mas faltou ter responsividade para Desktop.

Ainda é preciso:

  • Documentar os componentes e hooks com dockstring para melhor versionamento e registro
  • Fazer testes para cada componente utilizado, vide exemplo em #71



//hook animacao stagger p multiplos elementos
export const useStaggerScroll = (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adicionada nova task para melhorar a flexibilidade desse hook, em #74. Pode incluir no mesmo PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Só adicionar nos testes.

@oEnzoRibas oEnzoRibas linked an issue Mar 28, 2026 that may be closed by this pull request
@Luana-lua
Copy link
Copy Markdown
Author

Concluí as alterações solicitadas na revisão:

Hooks personalizados: Implementada a Task #74 no useStaggerScroll (agora aceita scrollVars) e removido o uso de any no useScrollPin, que agora retorna a tl (timeline) de forma tipada

Responsividade: Adicionado suporte a Mobile/Tablet através de ajustes de layout via Tailwind (grid adaptável e tipografia) e lógica condicional nos hooks

Segurança (TypeScript): Adicionadas guards (null checks) para garantir que os refs existam antes de iniciar as animações, evitando erros em Strict Mode

Documentação: Todos os hooks e componentes agora possuem Docstrings

@Luana-lua Luana-lua requested a review from oEnzoRibas April 4, 2026 04:04
@Code-EJ Code-EJ deleted a comment from Copilot AI Apr 4, 2026
@Code-EJ Code-EJ deleted a comment from Copilot AI Apr 4, 2026
@Code-EJ Code-EJ deleted a comment from Copilot AI Apr 4, 2026
Copy link
Copy Markdown
Member

@oEnzoRibas oEnzoRibas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oi, @Luana-lua! Ficou muito bom, especialmente o notebook. Agora só preciso dos testes unitários utilizando a @testing-library/react. Achei que já tinha pedido isso no outro review, mas aparentemente me esqueci.
O @pedrolucasvsr está utilizando essa biblioteca também, na branch dele tem um exemplo que eu deixei de como fazer os testes para cada componente e hook.
Só clicar aqui.

Um exemplo do formato do teste esperado:

import { render, screen, fireEvent } from "@testing-library/react";
import { Button } from "./Button";

/**
 * Test suite do componente Button.
 * Garante renderização correta, comportamento de clique e estado desabilitado.
 */
describe("Button", () => {
  /**
   * Deve renderizar o texto passado como children no botão.
   */
  it("renderiza o texto corretamente", () => {
    render(<Button>Salvar</Button>);
    expect(screen.getByText("Salvar")).toBeInTheDocument();
  });

  /**
   * Deve chamar a função onClick exatamente uma vez ao clicar no botão.
   */
  it("chama onClick quando clicado", () => {
    const handleClick = vi.fn();

    render(<Button onClick={handleClick}>Clique</Button>);
    fireEvent.click(screen.getByText("Clique"));

    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  /**
   * Deve impedir interação quando o botão estiver desabilitado.
   */
  it("está desabilitado quando disabled é true", () => {
    render(<Button disabled>Desativado</Button>);
    expect(screen.getByRole("button")).toBeDisabled();
  });
});

e para hooks:

import { render, screen, fireEvent } from "@testing-library/react";
import { Button } from "./Button";

/**
 * Test suite do componente Button.
 * Garante renderização correta, comportamento de clique e estado desabilitado.
 */
describe("Button", () => {
  /**
   * Deve renderizar o texto passado como children no botão.
   */
  it("renderiza o texto corretamente", () => {
    render(<Button>Salvar</Button>);
    expect(screen.getByText("Salvar")).toBeInTheDocument();
  });

  /**
   * Deve chamar a função onClick exatamente uma vez ao clicar no botão.
   */
  it("chama onClick quando clicado", () => {
    const handleClick = vi.fn();

    render(<Button onClick={handleClick}>Clique</Button>);
    fireEvent.click(screen.getByText("Clique"));

    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  /**
   * Deve impedir interação quando o botão estiver desabilitado.
   */
  it("está desabilitado quando disabled é true", () => {
    render(<Button disabled>Desativado</Button>);
    expect(screen.getByRole("button")).toBeDisabled();
  });
});



//hook animacao stagger p multiplos elementos
export const useStaggerScroll = (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Só adicionar nos testes.

Convert scroll hooks to generic, typed refs and add dependency tracking for useGSAP calls. useScrollTrigger, useScrollTimeline and useScrollPin now accept generic HTMLElement types, perform safer null checks, and include dependencies so animations update correctly. useStaggerScroll was refactored to take a containerRef plus from/to animation vars and uses gsap.fromTo with stagger and overwrite handling (and no longer creates an internal ref). MotionDemo was updated to use the new APIs: added cardsRef, updated useStaggerScroll invocation, switched timeline step to scaleX with transformOrigin, increased card count/styling (9 cards, binary labels, style tweaks), and minor layout/accessibility tweaks (will-change hint). These changes improve type safety, reusability, and correct stagger/scroll-trigger behavior.
@oEnzoRibas
Copy link
Copy Markdown
Member

Overview

Aproveitei pra me familirizar um pouco mais com o GSAP, então corrigi um bug da animação do hook de stagger.
A animação não estava sendo distribuida em todos os cards, e só rodava na ordem correta na primeira vez.

O que foi feito:

Arquitetura e Hooks (scrollHooks.ts):

  • Remoção de Anti-patterns: Removida a instanciação manual do gsap.context() e seleções com querySelectorAll. O próprio useGSAP agora gerencia o escopo e o cleanup nativamente, o que é mais seguro para o React Strict Mode.

  • Prevenção de Stale Closures: Adicionado o array de dependencies nos hooks useGSAP para garantir que as animações sejam re-calculadas corretamente se as props ou variáveis de configuração mudarem dinamicamente.

  • Tipagem Flexível (Generics): Substituição do tipo engessado HTMLDivElement por Generics (), permitindo que os hooks sejam acoplados a qualquer elemento semântico (section, ul, button, etc). (Recomendo que essa prática seja adotada por todos)

Bugfix Crítico:

  • Correção do State Corruption no Stagger: O hook useStaggerScroll foi refatorado para utilizar gsap.fromTo() em vez de gsap.from(). Isso resolve o bug onde a animação ficava quebrada ou fora de ordem ao fazer o scroll rapidamente para cima e para baixo. Adicionado também overwrite: "auto" para matar conflitos de tweens na memória.

  • Performance e Clean Code (MotionDemo.tsx):

  • Prevenção de Layout Thrashing (Reflow): A animação da linha no Demo 3 foi alterada de width (que aciona cálculos de CPU do browser quadro-a-quadro) para scaleX com transformOrigin, rodando 100% na GPU.

  • Otimização do Composite Layer: A classe utilitária will-change-transform foi movida do container pai direto para os .card-item, evitando uso desnecessário de memória de vídeo pelo browser.

Anexos:

Antes:

Desktop.2026.04.04.-.11.43.57.04.mp4

Depois:

Desktop.2026.04.04.-.11.52.54.07.mp4

Problemas sabidos

Ainda existem alguns bugs da animação no Stagger, que precisão de manuntenção futura.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix useStaggerScroll flexibility Motion Base (Infraestrutura de Animação)

3 participants