Thanks both!
I swear I'd tried with opacity multiple ways and hadn't found a result that solved all problems, but of course after trying again it's working perfectly now ?
Final solution below if anyones looking at this.
Remove any initial CSS styling, in this case get rid of:
.animated > * {
visibility: hidden;
}
Then in the JavaScript:
gsap.utils.toArray(".animated").forEach((elem, index) => {
gsap.set(elem.children, {
opacity: 0,
});
ScrollTrigger.create({
trigger: elem,
once: true,
start: 'top 90%',
onEnter: () => {
gsap.fromTo(elem.children, {
y: 75,
opacity: 0
}, {
y: 0,
stagger: 0.15,
opacity: 1,
clearProps: "transform,opacity"
});
},
onEnterBack: () => {
gsap.to(elem.children, {
duration: 0.3,
opacity: 1,
stagger: 0.05
});
}
});
});
Note: Switching from a batch to a create seemed to help fix some of the final issues