If you’ve ever used Vue’s built-in Transition
component before, chances are that this looks very familiar to you:
<template> <Transition mode="out-in" name="fade"> <div v-if="show" key="show">Showing</div> <div v-else key="hide">Hiding</div> </Transition></template>
<style>.fade-enter-active,.fade-leave-active { transition: opacity 0.5s ease;}
.fade-enter-from,.fade-leave-to { opacity: 0;}</style>
This works well, but the animation involves fading the leaving element out completely, before then fading in the entering element so that the two are never on the screen at the same time.
But what if you want to crossfade two images so that the entering element fades over the leaving element?
For this, we need to use the rarely-used transition mode of in-out
. We’re also going to use Tailwind utility classes to control our transition rather than manually writing out a named transition (fade) as above.
<template> <div class="relative"> <Transition mode="in-out" enter-active-class="transition-all duration-1000 absolute inset-0" leave-active-class="transition-none absolute hidden" enter-from-class="opacity-0" > <img v-if="showFirst" key="image-1" src="https://picsum.photos/seed/the-end-is-the-beginning/1920/1080" /> <img v-else key="image-2" src="https://picsum.photos/seed/tannhaus/1920/1080" /> </Transition> </div></template>
In-Out mode explained
The lesser-used mode pretty much does what it says. It brings the entering element in first before then removing the leaving element from the view. The caveat here is that while out-in
ensures that only one single element is rendered in the HTML document at a time, in in-out
mode, during the length of the transition, both the entering and the leaving elements will be visible. This can cause a significant reflow and is the reason the mode is seldom used.
However, since we need our entering element to show at the same time as the leaving one to successfully crossfade them, it is the mode we need.
Positioning absolutely
To combat the HTML reflow, we need to ensure that one element is removed from the document flow, we can do this via the position: absolute
style rule. We’ve also added the inset-0
class (shorthand for top:0;bottom:0;left:0;right:0
) to our entering element to size it the same as the other image. The parent container of the images would have a position:relative
style rule on it.
Hiding the leaving element
Because our entering element appears fully over the other element, we don’t need to have a leaving animation at all. Hence why we just disable transitions and hide the departing element. Without the leave-active-class
classes declared above, the exiting element would momentarily flicker since all Vue transitions take at least 2 frames to complete. Since the entered element is now part of the document flow, we can remove the leaving element from the document flow by applying absolute
to it instead.
Conclusion
For more information on using CSS classes in Vue transitions, see https://vuejs.org/guide/built-ins/transition.html#css-based-transitions