Back to library
INTERACTION_115

Layered Image Depth.

Foreground, subject and background layers move at different rates. All layers aligned before the trigger; offsetting each layer by an increasing fraction of the input once pointer movement on desktop, scroll on touch.

CATEGORY
Images
COMPLEXITY
Intermediate
TECH
CSS
PLATFORM
Both
PERFORMANCE
Light
LIVE PLAYGROUND
CSS / CSS
/* Layered Image Depth — Foreground, subject and background layers move at different rates. */
.layered-image-depth {
  --dur: 420ms;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --stagger: 60ms;
  will-change: transform, opacity;
}

.layered-image-depth > * {
  opacity: 0;
  transform: translateY(14px);
  animation: layered-image-depth-in var(--dur) var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}

/* all layers aligned -> offsetting each layer by an increasing fraction of the input */
@keyframes layered-image-depth-in {
  to {
    opacity: 1;
    transform: none;
    filter: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .layered-image-depth > * {
    opacity: 1;
    transform: none;
    animation: none;
  }
}
React / TSX
import { useEffect, useRef } from "react";

/** Layered Image Depth — trigger: pointer movement on desktop, scroll on touch */
export function LayeredImageDepth({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      el.dataset["state"] = "in";
      return;
    }
    const io = new IntersectionObserver(
      ([entry]) => {
        if (entry?.isIntersecting) {
          el.dataset["state"] = "in";
          io.unobserve(el);
        }
      },
      { threshold: 0.2 },
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);

  return (
    <div ref={ref} className="layered-image-depth" data-state="out">
      {children}
    </div>
  );
}

IF YOU LIKE THIS, STEAL THESE TOO.

INTERACTION_113CSS

Cinematic Image Entrance

The image arrives with a letterbox open and a slow push-in.

Images / LightView
INTERACTION_105CSS

Image Mask Reveal

The image uncovers behind a mask while it settles from a slow zoom.

Images / LightView
INTERACTION_106CSS + JS

Before And After Slider

A draggable handle wipes between two versions of an image.

Images / LightView
Media
INTERACTION_107CSS

Parallax Image

The image drifts slower than the frame that contains it.

Images / LightView