Таймер для посетителей на JS [setInterval не срабатывает в неактивной вкладке как ожидается]

Задача: при входе на сайт, должен появиться таймер (на JS), отчитывающий обратное время, по истечение которого можно будет нажать на определённую кнопку. Пока посетитель находится на одном document.domain, счётчик не сбрасывается. Если перешёл на другую страницу (или вкладку) счётчик должен сброситься. Заранее благодарен.

Сохраняй значение в cookie или localStorage что бы при переходе на другую страницу или рефреше таймер не обновлялся.

Что значит “перешел на другую вкладку” ?

Если нужно получить сразу код - задай такой же вопрос любой LLM (chatgpt, claude, etc).

Если нужно разобраться-понять - укажи становится не понятно. На каком этапе “затыка”?

Мы тут помогаем разобраться с подходами и пониманием в первую очередь.

    <style>
        #timer {
            font-size: 1em;
            align: center;
        }
        #gbsbm {
            display: none;
        }
    </style>
    <div id="timer">ОЖИДАЙТЕ: 00:00:00</div>
 
    <script>
        const timerElement = document.getElementById('timer');
        const gbsbm = document.getElementById('gbsbm');
        let countdown;

        
        const countdownTime = 10000;
        let endTime;

        function startTimer() {
            endTime = Date.now() + countdownTime;

            // Сохраняем время окончания таймера в localStorage
            localStorage.setItem('endTime', endTime);
            updateTimer();
        }

        function updateTimer() {
            const now = Date.now();
            const remainingTime = endTime - now;

            if (remainingTime <= 0) {
                gbsbm.style.display = 'block';
				timer.style.display = 'none';
                clearInterval(countdown);
            } else {
                const hours = Math.floor((remainingTime / (1000 * 60 * 60)) % 24);
                const minutes = Math.floor((remainingTime / (1000 * 60)) % 60);
                const seconds = Math.floor((remainingTime / 1000) % 60);
                timerElement.textContent = 'ОЖИДАЙТЕ: ' + `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
            }
        }

        function checkTimer() {
            const endTimeStored = localStorage.getItem('endTime');
            if (endTimeStored) {
                endTime = parseInt(endTimeStored, 10);
                updateTimer();
                countdown = setInterval(updateTimer, 1000);
            } else {
                startTimer();
                countdown = setInterval(updateTimer, 1000);
            }
        }

        document.addEventListener('visibilitychange', () => {
            if (document.visibilityState = 'hidden') {
                localStorage.removeItem('endTime');
                clearInterval(countdown);
            } else {
                checkTimer();
            }
        });
        checkTimer();
    </script>

Почти то, что нужно. Но! При переключении вкладок в браузере - таймер останавливается.
А задача в том, что если посетитель находится на страницах моего сайта, просто свернул окно браузера, открыл вкладку с чужим сайтом, таймер не останавливался. В общем: таймер должен сбрасываться, если закрыты все мои страницы. Заранее благодарен

Код написан так, что он предполагает что для строки setInterval(tick, 1000) функция tick будет вызываться каждую секунду. Браузеры же меняют поведение работы setInterval-a в неактивных табах чтобы сэкономить энергию: они начинают вызывать интервалы раз в минуту или нечто в подобном стиле. Получается что предположение зашитое в коде не срабатывает как надо, и создается впечатление что таймер стоит, не идет пока пользователь свернул окно или находится в другой вкладке.

Решение - поменять логику счета времени: вместо того чтобы опираться на стабильный размер интервала, опираться на фактически пройденное время между вызовами таймера. Смотреть надо в объект даты, брать разницу между вызовами интервалов в миллисекундах из этого объекта.

Суть моего предложения на псевдокоде:

var timeLeft = 24 * 60 * 60 * 1000 // 24 часа
var lastTick = Date.now()

var intervalId = setInterval(() => {
	var now = Date.now()
	var elapsed = now - lastTick
	lastTick = now // фиксируем когда был последний тик таймера
	timeLeft = timeLeft - elapsed
	if (timeLeft <= 0) {
		alert("Время вышло")
		clearInterval(intervalId) // чистим таймер чтобы больше не запускался 
	}
}, 1000)

Этому коду все равно с каким именно интервалом будет вызываться таймер, он всегда будет точно считать оставшееся время, реагируя на истекание времени.

<style>
 #timer {
 font-size: 1em;
 align: center;
font-weight: bold;
 }
 #gbsbm {
 display: none;
 }
 </style>
 <div align="center" id="timer">ОЖИДАЙТЕ: 00:00:00</div>
 
 <script>
 const timerElement = document.getElementById('timer');
 const gbsbm = document.getElementById('gbsbm');

 function checkTimer() {
	var timeLeft = 30 * 60 * 1000
	var lastTick = Date.now()
	var intervalId = setInterval(() => {
	var now = Date.now()
	var elapsed = now - lastTick
	lastTick = now
	timeLeft = timeLeft - elapsed

	if (timeLeft <= 0) {
		gbsbm.style.display = 'block';
		timer.style.display = 'none';
		clearInterval(intervalId)
	}
	else {
		const hours = Math.floor((timeLeft / (1000 * 60 * 60)) % 24);
		const minutes = Math.floor((timeLeft / (1000 * 60)) % 60);
		const seconds = Math.floor((timeLeft / 1000) % 60);
		timerElement.textContent = 'ОЖИДАЙТЕ: ' + `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
	}
	}, 1000)
	};

 checkTimer();
 </script>

Благодарю! Код рабочий. Но одна проблема осталась. Со страницы с этим кодом можно перейти по ссылкам на другие страницы моего сайта. И таймер в этом случае сбрасывается. Мне нужен сброс таймера, если ни одна моя страница не открыта. Заранее благодарен.

Предыдущий подход из этого куска кода Таймер для посетителей на JS [setInterval не срабатывает в неактивной вкладке как ожидается] - #4 от пользователя Vitalysan сработает.

Я не вижу в коде решенную задачу по сбросу таймера когда все табы закрыты. Код не выглядит так что он поведет себя так как ты описываешь. Но эта задача - учета всех вкладок, отдельная-параллельная задаче общего таймера для всех вкладок. Ее можно будет решить отдельно. Пожалуйста сделай отдельную тему если понадобится с ней разобраться. Я хочу чтобы читающие имели дело с отдельными независимыми аспектами.

Понял, благодарю!