Key Issues:
-
Double Decrementing (--timer): This is likely why your timer "stops early" or moves too fast. You are calling --timer twice inside the same setInterval loop: once when checking for the "warning" class and again when checking for the "expired" state. This means for every 1 second of real time, the code removes 2 seconds from the counter.
-
Overlapping Intervals: If startCountdown is called more than once (e.g., a user clicks a button twice), multiple intervals will run simultaneously. This causes the timer to flicker or count down at warp speed because each interval is fighting to update the same display element.
-
Scope and Garbage Collection: The interval variable is defined locally within the function. While the listener for the "compareButton" can access it via a closure, if the function is re-run, the reference to the previous interval is lost, making it impossible to clear the old timer.
-
Event Listener Stacking: Every time the disableBackspaceCheckbox is toggled, a new event listener is added to userInput. If a user checks and unchecks the box 5 times, there will be 5 duplicate functions running on every single keystroke, which will cause serious lag.
-
Window Onload Inside a Function: You have window.onload nested inside enterFullscreen. This is a major issue because window.onload only triggers when the page finishes loading. If the user clicks the fullscreen button after the page has already loaded, the code inside window.onload will never run, and your timer will never start.
-
UI Blocking: The timer depends on setInterval, which can drift over long periods (like 50 minutes). However, the immediate fix is ensuring the logic doesn't double-decrement.
Fixed code:
// Move interval to outer scope to ensure we can stop it reliably
let countdownInterval;
function startCountdown(duration, display) {
// Clear any existing timer before starting a new one
if (countdownInterval) clearInterval(countdownInterval);
let timer = duration;
const timerContainer = document.getElementById('timerContainer');
const compareButton = document.getElementById("compareButton");
const updateDisplay = () => {
let minutes = Math.floor(timer / 60);
let seconds = timer % 60;
display.textContent = `${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
};
updateDisplay(); // Initial call to avoid 1s delay
countdownInterval = setInterval(() => {
timer--; // DECREMENT ONCE ONLY
updateDisplay();
// Warning Logic (300 seconds = 5 minutes)
if (timer < 300 && timer >= 0) {
timerContainer.classList.add('warning');
} else {
timerContainer.classList.remove('warning');
}
// Expiration Logic
if (timer < 0) {
clearInterval(countdownInterval);
display.classList.add('expired');
compareButton.click();
}
}, 1000);
}
function enterFullscreen() {
const elem = document.documentElement;
// Request Fullscreen
if (elem.requestFullscreen) elem.requestFullscreen();
else if (elem.webkitRequestFullscreen) elem.webkitRequestFullscreen();
else if (elem.msRequestFullscreen) elem.msRequestFullscreen();
// Hide overlay
document.getElementById('fullscreenOverlay').style.display = 'none';
// FIX: Start timer immediately, don't wait for a window.onload that already happened
const display = document.querySelector('#timer');
const tmx = "50";
const fiftyMinutes = parseTimeString(tmx);
startCountdown(fiftyMinutes, display);
}
// FIX: Prevent stacking listeners. Define the logic once, check the state inside.
document.getElementById('userInput').addEventListener('keydown', function(e) {
const disableBackspace = document.getElementById('disableBackspaceCheckbox').checked;
if (disableBackspace && e.key === 'Backspace') {
e.preventDefault();
}
});
function parseTimeString(timeStr) {
if (timeStr.includes(':')) {
const parts = timeStr.split(':');
return (parseInt(parts[0], 10) * 60) + parseInt(parts[1], 10);
}
return parseInt(timeStr, 10) * 60;
}
// Ensure the button stops the timer
document.getElementById("compareButton").addEventListener("click", () => {
if (countdownInterval) clearInterval(countdownInterval);
});