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}