@Eunomiac Sooo I finally got around to

@Eunomiac Sooo I finally got around to playing with GSAP, and it looks incredible. I'm running into one issue though that I can't find a clear answer for, perhaps you know! TL;DR is it possible to crossfade an App re-render? I have basically all my (AppV2) apps set-up using Handlebars rendering; so whenever the app re-renders the DOM is fully replaced. I looked into using Flip but couldn't get that to do much of anything (as it appears to mostly be 'positioning' based, so f.e. if only text or only images change, but not the position of their elements, I can't get it to cross-fade that transition). I tried creating a cloned element set to absolute positioning over the current app -> re-render the app -> fade out the old app while fading in the new one, but this "stutters" (primarily due to images, see attached example pre vs post appendChild call of the cloned dom element) . Don't really have any other ideas to try and my google searches are failing me, so wonder if you have any ideas, or how you handle this in your use of gsap, or; if this is an issue you never run into because what I'm trying to do is silly and there's a much easier way to do stuff ^^;
No description
No description
94 Replies
Eon
EonOPโ€ข5d ago
Opening up a thread to answer to the replies... @Ethaks
Flip should work across handlebars renders as well, although you might need to give GSAP a little support there (including getting the state pre-render and applying it on render, possibly using flip ids to track things).
Yeah so what I was effectively doing was :
MyAppV2App extends..... {
rerender() {
const foo = this.element.querySelector(".foo");
this.fooState = Flip.getState(foo);
this.render({parts: ["foo"]});
}

async _onRender(context, options) {
await super._onRender(context, options);

Flip.from(this.fooState, {
duration: 1.5,
fade: true
});
}
}
MyAppV2App extends..... {
rerender() {
const foo = this.element.querySelector(".foo");
this.fooState = Flip.getState(foo);
this.render({parts: ["foo"]});
}

async _onRender(context, options) {
await super._onRender(context, options);

Flip.from(this.fooState, {
duration: 1.5,
fade: true
});
}
}
And I had set data-flip-id="foo" in the template. @joaquinp98
exactly which apps do you want to crossfade? or do you wanna crossfade a element on the app?
I've tried a single element and a PART, neither with any success.
Ethaks
Ethaksโ€ข5d ago
Without knowing all the details, some of the knobs closest to hand usually are the selectors used, as well as some of the options like absolute, absoluteOnLeave, and nested.
Eon
EonOPโ€ข5d ago
But in theory it should be able to do this? cause as far as I could see the Flip was doing absolutely nothing; so I stopped trying to play around with it.
Ethaks
Ethaksโ€ข5d ago
While I am far from a GSAP/Flip guru, I'd guess yes. I mostly use Flip to animate e.g. items in item lists so that e.g. toggling between alphabetical and manual sort mode animates etc. But that does mean that such animations are run across hbs renders, which works mostly fine on my end. Due to my own missing experience with GSAP it took quite a bit of throwing paint at the wall though ๐Ÿ˜…
Eon
EonOPโ€ข5d ago
I've got a "Token Dashboard" that presents different options depending on the token selected which I tinkered onto the Hotbar (macro bar). I'm trying to animate it cross-fading from the old token's state to the new token's whenever you select a new token.
joaquinp98
joaquinp98โ€ข5d ago
PARTs at the end of the day are still only elements when the app is rendered
Eon
EonOPโ€ข5d ago
yup.
joaquinp98
joaquinp98โ€ข5d ago
my advice is to perform the entire animation before re-rendering. some like Click on Button -> Element-1 fade out -> Element-2 fade-in ->re-render the app
Eon
EonOPโ€ข5d ago
I mean effectively to be able to do that, I'd have to render the app twice, not? It'd be Click Button -> Current app fades out -> manually prepare context & render the template HTML -> render this manually prepared version in -> fade it in -> re-render using 'natural' AppV2 render flow. Which... would be very counter productive. Tried the above with absolute & nested on true but still no animation at all. Despite the duration of 1.5 it seems to also just instantly finish
joaquinp98
joaquinp98โ€ข5d ago
actually, you only have to render the part with the context, nothing more complex is needed and this is done in one line
Eon
EonOPโ€ข5d ago
You have any example? Cause I don't think I'm following what you're putting down Ah I might've figured it out, Flip.from seems to have been missing targets.
joaquinp98
joaquinp98โ€ข5d ago
I don't know exactly how Flip works but I could use something like, but it's probably better to use a timeline.
const oldEl = this.element.querySelector(".foo");
//if the element its a part should render that part
const newEl = renderTemplate(this.constructor.PARTS.foo.template, context);
gsap.to(oldEl, {
//Set opacity 0 on old element in 0.5s
duration: 0.5,
opacity: 0,
//---
onComplete: () => {
const parent = oldEl.parentNode;
if (parent) {
parent.replaceChild(newEl, oldEl);
// Set the new element opacity to 0 to init
gsap.set(newEl, { opacity: 0 });
// Set the new element opacity to 1 for the fade-in animation
gsap.to(newEl, {
duration: 0.5,
opacity: 1
});
//make the re-render
this.render()
}
}
});
const oldEl = this.element.querySelector(".foo");
//if the element its a part should render that part
const newEl = renderTemplate(this.constructor.PARTS.foo.template, context);
gsap.to(oldEl, {
//Set opacity 0 on old element in 0.5s
duration: 0.5,
opacity: 0,
//---
onComplete: () => {
const parent = oldEl.parentNode;
if (parent) {
parent.replaceChild(newEl, oldEl);
// Set the new element opacity to 0 to init
gsap.set(newEl, { opacity: 0 });
// Set the new element opacity to 1 for the fade-in animation
gsap.to(newEl, {
duration: 0.5,
opacity: 1
});
//make the re-render
this.render()
}
}
});
this is just a crude example I repeat it would be better to use timeline instead of doing it this way, but I don't remember well how to do it, I should review it. oh i find a example what use Flip....
const oldEl = ...;
const newEl = ...;

const state = Flip.getState(oldEl);

oldEl.parentNode.replaceChild(newEl, oldEl);

gsap.set(newEl, { opacity: 0 });

Flip.from(state, {
duration: 0.5,
fade: true,
onEnter: (el) => {
gsap.to(el, { opacity: 1, duration: 0.5 });
},
onLeave: (el) => {
gsap.to(el, { opacity: 0, duration: 0.5 });
}
});
const oldEl = ...;
const newEl = ...;

const state = Flip.getState(oldEl);

oldEl.parentNode.replaceChild(newEl, oldEl);

gsap.set(newEl, { opacity: 0 });

Flip.from(state, {
duration: 0.5,
fade: true,
onEnter: (el) => {
gsap.to(el, { opacity: 1, duration: 0.5 });
},
onLeave: (el) => {
gsap.to(el, { opacity: 0, duration: 0.5 });
}
});
Eon
EonOPโ€ข5d ago
Yeah but in this example effectively what is being written is const newEl = this.render() minus the "replace current dom" part, then we animate said transfer, and then we render again and just replace the dom with an exact copy of itself...?
joaquinp98
joaquinp98โ€ข5d ago
use this.render is not the same as renderTemplate the first one involves updating the listener, calling the hooks, performing all the preRender, onRender, replace the app entirely
Eon
EonOPโ€ข5d ago
Yes but to re-render a part to the proper state I'm gonna need to do all of those things too? I mean perhaps not activating listeners. But everything else I do.
joaquinp98
joaquinp98โ€ข5d ago
so you still need to store a variable that keeps between re-renders the current state so that when you re-render it stays as is but that is not done with animation
Eon
EonOPโ€ข5d ago
What I've got now that actually animates something is:
MyAppV2App extends..... {
rerender() {
const foo = this.element.querySelector(".foo");
this.fooState = Flip.getState(foo);
this.render({parts: ["foo"]});
}

async _onRender(context, options) {
await super._onRender(context, options);

Flip.from(this.fooState, {
duration: 1.5,
fade: true,
// Added properties:
absolute: true,
nested: true,
targets: ".foo"
});
}
}
MyAppV2App extends..... {
rerender() {
const foo = this.element.querySelector(".foo");
this.fooState = Flip.getState(foo);
this.render({parts: ["foo"]});
}

async _onRender(context, options) {
await super._onRender(context, options);

Flip.from(this.fooState, {
duration: 1.5,
fade: true,
// Added properties:
absolute: true,
nested: true,
targets: ".foo"
});
}
}
Haven't tested if absolute/nested are necessary, the only problem with this is that while it fades in the new element; the old one isn't faded out, presumably since the Handlebars renderer has already removed them from the dom
joaquinp98
joaquinp98โ€ข5d ago
exactly how the animation is I don't know much about Flip, if I had to do it, I would use timeline. but I don't understand why to trig the animation in the _onRender I don't understand the existence of the rerender in this example either
Eon
EonOPโ€ข5d ago
Where else would I?
joaquinp98
joaquinp98โ€ข5d ago
I repeat, I do not understand what animation you are trying to achieve. for example let's say you want to replace one image with another when a user clicks on a button then trigger the animation when the user clicks on the button. I assume that the animation is a reaction to an input from the user. or do you want to set an initial animation in the app?
Eon
EonOPโ€ข5d ago
So I explained above that my goal is that this is a "Token Dashboard". Whenever you switch control of a Token the application re-renders to update its information for the new token. So in this case the 'selecting of a Token' is the equivalent of your 'pressing a button'. What I want to do is a crossfade from the old state (Token A's Dashbaord) to the new state (Token B's Dashboard). Both with Flip.from as well as just using a timeline, I can in the _onRender get the element to 'fade in'. But I can't get the crossfade to work.
joaquinp98
joaquinp98โ€ข5d ago
then I would create a method called: โ€œonChangeTokenโ€ and call that method when changing tokens.
Eon
EonOPโ€ข5d ago
Pressumably because the AppV2 render call replaces the dom. The token interactivity is sorted. The "button" works. the cross-fade animation doesn't. it only fades in; not out
joaquinp98
joaquinp98โ€ข5d ago
the rerender method is the method you call when another token is clicked?
Eon
EonOPโ€ข5d ago
Yes.
joaquinp98
joaquinp98โ€ข5d ago
so i do this
async rerender() {
const foo = this.element.querySelector(".foo");

//perform the animacion using Flip or Timeline,etc
await this.myMethosForMakeTheAnimation();

this.fooState = Flip.getState(foo);//save the new state
await this.render({parts: ["foo"]});//make the actual render
}
async rerender() {
const foo = this.element.querySelector(".foo");

//perform the animacion using Flip or Timeline,etc
await this.myMethosForMakeTheAnimation();

this.fooState = Flip.getState(foo);//save the new state
await this.render({parts: ["foo"]});//make the actual render
}
you will probably use the currentState somewhere in the rendering cycle so that the app has the correct state.
Eon
EonOPโ€ข5d ago
and your myMethosForMakeTheAnimation would require calling _prepare(Part)Context and the like to prepare the context to feed to renderTemplate to get the "new" state as part of your animation before you call this.render, so you're effectively calling this.render twice?
joaquinp98
joaquinp98โ€ข5d ago
the render method of an app is much more complex and the context can be obtained in a simple way think that when you call renderTemplate it is actually rendering a string without listeners, without actually existing in memory. and avoids the hassle of dealing with the entire rendering cycle to fix that.
Eon
EonOPโ€ข5d ago
I can't get this approach to function at all FYI.
joaquinp98
joaquinp98โ€ข5d ago
mmm... well the example I found it on a forum page so it may be terribly outdated or I may have made a mistake when adapting the example should do some serious testing with Flip
Eon
EonOPโ€ข5d ago
This 'works' but isn't a cross-fade. if I change it from onComplete to onStart I get the same issue where it only fades in, not out.
joaquinp98
joaquinp98โ€ข5d ago
mmm... I will see if later I can make a simple example in foundry.
Zhell
Zhellโ€ข5d ago
AppV2 has two methods for sync'ing rendered PARTS, mainly used to store input focus and scroll positions. I've used it successfully to animate health bars and fade in and out various elements across re-renders.
Eon
EonOPโ€ข5d ago
So would you imagine then to take the flip state in _preSyncPartState, and then in _syncPartState you run the Flip.from? cause I imagine that'd just result in the whole "the new element fades in but the old one doesn't fade out" issue.
Zhell
Zhellโ€ข5d ago
I would fade out -> submit (causes re-render) -> fade in on render But I did not look too closely at the sheet in question here. ๐Ÿ™‚
Eon
EonOPโ€ข5d ago
Ah see that is easy, but the issue is I've been trying to get it to cross-fade.
Zhell
Zhellโ€ข5d ago
I'll scroll up and get a better idea but what does cross-fade mean in this context?
Eon
EonOPโ€ข5d ago
The fade in & out occur simultaneously instead of staggered
Zhell
Zhellโ€ข5d ago
Ah, so for example two sheets and the old fades out, the new fades in, all at the same time
Eon
EonOPโ€ข5d ago
Yup. I believe changing token art these days does the same thing Yes, it does.
Zhell
Zhellโ€ข5d ago
I think that is PIXI, not animated html tho.
Eon
EonOPโ€ข5d ago
Oh yeah no correct; I more so meant as a visual example of what I'm trying to achieve So we're on the same page x)
Zhell
Zhellโ€ข5d ago
I wonder if overriding HandlebarsApplicationMixin#_replaceHTML is helpful
Eon
EonOPโ€ข5d ago
I thought about it but I don't see how it'd end up being any different, I'm starting to believe that cross-fade is just not a possibility within HTML + GSAP. At the very least; not while replacing DOM elements.
Zhell
Zhellโ€ข5d ago
Yeah I'm not much help, sorry. My only ideas would be to, somehow, cause the replaced HTML element to fade out in its current position instead of just being fully replaced (in _replaceHTML), or duplicate it, re-insert it, and manually fade it out and remove it in _syncPartState.
Eon
EonOPโ€ข5d ago
So far the thing that works best is:
class MyAppV2App extends .... {

rerender(options) {
if(this.element) {
const current = this.element.cloneNode(true);
gsap.set(currentState, {
position: "absolute",
top: element.offsetTop,
left: element.offsetLeft,
width: element.offsetWidth,
height: element.offsetHeight,
pointerEvents: "none",
});

this.element.appendChild(currentState);
this.oldState = currentState;
}
return this.render(options);
}

async _onRender(context, options) {
await super._onRender(context, options);
if(this.element && this.oldState) {
const timeline = gsap.timeline();
tl.from(this.element, {opacity: 0, duration: 0.5});
tl.to(this.oldState, {
opacity: 0,
duration: 0.5,
onComplete: () => {
this.oldState?.remove();
this.oldState = null;
}
}, "<");
}
}
}
class MyAppV2App extends .... {

rerender(options) {
if(this.element) {
const current = this.element.cloneNode(true);
gsap.set(currentState, {
position: "absolute",
top: element.offsetTop,
left: element.offsetLeft,
width: element.offsetWidth,
height: element.offsetHeight,
pointerEvents: "none",
});

this.element.appendChild(currentState);
this.oldState = currentState;
}
return this.render(options);
}

async _onRender(context, options) {
await super._onRender(context, options);
if(this.element && this.oldState) {
const timeline = gsap.timeline();
tl.from(this.element, {opacity: 0, duration: 0.5});
tl.to(this.oldState, {
opacity: 0,
duration: 0.5,
onComplete: () => {
this.oldState?.remove();
this.oldState = null;
}
}, "<");
}
}
}
for some reason depending on your screen size the fade jumps by like 0.7px... But besides that it works It also 'blinks' (e.g. there is one frame where the "old state" is removed, then it comes back in the next frame, before being faded out) @Eunomiac I think your last suggestion in #development is basically this xD
Eunomiac
Eunomiacโ€ข5d ago
Ah yes, the FOUC -- "flash of unstyled content". This is why it helps to use your CSS styles to set whatever you're animating to visibility: hidden, so that it's invisible until you animate it with GSAP via autoAlpha (which works the same as animating opacity, but it animates out of visibility: hidden ) That way, when it "blinks" (i.e. the frame before GSAP gets ahold of it), it'll be invisible at that time so it'll look a lot smoother.
Eon
EonOPโ€ข5d ago
How'd you edit the above to achieve that? like just put visibility: hidden in the gsap.set and then autoAlpha: true in .to?
Eunomiac
Eunomiacโ€ข5d ago
I'd do it in the CSS styles. I'd set the whole this.element to visibility: hidden in CSS.
Eon
EonOPโ€ข5d ago
I mean that's what gsap.set is doing (or well it's setting it on the style but I assume that shouldn't matter?)
Eunomiac
Eunomiacโ€ข5d ago
And then go gsap.to(this.element, {autoAlpha: 1, duration: 0.5, onComplete...) The problem is that flash-of-unstyled-content is appearing in the brief frame between when the DOM loads, and when GSAP is able to take control of the elements' appearances. By setting the element to visibility: hidden in your .css file, that applies as the DOM is loading, so you don't see the flash
Eon
EonOPโ€ข5d ago
Ah to be clear, this flash is happening way-past dom load I'm triggering it with "a button click" (effectively) (selecting a token)
Eunomiac
Eunomiacโ€ข5d ago
Err, it's unrelated to a server-triggered re-rendering of the DOM? Okay, that's a whole different issue. Could you explain it in more detail (or point me to a message where you already did?)
Eon
EonOPโ€ข5d ago
The full code is effectively this above + in Token._onControl I go: app.token = this; app.rerender();
Eunomiac
Eunomiacโ€ข5d ago
Generally speaking though, a flash like the one you're describing is because the element is being displayed before GSAP has a chance to set it to opacity: 0. If you're adding a cloned element to the DOM, the same thing will apply. The fix is to make the element invisible until GSAP has a chance to manipulate it.
Eon
EonOPโ€ข5d ago
Hmm, I've now right after const current = this.element.cloneNode(true) added currentState.style.visibility = "hidden"; and it's still flashing .-.
Eunomiac
Eunomiacโ€ข5d ago
No, what you want to do is make sure the visibility: hidden is set in the CSS styling of the element --- you can't rely on GSAP to do it, or Javascript for that matter, because Javascript isn't "fast enough" to catch the element immediately.
Eon
EonOPโ€ข5d ago
But... JS is... creating the element... I set visibility to hidden before it is added to the DOM... how can it be too slow :psyduck: :LaughingSor: What I can see occuring if I put breakpoints: Before I create the timeline the new state is fully rendered while the old state is fully unrendered, which is what is causing the blink. So yeah this is the whole "I need to stop AppV2 from actually rendering and replacing the dom and let gsap do it completely"
Eunomiac
Eunomiacโ€ข5d ago
I'm embarrassed to say I've been using JQuery for so long, I've forgotten what most of the native DOM methods do, lol. cloneNode creates the element unattached to the DOM entirely?
Oh! That might be why GSAP isn't able to do anything with it. In fact I think that's it --- GSAP doesn't style elements well if they aren't in the DOM. You could try setting the style attribute using native DOM-manipulation methods instead of gsap.set, that might work. (In JQuery, it'd be $(current).attr("style", "visibility: hidden").
Before I create the timeline the new state is fully rendered
Yeah. To fix this, you need to make sure that the style of the new state is visibility: hidden --- not try to set it via Javascript or GSAP, but in the baseline CSS. Then, you animate autoAlpha to 1 to reveal it. Or even gsap.set(this.element, {autoAlpha: 1}) when you're ready for it to be shown, if you aren't fading it in.
Eon
EonOPโ€ข5d ago
I tried that actually, and because the result was the same I decided to move it over into the GSAP set because it's shorter to write, it didn't make a difference x)
Eunomiac
Eunomiacโ€ข5d ago
Try doing it with a class assignment. Give it a class like pre-hidden as your create it/before you attach it to the DOM, and then in your .css file, have .pre-hidden { visibility: hidden; }. Then test it without GSAP at all --- you simply shouldn't see it at all, no blink no nothing. Then you can be sure it won't appear until GSAP is good and ready to make it appear ๐Ÿ™‚ (That's actually kind of key to this discussion --- you should be able to get rid of the blink without using GSAP at all: If you can get it so that it renders "invisibly" without blinking, then you can use GSAP to animate it in) But if you use CSS to style the component visibility: hidden, there's no way it should appear until you make it appear via GSAP (or by removing that class/style property, etc)
Eon
EonOPโ€ข5d ago
I... feel like autoAlpha1 is setting visibility to hidden once it's done could that be correct o_O?
Eunomiac
Eunomiacโ€ข5d ago
You can also use a pseudoselector, if the blink happens while both elements are present in the DOM --- i.e. if you attach your cloned element after the original one before removing the original one, a selector like .myApp + .myApp {visibility: hidden} will ensure that any element with the class myApp that appears after another myApp element is hidden It should set it to visibility: hidden if you animate autoAlpha to 0, definitely not the other way around
Eon
EonOPโ€ข5d ago
yeah no this just isn't working. Tomorrow imma try again but with GSAP taking full rendering control instead of re-rendering with handlebars & then trying to animate the transition. Thanks for the advice tho!
Eunomiac
Eunomiacโ€ข5d ago
That would be my advice, for sure --- but the answer is almost certainly going to be setting the element to visibility: hidden in your .css file, I'd bet my mother's life on it. Wait, that's not how the saying goes
Eon
EonOPโ€ข5d ago
I did that ;-; It doesn't blink but it also doesn't fade out anymore now x)
Eunomiac
Eunomiacโ€ข5d ago
So, despite the element having a CSS-set style of visibility: hidden, it still flashes? That's bizarre. And it shouldn't have anything to do with GSAP --- i.e. if you remove all of the GSAP (or comment it out briefly ot test), you should still see that flash
Eon
EonOPโ€ข5d ago
well if I comment out all the gsap visibility: visible would never be set so I don't see anything
Eunomiac
Eunomiacโ€ข5d ago
Exactly. And that would confirm that the styles and DOM manipulation are working properly --- if you see nothing, no flash, nothing at all, then you know that you've correctly rendered the element to the DOM invisibly, so it's ready for GSAP to animate it back in. Once you confirm that, add back in the GSAP. If the flash reappears, then yeah, GSAP is doing something weird.
You can also try using fromTo instead of from -- I find it's more resilient because you're setting the start and end points. E.g. gsap.fromTo(this.element, {autoAlpha: 0}, {autoAlpha: 1, duration: 0.5})
Eon
EonOPโ€ข5d ago
I might've cracked it... lets seee.... nope, this way there is just no fade out happening :/
const tl = gsap.timeline();
tl.from(
app,
{
opacity: 0,
duration: 0.5,
autoAlpha: 0,
},
);
const tl = gsap.timeline();
tl.from(
app,
{
opacity: 0,
duration: 0.5,
autoAlpha: 0,
},
);
This, the fade-in, works 100% totally fine.
tl.to(
this.oldState,
{
opacity: 0,
duration: 0.5,
autoAlpha: 0,
onComplete: () => {
this.oldState?.remove();
this.oldState = null;
}
},
"<"
)
tl.to(
this.oldState,
{
opacity: 0,
duration: 0.5,
autoAlpha: 0,
onComplete: () => {
this.oldState?.remove();
this.oldState = null;
}
},
"<"
)
This, the fade-out, blinks into existence before fading out
Eunomiac
Eunomiacโ€ข5d ago
It might be that this.oldState isn't resolving to the element. Try using a selector, or re-fetch the element right after that await super._onRender call Oh, so it is resolving. Try using fromTo wherever you can --- tl.fromTo(this.oldState, {autoAlpha: 1}, {autoAlpha: 0, duration: 0.5} ... And don't use both opacity and autoAlpha --- autoAlpha handles opacity and visibility, so it's redundant to include opacity as well. (That might actually be causing the problem, I'm not sure) (Changed opacity to autoAlpha in my above example, just FYI ๐Ÿ™‚ ) Also I just want to reiterate that I've never liked gsap.from --- I always seem to run into problems with it. I exclusively use to and fromTo. Especially when building timelines.
Eon
EonOPโ€ข5d ago
OOOkay I have it 99% now I think My issue
Eunomiac
Eunomiacโ€ข5d ago
Was it the use of both autoAlpha and opacity that was causing the problem? That would be my suspicion.
Eon
EonOPโ€ข5d ago
was that I was appending the oldState to this.element instead of this.element.parentNode
Eunomiac
Eunomiacโ€ข5d ago
Ahhhhh, that makes sense -- so oldState was being animated out and in at the same time
Eon
EonOPโ€ข5d ago
for some reason this.oldState is again like 0.7px off. yup
Eunomiac
Eunomiacโ€ข5d ago
0.7px off position-wise? Set font-size: 0 in the parent element -- sometimes a simple white-space can cause that
Eon
EonOPโ€ข5d ago
If you overlay these two images in any kind of photo editor with layers you can see what I mean. font-size: 0 on the parent doesn't seem to fix it :(
No description
No description
Eunomiac
Eunomiacโ€ข5d ago
Hmm. What does the style inspector in the dev console show? Any differences in the box positioning?
Eon
EonOPโ€ข5d ago
Well the "pre" isn't positioned (in the sense it ain't absolute)
Eon
EonOPโ€ข5d ago
I swear it's that .594
No description
Eunomiac
Eunomiacโ€ข5d ago
That might be why -- if you're trying to align two differently-positioned elements, that can be tricky, especially if some of their dimensions are floats -- like that 174.594 Maybe consider a parent wrapper element set to position: relative, and then set both the pre and post positions via position: absolute?
Eon
EonOPโ€ข5d ago
hmmm interestingly enough, setting position relative on the parent and position absolute just works. Like the element doesn't move and just stays exactly where I want it to, never had that be the case before lol. But if I do that (and then also don't set any top/left/width/height on the clone) they indeed become identical
Eon
EonOPโ€ข5d ago
to this
No description
Eunomiac
Eunomiacโ€ข5d ago
Ahh, I see -- the element is being centered horizontally in the second case, which was causing that 0.3px offset Yeah ideally you want the DOM structure (including the stacking context, set via position: relative on the parent) to be identical for both of the elements you want to align.
Eon
EonOPโ€ข5d ago
oh no how did the stutter return now aaaagh.
Eunomiac
Eunomiacโ€ข5d ago
And you should be able to position both elements without misalignment if they're both children of a position: relative parent -- and assuming you set their positions identically, of course.
Eon
EonOPโ€ข5d ago
I don't understand why the flicker is back gah.
rerender(options) {
if(this.element) {
const currentState = app.cloneNode(true);
currentState.style.visibility = "visible";
this.element.parentNode?.appendChild(currentState);
this.oldState = currentState;
}
return this.render(options);
}

async _onRender(context, options) {
await super._onRender(context, options);
this.fadeOldState();
}

fadeOldState() {
if (!this.element || !this.oldState) return

const tl = gsap.timeline();
tl.fromTo(
this.element,
{
autoAlpha: 0,
},
{
autoAlpha: 1,
duration: 0.5
}
);
tl.fromTo(
this.oldState,
{
autoAlpha: 1
},
{
duration: 0.5,
autoAlpha: 0,
onComplete: () => {
this.oldState?.remove();
this.oldState = null;
}
},
"<"
)
}
rerender(options) {
if(this.element) {
const currentState = app.cloneNode(true);
currentState.style.visibility = "visible";
this.element.parentNode?.appendChild(currentState);
this.oldState = currentState;
}
return this.render(options);
}

async _onRender(context, options) {
await super._onRender(context, options);
this.fadeOldState();
}

fadeOldState() {
if (!this.element || !this.oldState) return

const tl = gsap.timeline();
tl.fromTo(
this.element,
{
autoAlpha: 0,
},
{
autoAlpha: 1,
duration: 0.5
}
);
tl.fromTo(
this.oldState,
{
autoAlpha: 1
},
{
duration: 0.5,
autoAlpha: 0,
onComplete: () => {
this.oldState?.remove();
this.oldState = null;
}
},
"<"
)
}
#parent {
position: relative;
}

#child {
position: absolute;
visibility: hidden;
}
#parent {
position: relative;
}

#child {
position: absolute;
visibility: hidden;
}
I think this may have to do with the fact that images are refreshing so it feels like it's blinking but in actuality only the images are okay updating rerender to this fixed it:
async rerender(options) {
if(this.element) {
const currentState = app.cloneNode(true);
currentState.style.visibility = "visible";

const images = Array.from(currentState.querySelectorAll("img"));
await Promise.all(images.map(img => img.decode()));

this.element.parentNode?.appendChild(currentState);
this.oldState = currentState;
}
return this.render(options);
}
async rerender(options) {
if(this.element) {
const currentState = app.cloneNode(true);
currentState.style.visibility = "visible";

const images = Array.from(currentState.querySelectorAll("img"));
await Promise.all(images.map(img => img.decode()));

this.element.parentNode?.appendChild(currentState);
this.oldState = currentState;
}
return this.render(options);
}
Fully working cross-fade while keeping the re-render with Handlebars, woop! fun fact; I don't need the css visibility: hidden rule anymore either now x)
Eon
EonOPโ€ข5d ago
The full diff that got it all working x)
No description
Eon
EonOPโ€ข5d ago
Thanks btw @Eunomiac! Now that I've got a bit better of a grasp & it's working it's a lot more fun to play around with :ashelaugh: For tonight I'm done, tomorrow it's time to animate more stuff & things~
No description
Leo The League Lion
Leo The League Lionโ€ข5d ago
@Eon gave :vote: LeaguePointsโ„ข to @Eunomiac (#77 โ€ข 30)
Eon
EonOPโ€ข5d ago
Maybe figure out a mixin for adding gsap animation of handlebars parts :sprigatitoNote:
Eunomiac
Eunomiacโ€ข5d ago
Oh that's nice! I've actually never done much with the canvas --- in fact, in my current system, I display: none it completely, lol. Everything is apps and popups. One thing you might find really fun to add to your system and animate with GSAP are alert popups for when fun things happen --- I had a blast making these ๐Ÿ™‚
No description
Eunomiac
Eunomiacโ€ข5d ago
But this is probably my fave of the things I've done with GSAP
No description

Did you find this page helpful?