Skip to content

Commit

Permalink
feat(useImage): new function (#1460)
Browse files Browse the repository at this point in the history
Co-authored-by: Lucio Rubens <luciorubeens@users.noreply.github.com>
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
3 people committed Jun 16, 2022
1 parent 9ed2898 commit f4b37a5
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/components/index.ts
Expand Up @@ -24,6 +24,7 @@ export * from '../core/useEyeDropper/component'
export * from '../core/useFullscreen/component'
export * from '../core/useGeolocation/component'
export * from '../core/useIdle/component'
export * from '../core/useImage/component'
export * from '../core/useInfiniteScroll/directive'
export * from '../core/useIntersectionObserver/directive'
export * from '../core/useMouse/component'
Expand Down
1 change: 0 additions & 1 deletion packages/contributors.json
Expand Up @@ -196,7 +196,6 @@
"shorwood",
"xstevenyung",
"Atinux",
"thalesagapito",
"thierrymichel",
"LeSuisse",
"tmkx",
Expand Down
1 change: 1 addition & 0 deletions packages/core/index.ts
Expand Up @@ -52,6 +52,7 @@ export * from './useFullscreen'
export * from './useGamepad'
export * from './useGeolocation'
export * from './useIdle'
export * from './useImage'
export * from './useInfiniteScroll'
export * from './useIntersectionObserver'
export * from './useKeyModifier'
Expand Down
31 changes: 31 additions & 0 deletions packages/core/useImage/component.ts
@@ -0,0 +1,31 @@
import { defineComponent, h, reactive } from 'vue-demi'
import { useImage } from '../useImage'
import type { UseImageOptions } from '../useImage'

import type { RenderableComponent } from '../types'

export const UseImage = defineComponent<UseImageOptions & RenderableComponent>({
name: 'UseImage',
props: [
'src',
'srcset',
'sizes',
'as',
] as unknown as undefined,
setup(props, { slots }) {
const data = reactive(useImage(props))

return () => {
if (data.isLoading && slots.loading)
return slots.loading(data)

else if (data.error && slots.error)
return slots.error(data.error)

if (slots.default)
return slots.default(data)

return h(props.as || 'img', props)
}
},
})
26 changes: 26 additions & 0 deletions packages/core/useImage/demo.vue
@@ -0,0 +1,26 @@
<script lang="ts" setup>
import { useImage } from '@vueuse/core'
import { ref } from 'vue'
const imageOptions = ref({ src: 'https://place.dog/300/200' })
const { isLoading, error } = useImage(imageOptions, { delay: 2000 })
const change = () => {
const time = new Date().getTime()
imageOptions.value.src = `https://place.dog/300/200?t=${time}`
}
</script>

<template>
<div v-if="isLoading" class="w-[300px] h-[200px] animate-pulse bg-gray-500/5 p-2">
Loading...
</div>
<div v-else-if="error">
Failed
</div>
<img v-else :src="imageOptions.src" class="w-[300px] h-[200px]">

<button @click="change">
Change
</button>
</template>
39 changes: 39 additions & 0 deletions packages/core/useImage/index.md
@@ -0,0 +1,39 @@
---
category: Browser
---

# useImage

Reactive load an image in the browser, you can wait the result to display it or show a fallback.

## Usage

```html
<script setup>
import { useImage } from '@vueuse/core'
const avatarUrl = 'https://place.dog/300/200'
const { isLoading } = useImage({ src: avatarUrl })
</script>

<template>
<span v-if="isLoading">Loading</span>
<img v-else :src="avatarUrl">
</template>
```

## Component Usage

```html
<template>
<UseImage src="https://place.dog/300/200">
<template #loading>
Loading..
</template>

<template #error>
Failed
</template>
</UseImage>
</template>
```
60 changes: 60 additions & 0 deletions packages/core/useImage/index.ts
@@ -0,0 +1,60 @@
import type { MaybeRef } from '@vueuse/shared'

import { unref, watch } from 'vue-demi'

import type { AsyncStateOptions } from '../useAsyncState'
import { useAsyncState } from '../useAsyncState'

export interface UseImageOptions {
/** Address of the resource */
src: string
/** Images to use in different situations, e.g., high-resolution displays, small monitors, etc. */
srcset?: string
/** Image sizes for different page layouts */
sizes?: string
}

async function loadImage(options: UseImageOptions): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image()
const { src, srcset, sizes } = options

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

img.onload = () => resolve(img)
img.onerror = reject
})
}

/**
* Reactive load an image in the browser, you can wait the result to display it or show a fallback.
*
* @see https://vueuse.org/useImage
* @param options Image attributes, as used in the <img> tag
* @param asyncStateOptions
*/
export const useImage = <Shallow extends true>(
options: MaybeRef<UseImageOptions>,
asyncStateOptions: AsyncStateOptions<Shallow> = {},
) => {
const state = useAsyncState<HTMLImageElement | undefined>(
() => loadImage(unref(options)),
undefined,
{
resetOnExecute: true,
...asyncStateOptions,
},
)

watch(
() => unref(options),
() => state.execute(asyncStateOptions.delay),
{ deep: true },
)

return state
}

0 comments on commit f4b37a5

Please sign in to comment.