117 行
2.9 KiB
TypeScript
117 行
2.9 KiB
TypeScript
|
|
import React, { useState, useEffect } from 'react'
|
||
|
|
import { Image, View, StyleSheet } from 'react-native'
|
||
|
|
|
||
|
|
interface Props {
|
||
|
|
uri: string
|
||
|
|
originalWidth?: number
|
||
|
|
originalHeight?: number
|
||
|
|
minWidth?: number
|
||
|
|
maxWidth?: number
|
||
|
|
minHeight?: number
|
||
|
|
maxHeight?: number
|
||
|
|
borderRadius?: number
|
||
|
|
style?: object
|
||
|
|
}
|
||
|
|
|
||
|
|
function computeDimensions(
|
||
|
|
srcWidth: number,
|
||
|
|
srcHeight: number,
|
||
|
|
minWidth: number,
|
||
|
|
maxWidth: number,
|
||
|
|
minHeight: number,
|
||
|
|
maxHeight: number,
|
||
|
|
): { width: number; height: number } {
|
||
|
|
if (srcWidth <= 0 || srcHeight <= 0) {
|
||
|
|
return { width: minWidth, height: minHeight }
|
||
|
|
}
|
||
|
|
|
||
|
|
const aspectRatio = srcWidth / srcHeight
|
||
|
|
|
||
|
|
// Start from maxWidth and compute height
|
||
|
|
let width = maxWidth
|
||
|
|
let height = width / aspectRatio
|
||
|
|
|
||
|
|
// If height exceeds maxHeight, clamp by height
|
||
|
|
if (height > maxHeight) {
|
||
|
|
height = maxHeight
|
||
|
|
width = height * aspectRatio
|
||
|
|
}
|
||
|
|
|
||
|
|
// Enforce minimums
|
||
|
|
if (width < minWidth) {
|
||
|
|
width = minWidth
|
||
|
|
height = width / aspectRatio
|
||
|
|
}
|
||
|
|
if (height < minHeight) {
|
||
|
|
height = minHeight
|
||
|
|
width = height * aspectRatio
|
||
|
|
}
|
||
|
|
|
||
|
|
// Final clamp to maximums after minimum adjustments
|
||
|
|
width = Math.min(width, maxWidth)
|
||
|
|
height = Math.min(height, maxHeight)
|
||
|
|
|
||
|
|
return { width: Math.round(width), height: Math.round(height) }
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ScaledImage(props: Props): JSX.Element {
|
||
|
|
const {
|
||
|
|
uri,
|
||
|
|
originalWidth,
|
||
|
|
originalHeight,
|
||
|
|
minWidth = 120,
|
||
|
|
maxWidth = 240,
|
||
|
|
minHeight = 80,
|
||
|
|
maxHeight = 320,
|
||
|
|
borderRadius,
|
||
|
|
style,
|
||
|
|
} = props
|
||
|
|
|
||
|
|
const [dimensions, setDimensions] = useState<{ width: number; height: number } | null>(() => {
|
||
|
|
if (originalWidth !== undefined && originalHeight !== undefined) {
|
||
|
|
return computeDimensions(originalWidth, originalHeight, minWidth, maxWidth, minHeight, maxHeight)
|
||
|
|
}
|
||
|
|
return null
|
||
|
|
})
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (originalWidth !== undefined && originalHeight !== undefined) {
|
||
|
|
setDimensions(
|
||
|
|
computeDimensions(originalWidth, originalHeight, minWidth, maxWidth, minHeight, maxHeight),
|
||
|
|
)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Use Image.getSize to determine dimensions at runtime
|
||
|
|
Image.getSize(
|
||
|
|
uri,
|
||
|
|
(w, h) => {
|
||
|
|
setDimensions(computeDimensions(w, h, minWidth, maxWidth, minHeight, maxHeight))
|
||
|
|
},
|
||
|
|
() => {
|
||
|
|
// Fallback to min dimensions on error
|
||
|
|
setDimensions({ width: minWidth, height: minHeight })
|
||
|
|
},
|
||
|
|
)
|
||
|
|
}, [uri, originalWidth, originalHeight, minWidth, maxWidth, minHeight, maxHeight])
|
||
|
|
|
||
|
|
if (!dimensions) {
|
||
|
|
// Placeholder while size is being resolved
|
||
|
|
return <View style={[styles.placeholder, { width: minWidth, height: minHeight, borderRadius }, style]} />
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Image
|
||
|
|
source={{ uri }}
|
||
|
|
style={[{ width: dimensions.width, height: dimensions.height, borderRadius }, style]}
|
||
|
|
resizeMode="cover"
|
||
|
|
/>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
const styles = StyleSheet.create({
|
||
|
|
placeholder: {
|
||
|
|
backgroundColor: '#e0e0e0',
|
||
|
|
},
|
||
|
|
})
|