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.
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.
My resume
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.
0s2.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.85s3.Land
Unseen beneath the backdrop, the route changes and the résumé page goes in, its sheet right under the one that landed.
router.push4.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
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.
/** 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.
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.
// 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.
Open the résumé island at the top of the page, then click the thumbnail.