Pular para o conteúdo
Tempo limitadoO Starter agora custa US$ 9 por mês em vez de US$ 20.Ver planos
charmotion

Animação de logo e mascote guiada por rolagem: CSS, Rive e vídeo

Por equipe Charmotion · Atualizado em

Ilustração de capa do guia: Animação de logo e mascote com scroll: CSS, Rive e vídeo, com personagens de demonstração do Charmotion

Para um logo que se move com o scroll, use animações CSS controladas por scroll: animation-timeline com scroll() ou view(), animando transform e opacity. Em novembro de 2026, elas funcionam nas versões atuais do Chrome, Edge, Firefox e Safari, então envolva-as em @supports e deixe os navegadores antigos mostrarem o logo em repouso. Para um mascote em Rive, associe o progresso do scroll a um número com data binding.

Principais pontos

  • As animações CSS controladas por scroll viraram Baseline (newly available) em outubro de 2026: Chrome e Edge 115, Firefox 159 e Safari 26 suportam animation-timeline.
  • Use view() para animar um elemento enquanto ele atravessa a viewport e scroll() para ligar o movimento ao quanto a página já rolou. Declare animation-timeline depois do shorthand animation.
  • Para revelações que tocam uma vez, um Intersection Observer que inicia um vídeo ou dispara um trigger do Rive é mais simples e também funciona em navegadores antigos.
  • O Rive tornou scrub() obsoleto no runtime v2.41.0. Em vez disso, controle um blend state 1D a partir de um número com data binding.
  • Coloque toda animação de scroll atrás de prefers-reduced-motion: no-preference e faça do estado estático o logo finalizado, em repouso.

Já tem um personagem ou logo? Veja exemplos em telas reais de app: telas de splash, onboarding, carregamento e estado vazio, com a animação que toca em cada uma.

Qual técnica de animação com scroll serve para um logo ou mascote?

Há dois efeitos diferentes que as pessoas chamam de "animação com scroll", e eles exigem ferramentas diferentes:

  • Vinculada ao scroll (scrubbed): o progresso da animação acompanha a posição do scroll. Role de volta para cima e ela toca de trás para frente. Um logo de cabeçalho que encolhe conforme você rola é vinculado ao scroll.
  • Disparada pelo scroll: a animação toca no próprio ritmo assim que algo entra na tela pelo scroll. Um mascote que acena quando a seção dele aparece é disparado pelo scroll.
TécnicaTipoIdeal paraCusto
CSS animation-timeline: view() ou scroll()Vinculada ao scrollLogos em SVG ou imagem: revelar, encolher, girar, esmaecerSem JavaScript; só transform e opacity para ficar suave
Intersection Observer + vídeo ou trigger do RiveDisparada pelo scrollUm mascote que reage uma vez, uma intro de logo quando uma seção apareceAlgumas linhas de JavaScript; funciona em todo lugar
Número com data binding do Rive controlando um blend stateVinculada ao scrollPoses de personagem que acompanham o scroll: olhando para baixo, virando, um mascote de progressoRuntime do Rive mais um handler de scroll
Scrubbing de vídeo com currentTimeVinculada ao scrollGiros de logo 3D pré-renderizados e rotações de produtoArquivos grandes; o custo de busca varia conforme a codificação e o dispositivo

Para a maioria dos logos, CSS basta. Recorra ao Rive quando o que se move tem rosto e precisa de poses de verdade, e ao vídeo quando o movimento é uma cena 3D renderizada que nenhum runtime consegue desenhar ao vivo.

Como funcionam as animações CSS controladas por scroll?

O MDN as descreve como uma forma de "animar valores de propriedades ao longo de uma timeline baseada em scroll, em vez da timeline padrão do documento baseada em tempo". Você escreve @keyframes normais e depois as associa a uma timeline com animation-timeline:

  • scroll() cria uma timeline de progresso de scroll para "o elemento raiz, o scroller mais próximo ou o próprio elemento". 0% é o topo, 100% é o fim da área de scroll.
  • view() cria uma timeline de progresso de visualização que "avança com base nas mudanças de visibilidade de um elemento dentro do seu contêiner de scroll mais próximo". Por padrão, ela vai do momento em que o elemento aparece em uma borda até o momento em que sai pela outra.
  • animation-range restringe a parte da timeline que a animação usa, por exemplo entry 0% entry 100% para terminar exatamente quando o elemento tiver entrado por completo.

Um logo que sobe e se acomoda conforme a seção dele entra pelo scroll:

@keyframes logo-in {
  from { opacity: 0; transform: translateY(32px) scale(0.85) rotate(-6deg); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .section-logo {
      animation: logo-in 1ms linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }
  }
}

Um logo de cabeçalho que encolhe nos primeiros 200 pixels de scroll da página:

@keyframes logo-shrink {
  to { transform: scale(0.7); }
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    .site-logo {
      transform-origin: left center;
      animation: logo-shrink 1ms linear both;
      animation-timeline: scroll(root);
      animation-range: 0px 200px;
    }
  }
}

Uma armadilha pega quase todo mundo: animation-timeline é redefinido pelo shorthand animation. O MDN diz que você "precisa declarar animation-timeline depois de declarar qualquer shorthand animation para que ele tenha efeito", e o guia do Chrome dá o mesmo aviso. A duração de 1ms é proposital: em uma timeline de scroll, a animação é esticada pela área de scroll de qualquer forma, mas o MDN observa que o Firefox precisa de uma duração definida para aplicar a animação e recomenda 1ms. Use easing linear na timeline para que o logo acompanhe a barra de rolagem de forma uniforme; coloque a personalidade nos keyframes.

Quais navegadores suportam animation-timeline em novembro de 2026?

Todos os principais motores atuais. Em novembro de 2026, o MDN marca animation-timeline, view() e animation-range como "Baseline 2026, newly available", funcionando nos navegadores mais recentes "desde outubro de 2026".

NavegadorPrimeira versão com animation-timeline
Chrome e Edge115
Firefox159
Safari (macOS e iOS)26
Samsung Internet23

O Can I use estima o suporte global em cerca de 89% dos usuários em novembro de 2026. "Newly available" significa que aparelhos mais antigos ainda ficam de fora: quem usa Safari 18 ou um Firefox mais antigo. Por isso todos os exemplos acima ficam dentro de @supports.

O fallback deve ser o estado final, não um logo escondido. Escreva os estilos normais do elemento como o logo em repouso e adicione movimento só dentro do bloco @supports. Navegadores sem suporte mostram então um logo estático e correto, que também é o que os usuários com movimento reduzido veem. Se o movimento também importa em navegadores antigos, use a abordagem com Intersection Observer abaixo como fallback que toca uma vez; não publique um polyfill de eventos de scroll só por causa de um logo.

Como tocar uma animação de logo ou mascote quando ela entra na tela pelo scroll?

Use um Intersection Observer. O MDN lista "decidir se executa ou não tarefas ou processos de animação com base em se o usuário vai ver o resultado" como um dos principais usos, e ele é Baseline widely available (desde março de 2019). Esta versão toca vídeos sem som enquanto pelo menos metade está visível e os pausa no resto do tempo:

<video data-play-on-view muted playsinline loop preload="none"
       width="720" height="720" poster="/mascot-wave.webp">
  <source src="/mascot-wave.webm" type="video/webm">
  <source src="/mascot-wave.mp4" type="video/mp4">
</video>
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)");

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    const video = entry.target;
    if (entry.isIntersecting && !reduce.matches) {
      video.play().catch(() => {});
    } else {
      video.pause();
    }
  }
}, { threshold: 0.5 });

document.querySelectorAll("video[data-play-on-view]").forEach((v) => observer.observe(v));

O atributo muted importa: o guia de autoplay do MDN diz que o bloqueio de autoplay não se aplica quando "a faixa de áudio está sem som", então play() sem um gesto do usuário só funciona em vídeo sem som. O .catch() evita que uma reprodução bloqueada registre um erro.

Para uma reação única, como uma intro de logo ou um aceno de mascote, chame observer.unobserve(entry.target) depois da primeira reprodução para que ela não recomece toda vez que o visitante passar rolando. Para um arquivo Rive, o mesmo observer pode disparar um trigger em vez de chamar play() (veja a próxima seção) e pausar a instância do Rive com r.pause() quando ela sair da viewport.

Como fazer scrubbing de uma animação Rive com o scroll?

Pela state machine, não pela timeline. O runtime web do Rive ainda tem scrub(), mas a documentação o marca como "Deprecated in v2.41.0, use a state machine to control playback instead". O caminho suportado é um blend state 1D: a documentação do Rive diz que ele mistura timelines "com uma única propriedade numérica de view model" e lista "interações de scroll" entre seus usos. No editor, crie a pose inicial em uma timeline e a pose final em outra, adicione um blend state 1D controlado por uma propriedade numérica (por exemplo, scrollProgress, de 0 a 100) e exporte.

Em tempo de execução, defina esse número a partir do progresso do scroll com data binding:

import { Rive } from "@rive-app/canvas";

const section = document.querySelector("#story");
const canvas = document.querySelector("#mascot");
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

const r = new Rive({
  src: "/mascot.riv",
  canvas,
  stateMachine: "State Machine 1",
  autoplay: true,
  autoBind: true,
  onLoad: () => {
    r.resizeDrawingSurfaceToCanvas();
    const progress = r.viewModelInstance?.number("scrollProgress");
    if (!progress || reduce) return;

    let queued = false;
    const update = () => {
      queued = false;
      const box = section.getBoundingClientRect();
      const range = box.height - window.innerHeight;
      const p = range > 0 ? Math.min(Math.max(-box.top / range, 0), 1) : 0;
      progress.value = Math.round(p * 100);
    };
    window.addEventListener("scroll", () => {
      if (!queued) {
        queued = true;
        requestAnimationFrame(update);
      }
    }, { passive: true });
    update();
  },
});

Por que está escrito assim: o listener de scroll é passive e faz seu trabalho no máximo uma vez por frame em requestAnimationFrame, então o scroll nunca espera por ele. Arredondar para passos inteiros faz a state machine avançar só quando o valor muda, e o Rive observa que uma state machine sem nada para fazer "se acomoda" e para de avançar. Arquivos antigos que usam inputs de state machine em vez de um view model ainda funcionam via r.stateMachineInputs(), mas o Rive agora considera obsoleto definir inputs diretamente e recomenda data binding.

Nem todo momento do scroll precisa de scrubbing. Muitas vezes basta uma mudança de humor: o mascote parece curioso no topo da página e feliz na tabela de preços. O arquivo Rive de um conjunto de personagem do Charmotion já expõe um número mood que alterna os humores em loop e um trigger por reação única, como success, via Data Binding. Definir mood a partir de um Intersection Observer dá um mascote que reage ao scroll sem precisar criar um blend state. O README de cada conjunto lista os valores. Veja como funciona.

Dá para fazer scrubbing de um vídeo com o scroll?

Sim. Definir currentTime faz uma busca: o MDN diz que "alterar o valor de currentTime leva a mídia para o novo tempo", e o navegador pode ajustar a posição para uma que a mídia suporte. Associe o progresso do scroll ao tempo dentro de requestAnimationFrame:

const video = document.querySelector("#logo-turn");
const track = document.querySelector("#logo-turn-track");
let queued = false;

function update() {
  queued = false;
  if (!video.duration) return;
  const box = track.getBoundingClientRect();
  const range = box.height - window.innerHeight;
  const p = range > 0 ? Math.min(Math.max(-box.top / range, 0), 1) : 0;
  video.currentTime = p * video.duration;
}

window.addEventListener("scroll", () => {
  if (!queued) {
    queued = true;
    requestAnimationFrame(update);
  }
}, { passive: true });

Funciona melhor com cenas curtas e pré-renderizadas, como um logo 3D girando 90 graus, e tem custos reais:

  • Buscar é trabalho de decodificação. A suavidade de uma busca depende de como o vídeo foi codificado e do dispositivo. Codificar com keyframes mais frequentes costuma ajudar, ao custo de um arquivo maior. Teste em um celular intermediário.
  • O arquivo inteiro precisa estar disponível. Não combine scrubbing com preload="none"; faça o preload quando a seção estiver perto.
  • Mantenha curto e leve. Alguns segundos, no tamanho em que é exibido. Para um logo que só precisa girar ou mudar de escala, transforms CSS em um SVG ou imagem fazem o mesmo trabalho sem vídeo nenhum.
  • Dê a ele um pôster mostrando o frame de repouso, que também é o estado com movimento reduzido e sem JavaScript.

Como manter a animação com scroll suave e confortável?

Primeiro, suavidade. O guia do Chrome explica por que eventos de scroll são uma base ruim para animação: os navegadores fazem o scroll em um processo separado e entregam os eventos de scroll de forma assíncrona, e "animações na main thread estão sujeitas a travamentos". As animações CSS controladas por scroll evitam isso e podem rodar fora da main thread. Mantenha-as leves:

  • Anime transform e opacity. Alterar top, left, largura ou altura no scroll gera trabalho de layout e, segundo o web.dev, layout shifts.
  • Um elemento com scrubbing por tela. Um logo, um mascote ou uma imagem de produto, não os três.
  • Listeners passivos e uma atualização por frame para tudo que for controlado por JavaScript, como nos exemplos acima.
  • Pause o que não está visível. Um canvas do Rive ou um vídeo que saiu da tela deve parar de desenhar.

Depois, conforto. O movimento vinculado ao scroll é o tipo que mais incomoda pessoas com distúrbios vestibulares. A explicação do W3C para o SC 2.3.3 (Animation from Interactions, Nível AAA) cita o scroll com parallax como uma animação muitas vezes não essencial e diz que o movimento disparado pelo scroll "pode desencadear distúrbios vestibulares", com reações como "distração, tontura, dores de cabeça e náusea". Regras práticas:

  • Coloque toda animação de scroll dentro de @media (prefers-reduced-motion: no-preference) ou verifique matchMedia em JavaScript, como fazem todos os exemplos aqui.
  • Mantenha o movimento pequeno: algumas dezenas de pixels, alguns graus. Zooms grandes e parallax em tela cheia são onde o desconforto começa.
  • Nunca esconda conteúdo atrás de uma animação de scroll. Se a animação falhar ou for desativada, o logo e o texto precisam continuar lá.

Mais sobre a configuração de movimento reduzido em animação acessível e movimento reduzido.

Como adicionar uma animação de logo com scroll e um fallback

  1. Estilize o logo em repouso. Escreva o CSS normal do logo como o estado final e estático. É isso que navegadores antigos e usuários com movimento reduzido vão ver.
  2. Escreva os keyframes. Anime só transform e opacity, da pose inicial até a pose de repouso.
  3. Associe uma timeline. Dentro de @supports (animation-timeline: view()), defina o shorthand animation, depois animation-timeline: view() ou scroll(), e depois animation-range.
  4. Respeite as configurações de movimento. Envolva o bloco em @media (prefers-reduced-motion: no-preference).
  5. Adicione um fallback que toca uma vez, se precisar. Para navegadores antigos, inicie uma animação única com um Intersection Observer em vez de um listener de scroll.
  6. Teste. Role devagar e rápido em um celular intermediário, role de volta para cima e confira o estado estático com o movimento reduzido ativado.

Perguntas frequentes

Por que meu animation-timeline não faz nada?

A causa mais comum é a ordem: o shorthand animation redefine animation-timeline, então declare animation-timeline depois dele. Confira também se o elemento tem um contêiner de scroll no eixo certo (view() fica inativo se esse eixo não rola) e se o navegador tem suporte: Chrome 115, Firefox 159 e Safari 26 ou posteriores.

Ainda preciso de um polyfill para animações controladas por scroll em novembro de 2026?

Para um logo, normalmente não. As animações CSS controladas por scroll são suportadas nas versões atuais do Chrome, Edge, Firefox e Safari em novembro de 2026. Para navegadores antigos, mostre o logo em repouso ou toque uma animação única com um Intersection Observer em vez de carregar um polyfill.

Uma animação Rive pode acompanhar a posição do scroll?

Sim. Crie no Rive um blend state 1D controlado por uma propriedade numérica de view model e defina esse número a partir do progresso do scroll em um listener de scroll passivo, no máximo uma vez por frame. O método scrub() do Rive ainda existe, mas está obsoleto desde o runtime v2.41.0.

Fontes

Guias relacionados