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参数表示当前页的索引,当前返回的只有0、1、2三个,往后切换的顺序是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组件提供的方法去切换,其中prev和next已经可以直接调用了,但是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)