Skip to content Skip to sidebar Skip to footer

Using UseEffect To Manipulate DOM Events Not Responsive On Different Screens

Currently I'm trying to achieve the following parallax effect in React where my image is in a fixed position vertically but moves left to right along with text. I've used useEffect

Solution 1:

For a solution is refactoring your code.

  1. to place all the elements in the center and create four sections.
  2. define the center of the sections this help to find where need stop move image and move it back.
  3. in the same way need to do with text.
  4. when the window is resized all content is centered with window.resize.

Sandbox example link

function App() {
  const [screen, setScreen] = React.useState(false);

  const ref = React.useRef(null);

  // Reduce value if want the image to be closer to the edges
  // otherwise to the center
  const setImageLimitMovement = 2;

  const setTextLimitMovement = 4;
  const opacityRange = 400;
  // Speed text movement
  const speed = 2; // .5

  React.useEffect(() => {
    window.addEventListener("resize", () => {
      if (window.innerWidth !== 0) {
        setScreen(window.innerWidth);
      }
    });
  }, []);

  React.useEffect(() => {
    const app = [...ref.current.children];
    const titles = app.filter((el) => el.matches(".titles") && el);
    const blocks = app.filter((el) => el.matches(".blocks") && el);
    const img = app.find((el) => el.matches("#passport") && el);

    // Get the center point of  blocks in an array
    const centerPoints = blocks.map((blockEl, idx) => {
      const blockindex = idx + 1;
      const blockHeight = Math.floor(blockEl.getBoundingClientRect().height);
      const blockHalf = blockHeight / 2;
      return blockHeight * blockindex - blockHalf;
    });

    const leftMoveLimitImg = -centerPoints[0] / setImageLimitMovement;
    const rightMoveLimitImg = centerPoints[0] / setImageLimitMovement;

    const textLimit = centerPoints[0] / setTextLimitMovement;

    const changeBackground = () => {
      const value = window.scrollY;

      titles[0].style.transform = `translateY(-${value * speed}px)`;
      // IMAGE BOUNCE
      // Move to <==
      if (centerPoints[0] > value) {
        img.style.transform = `translateX(-${
          value * (1 / setImageLimitMovement)
        }px)`;

        titles[1].style.transform = `translateX( ${
          0 + value / setTextLimitMovement
        }px)`;
        titles[1].style.opacity = value / opacityRange;
        return;
      }

      // Move to ==>
      if (centerPoints[1] > value) {
        const moveTextToRight =
          centerPoints[1] / setTextLimitMovement - textLimit;
        const hideText = centerPoints[0] / opacityRange;
        const checkDirection = Math.sign(
          textLimit + (textLimit - value / setTextLimitMovement)
        );

        const moveImageToRight =
          (value - centerPoints[0]) / setImageLimitMovement;
        img.style.transform = `translateX(${
          leftMoveLimitImg + moveImageToRight
        }px)`;

        if (checkDirection === -1) {
          titles[1].style.opacity = 0;
          titles[1].style.transform = `translateX(${0}px)`;

          titles[2].style.opacity =
            Math.abs(hideText - value / opacityRange) - 1;
          titles[2].style.transform = `translateX(${
            moveTextToRight - value / setTextLimitMovement
          }px)`;
          return;
        }
        if (checkDirection === 1) {
          titles[1].style.opacity = 1 + (hideText - value / opacityRange);
          titles[1].style.transform = `translateX(${
            textLimit + (textLimit - value / setTextLimitMovement)
          }px)`;

          titles[2].style.opacity = 0;
          titles[2].style.transform = `translateX(${0}px)`;
        }
        return;
      }

      // Move to <==
      if (centerPoints[2] > value) {
        const moveTextToLeft =
          centerPoints[2] / setTextLimitMovement - textLimit;
        const hideText = centerPoints[1] / opacityRange;
        const checkDirection = Math.sign(
          moveTextToLeft - value / setTextLimitMovement
        );

        const moveImageToLeft =
          (-value + centerPoints[1]) / setImageLimitMovement;
        img.style.transform = `translateX(${
          rightMoveLimitImg + moveImageToLeft
        }px)`;

        if (checkDirection === -1) {
          titles[2].style.opacity = 0;
          titles[2].style.transform = `translateX(${0}px)`;

          titles[3].style.opacity =
            Math.abs(hideText - value / opacityRange) - 1;
          titles[3].style.transform = `translateX(${Math.abs(
            moveTextToLeft - value / setTextLimitMovement
          )}px)`;
        }

        if (checkDirection === 1) {
          titles[2].style.opacity = 1 + (hideText - value / opacityRange);
          titles[2].style.transform = `translateX(-${
            moveTextToLeft - value / setTextLimitMovement
          }px)`;

          titles[3].style.opacity = 0;
          titles[3].style.transform = `translateX(${0}px)`;
        }
        return;
      }

      // Move to ==>
      if (centerPoints[3] > value) {
        const moveTextToRight =
          centerPoints[3] / setTextLimitMovement - textLimit;
        const hideText = centerPoints[2] / opacityRange;
        const checkDirection = Math.sign(
          moveTextToRight - value / setTextLimitMovement
        );

        const moveImageToRight =
          (value - centerPoints[2]) / setImageLimitMovement;
        img.style.transform = `translateX(${
          leftMoveLimitImg + moveImageToRight
        }px)`;

        if (checkDirection === -1) {
          titles[3].style.opacity = 0;
          titles[3].style.transform = `translateX(${0}px)`;
        }
        if (checkDirection === 1) {
          titles[3].style.opacity = 1 + (hideText - value / opacityRange);
          titles[3].style.transform = `translateX(${
            moveTextToRight - value / setTextLimitMovement
          }px)`;
        }
        return;
      }

      window.requestAnimationFrame(changeBackground);
    };
    window.addEventListener("scroll", changeBackground);
    return () => window.removeEventListener("scroll", changeBackground);
  }, [screen]);

  return (
    <div className="App" ref={ref}>
      <h1 id="title" className="titles">
        Random Title
      </h1>
      <section id="block1" className="blocks">
        <h4>Block 1</h4>
      </section>
      <figure id="passport">
        <img
          alt="passport"
          src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg"
        />
      </figure>
      <h2 id="text1" className="titles text1">
        Random Text 1
      </h2>
      <section id="block2" className="blocks">
        <h4>Block 2</h4>
      </section>
      <h2 id="text2" className="titles text2">
        Random Text 2
      </h2>
      <section id="block3" className="blocks">
        <h4>Block 3</h4>
      </section>
      <h2 id="text3" className="titles text3">
        Random Text 3
      </h2>
      <section id="block4" className="blocks">
        <h4>Block 4</h4>
      </section>
    </div>
  );
}


const rootElement = document.getElementById("root");
ReactDOM.render( <
  App / > ,
  rootElement
);
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
.App {
  font-family: sans-serif;
  width: 100%;
  background-color: hsl(220, 65%, 16%);
}
figure {
  width: 280px;
  height: max-content;
  position: fixed;
  inset: 0;
  margin: auto;
  z-index: 100;
}
img {
  width: 100%;
}

.blocks {
  height: 100vh;
  display: flex;
  position: relative;
  grid-column: 1 / -1;
  color: grey;
}

.titles {
  width: max-content;
  height: max-content;
  position: fixed;
  inset: 0;
  margin: auto;
  color: white;
  z-index: 99;
}

h1 {
  font-size: 3.5em;
}
h2 {
  display: flex;
  opacity: 0;
  font-size: 2.5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Solution 2:

You have two options.

  1. make this effect available only for the screen width size you intended
  2. calculate how many pixels you should move the passport depending on the window size of the browser.

The latter solution can make your application annoying to maintain because there will be so many devices to consider.

How to get window size

  const [size, setSize] = useState({
    x: window.innerWidth,
    y: window.innerHeight
  });
  const updateSize = () =>
    setSize({
      x: window.innerWidth,
      y: window.innerHeight
    });
  useEffect(() => (window.onresize = updateSize), []);

Post a Comment for "Using UseEffect To Manipulate DOM Events Not Responsive On Different Screens"