← Case study

Detail 10 · Transitions

From thumbnail to page

The resume island's thumbnail flying to where the sheet sits, its sketch riding along. 6 minutes, 1 demo.

Try it: open the résumé
/
Sujoy Kr. Haldar

My resume

01 · The idea

The thumbnail is the page

The résumé island shows the real résumé, small: the same component as the résumé page, not a picture of it. So opening it shouldn’t throw the thumbnail away and load a page from nothing. The thing you clicked becomes the page: it flies up and grows into place, and the résumé page arrives around it.

It’s the one page change on the site that doesn’t sink away. Everything else about it, the sketch and the build, is the same as every other page; only the way it covers the old page is different.

02 · Watch it

Take off, fly, land, build

Below is a small site with the résumé island open. Click the thumbnail, then slow it down. Switch the grow off to compare it with the ordinary page change, and switch off the sketch riding along to see what it saves.

From thumbnail to page
/
Sujoy Kr. Haldar

My resume

  1. 1.Take off

    A backdrop fades over the page you're on. The thumbnail clears to blank paper, and its sketch starts drawing on it.

    0s
  2. 2.Fly

    It flies and grows to exactly where the sheet sits on the résumé page, the sketch riding along and growing with it.

    0.85s
  3. 3.Land

    Unseen beneath the backdrop, the route changes and the résumé page goes in, its sheet right under the one that landed.

    router.push
  4. 4.Build

    Once the sketch is done, the backdrop and the paper fade off the real page, and the sheet builds in quickly, box by box.

    quickRevealDelay
↺ The résumé, where the thumbnail landed

03 · How it flies

Landing on a page that doesn't exist yet

The thumbnail has to fly to where the sheet sits on the résumé page, and it starts flying before that page has loaded. So the sheet’s place isn’t measured; it’s worked out from the same numbers the résumé page lays itself out with, shared between the two.

Where the sheet will be, before the page is there
/** The sheet's box on screen when /resume is scrolled to the top. */
export function resumeSheetRect() {
  const gutter = window.matchMedia(WIDE_FROM).matches ? SHEET_GUTTER_WIDE : SHEET_GUTTER;
  const width = Math.min(SHEET_MAX_WIDTH, window.innerWidth - gutter * 2);
  // Centered in the page's content box (which excludes any scrollbar)
  const left = (document.documentElement.clientWidth - width) / 2;
  return { left, top: SHEET_TOP, width };
}

The flying copy is rendered at its full size, already in its final place, and started off moved and scaled down onto the thumbnail. Then it animates back to no transform at all. Scaling something small up would blur it on the way and leave it soft; this way it lands at its real size, crisp, and only the transform ever moves.

Full size in its final place, started on the thumbnail
className="absolute origin-top-left"
style={{ left: to.left, top: to.top, width: to.width }}
initial={{ x: from.left - to.left, y: from.top - to.top, scale: startScale }}
animate={{ x: 0, y: 0, scale: 1 }}
transition={{ duration: GROW_DURATION, ease: EASE }}
onAnimationComplete={onCovered}

04 · The sketch

A sketch that rides along

On an ordinary page change, the sketch can only start once the new page has loaded and been measured. Here the sketch is of the thing that’s already flying, so it can start at take-off. The flying copy is measured straight away, with its scale undone, so the sketch is drawn in its real pixels and scales up with it, riding along as part of it.

Measured mid-flight, in its own unscaled pixels
// Measure the flying copy straight away, in its own unscaled pixels, so the
// sketch can draw on it during the flight
useEffect(() => {
  const frame = requestAnimationFrame(() => {
    const flyer = flyerRef.current;
    const scale = flyer.getBoundingClientRect().width / flyer.offsetWidth || 1;
    const measured = measureWireframe(flyer, { scale });
    if (measured?.blocks.length) setLayout(measured);
    else onSketched();
  });
  return () => cancelAnimationFrame(frame);

The page loads during the flight, and the build waits for whichever finishes last: the landing with the new page in, or the sketch. Then the sheet builds in briskly, a few hundredths of a second between blocks instead of a tenth, because the slow part, the sketch, has already happened in the air.

05 · Decisions

Why it’s built this way

Keep what was clicked

The thumbnail turns into the page instead of disappearing, so there’s never a moment of looking for the résumé again.

Shared numbers, not a guess

The island and the résumé page read the sheet’s place from one file. Change the layout, and the flight still lands exactly.

Scale down, not up

Full size, started small. It lands crisp, and the animation only ever touches the transform.

Do the slow part once

The sketch draws during the flight, not after it, so the whole change is shorter than an ordinary one.

06 · Takeaways

What to keep

  • When a page opens from a preview of itself, let the preview become the page.
  • Know where it lands before the page exists: share the layout’s numbers rather than measuring a page that isn’t there yet.
  • Render at full size and start scaled down; animate the transform back to nothing, and start the slow parts while it flies.
Try the real one→

Open the résumé island at the top of the page, then click the thumbnail.