Shots in this scene
The FLIP Technique
In one line
FLIP moves an element that jumped to a new place in the layout back to where it was with transform, then lets it slide home. You measure First, change the page to get Last, Invert the difference, and Play.
Think of it like this
You move a heavy sofa across a room, but you want your friend, filming from the doorway, to see it glide. So you photograph the room first, move the sofa at once, photograph again, and then slide a copy of the sofa along the line between the two photos. The room is rearranged instantly. Only the picture glides.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- FLIP
- First, Last, Invert, Play: measure where things are, change the page, then animate each element from its old place to its new one.
- getBoundingClientRect()
- A method that returns an element's box (left, top, width, height) in viewport pixels, including any transform it has right now.
- Invert
- Pushing the element back to where it was with a transform, so the final layout looks like the old one.
- layout thrashing
- Alternating reads and writes of layout so the browser must recompute layout again and again.
- compositor
- The part of the browser that moves and blends already-painted layers, often off the main thread.
Step by step
01Feel it first
Use the stepper below. Each click goes one step: First (outlines at the old places), Last (outlines at the new ones), Invert (each card sits on its old outline, with its translate(dx, dy) shown), Play. Go through it twice.
02Run the hand-written version
In "FLIP by hand" press Reverse the cards. Card F sat at column 3, row 2 and now belongs at column 1, row 1. Its dx is 124 px and dy is 40 px (two columns of 56 + 6 px, one row of 34 + 6 px).
03Freeze it at the start
Press Pause, then Reverse. At 0 ms the first card in the grid is at matrix(1, 0, 0, 1, 124, 40): the Invert. At 300 ms it is at (39.1, 12.6). At 600 ms it is none. Every card is a green transform track.
04Switch the inverse to left and top
Tick invert with left/top and run it again. The picture is the same, but all six tracks turn red (layout). The page lays out on every frame.
05Scroll the container
In "FLIP in a scrolled list" press the button without the tick: the console says measured in the viewport: 8 of 8 rows animate. Tick measure from the scroller and press again: 2 of 8 rows animate.
Try it yourself
- 1
Read the Invert
In "FLIP by hand", pause, press Reverse, and click the first card's track. The graph falls from the inverted translate to
none. Change the grid torepeat(3, 80px)in the CSS and run again:dxbecomes 172 (two columns of 80 + 6). - 2
Slow the move to see the sliding
Set the speed to 0.1×, press Reverse, and step the first 5 frames. The cards cover most of their distance early, because the easing is
ease-out. - 3
Measure both ways in the list
Press the list button once without the tick, Reset, tick the box and press again. 8 rows animate against 2: the other six only appeared to move because the scroll jumped.
Code & diagrams
Try it: interactive
FLIP, one step at a time
Click through First, Last, Invert and Play. At Invert, read the translate(dx, dy) on a card.
Press Reverse. Then tick the box and press it again: the tracks go from green (transform) to red (left and top). Pause and step to read dx and dy.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
cards: 6The list starts scrolled to the bottom. The button swaps the last two rows and scrolls to the top. Run it with and without the tick and read the console.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
rows: 8Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget the Invert
In "FLIP by hand", change the frames to [{ transform: "none" }, { transform: "none" }].
Break #2
Read the Last box too early
Move the line grid.append(...) to after the cards.forEach(...) block, so the Last boxes are read before the change.
Myth vs fact
Myth
FLIP is a library.
Fact
It is a way of working with four steps you can write in ten lines. GSAP Flip, Motion's layout animations and auto-animate all do these steps for you.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
getBoundingClientRect()includes the transforms an element has at this moment. That makes FLIP safe to interrupt: if a new FLIP starts while one is playing, the First box is where the card is on screen right now, not where the last animation was going. - ▸
The transform you animate replaces the element's own
transformwhile it plays. If a card has its owntransform(a hover tilt), animate the individualtranslateproperty instead, or addcomposite: "add"to the options. - ▸
Layer promotion has a cost. Animating the transform of 200 elements at once can create 200 compositor layers. Skipping the elements that did not move is the cheapest saving.
- ▸
An element with
display: nonehas no box:getBoundingClientRect()returns all zeros, which would look like a huge move from the top-left corner. Checkel.getClientRects().length > 0before and after (the project'sflip()does) and treat the element as arriving or leaving instead. Right-to-left pages need no change: boxes andtranslateare in physical pixels, sodxjust has the other sign.
Remember this
- 1
The problem this solves: some changes cannot be animated directly. Reordering a list, filtering a grid, or moving an element to a different parent changes its place in the layout, and a browser cannot tween between two layouts. Animating
toporleftinstead is red on the timeline: it asks for layout on every frame (Phase 6 measured it). - 2
First, Last, Invert, Play. First: read each element's box with
getBoundingClientRect(). Last: make the change (the DOM changes, the browser lays it out, and the element is already where it will end up), then read the box again. Invert: the difference between the two boxes isdxanddy. Applytransform: translate(dx, dy)so the element looks as if it never moved. Play: animate the transform tonone. Paul Lewis named the technique in 2015. - 3
**Why only
transform.** The element's real layout is already final, so there is nothing to lay out while it plays. A transform on the compositor only moves pixels the browser has already painted. Tick "invert with left/top" in the first example: the same move now costs layout (red tracks), becauseleftandtopare layout inputs. - 4
Read everything, then write. Reading a box forces the browser to lay the page out. If you read one element, change it, then read the next, you force a layout for every element (layout thrashing). FLIP reads all the First boxes together, changes the page once, then reads all the Last boxes together. Two layouts in total, however many elements.
- 5
An element that did not move. Its
dxanddyare 0. Animating it fromtranslate(0, 0)tononewastes a track and may promote a layer for nothing, so skip anything whose delta is under half a pixel. Swap two cards in a list of 100 and only two animations should exist. - 6
An element inside a scrolled container.
getBoundingClientRect()is relative to the viewport, so when the change also scrolls the container, every row's box moves even though no row moved inside the content. Measure from the scroller's content instead:rect.top - scroller.top + scroller.scrollTop. The second example shows the difference as numbers.
Explain it without notes
Why is FLIP cheaper than animating left and top from the old position to the new one?
What goes wrong when you measure First and Last for a list inside a scrolled container, and how do you fix it?
Practice
Write a function flipOne(el, change) that animates a single element for 400 ms with ease-out.
Make the grid example skip elements that did not move. How many animations run if you swap the first two cards?
Trade-offs
- ↔
FLIP animates the picture of the move, not the layout. Content inside an element that changes size is distorted by the transform (Topic 10.2), and text can look soft while it is scaled.
Done when you can
You can name the four steps and what each one reads or writes
You can say why only transform animates
You can skip elements that did not move
You can measure correctly inside a scrolled container