be01f0b4
Sergio Crisostomo
New component: Sc...
|
1
2
3
4
|
<template>
<div :class="wrapClasses" style="touch-action: none;">
<div
:class="scrollContainerClasses"
|
6850c1da
梁灏
Scroll add `heigh...
|
5
|
:style="{height: height + 'px'}"
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
6
7
8
9
10
11
|
@scroll="handleScroll"
@wheel="onWheel"
@touchstart="onPointerDown"
ref="scrollContainer"
>
<div :class="loaderClasses" :style="{paddingTop: wrapperPadding.paddingTop}" ref="toploader">
|
78b46fa6
梁灏
Scroll add local ...
|
12
|
<loader :text="localeLoadingText" :active="showTopLoader"></loader>
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
13
14
15
16
17
|
</div>
<div :class="slotContainerClasses" ref="scrollContent">
<slot></slot>
</div>
<div :class="loaderClasses" :style="{paddingBottom: wrapperPadding.paddingBottom}" ref="bottomLoader">
|
78b46fa6
梁灏
Scroll add local ...
|
18
|
<loader :text="localeLoadingText" :active="showBottomLoader"></loader>
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
19
20
21
22
23
24
25
|
</div>
</div>
</div>
</template>
<script>
import throttle from 'lodash.throttle';
import loader from './loading-component.vue';
|
59dbe5d5
梁灏
update
|
26
|
import { on, off } from '../../utils/dom';
|
78b46fa6
梁灏
Scroll add local ...
|
27
|
import Locale from '../../mixins/locale';
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
28
29
30
31
32
33
34
|
const prefixCls = 'ivu-scroll';
const dragConfig = {
sensitivity: 10,
minimumStartDragOffset: 5, // minimum start drag offset
};
|
109465d3
梁灏
optimize Scroll ...
|
35
36
|
const noop = () => Promise.resolve();
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
37
38
|
export default {
name: 'Scroll',
|
78b46fa6
梁灏
Scroll add local ...
|
39
|
mixins: [ Locale ],
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
40
41
|
components: {loader},
props: {
|
6850c1da
梁灏
Scroll add `heigh...
|
42
43
44
45
|
height: {
type: [Number, String],
default: 300
},
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
46
|
onReachTop: {
|
109465d3
梁灏
optimize Scroll ...
|
47
|
type: Function
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
48
49
|
},
onReachBottom: {
|
109465d3
梁灏
optimize Scroll ...
|
50
|
type: Function
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
51
52
|
},
onReachEdge: {
|
109465d3
梁灏
optimize Scroll ...
|
53
|
type: Function
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
54
55
|
},
loadingText: {
|
78b46fa6
梁灏
Scroll add local ...
|
56
|
type: String
|
72ba6662
Sergio Crisostomo
add distance-to-e...
|
57
58
|
},
distanceToEdge: [Number, Array]
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
59
60
|
},
data() {
|
72ba6662
Sergio Crisostomo
add distance-to-e...
|
61
|
const distanceToEdge = this.calculateProximityThreshold();
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
return {
showTopLoader: false,
showBottomLoader: false,
showBodyLoader: false,
lastScroll: 0,
reachedTopScrollLimit: true,
reachedBottomScrollLimit: false,
topRubberPadding: 0,
bottomRubberPadding: 0,
rubberRollBackTimeout: false,
isLoading: false,
pointerTouchDown: null,
touchScroll: false,
handleScroll: () => {},
pointerUpHandler: () => {},
pointerMoveHandler: () => {},
|
72ba6662
Sergio Crisostomo
add distance-to-e...
|
78
79
80
81
|
// near to edge detectors
topProximityThreshold: distanceToEdge[0],
bottomProximityThreshold: distanceToEdge[1]
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
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
|
};
},
computed: {
wrapClasses() {
return `${prefixCls}-wrapper`;
},
scrollContainerClasses() {
return `${prefixCls}-container`;
},
slotContainerClasses() {
return [
`${prefixCls}-content`,
{
[`${prefixCls}-content-loading`]: this.showBodyLoader
}
];
},
loaderClasses() {
return `${prefixCls}-loader`;
},
wrapperPadding() {
return {
paddingTop: this.topRubberPadding + 'px',
paddingBottom: this.bottomRubberPadding + 'px'
};
|
78b46fa6
梁灏
Scroll add local ...
|
107
108
109
110
111
112
113
114
|
},
localeLoadingText () {
if (this.loadingText === undefined) {
return this.t('i.select.loading');
} else {
return this.loadingText;
}
},
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
115
116
117
118
119
|
},
methods: {
// just to improve feeling of loading and avoid scroll trailing events fired by the browser
waitOneSecond() {
return new Promise(resolve => {
|
46f37a14
梁灏
update Scroll
|
120
|
setTimeout(resolve, 1000);
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
121
122
123
|
});
},
|
72ba6662
Sergio Crisostomo
add distance-to-e...
|
124
125
126
127
128
129
|
calculateProximityThreshold(){
const dte = this.distanceToEdge;
if (typeof dte == 'undefined') return [20, 20];
return Array.isArray(dte) ? dte : [dte, dte];
},
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
130
|
onCallback(dir) {
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
131
132
133
134
135
136
137
138
139
140
141
142
143
|
this.isLoading = true;
this.showBodyLoader = true;
if (dir > 0) {
this.showTopLoader = true;
this.topRubberPadding = 20;
} else {
this.showBottomLoader = true;
this.bottomRubberPadding = 20;
// to force the scroll to the bottom while height is animating
let bottomLoaderHeight = 0;
const container = this.$refs.scrollContainer;
const initialScrollTop = container.scrollTop;
|
9d275e38
梁灏
update Scroll
|
144
|
for (let i = 0; i < 20; i++) {
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
145
146
147
148
149
150
151
152
153
154
|
setTimeout(() => {
bottomLoaderHeight = Math.max(
bottomLoaderHeight,
this.$refs.bottomLoader.getBoundingClientRect().height
);
container.scrollTop = initialScrollTop + bottomLoaderHeight;
}, i * 50);
}
}
|
109465d3
梁灏
optimize Scroll ...
|
155
156
|
const callbacks = [this.waitOneSecond(), this.onReachEdge ? this.onReachEdge(dir) : noop()];
callbacks.push(dir > 0 ? this.onReachTop ? this.onReachTop() : noop() : this.onReachBottom ? this.onReachBottom() : noop());
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
let tooSlow = setTimeout(() => {
this.reset();
}, 5000);
Promise.all(callbacks).then(() => {
clearTimeout(tooSlow);
this.reset();
});
},
reset() {
[
'showTopLoader',
'showBottomLoader',
'showBodyLoader',
'isLoading',
'reachedTopScrollLimit',
'reachedBottomScrollLimit'
].forEach(prop => (this[prop] = false));
this.lastScroll = 0;
this.topRubberPadding = 0;
this.bottomRubberPadding = 0;
clearInterval(this.rubberRollBackTimeout);
// if we remove the handler too soon the screen will bump
if (this.touchScroll) {
setTimeout(() => {
|
59dbe5d5
梁灏
update
|
186
|
off(window, 'touchend', this.pointerUpHandler);
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
187
188
189
190
191
192
|
this.$refs.scrollContainer.removeEventListener('touchmove', this.pointerMoveHandler);
this.touchScroll = false;
}, 500);
}
},
|
a3336b38
Sergio Crisostomo
add missing event...
|
193
|
onWheel(event) {
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
194
195
196
197
198
199
200
201
202
203
|
if (this.isLoading) return;
// get the wheel direction
const wheelDelta = event.wheelDelta ? event.wheelDelta : -(event.detail || event.deltaY);
this.stretchEdge(wheelDelta);
},
stretchEdge(direction) {
clearTimeout(this.rubberRollBackTimeout);
|
109465d3
梁灏
optimize Scroll ...
|
204
205
206
207
208
209
210
211
212
|
// check if set these props
if (!this.onReachEdge) {
if (direction > 0) {
if (!this.onReachTop) return;
} else {
if (!this.onReachBottom) return;
}
}
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
213
214
215
216
217
218
219
220
|
// if the scroll is not strong enough, lets reset it
this.rubberRollBackTimeout = setTimeout(() => {
if (!this.isLoading) this.reset();
}, 250);
// to give the feeling its ruberish and can be puled more to start loading
if (direction > 0 && this.reachedTopScrollLimit) {
this.topRubberPadding += 5 - this.topRubberPadding / 5;
|
72ba6662
Sergio Crisostomo
add distance-to-e...
|
221
|
if (this.topRubberPadding > this.topProximityThreshold) this.onCallback(1);
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
222
223
|
} else if (direction < 0 && this.reachedBottomScrollLimit) {
this.bottomRubberPadding += 6 - this.bottomRubberPadding / 4;
|
72ba6662
Sergio Crisostomo
add distance-to-e...
|
224
|
if (this.bottomRubberPadding > this.bottomProximityThreshold) this.onCallback(-1);
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
225
226
227
228
229
230
231
232
233
234
235
|
} else {
this.onScroll();
}
},
onScroll() {
if (this.isLoading) return;
const el = this.$refs.scrollContainer;
const scrollDirection = Math.sign(this.lastScroll - el.scrollTop); // IE has no Math.sign, check that webpack polyfills this
const displacement = el.scrollHeight - el.clientHeight - el.scrollTop;
|
72ba6662
Sergio Crisostomo
add distance-to-e...
|
236
237
238
|
const topNegativeProximity = this.topProximityThreshold < 0 ? this.topProximityThreshold : 0;
const bottomNegativeProximity = this.bottomProximityThreshold < 0 ? this.bottomProximityThreshold : 0;
if (scrollDirection == -1 && displacement + bottomNegativeProximity <= dragConfig.sensitivity) {
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
239
|
this.reachedBottomScrollLimit = true;
|
72ba6662
Sergio Crisostomo
add distance-to-e...
|
240
|
} else if (scrollDirection >= 0 && el.scrollTop + topNegativeProximity <= 0) {
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
|
this.reachedTopScrollLimit = true;
} else {
this.reachedTopScrollLimit = false;
this.reachedBottomScrollLimit = false;
this.lastScroll = el.scrollTop;
}
},
getTouchCoordinates(e) {
return {
x: e.touches[0].pageX,
y: e.touches[0].pageY
};
},
onPointerDown(e) {
// we just use scroll and wheel in desktop, no mousedown
if (this.isLoading) return;
if (e.type == 'touchstart') {
// if we start do touchmove on the scroll edger the browser will scroll the body
// by adding 5px margin on pointer down we avoid this behaviour and the scroll/touchmove
// in the component will not be exported outside of the component
const container = this.$refs.scrollContainer;
if (this.reachedTopScrollLimit) container.scrollTop = 5;
else if (this.reachedBottomScrollLimit) container.scrollTop -= 5;
}
if (e.type == 'touchstart' && this.$refs.scrollContainer.scrollTop == 0)
this.$refs.scrollContainer.scrollTop = 5;
this.pointerTouchDown = this.getTouchCoordinates(e);
|
59dbe5d5
梁灏
update
|
271
|
on(window, 'touchend', this.pointerUpHandler);
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
|
this.$refs.scrollContainer.parentElement.addEventListener('touchmove', e => {
e.stopPropagation();
this.pointerMoveHandler(e);
}, {passive: false, useCapture: true});
},
onPointerMove(e) {
if (!this.pointerTouchDown) return;
if (this.isLoading) return;
const pointerPosition = this.getTouchCoordinates(e);
const yDiff = pointerPosition.y - this.pointerTouchDown.y;
this.stretchEdge(yDiff);
if (!this.touchScroll) {
const wasDragged = Math.abs(yDiff) > dragConfig.minimumStartDragOffset;
if (wasDragged) this.touchScroll = true;
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
290
291
292
293
294
295
296
|
}
},
onPointerUp() {
this.pointerTouchDown = null;
}
},
|
59dbe5d5
梁灏
update
|
297
|
created() {
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
298
|
this.handleScroll = throttle(this.onScroll, 150, {leading: false});
|
9d275e38
梁灏
update Scroll
|
299
|
this.pointerUpHandler = this.onPointerUp.bind(this); // because we need the same function to add and remove event handlers
|
be01f0b4
Sergio Crisostomo
New component: Sc...
|
300
301
302
303
304
|
this.pointerMoveHandler = throttle(this.onPointerMove, 50, {leading: false});
}
};
</script>
|