expo-duooomi-app/@share/components/Img.tsx

141 lines
3.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Image as ExpoImage, type ImageProps as ExpoImageProps } from 'expo-image'
import React, { forwardRef, memo, useEffect, useMemo, useState } from 'react'
import { View } from 'react-native'
import tw from 'twrnc'
interface ImgProps extends ExpoImageProps {
className?: string
src?: string | number
width?: number
isWebP?: boolean
errorSource?: string | number
isCompression?: boolean
/** 自定义缓存键,用于需要重定向的 URL */
cacheKey?: string
/** 占位图 URL在真实图片加载完成前显示 */
placeholderSrc?: string
autoPlay?: boolean
}
// ExpoImage.clearDiskCache()
// ExpoImage.clearMemoryCache()
// cloudflare 图片优化服务示例地址
const ImageOrigin = `https://bowong.cc/cdn-cgi/image/width=128,quality=75,format=webp/https://cdn.roasmax.cn/material/b59b75841c484d8bafec9c5636930b69.webp`
const Img = forwardRef<ExpoImage, ImgProps>((props, ref) => {
const {
className = '',
style = {},
src,
source: propSource,
cacheKey,
width = 256,
isCompression = false,
isWebP = true,
placeholderSrc,
onLoad,
...reset
} = props
const [isLoaded, setIsLoaded] = useState(false)
useEffect(() => {
setIsLoaded(false)
}, [src])
const imageStyle = tw`${className}`
// 静态图全部压缩jpg
const compressionUrl = useMemo(() => {
const f = isWebP ? 'webp' : 'jpg'
return `https://bowong.cc/cdn-cgi/image/width=${width},quality=75,format=${f}/${src}`
}, [width, isWebP, src])
// 判断是否为网络图片
const isNetworkImage = (uri: string | number): boolean => {
if (typeof uri !== 'string') return false
return uri.startsWith('http://') || uri.startsWith('https://')
}
// 构建图片源
const imageSource = useMemo(() => {
if (propSource) return propSource
if (!src) return undefined
if (typeof src === 'number') {
return src
} else {
if (isNetworkImage(src)) {
const finalUrl = isCompression ? compressionUrl : src
return {
uri: finalUrl,
cacheKey: cacheKey || finalUrl,
}
} else {
return { uri: src }
}
}
}, [src, propSource, cacheKey, isCompression, compressionUrl])
const handleLoad = (e: any) => {
// console.log('handleLoad--------------', e)
setIsLoaded(true)
onLoad?.(e)
}
// 无占位图时直接返回原图
if (!placeholderSrc) {
return (
<ExpoImage
ref={ref}
style={[style, imageStyle]}
source={imageSource}
cachePolicy="disk"
recyclingKey={typeof src === 'string' ? src : undefined}
transition={{ duration: 200, effect: 'cross-dissolve' }}
onLoad={onLoad}
priority="low"
{...reset}
/>
)
}
const showPlaceholder = placeholderSrc
// 有占位图时使用层叠布局
return (
<View style={[style, imageStyle, { overflow: 'hidden' }]}>
{/* 占位图层 - 加载完成后隐藏 */}
{showPlaceholder && (
<ExpoImage
style={{ position: 'absolute', width: '100%', height: '100%' }}
source={{ uri: placeholderSrc }}
cachePolicy="disk"
contentFit={reset.contentFit || 'cover'}
transition={{ duration: 100, effect: 'curl-up' }}
/>
)}
{/* 真实图片层 */}
<ExpoImage
ref={ref}
style={{ width: '100%', height: '100%', opacity: isLoaded ? 1 : 0 }}
source={imageSource}
cachePolicy="disk"
recyclingKey={typeof src === 'string' ? src : undefined}
transition={{ duration: 300, effect: 'cross-dissolve' }}
onLoad={handleLoad}
contentFit={reset.contentFit || 'cover'}
priority="low"
{...reset}
/>
</View>
)
})
Img.displayName = 'Img'
export default memo(Img)