feat: optimize display effect when image loading fails
This commit is contained in:
@@ -2,20 +2,29 @@ import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { TImageInfo } from '@/types'
|
||||
import { decode } from 'blurhash'
|
||||
import { ImageOff } from 'lucide-react'
|
||||
import { HTMLAttributes, useEffect, useState } from 'react'
|
||||
|
||||
export default function Image({
|
||||
image: { url, blurHash },
|
||||
alt,
|
||||
className = '',
|
||||
classNames = {},
|
||||
hideIfError = false,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLDivElement> & {
|
||||
classNames?: {
|
||||
wrapper?: string
|
||||
errorPlaceholder?: string
|
||||
}
|
||||
image: TImageInfo
|
||||
alt?: string
|
||||
hideIfError?: boolean
|
||||
}) {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [displayBlurHash, setDisplayBlurHash] = useState(true)
|
||||
const [blurDataUrl, setBlurDataUrl] = useState<string | null>(null)
|
||||
const [hasError, setHasError] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (blurHash) {
|
||||
@@ -36,23 +45,41 @@ export default function Image({
|
||||
}
|
||||
}, [blurHash])
|
||||
|
||||
if (hideIfError && hasError) return null
|
||||
|
||||
return (
|
||||
<div className={cn('relative', className)} {...props}>
|
||||
{isLoading && <Skeleton className={cn('absolute inset-0', className)} />}
|
||||
<img
|
||||
src={url}
|
||||
alt={alt}
|
||||
className={cn(
|
||||
'object-cover transition-opacity duration-300',
|
||||
isLoading ? 'opacity-0' : 'opacity-100',
|
||||
className
|
||||
)}
|
||||
onLoad={() => {
|
||||
setIsLoading(false)
|
||||
setTimeout(() => setDisplayBlurHash(false), 500)
|
||||
}}
|
||||
/>
|
||||
{displayBlurHash && blurDataUrl && (
|
||||
<div className={cn('relative', classNames.wrapper)} {...props}>
|
||||
{isLoading && <Skeleton className={cn('absolute inset-0 rounded-lg', className)} />}
|
||||
{!hasError ? (
|
||||
<img
|
||||
src={url}
|
||||
alt={alt}
|
||||
className={cn(
|
||||
'object-cover transition-opacity duration-300 w-full h-full',
|
||||
isLoading ? 'opacity-0' : 'opacity-100',
|
||||
className
|
||||
)}
|
||||
onLoad={() => {
|
||||
setIsLoading(false)
|
||||
setTimeout(() => setDisplayBlurHash(false), 500)
|
||||
}}
|
||||
onError={() => {
|
||||
setIsLoading(false)
|
||||
setHasError(true)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
'object-cover flex flex-col items-center justify-center w-full h-full bg-muted',
|
||||
className,
|
||||
classNames.errorPlaceholder
|
||||
)}
|
||||
>
|
||||
<ImageOff />
|
||||
</div>
|
||||
)}
|
||||
{displayBlurHash && blurDataUrl && !hasError && (
|
||||
<img
|
||||
src={blurDataUrl}
|
||||
className={cn('absolute inset-0 object-cover w-full h-full -z-10', className)}
|
||||
|
||||
Reference in New Issue
Block a user