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 para a web. Meu conhecimento vem das palestras de design da WWDC da Apple, traduzido 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 de mola 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 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 à maneira 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 mesmos."
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 precipício". 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 evento
click/toque para mostrar feedback parece morto. - Seja vigilante com cada latência. Audite debounces, temporizadores 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, controle deslizante ou gaveta, atualize a interface 1:1 com o ponteiro durante todo o percurso — nunca anime apenas quando o gesto for 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 foi agarrado. Ajustar para o centro do elemento ao agarrar quebra a ilusão imediatamente.
- Use Pointer Events com
setPointerCapturepara que o rastreamento continue mesmo quando o ponteiro sair dos limites do elemento. - Rastreie um breve histórico de posição/velocidade (ú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 foi agarrado
// ...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 trajeto 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 presente (atual), nunca do valor alvo. Ao interromper, leia a transformação ativa na tela do elemento e inicie a nova animação a partir dela. 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 durante o movimento. As molas animam a partir do valor atual por padrão, que é exatamente o que a interrupção precisa. - Quando um gesto reverte, 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 mola que carregam a velocidade através de um novo alvo evitam isso. (É isso que as animações aditivas do iOS fazem nativamente; na web, escolha uma biblioteca de mola que mire novamente a partir da velocidade atual.)
- Decomponha o movimento 2D em molas independentes para 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é-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 deliberadamente substituiu o trio da física (massa/rigidez/amortecimento) por dois parâmetros mais amigáveis ao designer. Pense neles:
- Razão de amortecimento — controla o overshoot.
1.0= criticamente amortecido, sem oscilação, ajuste suave.< 1.0= overshoot e oscila. Quanto menor, mais saltitante. - Resposta — quão rapidamente 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 acomodação emerge dos parâmetros.
Padrões:
- Comece a maioria das interfaces 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 arremesso, um lançamento, uma liberação de arrasto). Overshoot em um menu que apenas apareceu parece errado; overshoot em um cartão que você arremessou 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 de mola bounce + duration mapeia de perto o amortecimento + resposta da Apple. Um estilo seguro padrão é usar molas com damping: 1.0 em toda parte por padrão; reserve a oscilação para interações físicas com 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 arremesso 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 'bom'.
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 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 geralmente você 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 liberação. Use a velocidade para projetar a posição de repouso — exatamente como a desaceleração de rolagem — e então ajuste para o alvo mais próximo desse ponto projetado. Isso é o que faz um arremesso parecer que está lançando 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 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 bons bottom-sheets e carrosséis (Vaul, Embla).
7. Consistência espacial — caminhos simétricos, origens ancoradas
"Se algo desaparece de uma maneira, esperamos que ele surja de onde veio."
- Entre e saia pelo mesmo caminho. Um painel que desliza da direita deve desaparecer para a direita. Entrada pela direita / saída por baixo parece desconexo e confuso.
- Âncore 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 que popovers que escalam a partir do seu acionador, não do 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-bezier inversos para as duas direções).
8. Dica na direção do gesto
Os humanos preveem um estado final a partir de uma trajetória. Os quadros intermediários devem 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 para ele.
9. Rubber-banding — limites suaves
Em um limite, resista progressivamente em vez de parar bruscamente. Uma parada brusca parece "congelada"; resistência contínua parece "responsivo, 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 da 'sensação')
- Toque: destaque ao tocar-para baixo (instantâneo), confirme ao tocar-para cima. Adicione ~10px de histerese/área de contato ao redor do alvo e permita cancelar arrastando para longe e de volta.
- Arrastar/deslizar: exija um pequeno limite de movimento (histerese, ~10px) antes de se comprometer com uma direção, depois rastreie 1:1.
- Detecte todos os gestos plausíveis em paralelo desde o primeiro movimento, e então cancele com confiança os perdedores quando a intenção estiver clara. Evite reconhecedores que apenas relatam um estado final (eventos do 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 únicos; só pague esse custo onde o toque duplo realmente existe.
11. Suavidade no nível do quadro
A 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 desfoque de movimento / esticamento codifica a velocidade e é melhor do que um traçado duro e nítido.
requestAnimationFrameé o relógio sincronizado com a exibição da web (a Apple usaCADisplayLink). Anime apenas propriedades amigáveis ao compositor —transformeopacity— e dê dica 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 navegação/barras de ferramentas/folhas como camadas translúcidas (
backdrop-filter: blur()+ um fundo semitransparente) com conteúdo rolando por baixo — não barras opacas que consomem uma faixa fixa. - O peso do material codifica a hierarquia: materiais mais escuros/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: desfoque mais forte + uma sombra mais profunda do que pequenos chips. Considere sombra consciente do contexto — mais pesada sobre conteúdo ocupado/texto para separação, mais clara sobre fundos simples.
- Escureça para focar, separe para manter o fluxo. Uma tarefa modal combina 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 interrompido. Para folhas empilhadas, escureça e empurre progressivamente cada camada pai.
- A 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 maior e um pequeno aumento no espaçamento entre letras. Coloque 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 interface flutuante realmente se sobrepõe ao conteúdo.
- Materialize, não apenas desvaneç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 pareça um material real chegando, em vez de uma simples transparência.
.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. Dispare-o no evento causal real (a alternância do botão, o item se encaixando no lugar) e corresponda seu caráter à fisicalidade da ação.
- Harmonia — o visual, o som e o háptico devem disparar no mesmo quadro. Latência entre eles destrói a ilusão. Não deixe uma…` }