React - check if element is visible in DOM

前端 未结 5 2010
刺人心
刺人心 2020-12-25 12:24

I\'m building a form - series of questions (radio buttons) the user needs to answer before he can move on to the next screen. For fields validation I\'m using yup (npm packa

相关标签:
5条回答
  • 2020-12-25 12:28

    You can attach a ref to the element that you want to check if it is on the viewport and then have something like:

      /**
       * Check if an element is in viewport
       *
       * @param {number} [offset]
       * @returns {boolean}
       */
      isInViewport(offset = 0) {
        if (!this.yourElement) return false;
        const top = this.yourElement.getBoundingClientRect().top;
        return (top + offset) >= 0 && (top - offset) <= window.innerHeight;
      }
    
    
      render(){
    
         return(<div ref={(el) => this.yourElement = el}> ... </div>)
    
      }
    

    You can attach listeners like onScroll and check when the element will be on the viewport.

    You can also use the Intersection Observer API with a polyfil or use a HoC component that does the job

    0 讨论(0)
  • 2020-12-25 12:32

    I have had the same problem, and, looks, I found the pretty good solution in pure react jsx, without installing any libraries.

    import React, {Component} from "react";
        
        class OurReactComponent extends Component {
    
        //attach our function to document event listener on scrolling whole doc
        componentDidMount() {
            document.addEventListener("scroll", this.isInViewport);
        }
    
        //do not forget to remove it after destroyed
        componentWillUnmount() {
            document.removeEventListener("scroll", this.isInViewport);
        }
    
        //our function which is called anytime document is scrolling (on scrolling)
        isInViewport = () => {
            //get how much pixels left to scrolling our ReactElement
            const top = this.viewElement.getBoundingClientRect().top;
    
            //here we check if element top reference is on the top of viewport
            /*
            * If the value is positive then top of element is below the top of viewport
            * If the value is zero then top of element is on the top of viewport
            * If the value is negative then top of element is above the top of viewport
            * */
            if(top <= 0){
                console.log("Element is in view or above the viewport");
            }else{
                console.log("Element is outside view");
            }
        };
    
        render() {
            // set reference to our scrolling element
            let setRef = (el) => {
                this.viewElement = el;
            };
            return (
                // add setting function to ref attribute the element which we want to check
                <section ref={setRef}>
                    {/*some code*/}
                </section>
            );
        }
    }
    
    export default OurReactComponent;
    

    I was trying to figure out how to animate elements if the are in viewport.

    Here is work project on CodeSandbox.

    0 讨论(0)
  • 2020-12-25 12:42

    Answer based on the post from @Alex Gusev

    React hook to check whether the element is visible with a few fixes and based on the rxjs library.

    import React, { useEffect, createRef, useState } from 'react';
    import { Subject, Subscription } from 'rxjs';
    import { debounceTime, throttleTime } from 'rxjs/operators';
    
    /**
     * Check if an element is in viewport
     * @param {number} offset - Number of pixels up to the observable element from the top
     * @param {number} throttleMilliseconds - Throttle observable listener, in ms
     * @param {boolean} triggerOnce - Trigger renderer only once when element become visible
     */
    export default function useVisibleOnScreen<Element extends HTMLElement>(
      offset = 0,
      throttleMilliseconds = 1000,
      triggerOnce = false,
      scrollElementId = ''
    ): [boolean, React.RefObject<Element>] {
      const [isVisible, setIsVisible] = useState(false);
      const currentElement = createRef<Element>();
    
      useEffect(() => {
        let subscription: Subscription | null = null;
        let onScrollHandler: (() => void) | null = null;
        const scrollElement = scrollElementId
          ? document.getElementById(scrollElementId)
          : window;
        const ref = currentElement.current;
        if (ref && scrollElement) {
          const subject = new Subject();
          subscription = subject
            .pipe(throttleTime(throttleMilliseconds))
            .subscribe(() => {
              if (!ref) {
                if (!triggerOnce) {
                  setIsVisible(false);
                }
                return;
              }
    
              const top = ref.getBoundingClientRect().top;
              const visible =
                top + offset >= 0 && top - offset <= window.innerHeight;
              if (triggerOnce) {
                if (visible) {
                  setIsVisible(visible);
                }
              } else {
                setIsVisible(visible);
              }
            });
          onScrollHandler = () => {
            subject.next();
          };
          if (scrollElement) {
            scrollElement.addEventListener('scroll', onScrollHandler, false);
          }
          // Check when just loaded:
          onScrollHandler();
        } else {
          console.log('Ref or scroll element cannot be found.');
        }
    
        return () => {
          if (onScrollHandler && scrollElement) {
            scrollElement.removeEventListener('scroll', onScrollHandler, false);
          }
          if (subscription) {
            subscription.unsubscribe();
          }
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
      }, [offset, throttleMilliseconds, triggerOnce, scrollElementId]);
    
      return [isVisible, currentElement];
    }
    
    0 讨论(0)
  • 2020-12-25 12:43

    Based on Avraam's answer I wrote a Typescript-compatible small hook to satisfy the actual React code convention.

    import { createRef, useEffect, useState } from "react";
    import throttle from "lodash.throttle";
    
    /**
     * Check if an element is in viewport
    
     * @param {number} offset - Number of pixels up to the observable element from the top
     * @param {number} throttleMilliseconds - Throttle observable listener, in ms
     */
    export default function useVisibility<Element extends HTMLElement>(
      offset = 0,
      throttleMilliseconds = 100
    ): [Boolean, React.RefObject<Element>] {
      const [isVisible, setIsVisible] = useState(false);
      const currentElement = createRef<Element>();
    
      const onScroll = throttle(() => {
        if (!currentElement.current) {
          setIsVisible(false);
          return;
        }
        const top = currentElement.current.getBoundingClientRect().top;
        setIsVisible(top + offset >= 0 && top - offset <= window.innerHeight);
      }, throttleMilliseconds);
    
      useEffect(() => {
        document.addEventListener('scroll', onScroll, true);
        return () => document.removeEventListener('scroll', onScroll, true);
      });
    
      return [isVisible, currentElement];
    }
    
    

    Usage example:

    const Example: FC = () => {
      const [ isVisible, currentElement ] = useVisibility<HTMLDivElement>(100);
    
      return <Spinner ref={currentElement} isVisible={isVisible} />;
    };
    
    

    You can find the example on Codesandbox. I hope you will find it helpful!

    0 讨论(0)
  • 2020-12-25 12:43

    Here is a reusable hook that takes advantage of the IntersectionObserver API.

    The hook

    export default function useOnScreen(ref) {
    
      const [isIntersecting, setIntersecting] = useState(false)
    
      const observer = new IntersectionObserver(
        ([entry]) => setIntersecting(entry.isIntersecting)
      )
    
      useEffect(() => {
        observer.observe(ref.current)
        // Remove the observer as soon as the component is unmounted
        return () => { observer.disconnect() }
      }, [])
    
      return isIntersecting
    }
    

    Usage

    const DummyComponent = () => {
      
      const ref = useRef()
      const isVisible = useOnScreen(ref)
      
      return <div ref={ref}>{isVisible && `Yep, I'm on screen`}</div>
    }
    
    0 讨论(0)
提交回复
热议问题