Design Apple
Resposta Inicial
Quando esta habilidade for invocada pela primeira vez sem uma pergunta específica, responda apenas com:
Estou pronto para ajudar você a construir interfaces fluidas no estilo Apple para a web. Meu conhecimento vem das palestras de design da WWDC da Apple, traduzidas para a web.
Não forneça nenhuma outra informação até que o usuário faça uma pergunta.
Como a Apple constrói interfaces que deixam de parecer um computador e começam a parecer uma extensão sua. Este conhecimento vem das palestras de design da WWDC da Apple — principalmente Designing Fluid Interfaces (WWDC 2018) — destilado e traduzido para a plataforma web (CSS, Pointer Events, requestAnimationFrame, bibliotecas spring como Motion/Framer Motion).
O fio condutor: 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 para frente e pode ser agarrada e invertida a qualquer instante. Molas (springs) são a ferramenta que torna tudo isso natural, pois são inerentemente interrompíveis e cientes da velocidade.
A Ideia Central
"Quando alinhamos a interface com a forma como pensamos e nos movemos, algo mágico acontece — ela deixa de parecer um computador e passa a ser 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 durante o movimento. Tudo abaixo é uma maneira de chegar mais perto 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 todo o resto é construído.
- Responda no pointer-down, não no release. 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 ~300ms do tap. 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, controle deslizante ou gaveta, atualize a UI 1:1 com o ponteiro o tempo todo — nunca anime apenas quando o gesto for concluído.
/* O feedback vive no pressionamento, e é instantâneo */
.button:active {
transform: scale(0.97);
transition: transform 100ms ease-out;
}
2. Manipulação direta — rastreamento 1:1
"Toque e conteúdo devem se mover juntos."
Quando o usuário arrasta algo, ele deve permanecer grudado ao dedo — e respeitar o deslocamento de onde ele agarrou. Ajustar 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. - Mantenha um breve histórico de velocidade/posição (últimos eventos
pointermove), não apenas o ponto atual — você precisará da velocidade no release.
el.addEventListener('pointerdown', (e) => {
el.setPointerCapture(e.pointerId);
const grabOffset = e.clientY - el.getBoundingClientRect().top; // respeita onde ele agarrou
// ...rastreia posição + timestamp para velocidade
});
3. Interrompibilidade — o princípio mais importante
"O pensamento e o gesto acontecem em paralelo."
Toda animação deve ser interrompível e redirecionável a qualquer momento. Um usuário deve poder agarrar um elemento em movimento no meio do ar e revertê-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 e depois reabrir.
- Nunca bloqueie a entrada durante uma transição.
- Sempre anime a partir do valor de apresentação (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 a gestos — eles não podem ser suavemente agarrados e invertidos no meio do movimento. Molas animam a partir do valor atual por padrão, que é exatamente o que a interrupção precisa. - Quando um gesto reverte, mescle a velocidade — não a corte abruptamente. Substituir uma animação por outra em uma reversão cria uma descontinuidade de velocidade, uma "parede de tijolos". Bibliotecas spring que carregam a velocidade através de um re-target evitam isso. (É o que as animações aditivas do iOS fazem nativamente; na web, escolha uma biblioteca spring que re-direcione a partir da velocidade atual.)
- Decomponha o movimento 2D em molas X e Y independentes. 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é-roteirizada e de duração fixa não pode responder a novas entradas. Uma mola pode — uma nova entrada apenas muda o alvo, e o movimento permanece contínuo. Recorra a molas para qualquer coisa que o usuário possa tocar.
A Apple substituiu deliberadamente o trio de física (massa/rigidez/amortecimento) por dois parâmetros amigáveis ao designer. Pense nestes:
- Razão de amortecimento — controla o overshoot.
1.0= criticamente amortecido, sem oscilação, ajuste suave.< 1.0= ultrapassa e oscila. Menor = mais saltitante. - Resposta — quão rápido 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 das UI com amortecimento
1.0(criticamente amortecido) — gracioso e não distrativo. - Adicione oscilação (amortecimento ~
0.8) apenas quando o gesto em si carregou momentum (um flick, um arremesso, um release de arrasto). Overshoot em um menu que apenas apareceu parece errado; overshoot em um cartão que você lançou 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 de perto para amortecimento + resposta da Apple. Um estilo de casa seguro é usar molas com damping: 1.0 em todos os lugares por padrão; reserve a oscilação para interações físicas e impulsionadas por 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 precedeu
animate(el, { y: target }, { type: 'spring', bounce: 0.2, duration: 0.4 });
5. Handoff 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 "bom".
Passe a velocidade de release do ponteiro como a velocidade inicial da mola. Algumas APIs spring querem velocidade relativa — normalize-a pela distância restante até o alvo:
velocidadeRelativa = velocidadeDoGesto / (valorAlvo − valorAtual)
Exemplo: elemento em y=50, alvo y=150 (100px para percorrer), 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 ajuste para o limite mais próximo a partir do ponto de release. Use a velocidade para projetar a posição de repouso — exatamente como a desaceleração de rolagem — então ajuste para o alvo mais próximo desse ponto projetado. Isso é o que faz um flick parecer que arremessa o elemento.
A função de projeção exata da Apple (do código de exemplo de Designing Fluid Interfaces):
// decelerationRate ≈ 0.998 para sensação de rolagem normal; 0.99 para mais rápido
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 faz o handoff de velocidade (§5)
Nota: a fórmula de livro-texto de física v²/(2·decel) 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 apareça de onde veio."
- Entre e saia pelo mesmo caminho. Um painel que desliza da direita deve desaparecer para a direita. Entrar pela direita / sair por baixo parece desconectado e confuso.
- Ancore as interações à sua origem. Um menu, popover ou folha deve se originar 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 dos popovers que escalam a partir de seu acionador, não de seu centro.) - Espelhe o easing em transições reversíveis para que o caminho de saída corresponda ao caminho de retorno (use pontos de controle cúbico-bezier inversos para as duas direções).
8. Sugira 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 interpolem cegamente até ele.
9. Elasticidade (rubber-banding) — limites suaves
Em um limite, resista progressivamente em vez de parar abruptamente. Uma parada brusca é lida como "congelado"; resistência contínua é lida como "responsivo, mas não há mais nada aqui." Aplique amortecimento que aumenta quanto mais o usuário arrasta além do limite.
// Quanto mais além 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 checklist de "sensação")
- Tap: destaque no touch-down (instantâneo), confirme no touch-up. Adicione ~10px de histerese/padding de acerto ao redor do alvo e permita cancelar arrastando para longe e voltando.
- 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 quando a intenção estiver clara. Evite reconhecedores que relatam apenas um estado final (eventos tipo
swipeleft) — eles jogam fora o rastreamento contínuo que você precisa para o feedback. - Minimize os 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 de posição por quadro abaixo do limite de percepção para evitar efeito estroboscópico.
- Para movimento muito rápido, um sutil borrão de movimento / estiramento codifica a 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 sugira 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 navs/barras de ferramentas/folhas como camadas translúcidas (
backdrop-filter: blur()+ um fundo semitransparente) com conteúdo rolando abaixo — 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 colapsa.
- Superfícies maiores devem parecer mais espessas: blur mais forte + sombra mais profunda do que pequenos chips. Considere sombra sensível ao contexto — mais pesada sobre conteúdo denso/ocupado para separação, mais clara 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 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 progressivamente e empurre cada camada pai para trás.
- 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 divisórias rígidas. Em vez de uma borda de 1px sob um cabeçalho fixo, desvaneça uma pequena máscara de blur/gradiente onde o conteúdo encontra o cromo flutuante — apenas onde a UI flutuante realmente se sobrepõe ao conteúdo.
- Materialize, não apenas fade. Para superfícies de vidro/blur, anime o raio do blur e a escala juntos na entrada/saída, para que a superfície pareça um material real chegando, em vez de um simples fade de opacidade.
.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 capturando o 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 do botão, o encaixe do item) 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
…
[truncado para tradução]