Search code examples
javascriptvue.jsnuxt.jscss-transitionsvue-transitions

Vue.js transition on drag and click


I'm trying to build a calendar component,and when I click next or prev I want a transition, I also want to add a transition when I slide, but I can't make vue transitions work. I only have one element(current Month) and after I click to go next or prev only thing It does is change the data. To be able to add a dynamic transform: translate, because I need to get mouse position when slide, should I create 3 elements(previous month, current month, next month)? Or can I make that work only changing data?

I tried the most simple example on vue docs and every time I click on the buttons the transition doesnt work.

 <transition name="home">
    <Calendar></Calendar>
  </transition>

.home-enter-active,
.home-leave-active {
    transition: opacity 4s;
}


.home-enter,
.home-leave-active {
    opacity: 0;
}

And this is my calendar component.

<template>
    <div id="calendar" class="calendar">
        <div>
            <div>{{ state.currentMonthName }}</div>
            <div>{{ state.currentYear }}</div>
            <div>
                <ul class="weekDays">
                    <li v-for="days in state.weekNames" :key="days">{{ days }}</li>
                </ul>
            </div>
            <div class="days">
                <span
                    @click="
                        selectDate(state.currentYear, state.currentMonth - 1, state.getLastDayOfPreviousMonth - state.startDay + day);
                        goPrev();
                    "
                    class="lastMonth"
                    v-for="day in state.startDay"
                    :key="'empty' + day"
                    >{{ state.getLastDayOfPreviousMonth - state.startDay + day }}</span
                >
                <span :class="currenDateClass(state.currentYear, state.currentMonth, day)" @click="selectDate(state.currentYear, state.currentMonth, day)" v-for="day in state.getLastDayOfMonth" :key="day">{{ day }}</span>
                <span
                    @click="
                        goNext();
                        selectDate(state.currentYear, state.currentMonth, day);
                    "
                    class="lastMonth"
                    v-for="day in 6 - state.endDay"
                    :key="'nextMonth' + day"
                    >{{ day }}</span
                >
            </div>
        </div>

        <button @click="goPrev">Prev</button>
        <button @click="goNext">Next</button>
    </div>
</template>

Solution

  • Your transition wraps the Calendar component but it never changes. Wrap only the part of the component that changes. From the docs:

    Vue provides a variety of ways to apply transition effects when items are inserted, updated, or removed from the DOM

    In their example, the transition wraps an element with v-if, which adds/removes items from the DOM, so the transition is applied. Move your transition inside the component around something that changes. For example, a transition on the month:

    <template>
      ...
      <transition name="home">
        <div :key="state.currentMonthName">{{ state.currentMonthName }}</div>
      </transition>
      ...
    </template>
    

    Note: The transition has to wrap an element. This wouldn't work without the div or some other tag.

    The key prop forces the div to rerender when the month changes so that the transition gets triggered. This is as suggested in the key documentation. Using the key, you can wrap whatever you want and it will all rerender when the key changes.

    Here's a demo using your transition CSS with a simpler template:

    Vue.component('Calendar', {
      template: `
        <div id="calendar" class="calendar">
            <div>
                <transition name="home">
                    <div :key="indexMonth">{{ months[indexMonth] }}</div>
                </transition>
            </div>
     
            <button @click="goPrev">Prev</button>
            <button @click="goNext">Next</button>
        </div>
      `,
      data() {
        return {
          months: [ "January", "February", "March", "April", "May", "June",
                   "July", "August", "September", "October", "November", "December" ],
          indexMonth: 0
        }
      },
      methods: {
        goPrev() {
            this.indexMonth = this.indexMonth === 0 ? this.months.length - 1 : this.indexMonth - 1;
        },
        goNext() {
            this.indexMonth = this.indexMonth === 11 ? 0 : this.indexMonth + 1;
        }
      }
    })
    
    /***** APP *****/
    new Vue({
      el: "#app"
    });
    .home-enter-active,
    .home-leave-active {
        transition: opacity 1s;
    }
    
    .home-enter,
    .home-leave-active {
        opacity: 0;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <div id="app">
        <Calendar></Calendar>
    </div>