Drdilyor
Drdilyor

Reputation: 1377

Why is transition inside an element with v-if not entering the transition but leaving works?

The title explains pretty much everything. I've searched StackOverflow for a solution but neither of them helped.

An example using classic modal component:

Vue.component('modal', {
  template: `
  <transition name="modal">
    <div class="modal-wrapper">
      <div class="modal-dialog">
        <slot></slot>
      </div>
    </div>
  </transition>
  `,
})

const app = new Vue({
  el: '#app',
  data: {
    showModal: false,
  },
})
/* transition */
.modal-enter-active,
.modal-leave-active {
  transition: opacity .5s;
}

.modal-enter,
.modal-leave-to {
  opacity: 0;
}

.modal-wrapper {
  position: absolute;
  left: 0; right: 0;
  top: 0; bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, .25);
}

.modal-dialog {
  max-width: 90%;
  padding: 1em;
  background: white;
}
<script src="https://vuejs.org/js/vue.js"></script>
<div id="app">
  <p><button @click="showModal = true">Show modal</button></p>
  <modal v-if="showModal">
    <h3>Hello world</h3>
    <p>Amet quam alias amet incidunt voluptatum sapiente Mollitia</p>
    <p><button @click="showModal = false">Close</button></p>
  </modal>
</div>

(There are no errors in the console as well)

However, everything is ok when using v-show. But I can't really use it instead of v-if in my project.

Vue.component('modal', {
  template: `
  <transition name="modal">
    <div class="modal-wrapper">
      <div class="modal-dialog">
        <slot></slot>
      </div>
    </div>
  </transition>
  `,
})

const app = new Vue({
  el: '#app',
  data: {
    showModal: false,
  },
})
/* transition */
.modal-enter-active,
.modal-leave-active {
  transition: opacity .5s;
}

.modal-enter,
.modal-leave-to {
  opacity: 0;
}

.modal-wrapper {
  position: absolute;
  left: 0; right: 0;
  top: 0; bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, .25);
}

.modal-dialog {
  max-width: 90%;
  padding: 1em;
  background: white;
}
<script src="https://vuejs.org/js/vue.js"></script>
<div id="app">
  <p><button @click="showModal = true">Show modal</button></p>
  <modal v-show="showModal">
    <h3>Hello world</h3>
    <p>Amet quam alias amet incidunt voluptatum sapiente Mollitia</p>
    <p><button @click="showModal = false">Close</button></p>
  </modal>
</div>

That said, I have to wrap <modal> with <transition> everywhere modal is used and remove the transition from the modal itself (which doesn't sound good)

<transition name="modal">
  <modal>
  ...
  </modal>
</transition>

Why is it so and How to make entering animation work (with v-if, and <transition> in the modal component?

I have noticed that there is no such problem with Vue 2.5 (instead of Vue 2.6). There surely something changed since then.

Upvotes: 2

Views: 12878

Answers (3)

Drdilyor
Drdilyor

Reputation: 1377

This should do the trick:

{
  template: `
  <transition name="modal">
    <div v-if="show">...</div>
  </transition>
  `,
  data() {return {
    show: false,
  }},
  mounted() {
    this.show = true
  },
}

This method in my opinion (and I believe it does) delays animation by 1 Vue tick, and nesting such animated element a lot would make a noticable slow down.

Vue.component('modal', {
  template: `
  <transition name="modal">
    <div class="modal-wrapper" v-if="show">
      <div class="modal-dialog">
        <slot></slot>
      </div>
    </div>
  </transition>
  `,
  data() {return {
    show: false,
  }},
  mounted() {
    this.show = true
  }
})

const app = new Vue({
  el: '#app',
  data: {
    showModal: false,
  },
})
/* transition */
.modal-enter-active,
.modal-leave-active {
  transition: opacity .5s;
}

.modal-enter,
.modal-leave-to {
  opacity: 0;
}

.modal-wrapper {
  position: absolute;
  left: 0; right: 0;
  top: 0; bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, .25);
}

.modal-dialog {
  max-width: 90%;
  padding: 1em;
  background: white;
}
<script src="https://vuejs.org/js/vue.js"></script>
<div id="app">
  <p><button @click="showModal = true">Show modal</button></p>
  <modal v-if="showModal">
    <h3>Hello world</h3>
    <p>Amet quam alias amet incidunt voluptatum sapiente Mollitia</p>
    <p><button @click="showModal = false">Close</button></p>
  </modal>
</div>

Upvotes: -1

Drdilyor
Drdilyor

Reputation: 1377

You are missing the appear attribute.

By default, Vue will not animate when element is first inserted. As per docs:

If you also want to apply a transition on the initial render of a node, you can add the appear attribute:

<transition appear>
  <!-- ... -->
</transition>

By default, this will use the transitions specified for entering and leaving. If you’d like however, you can also specify custom CSS classes:

<transition
  appear
  appear-class="custom-appear-class"
  appear-to-class="custom-appear-to-class"
  appear-active-class="custom-appear-active-class"
>
<!-- ... -->
</transition>

So just adding appear should fix the problem.

Upvotes: 7

Anastazy
Anastazy

Reputation: 4766

That's because v-if inserts/destroys elements and v-show hides them (does not remove them from DOM). In your example, transition does not exists if v-if is setup on/above transition element. If you move v-if below transition, then it will work.

Vue.component('modal', {
  props: ['showModal'],
  template: `
  <transition name="modal">
    <div class="modal-wrapper" v-if="showModal">
      <div class="modal-dialog">
        <slot></slot>
      </div>
    </div>
  </transition>
  `,
})

const app = new Vue({
  el: '#app',
  data: {
    showModal: false,
  },
})
/* transition */
.modal-enter-active,
.modal-leave-active {
  transition: opacity .5s;
}

.modal-enter,
.modal-leave-to {
  opacity: 0;
}

.modal-wrapper {
  position: absolute;
  left: 0; right: 0;
  top: 0; bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, .25);
}

.modal-dialog {
  max-width: 90%;
  padding: 1em;
  background: white;
}
<script src="https://vuejs.org/js/vue.js"></script>
<div id="app">
  <p><button @click="showModal = true">Show modal</button></p>
  <modal :show-modal="showModal">
    <h3>Hello world</h3>
    <p>Amet quam alias amet incidunt voluptatum sapiente Mollitia</p>
    <p><button @click="showModal = false">Close</button></p>
  </modal>
</div>

Upvotes: 2

Related Questions