diff --git a/src/runtime/components/NuxtImg.vue b/src/runtime/components/NuxtImg.vue
index 53ecad0eb..1868b9642 100644
--- a/src/runtime/components/NuxtImg.vue
+++ b/src/runtime/components/NuxtImg.vue
@@ -2,7 +2,7 @@
@@ -45,6 +45,7 @@ const emit = defineEmits<{
defineSlots<{ default(props: DefaultSlotProps): any }>()
const $img = useImage()
+
const { providerOptions, normalizedAttrs, imageModifiers } = useImageProps(props)
const sizes = computed(() => $img.getSizes(props.src!, {
@@ -56,43 +57,46 @@ const sizes = computed(() => $img.getSizes(props.src!, {
const placeholderLoaded = ref(false)
-const attrs = useAttrs() as ImgHTMLAttributes
-const imgAttrs = computed(() => ({
- ...normalizedAttrs.value,
- 'data-nuxt-img': '',
- ...(!props.placeholder || placeholderLoaded.value)
- ? { sizes: sizes.value.sizes, srcset: sizes.value.srcset }
- : {},
- ...import.meta.server ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {},
- ...attrs,
-}))
+const _placeholder = computed(
+ () => props.placeholder || (props.preset && $img.options.presets[props.preset]?.placeholder),
+)
+
+const _placeholderClass = computed(
+ () => props.placeholderClass || (props.preset && $img.options.presets[props.preset]?.placeholderClass),
+)
-const placeholder = computed(() => {
+const placeholderSrc = computed(() => {
if (placeholderLoaded.value) {
- return false
+ return undefined
}
- const placeholder = props.placeholder === '' ? [10, 10] : props.placeholder
+ const src = _placeholder.value
- if (!placeholder) {
- return false
+ if (!src) {
+ return undefined
}
- if (typeof placeholder === 'string') {
- return placeholder
+ if (typeof src === 'string') {
+ return src
}
- const [width = 10, height = width, quality = 50, blur = 3] = Array.isArray(placeholder)
- ? placeholder
- : typeof placeholder === 'number' ? [placeholder] : []
-
- return $img(props.src!, {
- ...imageModifiers.value,
- width,
- height,
- quality,
- blur,
- }, providerOptions.value)
+ const [width = 10, height = width, quality = 50, blur = 3] = Array.isArray(src)
+ ? src
+ : typeof src === 'number'
+ ? [src]
+ : []
+
+ return $img(
+ props.src!,
+ {
+ ...imageModifiers.value,
+ width,
+ height,
+ quality,
+ blur,
+ },
+ providerOptions.value,
+ )
})
const mainSrc = computed(() =>
@@ -101,7 +105,19 @@ const mainSrc = computed(() =>
: $img(props.src!, imageModifiers.value, providerOptions.value),
)
-const src = computed(() => placeholder.value || mainSrc.value)
+const src = computed(() => placeholderSrc.value || mainSrc.value)
+
+const attrs = useAttrs() as ImgHTMLAttributes
+
+const imgAttrs = computed(() => ({
+ ...normalizedAttrs.value,
+ 'data-nuxt-img': '',
+ ...!placeholderSrc.value
+ ? { sizes: sizes.value.sizes, srcset: sizes.value.srcset }
+ : {},
+ ...import.meta.server ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {},
+ ...attrs,
+}))
if (import.meta.server && props.preload) {
const hasMultipleDensities = sizes.value.srcset.includes('x, ')
@@ -134,7 +150,7 @@ const imgEl = useTemplateRef('imgEl')
defineExpose({ imgEl })
onMounted(() => {
- if (placeholder.value || props.custom) {
+ if (placeholderSrc.value || props.custom) {
const img = new Image()
if (mainSrc.value) {
diff --git a/src/types/image.ts b/src/types/image.ts
index ae0f06ffc..0133aba3e 100644
--- a/src/types/image.ts
+++ b/src/types/image.ts
@@ -23,6 +23,12 @@ export interface ImageOptions>
& ('modifiers' extends keyof ConfiguredImageProviders[Provider] ? ConfiguredImageProviders[Provider]['modifiers'] : Record)
sizes?: string | Record
diff --git a/test/nuxt/image.test.ts b/test/nuxt/image.test.ts
index 60446a74a..98ead9154 100644
--- a/test/nuxt/image.test.ts
+++ b/test/nuxt/image.test.ts
@@ -392,7 +392,7 @@ describe('Sizes and densities behavior', () => {
})
})
-describe('Preset sizes and densities inheritance', () => {
+describe('Preset inheritance', () => {
const src = '/image.png'
beforeEach(() => {
@@ -505,6 +505,34 @@ describe('Preset sizes and densities inheritance', () => {
expect(srcset).not.toMatch(/\b\d+w\b/)
expect(sizes).toBeFalsy()
})
+
+ it('preset placeholder and placeholderClass are inherited', () => {
+ setImageContext({
+ presets: {
+ withPlaceholder: {
+ placeholder: true,
+ placeholderClass: 'placeholder-class',
+ },
+ },
+ })
+
+ const wrapper = mount(NuxtImg, {
+ propsData: {
+ src,
+ width: 200,
+ height: 200,
+ preset: 'withPlaceholder',
+ },
+ })
+
+ const imgElement = wrapper.find('img').element
+ const domSrc = imgElement.getAttribute('src')
+
+ expect(domSrc).toMatchInlineSnapshot(
+ '"/_ipx/q_50&blur_3&s_10x10/image.png"',
+ )
+ expect(imgElement.classList).toContain('placeholder-class')
+ })
})
describe('Renders image, applies module config', () => {
diff --git a/test/unit/bundle.test.ts b/test/unit/bundle.test.ts
index eea4df32d..8ba3ada0f 100644
--- a/test/unit/bundle.test.ts
+++ b/test/unit/bundle.test.ts
@@ -21,7 +21,7 @@ describe.skipIf(process.env.ECOSYSTEM_CI || isWindows)('nuxt image bundle size',
image: { provider: 'ipx' },
})
- expect(roundToKilobytes(withImage.totalBytes - withoutImage.totalBytes)).toMatchInlineSnapshot(`"12.6k"`)
+ expect(roundToKilobytes(withImage.totalBytes - withoutImage.totalBytes)).toMatchInlineSnapshot(`"12.8k"`)
})
})