最近接到一个需求,实现起来很简单,但是考虑到性能问题,需要花一些技巧。
页面性能优化,分别应用到:
事件委托、动态数组、懒加载、缓存、节流防抖
场景和需求:
移动端一个图库列表页,几百张甚至几千张图片的列表展示
点击其中的一张图片,可以预览大图,左右切换查看所有其他图片的大图
- 第一部分:
问题:点击事件,如果每张图的标签都绑定了事件,在移动端,性能很糟糕。
解决办法:事件委托。
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,对性能来说也是一件很糟糕的事情。
如图:
解决办法:使渲染swiper的数组永远只有三张图片,每次切换,根据索引让数组的三张图片变一次。
<swiper :options="swiperOption" ref="mySwiper" >
<swiper-slide v-for="(item,index) in swiperList" :key="index">
<img :src="item.srcUrl"/>
</swiper-slide>
</swiper>
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},
]
}
}
},
}
为什么是三张图?
因为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
}
},
}
}
},
}
最终效果
初衷是想让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)
}
}