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

Scroll-Driven Animations, corner-shape e CSS Nativo: Cobrindo as Lacunas que o Ecossistema JS Criou

O dia em que removi 14.3KB de JavaScript e a animação ficou mais fluida

Em março de 2024, herdei o front-end de um e-commerce com 380k visitas/mês. A página de produto tinha um header que encolhia ao rolar, cards com cantos arredondados customizados via SVG clip-path gerado por JS, e uma timeline de scroll com Intersection Observer + requestAnimationFrame. Três bibliotecas (framer-motion, react-intersection-observer e um utilitário custom de 2.1KB) controlavam tudo isso.

O Lighthouse da página marcava 67 em performance no mobile. O TBT (Total Blocking Time) era 480ms, e o CLS batia 0.18 por causa de reflows durante a animação do header.

Substituí tudo por CSS puro: animation-timeline: scroll(), corner-shape (via progressive enhancement) e @keyframes nativos. O resultado: Lighthouse subiu para 89, TBT caiu para 120ms, CLS zerou. O bundle JS do componente de produto foi de 14.3KB gzipped para zero.

Esse post cobre as três lacunas que tornaram essa migração possível.

Scroll-Driven Animations: o que muda na prática

A spec Scroll-driven Animations (Level 1, Chromium 115+) introduz duas primitivas: scroll progress timelines e view progress timelines. A diferença importa.

Scroll progress timeline vincula a progressão de uma animação à posição de scroll de um contêiner. View progress timeline vincula a progressão ao quanto um elemento está visível dentro do viewport (ou de outro contêiner de scroll).

Antes dessa spec, a única forma de fazer isso era ouvir o evento scroll, calcular porcentagens manualmente e aplicar transforms via JS. O problema: o evento scroll dispara na main thread. Qualquer cálculo pesado nesse handler bloqueia o compositor, e a animação engasga.

Com animation-timeline, a animação roda inteiramente no compositor thread do navegador. Sem JavaScript. Sem reflow. Sem jank.

O header que encolhe ao rolar

/* header-shrink.css */
/* Por que @keyframes e não transition: porque precisamos de
   múltiplos estágios de transformação vinculados ao progresso
   do scroll, não a um estado binário hover/active */
@keyframes header-shrink {
  from {
    height: 80px;
    background-color: rgba(255, 255, 255, 0);
    backdrop-filter: blur(0px);
  }
  to {
    height: 48px;
    background-color: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(12px);
  }
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  animation: header-shrink linear both;
  /* scroll() sem argumentos usa o nearest scroll ancestor,
     que neste caso é o root scroller (html) */
  animation-timeline: scroll();
  /* O header deve completar a animação nos primeiros 200px
     de scroll, não ao longo da página inteira */
  animation-range: 0px 200px;
}
/* fallback para navegadores sem suporte */
/* Por que @supports e não feature detection via JS:
   porque o fallback é puramente visual, não funcional */
@supports not (animation-timeline: scroll()) {
  .site-header {
    height: 48px;
    background-color: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(12px);
    /* Sem animação: o header fica no estado final.
       Melhor que carregar um polyfill de 8KB */
  }
}

O animation-range: 0px 200px é o detalhe que a maioria dos tutoriais ignora. Sem ele, a animação se distribui por toda a altura scrollável do documento. Em uma página de produto com 4000px de conteúdo, o header levaria a página inteira para encolher, o que é inútil.

View timeline: revelando cards ao entrar no viewport

/* card-reveal.css */
@keyframes card-reveal {
  from {
    opacity: 0;
    transform: translateY(40px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.product-card {
  animation: card-reveal linear both;
  animation-timeline: view();
  /* entry 0% = o elemento começa a entrar no viewport
     entry 100% = o elemento está totalmente dentro.
     Queremos que a animação complete quando 40% do card
     estiver visível, nã

---

Leia o artigo completo em [https://vivodecodigo.com.br/react/scroll-driven-animations-corner-shape-css-nativo](https://vivodecodigo.com.br/react/scroll-driven-animations-corner-shape-css-nativo)
Carregando publicação patrocinada...