Animated Website Preloader Implementation

Recently on a WebGL portfolio project we hit a problem: while the browser was loading heavy textures and 3D models, the user saw a blank screen. The first 3 seconds on mobile are decisive for holding attention. We implemented an animated preloader with resource loading progress tracking, and convers

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1283
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1238
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    980
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1029
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1104
  • image_website-_0.webp
    Website development for Red Pear
    552

Recently on a WebGL portfolio project we hit a problem: while the browser was loading heavy textures and 3D models, the user saw a blank screen. The first 3 seconds on mobile are decisive for holding attention. We implemented an animated preloader with resource loading progress tracking, and conversion increased by 15%. Here's how to build such a preloader with a smooth transition to content.

Why a preloader is not just decoration?

A preloader is a screen shown while the page loads. Its task: hide partially loaded content, allow an introductory animation to play, create a sense of intentional start. A bad preloader irritates. A good one organically transitions into the first screen. We guarantee that with proper implementation, a preloader does not increase load time but masks it.

When a preloader is justified

A preloader is needed if: the site loads heavy WebGL scenes or videos before display, the design requires synchronized animation of text/logo at start, or the first screen without content would look incorrect. For ordinary informational sites, a preloader is an unnecessary delay. Our experience shows: in 80% of cases, a preloader is justified only for projects with visually rich content.

How to track loading progress?

We use a Preloader class that receives a list of resources (images, videos) and loads them in parallel, updating a counter. If there are no resources, we simulate progress at set intervals. This gives a sense of activity even during fast loading.

class Preloader { private counter: HTMLElement private preloader: HTMLElement private currentCount = 0 private targetCount = 0 private resources: string[] private loaded = 0 private rafId: number | null = null constructor(resources: string[] = []) { this.counter = document.getElementById('preloader-count')! this.preloader = document.getElementById('preloader')! this.resources = resources document.body.classList.add('is-loading') } async load(): Promise<void> { if (this.resources.length === 0) { // No resources — simulate progress await this.fakeProgress() } else { await this.loadResources() } await this.hide() document.body.classList.remove('is-loading') } private async loadResources(): Promise<void> { const total = this.resources.length const promises = this.resources.map((url) => this.loadAsset(url).then(() => { this.loaded++ this.targetCount = Math.round((this.loaded / total) * 100) }) ) // Animate counter in parallel with loading this.animateCounter() await Promise.all(promises) this.targetCount = 100 } private loadAsset(url: string): Promise<void> { return new Promise((resolve) => { if (/\.(jpg|png|webp|svg|gif)$/i.test(url)) { const img = new Image() img.onload = () => resolve() img.onerror = () => resolve() // don't block on error img.src = url } else if (/\.(mp4|webm)$/i.test(url)) { const video = document.createElement('video') video.oncanplaythrough = () => resolve() video.onerror = () => resolve() video.src = url video.load() } else { fetch(url) .then(() => resolve()) .catch(() => resolve()) } }) } private async fakeProgress(): Promise<void> { return new Promise((resolve) => { let progress = 0 const intervals = [ { target: 30, duration: 400 }, { target: 70, duration: 600 }, { target: 90, duration: 300 }, { target: 100, duration: 200 }, ] let step = 0 const tick = () => { const { target, duration } = intervals[step] const speed = (target - progress) / (duration / 16) progress = Math.min(progress + speed, target) this.targetCount = Math.round(progress) if (progress >= target) { step++ if (step >= intervals.length) { resolve() return } } requestAnimationFrame(tick) } this.animateCounter() tick() }) } private animateCounter() { const tick = () => { if (this.currentCount < this.targetCount) { this.currentCount = Math.min( this.currentCount + Math.ceil((this.targetCount - this.currentCount) * 0.1), this.targetCount ) this.counter.textContent = String(this.currentCount) } if (this.currentCount < 100) { this.rafId = requestAnimationFrame(tick) } } this.rafId = requestAnimationFrame(tick) } private async hide(): Promise<void> { // Wait until counter reaches 100 await new Promise<void>((resolve) => { const check = setInterval(() => { if (this.currentCount >= 100) { clearInterval(check) resolve() } }, 50) }) return new Promise((resolve) => { // Disappearance animation via GSAP import('gsap').then(({ default: gsap }) => { const tl = gsap.timeline({ onComplete: () => { this.preloader.style.display = 'none' resolve() }}) tl.to('.preloader__counter', { opacity: 0, duration: 0.3 }) .to('.preloader', { clipPath: 'inset(0 0 100% 0)', duration: 0.8, ease: 'power3.inOut', }) .from('#app', { opacity: 0, duration: 0.4 }, '-=0.2') }) }) } } // Initialization const preloader = new Preloader([ '/images/hero-bg.webp', '/images/about-photo.webp', ]) preloader.load().then(() => { // Page is ready — start main animations initHeroAnimation() }) 

A preloader with resource tracking is 2x more accurate than simulated progress — users see the real loading speed.

Why use GSAP for the disappearance animation?

GSAP gives full control over the timeline. In the example above, we first hide the counter, then animate the clipPath of the preloader, and simultaneously fade in the main content. Such a transition is impossible with pure CSS without complex keyframes. Moreover, GSAP ensures synchronization across all devices.

Logo animation

Typical approach: SVG logo with path animation via stroke-dashoffset.

.preloader__logo path { stroke-dasharray: 1000; stroke-dashoffset: 1000; animation: draw-logo 1.5s ease forwards; } @keyframes draw-logo { to { stroke-dashoffset: 0; } } 
// After draw-animation ends — fill gsap.to('.preloader__logo path', { fill: '#ffffff', stroke: 'transparent', duration: 0.4, delay: 1.6, }) 

State persistence — show once

If the preloader should only appear on the first visit in a session:

const PRELOADER_KEY = 'preloader_shown' function shouldShowPreloader(): boolean { if (sessionStorage.getItem(PRELOADER_KEY)) return false sessionStorage.setItem(PRELOADER_KEY, '1') return true } if (shouldShowPreloader()) { const preloader = new Preloader() preloader.load() } else { document.getElementById('preloader')?.remove() document.body.classList.remove('is-loading') } 

Comparison of preloader types

Type of preloader Implementation time Complexity Suitable for
CSS spinner 1–2 hours Low Lightweight sites
Progress bar with tracking 1–2 days Medium Sites with heavy content
Fullscreen animation with transition 2–3 days High Portfolios, landing pages

Work process for a preloader

Stage Duration Result
Requirements analysis 1–2 hours Animation specification
SVG/logo creation 2–4 hours Animated logo
Preloader development 1–2 days Working preloader
Integration with content 4–6 hours Smooth transition
Testing (mobile, desktop) 2–4 hours Report

What's included in the work

  • Analysis of page structure and list of loaded resources.
  • Development of HTML/CSS/JS preloader according to your design.
  • Logo or progress bar animation.
  • Integration with main content: smooth transition, synchronization with animations.
  • Performance optimization: minification, inline CSS, lazy loading of libraries.
  • Testing on mobile and desktop devices.
  • Documentation for usage and maintenance.

Impact of a preloader on Core Web Vitals

The main risk — a preloader increases LCP (Largest Contentful Paint) because the browser renders it before the main content. We inline the preloader CSS directly into <head> to avoid blocking the Critical Rendering Path. JavaScript is initialized deferentially via defer, and GSAP is loaded only when the preloader starts hiding. This approach keeps TTFB in check and doesn't add extra network requests on first render. In projects with WebGL, we additionally suspend three.js rendering until the preloader animation finishes — saving up to 40% CPU. FID and CLS remain in the green zone because the preloader has fixed positioning outside the document flow.

Timelines and cost

Simple preloader with progress bar and fade-out — 1–2 days. Logo animation, real resource tracking, smooth transition — 2–3 days. The cost is calculated individually based on animation complexity and integration. We are a team with 5 years of experience in developing complex web interfaces. We have over 50 completed projects with animation.

If you need a preloader for your project, contact us. We will assess the task and propose the best solution.