# 优化背景
如果应用存在非常长的或者无限滚动的列表,那么需要采用窗口化的技术来优化性能,只需要渲染少部分的内容(可视区域),减少重新渲染组件和创建dom节点的时间
# 使用自定义指令,来实现无线滚动
Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。 自定义指令 (opens new window)
<!-- 底部bottom可见时,执行fetchNext 加载下一页数据 -->
<div class="bottom" v-intersect="{ handler: fetchNext }"></div>
1
2
2
// 自定义指令 src\directive\intersect
// 观察元素不可见的时候不调用获取列表的函数
// 在inserted钩子函数中观察元素,并且要注意给el一个属性,使其可以在unbind中也获取到observer,方便unbind钩子函数中停止观察
const intersect = {
inserted(el, binding) {
const value = binding.value;
const { handler, options = {} } = value;
const observer = new IntersectionObserver((entries = [], observer) => {
if (!el._observe) return;
if (handler && el._observe.init) {
const isIntersecting = Boolean(
entries.find((entry) => entry.isIntersecting)
);
if (isIntersecting) {
handler(entries, observer, isIntersecting);
}
}
el._observe.init = true;
}, options);
el._observe = { init: false, observer };
observer.observe(el);
},
unbind(el) {
if (!el._observe) return;
el._observe.observer.unobserve(el);
delete el._observe;
},
};
export default intersect;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
IntersectionObserver API (opens new window)
# 长列表优化
但是这个时候会发现一个问题,就是随着列表的长度的增加,DOM节点也会增加,当DOM节点到达一定数量的时候,会发生卡顿,所以接下来就要对它进行优化了
<template>
<div class="x-infinite" ref="container" :style="{padding: padding}">
<slot :sliceItems="sliceItems"></slot>
</div>
</template>
<script>
import { throttle } from "../util/throttle";
function getScrollTop() {
return document.documentElement.scrollTop || document.body.scrollTop;
}
export default {
props: {
items: {
required: true
},
itemHeight: {
required: true,
type: Number
}
},
data() {
return {
buffer: 5, //优化用户体验,一个过度的作用
scrollTop: 0, //document.body.scrollTop 网页被卷去的高
viewportHeight: 0 //window.innerHeight返回窗口的文档显示区的高度
};
},
computed: {
// 计算属性获得over(数组的开始索引) ,down(数组的结束索引),sliceItems(需要渲染的数组)padding(div样式用来减少div渲染列表的区域)这些数据
over() {
return Math.max(
Math.floor(this.scrollTop / this.itemHeight) - this.buffer,
0
);
},
down() {
return Math.min(
Math.ceil(
(this.scrollTop + this.viewportHeight) / this.itemHeight + this.buffer
),
this.items.length
);
},
sliceItems() {
return this.items.slice(this.over, this.down);
},
padding() {
return `${this.over * this.itemHeight}px 0 ${Math.max(
(this.items.length - this.down) * this.itemHeight,
0
)}px 0`;
}
},
created() {
this.scrollTop = getScrollTop();
this.viewportHeight = window.innerHeight;
// 监听浏览器滚动
document.addEventListener("scroll", this.onScroll, {
passive: true
});
},
destroyed() {
document.removeEventListener("scroll", this.onScroll);
},
methods: {
// 节流也可以减少对DOM的操作,对DOM进行一定的优化
onScroll: throttle(function() {
this.scrollTop = getScrollTop();
this.viewportHeight = window.innerHeight;
})
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78