Vue 中大型交错列表的速度动画

Velocity-animate for large staggered lists in Vue

我有一个很大的列表,我正在介绍velocity-animate into. At the moment, my test list has 59 individual items that a user can filter by search query (potentially could grow to hundreds of items). I am animating currently according to the way the Vue docs显示它已经完成,但当然他们的列表只有几个项目。

在我的应用程序中,动画需要几秒钟才能完成,这绝对不理想。有什么“批量”动画的方法吗?我认为理想情况下会发生的事情是我会对输入进行去抖动,这样只有一两秒钟后,搜索才会真正执行。然后在该方法中,过滤项目,所有不匹配的项目都会消失。我怎样才能做到这一点?

<template>
  <v-text-field
    background-color="#fff"
    clearable
    dense
    flat
    hide-details
    label="Search"
    placeholder="Search"
    solo-inverted
    :value="searchQuery"
    @click:append="clearSearch"
    @input="debounceSearchQuery" />
  <v-list>
    <v-list-item-group>
      <draggable
        class="drag-area list-group"
        :group="{ name: 'items', pull: 'clone', put: false}"
        handle=".gear-handle"
        :list="filteredItems"
        :sort="false"
        @change="log">
          <transition-group
            :css="false"
            name="staggered-fade"
            @before-enter="beforeEnter"
            @enter="enter"
            @leave="leave">
            <v-list-item
              v-for="(item, i) in filteredItems"
              :key="item.id"
              :data-index="i">
              <p class="mb-0 white--text">
                {{ item.name }}
              </p>
            </v-list-item>
          </transition-group>
        </draggable>
      </v-list-item-group>
    </v-list>
</template>

export default {
  data: () => ({
    searchQuery: ''
  }),

  computed: {
    filteredItems () {
      const filteredItems = [];
      this.categories.forEach(category => {
        if (this.searchQuery) {
          const text = this.searchQuery.toLowerCase();
          filteredItems.push(category.items.filter(item => item.name && item.name.toLowerCase().includes(text)));
        } else {
          filteredItems.push(category.items);
        }
      });
      return filteredItems.flat();
    },
  },

  methods: {
  // gsap methods //
    beforeEnter (el) {
      el.style.opacity = 0;
      el.style.height = 0;
    },
    enter (el, done) {
      const delay = el.dataset.index * 150;
      setTimeout(() => {
        this.$velocity(
          el,
          { opacity: 1, height: '1.6em', duration: 50 },
          [0.57, 0.06, 0, 1.06],
          { complete: done }
        );
      }, delay);
    },
    leave (el, done) {
      const delay = el.dataset.index * 150;
      setTimeout(() => {
        this.$velocity(
          el,
          { opacity: 0, height: 0 },
          { complete: done }
        );
      }, delay);
    },
    // end gsap methods
    debounceSearchQuery: debounce(function (e) {
      this.searchQuery = e;
    }, 1000)
  }
}

我认为“交错”动画和“批量”运行 过渡更像是两种不同的东西——这几乎就像你不能吃蛋糕一样。

尽管如此,如果您只是追求具有惊人效果的平滑滑动过渡,您实际上可以仅使用 <transition-group> + CSS 来实现(不需要过渡挂钩)。这是一个简单的示例(单击“运行”,然后单击“全页”以获得更好的视图):

new Vue({
  vuetify: new Vuetify({
    theme: {
      dark: true
    }
  }),

  data: () => ({
    searchQuery: '',
    categories: [
      { items: [{ name: 'vue.js', id: 1 }] },
      { items: [{ name: 'react.js', id: 2 }] },
      { items: [{ name: 'angular.js', id: 3 }] },
      { items: [{ name: 'velocity-animate', id: 4 }] },
      { items: [{ name: 'lodash', id: 5 }] },
      { items: [{ name: 'debounce', id: 6 }] },
    ]
  }),

  computed: {
    filteredItems() {
      const filteredItems = [];
      const text = this.searchQuery.toLowerCase();

      this.categories.forEach(category => {
        if (this.searchQuery) {
          filteredItems.push(category.items.filter(item =>
            item.name && item.name && item.name.toLowerCase().includes(text)));
        } 
        else {
          filteredItems.push(category.items);
        }
      });
      return filteredItems.flat();
    },
  },

  methods: {
    debounceSearchQuery: _.debounce(function(e) {
      this.searchQuery = e;
    }, 200),
    
    clearSearch() {
      this.searchQuery = '';
    }
  }
}).$mount('#app');
.staggered-fade-item {
  transition-timing-function: cubic-bezier(0.57, 0.06, 0, 1.06);
  transition-duration: 500ms;
  transition-property: opacity, transform;
}

.staggered-fade-enter,
.staggered-fade-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">

<div id="app">
  <v-app>
    <v-main>
      <v-container>
        <v-text-field
          @click:append="clearSearch"
          @input="debounceSearchQuery"
          :value="searchQuery"
          label="Search"
          placeholder="Search"
          autocomplete="off"
          solo-inverted clearable dense flat hide-details autofocus dark>
        </v-text-field>

        <v-list>
          <transition-group name="staggered-fade" tag="v-list-item-group">
            <v-list-item 
              v-for="item in filteredItems" :key="item.id"
              class="staggered-fade-item">
              <p class="mb-0 white--text">
                {{ item.name }}
              </p>
            </v-list-item>
          </transition-group>
        </v-list>
      </v-container>
    </v-main>
  </v-app>
</div>

另一个建议是 Velocity UI pack。这个插件允许你做序列 运行 并且它带有一些预注册的效果,这对于惊人的效果非常有用。 (查看文档以获得更多过渡效果)。

new Vue({
  vuetify: new Vuetify({
    theme: {
      dark: true
    }
  }),

  data: () => ({
    searchQuery: '',
    categories: [
      { items: [{ name: 'vue.js', id: 1 }] },
      { items: [{ name: 'react.js', id: 2 }] },
      { items: [{ name: 'angular.js', id: 3 }] },
      { items: [{ name: 'velocity-animate', id: 4 }] },
      { items: [{ name: 'lodash', id: 5 }] },
      { items: [{ name: 'debounce', id: 6 }] },
    ]
  }),

  computed: {
    filteredItems() {
      const filteredItems = [];
      const text = this.searchQuery.toLowerCase();

      this.categories.forEach(category => {
        if (this.searchQuery) {
          filteredItems.push(category.items.filter(item =>
            item.name && item.name && item.name.toLowerCase().includes(text)));
        } 
        else {
          filteredItems.push(category.items);
        }
      });
      return filteredItems.flat();
    },
  },

  methods: {
    debounceSearchQuery: _.debounce(function(e) {
      this.searchQuery = e;
    }, 200),
    
    clearSearch() {
      this.searchQuery = '';
    }
  },
  
  watch: {
    filteredItems() {
      Velocity(
        this.$refs.items.map(vnode => vnode.$el),
        'transition.flipYIn', {
          stagger: 100,
          drag: true
        }
      );
    }
  }
}).$mount('#app');
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script src="https://cdn.jsdelivr.net/npm/velocity-animate@1.5.2/velocity.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/velocity-animate@1.5.2/velocity.ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">

<div id="app">
  <v-app>
    <v-main>
      <v-container>
        <v-text-field
          @click:append="clearSearch"
          @input="debounceSearchQuery"
          :value="searchQuery"
          label="Search"
          placeholder="Search"
          autocomplete="off"
          solo-inverted clearable dense flat hide-details autofocus dark>
        </v-text-field>

        <v-list>
          <v-list-item-group>
            <v-list-item 
              v-for="item in filteredItems" :key="item.id"
              ref="items">
              <p class="mb-0 white--text">
                {{ item.name }}
              </p>
            </v-list-item>
          </v-list-item-group>
        </v-list>
      </v-container>
    </v-main>
  </v-app>
</div>