0

I want to change the id if the mouse is scrolled down or up the id is automatically replaced according to the scroll position

 const ref = useRef();
  const handleScroll = useCallback(() => {
    console.log("scrolling");
  }, []);
  useEffect(() => {
    const div = ref.current;
    div.addEventListener('scroll', handleScroll);
  }, [handleScroll]);

  return (
    <React.Fragment>
      <Indicator />
      <section 
        id="story"
        ref={ref}
      >
       <Story />
      </section>
      <section id="company">
        <Company />
      </section>
      <section id="milestone">
        <Milestone />
      </section>
      <section id="business">
        <Business />
      </section>
      <section id="business_maps">
        <Maps />
      </section>
    </React.Fragment>
  )

I've made a scroll function in the indicator component, it works fine because it's clicked

For example, when the function is clicked, it will go to the content according to the id that was clicked

 const scrollToElement = (id, value) => {
    const element = document.getElementById(`${value}`);
    element.scrollIntoView({ behavior: 'smooth' });
    setActive(id);
  }
{indicator.map(list => (
        <div
          id={list.id}
          key={list.id}
          className={`${list.id === active ? `${style.active} ${style.dot}` `${style.dot}`}`}
          onClick={() => scrollToElement(list.id, list.name)}
        >
          <small className={style.hide}>{list.name}</small>
        </div>
      ))}

1 Answers1

0

You may need a listener to know the current scroll position while scrolling and compare it to the calculated rendered position of each item.

const scrollHandler = (e) => {
  // you could get a list of items' position first
  // then compare it here with the e.target.documentElement.scrollTop
  console.log("scrollTop", e.target.documentElement.scrollTop);
  // if it's a fit (within the zone), set you state
};

useEffect(() => {
  document.addEventListener("scroll", scrollHandler, true);
  return () => document.removeEventListener("scroll", scrollHandler, true);
}, []);

the full demo could be found here: https://codesandbox.io/s/demo-listen-scroll-position-info-ioeu6?file=/src/App.js and below as well.

it has done the following:

  • initial get the list items with multiple ref, (which could be referred here: https://stackoverflow.com/a/60977523/11872246).
  • acquire their position once rendered via the API getBoundingClientRect()
  • add a listener to get the scrolling position once scrolled.
  • compare them to check where is the current item on scrolling inside the handler and then set the state to use.
import "./styles.css";
import { useEffect, useState, useRef, useCallback } from "react";

export default function App() {
  const list = [...Array(20).keys()];
  const [currentItem, setCurrentItem] = useState(undefined);
  const [listItemPosition, setListItemPosition] = useState([]);
  const listRef = useRef([]);

  useEffect(() => {
    if (listItemPosition.length === 0 && listRef.current.length > 0) {
      const positions = listRef.current.map((x) => x.getBoundingClientRect().y);
      setListItemPosition(positions);
    }
  }, [listItemPosition.length]);

  const scrollHandler = useCallback(
    (e) => {
      const currentPosition = e.target.documentElement.scrollTop;
      let item = undefined;
      listItemPosition.forEach((x, idx, self) => {
        if (
          item !== idx &&
          x <= currentPosition &&
          (self[idx + 1] === undefined || currentPosition <= self[idx + 1])
        ) {
          setCurrentItem(idx);
        }
      });
    },
    [listItemPosition]
  );

  useEffect(() => {
    document.addEventListener("scroll", scrollHandler, true);
    return () => document.removeEventListener("scroll", scrollHandler, true);
  }, [scrollHandler]);

  return (
    <div className="App">
      <h1 style={{ height: 2000, color: "blue" }}>
        <div
          style={{
            position: "sticky",
            top: 0,
            backgroundColor: "white"
          }}
        >
          current item: {currentItem}
        </div>
        {list.map((x) => (
          <div
            key={x}
            ref={(el) => (listRef.current[x] = el)}
            style={{
              height: 50,
              border: "1px solid black"
            }}
          >
            {x}
          </div>
        ))}
      </h1>
    </div>
  );
}

or some packages to help you check directly whether an item is going into the viewport,

like react-in-viewport

keikai
  • 14,085
  • 9
  • 49
  • 68