123

Card to Hero Page Transition

£10 or the whole Vault, £99

A shared-element page transition for ordinary multi-page sites: click a project card and its image flies out of the grid to become the hero of the page it links to, while the index recedes around it. Back flies it home.

Flip advanced
7 more details
page-transitionsoft-navigationflip-layoutshared-elementinterruptible-transitionsfocus-managementprogressive-enhancement

This demo reads better at your own screen size than in the frame below:

Released

About this effect

Click a card on an index page and its image lifts out of the grid and flies, in one confident move, to exactly where the hero image sits on the page it links to. On the way it changes shape from a portrait card to a wide hero, re-cropping as it goes so the photograph never stretches, and its corners ease from the card's radius to the hero's. The rest of the index drops away around it, the cards nearest the click first, and the new page's title and text settle in as the image lands. Back reverses it: the hero shrinks back into its own slot on the index, already restored around it, at the scroll position you left.

Read the full effect overview

The pairing lives in your markup. Give a card image and the hero it becomes the same key and the link morphs; a "Next project" thumbnail works the same way. Every other link on the site gets a quick crossfade, so you can switch it on site-wide. Underneath is a soft navigation engine for plain HTML, Webflow exports or a static WordPress theme: real links, real pages, with history, focus, announcements and prefetching handled.

Card to Hero Page Transition - GSAP animation effect preview

What's included

8 items
  • A true shared-element transition across a real page boundary: the card's image becomes the next page's hero, measured from the real layout of the arriving page
  • Portrait-to-wide shape change with the image re-cropped every frame, so nothing distorts, and the corner radius animating with it
  • The index recedes with a stagger that ripples out from the clicked card, and the arriving page's text settles in around the landed image
  • Back and Forward fly the image home into its card, with the index scroll position restored first so the slot is in view
  • Pairs declared in markup with one attribute; links without a matching image crossfade, so it is safe on every link of a site
  • Waits for the destination image to decode before flying, so the morph never lands on an empty box, and falls back to a normal page load if the page or image is too slow
  • Interruptible: a second click or Back before the new page arrives snaps the old page back and goes where you asked; after it arrives, the flight finishes fast and the next request follows
  • Start and end events for analytics and per-page scripts, prefetch on hover and touch, pause and play, and a single call that restores ordinary navigation

Perfect for

4 use cases
  • Studio and agency portfolios where a project tile should open into its case study without a jump cut
  • Shop category pages where a product card grows into the product page's main photograph
  • Editorial and journal indexes where an article thumbnail becomes the article's lead image
  • Webflow exports, static site generators and hand-built HTML sites that want the shared-element transitions frameworks offer, without a framework

How it works

1 section

Same-origin links are intercepted and the next page is fetched (usually already prefetched on hover), parsed, and its main container swapped in, with the title, description, history, focus and an announcement updated. When the clicked link holds an image with a pairing key and the new page has an image with the same key, a copy of the image is lifted into a fixed layer at its exact on-screen position. The new page goes in beneath the old one at the top of the document, its hero is measured in place while hidden, and GSAP's Flip plugin fits the flying copy to it by animating its size and position rather than its scale, so the clipped photograph re-crops instead of stretching. A GSAP timeline runs the receding grid, the fade of the old page and the arrival of the new text around that flight, and GSAP media queries shorten it on small screens and swaps it for a crossfade under reduced motion. History entries remember which image left each page, so Back flies the same one home. Without JavaScript, GSAP or a web server, every link is an ordinary link and every page is complete.

Plugins Flip
Difficulty Advanced
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo (7 pages, lowest score shown), from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid effect

Purchase to unlock the code.

Buying Card to Hero Page Transition opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£10 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £99, one payment.

Documentation

Quick Start

Every page of your site gets the same three things. Serve the site from a web server (http or https): the next page is fetched, so opened straight from disk every link stays an ordinary link.

1. In each page's <head>, before your stylesheet:

Code snippet omitted: it ships with the download.

2. Wrap the part of each page that changes in one container, and give each pair of images the same key:

Code snippet omitted: it ships with the download.

3. Before the closing </body> tag:

Code snippet omitted: it ships with the download.

That is all: the script starts itself. Links whose destination has an image with the same key morph; all other same-site links crossfade.

Using It With Your Own Design

What the engine needs from your pages.

  • One container per page matched by [data-cth]. Only that container is swapped; the title, meta description and <html lang> are updated from the fetched page and the rest of <head> is kept, so every page must share the same stylesheets and scripts. Scripts inside the container are not run on a soft navigation: start per-page code from the cth:end event.
  • Give the container an opaque background. During a transition the leaving page is pinned over the arriving one and fades out.
  • Do not put the container inside an element with a transform, filter, perspective or contain value: the leaving page and the flying image use position: fixed.

What a shared image needs. Put data-cth-key on the element that clips the picture (a wrapper with overflow: hidden, a border-radius and an <img> inside with object-fit: cover). Both ends can have any size, shape and radius: the flight animates the box's size and corners while the image re-crops, so a 4:5 card can become a 21:9 hero. For the cleanest hand-off, crop the same photograph at both ends with the same object-position (the demo sets it once per project with a --focus custom property). The destination may use a larger file or a srcset; it is faded in over the card's copy during the first part of the flight.

  • The key is matched exactly. One key per page per image; the first match wins.
  • Which image flies: the one inside the clicked link. For a link without one (a nav link back to the index, say), the first on-screen image on the current page whose key the next page also has: in the demo "All work" flies the hero back into its card. Back and Forward fly the image that left that page.
  • The destination must be on screen once the new page opens (at the top, or at the restored position on Back). If it is further down, that navigation crossfades instead.
  • Do not put data-cth-arrive on an ancestor of the destination image: it would move while being measured.

What arrives and what recedes. On the leaving page, the other blocks holding shared images (usually the other cards' links) drop and fade, staggered outwards from the clicked card, then the whole page fades. On the arriving page, elements marked data-cth-arrive rise into place as the image lands, and other shared-image blocks settle in around it. Without any data-cth-arrive, the new page simply appears beneath the fading old one.

What is only the demo's design. Norrell Studio, the copy, photographs, Mona Sans, the light index and dark project pages, the six-column grid and the phone list, the accent dot and the "Next project" tile are all replaceable.

Options

Attributes

Attribute On Default Description
data-cth the swap container required The element replaced on each page
data-cth-key a shared image or its clipping wrapper none Pairs an image with the one it becomes on another page
data-cth-arrive blocks on any page none Blocks that rise into place as a page arrives
data-cth-duration a link or the container 0.8 Flight time in seconds; a link beats the container
data-cth-ease a link or the container expo.out Any GSAP ease for the flight
data-no-transition a link, a wrapper, a container or <html> none Keeps those links ordinary
data-cth-manual <html> none Do not start automatically; call CardToHero.init(options)

CSS custom properties

Property Default Description
--cth-duration 0.8s Default flight time, read from the container (attributes override it)
--cth-ground #ececeb The demo's page ground; also the colour seen before a slow page arrives
--cth-accent #ff4d1f The demo's accent
--cth-flight-shadow none Optional shadow on the image while it is lifted; it eases away as it lands

On screens 700px wide or narrower the flight is capped at 0.7 seconds.

JavaScript options (with data-cth-manual, or call CardToHero.init() at any time to replace the running instance):

Option Default Description
container '[data-cth]' Selector for the swapped element
duration / ease 0.8 / 'expo.out' Defaults for the flight
compactDuration 0.7 Cap on narrow screens
prefetch true Fetch pages on hover, focus and touch (skipped with Save-Data)
cacheSize 8 Fetched pages kept in memory
timeout 8000 ms before a slow page is opened with a normal page load
decodeTimeout 2500 ms to wait for the destination image to decode before a normal page load
focus / announce true Move focus to the new h1 (or main) and read the new title
zIndex 60 Stacking order of the leaving page; the flying image sits 20 above

API

Code snippet omitted: it ships with the download.

Events on document, with event.detail:

  • cth:start: url, from (the leaving container), key, mode ('morph' or 'fade', as known at the click), direction ('forward' or 'back'), trigger (the link, 'popstate' or 'api').
  • cth:end: the same, plus container (the new page) and the final mode and key. Fires once focus has moved and the title has been announced.
  • cth:error: url and error, just before falling back to a normal page load.

History and interruptions. Each history entry records its scroll position and which image left it, so Back restores the index scroll before the hero flies home into its slot. Before the new page is in the document, the latest request wins: a click on another link, or Back, snaps the old page back in place and starts the new navigation at once. Once the new page is in, the flight finishes at triple speed and the latest request runs next. A second click on the same link is ignored. If the page fails to load, is not HTML, has no [data-cth] container, or its destination image cannot decode within decodeTimeout, the page opens with a normal load.

Accessibility

  • Links stay ordinary links: keyboard, middle-click, open in new tab and modified clicks all behave natively.
  • After each navigation focus moves to the new page's h1 (given tabindex="-1"), or main, without scrolling, and the new title is read in a polite live region. The leaving page is inert while it fades, and the flying image is aria-hidden.
  • Scrolling is held for the fraction of a second the image is in flight, so the page does not slide under it.
  • Under prefers-reduced-motion: reduce pages crossfade with no flight, travel or scale; focus, history and announcements are unchanged. A preference change mid-transition finishes it quickly.
  • Without JavaScript, when GSAP or Flip fails to load, or opened from file://, every page is complete and links load normally.

Dependencies

  • GSAP 3.13+ with the Flip plugin (free since 3.13; the demo pins 3.15.0). No smooth-scroll library.
  • A browser with fetch, DOMParser, AbortController and history.pushState: every current browser. Anything older keeps ordinary links.
  • A web server for soft navigation.

Teardown

CardToHero.revert() or window.gsapContext.revert() stops soft navigation and puts everything back: a transition in progress is cut short and the page the address bar shows is left in place, the flying image and pinned page are removed, in-flight fetches are aborted, listeners and the media queries are removed, the live region is deleted and history.scrollRestoration is restored.

Images and Credits

Images are baked WebP files in assets/img/ (a 900px card version and an 1800px hero version of each). assets/img-manifest.json records each source and size. Photographs from Unsplash and Pexels under their licences, cropped and resized: a white modernist villa, a dining room with an arched window, a stone country house across water, yellow pleated pendant lamps, a walnut sideboard, and a scaled metal facade.

Norrell Studio and its projects are fictional. Mona Sans is loaded from Google Fonts under the SIL Open Font License.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.