From 7f194af7eacbad56a41116f1c50eb7bcdb6e9c26 Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Tue, 2 Jul 2024 19:18:05 -0400 Subject: [PATCH 1/7] fix: nuxtpicture placeholder --- playground/pages/picture.vue | 12 ++++++ src/runtime/components/nuxt-picture.ts | 54 ++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/playground/pages/picture.vue b/playground/pages/picture.vue index 895faec5c..05a05a9ea 100644 --- a/playground/pages/picture.vue +++ b/playground/pages/picture.vue @@ -1,5 +1,6 @@ @@ -15,4 +26,5 @@ import { ref } from '#imports' const isLoaded = ref(false) +const isLoaded2 = ref(false) diff --git a/src/runtime/components/nuxt-picture.ts b/src/runtime/components/nuxt-picture.ts index 4cbce2ede..4752fa552 100644 --- a/src/runtime/components/nuxt-picture.ts +++ b/src/runtime/components/nuxt-picture.ts @@ -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({ @@ -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) { @@ -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[number] = { rel: 'preload', @@ -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() // Prerender static images @@ -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 } @@ -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, }) @@ -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, }), ]) }, From cc740d069ffc4dedf1befd2c7d48c2c09005f626 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 27 Aug 2024 12:29:38 +0100 Subject: [PATCH 2/7] fix: handle undefined source --- src/runtime/components/NuxtPicture.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/runtime/components/NuxtPicture.vue b/src/runtime/components/NuxtPicture.vue index c2aafe4b7..4bbeea3df 100644 --- a/src/runtime/components/NuxtPicture.vue +++ b/src/runtime/components/NuxtPicture.vue @@ -15,9 +15,9 @@ ...(isServer ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {}), ...imgAttrs, }" - :src="placeholder ? placeholder : sources[lastSourceIndex].src" - :sizes="placeholder ? undefined : sources[lastSourceIndex].sizes" - :srcset="placeholder ? undefined : sources[lastSourceIndex].srcset" + :src="placeholder ? placeholder : sources[lastSourceIndex]?.src" + :sizes="placeholder ? undefined : sources[lastSourceIndex]?.sizes" + :srcset="placeholder ? undefined : sources[lastSourceIndex]?.srcset" > From a1ee297040131a4158f3a81673c0a0c4d4985de7 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 27 Aug 2024 12:37:44 +0100 Subject: [PATCH 3/7] refactor: move placeholder behaviour into useBaseImage --- src/runtime/components/NuxtImg.vue | 41 ++++-------------------- src/runtime/components/NuxtPicture.vue | 28 +--------------- src/runtime/components/_base.ts | 44 ++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 69 deletions(-) diff --git a/src/runtime/components/NuxtImg.vue b/src/runtime/components/NuxtImg.vue index 31cd40c75..35ef0278e 100644 --- a/src/runtime/components/NuxtImg.vue +++ b/src/runtime/components/NuxtImg.vue @@ -34,30 +34,29 @@ const isServer = import.meta.server const $img = useImage() -const _base = useBaseImage(props) +const { placeholder, placeholderLoaded, options: baseOptions, modifiers: baseModifiers, attrs: baseAttrs } = useBaseImage(props) -const placeholderLoaded = ref(false) const imgEl = ref() -type AttrsT = typeof _base.attrs.value & { +type AttrsT = typeof baseAttrs.value & { 'sizes'?: string 'srcset'?: string 'data-nuxt-img'?: string } const sizes = computed(() => $img.getSizes(props.src!, { - ..._base.options.value, + ...baseOptions.value, sizes: props.sizes, densities: props.densities, modifiers: { - ..._base.modifiers.value, + ...baseModifiers.value, width: parseSize(props.width), height: parseSize(props.height), }, })) const _attrs = computed(() => { - const attrs: AttrsT = { ..._base.attrs.value, 'data-nuxt-img': '' } + const attrs: AttrsT = { ...baseAttrs.value, 'data-nuxt-img': '' } if (!props.placeholder || placeholderLoaded.value) { attrs.sizes = sizes.value.sizes @@ -67,38 +66,10 @@ const _attrs = computed(() => { return attrs }) -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 mainSrc = computed(() => props.sizes ? sizes.value.src - : $img(props.src!, _base.modifiers.value, _base.options.value), + : $img(props.src!, baseModifiers.value, baseOptions.value), ) const src = computed(() => placeholder.value ? placeholder.value : mainSrc.value) diff --git a/src/runtime/components/NuxtPicture.vue b/src/runtime/components/NuxtPicture.vue index 4bbeea3df..035659ffc 100644 --- a/src/runtime/components/NuxtPicture.vue +++ b/src/runtime/components/NuxtPicture.vue @@ -46,12 +46,11 @@ const isServer = import.meta.server const $img = useImage() -const { attrs: baseAttrs, options: baseOptions, modifiers: baseModifiers } = useBaseImage(props) +const { placeholder, placeholderLoaded, attrs: baseAttrs, options: baseOptions, modifiers: baseModifiers } = useBaseImage(props) 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) { @@ -120,31 +119,6 @@ for (const key in attrs) { } } -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!, { - ...baseModifiers.value, - width: size[0], - height: size[1], - quality: size[2] || 50, - blur: size[3] || 3, - }, baseOptions.value) -}) - const imgEl = ref() // Prerender static images diff --git a/src/runtime/components/_base.ts b/src/runtime/components/_base.ts index fb72e71f2..b6563d40e 100644 --- a/src/runtime/components/_base.ts +++ b/src/runtime/components/_base.ts @@ -53,6 +53,10 @@ export const baseImageProps = { // csp nonce: { type: [String], default: undefined }, + + // placeholders + placeholder: { type: [Boolean, String, Number, Array], default: undefined }, + placeholderClass: { type: String, default: undefined }, } export interface BaseImageAttrs { @@ -117,7 +121,39 @@ export const useBaseImage = (props: ExtractPropTypes) => } }) + const placeholderLoaded = ref(false) + + 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!, { + ...modifiers.value, + width: size[0], + height: size[1], + quality: size[2] || 50, + blur: size[3] || 3, + }, options.value) + }) + return { + placeholder, + placeholderLoaded, options, attrs, modifiers, @@ -128,12 +164,6 @@ 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 const imgProps = { - ...baseImageProps, - placeholder: { type: [Boolean, String, Number, Array], default: undefined }, - placeholderClass: { type: String, default: undefined }, -} +export const imgProps = baseImageProps From f30301e4b0503772f1a3c57709b302b30f4bc9bb Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 27 Aug 2024 13:22:59 +0100 Subject: [PATCH 4/7] fix: add placeholder class --- src/runtime/components/NuxtImg.vue | 6 +++--- src/runtime/components/NuxtPicture.vue | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/runtime/components/NuxtImg.vue b/src/runtime/components/NuxtImg.vue index 35ef0278e..64a4ff66e 100644 --- a/src/runtime/components/NuxtImg.vue +++ b/src/runtime/components/NuxtImg.vue @@ -1,11 +1,11 @@