useImage
Reactive load an image in the browser, you can wait the result to display it or show a fallback. 在浏览器中响应式加载图像,您可以等待结果显示它或显示后备。
- promisify将加载图片转化成promise的形式
// hook/useImage/index.ts
import type { MaybeRef } from '@vueuse/shared'
import type { AsyncStateOptions } from '../useAsyncState'
import { useAsyncState } from '../useAsyncState'
export interface UseImageOptions {
src: string,
srcset?:string //它引用了 MDN 标志高清版本;在高分辨率设备上,它将被优先加载,取代 src 属性中的图像
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
})
}
- 复用useAsyncState监听loadImage执行输出响应式数据
export const useImage = <Shallow extends true>(
options:MaybeRef<UseImageOptions>,
asyncStateOptions:AsyncStateOptions<Shallow> = {}
) => {
const state = useAsyncState<HTMLImageElement | undefined>(
()=> loadImage(unref(options)),
undefined,
{
resetOnExecute: true,
...asyncStateOptions
}
)
// 监听options变化重新执行loadImage函数
watch(
()=>unref(options),
()=>{
state.execute(asyncStateOptions.delay)
},
{deep:true}
)
return state
}
- UseImage组件
// component.ts
import { useImage } from '../UseImage'
import type { UseImageOptions } from '../UseImage'
export interface RenderableComponent {
/**
* The element that the component should be rendered as
*
* @default 'div'
*/
as?: Object | string
}
// defineComponent 返回的值有一个合成类型的构造函数,用于手动渲染函数、TSX 和 IDE 工具
export const UseImage = defineComponent<UseImageOptions & RenderableComponent>({
name: 'useImage',
props: [
'src',
'srcset',
'sizes',
'as',
],
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)
}
},
})
demo
<script setup lang="ts">
import {useImage} from '../../hook/UseImage/index.ts'
import {UseImage} from '../../hook/UseImage/UseImage.ts'
const imageOptions = $ref({ src: "https://place.dog/300/200" });
const { isLoading, error } = useImage(imageOptions, { delay: 2000 });
const change = () => {
const time = new Date().getTime()
imageOptions.src = `https://place.dog/300/200?t=${time}`
}
</script>
<template>
<div>
<div border="~ base rounded" bg-base shadow m-auto w-300px>
<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 m-2 p-1 bg-gray-500 border="~ base rounded" @click="change">Change</button>
<hr m-2>
<div class="w-[300px] h-[200px] p-2">
<UseImage :src="imageOptions.src" h-200px bg-base shadow m-auto w-300px>
<template #loading>
Loading..
</template>
<!-- <template #default>
666
</template> -->
<template #error>
Failed
</template>
</UseImage>
</div>
</div>
</div>
</template>
总结
useImage监听options中src的变化响应式加载图像,在基础上封装组件更便捷的使用,状态更少