Iworb
Iworb

Reputation: 532

Vue.js transition on replacing list

I faced up with problem which described in vue.js official documentation here, but with kidna different data. I want to create tree-like structure with items and sub-items to describe tree (files and folders structure is good example). To make some visual enhancement I wanted to make them sliding, but got that. mode="out-in" was already set and made no effect.

Any idea how to fix this transition?

Vue.component('booster', {
    props: {
        item: {
            type: Object
        }
    },
    template: '<div class="booster" @click="$emit(\'click\')"><img :src="item.image"></div>'
});

Vue.component('boosters', {
    data: function() {
        return {
            boosters: this.items,
            path: [],
            root: this.items
        };
    },
    props: {
        items: {
            type: Array
        },
        item_up: {
            type: Object,
            default: function() {
                return {
                    name: "Up",
                    image: "http://via.placeholder.com/128x178/000000/ffffff?text=↑"
                };
            }
        }
    },
    methods: {
        navigate: function(item) {
            var self = this;
            if (item === self.item_up && self.path.length) {
                self.root = self.path.pop();
            } else if ("undefined" !== typeof item.items) {
                self.path.push(self.root);
                self.root = [self.item_up].concat(item.items);
            } else {
                console.log(item.name);
            }
        }
    },
    template: '<transition-group name="slide" mode="out-in" tag="div" class="boosters"><template v-for="item in root"><booster :item="item" :key="item.name" @click="navigate(item)"></booster></template></transition-group>'
});

var vue = new Vue({
    el: '#content'
});
#content {
    margin: 4rem;
}

.boosters {
    display: flex;
    flex-wrap: wrap;
    align-content: center;
}

.booster {
    box-shadow: 0px 0px 6px 3px black;
    box-sizing: border-box;
    margin: 15px;
}

.booster img {
    width: 128px;
    height: 178px;
    display: block;
}

.slide-enter-active, .slide-leave-active {
    transition: all 0.6s ease-in-out;*/
}

.slide-move {
    transition: transform 0.5s;
}

.slide-enter {
    transform: translateY(-100%);
}

.slide-leave-to {
    transform: translateY(100%);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.3/vue.min.js"></script>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<div id="content">
    <boosters :items='[
            {name:"First",image:"http://via.placeholder.com/128x178?text=1",items:[
                {name:"Sub-first-1",image:"http://via.placeholder.com/128x178?text=1.1"},
                {name:"Sub-first-2",image:"http://via.placeholder.com/128x178?text=1.2"}
                ]},
            {name:"Second",image:"http://via.placeholder.com/128x178?text=2", items:[
                {name:"Sub-second-1",image:"http://via.placeholder.com/128x178?text=2.1"},
                {name:"Sub-second-2",image:"http://via.placeholder.com/128x178?text=2.2"}
                ]},
            {name:"Third",image:"http://via.placeholder.com/128x178?text=3"}
            ]'>
    </booster>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
    </div>
</body>
</html>

Upvotes: 2

Views: 6679

Answers (1)

Emile Bergeron
Emile Bergeron

Reputation: 17430

Why transition group with mode does nothing?

transition-group doesn't have a mode prop. It is explicitly deleted from the props in the transition-group.js component source:

const props = extend({
  tag: String,
  moveClass: String
}, transitionProps)

delete props.mode

As Evan You said:

This is unlikely to happen due to the sheer complexity - it will likely introduce too much extra code for a relatively non-critical use case, and the behavior of the transition modes on multiple items can be vague and hard to define. Even if we were to implement it, we'd probably ship it as a separate plugin instead of as part of core.

I opened an issue to mention that in the documentation on list transition. It's now in the documentation:

Transition modes are not available, because we are no longer alternating between mutually exclusive elements.


How to simulate the out-in transition with a full list

A small workaround mentioned by NonPolynomial in the issue is to use transition-delay to delay the entering animation to after the leaving animation is done.

new Vue({
  el: '#app',
  data: {
    elements: [
      [1, 2, 3],
      [4, 5, 6, 7]
    ],
    index: 0
  },
});
.fade-out-in-enter-active,
.fade-out-in-leave-active {
  transition: opacity .5s;
}

.fade-out-in-enter-active {
  transition-delay: .5s;
}

.fade-out-in-enter,
.fade-out-in-leave-to {
  opacity: 0;
}
<div id="app">
  <button type="button" @click="index = (index + 1) % elements.length">Swap</button>

  <transition-group tag="ul" name="fade-out-in">
    <li v-for="num in elements[index]" :key="num">
      {{num}}
    </li>
  </transition-group>
</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>

As an alternative, the documentation on Staggering List Transitions has a good example of how to handle the transition with JavaScript for a full control.

new Vue({
  el: '#staggered-list-demo',
  data: {
    query: '',
    list: [
      { msg: 'Bruce Lee' },
      { msg: 'Jackie Chan' },
      { msg: 'Chuck Norris' },
      { msg: 'Jet Li' },
      { msg: 'Kung Fury' }
    ]
  },
  computed: {
    computedList: function () {
      var vm = this
      return this.list.filter(function (item) {
        return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
      })
    }
  },
  methods: {
    beforeEnter: function (el) {
      el.style.opacity = 0
      el.style.height = 0
    },
    enter: function (el, done) {
      var delay = el.dataset.index * 150
      setTimeout(function () {
        Velocity(
          el,
          { opacity: 1, height: '1.6em' },
          { complete: done }
        )
      }, delay)
    },
    leave: function (el, done) {
      var delay = el.dataset.index * 150
      setTimeout(function () {
        Velocity(
          el,
          { opacity: 0, height: 0 },
          { complete: done }
        )
      }, delay)
    }
  }
})
<div id="staggered-list-demo">
  <input v-model="query">
  <transition-group
    name="staggered-fade"
    tag="ul"
    v-bind:css="false"
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
  >
    <li
      v-for="(item, index) in computedList"
      v-bind:key="item.msg"
      v-bind:data-index="index"
    >{{ item.msg }}</li>
  </transition-group>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>

Upvotes: 3

Related Questions