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 to achieve this where I take the total height pixels and move my components accordingly. The problem with this is that it looks perfect on my screen, but as soon as I resize it to a bigger or smaller screen the layout gets janky. Is there anyway to have this same effect but responsive friendly. Feel free to edit the CodeSandBox

CodeSandBox(View in fullscreen for better reference): https://codesandbox.io/s/stoic-rumple-s8cr6?file=/src/App.js

Code:

export default function App() {
  const [index, setIndex] = useState(false);
  const [display, setDisplay] = useState(false);
  const [number, setNumber] = useState(false);
  const [screen, setScreen] = useState(false);

  useEffect(function onFirstMount() {
    const changeBackground = () => {
      let value = window.scrollY;
      console.log(value);
      let img = document.getElementById("moveLeft");
      let text = document.getElementById("moveUp");
      let text2 = document.getElementById("text2");
      let text3 = document.getElementById("text3");
      let text4 = document.getElementById("text4");

      let imgWidth = 280;

      text.style.marginTop = "-" + value * 0.5 + "px";
      text2.style.transform = `translateX(${value * 1.3}px)`;
      text3.style.transform = `translateX(-${value * 1.3}px)`;
      text4.style.transform = `translateX(${value * 1.3}px)`;

      if (value > 600) {
        img.style.transform = `translateX(${value * 0.8 - 480 - imgWidth}px)`;
      } else {
        img.style.transform = `translateX(-${value * 0.5}px)`;
      }

      if (value > 1400) {
        img.style.transform = `translateX(${
          -1 * (value * 0.8 - 1120) + 80 + imgWidth
        }px)`;
      }

      if (value > 1700) {
        setNumber(true);
      } else {
        setNumber(false);
      }

      if (value > 1100) {
        setIndex(true);
      } else {
        setIndex(false);
      }
    };
    window.addEventListener("scroll", changeBackground);
    return () => window.removeEventListener("scroll", changeBackground);
  }, []);

  return (
    <>
      <div className="App">
        <div className="middletext" id="moveUp" style={{ zIndex: "9" }}>
          Random Text
        </div>

        <div class="inflow">
          <div class="positioner">
            <div class="fixed" style={{ zIndex: "11" }}>
              <div id="moveLeft">
                <img
                  alt="passport"
                  src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg"
                />
              </div>
            </div>
          </div>
          <div className="halfWindow" style={{ zIndex: "8" }}></div>
          <div>
            <div class="fixedText" style={{ zIndex: "7" }}>
              <div id="text2" className="text2">
                Random Text
              </div>
            </div>
          </div>
          <div className="secondhalfWindow" style={{ zIndex: index ? "10" : "6" }}></div>
          <div>
            <div
              class="secondfixedText"
              style={{
                zIndex: index ? "9" : "5",
                display: "block"
              }}
            >
              <div id="text3" className="text3">
                Random Text 2
              </div>
            </div>
          </div>

          <div className="thirdhalfWindow" style={{ zIndex: "4" }}></div>
          <div>
            <div
              class="thirdfixedText"
              style={{
                zIndex: number ? "10" : "3"
              }}
            >
              <div id="text4" className="text4">
                Random Text 3
              </div>
            </div>
          </div>
        </div>
      </div>
    </>

Using vw instead of px should work but the calculations are really complex.

Answer

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(
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
);
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(
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
);
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(
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
);
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(
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
);
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(
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
);
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>