Field studies

Layout study

Meridian

An editorial page where a single photograph is the only thing that moves. It starts full-bleed, then travels through six completely different layouts — a column, a line of type, a grid — without ever reloading or being duplicated.

The restraint is the piece. The moment a second element starts animating, the effect stops reading.

Type
Layout study
Year
2026
Stack
GSAP Flip, ScrollTrigger, Lenis
Meridian — a still from the piece

Six positions, one element

The layout declares where the photograph belongs at each step with empty placeholder elements. On load, each placeholder's box is recorded, and Flip works out the transform from wherever the photograph currently is to wherever it needs to be next.

So there is exactly one <img> for the whole page. It is never swapped, cloned or re-decoded — it just changes shape.

Timing sized by section, not by clock

This is the part that took the longest. A scrubbed timeline maps scroll distance to time linearly, so evenly-spaced transitions land at uneven moments once the sections differ in height — and they do, wildly: a single line of type versus a six-image grid.

The fix is to stop spacing transitions evenly and instead place each one around its own section's centre-of-viewport scroll position. Each move then arrives exactly as its section comes into view, no matter how tall the sections between them are.

const centerScrollY = (el) => {
  const r = el.getBoundingClientRect();
  return r.top + window.scrollY + r.height / 2 - window.innerHeight / 2;
};

const startY = centerScrollY(parentElement);
const endY   = centerScrollY(stepElements[stepElements.length - 1]);

states.forEach((state, index) => {
  const fraction = (centerScrollY(stepElements[index]) - startY) / (endY - startY);
  const arriveAt = fraction * totalDuration;
  const startAt  = Math.max(0, arriveAt - transitionDuration);

  tl.add(Flip.fit(oneElement, state, {
    duration: transitionDuration,
    ease: index === 0 ? 'none' : 'sine.inOut'
  }), startAt);
});
Place each transition at its own step's scroll position.

A flash of the wrong box

Building all the Flip tweens synchronously leaves the photograph holding stale inline width, height and transform from the last state in the loop. It sits there wrong until the first scroll event re-renders the timeline — brief, but on a page this quiet you notice it.

Clearing those properties immediately after the loop lets the element fall back to its natural CSS box until the scrub takes over.

// Fall back to the natural CSS box until scrub takes over.
gsap.set(oneElement, { clearProps: 'width,height,transform' });

Everything else holds still

The surrounding images never animate. The display headings drift in from alternating sides and stop. That is the entire motion budget, and spending any more of it would cost the effect.

Next

Arc

A carousel bent onto a curve you drag along.