<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script> <script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script> <div class="header"> <h1>Lazy Loading on Scroll</h1> <p class="subtitle">Images and content load automatically as you scroll down</p> <div class="stats"> <div class="stat"> <ion-icon name="images-outline"></ion-icon> <span id="loadedCount">0</span> Loaded </div> <div class="stat"> <ion-icon name="time-outline"></ion-icon> <span id="totalTime">0ms</span> Total Time </div> </div> </div> <div class="container"> <div class="grid" id="grid"></div> <div class="loader" id="loader"> <div class="spinner"></div> </div> <div class="end-message" id="endMessage"> <ion-icon name="checkmark-circle-outline"></ion-icon> <p>All content loaded!</p> </div> </div>
* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background: #f5f7fa; color: #2c3e50; } .header { background: white; padding: 30px 20px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1); position: sticky; top: 0; z-index: 100; } h1 { font-size: 32px; color: #1a202c; margin-bottom: 10px; } .subtitle { color: #64748b; font-size: 16px; } .stats { display: flex; gap: 20px; justify-content: center; margin-top: 20px; flex-wrap: wrap; } .stat { background: #eff6ff; padding: 12px 24px; border-radius: 8px; display: flex; align-items: center; gap: 8px; color: #3b82f6; font-weight: 600; } .container { max-width: 1200px; margin: 40px auto; padding: 0 20px; } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 24px; margin-bottom: 40px; } .card { background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: all 0.3s; opacity: 0; transform: translateY(30px); } .card.loaded { opacity: 1; transform: translateY(0); } .card:hover { transform: translateY(-5px); box-shadow: 0 8px 20px rgba(0,0,0,0.15); } .card-image { width: 100%; height: 200px; background: #e2e8f0; position: relative; overflow: hidden; } .card-image img { width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 0.5s; } .card-image img.loaded { opacity: 1; } .skeleton { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient( 90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75% ); background-size: 200% 100%; animation: loading 1.5s infinite; } @keyframes loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } .card-content { padding: 20px; } .card-title { font-size: 18px; font-weight: 600; margin-bottom: 8px; color: #1a202c; } .card-description { color: #64748b; font-size: 14px; line-height: 1.6; margin-bottom: 12px; } .card-footer { display: flex; align-items: center; justify-content: space-between; padding-top: 12px; border-top: 1px solid #e2e8f0; } .card-tag { display: inline-flex; align-items: center; gap: 4px; padding: 4px 12px; background: #f0f9ff; color: #0284c7; border-radius: 6px; font-size: 12px; font-weight: 500; } .card-date { color: #94a3b8; font-size: 12px; } .loader { display: flex; justify-content: center; align-items: center; padding: 40px; opacity: 0; transition: opacity 0.3s; } .loader.active { opacity: 1; } .spinner { width: 40px; height: 40px; border: 4px solid #e2e8f0; border-top-color: #3b82f6; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .end-message { text-align: center; padding: 40px; color: #64748b; font-size: 16px; display: none; } .end-message.active { display: block; } .end-message ion-icon { font-size: 48px; color: #3b82f6; margin-bottom: 16px; } @media (max-width: 768px) { .grid { grid-template-columns: 1fr; } h1 { font-size: 24px; } }
const grid = document.getElementById('grid'); const loader = document.getElementById('loader'); const endMessage = document.getElementById('endMessage'); const loadedCount = document.getElementById('loadedCount'); const totalTime = document.getElementById('totalTime'); let currentPage = 0; let isLoading = false; let hasMore = true; let totalLoaded = 0; let startTime = Date.now(); const itemsPerPage = 9; const totalItems = 50; // Generate placeholder images using picsum.photos function generateItems(page) { const items = []; const startIndex = page * itemsPerPage; for (let i = 0; i < itemsPerPage && startIndex + i < totalItems; i++) { const index = startIndex + i; items.push({ id: index + 1, title: `Card ${index + 1}`, description: 'This content is lazy loaded as you scroll. Images use Intersection Observer API for optimal performance.', image: `https://picsum.photos/400/300?random=${index + 1}`, tag: ['Design', 'Development', 'Marketing', 'Business'][index % 4], date: new Date(Date.now() - index * 86400000).toLocaleDateString() }); } return items; } function createCard(item) { const card = document.createElement('div'); card.className = 'card'; card.innerHTML = ` <div class="card-image"> <div class="skeleton"></div> <img data-src="${item.image}" alt="${item.title}"> </div> <div class="card-content"> <h3 class="card-title">${item.title}</h3> <p class="card-description">${item.description}</p> <div class="card-footer"> <span class="card-tag"> <ion-icon name="pricetag-outline"></ion-icon> ${item.tag} </span> <span class="card-date">${item.date}</span> </div> </div> `; return card; } // Intersection Observer for images const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; const src = img.getAttribute('data-src'); img.src = src; img.onload = () => { img.classList.add('loaded'); const skeleton = img.previousElementSibling; if (skeleton) { setTimeout(() => skeleton.remove(), 500); } }; imageObserver.unobserve(img); } }); }, { rootMargin: '50px' }); // Intersection Observer for cards const cardObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { setTimeout(() => { entry.target.classList.add('loaded'); }, 100); cardObserver.unobserve(entry.target); } }); }, { threshold: 0.1 }); function loadMoreItems() { if (isLoading || !hasMore) return; isLoading = true; loader.classList.add('active'); // Simulate network delay setTimeout(() => { const items = generateItems(currentPage); if (items.length === 0 || (currentPage + 1) * itemsPerPage >= totalItems) { hasMore = false; loader.classList.remove('active'); endMessage.classList.add('active'); return; } items.forEach(item => { const card = createCard(item); grid.appendChild(card); // Observe card for animation cardObserver.observe(card); // Observe image for lazy loading const img = card.querySelector('img'); imageObserver.observe(img); totalLoaded++; }); loadedCount.textContent = totalLoaded; const elapsed = Date.now() - startTime; totalTime.textContent = elapsed + 'ms'; currentPage++; isLoading = false; loader.classList.remove('active'); }, 800); } // Scroll event listener function handleScroll() { const scrollHeight = document.documentElement.scrollHeight; const scrollTop = document.documentElement.scrollTop; const clientHeight = document.documentElement.clientHeight; if (scrollTop + clientHeight >= scrollHeight - 500) { loadMoreItems(); } } window.addEventListener('scroll', handleScroll); // Initial load loadMoreItems();
Login to leave a comment
No comments yet. Be the first!
View Project
No comments yet. Be the first!