Vue过渡的动态样式

时间:2019-03-29 01:40:27

标签: javascript css vue.js

我正在研究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">&lt;&gt;</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>

工作codepen。它完全符合我的要求, 除外,过渡的宽度(按.slide-enter, .slide-leave-to样式指定)是硬编码的,并且对组件width属性没有响应。如果您设置了width=30em,则过渡是跳跃的。

我怀疑我可能需要使用transition hooks,但似乎无法正常使用。我尝试过:

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();
},

请参见modified codepen。您可以看到侧边栏仍在移动,但是瞬间没有动画。我认为也许将done回调包装在setTimeout中以允许完成过渡会有所帮助,但这没有帮助。

我知道我可以使用Velocity之类的库,也可以手动编写动画,但是似乎应该有一种方法可以让CSS来处理它。我想念什么?

1 个答案:

答案 0 :(得分:1)

首先,您设置样式的方法不起作用

el.style = {
   transition: 'all 0.6s',
   marginLeft: '-20em',
};

我只是将transition: 'all 0.6s'移至CSS并设置样式

el.style.marginLeft = '-20em';

第二, enter事件在beforeEnter事件之后很快被调用,因此浏览器无法检测到两种状态之间的变化。因此,我将enter事件包装到setTimeout中来欺骗触发转换。

在这种情况下,第三次 done回调是不必要的。仅在纯js转换中才需要。我们正在使用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">&lt;&gt;</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>