Saltar al contenido principal

Revelados a pantalla completa y cortinas

Cuatro efectos de revelado controlados por el scroll que no necesitan una pista horizontal: una imagen que crece desde un rectángulo centrado hasta llenar la pantalla, una palabra gigante cuyas letras se convierten en una foto a pantalla completa, una sección de cierre que se levanta como una cortina para descubrir el pie de página, y una cuadrícula de fotos que vuela en todas direcciones alrededor de un titular que se eleva.

Revelado desde el centro​

Una imagen de fondo crece desde un pequeño rectángulo centrado hasta llenar todo el escenario anclado mientras la página hace scroll, una capa oscura aparece gradualmente sobre ella, y el título y el texto se elevan encima, una vez que la imagen está casi completa.

Estructura (una escena anclada de 170vh, anclada entre 37–63%, contenido del escenario alineado al final):

div .pin__stage justify-content: flex-end
├─ div .reveal__media "Get parent" + Custom, Start 30, End 60, From 50
│ ├─ img .reveal__image
│ └─ div .reveal__shade
└─ div .reveal__content "Get parent" + Fade 50–60 + Bottom-to-top 50–60

Fade escribe opacity y Bottom-to-top escribe transform, así que ambos preajustes pueden compartir de forma segura el elemento de contenido.

CSS:

.reveal__media {
position: absolute; inset: 0; z-index: -1;
clip-path: polygon(
calc(50% - 50% * var(--fa-scroll--custom)) calc(50% - 50% * var(--fa-scroll--custom)),
calc(50% + 50% * var(--fa-scroll--custom)) calc(50% - 50% * var(--fa-scroll--custom)),
calc(50% + 50% * var(--fa-scroll--custom)) calc(50% + 50% * var(--fa-scroll--custom)),
calc(50% - 50% * var(--fa-scroll--custom)) calc(50% + 50% * var(--fa-scroll--custom)));
}
.reveal__image { width: 100%; height: 100%; object-fit: cover; border-radius: 0; }
.reveal__shade {
position: absolute; inset: 0;
background: rgb(0 0 0 / .6);
opacity: var(--fa-scroll--custom, 1);
}
.reveal__content { position: relative; z-index: 1; color: #fff; }

Ajustar From a 50 hace que --fa-scroll--custom empiece en 0.5, de modo que la imagen ya es un rectángulo de la mitad del tamaño en el momento en que empieza el revelado, en lugar de partir de un solo punto. Sin la animación en marcha, el clip-path se vuelve inválido (así que la imagen se muestra a tamaño completo) y la capa oscura se queda totalmente activa — dando como resultado de reserva una imagen completa con texto legible.

Valores que puedes cambiar: From (50 = empieza a la mitad del tamaño; 0 = empieza desde un punto; 70 = empieza ya como un marco grande), la ventana (30–60 aquí: empieza mientras la escena todavía está llegando, termina justo antes de que el escenario se soltaría — mantén el final dentro de la ventana anclada), el color/intensidad de la capa oscura, la ventana propia del contenido (para que el texto llegue una vez que la imagen está casi completa), y un revelado circular en vez de rectangular — para eso, sustituye la fila Custom y el clip-path por el preajuste Circle integrado en .reveal__media.

  • Media: "Get parent" + una fila Custom, Start 30, End 60, From 50.
  • Contenido: "Get parent" + una fila Fade y una fila Bottom-to-top, ambas Start 50, End 60.
  • La imagen dentro del elemento media: Object fit nativo "cover", border radius 0.

Problemas comunes:

  • Aparecen esquinas redondeadas cuando la imagen debería ser a pantalla completa. Muchos temas redondean por defecto cada img — fija border-radius: 0 en la clase propia de la imagen.
  • El recorte no hace nada, o la imagen salta. Un segundo preajuste que escribe clip-path en el mismo elemento (Sweep, Circle, Blinds) lo está sobrescribiendo — mantén una sola fuente de clip-path por elemento.
  • Se pierde el transform propio del contenido. Bottom-to-top ya escribe transform — pon cualquier transform adicional que necesites en un contenedor envolvente en su lugar.

Máscara de texto gigante​

Una palabra corta, rellena con una foto, se sitúa centrada en un escenario anclado. A medida que la página hace scroll, las letras crecen mucho más allá de los bordes de la pantalla, de modo que la foto que hay dentro termina cubriéndolo todo — después una capa oscura y el contenido real de la página aparecen gradualmente encima.

Estructura (una escena anclada de 200vh, anclada entre 33–67%, escenario con height: 100vh):

div .pin__stage position: sticky; isolation: isolate
├─ p .mask__word "Get parent" + Custom, Start 35, End 55
├─ div .mask__shade "Get parent" + Custom, Start 45, End 50
└─ div .mask__content "Get parent" + Fade, Start 45, End 60

CSS:

.mask__word {
--mk-size: 129px;
position: absolute; inset: 0; z-index: 0; margin: 0;
height: 100vh; overflow: hidden;
display: flex; justify-content: center; align-items: center;
white-space: nowrap; text-align: center; font-weight: 900;
font-size: calc(var(--mk-size) + 8900px * var(--fa-scroll--custom, 0));
letter-spacing: calc(-.5em * var(--fa-scroll--custom, 0));
transform: scale(calc(.99 + var(--fa-scroll--custom, 0) * .1));
color: transparent;
background: url("your-image.jpg") center / cover no-repeat;
-webkit-background-clip: text; background-clip: text;
}
.mask__shade { position: absolute; inset: 0; z-index: 1; background: rgb(0 0 0 / .6); opacity: var(--fa-scroll--custom, 1); pointer-events: none; }
.mask__content { position: relative; z-index: 2; color: #fff; }

En móviles, reduce aún más --mk-size (59px por debajo de 768px, 45px por debajo de 479px). Sin la animación la capa oscura se queda activa, así que el contenido blanco de la página permanece legible como estado de reserva.

Valores que puedes cambiar: --mk-size (el tamaño inicial de la palabra — elígelo de modo que la palabra llene aproximadamente el ancho de la pantalla), 8900px (lo grandes que llegan a ser las letras — la foto que hay dentro debe cubrir toda la pantalla a través de una sola letra al final), y las tres ventanas (palabra 35–55, capa oscura 45–50, contenido 45–60).

  • Palabra (como elemento Basic Text, etiqueta p): "Get parent" + una fila Custom, Start 35, End 55; usa la imagen como fondo de la clase del elemento y escribe el resto del CSS en el CSS personalizado.
  • Capa oscura: fila Custom, Start 45, End 50. Contenido: fila Fade, Start 45, End 60. Ambos "Get parent".
  • Los tamaños para móvil van en el campo de CSS personalizado de ese breakpoint.

Problemas comunes:

  • Movimiento entrecortado en portátiles más lentos. font-size cambia todo el maquetado del texto en cada fotograma — mantén la palabra corta, y comprueba la fluidez en un portátil de gama media, no solo en uno rápido.
  • La palabra pasa a dos líneas mientras crece. Falta white-space: nowrap.
  • Letras negras o con aspecto vacío en Safari. Falta el prefijo -webkit-background-clip: text junto a la propiedad sin prefijo.
  • El escenario deja de quedarse fijo a partir de cierto tamaño de pantalla. Un degradado superpuesto puesto directamente sobre el escenario fijo en ese breakpoint — mantén cualquier oscurecimiento en su propio elemento de capa separado.

Cortina de pie de página​

Al final mismo de la página, el bloque de cierre (una llamada a la acción) se desplaza hacia arriba, se encoge ligeramente y redondea sus esquinas inferiores, descubriendo el pie de página que hay debajo, el cual no se mueve en absoluto.

Estructura (el bloque de cierre y el pie de página deben ser hermanos dentro de la misma sección):

section .curtain background = el propio color del pie de página; padding 0; reinicio de overflow-x
├─ div .curtain__closing Custom, Start 50, End 65 (se mide a sí misma), Disable from tablet
│ └─ div .curtain__panel el bloque visible: background, padding
│ └─ div .curtain__inner título, texto, botón
└─ footer .curtain__footer position: sticky; bottom: 0

El revelado en sí es CSS puro: un bloque opaco encima se desplaza hacia afuera y descubre un pie de página fijado en la parte inferior debajo de él. Aquí no hay nada que realmente se mueva por el valor del scroll, así que nada se queda visiblemente retrasado un fotograma.

CSS:

.curtain {
padding: 0; overflow-x: initial; background: #18181b;
--ct-shrink: .05; --ct-corner: 2rem; --ct-fade: .6; --ct-rise: 2rem;
}
.curtain__closing {
position: relative; z-index: 1;
display: flex; flex-direction: column; align-items: stretch;
--ct-h: calc(var(--fa-scroll--custom) * 0vh + 100vh);
--ct-r: var(--fa-scroll--custom, 0);
min-height: var(--ct-h, auto);
}
.curtain__panel {
flex-grow: 1; display: flex; flex-direction: column; justify-content: center; align-items: center;
background: #fafafa; padding: 6rem 1.5rem;
transform-origin: 50% 0;
transform: scale(calc(1 - var(--ct-shrink) * var(--ct-r)));
border-bottom-left-radius: calc(var(--ct-r) * var(--ct-corner));
border-bottom-right-radius: calc(var(--ct-r) * var(--ct-corner));
will-change: transform;
}
.curtain__inner {
opacity: calc(1 - var(--ct-fade) * var(--ct-r));
transform: translateY(calc(var(--ct-r) * -1 * var(--ct-rise)));
}
.curtain__footer { position: sticky; bottom: 0; z-index: 0; color: #d4d4d8; padding: 3rem 1.5rem 1.5rem; }
@media (max-width: 991px) { .curtain__footer { position: relative; } }

Sincronización exacta sin necesidad de conocer la altura real del pie de página: mientras está animado, el bloque de cierre mide exactamente una pantalla de alto, así que en su propio progreso, 50% significa que su borde inferior está al mismo nivel que la parte inferior de la pantalla, y 65% significa que ya se ha levantado un 30% de la pantalla. Pon los efectos visuales en el panel, nunca en el bloque medido en sí — el plugin mide la caja después de los transforms.

Valores que puedes cambiar: --ct-shrink/--ct-corner/--ct-fade/--ct-rise en .curtain, y el valor de End (65 aquí — más tarde significa un levantamiento más lento). El fondo de la sección coincide con el color del pie de página a propósito: cualquier hueco que deje el panel al encogerse siempre coincide con el pie de página detrás de él.

  • Sección: padding 0, background = color del pie de página, más el reinicio de overflow-x en CSS personalizado.
  • Bloque de cierre: "Disable from tablet" + una fila Custom, Start 50, End 65 — sin "Get parent".
  • Pie de página: Position nativo "sticky", Bottom 0, con Position "relative" fijado para el breakpoint de tablet en vez de una regla @media escrita a mano.

Problemas comunes:

  • La parte superior del pie de página nunca llega a mostrarse. Un pie de página fijo más alto que la ventana sencillamente no puede mostrar su propio borde superior — mantenlo por debajo de unos 600px de alto en escritorio; en tablet y móvil de todos modos vuelve a ser un pie de página normal, no fijo.
  • La sincronización no encaja. La escala se aplicó al bloque medido en sí en vez de al panel — muévela al panel.
  • El pie de página salta, o no se queda fijo en absoluto. Falta el reinicio de overflow-x en la sección, o el pie de página no está dentro de la misma sección que el bloque de cierre.
  • Un hueco visible o una franja de color entre el panel y el pie de página. El fondo de la sección en realidad no coincide con el del pie de página.

Abanico​

Dos columnas de imágenes en medio de una sección normal (no anclada) vuelan hacia afuera, a izquierda y derecha, a medida que se hace scroll, cada una con su propia distancia y ángulo, mientras un título en el centro se eleva a través de ellas. Aquí no hace falta ningún escenario anclado.

Estructura:

section .fan overflow: hidden (las imágenes salen del encuadre)
└─ div .fan__container "I am parent", position: relative
├─ div .fan__media cuadrícula de 2 columnas
│ ├─ div .fan__left > img .fan__image ×3 "Get parent" + Custom, Start 20, End 85 (la primera), End 100 (el resto)
│ └─ div .fan__right > img .fan__image ×3 mismas filas, en espejo
└─ div .fan__content fila Custom propia, End 80 (se mide a sí misma, sin "Get parent")
└─ div .fan__content-inner el título — se mueve usando el valor heredado

CSS:

.fan { overflow: hidden; }
.fan__container { position: relative; z-index: 1; display: flex; justify-content: center; align-items: center; }
.fan__media { width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.fan__left, .fan__right { display: flex; flex-direction: column; gap: 1rem; }
.fan__image { position: relative; z-index: 2; aspect-ratio: 5 / 3; object-fit: cover; border-radius: .5rem; }
.fan__left > .fan__image { transform-origin: top left; }
.fan__right > .fan__image { transform-origin: top right; }
.fan__left > :nth-child(1) { transform: translateX(calc(var(--fa-scroll--custom, 0) * -200%)) translateY(calc(var(--fa-scroll--custom, 0) * 80%)) rotate(calc(var(--fa-scroll--custom, 0) * -30deg)); }
.fan__left > :nth-child(2) { transform: translateX(calc(var(--fa-scroll--custom, 0) * -280%)) rotate(calc(var(--fa-scroll--custom, 0) * -30deg)); }
.fan__left > :nth-child(3) { transform: translateX(calc(var(--fa-scroll--custom, 0) * -220%)) translateY(calc(var(--fa-scroll--custom, 0) * -20%)) rotate(calc(var(--fa-scroll--custom, 0) * -60deg)); }
.fan__right > :nth-child(1) { transform: translateX(calc(var(--fa-scroll--custom, 0) * 200%)) translateY(calc(var(--fa-scroll--custom, 0) * 80%)) rotate(calc(var(--fa-scroll--custom, 0) * 30deg)); }
.fan__right > :nth-child(2) { transform: translateX(calc(var(--fa-scroll--custom, 0) * 280%)) rotate(calc(var(--fa-scroll--custom, 0) * 30deg)); }
.fan__right > :nth-child(3) { transform: translateX(calc(var(--fa-scroll--custom, 0) * 220%)) translateY(calc(var(--fa-scroll--custom, 0) * -20%)) rotate(calc(var(--fa-scroll--custom, 0) * 60deg)); }
.fan__content { position: absolute; top: 50%; left: 50%; width: 100%; max-width: 550px; transform: translateX(-50%); text-align: center; }
.fan__content-inner { transform: translateY(calc(var(--fa-scroll--custom, 0) * -130%)); }

En móviles (por debajo de 768px), usa una sola columna: oculta .fan__right y usa movimientos más grandes solo en la columna izquierda, para que el efecto se siga leyendo con claridad con menos imágenes en pantalla.

Valores que puedes cambiar: la distancia propia de cada imagen (±200–280%), el levantamiento y el ángulo de rotación — replica a izquierda y derecha para que el abanico se mantenga visualmente simétrico; transform-origin en la esquina exterior (en vez del centro) hace que las imágenes giren hacia afuera en lugar de rotar sobre su propio sitio; las ventanas (20–85/20–100 para las imágenes, End 80 para el contenido).

  • Contenedor: "I am parent". Cada imagen: "Get parent" + una fila Custom, Start 20, End 85 (o solo Start 20, sin End, para el resto).
  • Contenido: una fila Custom, End 80, sin "Get parent".
  • Oculta .fan__right en el breakpoint de móvil con el control nativo de Display de ese breakpoint.

Problemas comunes:

  • La sincronización del título parece desajustarse. Un elemento que se mide a sí mismo nunca debe moverse a sí mismo — la fila se queda en .fan__content, el movimiento real en .fan__content-inner.
  • Aparece una barra de desplazamiento horizontal. Las imágenes que vuelan salen de los límites de la sección — añade overflow: hidden a la sección.
  • Los efectos hover de las imágenes dejaron de funcionar. Los elementos con scroll trigger tienen sus transiciones reiniciadas automáticamente — pon los efectos hover en un hijo o contenedor envolvente en su lugar (o, desde 3.2.0, activa Permitir transiciones propias).
  • Las imágenes no se mueven en absoluto. Falta "I am parent" en el contenedor, o falta "Get parent" en las imágenes.