feat: add hero image processing and API for serving variants
- Introduced a new script `prepare-hero-variants.mjs` to generate responsive hero image variants in WebP format. - Added a fallback image `hero_fallback.png` for each variant. - Created an API endpoint `hero-images.get.js` to retrieve available hero image variants and their fallback images. - Implemented directory and file checks to ensure the existence of required images before serving.
This commit is contained in:
@@ -9,12 +9,13 @@
|
||||
<div class="absolute bottom-0 left-0 w-96 h-96 bg-gray-300/30 rounded-full blur-3xl" />
|
||||
<picture class="absolute inset-0 opacity-10">
|
||||
<source
|
||||
v-if="heroImage.mobileWebp && heroImage.desktopWebp"
|
||||
type="image/webp"
|
||||
srcset="/images/club_about_us_hero_960.webp 960w, /images/club_about_us_hero_1600.webp 1600w"
|
||||
:srcset="`${heroImage.mobileWebp} 960w, ${heroImage.desktopWebp} 1600w`"
|
||||
sizes="(max-width: 1024px) 960px, 1600px"
|
||||
>
|
||||
<img
|
||||
src="/images/club_about_us.png"
|
||||
:src="heroImage.fallback"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="w-full h-full object-cover"
|
||||
@@ -42,6 +43,80 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useFetch, useHead, useState } from '#imports'
|
||||
|
||||
function buildInlineFallback() {
|
||||
const svg = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#eef2f7" />
|
||||
<stop offset="100%" stop-color="#d8e0ea" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="1600" height="900" fill="url(#g)" />
|
||||
</svg>`
|
||||
|
||||
return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`
|
||||
}
|
||||
|
||||
const DEFAULT_HERO_IMAGE = {
|
||||
key: 'fallback',
|
||||
mobileWebp: '',
|
||||
desktopWebp: '',
|
||||
fallback: buildInlineFallback()
|
||||
}
|
||||
|
||||
const { data: heroImagesResponse } = await useFetch('/api/hero-images')
|
||||
|
||||
const heroVariants = computed(() => {
|
||||
const variants = heroImagesResponse.value?.variants
|
||||
return Array.isArray(variants) && variants.length ? variants : [DEFAULT_HERO_IMAGE]
|
||||
})
|
||||
|
||||
function pickRandomHeroImage(variants) {
|
||||
const list = Array.isArray(variants) && variants.length ? variants : [DEFAULT_HERO_IMAGE]
|
||||
const index = Math.floor(Math.random() * list.length)
|
||||
return list[index]
|
||||
}
|
||||
|
||||
const heroImageState = useState('home-hero-image', () => pickRandomHeroImage(heroVariants.value))
|
||||
if (!heroVariants.value.some((variant) => variant.key === heroImageState.value?.key)) {
|
||||
heroImageState.value = pickRandomHeroImage(heroVariants.value)
|
||||
}
|
||||
|
||||
const heroImage = computed(() => heroImageState.value)
|
||||
|
||||
const preloadLinks = computed(() => {
|
||||
const links = []
|
||||
if (heroImage.value.mobileWebp) {
|
||||
links.push({
|
||||
rel: 'preload',
|
||||
as: 'image',
|
||||
href: heroImage.value.mobileWebp,
|
||||
type: 'image/webp',
|
||||
media: '(max-width: 1024px)'
|
||||
})
|
||||
}
|
||||
|
||||
if (heroImage.value.desktopWebp) {
|
||||
links.push({
|
||||
rel: 'preload',
|
||||
as: 'image',
|
||||
href: heroImage.value.desktopWebp,
|
||||
type: 'image/webp',
|
||||
media: '(min-width: 1025px)'
|
||||
})
|
||||
}
|
||||
|
||||
return links
|
||||
})
|
||||
|
||||
useHead(() => ({
|
||||
link: preloadLinks.value
|
||||
}))
|
||||
|
||||
const foundingYear = 1954
|
||||
const yearsSinceFounding = new Date().getFullYear() - foundingYear
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user