{"course":{"slug":"motion-and-audio","title":"Motion, Sound and Music","url":"https://addaly.com/learn/motion-and-audio","level":"intermediate","track":"create"},"lesson":{"slug":"transition-as-explanation","title":"The transition is the explanation","url":"https://addaly.com/learn/motion-and-audio/transition-as-explanation","position":12,"of":72,"minutes":10,"answer":"A shared-element transition measures an element before and after, inverts the difference with a transform and plays it off, which is why it can only describe a relationship that genuinely exists between the two states.","keyIdea":"A shared-element transition measures an element before and after, inverts the difference with a transform and plays it off, which is why it can only describe a relationship that genuinely exists between the two states."},"body":{"format":"markdown","text":"## Two screens, or one screen changing\n\nWhen a user taps a thumbnail in a grid and lands on a detail page, there are two stories the interface can tell.\n\nThe first: this grid is gone, and here is a different page. The second: this item you tapped is the thing now filling your screen. The second story is nearly always the true one, and a transition is how you tell it.\n\nThe technique has a name in every framework (shared element transition, hero animation, container transform, view transition) and the same mechanism underneath. You identify one element that exists in both states, measure where it is in each, and animate between the two measurements while the rest of the interface cross-fades.\n\n## FLIP: the mechanism, in four steps\n\nThe standard implementation is called FLIP, from Paul Lewis, and it is worth understanding even if a framework does it for you, because when a shared-element transition goes wrong the fault is always in one of these four steps.\n\n1. **First.** Before anything changes, measure the element's position and size. In the browser this is `getBoundingClientRect()`.\n2. **Last.** Apply the final state (swap the DOM, navigate, change the layout) and measure again, immediately, before the browser paints.\n3. **Invert.** Compute the difference and apply a `transform` that puts the element visually back where it started. The element is now in its final position in the layout, but looks as though it never moved.\n4. **Play.** Remove the transform with a transition. The element travels from the inverted position to its real one.\n\nThe reason for this dance rather than animating width, height, top and left: steps 3 and 4 use only `transform`, which the browser can animate on the compositor without re-running layout. Animating the geometry properties directly means a full layout calculation on every frame, which is where dropped frames come from.\n\n## What makes one look wrong\n\nThree failures cover nearly all of them.\n\n> Figure: FLIP, and where a shared-element transition goes wrong. Steps: 1. First (measure position and size before anything changes); 2. Last (apply the final state, then measure again); 3. Invert (a transform that puts it back where it looked); 4. Play (remove the transform with a transition). Steps three and four use transform alone, which the browser animates on the compositor without re-running layout. Animating top, left, width and height directly forces a full layout calculation on every frame, and that is where the dropped frames come from. When one of these transitions looks wrong, the fault is in one of these four steps.\n\n**The aspect ratio changes during the move.** A 1:1 thumbnail becoming a 16:9 header will squash visibly if you animate width and height independently. The fix is to animate a uniform scale and crop, or to cross-fade between two correctly-cropped images at the midpoint where the distortion is least visible. Most frameworks that do this well are cross-fading and hoping you do not look.\n\n**The text comes along for the ride.** Scaling a container scales the type inside it, so a 14px caption becomes a blurry 32px caption mid-flight and then snaps back. The standard fix is a counter-scale: scale the child by the inverse of the parent's scale each frame, so the text stays at its true size throughout.\n\n**Nothing corresponds.** If the two screens share no element, a shared-element transition has to invent one, and the result is a shape morphing into an unrelated shape. This reads as a special effect rather than an explanation. A cross-fade is the honest answer.\n\n## The rule underneath\n\nThe transition should describe a relationship that is already true in your data model.\n\n- Tapping an item in a list and seeing that item expand: the item is the same object. Shared element is correct.\n- Moving from step 2 to step 3 of a form: these are positions in a sequence. A horizontal slide is correct, and the direction should reverse when going back.\n- Switching between two unrelated top-level tabs: there is no journey. A cross-fade, or nothing, is correct. Sliding implies the tabs sit side by side in a space, which is a claim you probably do not want to make, and which breaks the moment somebody navigates to the fourth tab from the first.\n\nWhen a transition is hard to design, it is usually because you are trying to describe a relationship that does not exist.\n\n## Doing it without a framework\n\nThe browser now has `document.startViewTransition()`, which handles the capture-and-interpolate work natively for same-document changes and, with the multi-page variant, across navigations. You tag the element in both states with the same `view-transition-name` and the browser does the FLIP for you. It degrades to an instant change in browsers that do not support it, which is the correct fallback.\n\nFor everything else, FLIP in about thirty lines of plain JavaScript covers it, no library required. GSAP's Flip plugin and Motion's layout animations are convenience on top of the same four steps.\n\n## The free path\n\n- **View Transitions API**: built into the browser, nothing to install.\n- **Motion** (the open-source successor to Framer Motion): free, and its `layout` prop is FLIP with the measurement handled.\n- **GSAP**: now free for all use including commercial, Flip plugin included.\n- **Penpot**: open-source design tool, enough prototyping to test a transition before building it.\n\n## The limitation\n\nA shared-element transition costs frames, and it costs them at the worst moment: while a new screen is mounting and probably fetching data. On a low-end device, the transition and the render compete, and the result is a janky animation followed by a late-arriving screen: worse than no transition at all.\n\nThe defensible rule is to make the transition cancellable and to skip it under load. If the element's destination is not measurable within one frame, do not animate; just change. A transition is an explanation, and an explanation delivered badly is worse than none."},"check":{"question":"In a FLIP transition, why is the element moved back to its starting position with a transform in step three rather than by animating its top and left values directly?","options":[{"text":"Because transform is applied after layout, so the browser can run the animation on the compositor without recalculating layout on every frame.","correct":true},{"text":"Because top and left are only valid on absolutely positioned elements, and a shared element is usually in normal document flow where those properties are ignored entirely.","correct":false,"tempting":"True for static positioning but irrelevant: the technique works on absolutely positioned elements too, and the reason is cost, not validity."},{"text":"Because transform interpolates in a different colour space from the geometry properties, which is what keeps the movement visually smooth through the middle of the transition.","correct":false,"tempting":"Confuses interpolation of colour with interpolation of geometry; colour spaces have nothing to do with position."},{"text":"Because the transform is what gets captured by getBoundingClientRect in step two, so using top and left would mean the measurement taken in the Last step returns the pre-change position and the whole calculation inverts itself.","correct":false,"tempting":"Misreads the measurement step: getBoundingClientRect reports the final rendered box regardless of which properties produced it."}]},"prev":{"slug":"how-long-should-it-take","title":"How long an animation should take","url":"https://addaly.com/learn/motion-and-audio/how-long-should-it-take"},"next":{"slug":"stagger-and-orchestration","title":"Staggering without making people wait","url":"https://addaly.com/learn/motion-and-audio/stagger-and-orchestration"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The transition is the explanation', in Motion, Sound and Music. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/motion-and-audio/transition-as-explanation (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The transition is the explanation","courseTitle":"Motion, Sound and Music","module":{"index":2,"slug":"motion-that-carries-meaning","title":"Motion that carries meaning"},"answer":null,"keyIdea":"A shared-element transition measures an element before and after, inverts the difference with a transform and plays it off, which is why it can only describe a relationship that genuinely exists between the two states.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"flow","steps":[{"label":"First","detail":"measure position and size before anything changes"},{"label":"Last","detail":"apply the final state, then measure again"},{"label":"Invert","detail":"a transform that puts it back where it looked"},{"label":"Play","detail":"remove the transform with a transition"}],"title":"FLIP, and where a shared-element transition goes wrong","caption":"Steps three and four use transform alone, which the browser animates on the compositor without re-running layout. Animating top, left, width and height directly forces a full layout calculation on every frame, and that is where the dropped frames come from. When one of these transitions looks wrong, the fault is in one of these four steps."},"alt":"Figure: FLIP, and where a shared-element transition goes wrong. Steps: 1. First (measure position and size before anything changes); 2. Last (apply the final state, then measure again); 3. Invert (a transform that puts it back where it looked); 4. Play (remove the transform with a transition). Steps three and four use transform alone, which the browser animates on the compositor without re-running layout. Animating top, left, width and height directly forces a full layout calculation on every frame, and that is where the dropped frames come from. When one of these transitions looks wrong, the fault is in one of these four steps."}},"blocks":[{"t":"h2","id":"two-screens-or-one-screen-changing","runs":[{"text":"Two screens, or one screen changing"}]},{"t":"p","runs":[{"text":"When a user taps a thumbnail in a grid and lands on a detail page, there are two stories the interface can tell."}]},{"t":"p","runs":[{"text":"The first: this grid is gone, and here is a different page. The second: this item you tapped is the thing now filling your screen. The second story is nearly always the true one, and a transition is how you tell it."}]},{"t":"p","runs":[{"text":"The technique has a name in every framework (shared element transition, hero animation, container transform, view transition) and the same mechanism underneath. You identify one element that exists in both states, measure where it is in each, and animate between the two measurements while the rest of the interface cross-fades."}]},{"t":"h2","id":"flip-the-mechanism-in-four-steps","runs":[{"text":"FLIP: the mechanism, in four steps"}]},{"t":"p","runs":[{"text":"The standard implementation is called FLIP, from Paul Lewis, and it is worth understanding even if a framework does it for you, because when a shared-element transition goes wrong the fault is always in one of these four steps."}]},{"t":"ol","start":1,"items":[[{"text":"First.","b":true},{"text":" Before anything changes, measure the element’s position and size. In the browser this is "},{"text":"getBoundingClientRect()","code":true},{"text":"."}],[{"text":"Last.","b":true},{"text":" Apply the final state (swap the DOM, navigate, change the layout) and measure again, immediately, before the browser paints."}],[{"text":"Invert.","b":true},{"text":" Compute the difference and apply a "},{"text":"transform","code":true},{"text":" that puts the element visually back where it started. The element is now in its final position in the layout, but looks as though it never moved."}],[{"text":"Play.","b":true},{"text":" Remove the transform with a transition. The element travels from the inverted position to its real one."}]]},{"t":"p","runs":[{"text":"The reason for this dance rather than animating width, height, top and left: steps 3 and 4 use only "},{"text":"transform","code":true},{"text":", which the browser can animate on the compositor without re-running layout. Animating the geometry properties directly means a full layout calculation on every frame, which is where dropped frames come from."}]},{"t":"h2","id":"what-makes-one-look-wrong","runs":[{"text":"What makes one look wrong"}]},{"t":"p","runs":[{"text":"Three failures cover nearly all of them."}]},{"t":"p","runs":[{"text":"The aspect ratio changes during the move.","b":true},{"text":" A 1:1 thumbnail becoming a 16:9 header will squash visibly if you animate width and height independently. The fix is to animate a uniform scale and crop, or to cross-fade between two correctly-cropped images at the midpoint where the distortion is least visible. Most frameworks that do this well are cross-fading and hoping you do not look."}]},{"t":"p","runs":[{"text":"The text comes along for the ride.","b":true},{"text":" Scaling a container scales the type inside it, so a 14px caption becomes a blurry 32px caption mid-flight and then snaps back. The standard fix is a counter-scale: scale the child by the inverse of the parent’s scale each frame, so the text stays at its true size throughout."}]},{"t":"p","runs":[{"text":"Nothing corresponds.","b":true},{"text":" If the two screens share no element, a shared-element transition has to invent one, and the result is a shape morphing into an unrelated shape. This reads as a special effect rather than an explanation. A cross-fade is the honest answer."}]},{"t":"h2","id":"the-rule-underneath","runs":[{"text":"The rule underneath"}]},{"t":"p","runs":[{"text":"The transition should describe a relationship that is already true in your data model."}]},{"t":"ul","items":[[{"text":"Tapping an item in a list and seeing that item expand: the item is the same object. Shared element is correct."}],[{"text":"Moving from step 2 to step 3 of a form: these are positions in a sequence. A horizontal slide is correct, and the direction should reverse when going back."}],[{"text":"Switching between two unrelated top-level tabs: there is no journey. A cross-fade, or nothing, is correct. Sliding implies the tabs sit side by side in a space, which is a claim you probably do not want to make, and which breaks the moment somebody navigates to the fourth tab from the first."}]]},{"t":"p","runs":[{"text":"When a transition is hard to design, it is usually because you are trying to describe a relationship that does not exist."}]},{"t":"h2","id":"doing-it-without-a-framework","runs":[{"text":"Doing it without a framework"}]},{"t":"p","runs":[{"text":"The browser now has "},{"text":"document.startViewTransition()","code":true},{"text":", which handles the capture-and-interpolate work natively for same-document changes and, with the multi-page variant, across navigations. You tag the element in both states with the same "},{"text":"view-transition-name","code":true},{"text":" and the browser does the FLIP for you. It degrades to an instant change in browsers that do not support it, which is the correct fallback."}]},{"t":"p","runs":[{"text":"For everything else, FLIP in about thirty lines of plain JavaScript covers it, no library required. GSAP’s Flip plugin and Motion’s layout animations are convenience on top of the same four steps."}]},{"t":"h2","id":"the-free-path","runs":[{"text":"The free path"}],"role":"free"},{"t":"ul","items":[[{"text":"View Transitions API","b":true},{"text":": built into the browser, nothing to install."}],[{"text":"Motion","b":true},{"text":" (the open-source successor to Framer Motion): free, and its "},{"text":"layout","code":true},{"text":" prop is FLIP with the measurement handled."}],[{"text":"GSAP","b":true},{"text":": now free for all use including commercial, Flip plugin included."}],[{"text":"Penpot","b":true},{"text":": open-source design tool, enough prototyping to test a transition before building it."}]]},{"t":"h2","id":"the-limitation","runs":[{"text":"The limitation"}],"role":"limit"},{"t":"p","runs":[{"text":"A shared-element transition costs frames, and it costs them at the worst moment: while a new screen is mounting and probably fetching data. On a low-end device, the transition and the render compete, and the result is a janky animation followed by a late-arriving screen: worse than no transition at all."}]},{"t":"p","runs":[{"text":"The defensible rule is to make the transition cancellable and to skip it under load. If the element’s destination is not measurable within one frame, do not animate; just change. A transition is an explanation, and an explanation delivered badly is worse than none."}]}],"check":{"question":[{"text":"In a FLIP transition, why is the element moved back to its starting position with a transform in step three rather than by animating its top and left values directly?"}],"options":[{"text":[{"text":"Because transform is applied after layout, so the browser can run the animation on the compositor without recalculating layout on every frame."}],"correct":true,"tempting":null},{"text":[{"text":"Because top and left are only valid on absolutely positioned elements, and a shared element is usually in normal document flow where those properties are ignored entirely."}],"correct":false,"tempting":[{"text":"True for static positioning but irrelevant: the technique works on absolutely positioned elements too, and the reason is cost, not validity."}]},{"text":[{"text":"Because transform interpolates in a different colour space from the geometry properties, which is what keeps the movement visually smooth through the middle of the transition."}],"correct":false,"tempting":[{"text":"Confuses interpolation of colour with interpolation of geometry; colour spaces have nothing to do with position."}]},{"text":[{"text":"Because the transform is what gets captured by getBoundingClientRect in step two, so using top and left would mean the measurement taken in the Last step returns the pre-change position and the whole calculation inverts itself."}],"correct":false,"tempting":[{"text":"Misreads the measurement step: getBoundingClientRect reports the final rendered box regardless of which properties produced it."}]}],"order":[2,1,3,0],"why":null},"prev":{"slug":"how-long-should-it-take","title":"How long an animation should take","minutes":9},"next":{"slug":"stagger-and-orchestration","title":"Staggering without making people wait","minutes":9}}}