If you want a good UX webworkers are your friend.
Look at this code
worker.js:
// worker.js
self.onmessage = async (event) => {
const { action, count } = event.data;
if (action === 'fetchUsers') {
try {
// Fetch a large batch of users
const response = await fetch(`https://randomuser.me/api/?results=${count}`);
if (!response.ok) throw new Error('API fetch failed');
const data = await response.json();
// Transform data here to keep the main thread fast
// We only send the properties we actually need
const processedUsers = data.results.map(user => ({
id: user.login.uuid,
name: `${user.name.first} ${user.name.last}`,
email: user.email,
picture: user.picture.large, // Large image for quality
location: `${user.location.city}, ${user.location.country}`
}));
// Send back the clean data
self.postMessage({ status: 'success', users: processedUsers });
} catch (error) {
self.postMessage({ status: 'error', message: error.message });
}
}
};
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Optimized User Fetcher</title>
<style>
:root {
--card-bg: #ffffff;
--bg-color: #f0f2f5;
--text-main: #1c1e21;
}
body {
font-family: 'Inter', system-ui, sans-serif;
background-color: var(--bg-color);
margin: 0; padding: 20px; color: var(--text-main);
}
header { text-align: center; margin-bottom: 30px; }
.controls { text-align: center; margin-bottom: 40px; }
button {
background: #007bff; color: white; border: none;
padding: 12px 24px; border-radius: 8px; font-size: 16px;
cursor: pointer; transition: background 0.2s;
}
button:hover { background: #0056b3; }
/* The Grid */
#user-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px; max-width: 1200px; margin: 0 auto;
}
/* The Card */
.user-card {
background: var(--card-bg);
border-radius: 12px;
padding: 20px;
text-align: center;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
transition: transform 0.2s;
}
.user-card:hover { transform: translateY(-5px); }
/* Image Optimization Styles */
.img-container {
width: 200px; height: 200px;
margin: 0 auto 15px;
border-radius: 50%;
background: #e1e4e8; /* Skeleton placeholder color */
overflow: hidden;
}
.user-card img {
width: 100%; height: 100%;
object-fit: cover;
opacity: 0; /* Hidden initially for fade-in */
transition: opacity 0.6s ease-in-out;
}
.user-card img.loaded {
opacity: 1; /* Fade in once loaded */
}
.user-card h3 { margin: 10px 0 5px; font-size: 1.2rem; }
.user-card p { color: #65676b; font-size: 0.9rem; margin: 4px 0; }
#status { font-weight: bold; margin-left: 10px; color: #007bff; }
</style>
</head>
<body>
<header>
<h1>Network Optimized User Directory</h1>
<p>Using Web Workers + Native Lazy Loading + Intersection Observer</p>
</header>
<div class="controls">
<button id="fetchBtn">Fetch 100 Users</button>
<span id="status"></span>
</div>
<div id="user-grid"></div>
<script>
const fetchBtn = document.getElementById('fetchBtn');
const userGrid = document.getElementById('user-grid');
const status = document.getElementById('status');
// 1. Initialize Web Worker
const worker = new Worker('worker.js');
fetchBtn.addEventListener('click', () => {
status.innerText = "Worker fetching...";
userGrid.innerHTML = ""; // Clear existing
// 2. Request 100 users from the worker
worker.postMessage({ action: 'fetchUsers', count: 300 });
});
// 3. Handle data returned from Worker
worker.onmessage = (event) => {
const { status: resStatus, users, message } = event.data;
if (resStatus === 'success') {
status.innerText = "Complete!";
renderUsers(users);
} else {
status.innerText = "Error: " + message;
}
};
function renderUsers(users) {
const fragment = document.createDocumentFragment();
users.forEach(user => {
const card = document.createElement('div');
card.className = 'user-card';
// Note: loading="lazy" tells the browser to only fetch the image
// when it's near the viewport.
card.innerHTML = `
<div class="img-container">
<img
src="${user.picture}"
alt="${user.name}"
loading="lazy"
class="lazy-img"
>
</div>
<h3>${user.name}</h3>
<p>${user.email}</p>
<p><strong>${user.location}</strong></p>
`;
fragment.appendChild(card);
});
userGrid.appendChild(fragment);
// 4. Setup Intersection Observer for the "Fade-in" effect
initFadeInObserver();
}
function initFadeInObserver() {
const images = document.querySelectorAll('.lazy-img');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
// Add 'loaded' class when image is visible
img.onload = () => img.classList.add('loaded');
// If cached, it might already be loaded
if (img.complete) img.dispatchEvent(new Event('load'));
observer.unobserve(img);
}
});
}, { threshold: 0.1 });
images.forEach(img => observer.observe(img));
}
(function () { fetchBtn.click() })(); // Auto-fetch on load
</script>
</body>
</html>
Top comments (0)