Skip to content

Commit

Permalink
feat(nuxt-img): add custom slot for full control of rendering (#1626)
Browse files Browse the repository at this point in the history
  • Loading branch information
danekslama authored Jan 6, 2025
1 parent 059eca0 commit a1d459f
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 1 deletion.
41 changes: 41 additions & 0 deletions docs/content/2.usage/1.nuxt-img.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,45 @@ With [default provider](/get-started/providers), you should put `/nuxt-icon.png`

## Props

### `custom`

The `custom` prop determines whether `<NuxtImg>` should render as a simple `<img>` element or only serve as a provider for custom rendering. When set to `true`, it disables the default rendering behavior, allowing full control over how the image is displayed. This is useful for implementing custom functionalities, such as placeholders.

When using the `custom` prop, `<NuxtImg>` passes necessary data and attributes to its default slot. You can access the following values via the `v-slot` directive:

- **`imgAttrs`**: Attributes for the `<img>` element (e.g., `alt`, `width`, `height`, `srcset`, `sizes`).
- **`src`**: The computed image source URL.
- **`isLoaded`**: A boolean indicating whether the image has been loaded.

#### Example Usage

```html
<nuxt-img
src="/images/nuxt.png"
alt="image"
width="400"
height="400"
:custom="true"
v-slot="{ src, isLoaded, imgAttrs }"
>
<!-- Show the actual image when loaded -->
<img
v-if="isLoaded"
v-bind="imgAttrs"
:src="src"
/>

<!-- Show a placeholder while loading -->
<img
v-else
src="https://placehold.co/400x400"
alt="placeholder"
/>
</nuxt-img>
```

This approach ensures flexibility for custom rendering scenarios, while `<NuxtImg>` continues to handle image optimization and data provisioning behind the scenes.

### `src`

Path to image file
Expand Down Expand Up @@ -114,6 +153,8 @@ To generate special versions of images for screens with increased pixel density.

Display a placeholder image before the actual image is fully loaded.

You can also use the [custom prop](/usage/nuxt-img#custom) to make any placeholder you want.

The placeholder prop can be either a string, a boolean, a number, or an array. The usage is shown below for each case.

```html
Expand Down
15 changes: 14 additions & 1 deletion src/runtime/components/NuxtImg.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<img
v-if="!custom"
ref="imgEl"
:class="props.placeholder && !placeholderLoaded ? props.placeholderClass : undefined"
v-bind="{
Expand All @@ -9,6 +10,18 @@
}"
:src="src"
>
<slot
v-else
v-bind="{
...isServer ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {},
imgAttrs: {
...imgAttrs,
...attrs,
},
isLoaded: placeholderLoaded,
src,
}"
/>
</template>

<script setup lang="ts">
Expand Down Expand Up @@ -135,7 +148,7 @@ const nuxtApp = useNuxtApp()
const initialLoad = nuxtApp.isHydrating
onMounted(() => {
if (placeholder.value) {
if (placeholder.value || props.custom) {
const img = new Image()
if (mainSrc.value) {
Expand Down
1 change: 1 addition & 0 deletions src/runtime/components/_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,5 @@ export const imgProps = {
...baseImageProps,
placeholder: { type: [Boolean, String, Number, Array], required: false },
placeholderClass: { type: String, required: false },
custom: { type: Boolean, required: false },
}
48 changes: 48 additions & 0 deletions test/unit/image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,52 @@ describe('Renders image, applies module config', () => {
})
})

describe('Renders NuxtImg with the custom prop and default slot', () => {
let wrapper: VueWrapper<any>
const src = '/image.png'

it('renders custom placeholder when image is not loaded', async () => {
const {
resolve: resolveImage,
loadEvent,
} = getImageLoad(() => {
wrapper = mount(NuxtImg, {
propsData: {
width: 200,
height: 200,
src: src,
custom: true,
},
slots: {
default: ({ imgAttrs, src, isLoaded }) => {
const img = h('img', { ...imgAttrs, src })
const placeholder = h('p', {}, 'placeholder')

return isLoaded
? img
: placeholder
},
},
})
})

const placeholderText = wrapper.find('p').element.getHTML()
let img = wrapper.find('img')

expect(placeholderText).toMatchInlineSnapshot('"placeholder"')
expect(img.exists()).toBeFalsy()

resolveImage()
await nextTick()

img = wrapper.find('img')
const domSrc = img.element.getAttribute('src')

expect(img.element.getAttribute('width')).toBe('200')
expect(img.element.getAttribute('height')).toBe('200')
expect(domSrc).toMatchInlineSnapshot('"/_ipx/s_200x200/image.png"')
expect(wrapper.emitted().load![0]).toStrictEqual([loadEvent])
})
})

const mountImage = (props: ComponentMountingOptions<typeof NuxtImg>['props']) => mount(NuxtImg, { props })

0 comments on commit a1d459f

Please sign in to comment.