Reputation: 8177
I have a group of elements showing/hiding/rearranging with a transition group, nothing fancy.
.cross-fade-leave-active {
transition: transform $fadeSpeed ease-in-out, opacity $fadeSpeed ease-in-out;
position: absolute;
}
.cross-fade-enter-active {
transition: opacity $fadeSpeed ease-in-out $fadeSpeed;
}
.cross-fade-enter-from,
.cross-fade-leave-to {
opacity: 0;
}
.cross-fade-move{
transition: transform $fadeSpeed ease-in $fadeSpeed, opacity $fadeSpeed ease-in-out;
}
This works well except in situations where the dynamic height parent container is part of a flow of elements. The parent immediately snaps to the Last stage of the FLIP animation, while the contents of the transition-group smoothly do their thing.
While functionally it makes sense why it's happening, it's far from ideal.
Is there a straightforward way to hook into the Vue FLIP animation to grab the First and Last properties of the parent, so I can set the max height for transition?
My attempt
<div :ref="`container${i}`" :style="{'max-height': containers[`container${i}`]}">
<transition-group name="cross-fade"
@after-enter="clearMaxHeight(`container${i}`)"
@after-leave="clearMaxHeight(`container${i}`)"
@before-enter="maxHeight(`container${i}`)"
@enter="maxHeight(`container${i}`)"
@before-leave="maxHeight(`container${i}`)"
@leave="maxHeight(`container${i}`)"
>
//...conditional elements
</transition-group>
</div>
maxHeight(ref){
let container = this.$refs[ref];
this.containers[ref] = container.clientHeight + 'px';
},
clearMaxHeight(ref){
this.containers[ref] = 'none';
},
I would think, in theory, that before-enter
or before-leave
would capture the height of the element before the transition, which would lock in the First height of the parent. Then enter
or leave
would capture the new immediately after the elements have been added/removed from the flow, giving the Last height. Finally, when the animations are done, just set max height back to none so it can behave normally.
But this doesn’t work like that. Even with the transition pumped up to 5s, I see max-height getting set to a number for a split second, then immediately back to 'none'. Maybe I misunderstand the lifecycle of the Vue animation hooks, but the docs seem a little sparse on the exact execution.
Upvotes: 1
Views: 1840
Reputation: 8177
Disclaimer
It's a little buggy, but I think there may actually be an issue with transition-group (or undocumented feature that's trying and failing to do this very thing). Basically, some elements outside the transition-group container are most definitely getting transform matrices and -move
classes added to them by something in Vue in reaction to this, which is annoying.
However, this does basically do what I set out to do originally, maybe you can improve it. Or maybe I'll dig into the Vue3 source code and force it to behave.
Setup:
const firstAni = {};
const aniContainer = {};
const aniFirst = (i) => {
let container = aniContainer[i];
firstAni[i] = container.offsetHeight;
};
const aniLast = (i) => {
let container = aniContainer[i];
let lastAni = container.offsetHeight;
const aniClear = (e) => {
if(e.target !== container){
return false;
}
container.removeEventListener('transitionend', aniClear);
container.removeEventListener('transitioncancel', aniClear);
container.style.height = 'auto';
};
container.style.height = `${firstAni[i]}px`;
container.addEventListener('transitionend', aniClear);
container.addEventListener('transitioncancel', aniClear);
nextTick(() => {
container.style.height = `${lastAni}px`;
});
};
const setAniRef = (el, i) => {
aniContainer[i] = el;
};
The template:
<div class="cross-fade-wrapper" :ref="(el) => setAniRef(el, i)">
<transition-group name="cross-fade"
@before-enter="(el) => aniFirst(i, el)"
@before-leave="(el) => aniFirst(i, el)"
@enter="(el, done) => aniLast(i, el, done)"
@leave="(el, done) => aniLast(i, el, done)"
>
<!-- all the conditional elements -->
</transition-group>
</div>
Style it up:
$fadeSpeed: 0.1s;
.cross-fade-wrapper{
position: relative;
transition: height $fadeSpeed ease-out;
}
.cross-fade-leave-active {
transition: opacity $fadeSpeed / 2 ease-out;
position: absolute;
}
.cross-fade-enter-active {
transition: opacity $fadeSpeed / 2 ease-in $fadeSpeed * 0.8;
}
.cross-fade-enter-from,
.cross-fade-leave-to {
opacity: 0;
}
.cross-fade-move{
transition: transform $fadeSpeed ease-out;
}
Basically per FLIP, sorta:
auto
.Upvotes: 0