@@ -309,17 +309,52 @@ import WORKDECOR from '../assets/img/work-decor.webp';
309309
310310</Layout >
311311
312+ <!--
313+ Логика показа попапа консультации:
314+
315+ УСЛОВИЯ ПОКАЗА:
316+ 1. Пользователь проскроллил ниже первого экрана (на высоту viewport)
317+ 2. Пользователь не скроллит 2+ секунд
318+ 3. Пользователь не кликает 3+ секунд
319+ 4. Вкладка активна (не скрыта)
320+ 5. Прошло 14 секунд с момента последней активности
321+
322+ ТАЙМЕРЫ:
323+ - 2 сек - пауза в скролле (если пользователь перестал скроллить)
324+ - 3 сек - пауза в кликах (если пользователь перестал кликать)
325+ - 14 сек - основной таймер показа попапа
326+
327+ ОТМЕНА ТАЙМЕРОВ:
328+ - При любом скролле
329+ - При любом клике
330+ - При скрытии вкладки
331+ - При изменении размера окна
332+ - После показа попапа (один раз за сессию)
333+
334+ ЦЕЛЬ: Показывать попап только когда пользователь активно читает контент,
335+ но не мешать взаимодействию с интерфейсом.
336+ -->
312337<script >
313338document.addEventListener('DOMContentLoaded', () => {
314- const section = document.querySelector<HTMLElement>('#features');
315- if (!section) return;
316-
317339 let timerId: number | null = null;
318340 let hasTriggered = false;
341+ let isScrolling = false;
342+ let scrollTimeout: number | null = null;
343+ let isClicking = false;
344+ let clickTimeout: number | null = null;
319345
320346 const startTimer = () => {
321- if (hasTriggered || timerId) return;
322- timerId = window.setTimeout(triggerPopup, 3000) ;
347+ if (hasTriggered || timerId || isClicking) return;
348+
349+ // Проверяем, что пользователь проскроллил хотя бы на высоту экрана
350+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
351+ const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
352+
353+ if (scrollTop < viewportHeight) {
354+ return; // Не показываем попап на первом экране
355+ }
356+
357+ timerId = window.setTimeout(triggerPopup, 14000); // 14 секунд
323358 };
324359
325360 const cancelTimer = () => {
@@ -336,58 +371,89 @@ document.addEventListener('DOMContentLoaded', () => {
336371 window.dispatchEvent(new CustomEvent('open-contact-popup'));
337372
338373 cancelTimer();
339- observer.disconnect();
340374 window.removeEventListener('scroll', onScroll);
341375 window.removeEventListener('resize', onResize);
342376 window.removeEventListener('visibilitychange', onVisibilityChange);
343- window .removeEventListener('hashchange ', onHashChange );
377+ document .removeEventListener('click ', onClick );
344378 };
345379
346-
347- const debounce = <T extends (...args: any[]) => void>(fn: T, wait: number) => {
348- let timeout: number | null = null;
349- return (...args: Parameters<T>) => {
350- if (timeout) clearTimeout(timeout);
351- timeout = window.setTimeout(() => fn(...args), wait);
352- };
380+ const onScroll = () => {
381+ isScrolling = true;
382+
383+ // Отменяем предыдущий таймер скролла
384+ if (scrollTimeout) {
385+ clearTimeout(scrollTimeout);
386+ }
387+
388+ // Запускаем новый таймер - если через 2 секунды скролл остановится
389+ scrollTimeout = window.setTimeout(() => {
390+ isScrolling = false;
391+ // Если вкладка активна и пользователь не скроллит и не кликает - запускаем основной таймер
392+ if (!document.hidden && !isClicking) {
393+ startTimer();
394+ }
395+ }, 2000);
353396 };
354397
355- const onScrollEnd = debounce(() => {
356- const rect = section.getBoundingClientRect();
357- const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
358- const near = !(rect.bottom < -200 || rect.top > viewportHeight + 200);
398+ const onClick = () => {
399+ isClicking = true;
400+
401+ // Отменяем основной таймер при клике
402+ cancelTimer();
403+
404+ // Отменяем предыдущий таймер клика
405+ if (clickTimeout) {
406+ clearTimeout(clickTimeout);
407+ }
408+
409+ // Запускаем новый таймер - если через 3 секунды не будет кликов
410+ clickTimeout = window.setTimeout(() => {
411+ isClicking = false;
412+ // Если вкладка активна и пользователь не скроллит - запускаем основной таймер
413+ if (!document.hidden && !isScrolling) {
414+ startTimer();
415+ }
416+ }, 3000);
417+ };
359418
360- if (near) startTimer();
361- else cancelTimer();
362- }, 150);
419+ const onResize = () => {
420+ // При изменении размера окна отменяем таймеры
421+ cancelTimer();
422+ if (scrollTimeout) {
423+ clearTimeout(scrollTimeout);
424+ scrollTimeout = null;
425+ }
426+ if (clickTimeout) {
427+ clearTimeout(clickTimeout);
428+ clickTimeout = null;
429+ }
430+ };
363431
364- const onScroll = () => onScrollEnd();
365- const onResize = () => onScrollEnd();
366432 const onVisibilityChange = () => {
367- if (document.hidden) cancelTimer();
368- else onScrollEnd();
369- };
370- const onHashChange = () => {
371- cancelTimer();
372- onScrollEnd();
433+ if (document.hidden) {
434+ // Если вкладка скрыта - отменяем все таймеры
435+ cancelTimer();
436+ if (scrollTimeout) {
437+ clearTimeout(scrollTimeout);
438+ scrollTimeout = null;
439+ }
440+ if (clickTimeout) {
441+ clearTimeout(clickTimeout);
442+ clickTimeout = null;
443+ }
444+ } else {
445+ // Если вкладка стала активной и пользователь не скроллит и не кликает - запускаем таймер
446+ if (!isScrolling && !isClicking) {
447+ startTimer();
448+ }
449+ }
373450 };
374451
375- const observer = new IntersectionObserver(
376- (entries) => {
377- const entry = entries[0];
378- if (entry.isIntersecting && !hasTriggered) startTimer();
379- else cancelTimer();
380- },
381- { threshold: 0.0, rootMargin: '400px 0px 400px 0px' }
382- );
383-
384- observer.observe(section);
385- window.addEventListener('scroll', onScroll, { passive: true });
452+ // Запускаем отслеживание скролла и кликов
453+ window.addEventListener('scroll', onScroll);
386454 window.addEventListener('resize', onResize);
387455 window.addEventListener('visibilitychange', onVisibilityChange);
388- window.addEventListener('hashchange', onHashChange);
389-
390- onScrollEnd();
456+ document.addEventListener('click', onClick);
391457});
392458
393459</script >
0 commit comments