Search code examples
javascriptvue.jscss-transitions

How can I animate an element added dynamically to a list, in Vue.js 3?


I am working on a small Todo App with Vue 3. I want the appending of a new to-do item to be smooth, not instantaneous.

For this purpose, I have added the class active to the latest to-do item:

<template>
  <ul class="todo-list" v-if=dataIsLoaded>
      <TodoItem v-for="(todo, index) in todos.slice().reverse()"
        :key="todo.id" 
        :class="{done: todo.completed, active: index == 0}" 
        :todo="todo" 
        @delete-todo="$emit('delete-todo', todo.id)"
        @toggle-todo="$emit('toggle-todo', todo)"
        />
    </ul>
    <div class="loader" v-else></div>   
</template>

In the CSS I have:

li:first-child {
  opacity: 0;
  transform: translateX(-295px);
  transition: all 0.3s ease;
}

li.active {
  opacity: 1;
  transform: translateX(0);
}

It does not work as I expected hoped. It might be because the CSS is loaded late, it might be for another reason I was unable to figure out.

What is the easiest solution to this problem?


Solution

  • You can make use of Vue list transitions. I've simplified a bit your code for demonstration purposes. Also I've replaced your component TodoItem with a li tag (assumed that it contains a li as the root element).

    new Vue({
     el: "#app",
     data: {
        todos: [
         { id: 1, text: "Todo 1" },
         { id: 2, text: "Todo 2" },
         { id: 3, text: "Todo 3" }
         ]
      },
     methods: {
        add(){
        this.todos.push({ id: 4, text: "Todo 4"})
        }
      }
    })
    .list-enter-active, .list-leave-active {
      transition: all 1s;
    }
    .list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ {
      opacity: 0;
      transform: translateX(-295px);
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <div id="app">
      <transition-group name="list" tag="ul">
          <li v-for="(todo, index) in todos.slice().reverse()"
            :key="todo.id" 
            >{{ todo.text }}</li>
       </transition-group>
        <button @click="add">Add</button>
    </div>