Search code examples
vuejs3css-transitions

Slider Vue component using Transitions cannot get correct behaviour


I'm trying to create a simple Vue component that swaps out two elements by sliding one left and simultaneously sliding in the second from the right to replace it. And then vice-versa when triggered again.

I can get the content to slide out ok, but it doesn't seem to slide in, it just appears.

Also during the transition both the elements are visible which expands the parent element, and I am struggling to get them to slide side-by-side.

I've tried using absolute positioning for the elements but the parent element obviously loses it's height and I don't want to hard-code a height value if possible.

<script setup>

import { ref } from 'vue';

const isActivated = ref(false)

</script>

<template>


<div class="p-2 relative overflow-hidden">

    <Transition name="slide-left">
        <div v-show="!isActivated" class="w-full">
            <slot name="first-content" />
        </div>
    </Transition>

    <Transition  name="slide-right">
        <div v-show="isActivated" class="w-full absolute top-0 left-0">
            <slot name="second-content" />
        </div>
    </Transition>

    <div @click.prevent.stop="isActivated = !isActivated" class="px-2 absolute top-0 right-2" >
        <slot name="trigger" />
    </div>

</div>

</template>

<style>

.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active{
    transition: all .8s;

}


.slide-left-enter-from,
.slide-left-leave-to{
    transform: translateX(-100%);
    opacity:0;
}

.slide-left-enter-to,
.slide-left-leave-from{
    transform: translateX(0%);
    opacity:100
}

.slide-right-enter-from,
.slide-right-leave-to {
    transform: translateX(100%);
    opacity:0;
}

.slide-right-enter-to,
.slide-right-leave-from {
    transform: translateX(0%);
    opacity:100;

}

</style>

I've tried to replicate in a pen too: https://codepen.io/Ben-Wrigley/pen/xxBmPrd


Solution

  • You are using Vue 3 transition class names, in Vue 2, the from transition classes don't have a -from suffix (see documentation). So it has to be .slide-left-enter instead of .slide-left-enter-from, and similarly for slide-right. Fix the classes to make enter transitions work.

    An easy fix for the stack effect during transition is to position one of elements absolute during transition:

    .slide-left-leave-active,
    .slide-left-enter-active{
      position: absolute;
    }
    

    Here it is in a snippet:

    new Vue({
      template: `
        <v-app>
        <v-container class="w-1/4 h-15">
          <div class="p-2 relative overflow-hidden border">
    
            <Transition name="slide-left">
                <div v-show="!isActivated" class="w-full">
                    Some text in position 1
                </div>
            </Transition>
    
            <Transition  name="slide-right">
                <div v-show="isActivated" class="w-full">
                    Some other text in position 2
                </div>
            </Transition>
    
          <div @click.prevent.stop="isActivated = !isActivated" class="px-2 absolute top-1 right-2" >
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="w-5 fill-white">
                  <path d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
             </svg>
          </div>
    
          </div>
          </v-container>
         </v-app>
      `,
      el: "#app",
      vuetify: new Vuetify(),
      created() {
        this.$vuetify.theme.dark = true;
      },
      data() {
        return {
          isActivated: false,
        }
      },
    });
    .slide-left-enter-active,
    .slide-left-leave-active,
    .slide-right-enter-active,
    .slide-right-leave-active{
        transition: all .8s;
    }
    
    .slide-left-leave-active,
    .slide-left-enter-active{
      position: absolute;
    }
    
    .slide-left-leave-to,
    .slide-left-enter{
        transform: translateX(-100%);
        opacity:0;
    }
    
    .slide-left-enter-to,
    .slide-left-leave{
        transform: translateX(0%);
        opacity:100;
    }
    
    .slide-right-enter,
    .slide-right-leave-to {
        transform: translateX(100%);
        opacity:0;
    }
    
    .slide-right-enter-to,
    .slide-right-leave {
        transform: translateX(0%);
        opacity:100;
    
    }
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
    
    <div id="app"></div>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>
    <script src="https://cdn.tailwindcss.com"></script>