feat: optimize display effect when image loading fails

This commit is contained in:
codytseng
2025-02-14 12:17:01 +08:00
parent c4b9b397a6
commit 41d46b1a13
12 changed files with 76 additions and 55 deletions

View File

@@ -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)}