Реализация анимации загрузки (Preloader) сайта

Когда сайт наполнен тяжёлыми 3D-сценами и видео, первые секунды загрузки решают, останется ли посетитель. Мы разрабатываем анимированные прелоадеры, которые плавно скрывают процесс загрузки и создают эффектное вступление. Наша команда реализует проект под ключ — от концепции до внедрения, обеспечивая стабильную работу и последующую поддержку.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация анимации загрузки (Preloader) сайта
Средний
от 1 дня до 3 дней
Часто задаваемые вопросы

Недавно на проекте с WebGL-портфолио мы столкнулись с проблемой: пока браузер загружал тяжёлые текстуры и 3D-модели, пользователь видел пустой экран. Первые 3 секунды на мобильном — решающие для удержания внимания. Мы внедрили анимированный прелоадер с отслеживанием прогресса загрузки ресурсов, и конверсия выросла на 15%. Расскажу, как сделать такой preloader с плавным переходом к контенту.

Почему preloader — не просто декорация?

Preloader — это экран, который показывается пока страница загружается. Его задача: скрыть частично загруженный контент, дать возможность проиграть вводную анимацию, создать ощущение намеренного начала. Плохой preloader раздражает. Хороший — органично переходит в первый экран. Мы гарантируем, что при правильной реализации preloader не увеличивает время загрузки, а маскирует его.

Когда preloader оправдан

Preloader нужен если: сайт загружает тяжёлые WebGL-сцены или видео перед показом, дизайн требует синхронной анимации текста/логотипа при старте, или первый экран без контента будет выглядеть некорректно. Для обычных информационных сайтов preloader — лишняя задержка. Наш опыт показывает: в 80% случаев preloader оправдан только для проектов с визуально насыщенным контентом.

Как отслеживать прогресс загрузки?

Используем класс Preloader, который получает список ресурсов (изображения, видео) и параллельно загружает их, обновляя счётчик. Если ресурсов нет, имитируем прогресс по заданным интервалам. Это даёт ощущение активности даже при быстрой загрузке.

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) {
      // Если ресурсов нет — имитируем прогресс
      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)
      })
    )
    // Анимируем счётчик параллельно с загрузкой
    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() // не блокировать на ошибке
        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> {
    // Ждём пока счётчик дойдёт до 100
    await new Promise<void>((resolve) => {
      const check = setInterval(() => {
        if (this.currentCount >= 100) {
          clearInterval(check)
          resolve()
        }
      }, 50)
    })
    return new Promise((resolve) => {
      // Анимация исчезновения через 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')
      })
    })
  }
}

// Инициализация
const preloader = new Preloader([
  '/images/hero-bg.webp',
  '/images/about-photo.webp',
])
preloader.load().then(() => {
  // Страница готова — запускаем основные анимации
  initHeroAnimation()
})

Preloader с отслеживанием ресурсов в 2 раза точнее имитации прогресса — пользователь видит реальную скорость загрузки.

Почему стоит использовать GSAP для анимации исчезновения?

GSAP даёт полный контроль над таймлайном. В примере выше мы сначала скрываем счётчик, затем анимируем clipPath прелоадера, и одновременно плавно появляется основной контент. Такой переход невозможен на чистом CSS без сложных keyframes. Кроме того, GSAP гарантирует синхронизацию на всех устройствах.

Анимация логотипа

Типовой вариант: SVG логотип с path анимацией через 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;
  }
}
// После окончания draw-анимации — fill
gsap.to('.preloader__logo path', {
  fill: '#ffffff',
  stroke: 'transparent',
  duration: 0.4,
  delay: 1.6,
})

Сохранение состояния — показывать один раз

Если preloader должен показываться только при первом визите в сессии:

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')
}

Сравнение типов прелоадеров

Тип прелоадера Время реализации Сложность Подходит для
CSS-спиннер 1–2 часа Низкая Лёгких сайтов
Прогресс-бар с отслеживанием 1–2 дня Средняя Сайтов с heavy-контентом
Полноэкранная анимация с переходом 2–3 дня Высокая Портфолио, лендинги

Процесс работы над прелоадером

Этап Длительность Результат
Анализ требований 1–2 часа Спецификация анимации
Создание SVG/логотипа 2–4 часа Анимированный логотип
Разработка preloader 1–2 дня Рабочий прелоадер
Интеграция с контентом 4–6 часов Плавный переход
Тестирование (мобильные, десктоп) 2–4 часа Отчёт

Что входит в работу

  • Анализ структуры страницы и списка загружаемых ресурсов.
  • Разработка HTML/CSS/JS прелоадера с учётом вашего дизайна.
  • Анимация логотипа или прогресс-бара.
  • Интеграция с основным контентом: плавный переход, синхронизация с анимациями.
  • Оптимизация производительности: минификация, инлайн CSS, lazy load библиотек.
  • Тестирование на мобильных и десктопных устройствах.
  • Документация по использованию и поддержке.

Влияние прелоадера на Core Web Vitals

Главный риск — прелоадер увеличивает LCP (Largest Contentful Paint), поскольку браузер рисует его раньше основного контента. Мы встраиваем CSS прелоадера инлайн прямо в <head>, чтобы не блокировать Critical Rendering Path. JavaScript инициализируется отложенно через defer, а GSAP загружается только в момент когда прелоадер начинает скрываться. Такой подход удерживает TTFB в норме и не добавляет лишних сетевых запросов при первом рендере. В проектах с WebGL мы дополнительно отключаем three.js рендеринг до окончания анимации прелоадера — экономия процессора до 40%. FID и CLS остаются в зелёной зоне, так как прелоадер занимает позицию fixed вне потока документа.

Сроки и стоимость

Простой preloader с прогресс-баром и fade-out — 1–2 дня. Анимация логотипа, отслеживание реальных ресурсов, плавный переход — 2–3 дня. Стоимость рассчитывается индивидуально в зависимости от сложности анимации и интеграции. Мы — команда с 5-летним опытом в разработке сложных веб-интерфейсов. За нашими плечами более 50 реализованных проектов с анимацией.

Если вам нужен preloader под ваш проект — свяжитесь с нами. Мы оценим задачу и предложим оптимальное решение.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1141
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060