Files
graff.estate-nextjs/src/components/Histories.tsx
T
2024-04-01 15:19:37 +05:00

268 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createRef, useEffect, useState } from "react";
import { useInView } from "react-intersection-observer";
import { useSwipeable } from "react-swipeable";
import { Transition } from "react-transition-group";
import { Video } from "../types/Video";
import ArrowLeftIcon from "./icons/ArrowLeftIcon";
import ArrowRightIcon from "./icons/ArrowRightIcon";
const VIDEOS: Video[] = [
{
id: "1",
value: "/videos/histories/1.mp4",
title: "Интерактивный комплекс GRAFF.estate для ЖК Upside Towers, Москва",
desc: "",
poster: "/images/posters/histories/1.jpg",
},
{
id: "2",
value: "/videos/histories/2.mp4",
title: "Graff.estate на выставке 100+ TechnoBuild",
desc: "",
poster: "/images/posters/histories/2.jpg",
},
{
id: "3",
value: "/videos/histories/3.mp4",
title:
"Интерактивная инсталляция graff.estate для ЖК DNS City в г. Владивосток",
desc: "",
poster: "/images/posters/histories/3.jpg",
},
];
function Histories() {
const [selectedVideoIndex, setSelectedVideoIndex] = useState(0);
const videoRefs = VIDEOS.map(() => createRef<HTMLVideoElement>());
const [videoWidth, setVideoWidth] = useState<number>(0);
const [videoHeigth, setVideoHeigth] = useState<number>(0);
const [videoProgesses, setVideoProgresses] = useState<number[]>([]);
const { ref, inView } = useInView();
const [isEntered, setIsEntered] = useState(false);
const handlers = useSwipeable({
onSwipedLeft: next,
onSwipedRight: prev,
trackMouse: true,
});
useEffect(() => {
if (!videoRefs[0].current?.src) return;
const width = videoRefs[0].current.clientWidth;
const height = videoRefs[0].current.clientHeight;
setVideoWidth(width);
setVideoHeigth(height);
}, [videoRefs[0]]);
function handleEnded() {
if (selectedVideoIndex === VIDEOS.length - 1) {
setSelectedVideoIndex(0);
} else {
setSelectedVideoIndex((prev) => prev + 1);
}
}
function prev() {
if (selectedVideoIndex === 0) return;
setSelectedVideoIndex((prev) => prev - 1);
}
function next() {
if (selectedVideoIndex === VIDEOS.length - 1) return;
setSelectedVideoIndex((prev) => prev + 1);
}
useEffect(() => {
const progresses = VIDEOS.map(() => 0);
setVideoProgresses(progresses);
}, []);
useEffect(() => {
const interval = setInterval(() => {
const updatedProgresses = videoRefs.map((currentVideoRef) => {
if (!currentVideoRef.current) return 0;
return (
(100 / currentVideoRef.current.duration) *
currentVideoRef.current.currentTime
);
});
setVideoProgresses(updatedProgresses);
}, 1000);
return () => clearInterval(interval);
}, [selectedVideoIndex, videoRefs]);
useEffect(() => {
if (!inView || isEntered) return;
setIsEntered(true);
}, [inView]);
useEffect(() => {
for (let index = 0; index < videoRefs.length; index++) {
if (selectedVideoIndex === index) {
videoRefs[index].current?.play();
} else {
videoRefs[index].current?.pause();
}
}
}, [isEntered, selectedVideoIndex]);
return (
<div
ref={ref}
className="container mx-auto 2xl:max-w-screen-2xl mb-[120px]"
>
<div className="flex 2xl:h-[760px] xl:h-[687px] sm:h-[500px] h-[875px] sm:border-b border-b-[#3D425C] sm:flex-row flex-col">
<div className="left 2xl:min-w-[496px] xl:min-w-[395px] sm:min-w-[354px] flex flex-col sm:pb-10 pb-6">
<h2 className="font-medium 2xl:text-[64px] xl:text-5xl text-[40px] sm:mb-10 mb-6 font-gilroy leading-10">
<p className="from-[#798FFF] to-[#D375FF] bg-gradient-to-r bg-clip-text text-transparent">
Истории
</p>
<p>graff.estate</p>
</h2>
<div className="relative bg-white sm:block hidden">
{VIDEOS.map((item, index) => (
<Transition
key={index}
in={Boolean(index === selectedVideoIndex)}
timeout={300}
>
{(state) => (
<div
className={`absolute transition-opacity duration-300 pr-10 ${state}`}
>
{item.title ? (
<p className="font-semibold mb-4 2xl:text-xl xl:text-[16px]">
{item.title}
</p>
) : (
<></>
)}
<p className="font-normal 2xl:text-[16px] xl:text-[14px]">
{item.desc}
</p>
</div>
)}
</Transition>
))}
</div>
<div className="gap-2 hidden sm:flex mt-auto">
<button
className="p-4 border border-[#3D425C] rounded-full"
onClick={prev}
>
<ArrowLeftIcon />
</button>
<button
className="p-4 border border-[#3D425C] rounded-full"
onClick={next}
>
<ArrowRightIcon />
</button>
</div>
</div>
<div
{...handlers}
className="right relative sm:border-l border-l-[#3D425C] flex gap-4 sm:pb-10 sm:pl-10 select-none"
style={{
clipPath: `rect(auto auto auto 0)`,
}}
>
{VIDEOS.map((video, index) => (
<div
key={video.id}
className="relative aspect-[9/16] bg-black transition-transform duration-300 sm:h-auto sm:min-w-0 h-[546px] min-w-[100vw]"
style={{
transform: `translateX(-${
videoWidth * selectedVideoIndex + 16 * selectedVideoIndex
}px)`,
}}
>
<video
ref={videoRefs[index]}
src={isEntered ? video.value : undefined}
poster={video.poster}
muted
playsInline
className="aspect-[9/16] object-cover w-full h-full min-w-screen"
onEnded={handleEnded}
// onLoadedData={() =>
// setTimeout(() => {
// setVideoWidth(videoRefs[0].current!.clientWidth);
// setVideoHeigth(videoRefs[0].current!.clientHeight);
// }, 200)
// }
/>
<div
className={`absolute progress-bar h-[3px] bg-[#52587A] transition-opacity duration-500 sm:block hidden ${
selectedVideoIndex === index ? "opacity-100" : "opacity-0"
}`}
style={{
width: `${videoWidth}px`,
top: `${videoHeigth - 3}px`,
}}
>
<div
className="bg-white h-full transition-[width] duration-300"
style={{
width: `${videoProgesses[index]}%`,
}}
></div>
</div>
</div>
))}
</div>
<div className="relative block sm:hidden h-[100%] bg-[#212431] p-6">
{VIDEOS.map((item, index) => (
<Transition
key={index}
in={Boolean(index === selectedVideoIndex)}
timeout={300}
>
{(state) => (
<div
className={`absolute transition-opacity duration-300 pr-10 ${state}`}
>
{item.title ? (
<p className="font-semibold mb-4 2xl:text-xl xl:text-[16px]">
{item.title}
</p>
) : (
<></>
)}
<p className="font-normal 2xl:text-[16px] xl:text-[14px]">
{item.desc}
</p>
</div>
)}
</Transition>
))}
</div>
<div className="mx-auto flex gap-2 min-h-2 sm:hidden relative mt-6">
<div className="bg-[#a1a2a6] w-10 h-full rounded-full overflow-hidden">
<div
className="bg-white w-0 h-full rounded-full transition-all duration-500"
style={{ width: `${videoProgesses[selectedVideoIndex]}%` }}
></div>
</div>
{VIDEOS.map((video, index) => (
<div
key={video.id}
className="h-full w-2 rounded-full transition-all duration-300"
style={{
background: `${
selectedVideoIndex === index ? "white" : "#a1a2a6"
}`,
}}
/>
))}
</div>
</div>
</div>
);
}
export default Histories;