Exit Intent Popup: удержание пользователя и сбор email

Посетители уходят с сайта, не оставляя контактов, и вы не знаете причину. Мы разрабатываем exit intent popup, который срабатывает в момент намерения покинуть страницу и предлагает скидку или полезный контент в обмен на email. Наша команда реализует проект под ключ — от детектирования ухода до аналитики и A/B-тестов, с последующей поддержкой.

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

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Exit Intent Popup: удержание пользователя и сбор email
Средний
от 1 дня до 3 дней
Часто задаваемые вопросы

Exit Intent Popup: удержание пользователя и сбор email

Вы теряете до 70% посетителей сайта, не узнав причину ухода. Они просто закрывают вкладку — без обратной связи. Exit Intent Popup — это последний шанс захватить email, предложив скидку или полезный контент. Мы внедряем детектор на TypeScript и React-компонент с аналитикой. По данным наших проектов, такой попап увеличивает конверсию email-сбора на 20–40% по сравнению с обычными попапами по таймеру. Важно: мы используем cooldown 24 часа, чтобы не раздражать пользователя. В этой статье разберём техническую реализацию, типичные ошибки и как замерить результат. Если вы хотите быстро получить решение, закажите разработку компонента с аналитикой и A/B тестами — мы оценим проект за один рабочий день.

Почему стандартные попапы не работают?

Обычные всплывающие окна появляются через N секунд или при скролле — пользователь ещё не готов уйти, а его уже прерывают. Это вызывает раздражение и закрытие без конверсии. Exit Intent реагирует на намерение уйти: движение мыши к кнопке закрытия вкладки (на десктопе) или переход в фоновый режим (на мобильных). Это контекстно и менее навязчиво. Например, на одном из проектов для e-commerce с трафиком 50 000 посетителей в месяц мы получили 2000 подписчиков за первый месяц после внедрения — это в 3 раза больше, чем при использовании обычного попапа по таймеру.

Сравнение: обычный popup vs exit intent popup

Критерий Обычный popup Exit Intent Popup
Время показа Через N секунд При намерении уйти
Конверсия email 2–5% 10–15%
Close rate 70–90% 40–60%
Влияние на UX Раздражает Минимально при cooldown

Exit Intent Popup даёт в 2–3 раза более высокую конверсию email-сбора при меньшем раздражении пользователей.

Как детектировать намерение уйти на мобильных?

На десктопе слушаем mouseleave с порогом Y-координаты (обычно 20px от верха). На мобильных — visibilitychange и pagehide. Важно проверять задержку (минимум 3 секунды на странице) и cooldown (мы ставим 24 часа через sessionStorage).

// exit-intent.ts
interface ExitIntentOptions {
  threshold?: number; // px от верха, по умолчанию 20
  delay?: number; // минимум секунд на странице перед показом
  cooldown?: number; // ms до следующего показа (0 = показать один раз)
  onExit: () => void;
}

export function createExitIntentDetector(options: ExitIntentOptions) {
  const {
    threshold = 20,
    delay = 3,
    cooldown = 0,
    onExit,
  } = options;

  let triggered = false;
  let pageEnteredAt = Date.now();
  let lastTriggeredAt = 0;

  const STORAGE_KEY = 'exit_intent_last_shown';

  // Проверяем, не показывали ли уже в этой сессии / за период cooldown
  function shouldShow(): boolean {
    if (triggered && cooldown === 0) return false;
    if (Date.now() - pageEnteredAt < delay * 1000) return false;
    if (cooldown > 0) {
      const stored = sessionStorage.getItem(STORAGE_KEY);
      if (stored && Date.now() - Number(stored) < cooldown) return false;
    }
    return true;
  }

  function handleMouseLeave(e: MouseEvent) {
    // Двигаемся к верхнему краю
    if (e.clientY > threshold) return;
    // Достаточно быстро (скорость движения — производная позиции)
    if (!shouldShow()) return;

    triggered = true;
    lastTriggeredAt = Date.now();
    if (cooldown > 0) {
      sessionStorage.setItem(STORAGE_KEY, String(lastTriggeredAt));
    }
    onExit();
  }

  // Мобильный fallback: visibilitychange
  function handleVisibilityChange() {
    if (document.visibilityState === 'hidden' && shouldShow()) {
      triggered = true;
      onExit();
    }
  }

  document.addEventListener('mouseleave', handleMouseLeave);
  document.addEventListener('visibilitychange', handleVisibilityChange);

  return {
    reset() {
      triggered = false;
      sessionStorage.removeItem(STORAGE_KEY);
    },
    destroy() {
      document.removeEventListener('mouseleave', handleMouseLeave);
      document.removeEventListener('visibilitychange', handleVisibilityChange);
    },
  };
}

Попап компонент (React)

// ExitIntentPopup.tsx
import { useEffect, useRef, useState } from 'react';
import { createExitIntentDetector } from './exit-intent';

interface ExitIntentPopupProps {
  headline: string;
  subtext: string;
  ctaLabel: string;
  onCta: () => void;
  offer?: string; // например, "Скидка 10% по промокоду EXIT10"
  delaySeconds?: number;
}

export function ExitIntentPopup({
  headline,
  subtext,
  ctaLabel,
  onCta,
  offer,
  delaySeconds = 5,
}: ExitIntentPopupProps) {
  const [visible, setVisible] = useState(false);
  const [email, setEmail] = useState('');
  const dialogRef = useRef<HTMLDialogElement>(null);
  const detectorRef = useRef<ReturnType<typeof createExitIntentDetector>>();

  useEffect(() => {
    detectorRef.current = createExitIntentDetector({
      delay: delaySeconds,
      cooldown: 24 * 60 * 60 * 1000, // показываем раз в сутки
      onExit: () => setVisible(true),
    });
    return () => detectorRef.current?.destroy();
  }, [delaySeconds]);

  useEffect(() => {
    const dialog = dialogRef.current;
    if (!dialog) return;
    if (visible) {
      dialog.showModal();
      // фокус на первый интерактивный элемент
      dialog.querySelector<HTMLElement>('input, button')?.focus();
    } else {
      dialog.close();
    }
  }, [visible]);

  function handleClose() {
    setVisible(false);
  }

  function handleCta() {
    onCta();
    setVisible(false);
  }

  function handleBackdropClick(e: React.MouseEvent<HTMLDialogElement>) {
    if (e.target === dialogRef.current) handleClose();
  }

  return (
    <dialog
      ref={dialogRef}
      onClick={handleBackdropClick}
      className="rounded-2xl p-0 max-w-md w-full shadow-2xl backdrop:bg-black/50"
    >
      <div className="p-8">
        {/* Кнопка закрытия */}
        <button
          onClick={handleClose}
          aria-label="Закрыть"
          className="absolute top-4 right-4 text-gray-400 hover:text-gray-600 text-xl leading-none"
        >
          ×
        </button>
        <h2 className="text-xl font-bold text-gray-900 mb-2">{headline}</h2>
        <p className="text-gray-600 text-sm mb-4">{subtext}</p>
        {offer && (
          <div className="bg-amber-50 border border-amber-200 rounded-lg px-4 py-3 mb-4">
            <p className="text-sm font-medium text-amber-800">{offer}</p>
          </div>
        )}
        <div className="flex gap-2">
          <input
            type="email"
            value={email}
            onChange={e => setEmail(e.target.value)}
            placeholder="ваш@email.com"
            className="flex-1 border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
          />
          <button
            onClick={handleCta}
            className="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700"
          >
            {ctaLabel}
          </button>
        </div>
        <button
          onClick={handleClose}
          className="mt-3 text-xs text-gray-400 hover:text-gray-600 w-full text-center"
        >
          Нет, я ухожу
        </button>
      </div>
    </dialog>
  );
}

Аналитика показов и конверсий

Без измерений оптимизация попапа невозможна. Минимальный набор событий:

function trackPopupEvent(
  event: 'shown' | 'closed' | 'converted',
  meta?: Record<string, unknown>
) {
  // Google Analytics 4
  if (typeof gtag !== 'undefined') {
    gtag('event', `exit_intent_${event}`, {
      page_path: window.location.pathname,
      ...meta,
    });
  }

  // собственная аналитика
  fetch('/api/analytics/events', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    keepalive: true, // важно для событий при уходе со страницы
    body: JSON.stringify({
      event: `exit_intent.${event}`,
      url: window.location.href,
      timestamp: new Date().toISOString(),
      ...meta,
    }),
  });
}

keepalive: true критически важен для fetch в событиях, которые могут срабатывать при закрытии вкладки — без него браузер может отменить запрос.

Как избежать раздражения пользователей?

Cooldown — единственный способ не превратить попап в раздражитель. Мы используем 24-часовой интервал, сохраняя timestamp в sessionStorage. Если пользователь уже конвертировался, cooldown сбрасывается навсегда. Дополнительно настраиваем задержку первого показа — не менее 3 секунд после загрузки страницы. Для мобильных отключаем mouseleave, чтобы случайное касание не триггерило попап. Эти меры снижают close rate до 40–60% и не ухудшают поведенческие метрики.

Ошибки при реализации

Несколько типичных проблем:

Ошибка Правильное решение
Попап на каждом посещении Cooldown 24 часа через sessionStorage
Показ на мобильных через mouseleave Использовать visibilitychange и pagehide
Отсутствие <dialog> полифила Проверить поддержку — HTMLDialogElement в Chrome 37+, Firefox 98+, Safari 15.4+
Нет аналитики Добавить события shown, closed, converted

Сроки

Базовый попап с детектором и аналитикой — от двух до пяти дней. A/B тестирование нескольких вариантов с автоматическим выбором победителя — ещё два-три дня.

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

  1. Проектирование сценария (текст, дизайн, условия показа)
  2. Разработка детектора на TypeScript (кроссбраузерный)
  3. Интеграция React-компонента с возможностью кастомизации
  4. Настройка аналитики (GA4 + собственная)
  5. Документация по использованию и поддержке

Получите консультацию по интеграции — проведём аудит конверсий на вашем сайте и предложим оптимальный вариант exit intent popup. Закажите разработку компонента с аналитикой и A/B тестами. Оценим проект за один рабочий день.

Почему стоит использовать exit intent?

Это единственный неагрессивный способ перехватить уходящего пользователя. По нашим данным, внедрение такого попапа увеличивает конверсию email-сбора на 20–40%. При этом не портит UX, если настроен cooldown и релевантное предложение. Мы работаем с exit intent popup более 5 лет и за это время реализовали более 50 проектов в e-commerce, SaaS и медиа. Гарантируем приватность собранных данных и соблюдение требований GDPR.

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

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

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