Open SunXinFei opened 4 years ago
长列表业务上比如1w+的table列表、feed流(twitter、ins),这一类的长列表一般都是伴随着dom元素与绑定数据越来越多,加载到一定的程度会变得非常卡顿。优化的痛点就是减少可视范围内的dom元素数量。
从结构上分为单行元素item定高与不定高的情况。定高的情况相对来说就很简单,我们只需要判断当前元素是否在可视范围,如果超出可视范围,便用相同的高度占位元素进行替换。关于如何判断一个dom元素是否在可视范围,在懒加载这个模块进行详细讲述。不定高的元素,则需要计算/手动传入该item元素的高度。
除了刚才提到的占位符替换,还有padding和transform处理,以ins和twitter为代表的处理方法是分两种: 前提: 整个列表只容载20个item,至于每一个item是在滚动过程中数据刷新还是item结点进行摘取和挂载dom树我们这里先不讨论,比如我们滑出可视范围10个item之后,将这10个item的高度进行累加。
padding的情况处理: 我们在group上面修改css样式,padding的top值即为这10个item的高度和;
transform的情况处理: 每一个item的样式是
position: absolute;
top: 0;
left: 0;
然后每一个item的纵向偏移之前元素的高度和,比如第一个元素:transform: translateY(0px);
(()=>{
const onScroll = (e)=>{
this.firstViewIndex = e.detail.applyIndex;//scroll触发修改firstViewIndex;
//如果feed总长度减去当前的index值小于等于模版的长度,则请求新数据
//eg. 20 - 14 <= 6 那么请求新的数据
if (feed_list.length - firstViewIndex <= tmpNoop.length) {
DeferFn(ajaxAction, 500)();
}
}
const tmpNoop = new Array(6).fill(0);//6个占位符
const firstViewIndex = 0;//当前看到的游标
const calcIndex = (index) => {//通过firstViewIndex,计算tmpNoop中实际应该展示的index数组
let indexShouldBe = index - (firstViewIndex % tmpNoop.length);
if (indexShouldBe < 0) {
indexShouldBe += tmpNoop.length;
}
let currentIndex = indexShouldBe + firstViewIndex;
return currentIndex;
}
let ret = tmpNoop.map((_, viewIndex) => {
let dataIndex = calcIndex(viewIndex);
return dataIndex;
});
console.log(ret);
//firstViewIndex = 0 => [0, 1, 2, 3, 4, 5]
//firstViewIndex = 1 => [6, 1, 2, 3, 4, 5]
//firstViewIndex = 2 => [6, 7, 2, 3, 4, 5]
})()
twitter、ins分别是上面的两个思路的实现体, 另外两个开源项目可以进行参考: https://github.com/tangbc/vue-virtual-scroll-list https://github.com/Akryum/vue-virtual-scroller
综上所述这种思路:dom元素并不一口气for循环挂载到dom树上,而是通过事件来控制挂载和摘取或者复用dom。这种思路也适用于轮播图切换,比如一共有10张轮播,我们每次控制3个轮播图占位符的挂载,第一次挂载index为0,1,2这三张,随着滑动,我们挂载index为3,1,2,再滑动index为3,4,2,代码层面通过滑动改变index,从而计算3个轮播占位符的数据即可。
// 轮播滑动事件处理
onSlideChange (event) {
const index = event.detail.index
this.activeIndex = index
if (index == 0) {
this.slideIndexes.splice(0, 1, 0)
} else {
switch (index%3) {
case 1:
this.slideIndexes = [index - 1, index, index + 1]
break;
case 2:
this.slideIndexes = [index + 1, index - 1, index]
break;
case 0:
this.slideIndexes = [index, index + 1, index - 1]
break;
}
}
}
first () {//第一个轮播占位符
return this.items[this.slideIndexes[0]] || {}
},
second () {//第二个轮播占位符
return this.items[this.slideIndexes[1]] || {}
},
third () {//第三个轮播占位符
return this.items[this.slideIndexes[2]] || {}
}
懒加载泛指元素由不可视范围内=>可视范围内,元素由未加载到加载的过程。此常用来做图片处理,目的是为了减少http的请求 ,减少服务器端压力,尤其是图片较多的页面,效果尤为明显。主要实现思路是三种:
el.offsetTop- scrollTop <= viewPortHeight.
function isInView (el) {
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const offsetTop = el.offsetTop
const scrollTop = document.documentElement.scrollTop
const top = offsetTop - scrollTop
return top <= viewPortHeight
}
el.getBoundingClientReact().top <= viewPortHeight
function isInView (el) {
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
return top <= viewPortHeight
}
// 定义一个观察器
const observer = new IntersectionObserver(ioes => {
ioes.forEach(ioe => {
const el = ioe.target
const intersectionRatio = ioe.intersectionRatio
if (intersectionRatio > 0 && intersectionRatio <= 1) {
loadImg(el)
io.unobserve(el)
}
el.onload = el.onerror = () => io.unobserve(el)
})
})
// 执行交叉观察器
function isInView (el) {
observer.observe(el)
}
transform涉及到性能优化除了使用GPU加速之外,还涉及到合成线程; 合成的好处在于其独立于主线程。合成线程不需要等待样式计算和Javascript代码的运行。这也是为什么合成更适合优化交互性能,但如果布局或者绘制需要重新计算则主线程是必须要参与的。 GPU会加速的属性:transform、opacity、filter 附加: 浏览器进程:浏览器进程、渲染进程(GUI渲染线程、js引擎线程、io线程、定时器线程、事件触发线程)、GPU进程、网络进程、插件进程 每一个页面分配一个渲染进程,一个渲染进程由多个线程相互协同
参考: https://xie.infoq.cn/article/5d36d123bfd1c56688e125ad3?utm_source=tuicool&utm_medium=referral http://sy-tang.github.io/2014/05/14/CSS%20animations%20and%20transitions%20performance-%20looking%20inside%20the%20browser/ http://www.cainiaoxueyuan.com/zhizuo/8484.html https://segmentfault.com/a/1190000008650975 https://zhuanlan.zhihu.com/p/78230297
http层面:
静态资源方面:
webpack层面:
代码层面
前端性能优化是特别庞大内容区域,涉及浏览器渲染机制、框架源码、网络请求...这里做一些总结。比较好的总结:google developers