题意:动态样式的 Vue 过渡效果
问题背景:
I'm working on a VueJS sidebar component. It should allow the parent to specify a width and render a toggle that slides the sidebar in and out. Something like this:
我正在制作一个 VueJS 侧边栏组件。它应该允许父组件指定宽度,并渲染一个切换按钮,使侧边栏可以滑动进出。类似于这样:
<template>
<div class="sidebarContainer">
<transition
name="slide"
>
<div v-if="isOpen" class="sidebar" :style="{ width }">
<slot/>
</div>
</transition>
<div class="toggle" @click="isOpen = !isOpen"><></div>
</div>
</template>
export default {
props: {
'width': {
default: '20em',
}
},
data() {
return {
isOpen: true,
};
},
};
<style scoped>
.slide-enter-active, .slide-leave-active {
transition: all 0.6s;
}
.slide-enter, .slide-leave-to {
margin-left: -20em;
}
</style>
Working codepen. It works exactly how I want, except that the width for the transition (as specified in .slide-enter, .slide-leave-to
style) is hard coded and not responsive to the component width
property. If you set width=30em
then the transition is jumpy.
这是一个有效的 codepen。它的工作方式正是我想要的,除了过渡的宽度(如 .slide-enter 和 .slide-leave-to 样式中指定的)是硬编码的,而不是响应组件的宽度属性。如果你将 width 设置为 30em,那么过渡效果会显得不流畅。
I suspect that I might need to use transition hooks, but I can't seem to get that to work. I tried this:
我怀疑我可能需要使用过渡钩子,但我似乎无法让它正常工作。我尝试了这个:
beforeEnter(el) {
el.style = {
transition: 'all 0.6s',
marginLeft: '-20em',
};
},
enter(el, done) {
el.style.marginLeft = '0';
done();
},
beforeLeave(el) {
el.style = {
transition: 'all 0.6s',
marginLeft: '0',
};
},
leave(el, done) {
el.style.marginLeft = '-20em';
done();
},
See modified codepen. You can see that the sidebar still moves, but instantaneously with no animation. I thought that maybe wrapping the done
callback in setTimeout
to allow the transition to complete would help, but it does not.
请查看修改后的 CodePen。你可以看到侧边栏仍然移动,但没有动画效果。我想可能将 `done` 回调包装在 `setTimeout` 中以允许过渡完成会有所帮助,但并没有。
I know I could use a library like Velocity or I could manually code the animation, but it seems like there should be a way to just let CSS take care of it. What am I missing?
我知道我可以使用像 Velocity 这样的库,或者手动编写动画,但似乎应该有办法让 CSS 自行处理这件事。我错过了什么?
问题解决:
First, your way to set style just not work
首先,你设置样式的方式就是不正确的。
el.style = {
transition: 'all 0.6s',
marginLeft: '-20em',
};
I just move transition: 'all 0.6s'
to css and set style like this
我只是把 `transition: 'all 0.6s'` 移到 CSS 中,并这样设置样式。
el.style.marginLeft = '-20em';
Second, enter
event is called very soon after beforeEnter
event so the browser cannot detect change between two states. So I wrap enter
event into setTimeout
to make a trick to trigger the transition.
第二,`enter` 事件在 `beforeEnter` 事件之后很快被调用,因此浏览器无法检测到两个状态之间的变化。所以我把 `enter` 事件包装在 `setTimeout` 中,以此来触发过渡效果。
Third, done
callback is not necessary in this case. It's only required in pure js transition. We are using mixed CSS and JS
第三,在这种情况下,`done` 回调并不是必需的。它只在纯 JavaScript 过渡中需要,我们这里使用的是混合的 CSS 和 JS。
Vue.component('app', {
template: `<div class="app">
<sidebar>sidebar content</sidebar>
<div class="main">Hello, VueJS!</div>
</div>`
});
Vue.component('sidebar', {
template: ` <div class="sidebarContainer">
<transition
name="slide"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<div v-if="isOpen" class="sidebar" :style="{ width }">
<slot/>
</div>
</transition>
<div class="toggle" @click="isOpen = !isOpen"><></div>
</div>`,
props: {
'width': {
default: '20em',
}
},
data() {
return {
isOpen: true,
};
},
methods: {
beforeEnter(el) {
el.style.marginLeft = '-20em';
},
enter(el, done) {
// Wait a tick here, so browser can detect style change and tigger transition
setTimeout(() => {
el.style.marginLeft = '0';
}, 0)
},
leave(el, done) {
el.style.marginLeft = '-20em';
},
},
});
new Vue({
el: '#app',
template: '<app/>'
});
html,
body,
.app {
height: 100%;
}
.app {
display: flex;
}
.main {
flex-grow: 1;
background: red;
}
.sidebarContainer {
display: flex;
}
.sidebar {
flex-grow: 1;
padding: 0.5em;
background: blue;
transition: all 0.6s;
}
.toggle {
margin: 0.5em;
}
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>