Optimize hero image loading

This commit is contained in:
Dorian
2026-05-15 12:27:39 -05:00
parent a981f13675
commit 64a81f9014

View File

@@ -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) => {
>
<div class="hero-bg-layer">
<img
v-for="(background, index) in heroBackgrounds"
v-for="{ background, index } in heroBackgroundEntries"
:key="background"
class="hero-bg absolute inset-0 h-full w-full object-cover"
:class="{ 'is-active': index === activeBackground }"
:src="background"
:loading="index === 0 ? 'eager' : 'lazy'"
:fetchpriority="index === 0 ? 'high' : 'low'"
decoding="async"
alt=""
aria-hidden="true"
/>
@@ -2278,6 +2347,8 @@ watch(mobileMenuOpen, (open) => {
class="facilities-bg"
:class="{ 'is-active': index === activeFacilityBackground }"
:src="background"
loading="lazy"
decoding="async"
alt=""
/>
</div>