VueJS JS-only 转换钩子需要 setTimeout 才能使 CSS 转换工作

Posted

技术标签:

【中文标题】VueJS JS-only 转换钩子需要 setTimeout 才能使 CSS 转换工作【英文标题】:VueJS JS-only transition hook requires setTimeout for CSS transition to work 【发布时间】:2019-08-10 21:38:54 【问题描述】:

我正在为 VueJS 上的 <transition-group> 元素使用纯 JS 钩子,我对 enter 钩子的实际工作方式感到非常困惑。根据文档,我知道我将不得不call done() to avoid events being called synchronously:

使用纯 javascript 转换时,enterleave 挂钩需要 done 回调。否则,钩子将被同步调用,并且过渡将立即完成。

但是,即使我使用它,它似乎也阻止了 CSS 过渡在 进入过渡 中发生。我找到的唯一解决方案是使用window.setTimeout 设置样式,我认为这是代码异味。这是没有超时的代码和有超时的代码之间的快速视觉比较(有超时的代码是想要的效果):

Broken enter 过渡(没有左侧填充和不透明度的过渡):

所需的输入转换:

在下面的示例中,我使用 <transition-group> 显示一个列表,并希望使用 JS-hooks 以便我可以在单个列表项上创建交错填充。除了在enter 转换中,填充属性上的 CSS 转换不起作用之外,它似乎可以工作。

new Vue(
  el: '#app',
  data: 
    items: [
      'Lorem',
      'Ipsum',
      'Dolor',
      'Sit',
      'Amet'
    ],
    toggle: false
  ,
  computed: 
    filteredItems: function() 
      if (!this.toggle)
        return [];

      return this.items;
    
  ,
  methods: 
    toggleItems: function() 
      this.toggle = !this.toggle;
    ,
    beforeEnter: function(el) 
      el.style.paddingLeft = '0px';
      el.style.opacity = '0';
    ,
    enter: function(el, done) 
      el.style.paddingLeft = `$10 * +el.dataset.indexpx`;
      el.style.opacity = '1';
      done();
    ,
    beforeLeave: function(el) 
      el.style.paddingLeft = '0px';
      el.style.opacity = '0';
    
  
)
ul 
  list-style: none;
  margin: 0;
  padding: 0;


ul li 
  transition: all 500ms ease-in-out;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">

  <button @click="toggleItems">
    Toggle items
  </button>
  
  <transition-group
    tag="ul"
    @before-enter="beforeEnter"
    @enter="enter"
    @before-leave="beforeLeave">
    
    <li
      v-for="(item, i) in filteredItems"
      v-bind:key="i"
      v-bind:data-index="i">
       item 
    </li>
  </transition-group>
</div>

如果您将enter 方法中的所有逻辑封装在任意超时中,那么它可以工作:

enter: function(el, done) 
  window.setTimeout(() => 
    el.style.paddingLeft = `$10 * +el.dataset.indexpx`;
    el.style.opacity = '1';
    done();
  , 100);
,

这就是我有点困惑的地方:enter 钩子不等待beforeEnter 先完成吗?工作的sn-p如下

【问题讨论】:

你不应该在enter方法中调用donedone 仅在 JavaScript 转换中需要回调。您正在使用混合 CSS 过渡和 javascript 【参考方案1】:

@enter 挂钩更改为@after-enter 应该可以解决问题

我不知道为什么 @enter 钩子不起作用,因为查看文档 应该 但这至少应该摆脱超时而不是黑客 p>

new Vue(
  el: '#app',
  data: 
    items: [
      'Lorem',
      'Ipsum',
      'Dolor',
      'Sit',
      'Amet'
    ],
    toggle: false
  ,
  computed: 
    filteredItems: function() 
      if (!this.toggle)
        return [];

      return this.items;
    
  ,
  methods: 
    toggleItems: function() 
      this.toggle = !this.toggle;
    ,
    beforeEnter: function(el) 
      el.style.paddingLeft = '0px';
      el.style.opacity = '0';
    ,
    afterEnter: function(el) 
      el.style.paddingLeft = `$10 * +el.dataset.indexpx`;
      el.style.opacity = '1';
    ,
    beforeLeave: function(el) 
      el.style.paddingLeft = '0px';
      el.style.opacity = '0';
    
  
)
ul 
  list-style: none;
  margin: 0;
  padding: 0;


ul li 
  transition: all 500ms ease-in-out;


li.v-enter-active 
  transition: none
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">

  <button @click="toggleItems">
    Toggle items
  </button>

  <transition-group 
    tag="ul" 
    @before-enter="beforeEnter"
    @after-enter="afterEnter" 
    @before-leave="beforeLeave">
    <li v-for="(item, i) in filteredItems" v-bind:key="i" v-bind:data-index="i">
       item 
    </li>
  </transition-group>
</div>

附带说明,如果您使用的是 SCSS 或 SASS,则可以使用它而不是 JavaScript 来实现这一点

【讨论】:

感谢您的回答 :) 尽管这确实解决了问题,但您会注意到在列表项实际过渡之前存在一些延迟。我怀疑 after-enter 挂钩被称为有点晚了。 @Terry 嗨,我现在刚刚编辑了答案,这是因为 @before-enter@after-enter 的转换正在运行/执行,所以它需要 1s 一半的时间是不可见的,添加一个新的 ccs 规则可以解决这个问题。

以上是关于VueJS JS-only 转换钩子需要 setTimeout 才能使 CSS 转换工作的主要内容,如果未能解决你的问题,请参考以下文章

vuejs啥时候使用钩子函数

VueJS updated() 生命周期钩子导致无限循环

从 vuejs 组件中创建的钩子调用方法

Vuejs:使用keep alive的子routerview组件的生命周期钩子

在 onMounted 钩子中使用 watch 或设置 vuejs 3

开发Vue插件四种方式