Ir al contenido
English

Navegar

Escribe para buscar. Pulsa Escape para cerrar.

    Fundamentos

    Movimiento por scroll

    Cuatro cosas que se mueven porque la página se mueve: una capa que va por detrás de su marco, una barra que mide lo leído, una fila que se desliza y una baraja de tarjetas que se apila al pasar. Donde el motor tiene animaciones dirigidas por scroll de CSS, la hoja de estilos lo mueve todo sola, sin un solo escuchador. Donde no las tiene, un componente pequeño escribe dos propiedades personalizadas y la misma hoja sigue funcionando. Con prefers-reduced-motion: reduce, el parallax, la marquesina y la baraja se quedan quietos.

    CSS: css/motion.css (en ivolt.css). JS: @intervolutions/ivolt/scroll-motion o auto.js, solo como reserva. Añadido en v0.7.

    Qué se ejecuta dónde

    animation-timeline está implementado en Chromium y en WebKit; Firefox no lo trae hoy, y allí toma el relevo ScrollMotion: escucha scroll y resize de forma pasiva, agrupa en requestAnimationFrame y escribe --iv-view en cada .iv-parallax de su raíz y --iv-scroll en la raíz misma. El efecto es el mismo; el coste es un poco de JavaScript en un motor. Donde existen las líneas de tiempo nativas y force está apagado, el componente no monta nada e informa native: true.

    Parallax

    iv-parallax-frame sostiene la proporción y recorta; iv-parallax es la capa que viaja, e iv-parallax--media hace que una imagen mida el 130 % de alto para que el recorrido no descubra ningún borde. La fuerza es un solo número, --iv-parallax: 0 es fijo a la página y 1 es teatral. Entre 0,1 y 0,3 es donde se lee como profundidad y no como truco.

    Marcos con parallaxfixtures/motion/parallax.htmlAbrir sola — Marcos con parallax
    <section id="motion-parallax" data-iv-component="scroll-motion" class="iv-stack iv-u-gap-8">
      <p class="iv-u-m-0">Sample copy for the fixture. Scroll the page: every frame keeps its place in the layout while the layer inside it lags behind. Where the engine ships CSS scroll-driven animations the stylesheet drives itself from <code>animation-timeline: view()</code>; where it does not, <code>ScrollMotion</code> writes <code>--iv-view</code> and the same travel happens from JavaScript. The artwork is an inline SVG of gradients, drawn for this fixture: no photograph, no network request.</p>
    
      <figure class="iv-stack iv-u-gap-3 iv-u-m-0">
        <div class="iv-parallax-frame" style="--iv-parallax-ratio: 21 / 9">
          <img class="iv-parallax iv-parallax--media" style="--iv-parallax: 0.2" width="1200" height="800" alt="Abstract ridges in green fading into a graded sky with a bright disc" src="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%201200%20800%27%20preserveAspectRatio%3D%27xMidYMid%20slice%27%20role%3D%27img%27%3E%20%3Cdefs%3E%20%3ClinearGradient%20id%3D%27sky%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%270%27%20y2%3D%271%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%23071a14%27%2F%3E%3Cstop%20offset%3D%27.55%27%20stop-color%3D%27%230c3b2c%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%2316705031%27%2F%3E%20%3C%2FlinearGradient%3E%20%3CradialGradient%20id%3D%27sun%27%20cx%3D%27.72%27%20cy%3D%27.34%27%20r%3D%27.28%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%239bf7c8%27%2F%3E%3Cstop%20offset%3D%27.45%27%20stop-color%3D%27%2329f59a%27%20stop-opacity%3D%27.55%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%2329f59a%27%20stop-opacity%3D%270%27%2F%3E%20%3C%2FradialGradient%3E%20%3ClinearGradient%20id%3D%27r1%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%271%27%20y2%3D%271%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%230f5f47%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%230a3d2e%27%2F%3E%20%3C%2FlinearGradient%3E%20%3ClinearGradient%20id%3D%27r2%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%271%27%20y2%3D%270%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%230b2c22%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%23123f31%27%2F%3E%20%3C%2FlinearGradient%3E%20%3C%2Fdefs%3E%20%3Crect%20width%3D%271200%27%20height%3D%27800%27%20fill%3D%27url%28%23sky%29%27%2F%3E%20%3Ccircle%20cx%3D%27864%27%20cy%3D%27272%27%20r%3D%27300%27%20fill%3D%27url%28%23sun%29%27%2F%3E%20%3Ccircle%20cx%3D%27864%27%20cy%3D%27272%27%20r%3D%2758%27%20fill%3D%27%23d6fde9%27%20opacity%3D%27.92%27%2F%3E%20%3Cpath%20d%3D%27M0%20470%20L180%20386%20L322%20452%20L470%20350%20L640%20458%20L812%20372%20L980%20462%20L1200%20384%20L1200%20800%20L0%20800%20Z%27%20fill%3D%27url%28%23r1%29%27%20opacity%3D%27.92%27%2F%3E%20%3Cpath%20d%3D%27M0%20566%20L146%20508%20L300%20578%20L452%20500%20L612%20588%20L790%20512%20L962%20592%20L1200%20520%20L1200%20800%20L0%20800%20Z%27%20fill%3D%27url%28%23r2%29%27%2F%3E%20%3Cpath%20d%3D%27M0%20690%20L220%20648%20L430%20704%20L660%20646%20L900%20710%20L1200%20654%20L1200%20800%20L0%20800%20Z%27%20fill%3D%27%23061410%27%2F%3E%20%3C%2Fsvg%3E">
        </div>
        <figcaption class="iv-u-text-sm iv-u-text-muted iv-u-m-0">Default strength, <code>--iv-parallax: 0.2</code>. The layer is 130&nbsp;% of the frame, so the travel never uncovers an edge.</figcaption>
      </figure>
    
      <div class="iv-stack iv-u-gap-4">
        <h3 class="iv-u-m-0">Two strengths, side by side</h3>
        <p class="iv-u-m-0">The left frame lags gently, the right one twice as much. Both use the same markup; only the fraction changes.</p>
      </div>
    
      <div class="iv-grid iv-u-grid-cols-1 iv-u-md-grid-cols-2 iv-u-gap-5">
        <figure class="iv-stack iv-u-gap-3 iv-u-m-0">
          <div class="iv-parallax-frame" style="--iv-parallax-ratio: 4 / 5">
            <img class="iv-parallax iv-parallax--media" style="--iv-parallax: 0.12" width="1200" height="800" alt="Abstract blue strata crossed by two thin luminous curves" src="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%201200%20800%27%20preserveAspectRatio%3D%27xMidYMid%20slice%27%20role%3D%27img%27%3E%20%3Cdefs%3E%20%3ClinearGradient%20id%3D%27b%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%27.35%27%20y2%3D%271%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%230a1630%27%2F%3E%3Cstop%20offset%3D%27.5%27%20stop-color%3D%27%23123a6e%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%23071a14%27%2F%3E%20%3C%2FlinearGradient%3E%20%3ClinearGradient%20id%3D%27c%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%271%27%20y2%3D%270%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%236fb3ff%27%20stop-opacity%3D%270%27%2F%3E%3Cstop%20offset%3D%27.5%27%20stop-color%3D%27%236fb3ff%27%20stop-opacity%3D%27.85%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%236fb3ff%27%20stop-opacity%3D%270%27%2F%3E%20%3C%2FlinearGradient%3E%20%3ClinearGradient%20id%3D%27d%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%271%27%20y2%3D%270%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%2329f59a%27%20stop-opacity%3D%270%27%2F%3E%3Cstop%20offset%3D%27.42%27%20stop-color%3D%27%2329f59a%27%20stop-opacity%3D%27.9%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%2329f59a%27%20stop-opacity%3D%270%27%2F%3E%20%3C%2FlinearGradient%3E%20%3C%2Fdefs%3E%20%3Crect%20width%3D%271200%27%20height%3D%27800%27%20fill%3D%27url%28%23b%29%27%2F%3E%20%3Cpath%20d%3D%27M0%20300%20C%20300%20220%20480%20380%20760%20300%20S%201050%20210%201200%20268%27%20stroke%3D%27url%28%23c%29%27%20stroke-width%3D%273%27%20fill%3D%27none%27%2F%3E%20%3Cpath%20d%3D%27M0%20372%20C%20280%20300%20520%20452%20800%20376%20S%201060%20292%201200%20344%27%20stroke%3D%27url%28%23d%29%27%20stroke-width%3D%272%27%20fill%3D%27none%27%20opacity%3D%27.8%27%2F%3E%20%3Cpath%20d%3D%27M0%20520%20C%20260%20448%20540%20600%20820%20520%20S%201080%20440%201200%20486%20L1200%20800%20L0%20800%20Z%27%20fill%3D%27%230a2a20%27%20opacity%3D%27.9%27%2F%3E%20%3Cpath%20d%3D%27M0%20640%20C%20240%20596%20560%20704%20840%20640%20S%201090%20588%201200%20620%20L1200%20800%20L0%20800%20Z%27%20fill%3D%27%2305100d%27%2F%3E%20%3Ccircle%20cx%3D%27250%27%20cy%3D%27188%27%20r%3D%2796%27%20fill%3D%27%236fb3ff%27%20opacity%3D%27.16%27%2F%3E%20%3Ccircle%20cx%3D%27250%27%20cy%3D%27188%27%20r%3D%2730%27%20fill%3D%27%23cfe6ff%27%20opacity%3D%27.75%27%2F%3E%20%3C%2Fsvg%3E">
          </div>
          <figcaption class="iv-u-text-sm iv-u-text-muted iv-u-m-0"><code>--iv-parallax: 0.12</code></figcaption>
        </figure>
        <figure class="iv-stack iv-u-gap-3 iv-u-m-0">
          <div class="iv-parallax-frame" style="--iv-parallax-ratio: 4 / 5">
            <img class="iv-parallax iv-parallax--media" style="--iv-parallax: 0.28" width="1200" height="800" alt="Abstract basin of green light under a dark sky" src="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%201200%20800%27%20preserveAspectRatio%3D%27xMidYMid%20slice%27%20role%3D%27img%27%3E%20%3Cdefs%3E%20%3CradialGradient%20id%3D%27g1%27%20cx%3D%27.2%27%20cy%3D%27.1%27%20r%3D%27.9%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%231d5fb8%27%2F%3E%3Cstop%20offset%3D%27.55%27%20stop-color%3D%27%230b2f24%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%23050d0b%27%2F%3E%20%3C%2FradialGradient%3E%20%3ClinearGradient%20id%3D%27g2%27%20x1%3D%270%27%20y1%3D%271%27%20x2%3D%271%27%20y2%3D%270%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%230a6a43%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%2329f59a%27%20stop-opacity%3D%27.55%27%2F%3E%20%3C%2FlinearGradient%3E%20%3C%2Fdefs%3E%20%3Crect%20width%3D%271200%27%20height%3D%27800%27%20fill%3D%27url%28%23g1%29%27%2F%3E%20%3Cellipse%20cx%3D%27620%27%20cy%3D%27700%27%20rx%3D%27760%27%20ry%3D%27240%27%20fill%3D%27url%28%23g2%29%27%20opacity%3D%27.55%27%2F%3E%20%3Cellipse%20cx%3D%27620%27%20cy%3D%27760%27%20rx%3D%27560%27%20ry%3D%27180%27%20fill%3D%27%23061512%27%20opacity%3D%27.85%27%2F%3E%20%3Cpath%20d%3D%27M0%20430%20L210%20330%20L360%20430%20L520%20300%20L700%20442%20L880%20330%20L1060%20448%20L1200%20360%20L1200%20620%20L0%20620%20Z%27%20fill%3D%27%2308251c%27%20opacity%3D%27.85%27%2F%3E%20%3Cpath%20d%3D%27M120%20250%20q%20180%20-90%20360%200%20t%20360%200%27%20stroke%3D%27%239bf7c8%27%20stroke-width%3D%272%27%20fill%3D%27none%27%20opacity%3D%27.35%27%2F%3E%20%3C%2Fsvg%3E">
          </div>
          <figcaption class="iv-u-text-sm iv-u-text-muted iv-u-m-0"><code>--iv-parallax: 0.28</code></figcaption>
        </figure>
      </div>
    
      <div class="iv-stack iv-u-gap-4 iv-u-py-12">
        <h3 class="iv-u-m-0">Filler, so there is something to scroll</h3>
        <p class="iv-u-m-0 iv-u-max-w-measure">This block exists only to give the page height. Sample copy for the fixture: a parallax that never leaves the first screen cannot be judged, and the test below measures the transform of a frame before and after scrolling.</p>
        <p class="iv-u-m-0 iv-u-max-w-measure">Under <code>prefers-reduced-motion: reduce</code> every layer sits still at its authored position and the component writes nothing at all.</p>
      </div>
    </section>
    
    <section id="motion-parallax-forced" data-iv-component="scroll-motion" data-iv-force="true" class="iv-stack iv-u-gap-5 iv-u-pt-12">
      <h3 class="iv-u-m-0">The same frame, forced onto the JavaScript path</h3>
      <p class="iv-u-m-0 iv-u-max-w-measure">This container carries <code>data-iv-force="true"</code>, so <code>ScrollMotion</code> writes <code>--iv-view</code> even on an engine that has scroll-driven animations. It is how the fallback is checked without a second browser.</p>
      <figure class="iv-stack iv-u-gap-3 iv-u-m-0">
        <div class="iv-parallax-frame" style="--iv-parallax-ratio: 21 / 9">
          <img class="iv-parallax iv-parallax--media" style="--iv-parallax: 0.2" width="1200" height="800" alt="Abstract ridges in green fading into a graded sky with a bright disc" src="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%201200%20800%27%20preserveAspectRatio%3D%27xMidYMid%20slice%27%20role%3D%27img%27%3E%20%3Cdefs%3E%20%3ClinearGradient%20id%3D%27sky%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%270%27%20y2%3D%271%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%23071a14%27%2F%3E%3Cstop%20offset%3D%27.55%27%20stop-color%3D%27%230c3b2c%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%2316705031%27%2F%3E%20%3C%2FlinearGradient%3E%20%3CradialGradient%20id%3D%27sun%27%20cx%3D%27.72%27%20cy%3D%27.34%27%20r%3D%27.28%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%239bf7c8%27%2F%3E%3Cstop%20offset%3D%27.45%27%20stop-color%3D%27%2329f59a%27%20stop-opacity%3D%27.55%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%2329f59a%27%20stop-opacity%3D%270%27%2F%3E%20%3C%2FradialGradient%3E%20%3ClinearGradient%20id%3D%27r1%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%271%27%20y2%3D%271%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%230f5f47%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%230a3d2e%27%2F%3E%20%3C%2FlinearGradient%3E%20%3ClinearGradient%20id%3D%27r2%27%20x1%3D%270%27%20y1%3D%270%27%20x2%3D%271%27%20y2%3D%270%27%3E%20%3Cstop%20offset%3D%270%27%20stop-color%3D%27%230b2c22%27%2F%3E%3Cstop%20offset%3D%271%27%20stop-color%3D%27%23123f31%27%2F%3E%20%3C%2FlinearGradient%3E%20%3C%2Fdefs%3E%20%3Crect%20width%3D%271200%27%20height%3D%27800%27%20fill%3D%27url%28%23sky%29%27%2F%3E%20%3Ccircle%20cx%3D%27864%27%20cy%3D%27272%27%20r%3D%27300%27%20fill%3D%27url%28%23sun%29%27%2F%3E%20%3Ccircle%20cx%3D%27864%27%20cy%3D%27272%27%20r%3D%2758%27%20fill%3D%27%23d6fde9%27%20opacity%3D%27.92%27%2F%3E%20%3Cpath%20d%3D%27M0%20470%20L180%20386%20L322%20452%20L470%20350%20L640%20458%20L812%20372%20L980%20462%20L1200%20384%20L1200%20800%20L0%20800%20Z%27%20fill%3D%27url%28%23r1%29%27%20opacity%3D%27.92%27%2F%3E%20%3Cpath%20d%3D%27M0%20566%20L146%20508%20L300%20578%20L452%20500%20L612%20588%20L790%20512%20L962%20592%20L1200%20520%20L1200%20800%20L0%20800%20Z%27%20fill%3D%27url%28%23r2%29%27%2F%3E%20%3Cpath%20d%3D%27M0%20690%20L220%20648%20L430%20704%20L660%20646%20L900%20710%20L1200%20654%20L1200%20800%20L0%20800%20Z%27%20fill%3D%27%23061410%27%2F%3E%20%3C%2Fsvg%3E">
        </div>
        <figcaption class="iv-u-text-sm iv-u-text-muted iv-u-m-0">Forced fallback: watch <code>--iv-view</code> on the image go from 0 to 1 as it crosses the viewport.</figcaption>
      </figure>
      <div class="iv-u-py-12">
        <p class="iv-u-m-0 iv-u-max-w-measure">More filler, for the same reason as above.</p>
      </div>
    </section>
    .iv-root {
      --iv-parallax-ratio: 21 / 9;
      --iv-parallax: 0.2;
    }
    // data-iv-component="scroll-motion"
    {
      force: true,
    }

    El marco recorta con overflow: clip, no con hidden. Importa: hidden convertiría el marco en contenedor de scroll, view() mediría la capa contra ese contenedor en vez de contra la ventana y la línea de tiempo se congelaría. La segunda sección de la fixture lleva data-iv-force="true", que pone el componente en el camino de JavaScript incluso en un motor que tiene el nativo: así se comprueba la reserva sin un segundo navegador.

    Progreso de lectura

    iv-scroll-progress es una barra fijada arriba de la ventana, pintada una vez a todo lo ancho y escalada horizontalmente, de modo que al leer solo cambia una propiedad compuesta. Lleva aria-hidden="true": la posición de lectura ya llega a la tecnología asistiva por la barra de desplazamiento y por la estructura del documento, y un segundo anuncio sería ruido.

    Barra de progreso de lecturafixtures/motion/progress.htmlAbrir sola — Barra de progreso de lectura
    <section id="motion-progress" data-iv-component="scroll-motion" class="iv-stack iv-u-gap-6">
      <div class="iv-scroll-progress" aria-hidden="true"></div>
    
      <p class="iv-u-m-0 iv-u-max-w-measure">Sample copy for the fixture. The bar pinned to the top of the window is <code>.iv-scroll-progress</code>: it is painted full width once and scaled horizontally, so a single composited property changes as the page moves. On an engine with <code>animation-timeline: scroll(root)</code> the stylesheet drives it alone; elsewhere <code>ScrollMotion</code> writes <code>--iv-scroll</code> on this section and the bar inherits it.</p>
    
      <p class="iv-u-m-0 iv-u-max-w-measure">The bar is decoration and says so: <code>aria-hidden="true"</code>. The reading position is already available to assistive technology through the scrollbar and the document structure, and a second announcement would only be noise.</p>
    
      <div class="iv-card">
        <div class="iv-card__body iv-stack iv-u-gap-3">
          <h3 class="iv-card__title iv-u-m-0">What to watch</h3>
          <p class="iv-u-m-0">Scroll to the end of this fixture: the bar reaches the full width of the window exactly when the document does. Halfway down it covers half the window.</p>
        </div>
      </div>
    
      <h3 class="iv-u-m-0 iv-u-mt-10">A long enough page</h3>
      <p class="iv-u-m-0 iv-u-max-w-measure">Everything below is filler, written so the document is taller than the window and the bar has somewhere to travel.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 1. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 2. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 3. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 4. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 5. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 6. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 7. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 8. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 9. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
      <p class="iv-u-m-0 iv-u-max-w-measure iv-u-py-6">Filler paragraph 10. Sample copy for the fixture; it carries no product claim and no measurement. It is here to make the page scroll.</p>
    
      <p class="iv-u-m-0 iv-u-text-muted iv-u-py-12">End of the fixture. Under <code>prefers-reduced-motion: reduce</code> the bar is a still rule across the top and nothing is written from JavaScript.</p>
    </section>
    Dos reglas que conviene saber

    Alcance. En el camino de reserva, --iv-scroll se escribe en la raíz del contenedor scroll-motion, así que una barra que no esté dentro de esa raíz nunca la hereda. Pon la barra en el contenedor que marcaste, o marca el contenedor que ya la contiene.

    Movimiento reducido. La barra de progreso sigue acompañando a la página aunque se reduzca el movimiento, porque informa de un estado en lugar de decorarlo: el mismo argumento que una cabecera que se esconde al bajar. El parallax, la marquesina y la baraja sí se detienen.

    Marquesina

    CSS puro, y la duplicación es del autor: escribe los ítems una vez, escríbelos otra con aria-hidden="true" y una traslación de −50 % se repite sin costura. Se detiene bajo el puntero y mientras algo de dentro tenga el foco. Con movimiento reducido para y se convierte en una fila desplazable, por eso la ventana se sirve con role="group", tabindex="0" y etiqueta: a una región desplazable hay que poder llegar con el teclado.

    Marquesinafixtures/motion/marquee.htmlAbrir sola — Marquesina
    <section id="motion-marquee" class="iv-stack iv-u-gap-10">
      <p class="iv-u-m-0 iv-u-max-w-measure">Sample copy for the fixture. A marquee is pure CSS: the author writes the items twice and hides the copy from assistive technology, so the track is exactly two loops wide and a translation of &minus;50&nbsp;% is seamless. It pauses under the pointer and while anything inside it holds focus, and under <code>prefers-reduced-motion: reduce</code> it stops and becomes a scrollable row — which is why the author serves <code>tabindex="0"</code>, a role and a label on the window, exactly as the horizontal timeline does. The names below are real tokens of this framework.</p>
    
      <div class="iv-marquee iv-marquee--fade" role="group" tabindex="0" aria-label="Design tokens" style="--iv-marquee-duration: 24s">
        <div class="iv-marquee__track">
          <span class="iv-marquee__item iv-badge"><code>--iv-color-primary</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-color-accent</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-space-6</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-radius-lg</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-shadow-ambient</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-glow-primary</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-focus-halo</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-tracking-tight</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-motion-slow</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-ease-out</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-color-primary</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-color-accent</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-space-6</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-radius-lg</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-shadow-ambient</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-glow-primary</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-focus-halo</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-tracking-tight</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-motion-slow</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-ease-out</code></span>
        </div>
      </div>
    
      <div class="iv-stack iv-u-gap-4">
        <h3 class="iv-u-m-0">Reversed, and in display type</h3>
        <p class="iv-u-m-0 iv-u-max-w-measure">Same markup, <code>--reverse</code> added and a slower duration. The separators are decoration and are hidden from assistive technology.</p>
      </div>
    
      <div class="iv-marquee iv-marquee--reverse iv-marquee--fade" role="group" tabindex="0" aria-label="Parts of the framework" style="--iv-marquee-duration: 36s; --iv-marquee-gap: var(--iv-space-4)">
        <div class="iv-marquee__track">
          <span class="iv-marquee__item" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)">Tokens</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item iv-gradient-text" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)">Layers</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)">Fixtures</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item iv-gradient-text" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)">Contracts</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)">Utilities</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item iv-gradient-text" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)">Components</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">Tokens</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item iv-gradient-text" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">Layers</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">Fixtures</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item iv-gradient-text" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">Contracts</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">Utilities</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
          <span class="iv-marquee__item iv-gradient-text" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">Components</span>
          <span class="iv-marquee__item iv-u-text-muted" style="font-size: var(--iv-text-3xl); letter-spacing: var(--iv-tracking-tight)" aria-hidden="true">/</span>
        </div>
      </div>
    
      <div class="iv-stack iv-u-gap-4">
        <h3 class="iv-u-m-0">Vertical, inside a panel</h3>
        <p class="iv-u-m-0 iv-u-max-w-measure">The vertical variant needs a height of its own: <code>--iv-marquee-size</code> gives it one, and the edge mask turns to the block axis on its own.</p>
      </div>
    
      <div class="iv-card" style="max-inline-size: 22rem">
        <div class="iv-card__body">
          <div class="iv-marquee iv-marquee--vertical iv-marquee--fade" role="group" tabindex="0" aria-label="Parts of the framework, vertically" style="--iv-marquee-duration: 20s; --iv-marquee-size: 11rem; --iv-marquee-gap: var(--iv-space-4)">
            <div class="iv-marquee__track">
          <span class="iv-marquee__item iv-u-text-lg">Tokens</span>
          <span class="iv-marquee__item iv-u-text-lg">Layers</span>
          <span class="iv-marquee__item iv-u-text-lg">Fixtures</span>
          <span class="iv-marquee__item iv-u-text-lg">Contracts</span>
          <span class="iv-marquee__item iv-u-text-lg">Utilities</span>
          <span class="iv-marquee__item iv-u-text-lg">Components</span>
          <span class="iv-marquee__item iv-u-text-lg" aria-hidden="true">Tokens</span>
          <span class="iv-marquee__item iv-u-text-lg" aria-hidden="true">Layers</span>
          <span class="iv-marquee__item iv-u-text-lg" aria-hidden="true">Fixtures</span>
          <span class="iv-marquee__item iv-u-text-lg" aria-hidden="true">Contracts</span>
          <span class="iv-marquee__item iv-u-text-lg" aria-hidden="true">Utilities</span>
          <span class="iv-marquee__item iv-u-text-lg" aria-hidden="true">Components</span>
            </div>
          </div>
        </div>
      </div>
    
      <div class="iv-stack iv-u-gap-4">
        <h3 class="iv-u-m-0">With a link inside</h3>
        <p class="iv-u-m-0 iv-u-max-w-measure">Focus the link with the keyboard: the track stops while the focus is inside it, so a name can be read and acted on. The link points at the fixture itself and does nothing else.</p>
      </div>
    
      <div class="iv-marquee iv-marquee--fade" role="group" tabindex="0" aria-label="Design tokens, with a link" style="--iv-marquee-duration: 26s">
        <div class="iv-marquee__track">
          <span class="iv-marquee__item"><a href="#motion-marquee">Back to the top of this fixture</a></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-color-primary</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-color-accent</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-space-6</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-radius-lg</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-shadow-ambient</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-glow-primary</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-focus-halo</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-tracking-tight</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-motion-slow</code></span>
          <span class="iv-marquee__item iv-badge"><code>--iv-ease-out</code></span>
          <span class="iv-marquee__item" aria-hidden="true"><a href="#motion-marquee" tabindex="-1">Back to the top of this fixture</a></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-color-primary</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-color-accent</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-space-6</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-radius-lg</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-shadow-ambient</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-glow-primary</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-focus-halo</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-tracking-tight</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-motion-slow</code></span>
          <span class="iv-marquee__item iv-badge" aria-hidden="true"><code>--iv-ease-out</code></span>
        </div>
      </div>
    </section>
    .iv-root {
      --iv-marquee-duration: 24s;
      --iv-marquee-gap: var(--iv-space-4);
      --iv-marquee-size: 11rem;
    }

    Tarjetas apiladas

    Cada tarjeta se pega un escalón más abajo que la anterior, desplazada por --iv-i, así que la baraja se lee como una baraja mientras la recorres. Donde el motor tiene animaciones dirigidas por scroll, la tarjeta de debajo además se encoge y se atenúa cuando la baraja se va; donde no, el apilado pegajoso es todo lo que hay, y no se pierde nada.

    Una baraja pegajosafixtures/motion/stack.htmlAbrir sola — Una baraja pegajosa
    <section id="motion-stack" class="iv-stack iv-u-gap-8">
      <p class="iv-u-m-0 iv-u-max-w-measure">Sample copy for the fixture. Each card sticks one step lower than the one before it, so the deck reads as a deck. Where the engine has scroll-driven animations each card shrinks and dims exactly while the next one rides over it (the deck serves <code>--iv-stack-count</code> and every card its <code>--iv-i</code>); where it does not, the stack is still a stack and nothing is lost. The text of the cards describes this project.</p>
    
      <div class="iv-stack-cards" style="--iv-stack-top: var(--iv-space-6); --iv-stack-count: 4">
        <section class="iv-stack-cards__card iv-card" style="--iv-i: 0">
          <div class="iv-card__body iv-stack iv-u-gap-4 iv-u-py-8">
            <div class="iv-cluster"><span class="iv-badge iv-badge--primary">01</span></div>
            <h3 class="iv-card__title iv-u-m-0" style="font-size: var(--iv-text-2xl); letter-spacing: var(--iv-tracking-tight)">Contracts first</h3>
            <p class="iv-u-m-0 iv-u-max-w-measure">Every family of this framework is written as a contract before a line of it is implemented: markup, classes, options, events, accessibility and what is out of scope.</p>
          </div>
        </section>
        <section class="iv-stack-cards__card iv-card" style="--iv-i: 1">
          <div class="iv-card__body iv-stack iv-u-gap-4 iv-u-py-8">
            <div class="iv-cluster"><span class="iv-badge iv-badge--primary">02</span></div>
            <h3 class="iv-card__title iv-u-m-0" style="font-size: var(--iv-text-2xl); letter-spacing: var(--iv-tracking-tight)">One grammar</h3>
            <p class="iv-u-m-0 iv-u-max-w-measure">Classes start with <code>iv-</code>, custom properties with <code>--iv-</code>, attributes with <code>data-iv-</code> and events with <code>iv:</code>. Nothing else is allowed in.</p>
          </div>
        </section>
        <section class="iv-stack-cards__card iv-card" style="--iv-i: 2">
          <div class="iv-card__body iv-stack iv-u-gap-4 iv-u-py-8">
            <div class="iv-cluster"><span class="iv-badge iv-badge--primary">03</span></div>
            <h3 class="iv-card__title iv-u-m-0" style="font-size: var(--iv-text-2xl); letter-spacing: var(--iv-tracking-tight)">HTML that already works</h3>
            <p class="iv-u-m-0 iv-u-max-w-measure">The served markup is complete. JavaScript upgrades it; it never creates the thing the page is for.</p>
          </div>
        </section>
        <section class="iv-stack-cards__card iv-card" style="--iv-i: 3">
          <div class="iv-card__body iv-stack iv-u-gap-4 iv-u-py-8">
            <div class="iv-cluster"><span class="iv-badge iv-badge--primary">04</span></div>
            <h3 class="iv-card__title iv-u-m-0" style="font-size: var(--iv-text-2xl); letter-spacing: var(--iv-tracking-tight)">Motion with an argument</h3>
            <p class="iv-u-m-0 iv-u-max-w-measure">Movement is transform and opacity, it has a reason, and <code>prefers-reduced-motion</code> removes all of it while leaving the composition whole.</p>
          </div>
        </section>
      </div>
    
      <div class="iv-stack iv-u-gap-4 iv-u-py-12" style="min-block-size: 120vh">
        <h3 class="iv-u-m-0">After the deck</h3>
        <p class="iv-u-m-0 iv-u-max-w-measure">Filler, so the last card can leave the top of the window and the whole deck can be scrolled past: the shrink of a card only runs while it is on its way out.</p>
        <p class="iv-u-m-0 iv-u-max-w-measure">Under <code>prefers-reduced-motion: reduce</code> the cards keep their sticky offsets and drop the scale and the fade.</p>
      </div>
    </section>
    .iv-root {
      --iv-stack-top: var(--iv-space-6);
      --iv-stack-count: 4;
      --iv-i: 0;
    }

    Una nota honesta sobre esto: con animation-range: exit sobre sujetos pegajosos, toda la baraja informa casi del mismo progreso y retrocede junta al salir por arriba de la ventana, en vez de irse relevando tarjeta a tarjeta. Eso es lo que sale en 0.7. El relevo por tarjeta —una línea de tiempo con nombre y un rango derivado de --iv-i con un --iv-stack-count servido— queda en la lista para un ciclo posterior.

    Opciones

    OpciónAtributoPor defectoEfecto
    forcedata-iv-forcefalseEjecuta el camino de JavaScript incluso donde existen las líneas de tiempo nativas. Para probar la reserva.

    update() recalcula tras un cambio de maquetación que hayas hecho tú; destroy() retira los escuchadores y las propiedades que escribió; native es de solo lectura y dice qué camino está vivo. No hay eventos: nada de esto es un cambio de estado al que una página querría reaccionar.

    Teclado y tecnología asistiva

    Ninguna de las cuatro es un control. La barra de progreso está oculta a la tecnología asistiva; la capa del parallax es una imagen con su propio alt; la baraja es un conjunto de secciones que se leen en orden de documento, apiladas o no. La marquesina es la que pide cuidado: con movimiento reducido se convierte en una región de scroll horizontal, así que debe llevar rol, etiqueta y tabindex="0", y la mitad duplicada debe ser aria-hidden para que nada se anuncie dos veces.

    Sin JavaScript

    La marquesina y la baraja son CSS de principio a fin y no necesitan nada. El parallax y la barra de progreso también lo son en Chromium y en WebKit. En Firefox, con JavaScript desactivado, la capa se queda en la posición que escribió el autor y la barra se queda a cero: la página está completa, la decoración no está. Nada que lleve significado depende de ninguna de las dos cosas.

    Tokens y locales

    LocalPor defectoQué mueve
    --iv-parallax0.2Fuerza del retraso, como fracción.
    --iv-parallax-ratioproporción del marcoProporción de iv-parallax-frame.
    --iv-scroll-progress-size3pxGrosor de la barra de lectura.
    --iv-scroll-progress-colorvar(--iv-color-primary)Color de la barra, con el halo primario detrás.
    --iv-marquee-duration30sUna vuelta completa de la pista.
    --iv-marquee-gap, --iv-marquee-sizetokens de espacioSeparación entre ítems y alto de la ventana.
    --iv-stack-topvar(--iv-space-8)Dónde se pega una tarjeta.
    --iv-stack-step, --iv-stack-scale, --iv-stack-fadevalores de la barajaDesplazamiento por tarjeta, cuánto se encoge la de debajo, cuánto se atenúa.

    Dos propiedades las escribe JavaScript en el camino de reserva y se pueden leer desde CSS: --iv-view (0 cuando un elemento entra por abajo, 1 cuando sale por arriba) en cada capa de parallax, y --iv-scroll (de 0 a 1) en la raíz del componente.

    Errores habituales

    • Una capa de parallax sin la altura de --iv-parallax--media: el recorrido descubre el borde del marco.
    • Poner overflow: hidden al marco a mano y preguntarse por qué no se mueve nada en Chromium.
    • Una barra de progreso fuera de la raíz de scroll-motion: funciona en dos motores y se queda plana en el tercero.
    • Una marquesina cuya segunda mitad no lleva aria-hidden: cada ítem se lee dos veces.
    • Usar la baraja para pasos que hay que leer en orden y comparar. Eso es pasos o una línea de tiempo.
    • Entender el movimiento reducido como «apagar los efectos»: la barra de lectura es información y se queda.

    Índice de clases

    Partes y modificadores de esta familia que los ejemplos de arriba no muestran. La lista completa por hoja está en la referencia.

    ClaseQué hace
    iv-parallax__layerCapa dentro de un marco de parallax: es lo que se mueve; el marco la recorta.