UNPKG

3.89 kBJavaScriptView Raw
1import throttle from 'throttle-debounce/debounce';
2import {
3 isHtmlElement,
4 isFunction,
5 isUndefined,
6 isDefined
7} from 'element-ui/src/utils/types';
8import {
9 getScrollContainer
10} from 'element-ui/src/utils/dom';
11
12const getStyleComputedProperty = (element, property) => {
13 if (element === window) {
14 element = document.documentElement;
15 }
16
17 if (element.nodeType !== 1) {
18 return [];
19 }
20 // NOTE: 1 DOM access here
21 const css = window.getComputedStyle(element, null);
22 return property ? css[property] : css;
23};
24
25const entries = (obj) => {
26 return Object.keys(obj || {})
27 .map(key => ([key, obj[key]]));
28};
29
30const getPositionSize = (el, prop) => {
31 return el === window || el === document
32 ? document.documentElement[prop]
33 : el[prop];
34};
35
36const getOffsetHeight = el => {
37 return getPositionSize(el, 'offsetHeight');
38};
39
40const getClientHeight = el => {
41 return getPositionSize(el, 'clientHeight');
42};
43
44const scope = 'ElInfiniteScroll';
45const attributes = {
46 delay: {
47 type: Number,
48 default: 200
49 },
50 distance: {
51 type: Number,
52 default: 0
53 },
54 disabled: {
55 type: Boolean,
56 default: false
57 },
58 immediate: {
59 type: Boolean,
60 default: true
61 }
62};
63
64const getScrollOptions = (el, vm) => {
65 if (!isHtmlElement(el)) return {};
66
67 return entries(attributes).reduce((map, [key, option]) => {
68 const { type, default: defaultValue } = option;
69 let value = el.getAttribute(`infinite-scroll-${key}`);
70 value = isUndefined(vm[value]) ? value : vm[value];
71 switch (type) {
72 case Number:
73 value = Number(value);
74 value = Number.isNaN(value) ? defaultValue : value;
75 break;
76 case Boolean:
77 value = isDefined(value) ? value === 'false' ? false : Boolean(value) : defaultValue;
78 break;
79 default:
80 value = type(value);
81 }
82 map[key] = value;
83 return map;
84 }, {});
85};
86
87const getElementTop = el => el.getBoundingClientRect().top;
88
89const handleScroll = function(cb) {
90 const { el, vm, container, observer } = this[scope];
91 const { distance, disabled } = getScrollOptions(el, vm);
92
93 if (disabled) return;
94
95 const containerInfo = container.getBoundingClientRect();
96 if (!containerInfo.width && !containerInfo.height) return;
97
98 let shouldTrigger = false;
99
100 if (container === el) {
101 // be aware of difference between clientHeight & offsetHeight & window.getComputedStyle().height
102 const scrollBottom = container.scrollTop + getClientHeight(container);
103 shouldTrigger = container.scrollHeight - scrollBottom <= distance;
104 } else {
105 const heightBelowTop = getOffsetHeight(el) + getElementTop(el) - getElementTop(container);
106 const offsetHeight = getOffsetHeight(container);
107 const borderBottom = Number.parseFloat(getStyleComputedProperty(container, 'borderBottomWidth'));
108 shouldTrigger = heightBelowTop - offsetHeight + borderBottom <= distance;
109 }
110
111 if (shouldTrigger && isFunction(cb)) {
112 cb.call(vm);
113 } else if (observer) {
114 observer.disconnect();
115 this[scope].observer = null;
116 }
117
118};
119
120export default {
121 name: 'InfiniteScroll',
122 inserted(el, binding, vnode) {
123 const cb = binding.value;
124
125 const vm = vnode.context;
126 // only include vertical scroll
127 const container = getScrollContainer(el, true);
128 const { delay, immediate } = getScrollOptions(el, vm);
129 const onScroll = throttle(delay, handleScroll.bind(el, cb));
130
131 el[scope] = { el, vm, container, onScroll };
132
133 if (container) {
134 container.addEventListener('scroll', onScroll);
135
136 if (immediate) {
137 const observer = el[scope].observer = new MutationObserver(onScroll);
138 observer.observe(container, { childList: true, subtree: true });
139 onScroll();
140 }
141 }
142 },
143 unbind(el) {
144 const { container, onScroll } = el[scope];
145 if (container) {
146 container.removeEventListener('scroll', onScroll);
147 }
148 }
149};
150