so i have this members page where i fetch data from a json and then according to the size of team the width of #membersBox will be set once they are loaded . so the issue over here is i am using gsap to animate it and gsap runs before the width is set thus the variable "H" is set to 0, and my animation dosen't work . how do i solve it? Here is the code:
import { useEffect, useState } from 'react'
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import './membersPage.css'
import data from './members.json'
const membersPage = () => {
gsap.registerPlugin(ScrollTrigger);
useGSAP(() => {
const H = document.querySelector("#membersBox").clientWidth;
let mm = gsap.matchMedia();
mm.add("(max-width: 767px)", () => {
gsap.to(".teamBox", {
x: -H - 1300,
scrollTrigger: {
trigger: ".teamWrap",
scroller: "body",
start: "top 0%",
end: `top -${H / 5}%`,
scrub: 2,
pin: true,
},
});
});
mm.add("(min-width: 768px)", () => {
gsap.to(".teamBox", {
x: -H - 300,
scrollTrigger: {
trigger: ".teamWrap",
scroller: "body",
start: "top 0%",
end: `top -${H / 2}%`,
scrub: 2,
pin: true,
},
});
});
})
const [team, setTeam] = useState('')
// let [searchParams, setSearchParams] = useSearchParams();
useEffect(() => {
// setTeam(searchParams.get("team"))
var len = data['web'].length
if (len % 2 != 0) {
len = len + 1
for (let i = 0; i < len; i++) {
if (!document.getElementById(`wave${i}`)) {
if (i == 0) {
document.getElementById('wave').innerHTML += `
<div className="waveBox">
<img id="wave${i}" style="position: relative;" src="/PublicAssets/teamWave.svg" alt="" key=${i} />
</div>
`
}
else {
let leftVal = i * 28
document.getElementById('wave').innerHTML += `
<div className="waveBox">
<img id="wave${i}" style="position: relative;left: -${leftVal}px;" src="/PublicAssets/teamWave.svg" alt="" key=${i} />
</div>
`
}
}
}
}
else {
for (let i = 0; i < len; i++) {
if (!document.getElementById(`wave${i}`)) {
if (i == 0) {
document.getElementById('wave').innerHTML += `
<div className="waveBox">
<img id="wave${i}" style="position: relative;" src="/PublicAssets/teamWave.svg" alt="" key=${i} />
</div>
`
}
else {
let leftVal = i * 28
document.getElementById('wave').innerHTML += `
<div className="waveBox">
<img id="wave${i}" style="position: relative;left: -${leftVal}px;" src="/PublicAssets/teamWave.svg" alt="" key=${i} />
</div>
`
}
}
}
}
data['web'].forEach((e, i) => {
if (!document.getElementById(`member${i}`)) {
if (i == 0) {
document.getElementById('membersBox').innerHTML += `
<div className="member" id="member${i}" style="left:70px;flex-direction: column-reverse;bottom:0px">
<div id="teamDetailCont">
<div id="memName">${e.name}</div>
<div id="msg">"${e.msg}"</div>
</div>
<div id="mask">
<img id="teamImg" src="${e.img}" alt="" />
</div>
</div>
`
}
else {
var bottom = i % 2 == 0 ? 0 : -250
var colDirection = i % 2 != 0 ? 'column' : 'column-reverse'
var Left = Number(document.getElementById(`member${i - 1}`).style.left.replace('px', '')) + 135
document.getElementById('membersBox').innerHTML += `
<div className="member" id="member${i}" style="left:${Left}px;flex-direction: ${colDirection};bottom:${bottom}px">
<div id="teamDetailCont">
<div id="memName">${e.name}</div>
<div id="msg">"${e.msg}"</div>
</div>
<div id="mask">
<img id="teamImg" src="${e.img}" alt="" />
</div>
</div>
`
}
}
});
}, [])
return (
<>
<div className='teamWrap'>
<div className="teamCont">
<div className="teamHead">
<h1>Meet the minds behind the <span style={{ position: 'absolute', paddingLeft: '17px' }} >magic</span></h1>
<h1 className='teamNameHead' >The <span className='headHighlight'>{team}</span>{team.toLowerCase() == "board" ? " Members" : " Team"}</h1>
</div>
<div className="teamBox">
<div id='wave' className="wave"></div>
<div id="membersBox"></div>
</div>
</div>
</div>
</>
)
}
export default membersPage
I have removed imports because I am getting them from a CDN, obviously you can keep your imports ^^
I have factorized your rendering conditions (less ifs) but it should render the same.
Avoid at all cost doing innerHTML manipulation while using React ! It seems you did not know how to use loops in react, note the usual:
<div>
{
array.map((element, index) => {
// compute something if you need to and return the constructed element
return (
<span>{element.name /* or whatever with element */}</span>
);
})
}
</div>
If you do not have an array to begin with (for loop with length),you can construct one on the fly this way:
[...Array(12)].map((_, index) => {
// Note that '_' is just a regular variable name,
// but the value is undefined in this case so
// I name it '_' to help visually ignore it
})
Since you GSAP code requires your elements to have a size, wait for your component to be mounted. Note what I did with useEffect, setMounted.
// import { useEffect, useState } from 'react'
// import gsap from "gsap";
// import { useGSAP } from "@gsap/react";
// import { ScrollTrigger } from "gsap/ScrollTrigger";
// import './membersPage.css'
// import data from './members.json'
const { useEffect, useState, Fragment } = React;
const data = {
web: [
{
name: 'name1',
msg:'msg1',
img: 'img1'
},
{
name: 'name2',
msg:'msg2',
img: 'img2'
},
{
name: 'name3',
msg:'msg3',
img: 'img3'
}
]
};
// fake useGSAP, remove in your code, useGSAP also allows dependencies
const useGSAP = useEffect;
const MembersPage = () => {
const [mounted, setMounted] = useState(false);
useGSAP(() => {
if (!mounted) {
return;
}
// gsap.registerPlugin(ScrollTrigger);
const H = document.querySelector("#membersBox").clientWidth;
// removed gsap code we are only testing the width (H)
console.log(H);
}, [mounted]);
const [team, setTeam] = useState('');
// let [searchParams, setSearchParams] = useSearchParams();
useEffect(() => {
// setTeam(searchParams.get("team"))
setMounted(true)
}, []);
var len = data['web'].length;
// if len is odd, make len even by adding 1
if (len % 2 !== 0) {
len++;
}
return (
<div className='teamWrap'>
<div className="teamCont">
<div className="teamHead">
<h1>
Meet the minds behind the
<span style={{ position: 'absolute', paddingLeft: '17px' }}>
magic
</span>
</h1>
<h1 className='teamNameHead' >
The
<span className='headHighlight'>{team}</span>
{team.toLowerCase() == "board" ? " Members" : " Team"}
</h1>
</div>
<div className="teamBox">
<div id='wave' className="wave">
{
[...Array(len)].map((_, i) => {
// if i === 0 then leftVal === 0
let leftVal = i * 28;
return (
<div class="waveBox">
<img
id={`wave${i}`}
style={{ position: 'relative', left: -leftVal }}
src="/PublicAssets/teamWave.svg"
alt="teamWave"
key={i}
/>
</div>
);
})
}
</div>
<div id="membersBox">
{
data['web'].map((e, i) => {
var bottom = i % 2 === 0 ? 0 : -250;
var colDirection = i % 2 !== 0 ? 'column' : 'column-reverse';
var Left = 70 + 135 * i;
return(
<div
className="member"
id={`member${i}`}
style={{ left: Left, flexDirection: colDirection, bottom: bottom }}
>
<div id="teamDetailCont">
<div id="memName">{e.name}</div>
<div id="msg">"{e.msg}"</div>
</div>
<div id="mask">
<img id="teamImg" src={e.img} alt={`img${i+1}`} />
</div>
</div>
);
})
}
</div>
</div>
</div>
</div>
);
};
ReactDOM.render(<MembersPage />, document.getElementById('root'));
/* DO NOT USE THIS CSS, KEEP YOURS*/
#wave {
display: flex;
border: 1px solid violet;
}
#membersBox {
display: flex;
border: 1px solid red;
}
.waveBox {
border: 1px solid blue;
/* min-width, min-height to simulate missing image, remove on your side */
min-width: 100px;
min-height: 50px;
}
.member {
border: 1px solid orange;
min-width: 100px;
min-height: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>