]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/components/intersection_observer_article.js
bb83a4da096028c7117a7e78b29d9b3ddfcb818c
[mastodon.git] / app / javascript / mastodon / components / intersection_observer_article.js
1 import React from 'react';
2 import PropTypes from 'prop-types';
3 import ImmutablePureComponent from 'react-immutable-pure-component';
4 import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
5 import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
6
7 export default class IntersectionObserverArticle extends ImmutablePureComponent {
8
9 static propTypes = {
10 intersectionObserverWrapper: PropTypes.object.isRequired,
11 id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
12 index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
13 listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
14 saveHeightKey: PropTypes.string,
15 cachedHeight: PropTypes.number,
16 onHeightChange: PropTypes.func,
17 children: PropTypes.node,
18 };
19
20 state = {
21 isHidden: false, // set to true in requestIdleCallback to trigger un-render
22 }
23
24 shouldComponentUpdate (nextProps, nextState) {
25 if (!nextState.isIntersecting && nextState.isHidden) {
26 // It's only if we're not intersecting (i.e. offscreen) and isHidden is true
27 // that either "isIntersecting" or "isHidden" matter, and then they're
28 // the only things that matter (and updated ARIA attributes).
29 return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength;
30 } else if (nextState.isIntersecting && !this.state.isIntersecting) {
31 // If we're going from a non-intersecting state to an intersecting state,
32 // (i.e. offscreen to onscreen), then we definitely need to re-render
33 return true;
34 }
35 // Otherwise, diff based on "updateOnProps" and "updateOnStates"
36 return super.shouldComponentUpdate(nextProps, nextState);
37 }
38
39 componentDidMount () {
40 const { intersectionObserverWrapper, id } = this.props;
41
42 intersectionObserverWrapper.observe(
43 id,
44 this.node,
45 this.handleIntersection
46 );
47
48 this.componentMounted = true;
49 }
50
51 componentWillUnmount () {
52 const { intersectionObserverWrapper, id } = this.props;
53 intersectionObserverWrapper.unobserve(id, this.node);
54
55 this.componentMounted = false;
56 }
57
58 handleIntersection = (entry) => {
59 const { onHeightChange, saveHeightKey, id } = this.props;
60
61 if (this.node && this.node.children.length !== 0) {
62 // save the height of the fully-rendered element
63 this.height = getRectFromEntry(entry).height;
64
65 if (onHeightChange && saveHeightKey) {
66 onHeightChange(saveHeightKey, id, this.height);
67 }
68 }
69
70 this.setState((prevState) => {
71 if (prevState.isIntersecting && !entry.isIntersecting) {
72 scheduleIdleTask(this.hideIfNotIntersecting);
73 }
74 return {
75 isIntersecting: entry.isIntersecting,
76 isHidden: false,
77 };
78 });
79 }
80
81 hideIfNotIntersecting = () => {
82 if (!this.componentMounted) {
83 return;
84 }
85
86 // When the browser gets a chance, test if we're still not intersecting,
87 // and if so, set our isHidden to true to trigger an unrender. The point of
88 // this is to save DOM nodes and avoid using up too much memory.
89 // See: https://github.com/tootsuite/mastodon/issues/2900
90 this.setState((prevState) => ({ isHidden: !prevState.isIntersecting }));
91 }
92
93 handleRef = (node) => {
94 this.node = node;
95 }
96
97 render () {
98 const { children, id, index, listLength, cachedHeight } = this.props;
99 const { isIntersecting, isHidden } = this.state;
100
101 if (!isIntersecting && (isHidden || cachedHeight)) {
102 return (
103 <article
104 ref={this.handleRef}
105 aria-posinset={index}
106 aria-setsize={listLength}
107 style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
108 data-id={id}
109 tabIndex='0'
110 >
111 {children && React.cloneElement(children, { hidden: true })}
112 </article>
113 );
114 }
115
116 return (
117 <article ref={this.handleRef} aria-posinset={index} aria-setsize={listLength} data-id={id} tabIndex='0'>
118 {children && React.cloneElement(children, { hidden: false })}
119 </article>
120 );
121 }
122
123 }
This page took 0.075517 seconds and 2 git commands to generate.