I am creating a loading screen using an SVG path. there is a grey path to represent the unloaded bar, and a white path to represent the percentage loaded.
document.addEventListener("DOMContentLoaded", () => {
const loadingScreen = document.getElementById("loading-screen");
const loadingPercentage = document.getElementById("loading-percentage");
const progressPath = document.getElementById("progress-path");
let progress = 0; // Current progress
const minDisplayTime = 1750; // Minimum display time in milliseconds
const fadeDelay = 500; // Duration of fade-out animation (in ms)
const delayAfterComplete = 750; // Delay after hitting 100% (in ms)
const startTime = Date.now(); // Record when loading starts
const totalLength = 360; // Total length of the SVG path
// Set the initial stroke-dasharray and stroke-dashoffset for the white bar
progressPath.style.strokeDasharray = totalLength;
progressPath.style.strokeDashoffset = totalLength;
// Function to update progress
function updateProgress() {
const elapsedTime = Date.now() - startTime;
// Calculate the progress as a percentage based on time
if (elapsedTime < minDisplayTime) {
progress = Math.min((elapsedTime / minDisplayTime) * 100, 100);
} else {
progress = 100; // Ensure progress reaches 100% after minimum display time
}
// Update percentage text
loadingPercentage.textContent = `${Math.floor(progress)}%`;
// Update progress bar position
const offset = totalLength * (1 - progress / 100);
progressPath.style.strokeDashoffset = offset;
console.log(`Loading progress: ${progress}%`);
// Trigger fade-out when progress hits 100%
if (progress === 100) {
const remainingTime = minDisplayTime - elapsedTime; // Ensure minimum display time
setTimeout(() => {
// Add a 0.75-second delay after hitting 100% before starting fade-out
setTimeout(() => {
loadingScreen.style.opacity = "0"; // Trigger fade-out animation
// Completely hide the loading screen after the fade-out
setTimeout(() => {
loadingScreen.style.display = "none";
}, fadeDelay); // Match the CSS transition duration
}, delayAfterComplete); // 0.75-second delay after hitting 100%
}, Math.max(remainingTime, 0)); // Wait for any remaining time if necessary
} else {
// Continue updating progress until it reaches 100%
requestAnimationFrame(updateProgress);
}
}
// Start the progress update loop
updateProgress();
});
#loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #222;
/* Dark background */
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
/* Overlay all content */
overflow: hidden;
transition: opacity 0.5s ease;
/* Fade-out effect */
}
/* Logo and percentage container */
#loading-content {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
z-index: 1;
/* Above the loading bar */
}
/* Logo styling */
#ws-logo {
width: 15%;
max-width: 100px;
height: auto;
margin-bottom: 10px;
}
/* Percentage text styling */
#loading-percentage {
font-size: 1.5em;
color: #fff;
font-family: Arial, sans-serif;
}
/* SVG loading bar */
#loading-bar {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
/* Behind content */
padding: 0;
/* Ensure no internal spacing */
margin: 0;
/* Remove any external margin */
box-sizing: border-box;
/* Include border in dimensions */
}
#loading-bar path {
vector-effect: ;
}
#progress-path {
transition: stroke-dashoffset 0.2s ease-out;
/* Smooth animation for the progress bar */
}
<!-- Loading Screen -->
<div id="loading-screen">
<div id="loading-content">
<img id="ws-logo" src="ws-logo.png" alt="WS Logo">
<div id="loading-percentage">0%</div>
</div>
<svg id="loading-bar" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
<!-- Grey background path -->
<path
d="M5 5 L5 95 L95 95 L95 5 Z"
stroke="#666"
stroke-width="1"
stroke-linecap="square"
fill="none"
/>
<!-- White progress path -->
<path
id="progress-path"
d="M5 5 L5 95 L95 95 L95 5 Z"
stroke="#fff"
stroke-width="1"
stroke-linecap="square"
fill="none"
stroke-dasharray="0"
stroke-dashoffset="0"
/>
</svg>
</div>
here is a CodePen with what i've created: https://codepen.io/Ramon-117/pen/QwLrxvb
The concept: the loading bar will resemble a grey box border. As the site loads, the progress bar will grow white starting from the top right corner (0%), down to the bottom left corner (25%), to the bottom right corner (50%), up to the top right corner (75%), and back to the top left corner (100%).
The issue: I have everything working except for a key issue. If the viewheight is larger than the viewwidth, then then the top and bottom widths will be thicker than the left and right and vise-versa.
What I want: I want the stroke to have an even thickness throughout the path no matter the viewport size. I also want to be able to adjust said thickness to account for mobile devices.
What I've tried:
stroke-width="1" *doesnt work
stroke-width="1px" *doesnt work
stroke-width="1%" *doesnt work
stroke-width="1vmin" *doesnt work
vector-effect:non-scaling-stroke; *breaks the animation
vector-effect:non-scaling-stroke; AND vector-effect:non-rotation;
dynamically setting the width based on viewport with JavaScript (i dont have this code anymore)
You may simplify this loader by
replacing the <path>
with a <rect>
element as it supports relative units like %
.
This way we don't need a viewBox
or preserveAspectRatio
– the rect will adapt its dimensions to the parent HTM element.
Besides, you can simplify the dashoffset
calculations by applying the pathLength
attribute. This way you can use the percentage based progress
variable for the dynamic stroke-dashoffset
value.
document.addEventListener("DOMContentLoaded", () => {
const loadingScreen = document.getElementById("loading-screen");
const loadingPercentage = document.getElementById("loading-percentage");
const progressPath = document.getElementById("progress-path");
let progress = 0; // Current progress
const minDisplayTime = 1750; // Minimum display time in milliseconds
const fadeDelay = 500; // Duration of fade-out animation (in ms)
const delayAfterComplete = 750; // Delay after hitting 100% (in ms)
const startTime = Date.now(); // Record when loading starts
// Function to update progress
function updateProgress() {
const elapsedTime = Date.now() - startTime;
// Calculate the progress as a percentage based on time
if (elapsedTime < minDisplayTime) {
progress = Math.min((elapsedTime / minDisplayTime) * 100, 100);
} else {
progress = 100; // Ensure progress reaches 100% after minimum display time
}
// Update percentage text
loadingPercentage.textContent = `${Math.floor(progress)}%`;
// Update progress bar position
progressPath.style.strokeDashoffset = progress-100;
console.log(`Loading progress: ${progress}%`);
// Trigger fade-out when progress hits 100%
if (progress === 100) {
const remainingTime = minDisplayTime - elapsedTime; // Ensure minimum display time
setTimeout(() => {
// Add a 0.75-second delay after hitting 100% before starting fade-out
setTimeout(() => {
loadingScreen.style.opacity = "0"; // Trigger fade-out animation
// Completely hide the loading screen after the fade-out
setTimeout(() => {
loadingScreen.style.display = "none";
}, fadeDelay); // Match the CSS transition duration
}, delayAfterComplete); // 0.75-second delay after hitting 100%
}, Math.max(remainingTime, 0)); // Wait for any remaining time if necessary
} else {
// Continue updating progress until it reaches 100%
requestAnimationFrame(updateProgress);
}
}
// Start the progress update loop
updateProgress();
});
#loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #222;
/* Dark background */
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
/* Overlay all content */
overflow: hidden;
transition: opacity 0.5s ease;
/* Fade-out effect */
}
/* Logo and percentage container */
#loading-content {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
z-index: 1;
/* Above the loading bar */
}
/* Logo styling */
#ws-logo {
width: 15%;
max-width: 100px;
height: auto;
margin-bottom: 10px;
}
/* Percentage text styling */
#loading-percentage {
font-size: 1.5em;
color: #fff;
font-family: Arial, sans-serif;
}
/* SVG loading bar */
#loading-bar {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
/* Behind content */
padding: 0;
/* Ensure no internal spacing */
margin: 0;
/* Remove any external margin */
box-sizing: border-box;
/* Include border in dimensions */
}
#loading-bar path {
//vector-effect: ;
}
#progress-path {
transition: stroke-dashoffset 0.2s ease-out;
/* Smooth animation for the progress bar */
}
<!-- Loading Screen -->
<div id="loading-screen">
<div id="loading-content">
<div id="loading-percentage">0%</div>
</div>
<svg id="loading-bar" width="100%" height="100%" >
<!-- Grey background path -->
<rect width="100%" height="100%" stroke="#666" stroke-width="2%" stroke-linecap="square" fill="none" />
<rect id="progress-path" width="100%" height="100%" pathLength="100" stroke="#fff" stroke-width="2%" stroke-linecap="square" fill="none" stroke-dasharray="100 100" stroke-dashoffset="-100" />
</svg>
</div>