Home > OS >  Removing element after sliding up animation and contrary, mounting the element before sliding down i
Removing element after sliding up animation and contrary, mounting the element before sliding down i

Time:02-06

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

  • Sliding down case: It will not mount the element before animation starts
  • Sliding up case: It will not unmount the element after animation ends

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?

  • Sliding down case: Before animation starts, I want the element be mounted
  • Sliding up case: Once animation complete, I want the element be unmounted, not just hidden

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:

  • Sliding down case: animation starts before element mounted
  • Sliding up case: the element will be unmounted before animation starts

CodePudding user response:

If I got you right, there is the solution.

Use v-show instead of v-if

I have used the original example from the vue-slide-up-down site to demonstrate the behavior.

If you use 'v-show' and also toggle the v-slide- component together, then you have the slide effect when your hidden content will be show again.

Vue.component("slide-up-down", VueSlideUpDown);
var vm = new Vue({
  el: '#app',
  data: () => ({
      active: false,
      show: true,
      contentLength: 2,
      showOptions: false,
      useHidden: true,
      timing: "",
      duration: 500
   }),
   methods: {
      toggle() {
        this.active = !this.active;
      },
      toggleShow() {
        this.show = !this.show;
        this.active = this.show;
      }
    }
})
* {
  margin: 0;
  padding: 0;
  font-size: 1em;
  box-sizing: border-box;
}
body {
  font-family: sans-serif;
  padding: 20px;
  line-height: 1.5em;
}
#app {
  margin: 0 auto;
  max-width: 700px;
}
.Button {
  display: block;
  width: 100%;
  margin-top: 10px;
  margin-left: auto;
  margin-right: auto;
  padding: 1em;
  border: 0;
  background-color: gray;
  cursor: pointer;
}
.Button.small {
  width: auto;
  font-size: 0.8em;
  padding: 0.5em 1em;
  background-color: lightgray;
  margin: 0;
}
.AccordionWrapper {
  background-color: darkred;
}
.AccordionWrapper.easeInOutCirc {
  transition-timing-function: cubic-bezier(0.785, 0.135, 0.15, 0.86);
}
.AccordionWrapper.customTiming {
  transition-timing-function: cubic-bezier(0.195, 1.65, 0.435, -0.6);
}
.Accordion {
  display: block;
  width: 96%;
  margin-left: auto;
  margin-right: auto;
  padding: 1em;
  border: 0;
  background-color: lightgray;
}
.Accordion p,
.Accordion hr {
  margin-bottom: 10px;
}
.Accordion p:last-child {
  margin-bottom: 0;
}
<div id="app">  
  <div v-show="show">
    <button @click="toggle" >Toggle Content</button>
     <slide-up-down  :active="active" :use-hidden="this.useHidden" : :duration="this.duration"> 
      <div >
        <p>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.</p>
      </div>
    </slide-up-down>
    </div>
   <button  @click="toggleShow">Toggle if elements exists</button>
</div>
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue-slide-up-down.umd.js"></script>

CodePudding user response:

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:

  • when setting to true: turn isRendered on first, then set active to true in $nextTick, so the animation is played
  • when setting to false: turn active to false first, wait for animation to finish and then set isRendered to false.

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/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/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 >
      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/[email protected]/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/[email protected]/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 >
      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>
export default {
  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>

In Vue 3:

<template>
  <div>
    <slide-up-down
      v-if="isRendered"
      v-model="active"
      v-bind="{ duration }"
    >
      <slot />
    </slide-up-down>
  </div>
</template>
<script>
import {
  reactive,
  toRefs,
  computed,
  watch,
  nextTick,
  defineComponent
} from "vue"
export default defineComponent({
  props: {
    rendered: {
      type: Boolean,
      default: false
    },
    duration: {
      type: Number,
      default: 500
    }
  },
  setup(props) {
    const state = reactive({
      active: false,
      isRendered: false
    })
    watch(
      props.rendered,
      (val) => {
        if (val) {
          state.isRendered = val
          nextTick(() => {
            state.active = val
          })
        } else {
          state.active = val
          setTimeout(() => {
            state.isRendered = val
          }, props.duration)
        }
      },
      { immediate: true }
    )
    return toRefs(state)
  }
})

Feel free to add more props and pass them down to the inner <slide-up-down />.

  • Related