返回 AiToEarn
LazyImage.tsx
1 /**
2 * 懒加载图片组件
3 * 封装 Next.js Image 组件,提供加载状态骨架屏和淡入动画效果
4 */
5
6 'use client'
7
8 import type { ImageProps } from 'next/image'
9 import Image from 'next/image'
10 import { memo, useState } from 'react'
11 import { OssImage } from '@/components/common/OssImage'
12 import { Skeleton } from '@/components/ui/skeleton'
13 import { cn } from '@/utils/className'
14
15 interface LazyImageProps extends Omit<ImageProps, 'onLoad'> {
16 /** 骨架屏的额外类名 */
17 skeletonClassName?: string
18 /** 容器的额外类名 */
19 containerClassName?: string
20 /** 占位高度,用于图片加载前显示骨架屏 */
21 placeholderHeight?: number | string
22 /** OSS/R2 图片是否使用云端缩略图 */
23 useOssThumbnail?: boolean
24 }
25
26 export const LazyImage = memo(({
27 src,
28 alt,
29 className,
30 skeletonClassName,
31 containerClassName,
32 placeholderHeight,
33 useOssThumbnail,
34 ...props
35 }: LazyImageProps) => {
36 const [loaded, setLoaded] = useState(false)
37 const [error, setError] = useState(false)
38
39 const handleLoad = () => {
40 setLoaded(true)
41 }
42
43 const handleError = () => {
44 setError(true)
45 setLoaded(true)
46 }
47
48 return (
49 <div
50 className={cn('relative', containerClassName)}
51 style={!loaded && placeholderHeight ? { minHeight: placeholderHeight } : undefined}
52 >
53 {/* 骨架屏 */}
54 {!loaded && (
55 <Skeleton
56 className={cn(
57 'absolute inset-0 w-full h-full',
58 skeletonClassName,
59 )}
60 />
61 )}
62
63 {/* 图片 */}
64 {useOssThumbnail
65 ? (
66 <OssImage
67 src={error ? '/images/placeholder.png' : src}
68 alt={alt}
69 className={cn(
70 'transition-opacity duration-300',
71 loaded ? 'opacity-100' : 'opacity-0',
72 className,
73 )}
74 onLoad={handleLoad}
75 onError={handleError}
76 {...props}
77 />
78 )
79 : (
80 <Image
81 src={error ? '/images/placeholder.png' : src}
82 alt={alt}
83 className={cn(
84 'transition-opacity duration-300',
85 loaded ? 'opacity-100' : 'opacity-0',
86 className,
87 )}
88 onLoad={handleLoad}
89 onError={handleError}
90 {...props}
91 />
92 )}
93 </div>
94 )
95 })
96
97 LazyImage.displayName = 'LazyImage'
98
98 lines Plain Text