💫 @Fronchef offers you the best custom made websites according to your needs
Check Out Amazing offer and events for you
Here is a installation guide to help you
npm install gsap
npm install @gsap/react
npm install react react-dom
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
blue: "#2997FF",
gray: {
DEFAULT: "#86868b",
100: "#94928d",
200: "#afafaf",
300: "#42424570",
},
zinc: "#101010",
},
},
},
plugins: [],
};
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
color: white;
width: 100dvw;
overflow-x: hidden;
height: 100%;
background: #000;
border-color: #3b3b3b;
user-select: none;
}
canvas {
touch-action: none;
}
.scrim-max-width {
margin-inline-start: auto;
margin-inline-end: auto;
position: relative;
max-width: 1120px;
}
@layer utilities {
.flex-center {
@apply flex items-center justify-center;
}
.nav-height {
@apply h-[calc(100vh-60px)];
}
.btn {
@apply px-5 py-2 rounded-3xl bg-blue my-5 hover:bg-transparent border border-transparent hover:border hover:text-blue hover:border-blue;
}
.color-container {
@apply flex items-center justify-center px-4 py-4 rounded-full bg-gray-300 backdrop-blur;
}
.size-btn-container {
@apply flex items-center justify-center p-1 rounded-full bg-gray-300 backdrop-blur ml-3 gap-1;
}
.size-btn {
@apply w-10 h-10 text-sm flex justify-center items-center bg-white text-black rounded-full transition-all;
}
.common-padding {
@apply py-10 sm:px-10 px-5;
}
.section-heading {
@apply text-gray md:text-5xl text-3xl lg:mb-0 mb-2 font-medium;
}
.feature-video {
@apply w-full h-full object-cover object-center scale-150 opacity-0;
}
.feature-video-container {
@apply w-full flex flex-col md:flex-row gap-5 items-center;
}
.link {
@apply text-blue hover:underline cursor-pointer flex items-center text-lg;
}
.control-btn {
@apply ml-4 p-4 rounded-full bg-gray-300 backdrop-blur flex-center;
}
.hero-title {
@apply text-center font-semibold text-3xl text-gray-100 opacity-0 max-md:mb-10;
}
.hiw-title {
@apply text-4xl md:text-7xl font-semibold text-center;
}
.hiw-subtitle {
@apply text-gray font-semibold text-xl md:text-2xl py-10 text-center;
}
.hiw-video {
@apply absolute w-[95%] h-[90%] rounded-[56px] overflow-hidden;
}
.hiw-text-container {
@apply flex md:flex-row flex-col justify-between items-start gap-24;
}
.hiw-text {
@apply text-gray text-xl font-normal md:font-semibold;
}
.hiw-bigtext {
@apply text-white text-3xl md:text-5xl font-normal md:font-semibold my-2;
}
.video-carousel_container {
@apply relative sm:w-[70vw] w-[88vw] md:h-[70vh] sm:h-[50vh] h-[35vh];
}
.g_fadeIn {
@apply opacity-0 translate-y-[100px];
}
}
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/all";
import { useEffect, useRef, useState } from "react";
import { HightLightsSlides } from "../constant/index.js";
import { pauseImg, playImg, replayImg } from "../constant/index.js";
gsap.registerPlugin(ScrollTrigger);
const VideoCarousel = () => {
const videoRef = useRef([]);
const videoSpanRef = useRef([]);
const videoDivRef = useRef([]);
// video and indicator
const [video, setVideo] = useState({
isEnd: false,
startPlay: false,
videoId: 0,
isLastVideo: false,
isPlaying: false,
});
const [loadedData, setLoadedData] = useState([]);
const { isEnd, isLastVideo, startPlay, videoId, isPlaying } = video;
useGSAP(() => {
// slider animation to move the video out of the screen and bring the next video in
gsap.to("#slider", {
transform: `translateX(${-100 * videoId}%)`,
duration: 2,
ease: "power2.inOut", // show visualizer https://gsap.com/docs/v3/Eases
});
// video animation to play the video when it is in the view
gsap.to("#video", {
scrollTrigger: {
trigger: "#video",
toggleActions: "restart none none none",
},
onComplete: () => {
setVideo((pre) => ({
...pre,
startPlay: true,
isPlaying: true,
}));
},
});
}, [isEnd, videoId]);
useEffect(() => {
let currentProgress = 0;
let span = videoSpanRef.current;
if (span[videoId]) {
// animation to move the indicator
let anim = gsap.to(span[videoId], {
onUpdate: () => {
// get the progress of the video
const progress = Math.ceil(anim.progress() * 100);
if (progress !== currentProgress) {
currentProgress = progress;
// set the width of the progress bar
gsap.to(videoDivRef.current[videoId], {
width:
window.innerWidth < 760
? "10vw" // mobile
: window.innerWidth < 1200
? "10vw" // tablet
: "4vw", // laptop
});
// set the background color of the progress bar
gsap.to(span[videoId], {
width: `${currentProgress}%`,
backgroundColor: "white",
});
}
},
// when the video is ended, replace the progress bar with the indicator and change the background color
onComplete: () => {
if (isPlaying) {
gsap.to(videoDivRef.current[videoId], {
width: "12px",
});
gsap.to(span[videoId], {
backgroundColor: "#afafaf",
});
}
},
});
if (videoId === 0) {
anim.restart();
}
// update the progress bar
const animUpdate = () => {
anim.progress(
videoRef.current[videoId].currentTime /
HightLightsSlides[videoId].videoDuration
);
};
if (isPlaying) {
// ticker to update the progress bar
gsap.ticker.add(animUpdate);
} else {
// remove the ticker when the video is paused (progress bar is stopped)
gsap.ticker.remove(animUpdate);
}
}
}, [videoId, startPlay, isPlaying]);
useEffect(() => {
if (loadedData.length > 3) {
if (!isPlaying) {
videoRef.current[videoId].pause();
} else {
startPlay && videoRef.current[videoId].play();
}
}
}, [startPlay, videoId, isPlaying, loadedData]);
// vd id is the id for every video until id becomes number 3
const handleProcess = (type, i) => {
switch (type) {
case "video-end":
setVideo((pre) => ({ ...pre, isEnd: true, videoId: i + 1 }));
break;
case "video-last":
setVideo((pre) => ({ ...pre, isLastVideo: true }));
break;
case "video-reset":
setVideo((pre) => ({ ...pre, videoId: 0, isLastVideo: false }));
break;
case "pause":
setVideo((pre) => ({ ...pre, isPlaying: !pre.isPlaying }));
break;
case "play":
setVideo((pre) => ({ ...pre, isPlaying: !pre.isPlaying }));
break;
default:
return video;
}
};
const handleLoadedMetaData = (i, e) => setLoadedData((pre) => [...pre, e]);
return (
<>
<div className="flex items-center">
{HightLightsSlides.map((list, i) => (
<div key={list.id} id="slider" className="sm:pr-20 pr-10">
<div className="video-carousel_container">
<div className="w-full h-full flex-center rounded-3xl overflow-hidden bg-black">
<video
id="video"
playsInline={true}
className={`${
list.id === 2 && "translate-x-44"
} pointer-events-none`}
preload="auto"
muted
ref={(el) => (videoRef.current[i] = el)}
onEnded={() =>
i !== 3
? handleProcess("video-end", i)
: handleProcess("video-last")
}
onPlay={() =>
setVideo((pre) => ({ ...pre, isPlaying: true }))
}
onLoadedMetadata={(e) => handleLoadedMetaData(i, e)}
>
<source src={list.video} type="video/mp4" />
</video>
</div>
<div className="absolute top-12 left-[5%] z-10">
{list.textLists.map((text, i) => (
<p key={i} className="md:text-2xl text-xl font-medium">
{text}
</p>
))}
</div>
</div>
</div>
))}
</div>
<div className="relative flex-center mt-10">
<div className="flex-center py-5 px-7 bg-gray-300 backdrop-blur rounded-full">
{videoRef.current.map((_, i) => (
<span
key={i}
className="mx-2 w-3 h-3 bg-gray-200 rounded-full relative cursor-pointer"
ref={(el) => (videoDivRef.current[i] = el)}
>
<span
className="absolute h-full w-full rounded-full"
ref={(el) => (videoSpanRef.current[i] = el)}
/>
</span>
))}
</div>
<button className="control-btn">
<img
src={isLastVideo ? replayImg : !isPlaying ? playImg : pauseImg}
alt={isLastVideo ? "replay" : !isPlaying ? "play" : "pause"}
onClick={
isLastVideo
? () => handleProcess("video-reset")
: !isPlaying
? () => handleProcess("play")
: () => handleProcess("pause")
}
/>
</button>
</div>
</>
);
};
export default VideoCarousel;
import highlightFirstmv from "../constant/video/highlight-first.mp4"; {/* change path accordingly */}
import highlightSecmv from "../constant/video/hightlight-sec.mp4";
import highlightThirdmv from "../constant/video/hightlight-third.mp4";
import highlightFourthmv from "../constant/video/hightlight-fourth.mp4";
import replay from "../constant/images/replay.svg";
import play from "../constant/images/play.svg";
import pause from "../constant/images/pause.svg";
import logo from "../constant/images/logoo.png";
export const replayImg = replay;
export const playImg = play;
export const pauseImg = pause;
export const logoImg = logo;
export const HightLightsSlides = [
{
id: 1,
textLists: [
"Enter A17 Pro.",
"Game‑changing chip.",
"Groundbreaking performance.",
],
video: highlightFirstmv,
videoDuration: 4,
},
{
id: 2,
textLists: ["Titanium.", "So strong. So light. So Pro."],
video: highlightSecmv,
videoDuration: 5,
},
{
id: 3,
textLists: [
"iPhone 15 Pro Max has the",
"longest optical zoom in",
"iPhone ever. Far out.",
],
video: highlightThirdmv,
videoDuration: 2,
},
{
id: 4,
textLists: ["All-new Action button.", "What will yours do?."],
video: highlightFourthmv,
videoDuration: 3.63,
},
];