added in v3.9.0
Flip
Quick Start
CDN Link
gsap.registerPlugin(Flip)
Minimal usage
// grab state
const state = Flip.getState(squares);
// Make DOM or styling changes
switchItUp();
// Animate from the initial state to the end state
Flip.from(state, {duration: 2, ease: "power1.inOut"});
Flip involves a whole different way of thinking about animation. Once you get it, you'll be wowed.
Flip plugin lets you seamlessly transition between two states even if there are sweeping changes to the structure of the DOM that would normally cause elements to jump.
Flip records the current position/size/rotation of your elements, you make whatever changes you want, and then Flip applies offsets to make them look like they never moved... Lastly FLIP animates the removal of those offsets! UI transitions become remarkably simple to code. Flip does all the heavy lifting.
What people are saying:
Experimenting more with GSAP's flip plugin and I'm blown away by how much this accelerates development! What a powerful tool!💪" - Manoela Ilic, Codrops
"HOW DOES IT KNOW WHAT TO DO? 🤯" - @georgedoescode
"I'm truly amazed at the power of GSAP and FLIP." - @Alex.Marti
"My 'Wow' of the week goes to the Flip Plugin. Star-struck. It's soooo smart!" - @PeHaa
Video​
Of course like all GreenSock tools, there's a rich API for finessing effects and getting exactly the look you want.
"FLIP" is an animation technique that stands for "First", "Last", "Invert", "Play" and was coined by Paul Lewis. Here's a demo of how it works:
loading...
Features​
- Nested transforms? No problem! Most FLIP libraries only calculate basic offsets assuming no transforms beyond x/y, so a scaled parent breaks things. Rotations certainly aren't allowed. GSAP's Flip plugin just works!
- Set
absolute: true
to make elements useposition: absolute
during the flip. This solves layout challenges with flexbox, grid, etc. You can even define a subset of targets, likeabsolute: ".box"
- One flip can handle multiple elements and even stagger them.
- Resize via width/height properties (default) or scaleX/scaleY (
scale: true
) - You get the full power of GSAP under the hood, so you can use any
ease
, define special properties likeonComplete
,onUpdate
,repeat
,yoyo
, and evenadd()
other animations with total control of timing, etc.
read more...
- Apply a CSS class during the flip with
toggleClass
. It'll be removed at the end of the flip. - Flip.fit() repositions/resizes one element to fit perfectly on top another (or even a previous state of the same element).
- Compensate for nested offsets. If a container element is getting flipped along with some of its children, set
nested: true
to prevent the offsets from compounding. - Smoothly handles interruptions.
- Flip one element to another; even have them cross-fade (
fade: true
). Just give them the samedata-flip-id
attribute to correlate them. onEnter
andonLeave
callbacks for when elements enter or leave (like if the flip senses adisplay: none
toggle and there's no matching target to swap), making it easy to elegantly animate on/off elements.- Batch multiple Flip animations so they don't step on each other's toes, like in a React app with multiple independent components that need to work together.
How does Flip work?​
There are typically 3 steps to a "FLIP" animation:
Get the current state​
// returns a state object containing data about the elements' current position/size/rotation in the viewport
const state = Flip.getState(".targets");This merely captures some data about the current state. Use selector text, an Element, an Array of Elements, or NodeList. Flip.getState() doesn't alter anything (unless there's an active flip animation affecting any of the targets in which case it will force it to completion to capture the final state accurately). By default, Flip only concerns itself with position, size, rotation, and skew. If you want your Flip animations to affect other CSS properties, you can define a configuration object with a comma-delimited list of
props
, like:// record some extra properties (optional)
const state = Flip.getState(".targets", { props: "backgroundColor,color" });Make your state changes​
Perform DOM edits, styling updates, add/remove classes, or whatever is necessary to get things in their final state. There's no need to do that through the plugin (unless you're batching). For example, we'll toggle a class:
// make state changes. We'll toggle a class, for example:
element.classList.toggle("full-screen");Call
Flip.from(state, options)
​Flip will look at the
state
object, compare the recorded positions/sizes to the current ones, immediately reposition/resize them to appear where they were in that previous state, and then animate the removal of those offsets. You can specify almost any standard tween special properties likeduration
,ease
,onComplete
, etc. Flip.from() returns a timeline that you canadd()
things to or control in any way:// animate from the previous state to the current one:
Flip.from(state, {
duration: 1,
ease: "power1.inOut",
absolute: true,
onComplete: myFunc,
});That's it!
Simple example​
loading...
Flex example​
loading...
Advanced example​
loading...
Config Object​
The Flip.from() options object (2nd parameter) can contain any of the following optional properties in addition to any standard tween properties like duration
, ease
, onComplete
, etc. as described here:
- Boolean | String | Array | NodeList | Element - specifies which of the targets should have
position: absolute
applied during the course of the FLIP animation. Iftrue
, all of the targets are affected, or use selector text like".box"
(or an Array/NodeList of Elements, or even a single Element) to specify a subset of the targets. This can solve layout challenges with flex and grid layouts, for example. If things aren't behaving in a seamless way, try settingabsolute: true
. Beware, thatposition: absolute
removes the elements from document flow, so things below can collapse. In that case, just define a subset that doesn't include the container element so it props the layout open. (added in 3.9.0) - Boolean - if
true
, any "leaving" Elements (ones passed to theonLeave()
) will be set toposition: absolute
during the flip animation. This can be very useful when you set elements todisplay: none
to hide them in the final state, but you want to animate them out (fade, scale, whatever). It's critical that they not affect layout but you still want them visible during the animation. (added in 3.9.0) - Boolean - by default, if the target element associated with a particular
data-flip-id
in the previous state is a different element than the one with the samedata-flip-id
in the end state, it will get swapped immediately but if you'd prefer that they cross-fade, setfade: true
. Again, this only applies when swapping elements. If the "swapping out" (leaving) element isdisplay: none
(CSS), obviously it won't be visible for fading but if you set the Flip toabsolute: true
, it will force the element to the previous display state during the flip so that it can cross-fade. The reasonabsolute: true
is necessary in this case is because otherwise the element would affect document flow and throw off the positioning of other elements but if it isposition: absolute
(CSS), it's removed from the document flow and won't contaminate positioning. - Boolean - if the Flip has any nested targets (like a parent and its child are both in the
targets
), setnested: true
to have Flip perform extra calculations to prevent those movements from compounding. A parent's movement affects its children, so if both are mapped to end up 200px from their original position and Flip moves them both 200px, the child would end up moving 400px unlessnested: true
is set. - Function - A callback that's called if/when a target either isn't found in the original
state
or it was not in the document flow in that original state (likedisplay: none
), but it IS in the document flow in the end state. Since there is no position/size data to compare to in the original state, it won't be included in the flip animation, but the callback receives an Array of the entering elements as a parameter so that you can animate them as you please (like fade them in). Any animation returned by this callback will get added to the flip timeline so that it gets forced to completion if a competing flip interrupts it. For example:onEnter: elements => gsap.fromTo(elements, {opacity: 0}, {opacity: 1})
- Function - A callback that's called if/when a target is in the original
state
but not the end state, or if it isn't in the document flow in the end state (likedisplay: none
). Since there is no position/size data to compare to in the end state, it won't be included in the flip animation, but the callback receives an Array of the leaving elements as a parameter so that you can animate them as you please (like fade them out). IMPORTANT: these elements won't be visible unless you also setabsolute: true
(otherwise, it'd throw off document flow). Ifabsolute: true
is set, it will forcedisplay
to whatever it was in the previous state and then revert it back at the end of the flip. Any animation returned by this callback will get added to the flip timeline so that it gets forced to completion if a competing flip interrupts it. For example:onLeave: elements => gsap.fromTo(elements, {opacity: 1}, {opacity: 0})
- String - a comma-delimited list of camelCased CSS properties that should be included in the flip animation beyond the standard positioning/size/rotation/skew ones. For example,
"backgroundColor,color"
. This will only work, however, if the props exist in thestate
object (first parameter) because otherwise there's no corresponding data to pull from. By default, Flip will use theprops
that were captured in the state with Flip.getState(targets, props), so it's very rare that you'd need to defineprops
in Flip.from(). It's only useful if you want to LIMIT them to a subset of the ones captured in the state. - Boolean - if
true
, Flip will remove any targets from the animation that match the previous state (position/size) in order to conserve resources. This requires a little more processing up-front, but it may improve performance during the animation when several get removed, plus it also makes staggering more intuitive since you may not want non-animating targets to be factored into the staggering. (added in 3.9.0) - Boolean - by default, Flip will affect the
width
andheight
CSS properties to alter the size, but if you'd rather scale the element instead (typically better performance), setscale: true
. - Boolean - if
true
, Flip will skip the extra calculations that would be necessary to accommodate rotation/scale/skew in determining positions. It's like telling Flip "I promise that there aren't any rotated/scaled/skewed containers for the Flipping elements" which makes things faster. In most cases, the performance difference isn't noticeable, but if you're flipping a lot of elements it can help keep things snappy. - Boolean | Number | Function -
if
true
, the elements will spin an extra 360 degrees during the flip animation which makes it look a little more fun. Or you can define a number of full rotations, including a negative number, so-1
would spin in the opposite direction once. If you provide a function, it will be called once for each target so that you can return whatever value you'd like for each individual element's spin. This allows you to, for example, have certain targets spin one direction, other elements spin another direction, or return 0 to not spin at all. Sample code: ...Flip.from(state, {
spin: (index, target) => {
if (target.classList.contains("clockwise")) {
return 1;
} else if (target.classList.contains("counter-clockwise")) {
return -1;
} else {
return 0;
}
},
}) - String | Element | Array | NodeList - by default, Flip will use the targets from the
state
object (first parameter), but you can specify a subset of those as either selector text (".class, #id"
), an Element, an Array of Elements, or a NodeList. If any of the targets provided is NOT found in thestate
object, it will be passed to theonEnter
and not included in the flip animation because there's no previous state from which to pull position/size data. - String - adds a CSS class to the targets while the flip animation is in progress. For example
"flipping"
. - Number - immediately sets the zIndex CSS property to this value for the entire course of the flip animation and then reverts at the end. This makes it easy to ensure that your flipping elements are on top of other elements during the animation, for example.
Property
Description
How do I flip between two different elements?​
Flip looks for a data-flip-id
attribute on every element it interacts with (via Flip.getState() or Flip.from(), etc.) and if one isn't found, Flip assigns an incremented one automatically ("auto-1", "auto-2", etc.). It lets you correlate targets (the target with the data-flip-id
of "5"
in the "from" state gets matched up with the target with a data-flip-id
of "5"
in the end state). The data-flip-id
can be any string, not just a number.
So if you want to flip between two different targets, make sure the data-flip-id attribute in the end state matches the one in the "from" state. When Flip sees that there are two with the same value in the from/end state, it will automatically figure out which one is disappearing (typically with display: none
) and base things off of that to "swap" the elements. If you want them to crossfade, simply set fade: true
, otherwise they'll immediately swap. And it is typically best to set absolute: true
so that when Flip alters the display
value, it doesn't affect the document flow.
loading...
Batching​
What if you need to create multiple coordinated Flip animations (perhaps in various React components)? They'd need to all .getState() BEFORE any of them make their changes to the DOM/styling because doing so could alter the position/size of the other elements. See the docs for Flip.batch() for details.
Caveats & Tips​
- Flip does not accommodate 3D transforms (like rotationX, rotationY, or z).
- It is strongly recommended that you use
box-sizing: border-box
on your elements to ensure accurate width/height calculations. - When
absolute: true
is set, remember that coordinates will be calculated based on the current viewport, so if the viewport size changes or the user scrolls DURING the flip, it may affect positioning (but once the flip is done and the offsets are removed, things will be where they should be). In other words, in-progress flipping isn't always responsive. - Set any transform-related values (x, y, scale, rotation, etc.) directly via GSAP whenever possible (instead of just in CSS classes or inline) because GSAP caches transform-related data to supercharge performance and maximize accuracy. To clear GSAP's cache on a particular element (which you'd never need to do if you're making all your changes via GSAP),
gsap.set(element, {clearProps: "transform"});
Deep Dive - Using a framework like Vue, React or Angular?
Beware that frameworks often DON'T render changes immediately, so you should wait until the render occurs before initiating the Flip.from(). Batching may be an excellent option because you can batch.getState(true)
and then perhaps a useLayoutEffect(() => batch.run(true), [...])
in React. Another hack would be to use requestAnimationFrame() to wait one tick: requestAnimationFrame(() => Flip.from(...));
When you Flip.getState(".your-class")
, it records position/size data for the elements with ".your-class" at that time, remembering those particular elements and their data-flip-id
attribute values. Then, if you Flip.from(yourState)
, and don't specify any targets
, it will default to using the elements that were captured in the getState() but your framework may have re-rendered entirely new element instances (even if they look the same), thus they won't animate because Flip doesn't know to look at those new elements. The original ones were completely removed from the DOM, hence the need to tell the Flip "use these new targets and search the state object for the IDs that match...". So make sure you define targets
like this:
// BAD
Flip.from(state, {
duration: 1,
});
// GOOD
Flip.from(state, {
targets: ".your-class", // <-- BINGO!
duration: 1,
});
And don't forget that you'll probably need to set a data-flip-id
on those elements to make sure Flip knows which target matches up with which one from the captured state.
Showcase & how-to demos​
Methods​
Flip.batch( id:String ) : FlipBatch | Coordinates the creation of multiple Flip animations in the properly sequenced set of steps to avoid cross-contamination. |
Flip.fit( targetToResize:String | Element, destinationTargetOrState:String | Element | FlipState, vars:Object ) ; | Repositions/resizes one element so that it appears to fit exactly into the same area as another element. Using the |
Flip.from( state:FlipState, vars:Object ) : Timeline | Immediately moves/resizes the targets to match the provided |
Flip.getState( targets:String | Element | Array, vars:Object ) : Object | Captures information about the current state of the |
Flip.isFlipping( target:String | Element ) : Boolean | Returns |
Flip.killFlipsOf( targets:String | Array | NodeList | Element, complete:boolean ) ; | Immediately kills the Flip animation associated with any of the targets provided. |
Flip.makeAbsolute( targets:String | Element | Array | NodeList | FlipState ) : Array | Sets all of the provided target elements to |
Flip.to( state:FlipState, vars:Object ) : Timeline | Identical to Flip.from() except inverted, so this would animate to the provided state (from the current one). |
FAQ​
How do I include Flip in my project?
See the installation page for all the options (CDN, NPM, download, etc.) where there's even an interactive helper that provides the necessary code. Easy peasy. Don't forget to register Flip like this in your project:
gsap.registerPlugin(Flip)
Is this included in the GSAP core?
Is this only for Club GSAP members?
No, it's available to everyone for free! But Club GSAP is pretty awesome...just sayin'.
It works fine during development, but suddenly stops working in the production build! What do I do?
Your build tool is probably dropping the plugin when tree shaking and you forgot to register Flip (which protects it from tree shaking). Just register the plugin like this:
gsap.registerPlugin(Flip)