vant Swipe组件虚拟轮播

1,655 阅读3分钟

vant 组件库使用 Swipe 组件,只展示几个页面没什么大问题,但是当个数达到成千上万个的时候,就会出现各种问题,加载慢、滑动卡顿,甚至白屏加载不出来。

遇到这样的问题怎么去解决?这个问题跟滚动加载数据有点类似,使用虚拟滚动可以处理不断下拉加载节点越来越多出现卡顿、崩溃的问题,Swipe处理原理也应该一样,只加载部分需要的节点,但是有一点不一样,当前内容到最后一个,不能直接返回第一个继续,而是要无感知的往后继续切换,往前也一样。

首先,要达到上面的效果,需要配置循环滚动,SwipeItem 只需要三个就可以。代码如下:

<div id="app">
  <!-- loop 循环滚动 -->
  <Swipe class="my-swipe" loop :show-indicators="false">
    <!-- 显示3个 -->
    <SwipeItem v-for="i in 3" :key="i">
      <img :src="lists[current]" />
    </SwipeItem>
  </Swipe>
</div>
import { Swipe, SwipeItem } from 'vant';

export default {
  name: 'App',
  components: {
    Swipe,
    SwipeItem,
  },
  setup() {
    return {
      current: 0, // 表示循环列表中的实际下标
      lists: [
        'https://64.media.tumblr.com/e746384d214dcc8240d5e898ce871db8/tumblr_ng37kbjFNO1qfirfao1_1280.jpg',
        'https://64.media.tumblr.com/76e0830e214ebdef7cead2769697035e/tumblr_nsoc5ikBbb1qfirfao1_1280.jpg',
        'https://64.media.tumblr.com/bd52bc6811c23836b64bbf96b64ce5d7/tumblr_nsoavu8uyV1qfirfao1_1280.jpg',
        'https://64.media.tumblr.com/54effd3041490f14c2e4786c190641b7/tumblr_nnyp46Nruh1qfirfao1_1280.jpg',
        'https://64.media.tumblr.com/6a4762a136c226d3d407110095f93651/tumblr_nmxfkc5Udl1qfirfao1_1280.jpg',
        'https://64.media.tumblr.com/a445893bfd7968cb436bd6270e9a86e3/tumblr_nmxffl30MX1qfirfao1_1280.jpg',
        'https://64.media.tumblr.com/f2e6041172a6896bd550a9263393271e/tumblr_nmkinjQnM81qfirfao1_1280.jpg',
        'https://64.media.tumblr.com/e27c4cb06cd3cd7c2e2fa2b5e039486f/tumblr_nlu5cm6tKL1qfirfao1_1280.jpg',
      ],
    };
  },
};

然后就是监听切换的动作,显示对应下标的内容,上面是图片。Swipe 组件中提供 change 事件,事件回传一个index参数表示当前页的索引,当前返回的只有012三个,往后切换的顺序是0 - 1 - 2 - 0,往前切换的顺序是0 - 2 - 1 - 0,了解了切换规律就可以确定是current值是增加还是减少了。代码如下:

import { ref } from 'vue';

const prevIndex = ref(0);
const swipeChange = (index) => {
  if (index - prevIndex.value === 1 || index - prevIndex.value === -2) {
    // 往后切换
    current.value++;
  }
  if (index - prevIndex.value === -1 || index - prevIndex.value === 2) {
    // 往前切换
    current.value--;
  }
  prevIndex.value = index;
};

上一步已经完成切换显示了,但是你应该会发现一个问题,切换完成还要加载内容,这样体验不好,所以需要做下优化,预加载上一页和下一页内容。

// 把图片请求保存成blob url
const setBlobUrl = (index) => {
  return fetch(lists[index])
    .then((resp) => resp.blob())
    .then((res) => {
      cacheLists.value[index] = URL.createObjectURL(res);
    });
};
// 初始化
setBlobUrl(0);
// 监听current改变,判断前一个和后一个是否需要缓存
watch(
  () => current.value,
  (value) => {
    if (lists[value - 1] && !cacheLists.value[value - 1]) {
      setBlobUrl(value - 1);
    }
    if (lists[value + 1] && !cacheLists.value[value + 1]) {
      setBlobUrl(value + 1);
    }
  },
  {
    immediate: true,
  }
);

通过上面的预加载缓存,已经几乎达到无缝切换了,对应接口请求也是一样的逻辑,但是还有一点不完美的,就是切换过程中会有一点空白,如果想继续优化,可以继续下面的代码,如果上一步的代码是懒加载的话,下面是直接加载完成的。

// 计算当前显示的3个数据
const showLists = computed(() => {
  const ls = [];
  ls[prevIndex.value] = cacheLists.value[current.value];
  const prev = cacheLists.value[current.value - 1] || '';
  const next = cacheLists.value[current.value + 1] || '';
  if (prevIndex.value === 0) {
    ls[1] = next;
    ls[2] = prev;
  } else if (prevIndex.value === 2) {
    ls[1] = prev;
    ls[0] = next;
  } else {
    ls[0] = prev;
    ls[2] = next;
  }
  return ls;
});

解决了切换显示问题,也许你已经发现了,当在第一个或者最后一个,往前切或者往后切,会显示空白,因为前面和后面已经没有数据了。这个时候如果想循环切,可以判断一下如果是第一个,往前切的current应设置为内容列表最后的下标,如果是最后一个往后切,current应该设置为0。如果不想要循环滚动,那就要禁用touchmove

// 禁用第一个和最后一个touchmove
let startX = 0;
const ontTouchStart = (event) => {
  startX = event.touches[0].clientX;
};

const onTouchmove = (event) => {
  const touch = event.touches[0];
  if (current.value === 0 && touch.clientX - startX > 0) {
    // 由于绑定的事件上一级节点有touchmove事件,所以禁止冒泡即可
    event.stopPropagation();
  }
  if (current.value === lists.length - 1 && touch.clientX - startX < 0) {
    event.stopPropagation();
  }
};

以上问题都解决了,但是处理手动切换,可能通过Swipe组件提供的方法去切换,其中prevnext已经可以直接调用了,但是swipeTo切换到对应下标的,在这里不能直接调用了,可以写个方法去模拟。

const swiper = ref();
let customSwipe = false;
let toIndex = 0;
const swipeTo = (to) => {
  // 需要切换的下标大于当前,用next方法模拟
  if (current.value < to) {
    swiper.value.next();
  }
  // 反之,使用prev方法
  if (current.value > to) {
    swiper.value.prev();
  }
  customSwipe = true; // 用于判断是否自定义的切换
  toIndex = to; // 跳转的下标
};

完整代码查看:stackblitz.com/edit/vue-zf…
个人网站:简单小记 (vekun.com)