skip to Main Content

I use "Scrollytelling" in my React application and need to change an svg dynamically according to the current scrollprogress of a section.
For example like this:

  useEffect(() => {
    if(scrollProgress > 0.5 && scrollProgress < 0.7) foo(); // Called at each change of scrollProgress
    if(scrollProgress > 0.7 && scrollProgress < 0.9) bar(); // Called at each change of scrollProgress
    // etc.
  }, [scrollProgress]);

But I do not want to call these functions at every change of scrollProgress which changes very frequently, I only want to call it once (and not at the first render of the element then I would use an empty dependeny-Array).
Surely this has to be something that happens all the time. I cannot imagine to make a state variable "hasCalled" for each function I want to call in such a scenario and change it accordingly.

Heres an example of where I want to draw an arrow only if the scrollProgress reaches a certain threshold (problem is, it keeps calling the drawArrow() function as scrollProgress keeps on changing)

Demonstration

What is the proper way to handle something like this?

2

Answers


  1. By initialize a ref with start, end, action ( which is the function will get executed ), called ( flag to determine if it get called or not ), then check the scrollProgress is in the range and didn’t get called, then execute the action, and then mark the called to be true.

    function Component({ scrollProgress }) {
    
      const actions = useRef([
        { start: 0.5, end: 0.7, action: action1, called: false },
        { start: 0.7, end: 0.9, action: action2, called: false },
        // Add more actions if needed
      ]).current;
    
      useEffect(() => {
        actions.forEach((item) => {
          if (scrollProgress > item.start && scrollProgress < item.end && !item.called) {
            item.action();
            item.called = true;  // Mark as called
          } else if (scrollProgress <= item.start || scrollProgress >= item.end) {
            item.called = false; // Reset if out of range
          }
        });
      }, [scrollProgress, actions]);
    
      return <div>Current Scroll: {scrollProgress}</div>;
    }
    
    Login or Signup to reply.
  2. If you want to fire your effect when some condition changes you should use that condition in the dependency array:

    const shouldFoo = scrollProgress > 0.5 && scrollProgress < 0.7;
    const shouldBar = scrollProgress > 0.7 && scrollProgress < 0.9;
    
    useEffect(() => {
        if (shouldFoo) foo();
    }, [shouldFoo]);
    
    useEffect(() => {
        if (shouldBar) bar();
    }, [shouldBar]);
    

    foo and bar will be called every time their respective condition changes from false to true. If this is not enough for your case you should add extra logic to ensure that conditions change only once per lifecycle of your component. Or undo the effects when conditions turn back to false:

    useEffect(() => {
        if (shouldFoo) {
            foo();
            return () => undoFoo();
        }
    }, [shouldFoo]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search