From 64a81f90145d352829e6e88eab0d5ab3795574a2 Mon Sep 17 00:00:00 2001 From: Dorian Date: Fri, 15 May 2026 12:27:39 -0500 Subject: [PATCH] Optimize hero image loading --- src/App.vue | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/App.vue b/src/App.vue index 1d7cb1c..17f9c74 100644 --- a/src/App.vue +++ b/src/App.vue @@ -132,6 +132,7 @@ const covenantItems = [ 'I agree not to represent L484 or its activities as open to the public or as a commercial entity.', ] const activeBackground = ref(0) +const loadedHeroBackgroundIndexes = ref(new Set([0])) const activeFacilityBackground = ref(0) const hasRotatingBackgrounds = computed(() => heroBackgrounds.length > 1) const isSignupOpen = ref(false) @@ -233,6 +234,8 @@ let adminToastTimer let parallaxFrame = 0 let adminEvents = null let adminEventsReconnectTimer = 0 +let isHeroRotationPreloading = false +const heroBackgroundPreloads = new Map() const cloneContent = (content) => JSON.parse(JSON.stringify(content)) const homepageContent = computed(() => siteContent.value.homepage || defaultSiteContent.homepage) @@ -399,6 +402,11 @@ const membershipBtcText = computed(() => const bitcoinUsdText = computed(() => `${formatUsd(bitcoinUsdPrice.value)} ${bitcoinPriceIsLive.value ? 'live' : 'est.'}`, ) +const heroBackgroundEntries = computed(() => + heroBackgrounds + .map((background, index) => ({ background, index })) + .filter(({ index }) => loadedHeroBackgroundIndexes.value.has(index)), +) const sanitizeText = (value, maxLength) => String(value ?? '') @@ -1957,6 +1965,55 @@ const requestHeroParallax = () => { parallaxFrame = window.requestAnimationFrame(syncHeroParallax) } +const markHeroBackgroundLoaded = (index) => { + if (loadedHeroBackgroundIndexes.value.has(index)) return + const next = new Set(loadedHeroBackgroundIndexes.value) + next.add(index) + loadedHeroBackgroundIndexes.value = next +} + +const preloadHeroBackground = (index) => { + const background = heroBackgrounds[index] + if (!background || loadedHeroBackgroundIndexes.value.has(index)) return Promise.resolve() + if (heroBackgroundPreloads.has(index)) return heroBackgroundPreloads.get(index) + + const preload = new Promise((resolve) => { + const img = new Image() + const done = () => { + markHeroBackgroundLoaded(index) + heroBackgroundPreloads.delete(index) + resolve() + } + img.decoding = 'async' + img.onload = () => { + if (img.decode) img.decode().catch(() => {}).finally(done) + else done() + } + img.onerror = () => { + heroBackgroundPreloads.delete(index) + resolve() + } + img.src = background + }) + heroBackgroundPreloads.set(index, preload) + return preload +} + +const scheduleHeroBackgroundPreloads = () => { + if (heroBackgrounds.length < 2) return + preloadHeroBackground(1) + const preloadRemaining = () => { + heroBackgrounds.forEach((_, index) => { + if (index > 1) preloadHeroBackground(index) + }) + } + if ('requestIdleCallback' in window) { + window.requestIdleCallback(preloadRemaining, { timeout: 4500 }) + } else { + window.setTimeout(preloadRemaining, 1800) + } +} + const selectFacility = (backgroundIndex) => { if (!facilityBackgrounds[backgroundIndex]) return activeFacilityBackground.value = backgroundIndex @@ -2000,9 +2057,18 @@ onMounted(async () => { window.addEventListener('scroll', requestHeroParallax, { passive: true }) window.addEventListener('resize', requestHeroParallax) syncHeroParallax() + scheduleHeroBackgroundPreloads() if (hasRotatingBackgrounds.value) { backgroundTimer = window.setInterval(() => { - activeBackground.value = (activeBackground.value + 1) % heroBackgrounds.length + if (isHeroRotationPreloading) return + const nextBackground = (activeBackground.value + 1) % heroBackgrounds.length + isHeroRotationPreloading = true + preloadHeroBackground(nextBackground).then(() => { + activeBackground.value = nextBackground + preloadHeroBackground((nextBackground + 1) % heroBackgrounds.length) + }).finally(() => { + isHeroRotationPreloading = false + }) }, 6500) } }) @@ -2203,11 +2269,14 @@ watch(mobileMenuOpen, (open) => { >
@@ -2278,6 +2347,8 @@ watch(mobileMenuOpen, (open) => { class="facilities-bg" :class="{ 'is-active': index === activeFacilityBackground }" :src="background" + loading="lazy" + decoding="async" alt="" />