Hi,
(stack : Next JS)
i'm close to achieve an effect but am stuck!
Here is the result of where i am for now : https://youtu.be/tXUPHLRPiDA
As you can see, I'd like to make a transition on clicking to a project thumbnail.
Problem : I don't want the container to scaleY up, but i want to keep the infos/image inside to a normal ratio. I tied to animate height, instead of scaleY, but then I have to also animation the scrollTo, and it was a bit messy...
Here is the ProjectThumb component (I do have a project list with several ProjectThumb) :
import React, { useRef, useState } from "react";
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
import {
lineAnimation,
projectContainerAnimation,
scrollAnimation,
subtitleAnimation,
titleAnimation,
} from "../animations/animations";
import { ScrollToPlugin } from "gsap/ScrollToPlugin";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import Image from "next/image";
import { useRouter } from "next/navigation";
gsap.registerPlugin(ScrollTrigger);
gsap.registerPlugin(ScrollToPlugin);
const ProjectThumb = ({
project,
setClicked,
clicked,
}: {
project: any;
setClicked: React.Dispatch<React.SetStateAction<number | null>>;
clicked: number | null;
}) => {
const lineRef = useRef<HTMLDivElement>(null);
const thumbContainerRef = useRef<HTMLDivElement>(null);
const titleRef = useRef(null);
const subTitleRef = useRef(null);
const imageRef = useRef(null);
const router = useRouter();
const [timeline, setTimeline] = useState<any>();
const [timeline2, setTimeline2] = useState<any>();
// set line animation timeline up
useGSAP(() => {
if (lineRef.current) {
const tl2 = gsap.timeline({
scrollTrigger: {
trigger: lineRef.current,
start: "top 85%",
toggleActions: "play end resume reverse",
},
});
tl2.add(lineAnimation(lineRef));
setTimeline2(tl2);
}
}, [lineRef]);
// Set project elements timeline up
useGSAP(() => {
const tl = gsap.timeline();
setTimeline(tl);
// show off all project container but the one clicked
if (clicked && clicked !== project.id && thumbContainerRef.current) {
timeline.to(
thumbContainerRef.current,
{
opacity: 0,
duration: 0.5,
},
`<${Math.abs((clicked - project.id) * 0.5) / 3}`
);
}
}, [clicked, thumbContainerRef]);
const handlePlayAnimation = () => {
if (
thumbContainerRef.current &&
subTitleRef.current &&
titleRef.current &&
timeline2 &&
timeline
) {
setClicked(project.id);
timeline2.clear();
timeline2.to(lineRef.current, {
scaleX: 0,
duration: 0.5,
});
const offset =
window.innerHeight * 0.5 -
thumbContainerRef.current.getBoundingClientRect().height / 2 -
32;
const thumbContainerScale =
window.innerHeight /
thumbContainerRef.current.getBoundingClientRect().height;
timeline
.add(scrollAnimation(thumbContainerRef, offset))
.add(titleAnimation(titleRef), "-=0.4")
.add(subtitleAnimation(subTitleRef), "-=0.25")
.add(
projectContainerAnimation(thumbContainerRef, thumbContainerScale),
"<=0.3"
);
// .then(() => router.push(`/projects/${project.id}`));
}
};
return (
<div
className={`project_container_${project.id} overflow-hidden min-h-[25vh] relative`}
ref={thumbContainerRef}
>
<div
ref={lineRef}
className="projectLine scale-x-0 h-2 bg-slate-500 opacity-100"
></div>
<div
onClick={handlePlayAnimation}
className="project_infos button absolute w-full h-full z-10 cursor-pointer"
>
<div></div>
<div
className="project_title flex gap-4 p-8 items-end text-white"
key={project.id}
>
<h3
ref={titleRef}
className="project_title text-2xl w-full text-slate-800"
>
{project.title}
</h3>
<p
ref={subTitleRef}
className="project_subtitle w-full text-slate-800"
>
{project.subtitle}
</p>
</div>
</div>
<Image
ref={imageRef}
src={project.images.thumbnail}
width={1920}
height={1080}
alt={project.title}
className="h-full object-cover aspect-square opacity-30 absolute"
/>
</div>
);
};
export default ProjectThumb;
And here are the animations called by the previous component :
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { ScrollToPlugin } from "gsap/ScrollToPlugin";
gsap.registerPlugin(ScrollTrigger);
gsap.registerPlugin(ScrollToPlugin);
export const lineAnimation = (ref: any) => {
return gsap.to(ref.current, {
scaleX: 1,
transformOrigin: "left",
duration: 0.75,
});
};
export const thumbContainerAnimation = (ref: any) => {
return gsap.to(ref.current, {
height: "100vh",
transformOrigin: "top center",
duration: 1,
ease: "expo.in",
});
};
export const scrollAnimation = (ref: any, offset: number) => {
return gsap.to(window, {
duration: 0.75,
scrollTo: { y: ref.current, offsetY: offset },
ease: "expo.inOut",
});
};
export const titleAnimation = (ref: any) => {
return gsap.to(ref.current, {
duration: 0.4,
y: -50,
opacity: 0,
ease: "expo.in",
});
};
export const subtitleAnimation = (ref: any) => {
return gsap.to(ref.current, {
duration: 0.35,
y: -50,
opacity: 0,
ease: "expo.in",
});
};
export const projectContainerAnimation = (ref: any, scale: number) => {
return gsap.to(ref.current, {
scaleY: scale,
transformOrigin: "center",
duration: 1.2,
ease: "power4.inOut",
});
};