add InView component which tracks element intersection with view

This commit is contained in:
Vyrtsev Mikhail
2019-07-29 13:47:35 -05:00
committed by Umputun
parent b4f083a051
commit a36733a2e3
@@ -0,0 +1,72 @@
import { Component } from 'preact';
import { sleep } from '@app/utils/sleep';
interface Props {
children: (props: { inView: boolean; ref: (ref: Component) => Component }) => JSX.Element;
}
interface State {
inView: boolean;
ref: Element | undefined;
}
const instance_map: Map<Element, Component<Props, State>> = new Map();
const observer = new IntersectionObserver(
entries => {
entries.forEach(e => {
const instance = instance_map.get(e.target);
if (!instance) return;
instance.setState({
inView: e.isIntersecting,
});
});
},
{
rootMargin: '50px',
}
);
export class InView extends Component<Props, State> {
state: State = {
inView: true,
ref: undefined,
};
componentWillUpdate(_nextProps: Props, nextState: State) {
if (this.state.ref === nextState.ref) return;
if (this.state.ref instanceof Element) {
observer.unobserve(this.state.ref);
instance_map.delete(this.state.ref);
}
if (nextState.ref instanceof Element) {
observer.observe(nextState.ref);
instance_map.set(nextState.ref, this);
}
}
refSetter = async (ref: Component | null) => {
await sleep(1);
const el = ref ? ref.base : undefined;
if (el === this.state.ref) return;
this.setState({
ref: ref ? ref.base : undefined,
});
};
componentWillUnmount() {
if (!(this.state.ref instanceof Element)) return;
observer.unobserve(this.state.ref);
instance_map.delete(this.state.ref);
}
render() {
const props = { inView: this.state.inView, ref: this.refSetter };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const r = (this.props.children as any)[0](props);
return r;
}
}