vue.jsanimationvuejs3slidetoggle

Removing element after sliding up animation and contrary, mounting the element before sliding down in Vue 3.X


The vue-slide-up-down library working with pre-mounted elements only.

It just manipulating with the element's height and hidden attribute.

Now what if I don't what the target element be mounted when it does not displaying?

If we try

<slide-up-down 
  v-if="active"
  :active="active" 
>
  Only show this if "active” is true
</slide-up-down>

it will not be the animation because:


Solution

  • You need a separate variable controlling whether the component is rendered (I named it isRendered below).
    And a setter + getter computed (named rendered below) which sets both active and isRendered to current value, but in different order:


    Vue2 demo:

    Vue.component("slide-up-down", VueSlideUpDown)
    new Vue({
      el: "#app",
      data: () => ({
        isRendered: false,
        active: false,
        duration: 500
      }),
      computed: {
        rendered: {
          get() {
            return this.isRendered
          },
          set(val) {
            if (val) {
              this.isRendered = val
              this.$nextTick(() => {
                this.active = val
              })
            } else {
              this.active = val
              setTimeout(() => {
                this.isRendered = val
              }, this.duration)
            }
          }
        }
      }
    })
    .wrap {
      padding: 1rem;
      border: 1px solid #ccc;
      background-color: #f5f5f5;
    }
    
    button {
      margin-bottom: 1rem;
    }
    <script src="https://unpkg.com/vue@2.7.14/dist/vue.min.js"></script>
    <script src="https://unpkg.com/vue-slide-up-down@2.0.0/dist/vue-slide-up-down.umd.js"></script>
    <div id="app">
      <button @click="rendered = !rendered">Toggle</button>
      <slide-up-down v-if="isRendered" v-bind="{ active, duration }">
        <div class="wrap">
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut, consequatur
          ut magnam, quos possimus, velit quam mollitia voluptate adipisci
          reiciendis sapiente accusamus ullam ab voluptatem laborum non! Accusamus,
          ullam, voluptatum.
        </div>
      </slide-up-down>
    </div>

    Vue3 demo:

    const { createApp, reactive, computed, nextTick, toRefs } = Vue
    const app = createApp({
      setup() {
        const state = reactive({
          isRendered: false,
          active: false,
          duration: 500,
          rendered: computed({
            get() { return state.isRendered },
            set(val) {
              if (val) {
                state.isRendered = val
                nextTick(() => {
                  state.active = val
                })
              } else {
                state.active = val
                setTimeout(() => {
                  state.isRendered = val
                }, state.duration)
              }
            }
          })
        })
        return toRefs(state)
      }
    })
    app.component("slide-up-down", Vue3SlideUpDown)
    app.mount('#app')
    .wrap {
      padding: 1rem;
      border: 1px solid #ccc;
      background-color: #f5f5f5;
    }
    
    button {
      margin-bottom: 1rem;
    }
    <script src="https://unpkg.com/vue@3.2.47/dist/vue.global.prod.js"></script>
    <script src="https://unpkg.com/vue3-slide-up-down@1.2.5/dist/vue3-slide-up-down.umd.js"></script>
    <div id="app">
      <button @click="rendered = !rendered">Toggle</button>
      <slide-up-down v-if="isRendered" v-model="active" v-bind="{ duration }">
        <div class="wrap">
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut, consequatur
          ut magnam, quos possimus, velit quam mollitia voluptate adipisci
          reiciendis sapiente accusamus ullam ab voluptatem laborum non! Accusamus,
          ullam, voluptatum.
        </div>
      </slide-up-down>
    </div>


    If you're gonna do this multiple times, you might want to extract it as a stand-alone component. Usage example:

    <conditional-slide :rendered="condition" :duration="1000">
      <div>content you want rendered based on `this.condition` (boolean)</div>
    </conditional-slide>
    

    Since the change now comes from the rendered prop, you can move the computed setter code into a watch:

    In Vue 2:

    <template>
      <div>
        <slide-up-down
          v-if="isRendered"
          v-bind="{ active, duration }"
        >
          <slot />
        </slide-up-down>
      </div>
    </template>
    <script>
    import SlideUpDown from 'vue-slide-up-down'
    export default {
      components: { SlideUpDown },
      props: {
        rendered: {
          type: Boolean,
          default: false
        },
        duration: {
          type: Number,
          default: 500
        }
      },
      data: () => ({
        active: false,
        isRendered: false
      }),
      watch: {
        rendered: {
          handler(val) {
            if (val) {
              this.isRendered = val
              this.$nextTick(() => {
                this.active = val
              })
            } else {
              this.active = val
              setTimeout(() => {
                this.isRendered = val
              }, this.duration)
            }
          },
          immediate: true
        }
      }
    }
    </script>
    

    Vue 3 example. Sandbox here.