O que motion faz?
Este skill constrói motion. Ele estende a seção de motion de product-ui/references/values.md, que contém os números definidos (curvas de easing, escala de pressionamento, bounce, escalonação, tabela de duração, durações de toast e layout, distâncias de viagem, razão saída-entrada). Esses números não são repetidos aqui; este skill adiciona a ordem das decisões, os tokens, as receitas e os modos de falha.
Antes de qualquer decisão, aplique product-ui §1 (o diário de gosto e precedência) e §5 (qual skill é dona do quê).
A ordem de decisão
Percorra de cima para baixo. Cada passo pode encerrar o trabalho. Zero linhas de animação é um resultado válido, e você diz isso.
1. Deve se mover? O portão de frequência
Pergunte quantas vezes por dia um único usuário vê isso. Os níveis estão em values.md. Na prática:
- Iniciado pelo teclado, ou visto 100+ vezes por dia: sem movimento. Por quê: movimento repetido se transforma em atraso.
- Visto dezenas de vezes por dia (hover, troca de aba, seleção de linha): nenhum, ou uma mudança instantânea de cor.
- Dados que o usuário está lendo ou agindo (um número em uma tabela, um gráfico em um dashboard ao vivo) nunca se movem por estilo. Por quê: movimento desvia o olhar da coisa que está sendo lida.
- Se o portão disser não, não escreva animação. Diga qual nível atingiu e ofereça a alternativa estática (uma mudança de estado instantânea).
2. Nomeie o propósito
Escolha uma palavra: espacial (de onde veio ou para onde foi), estado (isto mudou), feedback (ouvi você), continuidade (evita um pulo), explicação (apenas momentos raros). Se não conseguir nomear: pare. Por quê: "fica bonito" em um elemento frequente é a principal fonte de UI que parece lenta.
3. Escolha a ferramenta: a mais barata que funciona
| Necessidade | Ferramenta |
|---|---|
| Um estado alternado por classe, atributo, hover ou press | CSS transition |
Entrada de display: none, popover ou dialog | @starting-style + transition-behavior: allow-discrete |
| Uma sequência única, escalonação, ou motion que deve rodar enquanto a página está ocupada | CSS @keyframes |
JS decide quando; você precisa de cancelamento, reversão, finished, ou FLIP | WAAPI el.animate() |
| Molas (springs), handoff de velocidade, saída de um nó React que está desmontando, layout compartilhado | Motion (motion/react) |
Se a coisa é um componente inteiro (menu, toast, drawer), use uma biblioteca que lida com foco e pergunte pick-library. Nunca instale Motion para um fade. Por quê: CSS roda fora da thread principal e não custa bundle.
4. Propriedades
- Anime
transformeopacity;filterapenas para um blur de troca ≤ 2px. Nomeie cada propriedade; nuncatransition: all. - Mudanças de altura e layout: veja
references/recipes.md(linhas de grid, FLIP). Nunca animewidth,top,leftoumarginpara mover algo. Por quê: cada um força layout em cada frame. - Nunca anime
box-shadow. Em vez disso, faça fade daopacityde um pseudo-elemento. - Percentuais de
translatesão relativos ao tamanho do próprio elemento, entãotranslateY(100%)serve para drawers e toasts de qualquer altura.
5. Curva e duração, combinadas ao uso
Nunca escolha o número mais próximo do seu palpite. Diga o uso ("fechamento de menu", "troca de ícone"), então pegue essa linha de references/tokens.md. Se nenhuma linha corresponder ao uso, adicione um novo token nomeado para o uso; não adapte um existente. Por quê: um token é uma declaração sobre para que serve o movimento, então um número próximo com o uso errado é o token errado.
- Entrando ou saindo:
--ease-out. Movendo na tela:--ease-in-out. Drawers:--ease-drawer. Cor:ease. Spinners:linear. Nuncaease-in. - Qualquer coisa arrastável ou interrompível durante o movimento usa uma mola (spring), não uma curva. Por quê: uma curva não pode aceitar a velocidade que a mão do usuário tinha.
- Sensação lenta: reduza a duração primeiro, depois a distância, e só então toque na curva.
6. Interrupção
- Qualquer coisa que o usuário possa acionar duas vezes em um segundo é uma transição ou uma mola, nunca
@keyframes. Por quê: uma transição redireciona do valor atual; keyframes reiniciam e pulam. - WAAPI: cancele a animação antiga após ler o valor atual e inicie a nova a partir dele (
references/recipes.md). - Uma alternância pressionada 10 vezes deve terminar no estado correto sem pulo. Teste isso.
7. Saída
- A saída padrão é um fade, mais curto que a entrada (o arquivo de tokens tem cada par).
- Qualquer coisa com direção (drawer, folha, toast deslizado) sai pelo caminho que veio.
- O elemento que está saindo para de ocupar espaço imediatamente, então os irmãos fecham a lacuna enquanto ele desvanece.
- Saídas são simultâneas e nunca escalonadas. Por quê: o usuário já decidiu sair.
8. Movimento reduzido, enviado com a animação
Opacidade e cor permanecem. Slides, escala, blur e bounce desaparecem; slides viram fades, bounce vira 0. Nunca use motion como único indicador. Padrão em references/recipes.md.
Coreografia
Atribua um papel a cada elemento em movimento antes de temporizá-lo:
| Papel | Tratamento |
|---|---|
| Lead (a única coisa a notar) | Move-se primeiro, viaja mais |
| Suporte (Support) | Segue, viaja menos |
| Decoração (Decoration) | Desvanece no lugar, ou nada |
| Estático (Static) (containers, chrome) | Nunca se move |
- Uma entrada por container. Se o painel desliza para dentro, seu conteúdo já está lá. Por quê: duas entradas em camadas fazem o usuário esperar duas vezes.
- Ordene por importância, não por ordem DOM. O atraso permanece nos passos de values.md (40ms entre itens, 100ms entre título, texto e ações). Varie distância e duração por papel, em vez do atraso.
- Limite a escalonação de uma lista ao índice 6 (um atraso final de 240ms); itens além disso compartilham esse atraso. A entrada funciona enquanto isso ocorre.
- Nunca escalone um menu, paleta de comandos, conteúdo de aba ou qualquer abertura repetida.
- Fique parado: qualquer coisa que o usuário está prestes a clicar, qualquer coisa já na tela quando a página carrega novamente, e uma atualização que chega dentro de 150ms da anterior (pule para o estado final).
- Direção segue significado: próximo entra pela direita, voltar pela esquerda; coisas que o usuário acionou crescem para fora a partir do gatilho.
Carregar quando necessário
- references/tokens.md: carregue para qualquer valor exato de duração, distância, escala, blur, easing ou spring, e para tokenizar uma base de código existente.
- references/recipes.md: carregue ao construir um menu, popover, dialog, drawer, toast, entrada de lista, troca de ícone, mudança de altura, indicador de aba ou mudança de número.
- references/gestures.md: carregue para arrastar, deslizar, arremessar, folhas, ou qualquer coisa que siga um ponteiro.
- references/performance.md: carregue quando o motion cair frames, antes de recorrer a
will-change, e quando as atualizações chegarem rápido.
Pares
| Quebrado | Corrigido | Por quê |
|---|---|---|
.menu { transition: all 300ms ease-in } | transition: opacity 200ms, transform 200ms var(--ease-out) | Propriedades nomeadas; ease-in é lerdo; menus ≤ 200ms |
scale(0) para scale(1) | scale(0.95) e opacity: 0 para 1 | Nada aparece do nada |
| Popover escala do seu centro | transform-origin: var(--transform-origin) | Deve crescer a partir de seu gatilho |
.open { animation: slide-in 300ms } | transition: transform no estado alternado | Keyframes dão pulo quando interrompidos |
Cada linha animation-delay: i * 120ms | min(i, 6) * 40ms | A última linha chega em menos de 300ms |
| Drawer desvanece após deslizar para dentro | Desliza de volta pelo caminho que veio | Direção é a explicação |
Soltura de arraste em um ease-out fixo | Uma mola (spring) alimentada com a velocidade de soltura | O momentum da mão continua |
setY(y) em pointermove | el.style.transform = ... | Sem re-renderização por frame |
Antes de finalizar
Pesquise o diff, depois verifique.
| Pesquise por | Corrija |
|---|---|
transition: all, will-change em *, ease-in | Propriedades nomeadas, remova, --ease-out |
scale(0), transform-origin: center em um popover | 0.95, a origem do gatilho (diálogos permanecem centralizados) |
@keyframes em uma classe que alterna | Uma transição |
| Uma duração acima da sua linha em tokens.md, ou um número não em tokens.md | O token do uso |
Motion em handlers keydown ou uma paleta | Remova |
width, height, top, left, box-shadow em uma transição | transform, opacity, ou a receita |
Sem caminho prefers-reduced-motion | Adicione |
Verifique no aplicativo em execução: reproduza a 4× (painel Animations do DevTools, ou multiplique as durações), spamme qualquer alternância, teste com movimento reduzido ativado e limite a CPU em 4×. Diga quais destes você não conseguiu executar.