Design Apple
Resposta Inicial
Quando este skill for invocado pela primeira vez sem uma pergunta específica, responda apenas com:
Estou pronto para ajudar você a construir interfaces fluidas no estilo Apple na 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 passam a parecer uma extensão de você. Este conhecimento vem das palestras de design da WWDC da Apple — principalmente Designing Fluid Interfaces (WWDC 2018) — destiladas e traduzidas para a plataforma web (CSS, Pointer Events, requestAnimationFrame, bibliotecas de mola como Motion/Framer Motion).
A linha condutora: 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 revertida a qualquer instante. Molas 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 ao modo como pensamos e nos movemos, algo mágico acontece — ela deixa de parecer um computador e começa a parecer uma extensão natural 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 forma de chegar mais perto disso.
A Apple enquadra o design como serviço para 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 "despenca." A resposta é a base sobre a qual todo o resto é construído.
- Responda ao pressionar o ponteiro, não ao soltar. Destaque um botão no instante em que é pressionado. Esperar pelo
click/toque para mostrar feedback parece morto. - Esteja atento a cada latência. Audite debounces, timers artificiais, esperas de transição e o atraso de ~300ms do toque. 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 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
"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 o 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. - Rastreie um breve histórico de velocidade/posição (últimos eventos
pointermove), não apenas o ponto atual — você precisará da velocidade ao soltar.
el.addEventListener('pointerdown', (e) => {
el.setPointerCapture(e.pointerId);
const grabOffset = e.clientY - el.getBoundingClientRect().top; // respeita onde agarrou
// ...rastreia posição + histórico de 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 ser capaz de agarrar um elemento em movimento durante o voo e revertê-lo sem esperar a animação terminar. Um modal fechando que 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. Ao interromper, leia a transformação ao vivo do elemento na tela e inicie a nova animação a partir daí. Começar pelo valor lógico/alvo causa um salto visível.
- Evite transições CSS e
@keyframespara qualquer coisa orientada por gestos — eles não podem ser suavemente agarrados e revertidos durante o voo. Molas animam a partir do valor atual por padrão, que é exatamente o que a interrupção precisa. - Quando um gesto reverte, combine 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 mola que carregam a velocidade através de um novo alvo evitam isso. (Isto é o que as animações aditivas do iOS fazem nativamente; na web, escolha uma biblioteca de mola que redefina o alvo 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é-programada 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 um usuário possa tocar.
A Apple substituiu deliberadamente o trio de parâmetros físicos (massa/rigidez/amortecimento) por dois parâmetros amigáveis ao designer. Pense nestes:
- Razão de amortecimento — controla o overshoot.
1.0= amortecimento crítico, sem quique, estabilização suave.< 1.0= overshoot e oscila. Menor = mais elástico. - 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 com amortecimento
1.0(amortecimento crítico) — gracioso e não perturbador. - Adicione quique (amortecimento ~
0.8) apenas quando o gesto em si carregava 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ê 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 / painel | 0.8 | 0.3 |
Mapeamento web (Motion / Framer Motion): a API de mola bounce + duration mapeia de perto o amortecimento + resposta da Apple. Um estilo padrão seguro é usar molas com bounce: 0 em todos os lugares por padrão; reserve o quique para interações físicas impulsionadas por momentum.
import { animate } from 'motion';
// Padrão com amortecimento crítico (sem overshoot)
animate(el, { y: 0 }, { type: 'spring', bounce: 0, duration: 0.4 });
// Interação com momentum — um pouco de quique, apenas porque um flick 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 o "fluido" do "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:
velocidadeRelativa = velocidadeDoGesto / (valorAlvo − valorAtual)
Exemplo: elemento em y=50, alvo y=150 (100px a percorrer), dedo movendo-se 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 ao limite mais próximo a partir do ponto de liberação. Use a velocidade para projetar a posição de descanso — exatamente como a desaceleração de rolagem — e então ajuste ao alvo mais próximo daquele ponto projetado. É isso 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 normal de rolagem; 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 transfere a velocidade (§5)
Nota: a fórmula 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 surja 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 por baixo parece desconectado e confuso.
- Ancore as interações à sua origem. Um menu, popover ou painel 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 que popovers escalonam a partir de seu acionador, não de seu centro.) - Espelhe a easing em transições reversíveis para que o caminho de saída corresponda ao caminho de retorno (use pontos de controle cúbicos-bezier inversos para as duas direções).
8. Dê dicas 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 com que os quadros intermediários apontem para o resultado, não apenas interpolem cegamente até ele.
9. Elasticidade — limites suaves
Em um limite, resista progressivamente em vez de parar bruscamente. Uma parada brusca parece "congelada"; a resistência contínua parece "responsiva, 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 lista de verificação da "sensação")
- Toque: destaque ao pressionar (instantâneo), confirme ao soltar. Adicione ~10px de histerese/padding de acerto ao redor do alvo e permita cancelar arrastando para longe e voltando.
- Arrastar/deslizar: 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 descartam o rastreamento contínuo necessário para o 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 limiar de percepção para evitar estroboscopia.
- Para movimento muito rápido, um sutil desfoque de movimento / esticamento codifica a velocidade e é mais legível do que um traço 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/painéis 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. - 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 desaba.
- 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/texto movimentado para separação, mais leve sobre fundos simples.
- Escureça para focar, separe para manter o fluxo. Um modal de tarefa emparelha a superfície com um escurecimento de fundo e empurra o fundo para trás/para baixo. Um painel paralelo e não bloqueador usa translucidez e deslocamento sem um escurecimento para que o fluxo não seja interrompido. Para painéis empilhados, 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 liso — 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, desvaneça um pequeno gradiente/máscara de desfoque onde o conteúdo encontra o cromo flutuante — apenas onde a UI flutuante realmente sobrepõe o conteúdo.
- Materialize, não apenas desvaneça. Para superfícies de vidro/desfoque, anime o raio do desfoque e a escala juntos ao entrar/sair, para que a superfície seja lida como um material real chegando, em vez de um desvanecimento 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 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 que um
…
[truncado para tradução]