intersectionObserver.tsx 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import React, { ReactNode } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { isEqual, isEmpty } from 'lodash';
  4. import { isHTMLElement } from '../_base/reactUtils';
  5. export interface ReactIntersectionObserverProps {
  6. onIntersect?: IntersectionObserverCallback;
  7. option?: IntersectionObserverInit;
  8. children?: React.ReactNode;
  9. root?: IntersectionObserverInit['root'];
  10. threshold?: IntersectionObserverInit['threshold'];
  11. rootMargin?: IntersectionObserverInit['rootMargin'];
  12. items?: Record<string, Element>
  13. }
  14. export default class ReactIntersectionObserver extends React.PureComponent<ReactIntersectionObserverProps> {
  15. static propTypes = {
  16. onIntersect: PropTypes.func,
  17. option: PropTypes.object,
  18. root: PropTypes.any,
  19. threshold: PropTypes.number,
  20. rootMargin: PropTypes.string,
  21. items: PropTypes.object,
  22. };
  23. static defaultProps = {
  24. onIntersect: (): void => undefined,
  25. threshold: 0.75,
  26. rootMargin: '0px',
  27. option: {},
  28. items: {},
  29. };
  30. observer: IntersectionObserver;
  31. cachedKeys: Array<string>;
  32. componentDidMount(): void {
  33. const { items } = this.props;
  34. this.cachedKeys = Object.keys(items);
  35. const { root, threshold, rootMargin, option, onIntersect } = this.props;
  36. this.observer = new IntersectionObserver(
  37. onIntersect,
  38. {
  39. root,
  40. threshold,
  41. rootMargin,
  42. ...option,
  43. }
  44. );
  45. this.observeElement();
  46. }
  47. componentDidUpdate(): void {
  48. const { items } = this.props;
  49. const itemKeys = Object.keys(items);
  50. if (!isEqual(this.cachedKeys, itemKeys)) {
  51. this.observeElement(true);
  52. this.cachedKeys = itemKeys;
  53. }
  54. }
  55. componentWillUnmount(): void {
  56. if (this.observer) {
  57. this.observer.disconnect();
  58. this.observer = null;
  59. }
  60. }
  61. observeElement(force = false): void {
  62. const { items } = this.props;
  63. if (isEmpty(items)) {
  64. // stop everything if not defined
  65. this.observer.disconnect();
  66. return;
  67. }
  68. if (force) {
  69. this.observer.disconnect();
  70. }
  71. // observer callback is invoked immediately when observing new elements
  72. Object.keys(items).forEach(key => {
  73. const node = items[key];
  74. if (!(node && isHTMLElement(node))) {
  75. return;
  76. }
  77. this.observer.observe(node);
  78. });
  79. }
  80. render() {
  81. const { children } = this.props;
  82. return children;
  83. }
  84. }