From a36733a2e31b04ba7af14383ad6dc0c0503162ff Mon Sep 17 00:00:00 2001 From: Vyrtsev Mikhail Date: Sun, 28 Jul 2019 23:59:55 +0300 Subject: [PATCH] add InView component which tracks element intersection with view --- .../app/components/root/in-view/in-view.tsx | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 frontend/app/components/root/in-view/in-view.tsx diff --git a/frontend/app/components/root/in-view/in-view.tsx b/frontend/app/components/root/in-view/in-view.tsx new file mode 100644 index 00000000..3262665c --- /dev/null +++ b/frontend/app/components/root/in-view/in-view.tsx @@ -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> = 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 { + 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; + } +}