/
infinite-scroll.js
170 lines (153 loc) · 5.03 KB
/
infinite-scroll.js
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/**
* A scrollview which support infinite scroll.
*/
class InfiniteScroll {
/**
* @param {Object} config
* @param {String|HTMLElement} config.container - the container
* @param {String|HTMLElement} [config.scroller=window] - the scroll element,need be seted in ios, because need bind scroll event
* @param {String|HTMLElement} [config.itemsContainer=.items] - the container of items
* @param {String|HTMLElement} [config.indicator=.indicator] - the loading indicator
* @param {Number} [config.rootMarginY=200] - the distance to determine when to load the next page
*/
constructor(config = {}) {
config = Object.assign({
rootMarginY: 200,
scroller: window,
itemsContainer: '.items',
indicator: '.indicator'
}, config);
let getEl = function (el, container) {
return typeof el === 'string' ? (container || document).querySelector(el) : el;
}
/**
* @property {HTMLElement} container - the infinite scroll's container
*/
this.container = getEl(config.container);
this._itemsContainer = getEl(config.itemsContainer, this.container);
this._scroller = getEl(config.scroller, this.container);
this._indicator = getEl(config.indicator, this.container);
this._rootMarginY = config.rootMarginY;
/**
* @property {Number} page - the current page's index (start from 0)
*/
this.page = 0;
}
/**
* the common plug interface to add functions
* @param {Object} Plugin - the plugin
* @param {Object} [config] - the plugin's config
*/
use(Plugin, config) {
return Plugin.init(this, config)
}
/**
* strat infinite scroll, and request the first page of data
*/
init() {
// init infinite scroll
if (window.IntersectionObserver) {
this._io = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio > 0) {
this.pause();
this._requestAndRender();
}
}, {
root: this._scroller === window ? null : this._scroller,
threshold: 0.000001,
rootMargin: `${this._rootMarginY}px 0px`
})
} else {
this._listener = () => {
if (this._check()) {
this.pause();
this._requestAndRender();
}
};
}
// request first page
this._requestAndRender();
}
/**
* reset state, empty the items container, resume infinite scroll
*/
reset() {
this._itemsContainer.innerHTML = '';
this._indicator.style.display = '';
this.page = 0;
this._requestAndRender();
}
/**
* resume infinite scroll
*/
resume() {
if (this._io) {
this._io.observe(this._indicator);
} else if (this._listener) {
this._scroller.addEventListener('scroll', this._listener, { passive: true });
}
}
/**
* pause infinite scroll
*/
pause() {
if (this._io) {
this._io.unobserve(this._indicator);
} else if (this._listener) {
this._scroller.removeEventListener('scroll', this._listener);
}
}
/**
* request data and render
* @private
*/
_requestAndRender() {
this.request({
page: this.page
}).then((res) => {
if (res && res.hasMore) {
this.page++;
setTimeout(() => {
this.resume();
}, 100);
} else {
this._indicator.style.display = 'none';
}
}, () => {
this._indicator.style.display = 'none';
});
}
/**
* try again to load the current page and render
*/
retry() {
this._indicator.style.display = '';
this._requestAndRender();
}
/**
* you must achieve this interface to request data, you should return a promise
* @method InfiniteScroll#request
* @param {Object} params - the request data
* @param {Number} params.page - the page number
* @return {Promise}
*/
/**
* you must achieve this interface to render view
* @method InfiniteScroll#render
* @param {*} params - the data return by the request interface
* @return {String|HTMLElement}
*/
/**
* detect if need to request next page
* @returns {boolean}
* @private
*/
_check() {
let scrollerIsWindow = this._scroller === window;
let height = (scrollerIsWindow ? document.body : this._scroller).scrollHeight;
let top = scrollerIsWindow ? window.pageYOffset : this._scroller.scrollTop;
let offset = scrollerIsWindow ? window.innerHeight : this._scroller.clientHeight;
return (height - top - offset) <= this._rootMarginY;
}
}
export default InfiniteScroll