@extends('docs.detail', [ 'meta_title' => 'Using GSAP Flip during a Barba transition', 'meta_description' => 'Using GSAP\'s Flip Plugin to animate an element from one page to another using Barbajs', ]) @section('docs')
Used technologies:
If we are resetting the current scroll position back to top and animate to a smaller/larger next container Flip may be a problem.
Because Flip also remembers the current scroll position while animating this needs to be reset after a Barba transition.
The solution for this is to temporarily move the selected element outside the Barba containers and mimicking the same behaviour.
By doing so can do all container animations we'd like while maintaining the Flip element's scroll position.
We want to animate a picture/element to the same element in the next page.
For example, my work-archive to work-detail animation, seen here.
By using Barba and GSAP ScrollSmoother the main content needs to be inside a `#smooth-wrapper` and a `#smooth-content` wrapper.
We will move the Flip element inside the `#smooth-wrapper` element and mimick the current state.
First we'll clone the flip element inside the current DOM to make sure the page won't shift (layout shift).
Afterwards we move the Flip element outside the Barba container and immediately set the same state as the original one.
It is very important to make the Flip element absolute and Flip it without a duration.
This makes sure to make an absolute copy in the same position but outside the Barba lifecycle.
// get the current Flip element from the clicked trigger
var myflip = data.trigger.querySelector('.flippingawesome');
// save a copy so the current DOM doesn't move! CLS..
var myflipcopy = myflip.cloneNode(true);
data.trigger.appendChild(myflipcopy);
// get current Flip state and move it outside Barba
myflipstate = Flip.getState(myflip, {simple: true});
document.querySelector('#smooth-wrapper').appendChild(myflip);
Flip.to(myflipstate, {
absolute: true,
duration: 0,
});
Basically what happens now is that we have 2 the same elements positioned and styled the same way, one inside the current Barba container and the flip element outside the Barba containers so it won't be tempered with.
Now the Flip element is set outside the Barba containers we can safely remove the current container and start our animation. See a basic setup here, with this being the most important part:
data.current.container.remove();
You'll now see that the Flip element will stay in place (perhaps with the right z-index CSS) and not move while you transition to the next page.
First you need to create a defined slot of where the element should be placed. This is for Flip to know the correct sizing and spacing.
In my case I have the exact same element but with some different CSS classes and markup:
After knowing where to place the Flip element we can move and animate it there.
Also I'll remove the original placeholder so that element won't be animated. This element still is used when reloading or animating to the page without Flip animation.
Flip.fit(myflip, data.next.container.querySelector('.fliphere'), { // get the next placeholder here
scale: true, // scale so also the width/height will be adjusted to fit
duration: 0.9,
onComplete: function() {
// after the animation move it here so it doesnt stay absolute/fixed outside the containers
data.next.container.querySelector('.fliphere').prepend(myflip);
},
});
data.next.container.querySelector('.fliphere img').remove(); // remove the original one to hide that animation
Putting it all together:
import barba from '@barba/core';
import { Flip } from "./gsap.js";
var myflip;
var myflipstate;
barba.init({
...
views: [{
namespace: 'work-detail',
beforeEnter(data) {
// get the current Flip element from the clicked trigger
var myflip = data.trigger.querySelector('.flippingawesome');
// save a copy so the current DOM doesn't move! CLS..
var myflipcopy = myflip.cloneNode(true);
data.trigger.appendChild(myflipcopy);
// get current Flip state and move it outside Barba
myflipstate = Flip.getState(myflip, {simple: true});
document.querySelector('#smooth-wrapper').appendChild(myflip);
Flip.to(myflipstate, {
absolute: true,
duration: 0,
});
},
afterEnter(data) {
Flip.fit(myflip, data.next.container.querySelector('.fliphere'), {
scale: true,
duration: 0.9,
ease: "power2.inOut",
onComplete: function() {
// after the animation move it here so it doesnt stay absolute/fixed outside the containers
data.next.container.querySelector('.fliphere').prepend(myflip);
},
});
data.next.container.querySelector('.fliphere img').remove(); // remove the original one to hide that animation
}
}]
});
@endsection