Galerías con scroll-snap: contenido horizontal suave sin JS

¿Te fue útil?

Publicaste una galería horizontal “simple” y ahora los tickets de soporte dicen que “se atasca”, “salta”, “se traga el scroll” o “falla en iPad”. Producto quiere que sea aterciopelado. Accesibilidad quiere soporte de teclado. Marketing quiere que parezca un carrusel, pero “sin JS, por favor”.

Buenas noticias: el scroll snapping en CSS puede encargarse de gran parte de esto—si lo diseñas como gestionas sistemas en producción: predecible, observable y resistente a entradas malas (como imágenes de 14 MB y desbordes anidados). Malas noticias: te permite construir una galería que funciona en tu portátil y arruina el día de los demás.

Qué es (y qué no es) el scroll snap

El scroll snapping no es un “componente carrusel”. Es una característica del navegador que sesga un contenedor de desplazamiento para detenerse en puntos de anclaje definidos. Tú proporcionas las pistas físicas; el agente de usuario decide cómo aterrizar. Esa distinción importa, porque no puedes mandarlo como JS. Solo puedes establecer restricciones y dejar que el navegador negocie con los dispositivos de entrada, las configuraciones de accesibilidad y la intención del usuario.

En términos prácticos:

  • scroll-snap-type va en el contenedor desplazable. Declara “encajar en el eje x” y con qué rigor.
  • scroll-snap-align va en los hijos. Declara dónde quiere alinearse cada elemento con respecto al snapport del contenedor.
  • scroll-padding y scroll-margin son los ajustes para “sí, tenemos un encabezado fijo”.
  • scroll-behavior: smooth afecta el desplazamiento programático (y algunos comportamientos del UA), no la física del toque crudo.

Base con opinión: trata el scroll-snap como una mejora progresiva. La galería debe ser usable como desplazamiento horizontal simple incluso si el snapping está deshabilitado o ignorado.

Dos cosas que el scroll snap no es:

  • No es determinista. Si pruebas con un trackpad, verás un asentamiento de snap distinto al de una rueda de ratón y distinto al táctil. Eso no es un error; es el flujo de entrada.
  • No sustituye controles de navegación. Algunos usuarios querrán controles explícitos “siguiente/anterior”. “Sin JS” no significa “sin controles”; significa que usarás anclas, foco y un diseño razonable.

Una idea parafraseada de la cultura de ingeniería que aplica aquí (y es la razón por la que nos molestamos en medir): “La esperanza no es una estrategia.” — idea parafraseada atribuida a líderes de fiabilidad/operaciones. Si tu galería “usualmente se siente bien”, aún no has probado los modos de fallo.

Decisiones de diseño que determinan si se siente premium o roto

Mandatory vs proximity: elige tu batalla

mandatory fuerza el encaje. Es ideal para experiencias de “una tarjeta a la vez”. También es el modo que hace que los usuarios se sientan atrapados si tus puntos de snap son demasiado densos o tus tarjetas demasiado estrechas. proximity es más indulgente: encaja solo cuando la parada está lo bastante cerca.

Si tus usuarios probablemente pasarán rápidamente por un feed, prefiere proximity. Si la galería es un “selector” intencional (imágenes de producto, onboarding), mandatory es razonable.

Alineación del snap: start vs center vs end

scroll-snap-align: start es lo más predecible porque coincide con cómo se lee en diseños de izquierda a derecha: el contenido comienza en un borde consistente. La alineación al centro luce “diseñada” pero es más sensible al redondeo, padding del contenedor y diferentes anchos de viewport.

  • Usa start cuando la tarjeta tenga texto o UI alineada a la izquierda.
  • Usa center cuando la tarjeta sea principalmente imagen y buscas una sensación “coverflow-ish” (sin la energía de 2007).
  • Evita end a menos que tengas un requisito específico de diseño por el borde derecho.

scroll-padding y scroll-margin: el impuesto del encabezado fijo

Si tu diseño tiene un encabezado sticky o cualquier superposición, el snapping puede dejar contenido debajo de él. Usa scroll-padding en el contenedor para mantener las posiciones de snap desplazadas respecto a los bordes. Usa scroll-margin en los hijos cuando solo algunos ítems necesiten offsets diferentes.

Patrón típico:

  • El contenedor tiene scroll-padding-inline: 16px para evitar que la primera/última tarjeta bese el borde del viewport.
  • El contenedor tiene scroll-padding-top si esto es snapping vertical (no es nuestro foco, pero mismo principio).

Espaciado: gap vs margin, y por qué importa

Los puntos de snap se calculan a partir de las cajas del layout. Si mezclas gap, márgenes y pseudo-elementos, puedes acabar con puntos de snap que no coinciden con lo que ve tu diseñador. Mantenlo aburrido:

  • Usa gap del contenedor para el espacio entre ítems.
  • Evita márgenes por ítem a menos que necesites asimetría.
  • Si necesitas espacio inicial/final, usa padding del contenedor más scroll-padding.

Scrolling anidado: puedes tenerlo, pero tiene coste

El bug más común de “por qué se siente roto” son contenedores de desplazamiento anidados: una galería horizontal dentro de una página que desplaza verticalmente, más una tarjeta con su propio overflow. Trackpads y dispositivos táctiles entonces tienen que adivinar a dónde va la intención de desplazamiento.

Usa:

  • overscroll-behavior: contain para detener el encadenamiento de scroll donde es dañino.
  • touch-action: pan-x en el scroller horizontal para reducir la confusión vertical.

Comportamiento de la barra de desplazamiento: diseño estable vence a lo bonito

Algunas plataformas muestran barras de desplazamiento sobre el contenido; otras reservan espacio. Cuando una barra aparece/desaparece, el contenido puede moverse y los puntos de snap pueden cambiar. Ahí es donde recibes informes de “saltó a la diapositiva incorrecta” que no puedes reproducir.

Si puedes, usa scrollbar-gutter: stable en el contenedor de scroll para prevenir cambios de diseño cuando aparecen las barras. No está en todas partes, pero donde se soporta es una victoria de bajo esfuerzo.

No te adaptes en exceso a un solo dispositivo

La rueda del ratón envía deltas discretos. El trackpad envía deltas continuos de alta resolución. El tacto envía momentum con desaceleración. El scroll snapping está aguas abajo de todo eso. Probar solo con un ratón es como enviar una galería que te parece bien a ti y hostil para todos los demás.

Accesibilidad y dispositivos de entrada: táctil, trackpad, teclado

Soporte de teclado sin JS: el foco es tu amigo

Sin JavaScript no vas a “escuchar flechas” y actualizar la posición de desplazamiento. Pero aún puedes ofrecer una experiencia de teclado razonable:

  • Haz los elementos enfocables (tabindex="0") o incluye contenido enfocables dentro (enlaces/botones).
  • Asegura que los elementos enfocados sean visibles. Los navegadores normalmente desplazarán elementos enfocados a la vista—a menudo respetando el snap.
  • Usa :focus-visible para mostrar un indicador de foco claro.

Si quieres controles explícitos siguiente/anterior sin JS, puedes hacerlo con enlaces ancla a IDs en las diapositivas. Es a la antigua, pero funciona y es accesible.

cr0x@server:~$ cat anchor-nav.html
<style>
  .gallery { overflow-x: auto; scroll-snap-type: x mandatory; display: grid; grid-auto-flow: column; grid-auto-columns: 85%; gap: 16px; padding: 16px; }
  .slide { scroll-snap-align: start; border: 1px solid #e6e6e6; border-radius: 12px; min-height: 200px; }
  .nav a { margin-right: 10px; }
  .gallery { scroll-behavior: smooth; }
  @media (prefers-reduced-motion: reduce) { .gallery { scroll-behavior: auto; } }
</style>

<div class="nav" aria-label="Gallery navigation">
  <a href="#s1">1</a>
  <a href="#s2">2</a>
  <a href="#s3">3</a>
</div>

<div class="gallery">
  <section id="s1" class="slide" tabindex="-1">Slide 1</section>
  <section id="s2" class="slide" tabindex="-1">Slide 2</section>
  <section id="s3" class="slide" tabindex="-1">Slide 3</section>
</div>

tabindex="-1" en las diapositivas permite el foco cuando se navega por ancla sin añadirlas al orden de tabulación. Eso mantiene el tabbing de la página sensato.

Reducir movimiento no es opcional

Si añades scroll-behavior: smooth, debes respetar prefers-reduced-motion. El desplazamiento suave puede provocar náuseas a algunos usuarios, y puede dificultar el debug porque cada interacción se anima. Haz el desplazamiento suave condicional y sigue adelante con tu vida.

Lectores de pantalla y semántica

Las galerías con scroll-snap siguen siendo contenedores de desplazamiento. No intentes simular un “widget carrusel” a menos que implementes las semánticas interactivas completas (lo que normalmente significa JS). Mantén buen HTML:

  • Usa <section>, <article>, <figure> con <figcaption> y encabezados significativos.
  • Etiqueta la región con aria-label si no está descrita por texto adyacente.
  • No atrapes el foco dentro de la galería.

Touch-action: una herramienta afilada

touch-action: pan-x puede mejorar la intención horizontal en dispositivos táctiles, pero ten cuidado: si tu galería está dentro de una página que desplaza verticalmente, aún quieres que los usuarios puedan desplazarse verticalmente cuando su dedo no está perfectamente horizontal. Prueba. Si la galería es alta y densa en contenido, puede que prefieras dejar touch-action y confiar en buen espaciado y overscroll behavior.

Rendimiento: el verdadero cuello de botella rara vez es “scroll snap”

Cuando alguien dice “scroll snap va a trompicones”, normalmente quieren decir “el desplazamiento va a trompicones mientras el snap está activado”. Esa es una diferencia importante. El snapping puede exponer problemas de rendimiento que ya existían: imágenes sobredimensionadas, thrash de layout, pintura pesada y compositing anidado.

Qué hace que el snap se sienta mal

  • Cambios de diseño durante el scroll: imágenes sin dimensiones, fuentes que se cargan después, contenido dinámico que aparece en las tarjetas.
  • Pintado pesado: sombras grandes, filtros, backdrop-filter y capas translúcidas grandes.
  • Contención del hilo principal: efectos ligados al scroll, selectores CSS costosos, demasiados elementos sticky.
  • Presión de memoria: muchas imágenes de alta resolución decodificadas a la vez; el navegador comienza a expulsar superficies.

Estabiliza el diseño primero: fija relaciones de aspecto de imágenes

Si la altura de la tarjeta cambia durante el scroll, la geometría del contenedor cambia y los puntos de snap pueden sentirse como si “se movieran”. La solución es aburrida y eficaz: declara atributos width/height de la imagen (o CSS aspect-ratio) para que el navegador reserve espacio antes de decodificar.

Containment y content-visibility: usa, pero verifica

content-visibility: auto y contain pueden mejorar el rendimiento en páginas grandes al evitar render offscreen. Pero en galerías con scroll-snap, la región “offscreen” suele estar a solo una tarjeta de distancia—y el snapping puede forzar una revelación casi instantánea. El uso excesivo puede hacer que el aterrizaje del snap se sienta como un parpadeo en blanco.

Úsalos si tienes muchas diapositivas pesadas. Luego valida en dispositivos de gama baja y Safari. Si ves en blanco, reduzca la agresividad o precarga una diapositiva adelante.

Compositing: no crees accidentalmente 40 capas

Rociar will-change: transform al azar es el equivalente frontend de activar todas las capas de cache porque “las caches son rápidas”. Es cómo quemas memoria y obtienes fallos extraños.

Broma corta #2: will-change es como imprimir “URGENTE” en cada correo—eventualmente nada lo es.

CSS scroll snapping y scroll suave

scroll-behavior: smooth puede enmascarar problemas al hacer que el movimiento parezca “diseñado”, pero también puede amplificar la sensación de retraso porque las animaciones se traban bajo carga. No uses el desplazamiento suave como parche de rendimiento. Arregla la causa raíz: layout y pintura.

Datos y breve historia que vale la pena conocer

  1. Las ideas tempranas de “puntos de snap” existían en toolkits de UI mucho antes de CSS—piensa en vistas paginadas en marcos móviles nativos.
  2. CSS Scroll Snap comenzó como un esfuerzo del W3C para formalizar el comportamiento de “paginación” para UIs táctiles, especialmente con la explosión del browsing móvil.
  3. Los nombres de propiedades cambiaron con el tiempo; los borradores antiguos usaron nomenclatura distinta, por eso aún verás fragmentos obsoletos en blogs.
  4. scroll-snap-stop existe porque los usuarios se quejaban de pasar por alto elementos al lanzar con momentum; es una perilla de control para “debe detenerse aquí”.
  5. El momentum del desplazamiento no es una característica de CSS—es comportamiento de la plataforma. Los algoritmos de snap deben coexistir con la física a nivel de OS.
  6. Los motores de navegador tratan el desplazamiento como una pipeline de alta prioridad; las implementaciones modernas se esfuerzan por mantener el desplazamiento fuera del hilo principal cuando es posible.
  7. El scroll snapping interactúa con funciones de accesibilidad como reducir movimiento; los usuarios pueden anular tu intención, y deberían poder hacerlo.
  8. Los layouts RTL complican el snapping horizontal porque “start” y “end” se invierten; pruebas en RTL son buenas incluso si tu producto es mayormente en inglés.
  9. El renderizado de barras de desplazamiento varía según SO y ajustes; puntos de snap que asumen un gutter fijo pueden desviarse cuando las barras no son overlay.

Tres micro-historias corporativas desde las trincheras

Incidente: la suposición errónea (“los puntos de snap son solo los bordes de la tarjeta”)

En una empresa mediana, un equipo de producto desplegó una galería con scroll-snap para una página de precios. Se veía genial en una captura de escritorio Chrome. En iOS Safari, los usuarios reportaron que la galería “se negaba a asentarse” y a veces encajaba en lo que parecía media tarjeta.

La suposición errónea fue sutil: el ingeniero asumió que las posiciones de snap se alinearían con el borde visual izquierdo de la tarjeta. Pero el diseño usaba márgenes negativos en las tarjetas para crear un efecto de “sobresaliente”, además de un pseudo-elemento para un degradado. La caja de layout no coincidente con el borde visual.

Soporte lo reprodujo rápido en iPhone. Ingeniería no pudo reproducirlo en MacBooks con trackpads. Esa discrepancia retrasó la solución porque todos discutían si era “real”. Lo era. Dependía del dispositivo de entrada y de la caja de layout.

La solución fue aburrida: quitar los márgenes negativos, mover la decoración de sobresaliente a padding y capas de fondo dentro de la tarjeta, y usar gap del contenedor. Las posiciones de snap se estabilizaron porque coincidieron con las cajas que el navegador usa para calcular puntos de snap.

Conclusión postmortem: cuando construyas scroll snap, tus cajas de layout son la API. Si lo visual no coincide con las cajas, el navegador hará snap a las cajas, no a tus intenciones.

Optimización que salió mal (“vamos a renderizar perezosamente todo”)

Otro equipo tenía un scroller de “tiles de características” en la home. El rendimiento estaba bien en portátiles, pero en Android los dispositivos se trababan muchísimo. Un ingeniero añadió content-visibility: auto en cada tarjeta y cargó imágenes de forma muy perezosa para reducir trabajo inicial.

Las métricas mejoraron para render inicial. Todos celebraron. Luego llegaron los reportes: los usuarios deslizan la galería y ven tiles en blanco por un momento, a veces lo bastante largo como para parecer roto. El snapping empeoró esto porque forzaba que la vista “aterrizara” en un tile que no se había renderizado aún.

El problema raíz no es que el render perezoso sea malo. Es que el scroll-snap convierte al “siguiente ítem” en un objetivo garantizado a corto plazo. Si te saltas el render demasiado agresivamente, creas un hueco visible justo en el momento de interacción, que los usuarios interpretan como lag.

El plan de recuperación fue mantener content-visibility, pero limitarlo: renderizar la tarjeta actual más una por delante/detrás asegurando que esos elementos sean “suficientemente visibles” (p. ej. con un umbral menos agresivo o no aplicándolo a los vecinos más cercanos). También declarar tamaños de imagen y usar fuentes responsive para reducir picos de decodificación.

Lección: optimiza para la interacción, no para capturas de Lighthouse. Una galería es una superficie de interacción. Trátala como un camino caliente, porque lo es.

Práctica aburrida pero correcta que salvó el día (“feature flag + rollback rápido”)

Un minorista reemplazó un carrusel JS con CSS scroll snap para reducir el tamaño del bundle. El cambio estaba tras un feature flag. Sin fanfarrias. Solo un despliegue controlado con interruptor de apagado.

Durante el aumento, atención al cliente reportó que algunos usuarios en Safari antiguo veían un comportamiento “rubber band”: la galería sobre-desplazaba y rebotaba, luego encajaba en el elemento equivocado. No era universal, y reproducirlo requería configuraciones de SO específicas.

En lugar de pasar un fin de semana discutiendo si Safari estaba “equivocado”, el SRE on-call hizo lo que debe hacer: reducir el radio de incendio. Giró la bandera a off para los agentes de usuario afectados mientras ingeniería investigaba. Sin drama. Sin parche de emergencia. Sin despliegue de medianoche.

Luego ingeniería construyó una mitigación pequeña basada en UA: para las versiones problemáticas de Safari, cambiaron de mandatory a proximity y quitaron el desplazamiento suave. También añadieron overscroll-behavior y simplificaron desbordes anidados. La tasa de errores bajó a ruido de fondo.

La práctica que salvó el día no fue CSS ingenioso. Fue disciplina operativa: despliegue gradual, informes medibles de errores y una ruta de rollback que no requería heroísmos.

Guía rápida de diagnóstico

Tienes una galería con scroll-snap. Los usuarios dicen que va a trompicones, salta a elementos equivocados o no encaja. Necesitas un camino rápido al cuello de botella sin convertir el debug en un estilo de vida.

Primero: confirma que el contenedor y los puntos de snap son reales

  1. Verifica que el contenedor desplazable sea el elemento que crees (overflow-x: auto en el nodo correcto).
  2. Verifica que los hijos participen directamente con scroll-snap-align (no aplicado a un wrapper interno al que en realidad no desplazas).
  3. Busca contenedores de overflow anidados dentro de las diapositivas que puedan robar el scroll o causar encadenamiento.

Segundo: aisla los cambios de diseño

  1. Desactiva la carga de imágenes (o reemplaza con placeholders de tamaño fijo) y observa si el problema desaparece.
  2. Comprueba si las fuentes se intercambian después del primer paint.
  3. Busca inyecciones de contenido dinámico (ads, personalización, módulos “recomendados”) que cambien el tamaño de las tarjetas.

Tercero: perfila el rendimiento del scroll como adulto

  1. Graba un perfil de rendimiento durante el desplazamiento y el asentamiento del snap.
  2. Busca tareas largas en el hilo principal durante el scroll.
  3. Busca pintura y compositing pesados (sombras grandes, filtros, elementos en posición fija).

Cuarto: prueba en distintos métodos de entrada y configuraciones

  1. Trackpad vs rueda de ratón vs táctil (dispositivo real si es posible).
  2. Movimiento reducido activado.
  3. RTL si tu producto lo soporta (o lo hará).

Regla de decisión: si el snap es incorrecto, arregla el layout y la geometría del snap. Si el snap es correcto pero la interacción es desagradable, arregla el rendimiento y el manejo de entrada (overscroll/touch-action), o relaja de mandatory a proximity.

Tareas prácticas: comandos, salidas y decisiones

Estos son los tipos de comprobaciones que espero en un canal de incidentes real: rápidas, deterministas y vinculadas a una decisión. Los comandos son ejecutables en una caja dev típica Linux. Cuando la comprobación es “frontend”, seguimos usando herramientas del sistema porque el debugging en producción es multidisciplinario.

Tarea 1: Confirma que el CSS de la galería realmente está desplegado

cr0x@server:~$ curl -sS -D- https://example.test/gallery | head
HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: public, max-age=60
etag: "a1b2c3"
...

Qué significa la salida: Estás recibiendo un 200 con HTML. La conectividad básica está bien.

Decisión: Si ves un bucle de redirección o un 500, deja de culpar al CSS y arregla la entrega primero.

Tarea 2: Verifica que el CSS contiene reglas de scroll-snap

cr0x@server:~$ curl -sS https://example.test/assets/app.css | grep -n "scroll-snap" | head
1842:.gallery{scroll-snap-type:x mandatory;scroll-padding-inline:16px}
1843:.card{scroll-snap-align:start}

Qué significa la salida: El bundle CSS incluye las propiedades. Si grep no devuelve nada, tu pipeline de build probablemente eliminó estilos o estás alcanzando el asset equivocado.

Decisión: Reglas faltantes significa problema de despliegue/build, no un bug de navegador. Arregla el bundling o selectores.

Tarea 3: Comprueba si la compresión funciona (CSS/HTML grandes pueden retrasar la interacción)

cr0x@server:~$ curl -sS -I https://example.test/assets/app.css | egrep -i "content-encoding|content-length|cache-control"
cache-control: public, max-age=31536000, immutable
content-encoding: br
content-length: 41231

Qué significa la salida: Brotli está activo, el tamaño es manejable y la caché es fuerte.

Decisión: Si no hay compresión y content-length es enorme, arregla eso antes de micro-optimizar el comportamiento del scroll.

Tarea 4: Mide el tamaño de las imágenes (imágenes sobredimensionadas son el culpable #1 del jank)

cr0x@server:~$ curl -sS -I https://example.test/media/slide-1.jpg | egrep -i "content-type|content-length|cache-control"
content-type: image/jpeg
content-length: 8421932
cache-control: public, max-age=31536000

Qué significa la salida: Son ~8MB para una imagen. En móvil, esto es un problema incluso con CSS perfecto.

Decisión: Añade imágenes responsive, formatos modernos y declara dimensiones. Reduce bytes antes de debatir la alineación del snap.

Tarea 5: Confirma que el servidor soporta peticiones range (ayuda con manejo de media)

cr0x@server:~$ curl -sS -I https://example.test/media/slide-1.jpg | egrep -i "accept-ranges"
accept-ranges: bytes

Qué significa la salida: Las peticiones range están habilitadas.

Decisión: Si falta, la entrega de media grande puede ser menos eficiente. Investiga la configuración de CDN/origen.

Tarea 6: Identifica tareas largas durante el scroll en una máquina de prueba (presión CPU a nivel sistema)

cr0x@server:~$ top -b -n 1 | head -n 12
top - 10:21:14 up 12 days,  4:02,  1 user,  load average: 2.11, 1.88, 1.74
Tasks: 238 total,   1 running, 237 sleeping,   0 stopped,   0 zombie
%Cpu(s): 18.2 us,  3.3 sy,  0.0 ni, 77.8 id,  0.0 wa,  0.0 hi,  0.7 si,  0.0 st
MiB Mem :  15948.5 total,   2142.9 free,   6120.3 used,   7685.3 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.   9182.2 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 4121 cr0x      20   0 3241560 482912 156324 S  48.0   3.0  10:12.33 chrome

Qué significa la salida: Chrome está usando mucha CPU durante tu prueba de interacción.

Decisión: Si la CPU se dispara al hacer scroll, espera jank. Pasa a perfilar en el navegador y reduce trabajo de pintura/layout.

Tarea 7: Comprueba si el sistema está intercambiando memoria (swap)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            15Gi       5.9Gi       2.1Gi       268Mi       7.5Gi       8.9Gi
Swap:          2.0Gi          0B       2.0Gi

Qué significa la salida: El swap no está en uso. Buena base para pruebas de perf fiables.

Decisión: Si el swap está muy usado, tu test de “jank de scroll snap” es inválido. Arregla el estado de la máquina primero.

Tarea 8: Detecta señales de layout shift en logs de campo (correlación CLS)

cr0x@server:~$ journalctl -u webapp -n 30 | grep -i "CLS" | tail
Dec 29 10:19:12 web01 webapp[2381]: rum metric CLS=0.21 route=/pricing device=mobile
Dec 29 10:19:48 web01 webapp[2381]: rum metric CLS=0.19 route=/pricing device=mobile

Qué significa la salida: El CLS en campo está elevado en la página que contiene la galería.

Decisión: Prioriza estabilidad geométrica (dimensiones de imágenes, carga de fuentes) antes de afinar el snap. El snap no puede arreglar geometría inestable.

Tarea 9: Verifica que los archivos de fuentes no sean enormes o lentos (cambios de fuente pueden alterar anchos de tarjeta)

cr0x@server:~$ curl -sS -I https://example.test/assets/fonts/Inter-var.woff2 | egrep -i "content-length|cache-control|content-type"
content-type: font/woff2
content-length: 986432
cache-control: public, max-age=31536000, immutable

Qué significa la salida: Fuente casi 1MB. No es automáticamente malo, pero es sospechoso para swaps tardíos.

Decisión: Considera subsetting, o asegúrate de que la estrategia font-display no cause shifts visibles en las tarjetas.

Tarea 10: Comprueba que el caché HTTP es correcto (evita re-descargar assets de la galería)

cr0x@server:~$ curl -sS -I https://example.test/assets/app.css | egrep -i "etag|last-modified|cache-control"
cache-control: public, max-age=31536000, immutable
etag: "d34db33f"

Qué significa la salida: Caché fuerte con assets inmutables.

Decisión: Si la caché es débil, los usuarios re-descargan assets y las interacciones empiezan tarde. Arregla el caching antes de discutir detalles micro de CSS.

Tarea 11: Confirma que la página no deshabilita por accidente el desplazamiento overflow

cr0x@server:~$ rg -n "overflow-x:\s*hidden|overflow:\s*hidden" -S ./src | head
src/styles/layout.css:44:body { overflow-x: hidden; }
src/components/Gallery.css:3:.gallery { overflow-x: auto; }

Qué significa la salida: El body tiene overflow-x: hidden. Eso puede estar bien, pero es una causa común de bugs “no se puede desplazar la galería” cuando se combina con otras restricciones de layout.

Decisión: Si la galería no se desplaza en algunos dispositivos, audita reglas globales de overflow y tamaño de contenedores.

Tarea 12: Busca redimensionado accidental del contenedor de scroll (unidades viewport, toolbars dinámicas)

cr0x@server:~$ rg -n "100vw|100vh|dvh|svh|lvh" ./src/styles | head
src/styles/gallery.css:12:.gallery { width: 100vw; }
src/styles/page.css:8:.page { min-height: 100vh; }

Qué significa la salida: width: 100vw puede incluir el ancho de la barra de desplazamiento en algunas plataformas, causando desbordes horizontales sutiles y deriva de snap.

Decisión: Prefiere width: 100% para contenedores y gestiona padding explícitamente. Si necesitas tamaño viewport, prueba comportamiento de barras y toolbars móviles dinámicas.

Tarea 13: Valida que no introdujiste costes de pintura enormes (box-shadows por todos lados)

cr0x@server:~$ rg -n "box-shadow:|filter:|backdrop-filter:" ./src/styles | head -n 12
src/styles/cards.css:18:.card { box-shadow: 0 24px 80px rgba(0,0,0,0.22); }
src/styles/hero.css:9:.hero { backdrop-filter: blur(18px); }

Qué significa la salida: Sombras grandes y backdrop filters son costosos de pintar, especialmente durante el scroll.

Decisión: Reduce blur/spread de sombras, elimina backdrop-filter en contextos de scroll o confina efectos a capas no desplazables.

Tarea 14: Confirma que el build no quitó propiedades con prefijos o fallback (quinas de Safari)

cr0x@server:~$ node -p "process.versions.node"
22.11.0

Qué significa la salida: Tienes un entorno Node moderno. Si tu pipeline CSS también es moderno, podría depender de features que tus navegadores objetivo no soportan totalmente sin fallbacks.

Decisión: Asegura que tu CSS se pruebe contra la matriz de navegadores soportados. Si Safari está en alcance, valida en Safari real, no solo suposiciones “tipo WebKit”.

Tarea 15: Audita el número de ítems en la galería (demasiadas diapositivas = presión de memoria)

cr0x@server:~$ python3 - <<'PY'
from bs4 import BeautifulSoup
html = open("gallery.html","r",encoding="utf-8").read()
s = BeautifulSoup(html,"html.parser")
print("cards:", len(s.select(".card")))
PY
cards: 4

Qué significa la salida: La muestra es pequeña. Las páginas reales suelen tener 30+ ítems.

Decisión: Si tienes muchas diapositivas con contenido pesado, considera paginación, menos ítems o estrategia de renderización—pero prueba para evitar parpadeos con el snap.

Errores comunes: síntomas → causa raíz → solución

1) “No encaja para nada”

  • Síntomas: El desplazamiento horizontal funciona, pero nunca se posa limpiamente en los ítems.
  • Causa raíz: scroll-snap-type no está en el contenedor real de scroll, o el contenedor no se desplaza (sin overflow).
  • Solución: Pon overflow-x: auto y scroll-snap-type: x ... en el mismo elemento. Asegura que su ancho esté limitado para que exista overflow.

2) “Encaja en medias posiciones raras”

  • Síntomas: Los ítems se alinean de forma inconsistente; a veces ves mitad de la siguiente tarjeta.
  • Causa raíz: El layout visual no coincide con la geometría de caja (márgenes negativos, transforms, pseudo-elementos que afectan los bordes percibidos).
  • Solución: Elimina márgenes negativos de los ítems snap; usa gap y padding. Mantén las cajas de los ítems alineadas con lo que los usuarios ven.

3) “El desplazamiento de la página se queda atascado en la galería”

  • Síntomas: En móvil, el desplazamiento vertical se vuelve difícil cuando el dedo pasa sobre la galería.
  • Causa raíz: El contenedor horizontal captura la intención táctil; encadenamiento de scroll anidado; touch-action agresivo.
  • Solución: Usa overscroll-behavior-x: contain y considera quitar o relajar touch-action. Haz la galería más corta para que sea menos probable interceptar el scroll vertical.

4) “El snap queda bajo el encabezado”

  • Síntomas: El inicio de una tarjeta queda oculto bajo UI sticky.
  • Causa raíz: Falta scroll-padding o scroll-margin para compensar superposiciones.
  • Solución: Define scroll-padding-inline o scroll-padding-top en el contenedor de scroll según el eje.

5) “En escritorio va fluido, en teléfonos es horrible”

  • Síntomas: Stutter en móvil, flashes en blanco, aparición tardía de imágenes al hacer snap.
  • Causa raíz: Imágenes pesadas y tiempo de decodificación; render perezoso demasiado agresivo; pintura pesada.
  • Solución: Usa imágenes responsive, declara dimensiones/aspect-ratio, simplifica sombras/filtros y evita ocultar renderizado de ítems cercanos fuera de pantalla.

6) “Salta cuando aparece la barra de desplazamiento”

  • Síntomas: En escritorio, la galería se desplaza ligeramente y luego la alineación de snap queda fuera.
  • Causa raíz: El gutter de la barra de desplazamiento cambia el layout, a menudo por ajustes del SO o barras hover.
  • Solución: Usa scrollbar-gutter: stable donde se soporte; de lo contrario asegura que el tamaño del contenedor no dependa de 100vw.

7) “Safari ignora mi bonito comportamiento”

  • Síntomas: Asentamiento de snap distinto al de Chromium/Firefox; goma de mascar extraña.
  • Causa raíz: Diferencias de motor más física de momentum; también overflow anidado y transforms.
  • Solución: Simplifica: menos regiones de scroll anidadas, evita transforms en padres del contenedor de scroll, considera cambiar a proximity, quita smooth scroll para builds afectados.

8) “Los usuarios de teclado no alcanzan el contenido”

  • Síntomas: Tab no entra en las diapositivas, o el anillo de foco desaparece fuera de pantalla.
  • Causa raíz: No hay elementos enfocables; estilos de foco eliminados; clipping de overflow sin comportamiento de scroll-to-focus.
  • Solución: Asegura contenido enfocables; añade estilo :focus-visible; evita outline: none sin reemplazo.

Listas de verificación / plan paso a paso

Paso a paso: construye una galería con snap que no te avergüence luego

  1. Elige el modelo de layout. Usa CSS Grid con columnas para las tarjetas. Evita floats complicados, márgenes negativos y transforms en el contenedor de scroll.
  2. Crea un contenedor de scroll real. overflow-x: auto, ancho limitado por el padre, sin reglas globales de overflow que lo combatan.
  3. Define el comportamiento de snap. Empieza con scroll-snap-type: x proximity a menos que tengas una razón fuerte para mandatory.
  4. Establece alineación de ítems. Por defecto usa scroll-snap-align: start.
  5. Añade espaciado predecible. Usa gap y padding del contenedor; ajusta scroll-padding-inline para que coincida.
  6. Hazlo usable por teclado. Asegura elementos enfocables; añade :focus-visible.
  7. Respeta movimiento reducido. Activa smooth solo cuando las preferencias lo permitan.
  8. Estabiliza medios. Declara dimensiones/aspect-ratio de imágenes; evita contenido que cargue tarde y cambie tamaño de tarjeta.
  9. Vigila overflow anidado. No pongas regiones desplazables dentro de diapositivas salvo que sea imprescindible.
  10. Prueba en distintos inputs. Ratón, trackpad, dispositivo táctil y al menos un Safari.
  11. Despliega tras flag si es de alto riesgo. Especialmente en páginas de marketing de alto tráfico donde “pequeñas regresiones UX” se vuelven dinero real.
  12. Mide e itera. Monitorea CLS, latencia de interacción y feedback de usuarios correlacionado por dispositivo/navegador.

Checklist pre-lanzamiento (rápido)

Geometría: el contenedor desplaza; los hijos tienen snap align; no hay márgenes negativos en ítems snap.
UX: barra de desplazamiento visible o existe alternativa; anillo de foco visible; anclas funcionan si se proveen.
Perf: imágenes dimensionadas y comprimidas; no sombras/filtros gigantes; no parpadeos en flicks rápidos.
Compatibilidad: probado en Safari y al menos un Android; movimiento reducido verificado.

Preguntas frecuentes

1) ¿Debo usar mandatory o proximity?

Por defecto usa proximity a menos que la galería sea explícitamente por pasos (fotos de producto, onboarding). mandatory aumenta quejas de “atrapamiento” si tus puntos de snap son densos o tus tarjetas estrechas.

2) ¿Por qué se comporta distinto en trackpad que en rueda de ratón?

Porque el flujo de entrada es distinto: los trackpads envían deltas continuos y de alta resolución; las ruedas envían ticks gruesos. El snapping ocurre después de que el scroll se asienta, y el tiempo de “asentamiento” difiere por dispositivo.

3) ¿Puedo construir un carrusel completo (puntos, anterior/siguiente, autoplay) sin JS?

Puedes crear navegación con anclas y estilizarla bonito. El autoplay sin JS es mala idea por accesibilidad y control del usuario. Para “puntos”, usa enlaces a IDs de diapositivas y mantenlo simple.

4) ¿Hace scroll-behavior: smooth que el scroll táctil sea suave?

No realmente. Principalmente afecta el scroll programático y ciertas acciones impulsadas por el UA. La inercia táctil es física de la plataforma. No confíes en smooth para arreglar jank.

5) Mis diapositivas tienen padding—¿por qué los puntos de snap se desvían unos píxeles?

Normalmente es redondeo y box sizing. Prefiere alinear a un borde estable (start), iguala padding del contenedor con scroll-padding y evita anchos fraccionarios cuando sea posible (como 33.333% más gaps grandes).

6) ¿Cómo evito que la página se desplace cuando el usuario interactúa con la galería?

Usa overscroll-behavior-x: contain en la galería. Considera touch-action: pan-x con cuidado; puede mejorar la intención pero también dificultar el scroll vertical sobre la galería.

7) ¿Por qué algunos ítems “se saltan” si lanzo rápido?

El momentum puede llevar el scroll más allá de varios puntos de snap. Si necesitas que cada ítem sea parada dura, prueba scroll-snap-stop: always en los ítems—pero pruébalo; puede sentirse restrictivo.

8) ¿Debo ocultar la barra de desplazamiento para un aspecto más limpio?

Sólo si la reemplazas por algo igual de claro y accesible. Si no, estás quitando una affordance y la reemplazas por sensaciones. Si debes ocultarla, asegúrate de que la usabilidad por teclado y táctil sea excelente.

9) ¿Puede el scroll snap causar layout shift (CLS)?

El scroll snap por sí solo no crea CLS. Pero hace que los cambios de diseño sean más notables porque el usuario espera un aterrizaje estable. CLS viene típicamente de imágenes sin espacio reservado, swaps tardíos de fuentes o inyecciones dinámicas de contenido.

10) ¿Cuál es la forma más simple de hacerlo responsive?

Usa grid-auto-columns en porcentajes y ajusta en puntos de quiebre. Ejemplo: 85% en pantallas pequeñas (una tarjeta mayormente visible), 45% en pantallas anchas (dos tarjetas más o menos visibles). Luego afina gap y scroll-padding.

Conclusión: qué hacer a continuación

Si quieres galerías horizontales suaves sin JavaScript, scroll snap es la primitiva correcta. Pero no es magia. Trátalo como cualquier otra característica de producción: mantén la geometría honesta, estabiliza el layout y mide los cuellos de botella en lugar de adivinar.

Próximos pasos que puedes hacer esta semana:

  1. Refactoriza tu galería a un contenedor de desplazamiento único y obvio con cajas de hijo predecibles (grid + gap + padding).
  2. Cambia a proximity a menos que producto necesite estrictamente snap y añade scroll-padding-inline para coincidir con el espaciado de diseño.
  3. Arregla media: declara dimensiones/aspect-ratios y reduce bytes de imágenes hasta que los móviles dejen de sudar.
  4. Ejecuta la guía rápida de diagnóstico en Safari y en un teléfono real antes de discutir “quinas del navegador”.
  5. Si la página es de alto impacto, despliega tras un flag y estate listo para revertir. No es pesimismo; es cómo proteges tus fines de semana.

Construido bajo la suposición de que los sistemas en producción son reales, los navegadores son opinativos y los usuarios siempre encontrarán el único dispositivo que no probaste.

← Anterior
Problemas de entrega en Gmail/Outlook: las comprobaciones que importan
Siguiente →
Por qué los controladores de impresora se hicieron enormes: la hinchazón que nadie pidió

Deja un comentario