Saltar al contenido principal

Vídeo y dibujo de líneas

Cuatro recetas que revelan contenido de forma progresiva mientras algo se «dibuja»: un vídeo a pantalla completa que avanza y retrocede con el scroll, una ilustración SVG que se dibuja a sí misma trazo a trazo, una línea de tiempo vertical cuya línea se rellena hasta coincidir con el hito visible, y un viajero que se desplaza a lo largo de un camino sinuoso.

Scrub de vídeo​

Un vídeo a pantalla completa sigue el scroll fotograma a fotograma mientras el escenario está anclado — bajar en el scroll lo reproduce, subir lo rebobina — con subtítulos que se van turnando encima. Consulta Scrub de vídeo para ver el ajuste en sí; esta es la versión aplicada, de escena completa.

Estructura (una escena anclada de 400vh, anclada entre 20–80%):

div .pin__stage
├─ video .vscrub__video "Get parent" + Video scrub, Start 20, End 80 (+ opcionalmente Custom, misma ventana)
├─ div .vscrub__shade degradado de legibilidad, absolute
├─ div .vscrub__content subtítulos — constrúyelo con la receta "Secuencia: elementos que se turnan", Start 20, End 80
└─ div .scroll-progress opcional — ver "Altura de escena e indicadores de progreso", Custom 20–80

CSS:

.vscrub__video { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.vscrub__video, .vscrub__video video { width: 100%; height: 100%; object-fit: cover; }
.vscrub__shade {
position: absolute; inset: 0; z-index: 0; pointer-events: none;
background: linear-gradient(90deg, rgb(0 0 0 / .8), transparent 60%);
}
.vscrub__content { position: relative; z-index: 1; color: #fff; }

Opcional, para móviles: desplaza el recorte a medida que avanza el scroll, para que un producto que se mueve por el encuadre se mantenga a la vista, usando una fila Custom en el mismo elemento: object-position: calc(54% + var(--fa-scroll--custom) * 16%) 50%; en el propio <video>.

El archivo de vídeo es lo que decide si se ve fluido — consulta las notas de codificación de Scrub de vídeo: MP4 (H.264), sin pista de audio, fotogramas por segundo constantes, 1080p o menos, un keyframe en cada fotograma («all-intra»), y aproximadamente 300 fotogramas o más para una escena de 400vh. Los archivos all-intra pesan bastante (unos 7MB para 360 fotogramas) — mantén el propio clip corto.

Usa el elemento Video nativo, origen "Media": mute activado, inline activado, preload "auto", object-fit "cover", pointer events "none", "Get parent", y una fila Video scrub Start 20, End 80. Deja "Controls" completamente fuera (no te limites a desactivarlo — deja la clave sin establecer). Apunta a la propia etiqueta <video> para cualquier cosa que solo tenga sentido en ella (como object-position en móviles), ya que Bricks la envuelve en su propio div.

Problemas comunes:

  • El vídeo va a saltos o se nota claramente retrasado. Demasiados pocos keyframes o demasiado pocos fotogramas — vuelve a codificar en all-intra con 300+ fotogramas, y confírmalo con Check video (desde 3.2.0, en el lienzo del constructor).
  • Los subtítulos se ven todos a la vez cuando la animación no está en marcha. Consulta la nota de la receta de secuencia sobre condicionar el maquetado de celda compartida a la variable propia de la animación, nunca a una clase del body.

Dibujo de líneas SVG​

Una ilustración de líneas se dibuja a sí misma mientras el escenario está anclado, un grupo de trazos por paso, con el grupo que se está dibujando en ese momento mostrado en un color de acento y los grupos terminados en uno neutro. Solo Bricks — necesita un SVG en línea (inline), algo que Etch actualmente no admite importar de la misma manera.

El archivo SVG​

  • Cada trazo debe ser un <path> con pathLength="1" — esto permite que el mismo CSS dibuje cualquier trazo de forma idéntica, sea cual sea su longitud geométrica real.
  • Un <g> por paso, como hijos directos del <svg>, en el orden de dibujo.
  • Ningún atributo stroke, fill o de color en el archivo — el CSS se encarga de todo eso. Conserva el viewBox.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 360">
<g><path pathLength="1" d="M24 24 H456 V336 H24 Z"/></g>
<g><path pathLength="1" d="M40 116 H232 V256 H40 Z"/><path pathLength="1" d="M264 132 H440"/></g>
</svg>

Estructura (una escena anclada de 400vh, anclada entre 20–80%):

div .pin__stage
└─ div .draw__grid 2 columnas (1 en móviles)
├─ div .draw__steps opcional — "Sequence children", Start 20, End 80
└─ div .draw__frame
└─ svg .draw__svg "Get parent" + Custom, Start 20, End 80

CSS:

.draw__svg {
--dw-n: 4;
--dw-q: var(--fa-scroll--custom, 1);
width: 100%; height: auto; aspect-ratio: 4 / 3;
fill: none; stroke-width: 2px; stroke-linecap: round; stroke-linejoin: round;
}
.draw__svg:has(> g:nth-child(2)) { --dw-n: 2; }
.draw__svg:has(> g:nth-child(3)) { --dw-n: 3; }
/* … una regla por cada recuento, hasta 12 */
.draw__svg > g {
--dw-i: 0;
--dw-t: calc(var(--dw-q) * var(--dw-n) - var(--dw-i));
--dw-v: clamp(0, var(--dw-t) / .7, 1);
--dw-act: clamp(0, min(var(--dw-t), 1 - var(--dw-t)) * 8, 1);
stroke: color-mix(in srgb, var(--primary, #2563eb) calc(var(--dw-act) * 100%), #3f3f46);
opacity: min(var(--dw-v) * 20, 1);
}
.draw__svg > g:nth-child(2) { --dw-i: 1; }
.draw__svg > g:nth-child(3) { --dw-i: 2; }
/* … hasta 12 (--dw-i: 11) */
.draw__svg > g > * {
vector-effect: non-scaling-stroke;
stroke-dasharray: 1;
stroke-dashoffset: calc(1 - var(--dw-v));
}

El grupo i (desde 0) de n se dibuja mientras q·n va de i a i + .7 — el primer 70% de su parte, que coincide con la ventana propia del paso i si además usas una secuencia junto a él con el mismo Start/End. opacity oculta un grupo hasta que empieza (de lo contrario, los extremos redondeados pintarían un guion de longitud cero como un punto visible). Sin la animación, --dw-q cae por defecto a 1 y todo el dibujo se muestra a la vez.

Valores que puedes cambiar: .7 (la parte de cada paso realmente dedicada a dibujar — el resto es una pausa antes de que empiece el siguiente), * 8 (con qué rapidez aparece y desaparece el color activo), el grosor del trazo y los colores.

  • Elemento SVG con origen File (no Code): Bricks lee el archivo e imprime el <svg> en línea, para que el CSS pueda llegar a cada trazo, y su sanitizador conserva el atributo pathLength. Subir archivos SVG necesita el permiso "Upload SVG" activado en Bricks → Settings → Builder access — pide al propietario del sitio que lo active si aún no lo está.
  • Elemento SVG: "Get parent" + una fila Custom, Start 20, End 80.

Problemas comunes:

  • Un trazo se muestra como guiones finos en vez de dibujarse de verdad. A ese path en concreto le falta pathLength="1", así que el guion de una unidad se está midiendo contra su longitud real.
  • Las líneas se vuelven más gruesas o más finas al escalar la ilustración. Falta vector-effect: non-scaling-stroke.
  • Aparecen puntos antes de que un grupo deba empezar. Extremos de línea redondeados sobre un guion de longitud cero — mantén la regla de opacity que oculta los trazos de los grupos que todavía no han empezado.
  • El dibujo y una secuencia de pasos junto a él se desincronizan. Valores distintos de Start/End en la fila del SVG y en la secuencia — usa exactamente el mismo rango para ambas.

Línea de tiempo: una línea que se rellena, hitos que se iluminan​

Una lista de hitos se desplaza de forma completamente normal. Una línea vertical junto a ellos se rellena hacia abajo exactamente hasta la mitad de la pantalla; el punto de cada hito se ilumina en el instante en que la línea lo alcanza, y su tarjeta se desliza hacia dentro. Un año gigante opcional cuenta en el fondo, desde el año del primer hito hasta el del último.

Esta es la opción recomendada siempre que una línea sinuosa y dibujada a mano (el mapa del recorrido más abajo) no sea posible — por ejemplo, cuando las subidas de SVG no están disponibles en el sitio. No intentes simular una curva con degradados o patrones repetidos; se percibe como algo roto, no como algo dibujado a mano.

Estructura:

section .tl padding lateral, reinicio de overflow-x
└─ div .tl__list Custom, sin Start/End; suavizado del scroll ~0.1s
├─ div .tl__year opcional — año gigante sticky, oculto para lectores de pantalla
├─ div .tl__line la pista, altura completa de la lista
│ └─ div .tl__fill la parte rellenada
└─ div .tl__item ×n Custom, sin Start/End cada uno; 70vh de alto
├─ div .tl__dot
└─ div .tl__card > span .tl__date, h3, p

La lista y cada elemento se miden a sí mismos (aquí no hace falta "I am parent"/"Get parent"). La línea y el año leen el --fa-scroll--custom de la lista; cada punto y tarjeta leen el suyo propio.

CSS:

@property --tl-year { syntax: '<integer>'; inherits: true; initial-value: 2015; }

.tl { width: 100%; padding: 6rem clamp(1rem, 5vw, 2rem); overflow-x: initial; }
.tl__list { position: relative; width: 100%; max-width: 56rem; margin: 0 auto; overflow-x: clip; --tl-x: 1.25rem; --tl-n: 6; }

/* la línea: el 100% de su propia altura ES la altura de la lista, así que el relleno no necesita ninguna división */
.tl__line { position: absolute; left: var(--tl-x); top: 0; bottom: 0; width: 2px; background: #2a3442; }
.tl__fill { width: 100%; background: #00b5c0; height: clamp(0%, var(--fa-scroll--custom) * (100% + 100vh) - 50vh, 100%); }

.tl__item { position: relative; z-index: 1; min-height: 70vh; display: flex; align-items: center; padding-left: calc(var(--tl-x) * 2 + 2rem); }
/* un elemento está centrado en la pantalla exactamente cuando su propio progreso es 0.5 */
.tl__dot {
position: absolute; left: calc(var(--tl-x) - .55rem + 1px); top: 50%; translate: 0 -50%;
width: 1.1rem; height: 1.1rem; border-radius: 50%;
--on: clamp(0, (var(--fa-scroll--custom) - .45) / .05, 1);
background: color-mix(in srgb, #00b5c0 calc(var(--on, 1) * 100%), #2a3442);
box-shadow: 0 0 0 calc(var(--on, 1) * 6px) rgb(0 181 192 / .2);
}
.tl__card {
width: 100%; border-radius: 1rem; padding: clamp(1.25rem, 3vw, 2rem); background: rgb(26 33 43 / .8);
--in: clamp(0, (var(--fa-scroll--custom) - .15) / .2, 1);
opacity: var(--in, 1); translate: calc((1 - var(--in, 1)) * 2rem) 0;
}

/* opcional: el año del hito centrado, contando entre ellos (espaciados uniformemente en el tiempo) */
.tl__year {
position: sticky; top: 50vh; height: 0; pointer-events: none; text-align: right;
--tl-s: calc((var(--fa-scroll--custom) * (var(--tl-n) * .7 + 1) - .5) / .7 - .5);
--tl-year: calc(2015 + clamp(0, var(--tl-s) / (var(--tl-n) - 1), 1) * 10);
counter-reset: tly var(--tl-year);
}
.tl__year::before { content: counter(tly); display: block; translate: 0 -50%; font-weight: 800; font-size: clamp(5rem, 18vw, 16rem); line-height: 1; color: rgb(255 255 255 / .08); }

El porcentaje de relleno se deriva de dónde se sitúa actualmente la mitad de la pantalla en relación con la parte superior de la lista; como el propio 100% de la línea ya es igual a la altura de la lista, las matemáticas se quedan solo en longitudes, sin ninguna división. Los puntos se iluminan entre el progreso 0.45 y 0.5 de su propio elemento — justo cuando el relleno los alcanza. El año gigante depende de que todos los elementos tengan la misma altura (70vh); ajusta --tl-n, el primer año y el rango para que coincidan con tu contenido real. Sin JavaScript, el relleno está vacío, todos los puntos están encendidos, todas las tarjetas son visibles, y el año muestra su valor inicial.

Valores que puedes cambiar: --tl-x (posición de la línea), la altura del elemento (mantén el .7 de --tl-s coherente con ella si la cambias), los colores, el primer año/rango en --tl-year, y la sincronización de entrada de la tarjeta (.15/.2). Para fechas no espaciadas uniformemente, elimina el contador y simplemente muestra directamente el año propio de cada tarjeta.

  • Lista: una fila Custom, suavizado del scroll ~0.1s. Cada elemento: su propia fila Custom (ids únicos). La línea, el relleno, el punto, la tarjeta y el año no tienen ningún ajuste de Fancy propio.
  • El año es pura decoración — dale aria-hidden="true".
  • Los títulos dentro de tarjetas oscuras necesitan su propio color fijado explícitamente (si no, toman el color de título del tema).

Problemas comunes:

  • La página hace scroll lateral en móviles. Las tarjetas que esperan para deslizarse hacia dentro están ligeramente desplazadas a la derecha, más allá del borde de la pantalla — usa overflow-x: clip (no hidden, que también detendría el año fijo) en la lista.
  • El año grande nunca llega a mostrarse. Las tarjetas opacas lo están cubriendo, ya que se sitúa detrás de la lista — usa fondos de tarjeta translúcidos, o coloca el año en algún sitio al que las tarjetas no lleguen.
  • El relleno va por delante, o por detrás, de los puntos. La línea no tiene la altura completa de la lista, o la lista tiene un padding que los propios elementos no comparten.
  • El año se desfasa un hito. No todos los elementos miden exactamente 70vh, o algo más (un título suelto) está directamente dentro de la lista.

Mapa del recorrido​

Un «mundo» de tamaño fijo que contiene un camino sinuoso dibujado a mano, hecho en SVG. Mientras el escenario está anclado, un punto viaja a lo largo del camino, la línea se rellena detrás de él, y las tarjetas de parada — repartidas automáticamente de forma uniforme a lo largo del camino — se iluminan a medida que el viajero las alcanza. El mundo se escala a la altura de la pantalla y se desplaza (pan) para mantener centrado al viajero. Solo Bricks (necesita subidas de SVG en línea).

Esta construcción es sustancialmente más compleja que la simple línea de tiempo anterior — recurre a ella específicamente cuando una ruta sinuosa e ilustrada forma parte del encargo y las subidas de SVG están disponibles en el sitio; si no, la receta de línea de tiempo da un efecto narrativo muy similar con mucha menos configuración.

Estructura:

section .route-section reinicio de overflow-x, padding 0
└─ div .route__scene fila Custom propia (sin Start/End), Disable from tablet, suavizado del scroll ~0.2s
└─ div .route__stage sticky
└─ div .route__world
├─ svg .route__road un archivo SVG con dos <path> que comparten el MISMO d — el 2º con pathLength="1"
├─ div .route__traveler
└─ ol .route__stops "Index children"
└─ li .route__stop ×n las tarjetas de parada (hasta 12)

La forma del camino existe en tres sitios que deben coincidir entre sí: los dos <path> del SVG, y un path() de CSS usado para mover al viajero y a las paradas a lo largo de la misma curva con offset-path. En tablet y móvil se repliega de vuelta a una lista simple — el CSS del mundo fijo y el SVG se ocultan mediante body.fanim--initialized, ya que offset-path no se puede desactivar solo con una variable de scroll.

Valores que puedes cambiar: los colores de hecho/pendiente, el ancho de la tarjeta, la longitud del «tallo» que conecta una tarjeta con el camino, lo apagada que se ve una parada sin iluminar, las pantallas de scroll por parada, cuántas paradas hay antes de que el mundo empiece a estirarse horizontalmente, y la escala mínima de la tarjeta en pantallas pequeñas. Una forma de camino distinta implica actualizar d en ambos paths del SVG y en el path() del CSS a la vez — las fórmulas de cámara y desplazamiento (panning) asumen que el camino cabe dentro de una caja de 2400×900, que va aproximadamente de x=150 a x=2250.

  • Escena: "Disable from tablet", suavizado del scroll ~0.2s, una fila Custom. Paradas (ol): "Index children". Nada más necesita ajustes de Fancy.
  • Elemento SVG, origen File (en línea, para que el CSS pueda llegar a los paths) — nunca origen Code, que necesita una firma de código por sitio que un archivo importado o escrito por IA no puede llevar. Subir SVGs necesita Bricks → Settings → Builder access → "Upload SVG" activado.
  • Las reglas del breakpoint de tablet van en el campo de CSS personalizado de tablet propio de cada clase.

Problemas comunes:

  • No hay SVG en línea disponible (subidas desactivadas, o ningún archivo adecuado). Usa en su lugar la receta de línea de tiempo — nunca simules un camino sinuoso con degradados o patrones repetidos, se percibe como algo roto en lugar de dibujado a mano.
  • El viajero y las paradas se desvían de la línea. El SVG fue encogido por una regla max-width: 100% del tema mientras las coordenadas del path() de CSS se mantienen en píxeles fijos — fija max-width: none tanto en el mundo como en el SVG.
  • Las tarjetas se alejan cada vez más del camino cuanto más avanzadas están. Se encogieron con la propiedad scale, que se aplica fuera de offset-path — usa transform: scale() en su lugar.
  • Compatibilidad con navegadores: tan()/atan2(), offset-path y color-mix() necesitan un navegador bastante reciente — Chrome 111, Safari 16.2 o Firefox 113 como mínimo.