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
: turnisRendered
on first, then setactive
totrue
in$nextTick
, so the animation is played - when setting to
false
: turnactive
tofalse
first, wait for animation to finish and then setisRendered
tofalse
.
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 />
.