1 | import throttle from 'throttle-debounce/debounce';
|
2 | import {
|
3 | isHtmlElement,
|
4 | isFunction,
|
5 | isUndefined,
|
6 | isDefined
|
7 | } from 'element-ui/src/utils/types';
|
8 | import {
|
9 | getScrollContainer
|
10 | } from 'element-ui/src/utils/dom';
|
11 |
|
12 | const getStyleComputedProperty = (element, property) => {
|
13 | if (element === window) {
|
14 | element = document.documentElement;
|
15 | }
|
16 |
|
17 | if (element.nodeType !== 1) {
|
18 | return [];
|
19 | }
|
20 |
|
21 | const css = window.getComputedStyle(element, null);
|
22 | return property ? css[property] : css;
|
23 | };
|
24 |
|
25 | const entries = (obj) => {
|
26 | return Object.keys(obj || {})
|
27 | .map(key => ([key, obj[key]]));
|
28 | };
|
29 |
|
30 | const getPositionSize = (el, prop) => {
|
31 | return el === window || el === document
|
32 | ? document.documentElement[prop]
|
33 | : el[prop];
|
34 | };
|
35 |
|
36 | const getOffsetHeight = el => {
|
37 | return getPositionSize(el, 'offsetHeight');
|
38 | };
|
39 |
|
40 | const getClientHeight = el => {
|
41 | return getPositionSize(el, 'clientHeight');
|
42 | };
|
43 |
|
44 | const scope = 'ElInfiniteScroll';
|
45 | const 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 |
|
64 | const 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 |
|
87 | const getElementTop = el => el.getBoundingClientRect().top;
|
88 |
|
89 | const 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 |
|
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 |
|
120 | export default {
|
121 | name: 'InfiniteScroll',
|
122 | inserted(el, binding, vnode) {
|
123 | const cb = binding.value;
|
124 |
|
125 | const vm = vnode.context;
|
126 |
|
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 |
|