Skeleton Product Cards Loading
How to Create a Skeleton Loading for Product Cards with Vanilla JavaScript
Watch the full tutorial here:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./app.js" defer></script>
<link rel="stylesheet" href="./style.css">
<title>Document</title>
</head>
<body>
<div id="product-card-container">
<div class="product-card-item">
<div class="img-container skeleton">
<img width="100"
src="https://assets.adidas.com/images/w_383,h_383,f_auto,q_auto,fl_lossy,c_fill,g_auto/bc1e0a82cc724171b06b712bfb03cb1f_9366/gazelle-indoor-shoes.jpg">
</div>
<ul>
<li class="skeleton">Gazelle Indoor Shoes</li>
<li class="skeleton">$120.00</li>
</ul>
<button>Add to cart</button>
</div>
<div class="product-card-item">
<div class="img-container skeleton">
<img width="100"
src="https://assets.adidas.com/images/h_840,f_auto,q_auto,fl_lossy,c_fill,g_auto/a046f90f47c042d69294a97601139ffc_9366/Handball_Spezial_Shoes_Blue_BD7632_02_standard.jpg">
</div>
<ul>
<li class="skeleton">Handball Spezial Shoes</li>
<li class="skeleton">$110.00</li>
</ul>
<button>Add to cart</button>
</div>
<div class="product-card-item">
<div class="img-container skeleton">
<img width="100"
src="https://assets.adidas.com/images/h_2000,f_auto,q_auto,fl_lossy,c_fill,g_auto/8f0511c4303f43adaff26bdf3ec78806_9366/Handball_Spezial_Shoes_Green_JH5444_01_00_standard.jpg">
</div>
<ul>
<li class="skeleton">Gazelle Indoor Shoes</li>
<li class="skeleton">$120.00</li>
</ul>
<button>Add to cart</button>
</div>
<div class="product-card-item">
<div class="img-container skeleton">
<img width="100"
src="https://assets.adidas.com/images/h_840,f_auto,q_auto,fl_lossy,c_fill,g_auto/51fa97b24eb5404b809c29a39a87fca4_9366/Sambae_Shoes_White_JI1349_01_standard.jpg">
</div>
<ul>
<li class="skeleton">Sambae Shoes Price</li>
<li class="skeleton">$110.00</li>
</ul>
<button>Add to cart</button>
</div>
<div class="product-card-item">
<div class="img-container skeleton">
<img width="100"
src="https://assets.adidas.com/images/h_2000,f_auto,q_auto,fl_lossy,c_fill,g_auto/8bd96d0fe05a48b0800b8170a096c8f9_9366/Adizero_Prime_X_2.0_STRUNG_Shoes_Green_IH5683_HM1.jpg">
</div>
<ul>
<li class="skeleton">Adizero Prime X 2.0 STRUNG Shoes</li>
<li class="skeleton">$300.00</li>
</ul>
<button>Add to cart</button>
</div>
</div>
</body>
</html>
css
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
list-style: none;
}
#product-card-container {
width: 100%;
display: flex;
flex-direction: row;
gap: 10px;
flex-wrap: wrap;
justify-content: space-evenly;
padding: 10px;
& .product-card-item {
width: 250px;
display: flex;
flex-direction: column;
gap: 10px;
& .img-container {
height: 254px;
border-radius: 10px;
overflow: hidden;
& img {
width: 100%;
}
}
& ul {
display: flex;
flex-direction: column;
gap: 10px;
& li:nth-child(1) {
font-size: 14px;
}
& .skeleton {
overflow: hidden;
border-radius: 5px;
}
}
& button {
padding: 10px;
cursor: pointer;
}
}
}
.skeleton {
position: relative;
transition: opacity 0.90s, visibility 0.90s;
&::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 10;
background: linear-gradient(90deg, #eee, #f9f9f9, #eee);
background-size: 200%;
animation: skeleton-animate 1s infinite reverse;
}
}
@keyframes skeleton-animate {
0% {
background-position: -100% 0;
}
100% {
background-position: 100% 0;
}
}
javascript
window.addEventListener('load', () => {
const images = document.querySelectorAll('.img-container img');
images.forEach(img => {
if (img.complete) {
removeSkeleton(img);
} else {
img.addEventListener('load', () => removeSkeleton(img));
img.addEventListener('error', () => removeSkeleton(img));
}
});
const skeletonTexts = document.querySelectorAll('li.skeleton');
skeletonTexts.forEach(el => el.classList.remove('skeleton'));
});
function removeSkeleton(img) {
const container = img.closest('.img-container');
if (container && container.classList.contains('skeleton')) {
container.classList.remove('skeleton');
}
}