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.
For a solution is refactoring your code.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With