Dynamic style for Vue transition

题意:动态样式的 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">&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>

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">&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>

猜你喜欢

转载自blog.csdn.net/suiusoar/article/details/143487674