1

I am trying to copy an open curtain animation like in the following where the side divs expand horizontally on the background image on scroll.

enter image description here

I have a working example with the following code

import { useState, useEffect, useRef, useCallback } from "react";
import "./styles.css";

export default function App() {
  // check window scroll direction
  const [y, setY] = useState(null);
  const [scrollDirection, setScrollDirection] = useState("");

  const boxTwo = useRef(null);
  const boxTwoLeft = useRef(null);
  const boxTwoRight = useRef(null);
  const countRefTranslateX = useRef(0);

  // check window scroll direction https://stackoverflow.com/questions/62497110/detect-scroll-direction-in-react-js
  const handleScrollDirection = useCallback(
    (e) => {
      const window = e.currentTarget;
      if (y > window.scrollY) {
        setScrollDirection("up");
      } else if (y < window.scrollY) {
        setScrollDirection("down");
      }
      setY(window.scrollY);
    },
    [y]
  );

  const handleScroll = useCallback(() => {
    if (boxTwo.current) {
      let position = boxTwo.current.getBoundingClientRect();
      // checking for partial visibility and if 50 pixels of the element is visible in viewport
      if (
        position.top + 50 < window.innerHeight &&
        position.bottom >= 0 &&
        scrollDirection === "down"
      ) {
        countRefTranslateX.current = countRefTranslateX.current + 3;
        boxTwoLeft.current.style.transform = `translateX(-${countRefTranslateX.current}px)`;
        boxTwoRight.current.style.transform = `translateX(${countRefTranslateX.current}px)`;
      } else if (
        position.top + 50 < window.innerHeight &&
        position.bottom >= 0 &&
        scrollDirection === "up"
      ) {
        countRefTranslateX.current = countRefTranslateX.current - 3;
        boxTwoLeft.current.style.transform = `translateX(-${countRefTranslateX.current}px)`;
        boxTwoRight.current.style.transform = `translateX(${countRefTranslateX.current}px)`;
      } else {
        countRefTranslateX.current = 0;
        boxTwoLeft.current.style.transform = `translateX(-${countRefTranslateX.current}px)`;
        boxTwoRight.current.style.transform = `translateX(${countRefTranslateX.current}px)`;
      }
    }
  }, [scrollDirection]);

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, [handleScroll]);

  useEffect(() => {
    setY(window.scrollY);
    window.addEventListener("scroll", handleScrollDirection);
    return () => {
      window.removeEventListener("scroll", handleScrollDirection);
    };
  }, [handleScrollDirection]);

  return (
    <div className="App">
      <div className="boxOne"></div>
      <div ref={boxTwo} className="boxTwo">
        <div ref={boxTwoLeft} className="boxTwoLeft"></div>
        <div ref={boxTwoRight} className="boxTwoRight"></div>
      </div>
      <div className="boxThree"></div>
    </div>
  );
}

I have two issues.

  1. My right white div keeps going to the left while I scroll up

  2. I cannot get a fixed vertical window breakpoint. My animation continues after the window has scrolled after the point I want to start/stop moving the divs

How can I resolve these issues?

My codesandbox

dev_el
  • 2,357
  • 5
  • 24
  • 55

1 Answers1

2

I've looked over what you've done but it's way too complicated.

All you want is to place two curtains (panels) on top of your content. The container should have position:relative and the curtains should have position: absolute.

You then declare the scrollLimits in between which they should move. In the example below, 0 is the starting point and window.innerHeight the end point. Replace those values with whatever makes sense for your section, considering its vertical position in the page. You could use the section's current offsetTop and clientHeight to set the limits dynamically, based on current window size.

You then get the current scroll position and calculate the scroll percentage relative to the limits.

You then apply the percentage/2 + 50% to each curtain's transform: translateX() the left one with negative value, the right one with positive value.

Done.

document.addEventListener('scroll', revealSection);

/* function revealSection() {
  const scrollLimits = [0, window.innerHeight];
  const currentScroll = window.scrollY;
  const percent = Math.min(currentScroll * 100 / (scrollLimits[1] - scrollLimits[0]), 100);
  document.querySelector('.l-panel').style.transform = `translateX(-${percent/2 + 50}%)`;
  document.querySelector('.r-panel').style.transform = `translateX(${percent/2 + 50}%)`;
} */

function revealSection() {
  const top = document.querySelector('section').getBoundingClientRect().top;
  const percent = Math.min(Math.max(0, (window.innerHeight - top) / window.innerHeight * 100), 100);
  document.querySelector('.l-panel').style.transform = `translateX(-${percent/2 + 50}%)`;
  document.querySelector('.r-panel').style.transform = `translateX(${percent/2 + 50}%)`;
}
body {
  margin: 0;
}
section {
  background-image: url('https://static.toss.im/assets/homepage/new tossim/section2_4_big.jpg');
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  width: 100%;
  height: 100vh;
  margin: 100vh 0;
  position: relative;
  overflow: hidden;
}
.l-panel, .r-panel{
  position: absolute;
  height: 100%;
  width: 100%;
  content: '';
  top: 0;
  background-color: white;
  left: 0;
}
<section>
  <div class="l-panel"></div>
  <div class="r-panel"></div>
</section>

Note: change the CSS selectors so the styles apply to your section only.

Edit: I've come up with a more generic way to set the scroll interval, using the section's getBoundingClientRect().top and window.innerHeight. It's probably more useful, as you no longer have to worry about the section's position in page.
I've left the previous version in, for reference and/or for anyone who prefers it.

tao
  • 82,996
  • 16
  • 114
  • 150
  • The animation above starts with closing the curtain entirely and then opening on scroll. How would one change it so that the curtain is partially opened (like the link example) and the have it open it entirely on scroll? I've been playing with a few values but can't seem to figure it out. – dev_el Oct 12 '21 at 05:03
  • 1
    @dev. Each half is translated by the following percentages: `50 + percentage / 2`. The right one positive, the left one negative. Or, with a more intuitive syntax: `50 + percentage * 0.5`. If you want to give it a gap of 20% at start: `translateX(${percent*.4 + 60}%)`. Similarly: 50% initial gap: `translateX(${percent*.25 + 75}%)` – tao Oct 12 '21 at 05:13
  • for anyone curious, I made a react version of the answer above that starts with a partially opened curtain https://codesandbox.io/s/open-curtain-animation2-fw7fx?file=/src/App.js – dev_el Oct 12 '21 at 05:48