2

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>
tony19
  • 125,647
  • 18
  • 229
  • 307
Iworb
  • 522
  • 7
  • 29

1 Answers1

2

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/vue@2.5.16/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/vue@2.5.16/dist/vue.js"></script>
tony19
  • 125,647
  • 18
  • 229
  • 307
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
  • 1
    Meh. Apparently workaround works visually, but if there is content below the list it still gets pushed down, so not really useful... see https://codepen.io/anon/pen/zaMbwp – Traxo Jun 28 '18 at 19:21
  • @Traxo You'll always have to tailor transitions to your use-case as they'll always push the content below. For most cases, the simple example I gave is enough, or it's a good starting point before trying to [implement your own animation logic](https://stackoverflow.com/a/49882733/1218980). – Emile Bergeron Jun 28 '18 at 20:13
  • >`You'll always have to tailor transitions to your use-case as they'll always push the content below. ` Not sure what you mean by that because on ``s we can use `mode="out-in"` specifically to avoid that problem. And then we can simply reuse CSS provided in example. So I disagree that transitions will _always_ push the content down. Well it will unless we tell it not to, lol. But sometimes it's easy to tell it so (e.g. just use mode="out-in" when possible), and sometimes as for example in this case, is a bit harder. – Traxo Jun 28 '18 at 20:37
  • And I have upvoted your answer because it's useful of course, but I left comment for possible future visitors who stumble upon this issue, so that they save a bit time if they have same use case as I did. – Traxo Jun 28 '18 at 20:42
  • 1
    @Traxo Yeah thanks for the upvote! Just for the sake of the conversation: Mode `out-in` does nothing to prevent pushing the content around, you have to provide your own styling. It's just easier when it's only transitioning between 2 similar elements (or elements with a fixed size). – Emile Bergeron Jun 28 '18 at 20:43
  • 1
    Agree. However, there is specific usecase in vue-docs which deals with this problem (unwanted content pushing), and mode="out-in" is used to resolve it. So it's very easy to resolve with `mode`, and one could thus presume that we can solve list-transitions with same property, but we can't. And that is evident even from this very question :) And the fact that docs will be updated with regards to this use-case (or they were updated already but I haven't noticed?) – Traxo Jun 28 '18 at 20:49
  • 1
    I updated the answer to mention it's now in the documentation. – Emile Bergeron Jun 28 '18 at 21:12