Optimize hero image loading
This commit is contained in:
75
src/App.vue
75
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) => {
|
||||
>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user