Communitygithub.com

shubham001312/scroll-motion-graphics

Úsalo al construir o arreglar una sección de gráficos en movimiento basada en desplazamiento (scroll) en una página web, como un film en canvas controlado por la posición del scroll, una etapa adhesiva de tipo 'scrollytelling', capítulos vinculados al scroll con subtítulos, o un tambor/carrusel 3D controlado por scroll, arrastre y teclas de flecha. También se usa cuando una animación de scroll existente se traba, secuestra la rueda, dibuja debajo de su texto, solo avanza, quema CPU en reposo o ignora prefers-reduced-motion. No es para simples revelados por fade-in al hacer scroll, videos en reproducción automática o escenas WebGL.

¿Qué es scroll-motion-graphics?

Esta habilidad describe un patrón completo para crear narrativas visuales basadas en el desplazamiento (scroll) del usuario, donde la posición del scroll es el único reloj y todo elemento visual es una función pura de un progreso normalizado `p` entre 0 y 1. El enfoque evita secuestrar la rueda del ratón, respeta la preferencia de movimiento reducido, es eficiente al detener bucles de animación cuando la sección no está visible, y garantiza que los subtítulos permanezcan en el DOM para una correcta accesibilidad y legibilidad. Incluye modos de anclaje (`pin`) para secciones que ocupan toda la pantalla mientras se reproduce la historia, y modo vista (`view`) para efectos que acompañan el contenido. La técnica es la base de implementaciones como el tambor narrativo en la página de inicio de ImuTechz y viene acompañada de un conjunto de reglas estrictas, órdenes de construcción, listas de verificación, modos de fallo y archivos de referencia que facilitan su adopción en proyectos con React o JavaScript vanilla. Soporta canvas film para gráficos complejos y transformaciones DOM para elementos más simples, y proporciona un demo independiente para empezar rápidamente.

Compatible con✓Claude Code~Codex CLI✓Cursor✓OpenCode
npx skills add https://github.com/shubham001312/imutechz-skills/tree/HEAD/skills/scroll-motion-graphics

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Gráficos en movimiento con scroll

Una línea de tiempo en canvas impulsada por la posición del scroll: un escenario anclado, carretes que son funciones puras del progreso, y subtítulos que el dibujo nunca tiene permitido tocar. Este método impulsa el tambor narrativo en la página de inicio de ImuTechz, el tambor de I+D y la película del recorrido del fundador. También alimenta la demostración de cinco carretes en assets/demo.html.

Toda la técnica cabe en una frase. La posición del scroll es el único reloj: se asigna a p ∈ [0, 1], y cada píxel, subtítulo y rotación es una función de p.

Cómo se ve un buen resultado

  • Se desplaza como un video. Desplázate hacia adelante y la película se reproduce. Desplázate hacia atrás y rebobina, fotograma a fotograma. Detenerse a mitad de camino deja un fotograma que tiene sentido.
  • La página nunca es secuestrada. La rueda, el trackpad, el tacto y las teclas mueven la página exactamente como lo hacen en cualquier otro lugar. La sección simplemente es alta.
  • Las palabras siempre son legibles. Los subtítulos son texto DOM real en una tarjeta. El dibujo se sitúa en el espacio que dejan los subtítulos, y el canvas se recorta a su alrededor como una red de seguridad.
  • Es un control, no solo un espectáculo. Las caras de un tambor narrativo son enlaces. Su riel, botones de flecha, teclas de flecha y arrastre con el ratón buscan desplazando la página, para que el scroll y el tambor nunca estén en desacuerdo.
  • Una página inactiva no cuesta nada. Ningún requestAnimationFrame se ejecuta una vez que p se estabiliza o después de que la sección sale del viewport.
  • Se respeta el movimiento reducido. Sin suavizado, ni giro intermedio del tambor, ni animación de entrada de subtítulos. Todo el contenido sigue ahí.
  • Funciona desde 360 hasta 1920 px de ancho, en modo claro y oscuro, con DPR 1 y 2.

Reglas estrictas

  1. p proviene de la posición del scroll, nunca de un reloj. No uses performance.now(), Date, animaciones CSS o setInterval dentro de un carrete. Se permite el suavizado (un lerp hacia el p objetivo) porque converge al mismo fotograma.
  2. Un carrete es una función pura draw(ctx, t, env). No guarda estado entre fotogramas y nunca llama a Math.random(); usa un hash(i) con semilla en su lugar. El mismo (t, env) debe pintar los mismos píxeles.
  3. Ancla con CSS, no con JS. Usa una pista alta (position: relative; height: N×vh) que contenga un escenario position: sticky; top: 0; height: 100svh. Nunca establezcas position: fixed en el escenario, nunca bloquees el body, y nunca pongas overflow: hidden|auto|clip en un ancestro de la pista.
  4. Nunca captures la entrada. No uses preventDefault en la rueda o el tacto, ni overflow: hidden en html o body, ni scroll snapping en la pista. Solo un tambor enfocado puede manejar las teclas de flecha, y lo hace desplazando la página.
  5. Busca desplazándote. Cada botón, elemento del riel, tecla y ancla cambia window.scrollY. Nunca establezcas p o una rotación directamente. Usa behavior: "instant" para saltos programáticos que no deben animar, porque "auto"" obedece al scroll-behavior: smooth del CSS de la página.
  6. El texto vive en el DOM. Los subtítulos, títulos y enlaces son HTML. El texto en canvas se limita a etiquetas decorativas como años, marcas de eje y nombres de paso.
  7. Mide los subtítulos cada fotograma y mantén el dibujo alejado de ellos. Cada subtítulo y elemento HUD es una zona de exclusión (data-keep). Los carretes se colocan dentro de env.free, y el motor recorta las zonas de exclusión con un recorte par-impar.
  8. Nunca muestres dos subtítulos a la vez. Las ventanas de subtítulos se asientan dentro de su carrete con un espacio en cada límite, y los carretes se funden entre sí dentro de ese espacio.
  9. El bucle se detiene cuando está inactivo. Inicia rAF al hacer scroll o redimensionar solo mientras la sección está cerca del viewport (IntersectionObserver), y detenlo cuando p sea igual al objetivo.
  10. El canvas se dimensiona al tamaño del escenario con min(devicePixelRatio, 2). Llama a setTransform(dpr, …) una vez por fotograma y límpialo cada fotograma.
  11. Los colores provienen de propiedades personalizadas CSS. No codifiques una paleta de modo claro en un carrete. Vuelve a leer la paleta al cambiar prefers-color-scheme.
  12. Respeta prefers-reduced-motion: reduce. No uses suavizado, haz que el tambor salte de cara a cara, y desactiva las animaciones de entrada de subtítulos. Los fotogramas aún siguen el scroll, porque el usuario los controla.

Orden de construcción

Hazlo en este orden. Cada paso se puede probar antes del siguiente.

  1. Contenido primero. Escribe los capítulos: un anticipo, un título, una o dos frases y un enlace opcional para cada uno. Elige el número de carretes (de 3 a 7) y asigna a cada uno aproximadamente de 80 a 100 svh de desplazamiento.
  2. Pista y escenario. Agrega la sección alta con un escenario adhesivo de 100svh dentro, y verifica que se ancle sin JS alguno (consulta layout-and-captions).
  3. El motor de desplazamiento. Conecta ScrollMotion.scrub o film (consulta engine) y registra p. Verifica que lea 0 al inicio del anclaje y 1 al final del anclaje, y que se mueva en ambas direcciones.
  4. Subtítulos y HUD en el DOM. Colócalos según el punto de interrupción, márcalos con data-keep, y deja que el motor controle su opacidad desde p.
  5. Carretes, uno a la vez. Escribe draw(ctx, t, env) dentro de env.free (consulta drawing), y desplázalo hacia adelante y atrás antes de escribir el siguiente.
  6. Gramática de movimiento. Agrega envolventes de entrada, sostenimiento y salida, escalonamientos, pausas y transiciones (consulta motion-grammar).
  7. Interactividad, si es un tambor. Agrega enlaces de caras, el riel, botones anterior y siguiente, teclas y arrastre, todo buscando mediante desplazamiento.
  8. Pase de rendimiento y accesibilidad. Cubre el bucle inactivo, DPR, movimiento reducido, orden de enfoque, texto de lector de pantalla y modo oscuro (consulta performance-and-a11y).
  9. Verifica a 360, 390, 414, 768, 1024, 1280, 1440 y 1920 con el script sin cabeza (consulta verification). No lo des por terminado hasta que todas las comprobaciones pasen.

Listas de verificación de decisiones

¿Película en canvas o transformaciones DOM/CSS?

  • Muchas piezas pequeñas móviles, líneas que dibujan, partículas o gráficos → película en canvas (ScrollMotion.film).
  • Un puñado de tarjetas, imágenes o paneles de texto → transformaciones DOM impulsadas por scrub (el tambor es una).
  • Mallas 3D reales o shaders → esta habilidad no es suficiente. Usa WebGL, pero mantén las reglas 1 a 5 y 9 a 12.

¿Modo anclaje o vista?

  • La sección debe mantener la pantalla mientras se reproduce su historia → mode: "pin" (pista alta y escenario adhesivo).
  • El efecto viaja junto con el contenido, como en marquesinas o parallax → mode: "view", sin anclaje.

¿Qué altura tiene la pista?

  • Usa n × 80–100svh + 100svh para una película y n × 70vh + 100vh para un tambor. Si un lector de prueba dice que va "demasiado rápido", agrega altura. Nunca la frenes capturando la rueda.

¿Dónde van los subtítulos?

  • Escritorio (≥ 760 px): junto al dibujo (tarjeta izquierda o derecha) o como una tarjeta ancha en la parte inferior. Teléfonos: siempre una hoja inferior, con el dibujo encima.
  • Con un encabezado de sitio fijo, establece --sm-nav-h para que el HUD y el tambor lo eviten.

¿Necesita el tambor?

  • Usa el tambor cuando los capítulos sean cosas a las que el usuario quiera ir (páginas, productos, secciones). De lo contrario, usa solo la película.

Modos de fallo y correcciones

SíntomaCausaCorrección
El escenario se desplaza en lugar de anclarseUn ancestro tiene overflow: hidden/auto/clip, o el escenario no tiene topElimina el overflow (usa overflow-x: clip en el body si es necesario) y establece top: 0
La animación solo avanza, o salta al subir el scrollEstado acumulado entre fotogramas, o un reloj o Math.random en un carreteHaz que el carrete sea una función pura de t; usa hash(i) para la aleatoriedad
El dibujo se ejecuta debajo del subtítuloCarrete colocado en todo el escenarioColócalo en env.free; mantén data-keep en el subtítulo; deja que el motor recorte
La caja del subtítulo se mide en el lugar equivocado durante una transiciónSe usó getBoundingClientRect (incluye la transformación)Mide cajas de diseño (offsetLeft/Top/Width/Height, consulta boxIn)
Dos subtítulos se superponen en un límiteLas ventanas de subtítulos se tocanDesvanece los subtítulos hacia afuera en 1 − cross y hacia adentro en cross; los carretes se funden en el espacio
Los ventiladores giran en una página inactivaEl bucle rAF nunca se detieneDeténlo cuando cur === target; condicionalo con IntersectionObserver
Canvas borroso en teléfonos / lento en 4KCanvas al tamaño CSS, o DPR sin límiteUsa width = W × min(dpr, 2) y setTransform(dpr, 0, 0, dpr, 0, 0)
El tambor y la página no coinciden después de hacer clic en el rielEl control estableció la rotación directamenteCada control llama a scrollToProgress; la rotación solo proviene de p
El arrastre tiembla, luego vuelve a su lugarLa página tiene scroll-behavior: smooth, por lo que scrollTo(x, y) animaUsa scrollTo({ top, behavior: "instant" }) mientras arrastras
Al hacer clic en una cara después de un arrastre se abre su enlaceEl clic que termina un arrastre se disparaSuprime un clic en fase de captura después de un arrastre que se movió más de 4 px
Salto de 100vh en móvil cuando se oculta la barra de URLvh en el escenarioUsa 100svh para el escenario; calcula p con la altura del propio escenario
El modo oscuro muestra un dibujo de modo claroColores codificadosLee los tokens --sm-* mediante readPalette; vuelve a leerlos al cambiar el esquema
Las caras traseras del tambor se venFalta backface-visibility: hiddenEstablécelo, con el prefijo -webkit-, en cada cara
Las miniaturas de las caras del tambor se dibujan al tamaño incorrectoSe usó el rectángulo proyectado de una cara transformada en 3DDimensiónalos por clientWidth/clientHeight

Archivos

Comienza desde la demo: ábrela, ve el código fuente y reemplaza el contenido. El cableado al final del archivo tiene unas 60 líneas.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Skills relacionados