Skip to content
This page has been auto-translated and may contain errors.View in English

Transições e animações

docs.scrimba.com

Você adiciona uma cor :hover a um botão e funciona, mas a mudança é abrupta: um frame é azul, o próximo é mais escuro, sem nada no meio. Um pequeno fade tornaria a página inteira menos entrecortada. Esse fade é uma transição, e uma vez que você consegue descrever movimento entre dois estados, as mesmas ferramentas escalam para efeitos de hover, cards que se levantam, e um spinner que gira enquanto algo carrega. Este capítulo é sobre como CSS move as coisas, e como movê-las sem deixar a página mais difícil de usar.

Transições

Uma transição diz ao navegador para animar uma propriedade do seu valor antigo para o novo em vez de pular entre eles. Você descreve dois estados, o normal e o hover, e a transição preenche os frames no meio.

Aqui está um botão que muda de cor ao passar o mouse. Sem uma transição a cor pula; com uma ela desaparece gradualmente:

css
.btn {
  background: #3b82f6;
  transition: background 0.2s;   /* desaparece gradualmente a cor em 0.2 segundos */
}
.btn:hover {
  background: #1d4ed8;           /* o novo estado para animar em direção */
}

Você coloca transition no estado normal, não em :hover. Dessa forma ela se aplica tanto quando o mouse chega quanto quando sai.

Uma transição anima uma propriedade entre dois valores sempre que esse valor muda, geralmente porque um estado como :hover ou :focus entra em ação, ou uma classe é adicionada. Você não está descrevendo a animação frame a frame; você nomeia a propriedade e uma duração, e o navegador interpola os valores no meio.

O atalho transition tem quatro partes, nessa ordem: a propriedade a ser observada, a duração, a timing-function (a curva de velocidade), e um delay opcional.

css
.btn {
  background: #3b82f6;
  transition: background 0.2s ease-in-out 0s;
  /* propriedade | duração | timing-function | delay */
}
.btn:hover {
  background: #1d4ed8;
}

Você pode fazer transição de várias propriedades ao mesmo tempo separando-as com vírgulas, e você declara a transição no estado base para que cubra tanto entrar quanto sair do hover.

css
.card {
  transition: transform 0.2s, box-shadow 0.2s;   /* duas propriedades, mesmo timing */
}

Uma transição interpola uma propriedade entre seu valor inicial e final ao longo do tempo sempre que o valor computado muda. O gatilho é qualquer mudança nessa propriedade: uma pseudo-classe como :hover, uma classe alternada do JavaScript, ou uma edição de estilo inline. As quatro sub-propriedades são transition-property, transition-duration, transition-timing-function, e transition-delay, e o atalho as ordena como propriedade, duração, timing, delay.

Duas restrições decidem se uma transição é executada. A propriedade tem que ser animável, significando que tem uma interpolação definida entre dois valores: background-color, opacity, e transform são, enquanto uma propriedade como display é discreta e historicamente saltava em vez de fazer tweening. E ambos os valores inicial e final têm que ser concretos: você não pode fazer transição para ou de auto, é por isso que animar height para auto não faz nada e as pessoas recorrem a max-height ou transform em vez disso.

css
.btn {
  background: #3b82f6;
  transition: background-color 0.2s ease-in-out;   /* animável, ambos os valores concretos */
}
.btn:hover {
  background: #1d4ed8;
}

Declare a transição na regra base, não na regra :hover. A propriedade transition em vigor é lida do estado atual do elemento, então colocá-la no estado base significa que governa tanto a chegada quanto o retorno; colocá-la apenas em :hover anima a entrada e salta a saída.

JunoTransições Uma transição suaviza uma mudança em vez de pular, como desaparecer gradualmente a cor de um botão ao passar o mouse. Você a escreve no estado normal, não em :hover, para que funcione entrando e saindo. Comece com propriedade e uma duração e você já tem um fade funcionando.
JunoTransições O atalho transition lê propriedade, duração, timing-function, delay, e você lista vários separando-os com vírgulas. Coloque-o na regra base para que cubra entrar e sair do estado. Se um hover parece suave entrando e pula saindo, a transição está em :hover em vez da base, o que pega muitas pessoas.
JunoTransições Uma transição só é executada em propriedades animáveis com dois valores finais concretos, é por isso que fazer transição para height: auto silenciosamente não faz nada. Mantenha a declaração no estado base para que governe ambas as direções em vez de pular na saída. Uma vez que você conhece as quatro sub-propriedades, o atalho é apenas sua ordem: propriedade, duração, timing, delay.

Transforms

Um transform move, dimensiona, ou gira um elemento sem perturbar nada ao seu redor. É a coisa que você geralmente quer animar, porque fica suave e não empurra os vizinhos para fora do caminho.

Existem três que você vai usar constantemente:

css
.card:hover {
  transform: translateY(-4px);   /* move para cima 4 pixels */
}
.icon:hover {
  transform: scale(1.1);         /* cresce para 110% do seu tamanho */
}
.arrow.open {
  transform: rotate(90deg);      /* gira um quarto de volta no sentido horário */
}

translate move, scale redimensiona, e rotate gira. Combine qualquer um desses com uma transição e você consegue um card que se levanta, um ícone que salta, ou uma seta que se abre.

transform aplica uma mudança visual a um elemento, movendo, redimensionando, ou girando, sem mudar seu lugar no layout. Essa última parte é o ponto: um elemento transformado é pintado em uma nova posição, mas o espaço que originalmente ocupava permanece reservado, então seus vizinhos não refluem. É por isso que transform é a propriedade que você anima para movimento em vez de top ou margin.

Os três que você usa mais são translate(), scale(), e rotate(), e você pode combiná-los em uma declaração:

css
.card {
  transition: transform 0.2s ease-out;
}
.card:hover {
  transform: translateY(-4px) scale(1.02);   /* levanta e cresce levemente */
}

As funções se aplicam da esquerda para a direita, e transform-origin define o ponto em que giram, que padrão é o centro do elemento. Mude para top left e um rotate gira a partir do canto em vez disso.

css
.badge {
  transform-origin: top left;
  transform: rotate(-8deg);
}

transform reposiciona um elemento em sua própria camada pintada sem afetar o fluxo do documento: o espaço original da caixa permanece reservado, então nenhum irmão refluiria e nenhum layout seria recomputado quando o transform muda. Essa propriedade é o que torna transform a coisa certa para animar, um ponto que a seção Deep Dive sobre performance abaixo deixa preciso.

translate(), scale(), e rotate() são as funções comuns, e a ordem é significativa porque elas se compõem como multiplicação de matriz aplicada da direita para esquerda em efeito: translateX(50px) rotate(45deg) move depois gira o elemento movido, enquanto o reverso gira primeiro e depois traduz ao longo dos eixos girados, dando um resultado diferente. transform-origin define o pivô para scale e rotate, padrão sendo o centro do elemento (50% 50%).

css
.panel {
  transform-origin: top center;
  transition: transform 0.25s ease-out;
}
.panel.collapsed {
  transform: scaleY(0);          /* colapsa em direção à borda superior */
}

Uma restrição prática: transform apenas se aplica a elementos que geram uma caixa que você pode posicionar dessa forma, então não tem efeito em elementos inline como um <span> puro. Dê ao elemento display: inline-block ou block primeiro. Se você também está posicionando o elemento, note como transforms interagem com positioning contexts, já que um elemento transformado torna-se um bloco contêiner para seus descendentes absolutamente posicionados.

JunoTransformstransform move, dimensiona, ou gira um elemento sem empurrar seus vizinhos. Os três para lembrar são translate para mover, scale para redimensionar, e rotate para girar. Combine com uma transição e você consegue um card que se levanta ou um ícone que salta ao passar o mouse.
JunoTransformstransform muda como um elemento parece sem mudar onde ele senta no layout, então vizinhos nunca refluem, é por isso que você o anima em vez de top ou margin. Empilhe translate, scale, e rotate em uma declaração e eles se aplicam da esquerda para a direita. Use transform-origin quando você quer que um rotate ou scale gire em algum lugar que não seja o centro.
JunoTransforms Funções transform se compõem em ordem, então translateX(50px) rotate(45deg) não é o mesmo que o reverso, e transform-origin define o que um rotate ou scale gira. Não faz nada em um elemento inline puro até que você o faça inline-block. Tenha em mente que também transforma o elemento em um bloco contêiner para filhos absolutamente posicionados, o que pode surpreendê-lo depois.

Animações keyframe

Uma transição anima entre dois estados. Quando você precisa de mais que dois, ou precisa que o movimento se repita, você usa uma animação keyframe. Você escreve os passos com @keyframes, depois os anexa a um elemento com a propriedade animation.

Um spinner de carregamento é o exemplo clássico: ele gira para sempre, então uma transição não consegue fazer isso.

css
@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.spinner {
  animation: spin 1s linear infinite;   /* executa spin, 1s por volta, para sempre */
}

from é o início e to é o fim. A linha animation diz quais keyframes executar, quanto tempo uma volta leva, a curva de velocidade, e quantas vezes repetir. infinite significa que nunca para.

@keyframes define uma sequência nomeada de passos, e a propriedade animation a executa em um elemento. Diferentemente de uma transição, uma animação não precisa de uma mudança de estado para acioná-la, ela inicia por conta própria, e pode ter quantos passos você quiser, usando porcentagens de 0% a 100%.

css
@keyframes pulse {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.05); }   /* o passo do meio */
  100% { transform: scale(1); }
}
.badge {
  animation: pulse 1.5s ease-in-out infinite;
}

O atalho animation agrupa várias sub-propriedades: name, duration, timing-function, delay, iteration-count, e direction. infinite faz loop para sempre, um número executa essa quantidade de vezes, e alternate como uma direção faz cada ciclo reverter para que o movimento execute para frente e para trás em vez de pular para o início.

css
.spinner {
  animation: spin 1s linear infinite;
}

@keyframes declara uma linha do tempo nomeada de valores de propriedade em pontos expressos como porcentagens (from e to são aliases para 0% e 100%), e animation vincula essa linha do tempo a um elemento. Uma animação é auto-acionada e pode fazer loop, que é a linha entre ela e uma transição: use uma transição para suavizar uma mudança de estado, use uma animação para movimento que executa por conta própria ou se repete.

O atalho animation carrega oito sub-propriedades: animation-name, -duration, -timing-function, -delay, -iteration-count, -direction, -fill-mode, e -play-state. Duas delas pegam as pessoas. animation-fill-mode: forwards faz o elemento manter seu keyframe final após a animação terminar, em vez de pular de volta para seu estilo não animado, que é o que você quer para uma entrada única. E a timing-function se aplica por segmento de keyframe, não em toda a animação, então ease-in-out em uma animação de três passos faz ease in e out de cada segmento, não uma vez do início ao fim.

css
@keyframes slide-in {
  from { opacity: 0; transform: translateX(-16px); }
  to   { opacity: 1; transform: translateX(0); }
}
.toast {
  animation: slide-in 0.3s ease-out forwards;   /* mantém o estado final */
}

Prefira animar transform e opacity dentro de keyframes pela mesma razão que transições fazem, que a próxima seção desembrulha: essas duas propriedades são baratas para animar e a maioria das outras não.

JunoAnimações keyframe Quando dois estados não são o suficiente, ou o movimento precisa se repetir, escreva os passos em @keyframes e os execute com animation. Um spinner é o exemplo padrão: from em 0 graus, to em 360, looping com infinite. A linha animation define quais keyframes, quanto tempo, a curva, e quantas vezes.
JunoAnimações keyframe@keyframes contém os passos como porcentagens e animation os executa, nenhuma mudança de estado necessária. O atalho empacota name, duration, timing, delay, iteration-count, e direction, então infinite faz loop e alternate faz cada ciclo executar para frente e para trás. Movimento multi-passo e qualquer coisa que se repete pertence aqui, não em uma transição.
JunoAnimações keyframe A timing-function executa por segmento de keyframe, não uma vez em toda a animação, o que surpreende as pessoas na primeira vez que uma execução eased multi-passo parece entrecortada. Use animation-fill-mode: forwards quando uma entrada única deve manter seu estado final em vez de pular de volta. E mantenha keyframes em transform e opacity onde você conseguir, pela razão de performance que vem a seguir.

Movimento que respeita o usuário

Nem todo mundo quer movimento na tela. Algumas pessoas acham distrativo, e para algumas pode causar desconforto real ou náusea. Navegadores deixam as pessoas pedir menos disso nas suas configurações de sistema, e CSS pode ler esse pedido com prefers-reduced-motion.

Envolva suas animações em uma verificação para que elas apenas executem para pessoas que não pediram para diminuir o movimento:

css
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 0.2s;
  }
}

A outra metade de respeitar o usuário é moderação: mantenha o movimento curto, sutil, e atado a algo que o leitor fez, como um hover ou um clique. Movimento que chama atenção para uma mudança real ajuda; movimento por si só fica cansativo rápido.

Movimento não é neutro. Para alguns leitores, animação é uma distração, e para pessoas com condições vestibulares, movimento grande ou súbito pode desencadear tontura ou náusea. Sistemas operacionais expõem uma configuração "reduzir movimento", e a media feature prefers-reduced-motion deixa seu CSS honrá-la.

Trate o movimento como opt-in, não opt-out. Escreva a baseline calma como seu padrão, depois camada de movimento dentro de uma query no-preference:

css
.card {
  transition: none;              /* baseline calma para todos */
}
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 0.2s ease-out;
  }
}

Além da media query, propósito é o que separa movimento bom de ruído. Movimento ganha seu lugar quando explica uma mudança: um menu que desliza da borda para a qual está ancorado, um toast que desaparece gradualmente para não aparecer do nada. Rápido e sutil se lê como polish; lento e grande se lê como um obstáculo.

prefers-reduced-motion é uma media feature refletindo uma configuração de acessibilidade que o leitor escolheu no nível do SO, e honrá-la é um requisito baseline, não um atrativo, porque movimento não solicitado pode causar desconforto vestibular. O padrão para alcançar é autoria do estado reduzido como padrão e habilitação de movimento dentro de @media (prefers-reduced-motion: no-preference), então um leitor cujo navegador não suporta o recurso ainda consegue a baseline calma em vez do movimento.

"Reduzir" raramente significa "remover". Um fade ou uma mudança de cor geralmente é bom quando um slide ou scale não é, então o movimento útil é trocar movimento posicional grande por uma pequena mudança de opacidade em vez de matar todo feedback:

css
.modal {
  animation: slide-in 0.3s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .modal {
    animation: fade-in 0.3s ease-out;   /* mantém feedback, cai o movimento */
  }
}

Propósito e easing carregam o resto. Entradas se leem melhor com ease-out, que começa rápido e assentam, porque o elemento chega com intenção e vem ao repouso; saídas geralmente convêm a ease-in. Mantenha durações curtas (aproximadamente 150ms a 300ms para feedback de interface) e movimento pequeno; uma falha comum é uma animação encantadora que adiciona um quarto de segundo de espera a cada interação. Movimento deve esclarecer uma mudança de estado, não decorar uma.

JunoMovimento que respeita o usuário Algumas pessoas acham movimento distrativo ou até desconfortável fisicamente, e elas podem pedir ao navegador por menos disso. Leia isso com prefers-reduced-motion e apenas execute animações para pessoas que não diminuíram o movimento. Mantenha o movimento que você adicionar curto, sutil, e atado a algo que o leitor fez.
JunoMovimento que respeita o usuário Crie a versão calma como seu padrão e adicione movimento dentro de uma query no-preference, então movimento é opt-in em vez de algo que as pessoas têm que lutar. Bom movimento explica uma mudança, como um menu deslizando de sua âncora; movimento para decoração fica velho rápido. Rápido e pequeno se lê como polish, lento e grande se lê como um obstáculo.
JunoMovimento que respeita o usuário Reduzir raramente significa remover: troque um slide por um fade para que pessoas que pediram por menos movimento ainda recebam feedback, e padrão para o estado calmo para que navegadores não suportados caiam lá também. Use ease-out para entradas e mantenha feedback de interface ao redor de 150 a 300 milissegundos. Uma animação encantadora que adiciona uma espera a cada clique é um custo, não um recurso.

O que animar, e por que importa

Uma regra rápida que vai economizar problemas depois: quando você anima movimento, anima transform e opacity, não propriedades como width, height, top, ou margin.

css
/* suave: o navegador lida com essas barato */
.card:hover {
  transform: translateY(-4px);
  opacity: 0.9;
}

A razão é que transform e opacity são as duas propriedades que navegadores conseguem mover o mais barato, então animações construídas nelas ficam suaves mesmo em dispositivos mais lentes. Animar tamanho ou posição propriedades faz o navegador fazer mais trabalho a cada frame, e o movimento pode ficar entrecortado. Você vai conhecer a razão completa quando você for mais fundo, mas o hábito vale a pena construir agora.

Há uma razão pela qual os exemplos neste capítulo se apoiam em transform e opacity. Essas duas propriedades são as coisas mais baratas para animar, e a maioria das outras não. Animar width, height, top, ou margin força o navegador a recalcular layout a cada frame, o que é caro e se mostra como entrecortado em dispositivos mais lentes.

A regra prática é expressar movimento como transform e opacity onde você conseguir. Quer mover algo? Use translate, não top. Quer redimensioná-lo? Use scale, não width. Quer que apareça? Desapareça gradualmente opacity, não anima height de zero.

css
/* caro: animar uma propriedade de layout */
.panel {
  transition: height 0.3s;       /* recalcula layout a cada frame */
}

/* barato: mesmo efeito, expresso como transform */
.panel {
  transition: transform 0.3s;
  transform: scaleY(0);          /* nenhum trabalho de layout */
}

Isso conecta de volta a por que transform não perturba seus vizinhos: porque nunca toca layout, o navegador consegue animá-lo sem refazer a página. Se você se encontrar alcançando uma transição de cor ou background também, cores e backgrounds cobre quais delas interpolam.

Este é o Deep Dive que as seções anteriores apontaram: por que transform e opacity são baratos para animar e quase tudo mais não. Tudo se resume ao pipeline de renderização do navegador, que executa em três estágios. Layout computa o tamanho e posição de cada caixa. Paint preenche pixels: cores, texto, bordas. Composite monta as camadas pintadas no frame final, e é o estágio que a GPU consegue dirigir.

Animar transform ou opacity consegue ser feito inteiramente no estágio composite. O navegador pinta o elemento uma vez, o promove para sua própria compositor layer (uma superfície pintada separadamente que a GPU consegue mover e misturar independentemente), e depois cada frame apenas recompõe essa camada em uma nova posição ou opacidade. Nenhum layout, nenhum repaint. Animar width ou top em vez disso invalida layout, forçando o navegador a recomputar geometria e repaint a cada frame, que é o que cai frames sob carga.

css
/* composite-only: barato, GPU-friendly */
.card {
  transition: transform 0.2s, opacity 0.2s;
}

/* dispara layout e paint a cada frame: evite animar essas */
.card-heavy {
  transition: width 0.2s, top 0.2s;
}

will-change deixa você dica que uma propriedade está prestes a animar para que o navegador possa promover a camada com antecedência: will-change: transform. Use-o raramente e remova-o quando a animação termina, porque cada camada promovida custa memória, e promover tudo derrota o propósito e consegue deixar a página mais lenta. Trate-o como um correção direcionada para um entrecortado medido, não um padrão.

Easing é a outra alavanca que vale a pena nomear precisamente. A timing-function é uma curva mapeando tempo decorrido para progresso. ease-out (rápido depois desacelerando) convém entradas porque o elemento chega e assentam; ease-in convém saídas; linear convém movimento contínuo como um spinner. Para qualquer coisa customizada, cubic-bezier(x1, y1, x2, y2) define a curva por seus dois pontos de controle, e as palavras-chave construídas são cubic-beziers nomeados por baixo. Mantenha um orçamento de performance em mente: aim a ficar dentro de um frame de tempo (aproximadamente 16ms em 60fps), anima propriedades composite-only, e alcance o profiler antes de alcançar will-change. Se você dirige várias dessas do custom properties, você consegue manter durações e easing consistentes em um interface inteira.

JunoO que animar, e por que importa Anima transform e opacity, e dirija-se para longe de animar width, height, top, ou margin. Essas duas são as mais baratas para o navegador mover, então o movimento fica suave mesmo em dispositivos mais lentes. Você vai aprender o por que completo depois, mas o hábito vale a pena começar agora.
JunoO que animar, e por que importa Expresse movimento como transform e opacity: use translate em vez de top, scale em vez de width, e desapareça gradualmente opacity em vez de animar height de zero. Animar propriedades de layout faz o navegador refazer layout a cada frame, que entrecorta em dispositivos mais lentes. É a mesma razão que transform deixa vizinhos sozinhos: nunca toca layout.
JunoO que animar, e por que importatransform e opacity conseguem animar no estágio composite em sua própria camada GPU, então eles pulam layout e paint; width e top invalidam layout a cada frame e caem frames sob carga. Use will-change como uma correção direcionada para um entrecortado medido e remova-o depois, já que cada camada promovida custa memória. Escolha easing para combinar com o gesto, ease-out para entradas, e profile antes de otimizar.