fix progress bar

This commit is contained in:
Theis Pieter Hollebeek 2026-04-03 16:04:34 +02:00
parent 382992661e
commit 60e34a4d67
2 changed files with 86 additions and 79 deletions

View File

@ -1,7 +1,9 @@
var params = parseQueryParams(); var params = parseQueryParams();
// Set window title // Set window title
if (params.name) document.title = decodeURIComponent(params.name) + " - Popup Timer"; if (params.name) {
document.title = decodeURIComponent(params.name) + " - Popup Timer";
}
var hours, minutes, seconds; var hours, minutes, seconds;
var secondsElem = document.getElementById("seconds"); var secondsElem = document.getElementById("seconds");
@ -12,18 +14,20 @@ var progressBar = document.getElementById("progress-fg");
var timeout; var timeout;
var timesUpStr = "[ TIME'S UP! ] "; var timesUpStr = "[ TIME'S UP! ] ";
var lastPaused = -1; var lastPaused = -1;
var requiredProgress = -1;
var latestUpdate = Date.now();
var currentAnimationId = 0;
// Init audio // Init audio
var oscillator, audioContext; var oscillator, audioContext;
if (params.beep === "true") { if (params.beep === "true") {
try { try {
audioContext = new (window.AudioContext || window.webkitAudioContext); audioContext = new (window.AudioContext || window.webkitAudioContext)();
} catch(e) {} } catch (e) {}
} }
// Initialize clock and start // Initialize clock and start
switch (params.type) { switch (params.type) {
case "timer": case "timer":
initTimer(); initTimer();
if (params.autostart === "true") startTimer(1000); if (params.autostart === "true") startTimer(1000);
@ -39,15 +43,13 @@ switch (params.type) {
} }
// Reset timer/stopwatch // Reset timer/stopwatch
document.getElementById("restart").onclick = function() { document.getElementById("restart").onclick = function () {
if (timeout) clearTimeout(timeout); if (timeout) clearTimeout(timeout);
var paused = playBtn.classList.contains("fa-play"); var paused = playBtn.classList.contains("fa-play");
switch (params.type) { switch (params.type) {
case "timer": case "timer":
// Reset progress bar // Reset progress bar
progressBar.style.animationName = "none"; progressBar.style.animationName = "none";
progressBar.offsetHeight; // Trigger reflow progressBar.offsetHeight; // Trigger reflow
@ -58,7 +60,6 @@ document.getElementById("restart").onclick = function() {
break; break;
case "stopwatch": case "stopwatch":
// Reset displayed time // Reset displayed time
secondsElem.innerText = "00s"; secondsElem.innerText = "00s";
secondsElem.classList.remove("highlighted"); secondsElem.classList.remove("highlighted");
@ -72,16 +73,16 @@ document.getElementById("restart").onclick = function() {
break; break;
} }
} };
// Pause / Resume // Pause / Resume
playBtn.onclick = function() { playBtn.onclick = function () {
// Pause // Pause
if (playBtn.classList.contains("fa-pause")) { if (playBtn.classList.contains("fa-pause")) {
lastPaused = Date.now(); lastPaused = Date.now();
if (params.type === "timer") progressBar.style.animationPlayState = "paused"; if (params.type === "timer") {
progressBar.style.animationPlayState = "paused";
}
clearTimeout(timeout); clearTimeout(timeout);
// Change icon // Change icon
@ -90,7 +91,6 @@ playBtn.onclick = function() {
// Resume // Resume
} else { } else {
var startOffset = lastPaused % 1000; var startOffset = lastPaused % 1000;
switch (params.type) { switch (params.type) {
@ -107,12 +107,11 @@ playBtn.onclick = function() {
// Change icon // Change icon
playBtn.classList.remove("fa-play"); playBtn.classList.remove("fa-play");
playBtn.classList.add("fa-pause"); playBtn.classList.add("fa-pause");
} }
};
}
function initTimer() { function initTimer() {
document.getElementById("progress-bg").style.width = "100%";
hours = parseInt(params.h); hours = parseInt(params.h);
minutes = parseInt(params.m); minutes = parseInt(params.m);
@ -138,35 +137,51 @@ function initTimer() {
secondsElem.innerText = pad(seconds) + "s"; secondsElem.innerText = pad(seconds) + "s";
// Animate progress bar // Animate progress bar
var totalSeconds = (hours * 60 * 60) + (minutes * 60) + seconds; requiredProgress = (hours * 60 * 60) + (minutes * 60) + seconds;
progressBar.style.animationDuration = totalSeconds + "s"; ++currentAnimationId;
progressBar.style.animationName = "progress"; var runId = currentAnimationId;
var animate;
animate = function () {
var paused = playBtn.classList.contains("fa-play");
if (currentAnimationId !== runId || paused) {
return;
}
var milliProgress = (Date.now() - latestUpdate) / 1000;
var progress = (hours * 60 * 60) + (minutes * 60) + seconds -
milliProgress;
var percentage = 1 - (progress /
requiredProgress);
document.getElementById("progress-fg").style.transform =
`scaleX(${percentage})`;
requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
} }
function startTimer(startOffset) { function startTimer(startOffset) {
if (hours === 0 && minutes == 0 && seconds === 0) return; if (hours === 0 && minutes == 0 && seconds === 0) return;
latestUpdate = Date.now();
playBtn.classList.remove("fa-play"); playBtn.classList.remove("fa-play");
playBtn.classList.add("fa-pause"); playBtn.classList.add("fa-pause");
// Wait the starting offset // Wait the starting offset
timeout = setTimeout(updateTimer, startOffset); timeout = setTimeout(updateTimer, startOffset);
document.getElementById("progress-fg").style.animationPlayState = "running";
} }
function updateTimer() { function updateTimer() {
seconds--; seconds--;
if (seconds === 0 && minutes === 0 && hours === 0) secondsElem.classList.remove("highlighted"); if (seconds === 0 && minutes === 0 && hours === 0) {
secondsElem.classList.remove("highlighted");
}
if (seconds <= -1) { if (seconds <= -1) {
seconds = 59; seconds = 59;
minutes--; minutes--;
if (minutes === 0 && hours === 0) minutesElem.classList.remove("highlighted"); if (minutes === 0 && hours === 0) {
minutesElem.classList.remove("highlighted");
}
if (minutes <= -1) { if (minutes <= -1) {
minutes = 59; minutes = 59;
@ -179,24 +194,26 @@ function updateTimer() {
} }
secondsElem.innerText = pad(seconds) + "s"; secondsElem.innerText = pad(seconds) + "s";
latestUpdate = Date.now();
if (hours === 0 && minutes === 0 && seconds == 0) timeUp(); if (hours === 0 && minutes === 0 && seconds == 0) timeUp();
else timeout = setTimeout(updateTimer, 1000); else timeout = setTimeout(updateTimer, 1000);
} }
// Makes the numbers blink red etc. // Makes the numbers blink red etc.
function timeUp() { function timeUp() {
var timeUpInterval = setInterval(function () {
var timeUpInterval = setInterval(function() {
// Blink red // Blink red
[].slice.call(document.querySelectorAll("main > span")).forEach(function(elem) { [].slice.call(document.querySelectorAll("main > span")).forEach(
function (elem) {
elem.classList.toggle("red"); elem.classList.toggle("red");
}); },
);
// Blink title // Blink title
if (document.title.indexOf(timesUpStr) === -1) document.title = timesUpStr + document.title; if (document.title.indexOf(timesUpStr) === -1) {
else document.title = document.title.substr(timesUpStr.length); document.title = timesUpStr + document.title;
} else document.title = document.title.substr(timesUpStr.length);
// Play beep // Play beep
if (params.beep === "true" && audioContext) { if (params.beep === "true" && audioContext) {
@ -207,30 +224,30 @@ function timeUp() {
oscillator.connect(audioContext.destination); oscillator.connect(audioContext.destination);
oscillator.start(); oscillator.start();
oscillator.stop(audioContext.currentTime + 0.25); oscillator.stop(audioContext.currentTime + 0.25);
} catch(e) {} } catch (e) {}
} }
}, 500); }, 500);
// Stop when user clicks // Stop when user clicks
document.onclick = function() { document.onclick = function () {
if (oscillator) oscillator.stop(); if (oscillator) oscillator.stop();
// Reset color // Reset color
[].slice.call(document.querySelectorAll("main > span")).forEach(function(elem) { [].slice.call(document.querySelectorAll("main > span")).forEach(
function (elem) {
elem.classList.remove("red"); elem.classList.remove("red");
}); },
);
// Remove "TIME UP" from title // Remove "TIME UP" from title
if (document.title.indexOf(timesUpStr) !== -1) document.title = document.title.substr(timesUpStr.length); if (document.title.indexOf(timesUpStr) !== -1) {
document.title = document.title.substr(timesUpStr.length);
}
// Clean up // Clean up
clearInterval(timeUpInterval); clearInterval(timeUpInterval);
document.onclick = null; document.onclick = null;
};
}
} }
function initStopwatch() { function initStopwatch() {
@ -240,25 +257,26 @@ function initStopwatch() {
} }
function startStopwatch(startOffset) { function startStopwatch(startOffset) {
playBtn.classList.remove("fa-play"); playBtn.classList.remove("fa-play");
playBtn.classList.add("fa-pause"); playBtn.classList.add("fa-pause");
timeout = setTimeout(updateStopwatch, startOffset); timeout = setTimeout(updateStopwatch, startOffset);
} }
function updateStopwatch() { function updateStopwatch() {
// Update seconds // Update seconds
seconds++; seconds++;
if (hours === 0 && minutes === 0 && seconds === 1) secondsElem.classList.add("highlighted"); if (hours === 0 && minutes === 0 && seconds === 1) {
secondsElem.classList.add("highlighted");
}
if (seconds >= 60) { if (seconds >= 60) {
seconds = 0; seconds = 0;
// Update minutes // Update minutes
minutes++; minutes++;
if (hours === 0 && minutes === 1) minutesElem.classList.add("highlighted"); if (hours === 0 && minutes === 1) {
minutesElem.classList.add("highlighted");
}
if (minutes >= 60) { if (minutes >= 60) {
minutes = 0; minutes = 0;
@ -266,23 +284,20 @@ function updateStopwatch() {
hours++; hours++;
if (hours === 1) hoursElem.classList.add("highlighted"); if (hours === 1) hoursElem.classList.add("highlighted");
hoursElem.innerText = pad(hours) + "h"; hoursElem.innerText = pad(hours) + "h";
} }
// Minutes // Minutes
minutesElem.innerText = pad(minutes) + "m"; minutesElem.innerText = pad(minutes) + "m";
} }
// Seconds // Seconds
secondsElem.innerText = pad(seconds) + "s"; secondsElem.innerText = pad(seconds) + "s";
timeout = setTimeout(updateStopwatch, 1000); timeout = setTimeout(updateStopwatch, 1000);
} }
function parseQueryParams() { function parseQueryParams() {
if (location.search === "" || location.search.indexOf("?") !== 0) return {}; if (location.search === "" || location.search.indexOf("?") !== 0) return {};
var result = {}; var result = {};
location.search.substr(1).split("&").forEach(function(str) { location.search.substr(1).split("&").forEach(function (str) {
var key = str.split("=")[0]; var key = str.split("=")[0];
var value; var value;
if (str.split("=").length > 1) value = str.split("=")[1]; if (str.split("=").length > 1) value = str.split("=")[1];
@ -296,4 +311,3 @@ function parseQueryParams() {
function pad(num) { function pad(num) {
return ("00" + num).substr(-2); return ("00" + num).substr(-2);
} }

View File

@ -1,13 +1,14 @@
@font-face { @font-face {
font-family: "Jetbrains Mono"; font-family: "Jetbrains Mono";
src: url("/assets/JetBrainsMono-Regular.woff2") format("woff2"), src:
url("/assets/JetBrainsMono-Regular.woff2") format("woff2"),
url("/assets/JetBrainsMono-Regular.ttf") format("truetype"); url("/assets/JetBrainsMono-Regular.ttf") format("truetype");
} }
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
background-color: #FAFAFA; background-color: #fafafa;
color: #424242; color: #424242;
font-family: "Jetbrains Mono", helvetica, arial, sans-serif; font-family: "Jetbrains Mono", helvetica, arial, sans-serif;
} }
@ -21,13 +22,13 @@ main {
main > span { main > span {
font-size: 15vw; /* Browsers that don't support min() */ font-size: 15vw; /* Browsers that don't support min() */
font-size: min(15vw, 50vh); font-size: min(15vw, 50vh);
color: #9E9E9E; color: #9e9e9e;
} }
main > span.highlighted { main > span.highlighted {
color: #424242; color: #424242;
} }
main > span.red { main > span.red {
color: #F4511E; color: #f4511e;
} }
.progress { .progress {
position: absolute; position: absolute;
@ -37,28 +38,20 @@ main > span.red {
width: 0; width: 0;
} }
#progress-bg { #progress-bg {
background-color: #9E9E9E; background-color: #9e9e9e;
} }
#progress-fg { #progress-fg {
background-color: #43A047; background-color: #43a047;
animation-timing-function: linear; width: 100%;
animation-fill-mode: forwards; transform: scaleX(0);
} transform-origin: center left;
@keyframes progress {
from {
width: 0;
}
to {
width: 100vw;
}
} }
/* FontAwesome */ /* FontAwesome */
i { i {
font-size: 12px; font-size: 12px;
color: #9E9E9E; color: #9e9e9e;
} }
i:hover { i:hover {
cursor: pointer; cursor: pointer;
color: #424242; color: #424242;
} }