Bootstrap

【小技巧】基于vue+swiper实现图片库列表点击查看大图,可左右切换(事件委托、动态数组、懒加载、缓存、节流防抖)

最近接到一个需求,实现起来很简单,但是考虑到性能问题,需要花一些技巧。

页面性能优化,分别应用到:
事件委托、动态数组、懒加载、缓存、节流防抖

场景和需求:
移动端一个图库列表页,几百张甚至几千张图片的列表展示
点击其中的一张图片,可以预览大图,左右切换查看所有其他图片的大图

- 第一部分:

问题:点击事件,如果每张图的标签都绑定了事件,在移动端,性能很糟糕。

解决办法:事件委托。

HTML结构:

<ul class="photo-items"  @click="previewPhoto" id='photo-list'>
      <li v-for="(item,index) in items" class="photo-item"  :data-index="index">
           <img :src="item.srcUrl"/>
      </li>
</ul>
previewPhoto(ev){
   let oUl = document.getElementById('photo-list');
   let ev = ev || window.event;
   let target = ev.target || ev.srcElement;
   while(target !== oUl ){
        //递归调用,使当前点击对象指定到li上
        if(target.tagName.toLowerCase() == 'li'){
             const index = parseInt(target.dataset.index)
             break;
        }
       target = target.parentNode;
    }
},

事件委托参考文章:https://www.cnblogs.com/liugang-vip/p/5616484.html

- 第二部分:

问题:使用swiper,左右切换图片,
如果需要查看几百张图片,就要生成几百个swiper-slide,对性能来说也是一件很糟糕的事情。

如图:
image.png

解决办法:使渲染swiper的数组永远只有三张图片,每次切换,根据索引让数组的三张图片变一次。

<swiper :options="swiperOption" ref="mySwiper" >
        <swiper-slide  v-for="(item,index) in swiperList" :key="index">
             <img :src="item.srcUrl"/>
        </swiper-slide>
</swiper>

image.png

computed: {
  swiperPicList(){
   if(this.items.length > 0){  //图片数组如果不为空
        //curIndex表示当前查看的图片索引
        if(this.curIndex == 0){  //当前图片为第一张时候,返回的数组
            return [
               {srcUrl: this.items[this.curIndex].srcUrl},
               {srcUrl: this.items[this.curIndex + 1].srcUrl},
            ] 
        }else if(this.curIndex == (this.items.length - 1)){ //当前图片为最后一张时候,返回的数组
             return [
                {srcUrl: this.items[this.curIndex-2].srcUrl},
                {srcUrl: this.items[this.curIndex-1].srcUrl},
                {srcUrl: this.items[this.curIndex].srcUrl},
              ]
         }else{ 
             return [
                {srcUrl: this.items[this.curIndex - 1].srcUrl},
                {srcUrl: this.items[this.curIndex].srcUrl},
                {srcUrl: this.items[this.curIndex + 1].srcUrl},
              ] 
         }
     }
  },
}

image.png

为什么是三张图?

因为swiper,有两个属性,当滑动到第一张的时候isBeginning为true,这可以当做用户向左滑的判断,当滑动到最后一张的时候isEnd为true,同理可以当做用户向右滑动的判断。

[
{srcUrl: this.items[this.curIndex - 1].srcUrl},//当前图片前一张
{srcUrl: this.items[this.curIndex].srcUrl},//当前图片
{srcUrl: this.items[this.curIndex + 1].srcUrl},//当前图片后一张
] 

然后使当前索引的图片永远在数组swiperPicList中间,
1、向左滑动的时候,curIndex–,向右滑动的时候 curIndex++

但是对于swiper中间选中项向左滑到第一张,第一张会变成选中项,用户看到的其实是数组第一张图。向右同理。

所以需要使用swiper方法this.swiper.slideTo(1,0, false),使得每次滑动之后,swiper都切换到第二个为选中项,这样用户看到是就是数组的中间项。

参考API:
mySwiper.slideTo(index, speed, runCallbacks)
Swiper切换到指定slide。
index:必选,num,指定将要切换到的slide的索引。
speed:可选,num(单位ms),切换速度
runCallbacks: 可选,boolean,设置为false时不会触发transition回调函数。

除了是第一张和最后一张的时候做特殊处理。

watch:{
   curIndex(curIndex){
       if(curIndex == 0){ 
         //当滑动到第一张图片的时候,返回的数组是两张图,
         //slideTo的index应该为0,跳转到第一张图。
         this.swiper.slideTo(0,0, false);
       }else if (this.curIndex == (this.items.length-1)){
         //当滑动到最后一张图片的时候,返回数组是三张图
         //slideTo的index应该为2,跳转到第三张图。
         this.swiper.slideTo(2,10, false); 
      }else {
         this.swiper.slideTo(1,0, false);
         //其他索引的图片都是跳到第二张图
     }
  }
},
computed: {
  swiper() {
    return this.$refs.mySwiper.swiper
  },
  swiperOption(){
    let _this = this  // _this为VUE实例,要特别注意
    return {
       initialSlide :1,
       on: {
         //滑动事件
         //on事件里面的this指向swiper实例,要特别注意
         transitionEnd: function(){
                //isEnd为true,表示用户向右滑动
                if(this.isEnd){
                   if(_this.curIndex < (_this.items.length-1)){
                        _this.curIndex = _this.curIndex + 1
                      }
                }
               //isBeginning为true,表示用户向左滑动
                if(this.isBeginning){
                   if(_this.curIndex >= 1){
                        _this.curIndex  = _this.curIndex - 1
                   }
                } 
               //这里做的是特殊处理,
              // 因为当前图片为最后一张时候,选中的图片为第三张,
              //swiperPicList数组中也是第三张,
              //最后一张滑动的方向只有向左,所以_this.curIndex - 1
             //做这个处理是最后一张向左滑动因为返回数组的原因,不能用isBeginning来判断
               if(_this.curIndex == (_this.items.length-1)){
                     _this.curIndex = _this.curIndex - 1
                }
          },
       }
     }
   },
}

最终效果
image.png

初衷是想让swiper不需要渲染所有的图片,临时做一个小数组,每次切换,小数组都是动态的获取相邻的三张照片,可能不是最优的方法,创建数组也可以进行再封装

以上只是传达一个优化思想

swiper参考文档:https://www.swiper.com.cn/api/methods/109.html

- 第三部分:

问题:图片列表,页面上几百上千张图片,用户访问页面,要拉很长时间,还要考虑用户在页面来回滚动的情况
一、图片多,要拉很长时间

①、懒加载,这个很简单。
②、缓存,这个也很简单。

二、考虑到用户在页面上下来回滚动

③、节流防抖

节流:在频繁触发的情况下,按照一定的时间去执行。

//声明一个变量当标志位,记录当前代码是否在执行
const Throttling = (fn,intelval) => {
	let time = null;
		return function (){
			if(!time){
				time = setTimeout(() => {
					fn.call(this,arguments)
					time = null
				},intelval) 
			}
	 }
}

防抖:在频繁触发的情况下,只有足够的空闲时间,才执行一次。

//setTimeout做缓存池
const Throttling = (fn,delay) => {
	let time;
	return function (){
		if(time) clearTimeout(time)
		time = setTimeout(() => {
		   fn.call(this,arguments)
		},delay) 
	}
}
;