This commit is contained in:
2024-03-26 18:19:52 +05:00
parent 41b49c43ac
commit 1dbb96b09d
3 changed files with 296 additions and 16 deletions
+2
View File
@@ -36,6 +36,7 @@ import LabelCard from "@components/CardYear";
import ProjectsSection from "@components/ProjectsSection";
import { motion } from "framer-motion";
import { Video } from "../types/Video";
import Reviews from "@components/Reviews";
// const VIDEOS = [
// "/videos/features/virtual_tour.mp4",
@@ -680,6 +681,7 @@ export default function App() {
</div>
<Winners />
<Reviews />
<div className="flex flex-col 2xl:gap-16 xl:gap-10 gap-8 2xl:mb-[200px] sm:mb-[120px] mb-20">
<Heading2>Проекты</Heading2>
+23 -16
View File
@@ -6,8 +6,7 @@ import { Transition } from "react-transition-group";
import { motion } from "framer-motion";
import { Video } from "../types/Video";
const CARD_COUNT = 3;
const videos: Video[] = [
const VIDEOS: Video[] = [
{
id: "1",
value: "/videos/histories/1.mp4",
@@ -52,18 +51,19 @@ const Histories = () => {
});
const [selectedVideo, setSelectedVideo] = useState<number>(0);
const [_document, setDocument] = useState<Document>();
const [isVideoFinish, setIsVideoFinish] = useState(false);
function handleOnLeftMove(): void {
if (selectedVideo > 0) {
setSelectedVideo((prev) => prev - 1);
setOffset((prev) => prev + 1);
// setOffset((prev) => prev + 1);
}
}
function handleOnRightMove(): void {
if (selectedVideo < 2) {
setSelectedVideo((prev) => prev + 1);
setOffset((prev) => prev - 1);
// setOffset((prev) => prev - 1);
}
}
@@ -71,7 +71,7 @@ const Histories = () => {
setDocument(document);
setVideoRefs((elRefs) =>
Array.from({ length: CARD_COUNT }, (_, index) => index).map(
Array.from({ length: VIDEOS.length }, (_, index) => index).map(
(_, i) => elRefs[i] || createRef()
)
);
@@ -121,7 +121,7 @@ const Histories = () => {
currentVideoRef.current.currentTime;
setVideoProgress(progress);
if (progress === 100) {
if (97 <= progress) {
clearInterval(interval);
}
}, 1000);
@@ -130,12 +130,19 @@ const Histories = () => {
}, [selectedVideo, videoRefs]);
useEffect(() => {
if (100 - 3 < videoProgress) {
if (offset <= -1 * videoRefs.length + 1) {
setOffset(0);
if (100 - 5 < videoProgress) {
if (selectedVideo >= videoRefs.length - 1) {
// setOffset(0);
console.log("first");
setSelectedVideo(0);
setVideoProgress(0);
const currentVideoRef = videoRefs[selectedVideo];
if (currentVideoRef && currentVideoRef.current) {
currentVideoRef.current.currentTime = 0;
}
} else {
setOffset((prev) => prev - 1);
console.log("second");
// setOffset((prev) => prev - 1);
setSelectedVideo((prev) => prev + 1);
}
}
@@ -157,7 +164,7 @@ const Histories = () => {
<p>graff.estate</p>
</h2>
{videos.map((item, index) => (
{VIDEOS.map((item, index) => (
<Transition
key={index}
in={Boolean(index === selectedVideo)}
@@ -202,7 +209,7 @@ const Histories = () => {
transform: `translateX(${-selectedVideo * cardWidth}px)`,
}}
>
{videos.map((video, index) => (
{VIDEOS.map((video, index) => (
<div
key={video.id}
className={`relative 2xl:w-[404px] xl:w-[363px] sm:w-[354px] w-[calc(100vw-32px)] 2xl:h-[720px] xl:h-[647px] sm:h-[632px] h-[546px] transition-opacity duration-300 ${
@@ -223,7 +230,7 @@ const Histories = () => {
<div
className={`absolute bottom-0 w-full h-1 bg-[#52587A] sm:block hidden transition-opacity duration-500 ${
offset === -1 * index ? "opacity-100" : "opacity-0"
selectedVideo === index ? "opacity-100" : "opacity-0"
}`}
>
<div
@@ -238,10 +245,10 @@ const Histories = () => {
</div>
<div className=" p-6 block sm:hidden mb-6">
<h2 className="font-semibold text-sm leading-[18px] pb-2">
{videos[selectedVideo].title}
{VIDEOS[selectedVideo].title}
</h2>
<p className="text-[12px] leading-[18px]">
{videos[selectedVideo].desc}
{VIDEOS[selectedVideo].desc}
</p>
</div>
<div className="mx-auto flex gap-2 h-2 sm:hidden">
@@ -251,7 +258,7 @@ const Histories = () => {
style={{ width: `${videoProgress}%` }}
></div>
</div>
{videos.map((video, index) => (
{VIDEOS.map((video, index) => (
<div
key={video.id}
className="h-full w-2 rounded-full transition-all duration-300"
+271
View File
@@ -0,0 +1,271 @@
import { createRef, useEffect, useLayoutEffect, useRef, useState } from "react";
import { useSwipeable } from "react-swipeable";
import { motion } from "framer-motion";
import { Transition } from "react-transition-group";
import ArrowLeftIcon from "./icons/ArrowLeftIcon";
import ArrowRightIcon from "./icons/ArrowRightIcon";
import { Video } from "../types/Video";
const VIDEOS: Video[] = [
{
id: "1",
value: "/videos/histories/1.mp4",
title: "",
desc: "Интерактивный комплекс GRAFF.estate для ЖК Upside Towers, Москва",
poster: "",
},
{
id: "2",
value: "/videos/histories/2.mp4",
title: "",
desc: "Graff.estate на выставке 100+ TechnoBuild",
poster: "",
},
{
id: "3",
value: "/videos/histories/3.mp4",
title: "",
desc: "Интерактивная инсталляция graff.estate для ЖК DNS City в г.Владивосток",
poster: "",
},
];
const Reviews = () => {
const [isViewportEntered, setIsViewportEntered] = useState<boolean>(false);
function handleViewportEnter() {
if (isViewportEntered) return;
setIsViewportEntered(true);
}
const [offset, setOffset] = useState(0);
const [videoRefs, setVideoRefs] = useState<
React.RefObject<HTMLVideoElement>[]
>([]);
const [videoProgress, setVideoProgress] = useState(0);
const [cardWidth, setCardWidth] = useState(0);
const handlers = useSwipeable({
onSwipedLeft: handleOnRightMove,
onSwipedRight: handleOnLeftMove,
trackMouse: true,
});
const [selectedVideo, setSelectedVideo] = useState<number>(0);
const [_document, setDocument] = useState<Document>();
function handleOnLeftMove(): void {
if (selectedVideo > 0) {
setSelectedVideo((prev) => prev - 1);
setOffset((prev) => prev + 1);
}
}
function handleOnRightMove(): void {
if (selectedVideo < 2) {
setSelectedVideo((prev) => prev + 1);
setOffset((prev) => prev - 1);
}
}
useEffect(() => {
setDocument(document);
setVideoRefs((elRefs) =>
Array.from({ length: VIDEOS.length }, (_, index) => index).map(
(_, i) => elRefs[i] || createRef()
)
);
}, []);
useEffect(() => {
if (!_document) return;
const clientWidth = _document.children[0].clientWidth;
if (clientWidth >= 1600) {
setCardWidth(420);
} else if (clientWidth >= 1280) {
setCardWidth(379);
} else if (clientWidth >= 640) {
setCardWidth(370);
} else {
setCardWidth(clientWidth - 16);
}
}, [_document]);
useEffect(() => {
videoRefs.forEach((video, index) => {
if (!video.current) return;
if (index === selectedVideo) {
video.current.play();
} else {
video.current.pause();
}
});
}, [videoRefs, selectedVideo, isViewportEntered]);
useEffect(() => {
const currentVideoRef = videoRefs[selectedVideo];
if (!currentVideoRef || !currentVideoRef.current) return;
const progress = Math.round(
(100 / currentVideoRef.current.duration) *
currentVideoRef.current.currentTime
);
setVideoProgress(progress);
const interval = setInterval(() => {
if (!currentVideoRef || !currentVideoRef.current) return;
const progress =
(100 / currentVideoRef.current.duration) *
currentVideoRef.current.currentTime;
setVideoProgress(progress);
if (progress === 100) {
clearInterval(interval);
}
}, 1000);
return () => clearInterval(interval);
}, [selectedVideo, videoRefs]);
useEffect(() => {
if (100 - 3 < videoProgress) {
if (offset <= -1 * videoRefs.length + 1) {
setOffset(0);
setSelectedVideo(0);
} else {
setOffset((prev) => prev - 1);
setSelectedVideo((prev) => prev + 1);
}
}
}, [videoProgress]);
return (
<motion.div
onViewportEnter={handleViewportEnter}
className="container mx-auto 2xl:max-w-screen-2xl flex flex-col justify-center 2xl:mb-[200px] sm:mb-[120px] mb-20"
>
<div className="sm:border-b border-b-[#3D425C] 2xl:h-[760px] xl:h-[687px] sm:h-[656px] h-full">
<div className="flex sm:h-full h-fit sm:flex-row flex-col sm:w-full sm:mx-0 mx-auto">
<div className=" flex flex-col justify-between 2xl:min-w-[384px] xl:min-w-[308px] sm:min-w-[263px] sm:pb-6 xl:pb-10 sm:h-full">
<div className="2xl:pr-10 xl:pr-8 sm:pr-[37px]">
<h2 className="font-medium 2xl:text-[64px] xl:text-5xl text-[40px] leading-10 sm:mb-10 mb-6 font-gilroy">
<p className="from-[#798FFF] to-[#D375FF] bg-gradient-to-r bg-clip-text text-transparent">
Отзывы
</p>
<p>клиентов</p>
</h2>
{VIDEOS.map((item, index) => (
<Transition
key={index}
in={Boolean(index === selectedVideo)}
timeout={300}
>
{(state) => (
<div
className={`absolute 2xl:w-[344px] xl:w-[276px] sm:w-[226px] w-[280px] transition-opacity duration-300 ${state}`}
>
<p className="font-semibold mb-4 2xl:text-xl xl:text-[16px] sm:block hidden">
{item.title}
</p>
<p className="font-normal 2xl:text-[16px] xl:text-[14px] sm:block hidden">
{item.desc}
</p>
</div>
)}
</Transition>
))}
</div>
<div className="gap-2 sm:flex hidden">
<button
className="p-4 border border-[#3D425C] rounded-full"
onClick={handleOnLeftMove}
>
<ArrowLeftIcon />
</button>
<button
className="p-4 border border-[#3D425C] rounded-full"
onClick={handleOnRightMove}
>
<ArrowRightIcon />
</button>
</div>
</div>
<div className="relative sm:border-l border-l-[#3D425C] sm:h-full h-[548px] w-full">
<div className="absolute overflow-x-clip h-full ">
<div {...handlers} className="absolute h-full w-full z-10" />
<div
className="flex gap-4 w-full h-full transition-all duration-300 ease-in-out 2xl:pl-10 xl:pl-8 sm:pl-3 xl:pb-10 sm:pb-[25px] "
style={{
transform: `translateX(${-selectedVideo * cardWidth}px)`,
}}
>
{VIDEOS.map((video, index) => (
<div
key={video.id}
className={`relative 2xl:w-[984px] xl:w-[789px] sm:w-[720px] w-[calc(100vw-32px)] 2xl:h-[554px] xl:h-[444px] sm:h-[404px] h-[206px] transition-opacity duration-300 ${
selectedVideo === index ? "opacity-100" : "opacity-60"
}`}
>
<video
poster={"/images/posters/integra_crm.jpg"}
// poster={video.poster}
ref={videoRefs[index]}
src={isViewportEntered ? video.value : ""}
muted
loop
playsInline
preload="metadata"
className="w-full h-full object-cover touch-none"
/>
<div
className={`absolute bottom-0 w-full h-1 bg-[#52587A] sm:block hidden transition-opacity duration-500 ${
offset === -1 * index ? "opacity-100" : "opacity-0"
}`}
>
<div
className="w-0 bg-white h-1 transition-[width] duration-500"
style={{ width: `${videoProgress}%` }}
></div>
</div>
</div>
))}
</div>
</div>
</div>
<div className=" p-6 block sm:hidden mb-6">
<h2 className="font-semibold text-sm leading-[18px] pb-2">
{VIDEOS[selectedVideo].title}
</h2>
<p className="text-[12px] leading-[18px]">
{VIDEOS[selectedVideo].desc}
</p>
</div>
<div className="mx-auto flex gap-2 h-2 sm:hidden">
<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: `${videoProgress}%` }}
></div>
</div>
{VIDEOS.map((video, index) => (
<div
key={video.id}
className="h-full w-2 rounded-full transition-all duration-300"
style={{
background: `${
selectedVideo === index ? "white" : "#a1a2a6"
}`,
}}
/>
))}
</div>
</div>
</div>
</motion.div>
);
};
export default Reviews;