skip to Main Content

codesandbox example here: https://codesandbox.io/s/quirky-wozniak-3qih8p?file=/src/Progress.tsx

I am trying to use the Intersection Observer API to track both the first and last elements of my list. My goal is to display either a left or right button based on the horizontal scroll position.
However, I have run into an issue where the intersection observer is only tracking the first element and I am unsure why this is happening and how to correct it.

3

Answers


  1. This might be because you are observing the left and right arrow with same intersection observer instance. One possible fix can be making two separate instances for both arrows.

    useEffect(() => {
        if (!progressRef || !leftRef.current || !rightRef.current) return;
    
        const leftObserver = new IntersectionObserver(([entry]) => {
          setLeftVisible(!entry.isIntersecting);
        }, {
          root: progressRef.current,
          rootMargin: "0px",
          threshold: 1
        });
    
        const rightObserver = new IntersectionObserver(([entry]) => {
          setRightVisible(!entry.isIntersecting);
        }, {
          root: progressRef.current,
          rootMargin: "0px",
          threshold: 1
        });
    
        leftObserver.observe(leftRef.current);
        rightObserver.observe(rightRef.current);
    
        return () => {
          leftObserver.disconnect();
          rightObserver.disconnect();
        };
      }, []);
    
    Login or Signup to reply.
  2. From the docs, the IntersectionObserver callback is called with:

    entries
    An array of IntersectionObserverEntry objects, each representing one threshold which was crossed, either becoming more or less visible than the percentage specified by that threshold.

    So, you do not necessarily receive both [first, last] elements in the callback – just those whose amount of intersection has changed.

    As a rough cut you might use something like:

        const observer = new IntersectionObserver(
          entries => {
            const lookup = new Map(entries.map(entry => [entry.target, entry.isIntersecting]));
            setLeftVisible(!lookup.get(leftRef.current!));
            setRightVisible(!lookup.get(rightRef.current!));
          },
          ...
        );
    

    Sandbox

    Login or Signup to reply.
  3. A solution without IntersectionObserver:

    function useArrows() {
      const progressRef = useRef<HTMLUListElement>(null);
      const leftArrowRef = useRef<HTMLLIElement>(null);
      const rightArrowRef = useRef<HTMLLIElement>(null);
    
      useEffect(() => {
        const ref = progressRef.current;
        if (!ref) {
          return;
        }
    
        function onScroll(event: Event) {
          const {
            scrollLeft,
            offsetWidth,
            scrollWidth
          } = event.currentTarget as HTMLElement;
    
          const reachedStart = scrollLeft === 0;
          const reachedEnd = offsetWidth + scrollLeft >= scrollWidth;
          leftArrowRef.current?.classList.toggle("invisible", reachedStart);
          rightArrowRef.current?.classList.toggle("invisible", reachedEnd);
        }
    
        ref.addEventListener("scroll", onScroll);
        return () => {
          ref.removeEventListener("scroll", onScroll);
        };
      }, []);
      return {
        progressRef,
        leftArrowRef,
        rightArrowRef
      };
    }
    

    You may want to throttle the onScroll function of course.

    See example

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search