I'm creating a website in Next.js and in the homepage I have a scrollTriggered and scrubbed timeline.
The animation consists of two parts:
1) An image sequence, which I've done with the help of the codepen at the end https://codepen.io/osublake/pen/VwaKMpw/2152a28cffe2c2c0cca8a3e47f7b21c6
2) Simple tweens fading and zooming images
This is a snippet of what the animation does:
The animation works. I've deployed it to Netlify and it seems ok.
The thing is, though, that every once in a while, the homepage crashes. And what it shows is a weird DOM:
When I disable the animation I never get this weird DOM. Now, I'm not sure if the deployment is to blame or my gsap code is.
So I'd like to ask you if you could take a look at my code and see if I'm doing something obviously wrong. Thank you so much!
"use client";
import { useRef, useLayoutEffect, useContext } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
import NextImage from "next/image";
import { useRouter } from "next/navigation";
import { MenuContext } from "@/Providers/MenuProvider";
import styles from "./AnimatedSequence.module.css";
const AnimatedSequence = ({ projectImages }) => {
const router = useRouter();
const canvasRef = useRef(null);
const animation = useRef(null);
const { setHeaderAndMenuHidden } = useContext(MenuContext);
useLayoutEffect(() => {
setHeaderAndMenuHidden(true);
// Here's the canvas that will hold the image sequence
const canvas = canvasRef.current;
const context = canvas.getContext("2d");
canvas.width = 3024;
canvas.height = 1983;
const frameCount = 18;
const currentFrame = (index) => `imageSequence/frame_${index.toString().padStart(2, "0")}.jpg`;
const images = [];
const crack = {
frame: 0,
};
for (let i = 0; i < frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
images.push(img);
}
function render() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(images[crack.frame], 0, 0);
}
images[0].onload = render;
let ctx = gsap.context(() => {
let tl = gsap.timeline({
scrollTrigger: {
trigger: ".wrapper",
scrub: true,
pin: true,
// markers: true,
start: "top top",
end: "+=200%",
onEnter: () => {
gsap.fromTo(".bg", { autoAlpha: 1 }, { autoAlpha: 0, duration: 1, delay: 0.2 });
},
onLeave: () => {
router.push("/projects");
},
},
});
//Here's the tween that animates the sequence
tl.to(
crack,
{
frame: frameCount - 1,
snap: "frame",
ease: "none",
duration: 9,
onUpdate: render,
onComplete: () => setHeaderAndMenuHidden(false),
},
"crack"
);
//This tween fades the canvas to reveal what's underneath
tl.to(canvas, { opacity: 0, duration: 1, onReverseComplete: () => setHeaderAndMenuHidden(true) }, ">");
// Here's the tweens fading the images
projectImages.forEach((image, i) => {
tl.fromTo(
`.image-${i}`,
{ autoAlpha: 0, scale: 0.3 },
{
autoAlpha: 1,
scale: 1.25,
duration: 9,
},
">"
);
tl.to(`.image-${i}`, { autoAlpha: 0, duration: 1 }, ">");
});
tl.to(".scrollDown", { autoAlpha: 0, duration: 1 }, "crack+=1");
}, animation);
return () => ctx.revert(); // cleanup
}, [canvasRef]);
return (
<div ref={animation}>
<div className={`wrapper ${styles.wrapper}`}>
<div>
<NextImage
className={`scrollDown ${styles.scrollDown}`}
src="/scrollDown.svg"
width={108}
height={99}
alt=""
/>
<canvas ref={canvasRef} className={`canvas ${styles.canvas}`} />
</div>
<div className={`bg ${styles.bg}`}></div>
{projectImages.map((image, i) => (
<NextImage
key={image.file.url}
className={`${styles.image} image-${i}`}
src={image.file.url}
width={image.file.width}
height={image.file.height}
alt=""
/>
))}
</div>
</div>
);
};
export default AnimatedSequence;