Ultra Preloader

An ultra preloader animation.

Preview

Code

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:

Code
npx create-next-app@latest my-app

2. Install GSAP

Code
npm install gsap

Add Loader Component

Code

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

Code

src/components/hero.tsx

1const Hero = () => { 2 return ( 3 <div className="p-4"> 4 </div> 5 ); 6}; 7 8export default Hero;

Add Animations

Code

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

Code

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}

Become an Astrae
Affiliate Today

Make referrals, and bring in clients. Keep 50% of your earnings paid out weekly.

Description