Diseño Apple
Cómo Apple construye interfaces que dejan de sentirse como una computadora y comienzan a sentirse como una extensión de ti. Este conocimiento proviene de las charlas de diseño de la WWDC de Apple — principalmente Designing Fluid Interfaces (WWDC 2018) — destilado y traducido para la plataforma web (CSS, Pointer Events, requestAnimationFrame, librerías de resortes como Motion/Framer Motion).
El hilo conductor: una interfaz se siente viva cuando el movimiento comienza desde el valor actual en pantalla, hereda la velocidad del usuario, proyecta el impulso hacia adelante y se puede agarrar e invertir en cualquier instante. Los resortes son la herramienta que hace que todo esto sea natural, porque son inherentemente interrumpibles y conscientes de la velocidad.
La Idea Central
"Cuando alineamos la interfaz con la forma en que pensamos y nos movemos, sucede algo mágico: deja de sentirse como una computadora y comienza a sentirse como una extensión perfecta de nosotros."
Una interfaz es fluida cuando se comporta como el mundo físico: las cosas responden instantáneamente, se mueven de forma continua, llevan impulso, resisten en los límites y se pueden redirigir en medio del movimiento. Todo lo que sigue es una forma de acercarse a eso.
Apple enmarca el diseño como el servicio de cuatro necesidades humanas: seguridad/previsibilidad, comprensión, logro y alegría. Cada regla aquí sirve a una de ellas.
1. Respuesta — eliminar la latencia
En el momento en que aparece el retraso, la sensación de inmediatez "cae por un precipicio". La respuesta es la base sobre la que se construye todo lo demás.
- Responde al presionar, no al soltar. Resalta un botón en el instante en que se presiona. Esperar al evento
click/levantar el dedo para mostrar retroalimentación se siente muerto. - Vigila cada latencia. Audita los debounces, temporizadores artificiales, esperas de transición y el retraso de ~300ms del toque. Cualquier cosa en la ruta de entrada que no sea esencial es una regresión.
- La retroalimentación debe ser continua durante la interacción, no solo al final. Para un arrastre, deslizador o cajón, actualiza la UI 1:1 con el puntero durante todo el proceso — nunca animes solo cuando el gesto se complete.
/* La retroalimentación vive en la presión, y es instantánea */
.button:active {
transform: scale(0.97);
transition: transform 100ms ease-out;
}
2. Manipulación directa — seguimiento 1:1
"El toque y el contenido deben moverse juntos."
Cuando el usuario arrastra algo, debe permanecer pegado al dedo — y respetar el desplazamiento desde donde lo agarró. Ajustar al centro del elemento al agarrarlo rompe la ilusión de inmediato.
- Usa Pointer Events con
setPointerCapturepara que el seguimiento continúe incluso cuando el puntero sale de los límites del elemento. - Mantén un breve historial de velocidad/posición (los últimos eventos
pointermove), no solo el punto actual — necesitarás la velocidad al soltar.
el.addEventListener('pointerdown', (e) => {
el.setPointerCapture(e.pointerId);
const grabOffset = e.clientY - el.getBoundingClientRect().top; // respeta dónde agarraron
// ...registra la posición + la marca de tiempo para la velocidad
});
3. Interrumpibilidad — el principio más importante
"El pensamiento y el gesto ocurren en paralelo."
Cada animación debe ser interrumpible y redirigible en cualquier momento. El usuario debe poder agarrar un elemento en movimiento a medio vuelo e invertirlo sin esperar a que termine la animación. Un modal que se cierra y que el usuario vuelve a agarrar debe seguir el dedo — no terminar de cerrarse primero y luego reabrirse.
- Nunca bloquees la entrada durante una transición.
- Anima siempre desde el valor de presentación (actual), nunca desde el valor objetivo. Al interrumpir, lee la transformación en vivo del elemento en pantalla e inicia la nueva animación desde ahí. Comenzar desde el valor lógico/objetivo provoca un salto visible.
- Evita las transiciones CSS y
@keyframespara cualquier cosa impulsada por gestos — no se pueden agarrar e invertir suavemente a medio vuelo. Los resortes animan desde el valor actual por defecto, que es exactamente lo que necesita la interrupción. - Cuando un gesto se invierte, combina la velocidad — no la cortes bruscamente. Reemplazar una animación con otra en una inversión crea una discontinuidad de velocidad, un "muro de ladrillos". Las librerías de resortes que arrastran la velocidad a través de un reajuste lo evitan. (Esto es lo que las animaciones aditivas de iOS hacen de forma nativa; en la web, elige una librería de resortes que reajuste desde la velocidad actual).
- Descompón el movimiento 2D en resortes independientes para X e Y. Un solo resorte en una distancia 2D se desincroniza cuando X e Y tienen velocidades diferentes.
4. Comportamiento sobre animación — usa resortes
"Piensa en la animación como una conversación entre tú y el objeto, no algo prescrito por la interfaz."
Una animación preprogramada y de duración fija no puede responder a nuevas entradas. Un resorte puede — una nueva entrada solo cambia el objetivo, y el movimiento se mantiene continuo. Recurre a los resortes para cualquier cosa que el usuario pueda tocar.
Apple reemplazó deliberadamente el trío de física (masa/rigidez/amortiguamiento) con dos parámetros amigables para el diseñador. Piensa en estos:
- Relación de amortiguamiento (damping ratio) — controla el sobreimpulso.
1.0= críticamente amortiguado, sin rebote, asentamiento suave.< 1.0= sobrepasa y oscila. Menor = más saltarín. - Respuesta (response) — qué tan rápido el valor alcanza el objetivo, en segundos. Menor = más rápido. Esto no es "duración" — un resorte no tiene duración fija; su tiempo de asentamiento surge de los parámetros.
Valores predeterminados:
- Comienza la mayoría de las UI con amortiguamiento
1.0(críticamente amortiguado) — elegante y no distractivo. - Añade rebote (amortiguamiento ~
0.8) solo cuando el gesto en sí mismo llevaba impulso (un movimiento rápido, un lanzamiento, una liberación de arrastre). Un sobreimpulso en un menú que simplemente apareció se siente mal; un sobreimpulso en una tarjeta que lanzaste se siente bien.
Valores concretos que Apple envía:
| Interacción | Amortiguamiento | Respuesta |
|---|---|---|
| Mover / reposicionar (ej. PiP) | 1.0 | 0.4 |
| Rotación | 0.8 | 0.4 |
| Cajón / hoja | 0.8 | 0.3 |
Mapeo web (Motion / Framer Motion): la API de resorte bounce + duration se mapea cercanamente al amortiguamiento + respuesta de Apple. Un estilo base seguro es resortes con damping: 1.0 en todas partes por defecto; reserva el rebote para interacciones físicas impulsadas por el momento.
import { animate } from 'motion';
// Predeterminado críticamente amortiguado (sin sobreimpulso)
animate(el, { y: 0 }, { type: 'spring', bounce: 0, duration: 0.4 });
// Interacción con momento — un poco de rebote, solo porque lo precedió un movimiento rápido
animate(el, { y: target }, { type: 'spring', bounce: 0.2, duration: 0.4 });
5. Transferencia de velocidad — la unión entre arrastre y animación
Cuando un gesto termina, la animación debe continuar a la velocidad exacta del dedo, para que no haya una unión visible entre arrastrar y animar. Este es el detalle que más separa lo "fluido" de lo "correcto".
Pasa la velocidad de liberación del puntero como la velocidad inicial del resorte. Algunas APIs de resortes quieren una velocidad relativa — normalízala por la distancia restante al objetivo:
velocidadRelativa = velocidadDelGesto / (valorObjetivo − valorActual)
Ejemplo: elemento en y=50, objetivo y=150 (100px por recorrer), dedo moviéndose a 50px/s → velocidad inicial del resorte = 50 / 100 = 0.5. Framer Motion / Motion toman la velocidad absoluta en px/s directamente (opción velocity), por lo que generalmente le pasas el valor sin procesar.
6. Proyección de momento — anima hacia donde se dirige el gesto
"Toma una pequeña entrada y haz una gran salida."
No te ajustes al límite más cercano desde el punto de liberación. Usa la velocidad para proyectar la posición de reposo — exactamente como la desaceleración de desplazamiento — y luego ajusta al objetivo más cercano a ese punto proyectado. Esto es lo que hace que un movimiento rápido se sienta como si lanzara el elemento.
La función de proyección exacta de Apple (del código de muestra Designing Fluid Interfaces):
// decelerationRate ≈ 0.998 para sensación de desplazamiento normal; 0.99 para más 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); // elige el objetivo de la proyección
animateSpringTo(target, { velocity: releaseVelocity }); // luego transfiere la velocidad (§5)
Nota: la fórmula de física v²/(2·decel) no es lo que Apple envía — usa la forma de decaimiento exponencial anterior. Este es el comportamiento estándar en buenos bottom-sheets y carruseles (Vaul, Embla).
7. Coherencia espacial — caminos simétricos, orígenes anclados
"Si algo desaparece de una manera, esperamos que surja de donde vino."
- Entra y sale por el mismo camino. Un panel que se desliza desde la derecha debe descartarse hacia la derecha. Entrar desde la derecha / salir por abajo se siente desconectado y confuso.
- Ancla las interacciones a su origen. Un menú, popover o hoja debe originarse desde el elemento que lo desencadenó — establece
transform-original desencadenante, para que la relación espacial entre el botón y el contenido sea obvia. (Este es el mismo punto de conciencia de origen que los popovers escalando desde su desencadenante, no desde su centro). - Refleja la aceleración en transiciones reversibles para que el camino de salida coincida con el camino de retorno (usa puntos de control cúbico-bezier inversos para las dos direcciones).
8. Insinúa en la dirección del gesto
Los humanos predicen un estado final a partir de una trayectoria. El movimiento intermedio debe telegrafiar hacia dónde se dirigen las cosas — los módulos del Centro de Control "crecen hacia arriba y hacia afuera hacia tu dedo". Haz que los fotogramas intermedios apunten al resultado, no solo interpolen ciegamente hacia él.
9. Bandas de goma (Rubber-banding) — límites suaves
En un borde, resiste de forma progresiva en lugar de detenerte bruscamente. Una parada brusca se lee como "congelado"; una resistencia continua se lee como "responsive, pero no hay nada más aquí". Aplica un amortiguamiento que aumente cuanto más allá del límite arrastre el usuario.
// Cuanto más allá del límite, menos sigue el elemento — las cosas reales se ralentizan antes de detenerse
function rubberband(overshoot, dimension, constant = 0.55) {
return (overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
}
10. Detalles del diseño de gestos (la lista de verificación de "sensación")
- Toque: resalta al bajar el toque (instantáneo), confirma al levantar el toque. Añade ~10px de histéresis/relleno de acierto alrededor del objetivo, y permite cancelar arrastrando hacia afuera y volviendo.
- Arrastre/deslizamiento: requiere un pequeño umbral de movimiento (histéresis, ~10px) antes de comprometerse con una dirección, luego rastrea 1:1.
- Detecta todos los gestos plausibles en paralelo desde el primer movimiento, luego cancela con confianza los perdedores una vez que la intención esté clara. Evita los reconocedores que solo informan un estado final (eventos tipo
swipeleft) — descartan el seguimiento continuo que necesitas para la retroalimentación. - Minimiza las demoras de desambiguación. La detección de doble toque inevitablemente retrasa el toque simple; solo paga ese costo donde realmente existe el doble toque.
11. Suavidad a nivel de fotograma
La suavidad se trata de lo que hay en los fotogramas, no solo de la tasa de fotogramas.
- Mantén el cambio posicional por fotograma por debajo del umbral de percepción para evitar el efecto estroboscópico.
- Para movimientos muy rápidos, un sutil desenfoque de movimiento / estiramiento codifica la velocidad y se lee mejor que una línea dura y nítida.
requestAnimationFramees el reloj sincronizado con la pantalla de la web (Apple usaCADisplayLink). Anima solo propiedades amigables con el compositor —transformyopacity— e indica conwill-changedonde el movimiento es inminente.
12. Materiales y profundidad — la translucidez transmite jerarquía
Apple usa materiales translúcidos como una capa funcional flotante que aporta estructura sin robar el foco. En la web, aproxima con backdrop-filter.
- Construye barras de navegación/herramientas/hojas como capas translúcidas (
backdrop-filter: blur()+ un fondo semitransparente) con contenido que se desplaza debajo — no barras opacas que consumen una franja fija. - El peso del material codifica la jerarquía: los materiales más oscuros/más pesados separan regiones estructurales (barras laterales); los materiales más ligeros atraen la atención hacia elementos interactivos (botones). Nunca apiles una superficie translúcida clara sobre otra — la legibilidad se colapsa.
- Las superficies más grandes deben leerse como más gruesas: un desenfoque más fuerte + una sombra más profunda que los chips pequeños. Considera sombras sensibles al contexto — más pesadas sobre contenido ocupado/de texto para separación, más ligeras sobre fondos simples.
- Atenúa para enfocar, separa para mantener el flujo. Una tarea modal empareja la superficie con una cortina de atenuación y empuja el fondo hacia atrás/abajo. Un panel paralelo y no bloqueante usa translucidez y desplazamiento sin una cortina para que el flujo no se rompa. Para hojas apiladas, atenúa y empuja hacia atrás progresivamente cada capa principal.
- La vitalidad (vibrancy) mantiene el texto legible sobre fondos cambiantes. Sobre superficies translúcidas/desenfocadas, no uses texto gris plano — usa un contraste más alto, un peso ligeramente mayor y un pequeño aumento en el espaciado entre letras. Pon el color en una capa sólida, no en el primer plano translúcido.
- Efectos de borde de desplazamiento, no divisores duros. En lugar de un borde de 1px debajo de un encabezado fijo, desvanece una pequeña máscara de desenfoque/gradiente donde el contenido se encuentra con el cromo flotante — solo donde la UI flotante realmente se superpone al contenido.
- Materializa, no solo desvanezcas. Para superficies de vidrio/desenfoque, anima el radio de desenfoque y la escala juntos al entrar/salir, para que la superficie se lea como un material real que llega en lugar de un simple desvanecimiento de opacidad.
.toolbar {
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(20px) saturate(180%);
border-top: 1px solid rgba(255, 255, 255, 0.4); /* borde superior brillante = luz incidiendo en el material */
}
13. Retroalimentación multimodal — movimiento + sonido + háptica
Tres reglas para combinar sentidos (de Designing Audio-Haptic Experiences):
- Causalidad — debe ser obvio qué causó la retroalimentación. Actívala en el evento causal real (el interruptor que cambia, el elemento que encaja en su lugar), y haz coincidir su carácter con la fisicalidad de la acción.
- Armonía — lo visual, el sonido y lo háptico deben activarse en el mismo fotograma. La latencia entre ellos destruye la ilusión. No dejes que una transición CSS retrase el audio/háptico (API de Vibración).
- Utilidad — añade retroalimentación solo donde se gane su lugar. Reserva háptica/sonido para momentos significativos (éxito, error, compromiso, ajuste). El exceso de retroalimentación entrena al usuario a ignorarla toda.
14. Movimiento reducido y accesibilidad
Movimiento reducido no significa sin retroalimentación — significa que la retroalimentación no debe ser disruptiva. Usa la consulta de medios prefers-reduced-motion de CSS para detectar la preferencia del sistema por movimiento reducido. Cuando esté activa:
- Reemplaza las animaciones basadas en movimiento con transiciones de opacidad simples y cortas.
- Evita escalados, traslaciones y animaciones de desenfoque que podrían causar molestias.
- Asegúrate de que toda la información transmitida a través del movimiento (como el estado de arrastre o el enfoque) también esté disponible de forma estática.
- Mantén las animaciones funcionales, como las que indican carga o progreso, pero hazlas sutiles y no distractivas.
@media (prefers-reduced-motion: reduce) {
.animated-element {
transition: opacity 150ms ease-out;
transform: none !important;
}
}
La accesibilidad no es una ocurrencia tardía; es parte integral del diseño. Un diseño fluido debe ser utilizable y comprensible para todos, independientemente de sus capacidades motoras, visuales o cognitivas.
Conclusión
Los principios de diseño de Apple para interfaces fluidas son un conjunto de pautas que priorizan la sensación de inmediatez, control y naturalidad. Al implementarlos en la web, es crucial adaptarlos a las capacidades y limitaciones de la plataforma, pero el espíritu permanece: una interfaz debe sentirse como una extensión del usuario, respondiendo a sus acciones de manera predecible, suave e interrumpible. Desde la respuesta instantánea hasta la transferencia de velocidad y la proyección de momento, cada detalle contribuye a esa sensación mágica de que el software desaparece y solo queda la interacción humana.