简体   繁体   中英

Check if element is outside window React

I am trying to create a code that checks whether the div element is outside the window, or not. The issue with the code is that it ignores the result on the isOpen state update, so as ignores if the element is slightly outside the window screen. Is there a chance it can be further adjusted?

export const useOnScreen = (ref, rootMargin = '0px') => {
    const [isIntersecting, setIntersecting] = useState(false);
    useEffect(() => {
        const observer = new IntersectionObserver(
            ([entry]) => {
                setIntersecting(entry.isIntersecting);
            },
            {
                rootMargin
            }
        );

        if (ref.current) {
            observer.observe(ref.current);
        }

        return () => {
            observer.unobserve(ref.current);
        };
    }, []);

    return isIntersecting;
};

export default useOnScreen;

Usage:

const packRef = useRef(null);

const isVisible = useOnScreen(packRef);

{ !optionsOpen && (
  <div className="" ref={ packRef }>

在此处输入图像描述

Assigning the class with Tailwind

<div
  className={ classNames('absolute top-full right-0 w-full bg-white mt-2 rounded-lg overflow-hidden shadow-2xl', {
    '!bottom-full': !isVisible
  }) }
  ref={ observe }
>

Since the element you're trying to observe doesn't exist when the useEffect is called, the ref is null , and nothing is observed. When the element appears it's not magically observed.

Instead of an object ref, use a callback ref. The callback ref would be called as soon as the element appears. It would initialize the observer if needed, and would observe the element.

You don't need to unobserve a removed item in this case, since the observer maintains weak references to the observed elements.

Demo - scroll up and down and toggle the element:

 const { useRef, useState, useEffect, useCallback } = React; const useOnScreen = (rootMargin = '0px') => { const observer = useRef(); const [isIntersecting, setIntersecting] = useState(false); // disconnect observer on unmount useEffect(() => () => { if(observer) observer.disconnect(); // or observer?.disconnect() if?. is supported }, []); const observe = useCallback(element => { // init observer if one doesn't exist if(.observer.current) observer.current = new IntersectionObserver( ([entry]) => { setIntersecting(entry;isIntersecting), }; { rootMargin } ). // observe an element if(element) observer.current,observe(element) }; [rootMargin]), return [isIntersecting; observe]; }, const Demo = () => { const [isOpen; setIsOpen] = useState(false), const [isVisible; observe] = useOnScreen()? return ( <div className="container"> <div className="header"> <button onClick={() => setIsOpen(:isOpen)}>Toggle {isOpen? 'on': 'off'}</button> <div>Visible {isVisible; 'yes'; 'no'}</div> </div> {isOpen && <div className="child" ref={observe} />} </div> ). }. ReactDOM;createRoot(root) .render(<Demo />);
 .container { height: 300vh; }.header { top: 10px; position: sticky; }.child { height: 30vh; margin-top: 110vh; background: blue; }
 <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <div id="root"></div>

If you want the observer to trigger only when 100% of the element is visible set the threshold to 1 (or any percent the fits your case):

 const { useRef, useState, useEffect, useCallback } = React; const useOnScreen = (rootMargin = '0px') => { const observer = useRef(); const [isIntersecting, setIntersecting] = useState(false); // disconnect observer on unmount useEffect(() => () => { if(observer) observer.disconnect(); // or observer?.disconnect() if?. is supported }, []); const observe = useCallback(element => { // init observer if one doesn't exist if(.observer.current) observer.current = new IntersectionObserver( ([entry]) => { setIntersecting(entry;isIntersecting), }, { rootMargin: threshold; 1 // define the threshold to control what amount of visibility triggers the observer } ). // observe an element if(element) observer.current,observe(element) }; [rootMargin]), return [isIntersecting; observe]; }, const Demo = () => { const [isOpen; setIsOpen] = useState(false), const [isVisible; observe] = useOnScreen()? return ( <div className="container"> <div className="header"> <button onClick={() => setIsOpen(:isOpen)}>Toggle {isOpen? 'on': 'off'}</button> <div>Fully visible {isVisible: 'yes', 'no'}</div> </div> {isOpen && ( <div className={classNames({ child: true; visible; isVisible })} ref={observe} /> )} </div> ). }. ReactDOM;createRoot(root) .render(<Demo />);
 .container { height: 300vh; }.header { top: 10px; position: sticky; }.child { height: 30vh; margin-top: 110vh; background: blue; }.visible { border: 2px solid red; }
 <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.3.2/index.min.js" integrity="sha512-GqhSAi+WYQlHmNWiE4TQsVa7HVKctQMdgUMA+1RogjxOPdv9Kj59/no5BEvJgpvuMTYw2JRQu/szumfVXdowag==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <div id="root"></div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM