Tenarius
Tenarius

Reputation: 609

Vue transition: how to slide correctly?

I have a v-card that I want to animate back and forth with one click.

If I click on an arrow to the left, the card should scroll to the right and then immediately scroll back in from the left. The other button should work the other way around.

The problem is, nothing happens here. What am I doing wrong?

My template:

<v-card>
    <v-btn icon @click="back = false">
        <v-icon>mdi-arrow-left</v-icon>
    </v-btn>
    <v-btn icon @click="back = true">
        <v-icon>mdi-arrow-right</v-icon>
    </v-btn>
</v-card>

<transition :name="back ? 'slide-fade' : 'slide-fade-reverse'">
    <v-card max-width="200" class="mx-auto mt-5" height="80">
        <span class="d-flex justify-center pt-7">{{back}}</span>
    </v-card>
</transition>

My script:

data() {
    return {
        back: false,
    }
},

My css:

/* Prev */
.slide-fade-enter-active {
    transition: all .3s ease;
}
.slide-fade-leave-active {
    transition: all .3s ease;
}
.slide-fade-enter {
    transform: translateX(100px);
    opacity: 0;
}

.slide-fade-leave-to {
    transform: translateX(-100px);
    opacity: 0;
}

/* Next */
.slide-fade-reverse-enter-active {
    transition: all .3s ease;
}
.slide-fade-reverse-leave-active {
    transition: all .3s ease;
}
.slide-fade-reverse-enter {
    transform: translateX(-100px);
    opacity: 0;
}

.slide-fade-reverse-leave-to {
    transform: translateX(100px);
    opacity: 0;
}

I made a Pen for this: https://codepen.io/Tenarius/pen/WNwdEve

Upvotes: 0

Views: 4190

Answers (3)

gokudesu
gokudesu

Reputation: 371

I implemented this, and it is working:

// index.scss
$animation-duration: 0.15s;

.step-next-enter-active,
.step-next-leave-active,
.step-prev-enter-active,
.step-prev-leave-active {
  transition:
    transform #{$animation-duration} ease,
    opacity #{$animation-duration} ease;
}

.step-next-enter-from,
.step-prev-leave-to {
  transform: translateX(32px);
  opacity: 0;
}

.step-next-leave-to,
.step-prev-enter-from {
  transform: translateX(-32px);
  opacity: 0;
}

.step-next-leave-from,
.step-next-enter-to,
.step-prev-leave-from,
.step-prev-enter-to {
  transform: translateX(0);
}
<script setup lang="ts">
...
const animationDirection = ref<"step-next" | "step-prev">("step-next");

// Calculate the height of the current step for smooth transition
const currentStepHeight = ref<number>();

watch(
  () => [store.currentStep],
  ([currentStep], [oldStep]) => {
    if (currentStep !== oldStep) {
      animationDirection.value =
        currentStep > oldStep ? "step-next" : "step-prev";
    }
  },
  {
    flush: "pre",
  }
);

// Get the current transitioning element height
function onEnter(e: Element): void {
  const elHeight = e.getBoundingClientRect().height;
  currentStepHeight.value = elHeight;
}
</script>

<template>
...
        <div
          class="overflow-hidden transition-all"
          :style="{
            height: currentStepHeight + 'px',
            maxHeight: currentStepHeight + 'px',
          }"
        >
          <Transition
            :name="animationDirection"
            mode="out-in"
            @enter="onEnter"
          >
            <Component :is="steps[store.currentStep - 1]" />
          </Transition>
        </div>
...
</template>

Hope it helps!

Upvotes: 0

tao
tao

Reputation: 90103

In order for leave and enter transition to work, the <transition> element has to have a v-if condition. When it changes from false to true, the element gets inserted into DOM and animates according to enter transition. When the condition changes from true to false, the leaving transition is performed and, when it ends, the element is removed from DOM.

However, you don't have such a condition. You're simply updating the cards contents and expect it to be removed from DOM and replaced by a new one.

In order to achieve the expected functionality you should use a list of cards (which would only contain the currently active card), coupled with using <transition-group> which, internally, uses the same mechanics as transition but the v-if condition is whether the element is part of the collection or not.

In your case, the "collection" would be a filtered list of cards, containing only one card. With this technique, the leaving element gets the leave animation, while the entering element gets the enter animation, as the elements are actually removed and added to DOM, according to changes in your model.

See it working here.

Upvotes: 1

Tenarius
Tenarius

Reputation: 609

Since a transition needs leave and enter and thus the element has to "disappear" and "reappear", setTimout can be used to build a workaround.

data() {
    return {
        back: false,
        loading: false
    }
},
methods: {
    loadTimeout() {
        this.loading = true
        setTimeout(function(){
            this.loading = false
        }.bind(this), 500);
    }
}

The card can then be expanded with v-show="!loading" and the left- and right-buttons have to call the loadTimeout() function.

Working example here

Upvotes: 0

Related Questions