CategoriesAllHeroNavigationFooterCall-to-ActionsFeature SectionsTestimonialsPricingContactGalleryStatsFAQServicesNewsletterAuthMiscellaneousButtons
All Components3D Cube InteractionBorder Logo GridCountdownFlip ButtonExpandable FAQsFlow ButtonGlow ButtonGrayscale CarouselInertia Zoom ParallaxLogo Cloud GridMobile Drawer NavigationPixel PreloaderProject GalleryRing ButtonShowcase CarouselSlide PreloaderSliding Stairs PreloaderStormtrooper FAQText Gradient OpacityThreads ButtonTilt HeadlineUltra Preloader
Code
app/page.tsx
1"use client";
2
3import { useLayoutEffect, useState } from "react";
4import { gsap } from "gsap";
5import Loader from "./components/loader";
6import Hero from "./components/hero";
7
8const Home = () => {
9 const [loaderFinished, setLoaderFinished] = useState(false);
10 const [timeline, setTimeline] = useState<TimelineMax | null>(null);
11
12 useLayoutEffect(() => {
13 const context = gsap.context(() => {
14 const tl = gsap.timeline({
15 onComplete: () => setLoaderFinished(true),
16 });
17 setTimeline(tl);
18 });
19
20 return () => context.revert();
21 }, []);
22
23 return (
24 <main>{loaderFinished ? <Hero /> : <Loader timeline={timeline} />}</main>
25 );
26};
27
28export default Home;Installation
Initialized a project and installed the necessary dependencies to use this component.
1. Initialize a new Next.js project:
npx create-next-app@latest my-app2. Install GSAP
npm install gsapAdd Loader Component
src/components/loader.tsx
1import React, { useEffect, useRef } from "react";
2import { TimelineMax } from "gsap";
3import { words } from "../constants";
4import { collapseWords, introAnimation, progressAnimation } from "../anim";
5
6interface LoaderProps {
7 timeline: TimelineMax | null;
8}
9
10const Loader: React.FC<LoaderProps> = ({ timeline }) => {
11 const loaderRef = useRef(null);
12 const progressRef = useRef(null);
13 const progressNumberRef = useRef(null);
14 const wordGroupsRef = useRef(null);
15
16 useEffect(() => {
17 if (timeline) {
18 const duration = 2; // Set the duration according to your needs
19
20 timeline
21 .add(introAnimation(wordGroupsRef))
22 .add(progressAnimation(progressRef, progressNumberRef), 0)
23 .add(collapseWords(loaderRef), `-=${duration}`);
24 }
25 }, [timeline]);
26
27 return (
28 <div className="loader__wrapper">
29 <div className="loader__progressWrapper">
30 <div className="loader__progress" ref={progressRef}></div>
31 <span className="loader__progressNumber" ref={progressNumberRef}>
32 0
33 </span>
34 </div>
35 <div className="loader" ref={loaderRef}>
36 <div className="loader__words">
37 <div className="loader__overlay"></div>
38 <div ref={wordGroupsRef} className="loader__wordsGroup">
39 {words.map((word, index) => (
40 <span key={index} className="loader__word">
41 {word}
42 </span>
43 ))}
44 </div>
45 </div>
46 </div>
47 </div>
48 );
49};
50
51export default Loader;Add Hero Component
src/components/hero.tsx
1const Hero = () => {
2 return (
3 <div className="p-4">
4 </div>
5 );
6};
7
8export default Hero;Add Animations
src/anim/anim.js
1import gsap from "gsap";
2
3export const introAnimation = (wordGroupsRef) => {
4 const tl = gsap.timeline();
5 tl.to(wordGroupsRef.current, {
6 yPercent: -80,
7 duration: 5,
8 ease: "power3.inOut",
9 });
10
11 return tl;
12};
13
14export const collapseWords = (wordGroupsRef) => {
15 const tl = gsap.timeline();
16 tl.to(wordGroupsRef.current, {
17 "clip-path": "polygon(0% 50%, 100% 50%, 100% 50%, 0% 50%)",
18 duration: 3,
19 ease: "expo.inOut",
20 });
21
22 return tl;
23};
24
25export const progressAnimation = (progressRef, progressNumberRef) => {
26 const tl = gsap.timeline();
27
28 tl.to(progressRef.current, {
29 scaleX: 1,
30 duration: 5,
31 ease: "power3.inOut",
32 })
33 .to(
34 progressNumberRef.current,
35 {
36 x: "100vw",
37 duration: 5,
38 ease: "power3.inOut",
39 },
40 "<"
41 )
42 .to(
43 progressNumberRef.current,
44 {
45 textContent: "100",
46 duration: 5,
47 roundProps: "textContent",
48 },
49 "<"
50 )
51 .to(progressNumberRef.current, {
52 y: 24,
53 autoAlpha: 0,
54 });
55
56 return tl;
57};
58
59export const animateTitle = () => {
60 const tl = gsap.timeline({
61 defaults: {
62 ease: "expo.out",
63 duration: 2,
64 },
65 });
66
67 tl.to("[data-hero-line]", {
68 scaleX: 1,
69 })
70 .fromTo(
71 "[data-title-first]",
72 {
73 x: 100,
74 autoAlpha: 0,
75 },
76 {
77 x: 0,
78 autoAlpha: 1,
79 },
80 "<15%"
81 )
82 .fromTo(
83 "[data-title-last]",
84 {
85 x: -100,
86 autoAlpha: 0,
87 },
88 {
89 x: 0,
90 autoAlpha: 1,
91 },
92 "<"
93 );
94
95 return tl;
96};
97
98export const animateImage = () => {
99 const tl = gsap.timeline({
100 defaults: {
101 ease: "expo.out",
102 duration: 1.5,
103 },
104 });
105
106 tl.to("[data-image-overlay]", {
107 scaleY: 1,
108 })
109 .from(
110 "[data-image]",
111 {
112 yPercent: 100,
113 },
114 "<"
115 )
116 .to("[data-image-overlay]", {
117 scaleY: 0,
118 transformOrigin: "top center",
119 })
120 .from(
121 "[data-image]",
122 {
123 duration: 2,
124 scale: 1.3,
125 },
126 "<"
127 );
128
129 return tl;
130};
131
132export const revealMenu = () => {
133 const tl = gsap.timeline();
134
135 tl.fromTo(
136 "[data-menu-item]",
137 {
138 autoAlpha: 0,
139 y: 32,
140 },
141 {
142 autoAlpha: 1,
143 y: 0,
144 stagger: 0.2,
145 ease: "expo.out",
146 duration: 2,
147 }
148 );
149
150 return tl;
151};Add Styles
src/globals.css
1@tailwind base;
2@tailwind components;
3@tailwind utilities;
4
5*,
6*:before,
7*:after {
8 margin: 0;
9 padding: 0;
10 box-sizing: border-box;
11}
12
13html {
14 font-size: calc(100vw / 1728 * 10);
15}
16
17body {
18 font-size: clamp(16px, 1.6rem, 1.6rem);
19 font-family: "Matter Regular", sans-serif;
20
21 font-style: normal;
22 font-variation-settings: "ital" 0, "wght" 400;
23 background-color: white;
24 color: black;
25 min-height: 100vh;
26 text-rendering: optimizeLegibility;
27 -webkit-font-smoothing: antialiased;
28 -moz-osx-font-smoothing: grayscale;
29 width: 100%;
30 letter-spacing: -0.03em;
31}
32
33[data-hidden] {
34 opacity: 0;
35}
36
37h1 {
38 font-size: 16rem;
39 font-weight: 400;
40 letter-spacing: -0.03em;
41}
42
43img {
44 display: block;
45 width: 100%;
46 max-width: 100%;
47}
48
49.loader {
50 clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
51 height: 100%;
52 width: 100%;
53 display: flex;
54 justify-content: center;
55 align-items: center;
56 flex-direction: column;
57 background-color: white;
58 overflow: hidden;
59 z-index: 2;
60}
61
62.loader__wrapper {
63 position: relative;
64 height: 100%;
65 width: 100%;
66 position: fixed;
67 inset: 0;
68 overflow: hidden;
69}
70
71.loader__words {
72 position: relative;
73 overflow: hidden;
74 height: 41.8rem;
75}
76
77.loader__overlay {
78 position: absolute;
79 inset: 0;
80 height: 100%;
81 z-index: 2;
82 background: linear-gradient(
83 to bottom,
84 rgba(255, 255, 255, 0.9),
85 rgba(255, 255, 255, 0.9) 47%,
86 transparent,
87 transparent 47%,
88 transparent,
89 transparent 55%,
90 rgba(255, 255, 255, 0.9) 50%,
91 rgba(255, 255, 255, 0.9)
92 );
93}
94
95.loader__word {
96 display: block;
97 font-size: 3.2rem;
98}
99
100.loader__progressWrapper {
101 position: absolute;
102 bottom: 0;
103 left: 0;
104 height: 5vh;
105 width: 100%;
106 z-index: 3;
107}
108
109.loader__progress {
110 height: 100%;
111 width: 100%;
112 background-color: black;
113 transform: scaleX(0);
114 transform-origin: left center;
115}
116
117.loader__progressNumber {
118 position: absolute;
119 left: -5vw;
120 top: 50%;
121 transform: translateY(-50%);
122 z-index: 4;
123 white-space: nowrap;
124 color: white;
125 font-size: 3.2rem;
126}
127
128.logo {
129 width: 3.8rem;
130 height: 1.9rem;
131}
132
133.hero {
134 position: relative;
135 height: 100vh;
136 overflow: hidden;
137 display: flex;
138 flex-direction: column;
139 justify-content: space-between;
140}
141
142.hero__top {
143 display: flex;
144 justify-content: space-between;
145 align-items: center;
146 margin-inline: 4rem;
147 margin-top: 3.2rem;
148}
149
150.hero__title {
151 position: absolute;
152 top: 50%;
153 left: 4rem;
154 width: calc(100% - 8rem);
155 transform: translateY(-50%);
156 margin-bottom: 8rem;
157 display: grid;
158 grid-template-columns: max-content 1fr max-content;
159 align-items: center;
160 gap: 3.2rem;
161 font-size: 16rem;
162}
163
164.hero__line {
165 display: inline-block;
166 height: 0.4rem;
167 width: 100%;
168 background-color: black;
169 transform: scaleX(0);
170 transform-origin: center center;
171}
172
173.hero__image {
174 overflow: hidden;
175 position: absolute;
176 bottom: -10vh;
177 left: 0;
178 width: 100%;
179 transform-origin: top center;
180}
181
182.hero__imageOverlay {
183 position: absolute;
184 inset: 0;
185 z-index: 3;
186 background-color: black;
187 transform: scaleY(0.31);
188 transform-origin: bottom center;
189}