Анимация каждого элемента отдельно в цикле v -for (Vue.JS )

#javascript #animation #vue.js

#javascript #Анимация #vue.js

Вопрос:

Я создал простое приложение для выполнения задач, используя VueJS. Я также добавил vue2-animate (A Vue.js порт Animate.css 2.0. Для использования со встроенными переходами Vue.) Анимация добавления одного элемента работает корректно.

Но были две проблемы, которые я хотел бы решить без ненужного кодирования:

  1. Отображение анимации для списка загруженных из локального хранилища работает для всех элементов одновременно. Мне нужно, чтобы анимация работала последовательно для каждого элемента в отдельности.
  2. Анимация удаления элемента работает некорректно — всегда удаляется последний элемент, а затем следует сдвиг.

PS: Посмотрите демонстрацию в JSFiddle, потому что localstorage не работает во фрагментах SO.

 Vue.component("adder", {
  data: function() {
    return {
      task: ""
    };
  },
  template: `
    <div class="input-group mb-3">
    <input type="text" class="form-control" placeholder="New task..." aria-label="New task..." aria-describedby="" v-model="task" v-on:keyup.enter="add">
    <div class="input-group-append">
      <button class="btn btn-primary" id="" v-on:click="add" > </button>
    </div>
    </div>
    `,
  methods: {
    add: function() {
      this.$emit("addtodo", {
        title: this.task,
        done: false
      });
      this.task = "";
    }
  }
});

Vue.component("todo", {
  props: ["item"],
  template: `

    <a href="#" class="list-group-item list-group-item-action task" v-bind:class="{'disabled done' : item.done==true}">
    <label class="form-check-label">
            <input class="form-check-input" type="checkbox" name="" id="" value="checkedValue"  v-model="item.done"> {{item.title}}
        </label>
        <button type="button" class="close" aria-label="Close" v-on:click="del">
            <span aria-hidden="true">amp;times;</span>
        </button>
    </a>
    
    `,
  methods: {
    del: function() {
      this.$emit("deletetodo");
    }
  }
});

Vue.component("todos", {
  props: ["items"],
  template: `
    <div class="list-group">
        <transition-group name="bounceLeft" tag="a">
            <todo v-for="(item, index) in items" :key="index" :item.sync="item" v-on:deletetodo="delTodo(item)"></todo>
        </transition-group>
    </div>
    `,
  methods: {
    delTodo: function(i) {
      this.$emit("deletetodo", i);
    }
  }
});
Vue.config.devtools = true;

let app = new Vue({
  el: ".todoapp",
  data: {
    title: "Todo App",
    items: []
  },
  methods: {
    addTodo: function(e) {
      this.items.push(e);
    },
    delTodo: function(i) {
      this.items = this.items.filter(e => e != i);
    }
  },
  mounted() {
    if (localStorage.items) {
      this.items = JSON.parse(localStorage.getItem("items"));
    }
  },
  watch: {
    items: {
      handler(val) {
        localStorage.setItem("items", JSON.stringify(this.items));
      },
      deep: true
    }
  }
});  
 .done>label {
  text-decoration: line-through;
}

.task {
  padding-left: 36px;
}  
 <!DOCTYPE html>
<html lang="en">

<head>
  <title>Todo App</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />

  <link rel="stylesheet" href="https://unpkg.com/vue2-animate/dist/vue2-animate.min.css" />
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <div class="container todoapp">
    <div class="row">
      <br />
    </div>
    <div class="card">
      <div class="card-header">
        {{ title }}
      </div>
      <div class="card-body">
        <adder v-on:addtodo="addTodo"></adder>
        <todos :items.sync="items" v-on:deletetodo="delTodo"></todos>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X 965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH 8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV 2 9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3 MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
  <script src="script.js"></script>
</body>

</html>  

Демонстрация JSFiddle

Ответ №1:

Хорошо, беру это по одному за раз:

Удаление задачи

Причина, по которой всегда кажется, что удаляемая задача удаляется последней, заключается в том, что вы вводите элементы своего списка с помощью index . Когда вы заменяете весь items массив в вашем delTodo методе, который, в свою очередь, выдает вам новый массив с новыми ключами для каждого элемента в списке. Нажимайте item , и вы получите правильный результат:

 <todo v-for="(item, index) in items" :key="item" :item.sync="item" v-on:deletetodo="delTodo(item)"></todo>
  

Отображение задач по одной при загрузке

Мой совет состоял бы в том, чтобы подходить к показу / скрытию задач с вычисляемым свойством:

 computed: {
    tasks: function(){
        return this.items.filter(item => item.isVisible);
    }
}
  

Здесь мы будем показывать / скрывать, переключая isVisible для каждой задачи.

Это означает, что при первоначальной загрузке задач из локального хранилища вы могли бы установить для них всех значение isVisible: false , а затем использовать setTimeout в for цикле для отображения их всех по одному за раз:

 mounted() {
    // Get your items and set all to hidden
    if (localStorage.items) {
        this.items = JSON.parse(localStorage.getItem("items"))
                     .map(item => item.isVisible = false);
    }

    // Loop through and show the tasks
    for(let i=1; i<=this.items.length; i  ){
        // Where 300 is milliseconds to delay
        let delay = i * 300;

        setTimeout(function(){
            this.items[i].isVisible = true;
        }.bind(this), delay);
    }
},
  

Ответ №2:

Лучше всего сработало поэтапное добавление в массив items:

   mounted() {
    let items = [];
    if (localStorage.items) {
      items = JSON.parse(localStorage.getItem("items"))
    }
    for (let i = 0; i < items.length; i  ) {
      let delay = i * 1000;
      setTimeout(
        function() {
          this.items.push(items[i])
        }.bind(this),
        delay
      )
    }
  }
  

Комментарии:

1. Вы можете использовать обозначения со стрелками для этой функции вместо того, чтобы привязывать это к вашей функции. () => this.items.push(элементы[i]) Это обозначение сохраняет ‘this’ (или предполагается, что это. У меня было несколько странных сценариев, когда этого не происходило)

Ответ №3:

Просто чтобы добавить к разговору, следующее обеспечивает ошеломляющее действие в Vuex и использует синтаксис жирной стрелки:

 async fetchRepositories( {commit} ){
  const response = await gitHubApi.get<Repository[]>('/users/rodolphocastro/repos') // Calling API with Axios
  const staggered: Repository[] = []
  response.data.forEach((r, i) => {
    const delay = i * 300 // 300m -> Time to wait for each item in the array
    setTimeout(() => {
      staggered.push(r)
      commit('setRepositories', staggered)
    }, delay)
  })
}