Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: nuxtpicture placeholder #1396

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
12 changes: 12 additions & 0 deletions playground/pages/picture.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div>
<h1>Original</h1>
<nuxt-picture
src="/images/colors.jpg"
format="avif,webp"
Expand All @@ -8,11 +9,22 @@
@load="isLoaded = true"
/>
<p>Received onLoad event: {{ isLoaded }}</p>
<h1>Placeholder</h1>
<nuxt-picture
src="/images/colors.jpg"
placeholder
format="avif,webp"
width="500"
height="500"
@load="isLoaded2 = true"
/>
<p>Received onLoad event: {{ isLoaded2 }}</p>
</div>
</template>

<script setup lang="ts">
import { ref } from '#imports'

const isLoaded = ref(false)
const isLoaded2 = ref(false)
</script>
54 changes: 50 additions & 4 deletions src/runtime/components/nuxt-picture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const pictureProps = {
...baseImageProps,
legacyFormat: { type: String, default: null },
imgAttrs: { type: Object, default: null },
placeholder: { type: [Boolean, String, Number, Array], default: undefined },
placeholderClass: { type: String, default: undefined },
}

export default defineComponent({
Expand All @@ -22,6 +24,7 @@ export default defineComponent({

const originalFormat = computed(() => getFileExtension(props.src))
const isTransparent = computed(() => ['png', 'webp', 'gif', 'svg'].includes(originalFormat.value))
const placeholderLoaded = ref(false)

const legacyFormat = computed(() => {
if (props.legacyFormat) {
Expand Down Expand Up @@ -58,6 +61,8 @@ export default defineComponent({
})
const lastSourceIndex = computed(() => sources.value.length - 1)

const mainSrc = computed(() => sources.value[lastSourceIndex.value])

if (props.preload) {
const link: NonNullable<Head['link']>[number] = {
rel: 'preload',
Expand All @@ -84,6 +89,31 @@ export default defineComponent({
}
}

const placeholder = computed(() => {
let placeholder = props.placeholder
if (placeholder === '') {
placeholder = true
}
if (!placeholder || placeholderLoaded.value) {
return false
}
if (typeof placeholder === 'string') {
return placeholder
}

const size = (Array.isArray(placeholder)
? placeholder
: (typeof placeholder === 'number' ? [placeholder, placeholder] : [10, 10])) as [w: number, h: number, q: number, b: number]

return $img(props.src!, {
..._base.modifiers.value,
width: size[0],
height: size[1],
quality: size[2] || 50,
blur: size[3] || 3,
}, _base.options.value)
})

const imgEl = ref<HTMLImageElement>()

// Prerender static images
Expand All @@ -96,6 +126,22 @@ export default defineComponent({
const nuxtApp = useNuxtApp()
const initialLoad = nuxtApp.isHydrating
onMounted(() => {
if (placeholder.value) {
const img = new Image()

if (mainSrc.value.src) img.src = mainSrc.value.src
if (mainSrc.value.sizes) img.sizes = mainSrc.value.sizes
if (mainSrc.value.srcset) img.srcset = mainSrc.value.srcset

img.onload = (event) => {
placeholderLoaded.value = true
ctx.emit('load', event)
}

markFeatureUsage('nuxt-picture')
return
}

if (!imgEl.value) {
return
}
Expand All @@ -122,7 +168,7 @@ export default defineComponent({
h('picture', null, [
...sources.value.slice(0, -1).map((source) => {
return h('source', {
type: source.type,
type: placeholder.value ? 'display/never' : source.type,
sizes: source.sizes,
srcset: source.srcset,
})
Expand All @@ -132,9 +178,9 @@ export default defineComponent({
..._base.attrs.value,
...(import.meta.server ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {}),
...imgAttrs,
src: sources.value[lastSourceIndex.value].src,
sizes: sources.value[lastSourceIndex.value].sizes,
srcset: sources.value[lastSourceIndex.value].srcset,
src: placeholder.value ? placeholder.value : sources.value[lastSourceIndex.value].src,
sizes: placeholder.value ? undefined : sources.value[lastSourceIndex.value].sizes,
srcset: placeholder.value ? undefined : sources.value[lastSourceIndex.value].srcset,
}),
])
},
Expand Down