109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
import { gsap } from "gsap";
|
|
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
|
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
|
export interface TimelinePhoneViewItem {
|
|
trigger: string;
|
|
content: React.ReactNode;
|
|
imageOne?: string;
|
|
videoOne?: string;
|
|
imageAltOne?: string;
|
|
videoAriaLabelOne?: string;
|
|
imageTwo?: string;
|
|
videoTwo?: string;
|
|
imageAltTwo?: string;
|
|
videoAriaLabelTwo?: string;
|
|
}
|
|
|
|
const getImageAnimationConfig = (itemIndex: number, imageIndex: number) => {
|
|
const isFirstImage = imageIndex === 0;
|
|
const isOddItem = itemIndex % 2 === 1;
|
|
|
|
if (isFirstImage) {
|
|
return {
|
|
from: { xPercent: -200, rotation: -45 },
|
|
to: { rotation: isOddItem ? 10 : -10 },
|
|
};
|
|
} else {
|
|
return {
|
|
from: { xPercent: 200, rotation: 45 },
|
|
to: { rotation: isOddItem ? -10 : 10 },
|
|
};
|
|
}
|
|
};
|
|
|
|
export const usePhoneAnimations = (items: TimelinePhoneViewItem[]) => {
|
|
const imageRefs = useRef<(HTMLDivElement | null)[]>([]);
|
|
const mobileImageRefs = useRef<(HTMLDivElement | null)[]>([]);
|
|
|
|
useEffect(() => {
|
|
const mm = gsap.matchMedia();
|
|
|
|
const animatePhones = (isMobile: boolean) => {
|
|
items.forEach((item, itemIndex) => {
|
|
const images = [item.imageOne || item.videoOne, item.imageTwo || item.videoTwo];
|
|
|
|
images.forEach((_, imageIndex) => {
|
|
const refIndex = itemIndex * 2 + imageIndex;
|
|
const element = isMobile
|
|
? mobileImageRefs.current[refIndex]
|
|
: imageRefs.current[refIndex];
|
|
|
|
if (element) {
|
|
const isFirstImage = imageIndex === 0;
|
|
|
|
const fromConfig = isMobile
|
|
? {
|
|
xPercent: isFirstImage ? -150 : 150,
|
|
rotation: isFirstImage ? -25 : 25,
|
|
}
|
|
: getImageAnimationConfig(itemIndex, imageIndex).from;
|
|
|
|
const toConfig = isMobile
|
|
? {
|
|
xPercent: 0,
|
|
rotation: 0,
|
|
duration: 1,
|
|
scrollTrigger: {
|
|
trigger: element,
|
|
start: "top 90%",
|
|
end: "top 50%",
|
|
scrub: 1,
|
|
},
|
|
}
|
|
: {
|
|
xPercent: 0,
|
|
rotation: getImageAnimationConfig(itemIndex, imageIndex).to
|
|
.rotation,
|
|
scrollTrigger: {
|
|
trigger: `.${item.trigger}`,
|
|
start: "top bottom",
|
|
end: "top top",
|
|
scrub: 1,
|
|
},
|
|
};
|
|
|
|
gsap.fromTo(element, fromConfig, toConfig);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
mm.add("(max-width: 767px)", () => animatePhones(true));
|
|
mm.add("(min-width: 768px)", () => animatePhones(false));
|
|
|
|
return () => {
|
|
mm.revert();
|
|
imageRefs.current = [];
|
|
mobileImageRefs.current = [];
|
|
};
|
|
}, [items]);
|
|
|
|
return {
|
|
imageRefs,
|
|
mobileImageRefs,
|
|
};
|
|
};
|