vue.jsvuejs3transition

Transitions in vue3 work on slide in but not slide out


I have this sample of code

Parent:

<template>
  <div>
    <button @click="cartToggle">Toggle</button>
  </div>
  <div>
    <Child
      :visible="cartVisible"
      @update:visible="cartToggle"
      v-show="cartVisible"
    />
  </div>
</template>

<script setup>
import { ref } from "vue";
import Child from "./Child.vue";

const cartVisible = ref(false);
const cartToggle = () => {
  cartVisible.value = !cartVisible.value;
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
span {
  cursor: pointer;
}
</style>

Child:

<script setup lang="ts">
import { toRefs, watchEffect, onBeforeUnmount } from "vue";

const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
    default: false,
  },
});

const { visible } = toRefs(props);

const emit = defineEmits(["update:visible"]);

const hideCartBar = () => {
  emit("update:visible", false);
};

watchEffect(() => {
  document.body.style.overflow = visible.value ? "hidden" : "auto";
  document.body.style.paddingRight = visible.value ? "15px" : "0px";
});

onBeforeUnmount(() => {
  document.body.style.overflow = "auto";
  document.body.style.paddingRight = "0px";
});
</script>

<template>
  <div class="cartbar">
    <div class="cartbar-wrapper">
      <transition name="fade">
        <div
          v-show="props.visible"
          class="cartbar-overlay"
          @click="hideCartBar"
        ></div>
      </transition>
      <transition name="slide">
        <div v-show="props.visible" class="cartbar-container">
          <div class="cartbar__header">
            <h2>Menu</h2>
            <span @click="hideCartBar" class="cursor-pointer">Close</span>
          </div>
        </div>
      </transition>
    </div>
  </div>
</template>

<style scoped>
/* transition */
/* transition */
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

.fade-enter-to,
.fade-leave-from {
  opacity: 1;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 500ms;
}

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

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

.slide-enter-active,
.slide-leave-active {
  transition: transform 500ms;
}
/* cartbar */
.cursor-pointer {
  cursor: pointer;
}
.cartbar-wrapper {
  transition: visibility 0s 0s;
  z-index: 1300;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  width: 100%;
  padding-left: calc(4px * 9);
}

.cartbar-overlay {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.7);
  opacity: 1;
  cursor: pointer;
}
.cartbar-container {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 460px;
  height: 100%;
  overflow: hidden;
  margin-left: auto;
  padding: calc(4px * 7);
  background-color: #f4f2f1;
  color: #706b5f;
}
</style>

and the problem is, when I want to toggle the menu in .. it slides in fine, but it doesn't slide out ... plus - the overlay does not animate at all

what am I doing wrong?

Example: https://codesandbox.io/p/sandbox/vue-3-glziy


Solution

  • The v-show="cartVisible" on the Child removes the whole element before the out-transition even starts. But when simply removing the v-show, the .cartbar-wrapper will overlay the whole screen, making it impossible to press the button. You'll have to change the structure of the Child component so that it works without a container.

    A simple solution would be to just remove the wrapper and put the position:fixed on the .cartbar-container, have a look a the playground.