How to fix this overlap scroll effect in React?

71 Views Asked by At

I am currently facing an issue with a scroll effect that I'm trying to implement in React.

Here is the desired effect , I was able to implement it in react using ChatGPT, as long as all the panels have a fixed height of 100vh for example the scroll effect works as inteled.

For demo I've set up this codesandbox example

The issue is that the div with the double-panel class a a height of 200vh, this seems to break the scroll effect due to the fixed position of panel-fixed and panel-inner.

Once a reach a panel with a height of over 100vh I should be able to scroll over it, without it being fixed and it should be fixed only after its bottom reaches the bottom of the page.

How should I proceed?

1

There are 1 best solutions below

0
Wongjn On

You'd want to look at comparing the bottom Y values:

const panelsY = Array.from(panels).map((panel) => panel.offsetTop + panel.offsetHeight);

…
function updateWindow() {
  const y = _window.scrollY + _window.innerHeight;

const { useEffect, Fragment } = React;

function App() {
  const placeholderText =
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
  useEffect(() => {
    // Set up vars
    const _window = window;
    const panels = document.querySelectorAll(".panel");
    const panelsY = Array.from(panels).map((panel) => panel.offsetTop + panel.offsetHeight);

    // Bind our function to window scroll
    _window.addEventListener("scroll", updateWindow);

    // Update the window
    function updateWindow() {
      const y = _window.scrollY + _window.innerHeight;
      console.log("ye");
      // Loop through our panel positions
      let i;
      for (i = 0; i < panels.length; i++) {
        /* 
            Firstly, we break if we're checking our last panel,
            otherwise we compare if the y position is in between
            two panels
          */
        if (
          i === panels.length - 1 ||
          (y >= panelsY[i] && y <= panelsY[i + 1])
        ) {
          break;
        }
      }

      // Update classes
      panels.forEach((panel, index) => {
        if (index === i) {
          panel.classList.add("panel-fixed");
        } else {
          panel.classList.remove("panel-fixed");
        }
      });
    }

    // Cleanup event listener on component unmount
    return () => {
      _window.removeEventListener("scroll", updateWindow);
    };
  }, []);

  return (
    <Fragment>
      <div className="panel bg-teal-200">
        <div className="panel-inner text-4xl">{placeholderText}</div>
      </div>
      <div className="panel bg-amber-500">
        <div className="panel-inner text-4xl">{placeholderText}</div>{" "}
      </div>
      <div className="panel double-panel bg-red-600">
        <div className="panel-inner text-6xl">
          <p>{placeholderText}</p>
        </div>{" "}
      </div>
      <div className="panel bg-purple-200">
        <div className="panel-inner text-4xl">{placeholderText}</div>{" "}
      </div>
      <div className="panel bg-green-200">
        <div className="panel-inner text-4xl">{placeholderText}</div>{" "}
      </div>
    </Fragment>
  );
}

ReactDOM.createRoot(document.getElementById("app")).render(<App />);
.panel {
  position: relative;
  z-index: 5;
  min-height: 100vh;
}

.panel-fixed {
  z-index: 1;
}

.panel-inner {
  width: 100%;
}

.panel-fixed .panel-inner {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 2;
}

.double-panel {
  position: relative;
  z-index: 5;
  min-height: 200vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.3.3"></script>

<div id="app"></div>

Then you may wish to avoid the jumps that happens, with the inner content of the double panel being fixed back to top: 0 and when elements are taller than the min-height of the .panel parent:

.panel-inner {
  …
  height: 100vh;
  overflow: hidden;
}

.double-panel .panel-inner {
  …
  height: 200vh;
}

const { useEffect, Fragment } = React;

function App() {
  const placeholderText =
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
  useEffect(() => {
    // Set up vars
    const _window = window;
    const panels = document.querySelectorAll(".panel");
    const panelsY = Array.from(panels).map((panel) => panel.offsetTop + panel.offsetHeight);

    // Bind our function to window scroll
    _window.addEventListener("scroll", updateWindow);

    // Update the window
    function updateWindow() {
      const y = _window.scrollY + _window.innerHeight;
      console.log("ye");
      // Loop through our panel positions
      let i;
      for (i = 0; i < panels.length; i++) {
        /* 
            Firstly, we break if we're checking our last panel,
            otherwise we compare if the y position is in between
            two panels
          */
        if (
          i === panels.length - 1 ||
          (y >= panelsY[i] && y <= panelsY[i + 1])
        ) {
          break;
        }
      }

      // Update classes
      panels.forEach((panel, index) => {
        if (index === i) {
          panel.classList.add("panel-fixed");
        } else {
          panel.classList.remove("panel-fixed");
        }
      });
    }

    // Cleanup event listener on component unmount
    return () => {
      _window.removeEventListener("scroll", updateWindow);
    };
  }, []);

  return (
    <Fragment>
      <div className="panel bg-teal-200">
        <div className="panel-inner text-4xl">{placeholderText}</div>
      </div>
      <div className="panel bg-amber-500">
        <div className="panel-inner text-4xl">{placeholderText}</div>{" "}
      </div>
      <div className="panel double-panel bg-red-600">
        <div className="panel-inner text-6xl">
          <p>{placeholderText}</p>
        </div>{" "}
      </div>
      <div className="panel bg-purple-200">
        <div className="panel-inner text-4xl">{placeholderText}</div>{" "}
      </div>
      <div className="panel bg-green-200">
        <div className="panel-inner text-4xl">{placeholderText}</div>{" "}
      </div>
    </Fragment>
  );
}

ReactDOM.createRoot(document.getElementById("app")).render(<App />);
.panel {
  position: relative;
  z-index: 5;
  min-height: 100vh;
}

.panel-fixed {
  z-index: 1;
}

.panel-inner {
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

.panel-fixed .panel-inner {
  position: fixed;
  bottom: 0;
  left: 0;
  z-index: 2;
}

.double-panel {
  position: relative;
  z-index: 5;
  min-height: 200vh;
}

.double-panel .panel-inner {
  width: 100%;
  height: 200vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.tailwindcss.com/3.3.3"></script>

<div id="app"></div>

You could also consider this CSS-only solution using position: sticky:

.panel {
  position: sticky;
  top: 0;
  min-height: 100vh;
}

.double-panel {
  top: -100vh;
  min-height: 200vh;
}
<script src="https://cdn.tailwindcss.com/3.3.3"></script>

<div class="panel bg-teal-200">
  <div class="panel-inner text-4xl">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
</div>
<div class="panel bg-amber-500">
  <div class="panel-inner text-4xl">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
</div>
<div class="panel double-panel bg-red-600">
  <div class="panel-inner text-6xl">
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  </div>
</div>
<div class="panel bg-purple-200">
  <div class="panel-inner text-4xl">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
</div>
<div class="panel bg-green-200">
  <div class="panel-inner text-4xl">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
</div>