diff --git a/js/historyView.js b/js/historyView.js index 3e052fa4..144ed5c0 100644 --- a/js/historyView.js +++ b/js/historyView.js @@ -1241,6 +1241,8 @@ export function buildHistoryCard({ item, video, profile, variant }) { : FALLBACK_AVATAR; avatarImg.src = avatarSrc; avatarImg.alt = profile?.name || "Avatar"; + avatarImg.loading = "lazy"; + avatarImg.decoding = "async"; avatarBtn.appendChild(avatarImg); const authorNameBtn = document.createElement("button"); diff --git a/js/searchView.js b/js/searchView.js index 0a9e92a9..4cac8b7a 100644 --- a/js/searchView.js +++ b/js/searchView.js @@ -127,6 +127,7 @@ function renderProfileCards(profiles, container) { img.alt = name; img.className = "w-full h-full object-cover"; img.loading = "lazy"; + img.decoding = "async"; img.addEventListener("error", (e) => { e.target.src = FALLBACK_AVATAR; }); diff --git a/js/ui/profileModal/ProfileDirectMessageRenderer.js b/js/ui/profileModal/ProfileDirectMessageRenderer.js index 8075a402..2a905663 100644 --- a/js/ui/profileModal/ProfileDirectMessageRenderer.js +++ b/js/ui/profileModal/ProfileDirectMessageRenderer.js @@ -390,6 +390,8 @@ export class ProfileDirectMessageRenderer { img.src = result.objectUrl; img.alt = attachment.name || "Attachment preview"; img.className = "h-40 w-full rounded-lg object-cover"; + img.loading = "lazy"; + img.decoding = "async"; attachmentCard.appendChild(img); } else if (attachment.type?.startsWith("video/")) { const video = document.createElement("video"); @@ -884,6 +886,8 @@ export class ProfileDirectMessageRenderer { avatarImg.className = "h-full w-full object-cover"; avatarImg.src = avatarUrl; avatarImg.alt = `${safeName} avatar`; + avatarImg.loading = "lazy"; + avatarImg.decoding = "async"; avatarWrapper.appendChild(avatarImg); const textStack = document.createElement("div"); diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index 7ba903ae..549036ae 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -1471,6 +1471,8 @@ export class ProfileModalController { img.src = entry.previewUrl; img.alt = entry.name || "Attachment preview"; img.className = "h-24 w-24 rounded-lg object-cover"; + img.loading = "lazy"; + img.decoding = "async"; item.appendChild(img); } @@ -2892,6 +2894,8 @@ export class ProfileModalController { ? formatNpub(meta.npub) || DEFAULT_SAVED_PROFILE_LABEL : DEFAULT_SAVED_PROFILE_LABEL); avatarImg.alt = `${cardDisplayName} avatar`; + avatarImg.loading = "lazy"; + avatarImg.decoding = "async"; avatarSpan.appendChild(avatarImg); const metaSpan = document.createElement("div"); diff --git a/js/ui/subscriptionHistoryController.js b/js/ui/subscriptionHistoryController.js index 9eb08a72..bbc91e49 100644 --- a/js/ui/subscriptionHistoryController.js +++ b/js/ui/subscriptionHistoryController.js @@ -255,6 +255,8 @@ export class SubscriptionHistoryController { const img = document.createElement("img"); img.src = avatar; + img.loading = "lazy"; + img.decoding = "async"; img.className = "w-8 h-8 rounded-full object-cover bg-surface-2"; img.onerror = function () { this.src = "assets/svg/default-profile.svg"; diff --git a/scripts/measure_lazy_loading.mjs b/scripts/measure_lazy_loading.mjs new file mode 100644 index 00000000..9ae8a486 --- /dev/null +++ b/scripts/measure_lazy_loading.mjs @@ -0,0 +1,49 @@ +import fs from 'fs'; +import path from 'path'; + +const filesToCheck = [ + 'js/historyView.js', + 'js/ui/subscriptionHistoryController.js', + 'js/searchView.js', + 'js/ui/profileModal/ProfileDirectMessageRenderer.js', + 'js/ui/profileModalController.js' +]; + +let totalImages = 0; +let missingLazy = 0; +let missingAsync = 0; + +filesToCheck.forEach(file => { + const content = fs.readFileSync(file, 'utf8'); + const lines = content.split('\n'); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.includes('createElement("img")') || line.includes("createElement('img')")) { + totalImages++; + + // simple heuristic: look ahead next 20 lines for attributes + let chunk = lines.slice(i, i + 20).join('\n'); + + // Check for variable name + const match = line.match(/(?:const|let|var)\s+(\w+)\s*=/); + const varName = match ? match[1] : null; + + if (varName) { + if (!chunk.includes(`${varName}.loading = "lazy"`)) { + missingLazy++; + console.log(`[${file}] Missing loading="lazy" for ${varName} at line ${i+1}`); + } + if (!chunk.includes(`${varName}.decoding = "async"`)) { + missingAsync++; + console.log(`[${file}] Missing decoding="async" for ${varName} at line ${i+1}`); + } + } + } + } +}); + +console.log('--- Results ---'); +console.log(`Total images found: ${totalImages}`); +console.log(`Missing loading="lazy": ${missingLazy}`); +console.log(`Missing decoding="async": ${missingAsync}`);