videos upd
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 427 KiB After Width: | Height: | Size: 427 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -12,7 +12,7 @@ export default function MainLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Header />
|
||||
<main className="flex-1 md:max-lg:pt-[120px] pt-[100px] md:max-lg:px-4 lg:px-[1.389vw] px-[10px] overflow-clip relative overflow-x-hidden">
|
||||
<main className="flex-1 md:max-lg:pt-[120px] pt-[100px] md:max-lg:px-4 lg:px-[1.389vw] px-[10px] overflow-clip relative">
|
||||
{children}
|
||||
<Feedback />
|
||||
</main>
|
||||
|
||||
@@ -15,7 +15,6 @@ import { queryCompaniesOptions } from "@/queries/getCompanies";
|
||||
import { queryCompaniesCountOptions } from "@/queries/getCompaniesCount";
|
||||
import NewStreaming from "@/components/pages/MainPage/NewStreaming";
|
||||
import NewMap from "@/components/pages/MainPage/Map/NewMap";
|
||||
import WebMain from "@/components/pages/WebPage/WebMain";
|
||||
|
||||
export default async function HomePage() {
|
||||
const queryClient = new QueryClient();
|
||||
@@ -55,20 +54,19 @@ export default async function HomePage() {
|
||||
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
{/* <Motivation />
|
||||
<Motivation />
|
||||
<Integrations />
|
||||
<Presentation />
|
||||
<Statistics />
|
||||
<NewMap />
|
||||
<Projects /> */}
|
||||
<Projects />
|
||||
{/* <Map /> */}
|
||||
{/* <Streaming /> */}
|
||||
{/* <Calculator /> */}
|
||||
{/* <NewStreaming />
|
||||
<Reviews /> */}
|
||||
<NewStreaming />
|
||||
<Reviews />
|
||||
{/* <Awards /> */}
|
||||
{/* <Clients /> */}
|
||||
<WebMain />
|
||||
<Clients />
|
||||
</HydrationBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,49 +1,79 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
"use client";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import SearchIcon from "@/icons/SearchIcon";
|
||||
import GenPlanSearchIcon from "@/components/icons/GenPlanSearchIcon";
|
||||
import TreeIcon from "@/components/icons/TreeIcon";
|
||||
import FavoriteIcon from "@/components/icons/FavoriteIcon";
|
||||
import WebHeroControlBtn from "../PageComponents/WebHeroControlBtn";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import ThreeDTourIcon from "@/components/icons/3DTourIcon";
|
||||
import InfoIcon from "@/components/icons/InfoIcon";
|
||||
|
||||
const VIDEO_COUNT = 6;
|
||||
type VIDEO_EVENT = React.SyntheticEvent<HTMLVideoElement>;
|
||||
|
||||
export default function WebHero() {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const [currentVideo, setCurrentVideo] = useState<number>(0);
|
||||
const [activeVideoElement, setActiveVideoElement] = useState<0 | 1>(0);
|
||||
const [playbackProgress, setPlaybackProgress] = useState<number>(0);
|
||||
const [selectedVideo, setSelectedVideo] = useState<number | null>(null);
|
||||
const [currentVideo, setCurrentVideo] = useState<number>(0); // 0 | 1 | 2 | 3 | 4 | 5
|
||||
const videoElements = useRef<(HTMLVideoElement | null)[]>([null, null]);
|
||||
const isInitialLoad = useRef(true);
|
||||
|
||||
const updateProgress = useCallback(() => {
|
||||
if (!videoRef.current) return;
|
||||
const { currentTime, duration } = videoRef.current;
|
||||
const progressPercent = duration ? (currentTime / duration) * 100 : 0;
|
||||
function handleTimeUpdate(e: VIDEO_EVENT) {
|
||||
if (e.currentTarget !== videoElements.current[activeVideoElement]) return;
|
||||
|
||||
setPlaybackProgress(progressPercent);
|
||||
}, [currentVideo]);
|
||||
const { currentTime, duration } = e.currentTarget;
|
||||
const progressPercentage = duration ? (currentTime / duration) * 100 : 0;
|
||||
setPlaybackProgress(progressPercentage);
|
||||
}
|
||||
|
||||
const handleTimeUpdate = useCallback(() => {
|
||||
updateProgress();
|
||||
}, [updateProgress]);
|
||||
|
||||
useEffect(() => {
|
||||
setPlaybackProgress(0);
|
||||
}, [currentVideo]);
|
||||
|
||||
const onVideoEnd = () => {
|
||||
setCurrentVideo((cur) => (cur + 1) % 6);
|
||||
};
|
||||
function handleVideoEnd(e: VIDEO_EVENT) {
|
||||
if (e.currentTarget !== videoElements.current[activeVideoElement]) return;
|
||||
setCurrentVideo((cur) => (cur + 1) % VIDEO_COUNT);
|
||||
}
|
||||
|
||||
function onControlButtonClick(video: number) {
|
||||
if (selectedVideo === video) setSelectedVideo(null);
|
||||
if (currentVideo === video) return;
|
||||
else {
|
||||
setCurrentVideo(video);
|
||||
setSelectedVideo(video);
|
||||
setPlaybackProgress(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Как только произошло переключение на новое видео (изменился currentVideo)
|
||||
useEffect(() => {
|
||||
// indexOfVideoToPlay = 0 || 1, используется для определения индекса видео, которое нужно загрузить. При первой загрузке index = 0
|
||||
const indexOfVideoToPlay = isInitialLoad.current
|
||||
? 0
|
||||
: ((1 - activeVideoElement) as 0 | 1);
|
||||
isInitialLoad.current = false;
|
||||
|
||||
// Получаем видео, которое будет проиграно
|
||||
const videoToPlay = videoElements.current[indexOfVideoToPlay];
|
||||
if (!videoToPlay) return;
|
||||
|
||||
const handleCanPlay = () => {
|
||||
// Как только можем проиграть текущее видео, останавливаем то, которое было до него
|
||||
const currentlyPlayingVideo =
|
||||
videoElements.current[1 - indexOfVideoToPlay];
|
||||
currentlyPlayingVideo?.pause();
|
||||
|
||||
// Запускаем текущее видео
|
||||
videoToPlay.currentTime = 0;
|
||||
videoToPlay.play().catch(() => {});
|
||||
setActiveVideoElement(indexOfVideoToPlay);
|
||||
};
|
||||
|
||||
videoToPlay.src = `/videos/pages/web/hero_${currentVideo}.mp4`;
|
||||
videoToPlay.addEventListener("canplaythrough", handleCanPlay, {
|
||||
once: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
videoToPlay.removeEventListener("canplaythrough", handleCanPlay);
|
||||
};
|
||||
}, [currentVideo]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1
|
||||
@@ -59,25 +89,22 @@ export default function WebHero() {
|
||||
</h1>
|
||||
<div className="max-lg:flex-col flex relative justify-center">
|
||||
<div className="relative">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.video
|
||||
key={currentVideo}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
ref={videoRef}
|
||||
onEnded={onVideoEnd}
|
||||
{/* Два видео для избежания мерцания при смене src */}
|
||||
<WebHeroVideo
|
||||
refCallback={(el) => (videoElements.current[0] = el)}
|
||||
active={activeVideoElement === 0}
|
||||
onEnded={handleVideoEnd}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onLoadStart={() => setPlaybackProgress(0)}
|
||||
playsInline
|
||||
muted
|
||||
autoPlay
|
||||
src={`/videos/pages/web/hero_${currentVideo}.mp4`}
|
||||
className="absolute w-[50.694vw] aspect-[730/472] top-[4vw] left-[6.458vw]
|
||||
md:max-lg:w-[87.2%] max-lg:md:aspect-[585/345] md:max-lg:top-[1.5vw] md:max-lg:left-[6.458vw]
|
||||
max-md:w-[80.2%] max-md:left-[9.444vw] max-md:top-[1.444vw]"
|
||||
/>
|
||||
</AnimatePresence>
|
||||
<WebHeroVideo
|
||||
refCallback={(el) => (videoElements.current[1] = el)}
|
||||
active={activeVideoElement === 1}
|
||||
onEnded={handleVideoEnd}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onLoadStart={() => setPlaybackProgress(0)}
|
||||
/>
|
||||
|
||||
<img
|
||||
draggable={false}
|
||||
className="w-[63.75vw] aspect-[918/553] z-10 mt-[3.125vw] relative max-lg:w-full max-lg:mt-0"
|
||||
@@ -91,16 +118,16 @@ export default function WebHero() {
|
||||
max-md:grid max-md:grid-cols-3 max-md:grid-rows-[30vw_30vw] max-md:gap-[2.222vw] max-md:mt-[4.444vw] z-10"
|
||||
>
|
||||
<WebHeroControlBtn
|
||||
title={"Search"}
|
||||
icon={<SearchIcon />}
|
||||
title={"Search on the Master Plan"}
|
||||
icon={<GenPlanSearchIcon />}
|
||||
className="top-[14.722vw] left-0"
|
||||
active={currentVideo === 0}
|
||||
callback={() => onControlButtonClick(0)}
|
||||
progress={currentVideo === 0 ? playbackProgress : 0}
|
||||
/>
|
||||
<WebHeroControlBtn
|
||||
title={"Search on the Master Plan"}
|
||||
icon={<GenPlanSearchIcon />}
|
||||
title={"Search"}
|
||||
icon={<SearchIcon />}
|
||||
className="top-[20.486vw] left-[8.194vw]"
|
||||
active={currentVideo === 1}
|
||||
callback={() => onControlButtonClick(1)}
|
||||
@@ -145,3 +172,32 @@ export default function WebHero() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface WebHeroVideoProps
|
||||
extends React.VideoHTMLAttributes<HTMLVideoElement> {
|
||||
refCallback: (el: HTMLVideoElement) => void;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
function WebHeroVideo({ refCallback, active, ...props }: WebHeroVideoProps) {
|
||||
return (
|
||||
<video
|
||||
ref={(el) => {
|
||||
if (el) refCallback(el);
|
||||
}}
|
||||
onEnded={props.onEnded}
|
||||
onTimeUpdate={props.onTimeUpdate}
|
||||
onLoadStart={props.onLoadStart}
|
||||
playsInline
|
||||
muted
|
||||
autoPlay
|
||||
style={{
|
||||
opacity: active ? 1 : 0,
|
||||
pointerEvents: active ? "auto" : "none",
|
||||
}}
|
||||
className="absolute w-[50.694vw] aspect-[730/472] top-[4vw] left-[6.458vw]
|
||||
md:max-lg:w-[87.2%] max-lg:md:aspect-[585/345] md:max-lg:top-[1.5vw] md:max-lg:left-[6.458vw]
|
||||
max-md:w-[80.2%] max-md:left-[9.444vw] max-md:top-[1.444vw] transition-opacity duration-300"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user