Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to animate the sorting of a list with Vue.js

I’m trying to animate the sorting of a list with Vue.js, but not all items are animated. Do you know why? And how to make it work?

new Vue({
  el: '#app',
  data: {
    reverse: 1,
    items: [
      { name: 'Foo' },
      { name: 'Bar' },
      { name: 'Baz' },
      { name: 'Qux' }
    ]
  }
})
.moving-item {
  transition: all 1s ease;
  -webkit-transition: all 1s ease;
}
ul {
  list-style-type: none;
  padding: 0;
  position: relative;
}
li {
  position: absolute;
  border: 1px solid #42b983;
  height: 20px;
  width: 150px;
  padding: 5px;
  margin-bottom: 5px;
  color: #42b983;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.0-alpha.2/vue.min.js"></script>
<div id="app">
  <button on-click="reverse = Math.abs(reverse-1)">
    <span v-if="reverse == 0">△</span>
    <span v-if="reverse == 1">▽</span> Order
  </button>
  <ul>
    <li class="moving-item" v-for="item in items | orderBy 'name' reverse" bind-style="{ top: ($index * 35) + 'px'}">{{ item.name }}</li>
  </ul>  
</div>
like image 722
Samuel De Backer Avatar asked Jan 08 '23 15:01

Samuel De Backer


2 Answers

I believe the problem is that only one of the elements is remaining in the DOM during the sort. The other three are being removed and reinserted to satisfy the new ordering – but as a result they are not triggering an animation.

Typically, animation is done using the Vue transition system (http://vuejs.org/guide/transitions.html). However, the same basic problem of deletion and reinsertion not tracking position state will occur using that technique. Usually, items are animated independent of their previous and new positions (like fade-out in their old position and fade-in in their new one).

If you really need to animate from the the old position to the new one, I think you would need to write your own Javascript transition that remembers the previous position of each item before it is removed and animates it to the new position when it is inserted.

There is an example here which should be a good starting point: http://vuejs.org/guide/transitions.html#JavaScript_Only_Transitions

Another option is to not sort by a filter and do it in javascript instead (so that the v-for only renders once). Then target your bind-style against a new index parameter on your items like this:

new Vue({
  el: '#app',
  data: {
    reverse: 1,
    items: [
      { name: 'Foo', position: 0 },
      { name: 'Bar', position: 1 },
      { name: 'Baz', position: 2 },
      { name: 'Qux', position: 3 }
    ]
  },
  methods: {
    changeOrder: function (event) {
      var self = this;
      self.reverse = self.reverse * -1
      var newItems = self.items.slice().sort(function (a, b) { 
        var result;
        if (a.name < b.name) {
          result = 1
        }
        else if (a.name > b.name) {
          result = -1
        }
        else {
          result = 0
        }
        return result * self.reverse
      })
      newItems.forEach(function (item, index) {
        item.position = index;
      });
    }
  }
})
 
.moving-item {
  transition: all 1s ease;
  -webkit-transition: all 1s ease;
}
ul {
  list-style-type: none;
  padding: 0;
  position: relative;
}
li {
  position: absolute;
  border: 1px solid #42b983;
  height: 20px;
  width: 150px;
  padding: 5px;
  margin-bottom: 5px;
  color: #42b983;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.0-alpha.2/vue.min.js"></script>
<div id="app">
  <button on-click="changeOrder">
    <span v-if="reverse == -1">△</span>
    <span v-if="reverse == 1">▽</span> Order
  </button>
  <ul>
    <li class="moving-item" v-for="item in items" bind-style="{ top: (item.position * 35) + 'px'}">{{ item.name }}</li>
  </ul>  
</div>
like image 67
David K. Hess Avatar answered Jan 24 '23 20:01

David K. Hess


V2 has this built in: https://v2.vuejs.org/v2/guide/transitions.html#Transition-Modes

Check out "List Move Transitions" and the example with the shuffle button.

like image 23
Davis Avatar answered Jan 24 '23 21:01

Davis