main.js 3.8 KB

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