Animación de desplazamiento al estilo Apple
Los efectos se controlan con la posición del desplazamiento, no se activan por un temporizador. Desplazarse hacia arriba los reproduce en reversa. No se necesita ninguna librería.
Las tres ideas
- Fijar. Un contenedor alto (ej.
height: 300vh) contiene un hijo conposition: sticky; top: 0; height: 100vh; overflow: hidden. El hijo permanece en pantalla mientras el contenedor se desplaza. La altura extra del contenedor es tu presupuesto de animación. - Progreso. Una función convierte la posición de desplazamiento en un número de 0 a 1:
- sección fija:
clamp(-rect.top / (rect.height - innerHeight)) - elemento que entra al viewport:
clamp((innerHeight*a - rect.top) / (innerHeight*(a-b)))
- sección fija:
- Controla los visuales desde el número. Ajusta
translate,rotate,scale,opacity(yborderRadius) en cada fotograma. Un solo listener descrolllimitado conrequestAnimationFramerecalcula todo.
Motor listo para pegar: references/scroll-engine.js.
Efectos que funcionan bien (elige 3-5, no todos)
| Efecto | Cómo hacerlo |
|---|---|
| Ensamblar | Los elementos comienzan dispersos alrededor del borde del viewport (ángulo aleatorio, rotación, escala 0.35, opacidad 0.2) y se suavizan hasta su posición final. Calcula el desplazamiento de cada elemento a su lugar natural una vez (al cargar/redimensionar), luego multiplícalo por (1 - ease(p)). Escalona por índice. |
| Iluminación de palabras | Envuelve cada palabra en un span. Opacidad .18 → 1 conforme clamp(p * 1.25 * N - k). |
| Stepper fijo | step = floor(p * N); la fracción p*N - step llena una barra de progreso dentro del paso activo. Al hacer clic en un paso se debe ejecutar scrollTo a la posición de ese paso. |
| Inclinación plana | perspective(1400px) rotateX((1-e)*22deg) translateY((1-e)*60px) scale(.84 + .16*e) para un mockup de teléfono/dispositivo. |
| Tarjetas ascendentes | translate: 0 (1-e)*80px; scale: .9+.1e; opacity: e, con retardo por columna (i % cols). |
| Expandir | Video/imagen scale .78 → 1 y borderRadius 44px → 28px. |
Usa ease = t => 1 - (1-t)**3 en el progreso antes de aplicarlo.
Reglas (aprendidas con esfuerzo)
- Usa las propiedades CSS individuales
translate,rotate,scalepara el control por scroll, y manténtransformpara el hover. De lo contrario, hover y scroll pelean por la misma propiedad. - No combines una clase
.revealde desvanecimiento con una transformación controlada por scroll en el mismo elemento. Elige una. - Coloca el encabezado de la sección dentro del panel fijo junto con el contenido. Si el panel solo contiene el contenido y está centrado, los monitores altos muestran un gran espacio en blanco.
- Mantén la distancia de desplazamiento modesta. Aproximadamente
N * 55vh + 100vhpara un stepper de N pasos,240-300vhpara un efecto único. Los usuarios encuentran cansancio con 700vh+. - Limita lo que está fijo a lo que cabe en 100vh. En pantallas pequeñas (
max-height: 820px) y teléfonos, oculta subtítulos y extras, y muestra solo el paso activo en un stepper. - Movimiento reducido: si
prefers-reduced-motion: reduce, devuelve progreso1en todas partes (estado final, sin movimiento). No dejes cosas ocultas. - Sticky no necesita
overflowen ancestros exceptooverflow-x: hiddenenbody, que está bien. Evitaoverflow: hiddenen cualquier contenedor entre el elemento sticky y la página. - Vuelve a calcular al redimensionar (los desplazamientos de ensamblaje dependen del layout).
- Anima solo
translate/rotate/scale/opacity/border-radius; evita propiedades de layout. - Evita un segmento muy pequeño al principio/final del video: si controlas la reproducción de
<video>con el scroll, los clips cortos pueden renderizarse en blanco.
Orden típico de la página
Hero (fijo, ensamblar) → una declaración simple (sin efecto) → prueba/película (expandir) → stepper fijo → mockup de dispositivo (inclinación plana) → cuadrícula de tarjetas (ascendentes) → CTA. Alterna secciones negras y gris claro para lograr el estilo Apple; un color de acento.
Pruebas
El --screenshot de Chrome sin interfaz no puede desplazarse. Controla un Chrome real a través del protocolo DevTools:
chrome --headless=new --remote-debugging-port=9333 --user-data-dir=/tmp/prof about:blank &- Desde Node (v22+ tiene
WebSocketglobal): obténhttp://localhost:9333/json, abre lawebSocketDebuggerUrlde la página, envíaEmulation.setDeviceMetricsOverride,Page.navigate, luego para cada posiciónRuntime.evaluate("scrollTo(0,Y)"), espera ~700 ms,Page.captureScreenshot. - Elige Y basado en los
offsetTopy alturas de las secciones (ej. 0%, 60%, 100% de cada sección fija). - Verifica en escritorio (1440x900), pantalla grande (2000x1300) y teléfono (390x800), y confirma que
document.documentElement.scrollWidth === innerWidth(sin desbordamiento horizontal).
Lista de verificación para la entrega
- Todos los efectos controlados por el progreso del scroll, reversibles
- Soporte para movimiento reducido
- Diseño para teléfono verificado, sin scroll horizontal
- Longitud de desplazamiento de la sección fija modesta; sin espacio en blanco en pantallas altas
- Capturas de pantalla tomadas al inicio / mitad / final de cada sección fija