125 lines
3.4 KiB
TypeScript
125 lines
3.4 KiB
TypeScript
import React, { forwardRef, memo, useMemo } from 'react'
|
||
import { Canvas, Image as SkiaImage, useImage } from '@shopify/react-native-skia'
|
||
import type { StyleProp, ViewStyle } from 'react-native'
|
||
import tw from 'twrnc'
|
||
|
||
interface ImgProps {
|
||
className?: string
|
||
src?: string | number
|
||
/** 画布宽度(同时也作为压缩宽度) */
|
||
width?: number
|
||
/** 画布高度,不传则等于 width */
|
||
height?: number
|
||
isWebP?: boolean
|
||
errorSource?: string | number
|
||
isCompression?: boolean
|
||
/** 自定义缓存键,用于需要重定向的 URL */
|
||
cacheKey?: string
|
||
style?: StyleProp<ViewStyle>
|
||
[key: string]: any
|
||
}
|
||
|
||
const Img = forwardRef<any, ImgProps>((props, ref) => {
|
||
const {
|
||
className = '',
|
||
style = {},
|
||
src,
|
||
errorSource,
|
||
source: propSource,
|
||
cacheKey,
|
||
width = 256,
|
||
height,
|
||
isCompression = false,
|
||
isWebP = true,
|
||
...rest
|
||
} = props
|
||
|
||
const imageStyle = tw`${className}`
|
||
|
||
const canvasWidth = useMemo(() => {
|
||
if (typeof (style as ViewStyle)?.width === 'number') return (style as ViewStyle).width
|
||
return width
|
||
}, [style, width])
|
||
|
||
const canvasHeight = useMemo(() => {
|
||
if (typeof (style as ViewStyle)?.height === 'number') return (style as ViewStyle).height
|
||
if (height != null) return height
|
||
return width
|
||
}, [style, width, height])
|
||
|
||
// 静态图全部压缩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(() => {
|
||
// 如果提供了source属性,优先使用
|
||
if (propSource) return propSource
|
||
|
||
if (!src) return undefined
|
||
|
||
if (typeof src === 'number') {
|
||
// 本地图片资源(require导入的资源ID)
|
||
return src
|
||
} else {
|
||
// 网络图片或本地文件路径
|
||
if (isNetworkImage(src)) {
|
||
const finalUrl = isCompression ? compressionUrl : src
|
||
// console.log('finalUrl-------------', finalUrl)
|
||
|
||
return {
|
||
uri: finalUrl,
|
||
cacheKey: cacheKey || finalUrl,
|
||
}
|
||
} else {
|
||
// 本地文件路径
|
||
return { uri: src }
|
||
}
|
||
}
|
||
}, [src, propSource, cacheKey, isCompression, compressionUrl])
|
||
|
||
// Skia useImage 仅接受 require、URL 或命名资源
|
||
const skiaSource = useMemo(() => {
|
||
if (imageSource == null) return undefined
|
||
if (typeof imageSource === 'number') return imageSource
|
||
if (typeof imageSource === 'string') return imageSource
|
||
if (typeof (imageSource as any).uri === 'string') return (imageSource as any).uri as string
|
||
return undefined
|
||
}, [imageSource])
|
||
|
||
const skImage = useImage(skiaSource as any)
|
||
|
||
if (!skImage) {
|
||
// 图片还在加载中或加载失败时,暂时不渲染内容
|
||
return null
|
||
}
|
||
|
||
return (
|
||
<Canvas
|
||
ref={ref}
|
||
style={[style, imageStyle, { width: canvasWidth, height: canvasHeight }]}
|
||
{...rest}
|
||
>
|
||
<SkiaImage
|
||
image={skImage}
|
||
x={0}
|
||
y={0}
|
||
width={canvasWidth}
|
||
height={canvasHeight}
|
||
fit="contain"
|
||
/>
|
||
</Canvas>
|
||
)
|
||
})
|
||
|
||
Img.displayName = 'Img'
|
||
export default memo(Img)
|