Apple Design
Como a Apple constrói interfaces que deixam de parecer um computador e passam a parecer uma extensão de você. Este conhecimento vem das palestras de design da WWDC — principalmente Designing Fluid Interfaces (WWDC 2018) — destilado e traduzido para a plataforma web (CSS, Pointer Events, requestAnimationFrame, bibliotecas de spring como Motion/Framer Motion).
A linha central: uma interface parece viva quando o movimento começa a partir do valor atual na tela, herda a velocidade do usuário, projeta o momentum adiante e pode ser agarrada e revertida a qualquer instante. As Springs são a ferramenta que torna tudo isso natural, porque são inerentemente interrompíveis e conscientes da velocidade.
A Ideia Central
"Quando alinhamos a interface à maneira como pensamos e nos movemos, algo mágico acontece — ela deixa de parecer um computador e começa a parecer uma extensão perfeita de nós."
Uma interface é fluida quando se comporta como o mundo físico: as coisas respondem instantaneamente, movem-se continuamente, carregam momentum, resistem nos limites e podem ser redirecionadas no meio do movimento. Tudo abaixo é uma maneira de se aproximar disso.
A Apple enquadra o design como servindo a quatro necessidades humanas: segurança/previsibilidade, compreensão, realização e alegria. Cada regra aqui serve a uma delas.
1. Resposta — elimine a latência
No momento em que a latência aparece, a sensação de imediatismo "cai de um penhasco." A resposta é a base sobre a qual tudo o mais é construído.
- Responda no pointer-down, não na liberação. Destaque um botão no instante em que é pressionado. Esperar pelo
click/touch-up para mostrar feedback parece morto. - Seja vigilante em relação a cada latência. Audite debounces, timers artificiais, esperas de transição e o atraso de toque de ~300ms. Qualquer coisa no caminho de entrada que não seja essencial é uma regressão.
- O feedback deve ser contínuo durante a interação, não apenas no final. Para um arrasto, slider ou gaveta, atualize a UI 1:1 com o ponteiro durante todo o caminho — nunca anime apenas quando o gesto é concluído.
/* O feedback vive no pressionar, e é instantâneo */
.button:active {
transform: scale(0.97);
transition: transform 100ms ease-out;
}
2. Manipulação direta — rastreamento 1:1
"O toque e o conteúdo devem se mover juntos."
Quando o usuário arrasta algo, deve permanecer grudado ao dedo — e respeitar o deslocamento de onde o agarrou. Prender ao centro do elemento ao agarrar quebra a ilusão imediatamente.
- Use Pointer Events com
setPointerCapturepara que o rastreamento continue mesmo quando o ponteiro sai dos limites do elemento. - Rastreie um curto histórico de velocidade/posição (últimos eventos
pointermove), não apenas o ponto atual — você precisará da velocidade na liberação.
el.addEventListener('pointerdown', (e) => {
el.setPointerCapture(e.pointerId);
const grabOffset = e.clientY - el.getBoundingClientRect().top; // respeita onde agarrou
// ...rastreia histórico de posição + timestamp para velocidade
});
3. Interrompibilidade — o princípio mais importante
"O pensamento e o gesto acontecem em paralelo."
Cada animação deve ser interrompível e redirecionável a qualquer momento. Um usuário deve ser capaz de agarrar um elemento em movimento no meio do voo e invertê-lo sem esperar a animação terminar. Um modal que está fechando e o usuário agarra novamente deve seguir o dedo — não terminar de fechar primeiro, depois reabrir.
- Nunca bloqueie a entrada durante uma transição.
- Sempre anime a partir do valor presente (atual), nunca do valor alvo. Na interrupção, leia a transformação ao vivo do elemento na tela e inicie a nova animação a partir daí. Começar a partir do valor lógico/alvo causa um salto visível.
- Evite transições CSS e
@keyframespara qualquer coisa orientada por gestos — elas não podem ser suavemente agarradas e revertidas no meio do voo. As Springs animam a partir do valor atual por padrão, que é exatamente o que a interrupção precisa. - Quando um gesto inverte, misture a velocidade — não faça um corte brusco. Substituir uma animação por outra em uma reversão cria uma descontinuidade de velocidade, uma "parede de tijolos." Bibliotecas de spring que carregam velocidade através de um novo destino evitam isso. (É isso que as animações aditivas do iOS fazem nativamente; na web, escolha uma biblioteca de spring que redirecione a partir da velocidade atual.)
- Decomponha o movimento 2D em molas independentes X e Y. Uma única mola em uma distância 2D dessincroniza quando X e Y têm velocidades diferentes.
4. Comportamento sobre animação — use molas
"Pense na animação como uma conversa entre você e o objeto, não algo prescrito pela interface."
Uma animação pré-programada e de duração fixa não pode responder a novas entradas. Uma mola pode — novas entradas apenas mudam o alvo, e o movimento permanece contínuo. Recorra a molas para qualquer coisa que um usuário possa tocar.
A Apple substituiu deliberadamente o trio da física (massa/rigidez/amortecimento) por dois parâmetros amigáveis ao designer. Pense nisso:
- Taxa de amortecimento — controla o overshoot.
1.0= criticamente amortecido, sem oscilação, estabilização suave.< 1.0= ultrapassa e oscila. Menor = mais saltitante. - Resposta — a rapidez com que o valor atinge o alvo, em segundos. Menor = mais rápido. Isso não é "duração" — uma mola não tem duração fixa; seu tempo de estabilização emerge dos parâmetros.
Padrões:
- Comece a maioria da UI em amortecimento
1.0(criticamente amortecido) — gracioso e não distrativo. - Adicione oscilação (amortecimento ~
0.8) apenas quando o próprio gesto carregou momentum (um flick, um arremesso, uma liberação de arrasto). Overshoot em um menu que apenas apareceu parece errado; overshoot em um cartão que você jogou parece certo.
Valores concretos que a Apple utiliza:
| Interação | Amortecimento | Resposta |
|---|---|---|
| Mover / reposicionar (ex.: PiP) | 1.0 | 0.4 |
| Rotação | 0.8 | 0.4 |
| Gaveta / folha | 0.8 | 0.3 |
Mapeamento web (Motion / Framer Motion): a API spring bounce + duration mapeia próximo ao amortecimento + resposta da Apple. Um estilo de casa seguro é damping: 1.0 molas em todos os lugares por padrão; reserve oscilação para interações físicas baseadas em momentum.
import { animate } from 'motion';
// Padrão criticamente amortecido (sem overshoot)
animate(el, { y: 0 }, { type: 'spring', bounce: 0, duration: 0.4 });
// Interação com momentum — um pouco de oscilação, apenas porque um flick a precedeu
animate(el, { y: target }, { type: 'spring', bounce: 0.2, duration: 0.4 });
5. Transferência de velocidade — a costura entre arrasto e animação
Quando um gesto termina, a animação deve continuar na velocidade exata do dedo, para que não haja costura visível entre arrastar e animar. Este é o detalhe que mais separa "fluido" de "ok."
Passe a velocidade de liberação do ponteiro como a velocidade inicial da mola. Algumas APIs de mola querem velocidade relativa — normalize-a pela distância restante até o alvo:
relativeVelocity = gestureVelocity / (targetValue − currentValue)
Exemplo: elemento em y=50, alvo y=150 (100px para ir), dedo movendo a 50px/s → velocidade inicial da mola = 50 / 100 = 0.5. Framer Motion / Motion aceitam velocidade absoluta em px/s diretamente (opção velocity), então você geralmente passa o valor bruto.
6. Projeção de momentum — anime para onde o gesto está indo
"Pegue uma pequena entrada e produza uma grande saída."
Não encaixe no limite mais próximo a partir do ponto de liberação. Use a velocidade para projetar a posição de repouso — exatamente como a desaceleração de rolagem — e então encaixe no alvo mais próximo desse ponto projetado. Isso é o que faz um flick parecer que arremessa o elemento.
Função de projeção exata da Apple (do código de exemplo Designing Fluid Interfaces):
// decelerationRate ≈ 0.998 para sensação de rolagem normal; 0.99 para mais rápida
function project(initialVelocity /* px/s */, decelerationRate = 0.998) {
return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate);
}
const projectedEndpoint = currentPosition + project(releaseVelocity);
const target = nearestSnapPoint(projectedEndpoint); // escolhe o alvo a partir da projeção
animateSpringTo(target, { velocity: releaseVelocity }); // então transfere a velocidade (§5)
Nota: o v²/(2·decel) do livro de física não é o que a Apple utiliza — use a forma de decaimento exponencial acima. Este é o comportamento padrão em boas bottom-sheets e carrosséis (Vaul, Embla).
7. Consistência espacial — caminhos simétricos, origens ancoradas
"Se algo desaparece de uma maneira, esperamos que emergia de onde veio."
- Entre e saia pelo mesmo caminho. Um painel que desliza da direita deve ser dispensado para a direita. Entrar pela direita / sair pela parte de baixo parece desconexo e confuso.
- Ancore as interações à sua origem. Um menu, popover ou folha deve originar-se do elemento que o acionou — defina
transform-originpara o acionador, para que a relação espacial entre botão e conteúdo seja óbvia. (Este é o mesmo ponto de consciência de origem que popovers escalando a partir de seu acionador, não de seu centro.) - Espelhe a suavização em transições reversíveis para que o caminho de saída corresponda ao caminho de retorno (use pontos de controle cúbicos-bézier inversos para as duas direções).
8. Dica na direção do gesto
Humanos predizem um estado final a partir de uma trajetória. O movimento intermediário deve telegrafar para onde as coisas estão indo — os módulos da Central de Controle "crescem para cima e para fora em direção ao seu dedo." Faça os quadros intermediários apontarem para o resultado, não apenas interpole cegamente até ele.
9. Rubber-banding — limites suaves
Em uma borda, resista progressivamente em vez de parar bruscamente. Uma parada brusca parece "congelada"; resistência contínua parece "responsiva, mas não há mais nada aqui." Aplique amortecimento que aumenta quanto mais longe do limite o usuário arrasta.
// Quanto mais longe do limite, menos o elemento segue — coisas reais desaceleram antes de parar
function rubberband(overshoot, dimension, constant = 0.55) {
return (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
}
10. Detalhes de design de gestos (a lista de verificação de "sensação")
- Toque: destaque no touch-down (instantâneo), confirme no touch-up. Adicione ~10px de histerese/almofada de acerto ao redor do alvo e permita cancelar arrastando para longe e de volta.
- Arrasto/deslize: exija um pequeno limite de movimento (histerese, ~10px) antes de se comprometer com uma direção, então rastreie 1:1.
- Detecte todos os gestos plausíveis em paralelo desde o primeiro movimento, então cancele com confiança os perdedores assim que a intenção estiver clara. Evite reconhecedores que relatam apenas um estado final (eventos do tipo
swipeleft) — eles jogam fora o rastreamento contínuo necessário para feedback. - Minimize atrasos de desambiguação. A detecção de toque duplo inevitavelmente atrasa toques simples; só pague esse custo onde o toque duplo realmente existe.
11. Suavidade no nível do quadro
Suavidade é sobre o que está nos quadros, não apenas a taxa de quadros.
- Mantenha a mudança posicional por quadro abaixo do limite de percepção para evitar estroboscopia.
- Para movimento muito rápido, um sutil desfoque de movimento / esticamento codifica velocidade e é melhor visualmente do que um traçado nítido e duro.
requestAnimationFrameé o relógio sincronizado com a tela da web (a Apple usaCADisplayLink). Anime apenas propriedades amigáveis ao compositor —transformeopacity— e dê dicas comwill-changeonde o movimento é iminente.
12. Materiais e profundidade — translucidez transmite hierarquia
A Apple usa materiais translúcidos como uma camada funcional flutuante que traz estrutura sem roubar o foco. Na web, aproxime com backdrop-filter.
- Construa barras de navegação/barras de ferramentas/folhas como camadas translúcidas (
backdrop-filter: blur()+ um fundo semi-transparente) com conteúdo rolando por baixo — não barras opacas que consomem uma faixa fixa. - O peso do material codifica hierarquia: materiais mais escuros/mais pesados separam regiões estruturais (barras laterais); materiais mais claros atraem a atenção para elementos interativos (botões). Nunca empilhe uma superfície translúcida clara sobre outra — a legibilidade desmorona.
- Superfícies maiores devem parecer mais espessas: desfoque mais forte + uma sombra mais profunda do que pequenos chips. Considere sombra sensível ao contexto — mais pesada sobre conteúdo movimentado/texto para separação, mais leve sobre fundos simples.
- Escureça para focar, separe para manter o fluxo. Uma tarefa modal emparelha a superfície com um véu de escurecimento e empurra o fundo para trás/para baixo. Um painel paralelo e não bloqueador usa translucidez e deslocamento sem um véu para que o fluxo não seja quebrado. Para folhas empilhadas, escureça e empurre progressivamente cada camada pai.
- Vibração mantém o texto legível sobre fundos variáveis. Sobre superfícies desfocadas/translúcidas, não use texto cinza plano — use contraste mais alto, peso ligeiramente mais pesado e um pequeno aumento no espaçamento entre letras. Coloque a cor em uma camada sólida, não no primeiro plano translúcido.
- Efeitos de borda de rolagem, não divisores duros. Em vez de uma borda de 1px sob um cabeçalho fixo, desbote uma pequena máscara de desfoque/gradiente onde o conteúdo encontra o cromo flutuante — apenas onde a UI flutuante realmente sobrepõe o conteúdo.
- Materialize, não apenas apareça. Para superfícies de vidro/desfoque, anime o raio de desfoque e a escala juntos na entrada/saída, para que a superfície seja lida como um material real chegando, em vez de um fade de opacidade simples.
.toolbar {
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(20px) saturate(180%);
border-top: 1px solid rgba(255, 255, 255, 0.4); /* borda superior brilhante = luz pegando no material */
}
13. Feedback multimodal — movimento + som + háptica
Três regras para combinar sentidos (de Designing Audio-Haptic Experiences):
- Causalidade — deve ser óbvio o que causou o feedback. Acione-o no evento causal real (a alternância virando, o item encaixando no lugar) e combine seu caráter com a fisicalidade da ação.
- Harmonia — o visual, o som e o háptico devem disparar no mesmo quadro. A latência entre eles destrói a ilusão. Não deixe uma transição CSS atrasar o áudio/háptico (API de Vibração).
- Utilidade — adicione feedback apenas onde ele ganha seu lugar. Reserve háptica/som para momentos significativos (sucesso, erro, confirmação, encaixe). Excesso de feedback treina os usuários a ignorar tudo.
14. Movimento reduzido e acessibilidade
Movimento reduzido não significa nenhum feedback
...[truncado para tradução]