終於到內建組件最後一天了!
今天要分享的是 Image 組件容易讓人混淆的一些用法,如果不是真的踩過這些坑的話光看官方文檔是很難一下就理解的 XD
圖像支持以下兩種類型的來源:
uri
require
支持的圖片格式:png、jpg、jpeg、bmp、gif、webp、psd(僅限 iOS)
<Image source={require('../../assets/icon.png')} />
<Image
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/>
<Image
source={{
uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg==',
}}
/>
但很神奇的是,以上這三張圖片,只有第一張會顯示。
這是因為使用 require
關鍵字導入靜態圖像資源的話 RN 會在編譯時獲取圖像的信息,因此不需要特別設置寬高,而 網絡圖片、base64 這兩種方式則無法,需要你事先設置圖片的寬高,只設寬度或只設高度一樣不會顯示。
雖然官方文檔說有提供 width
, height
的 prop,但實測這不適用於所有來源的圖片,比如說使用 require
關鍵字導入的靜態圖像資源就不適用:
<Image
source={require('../../assets/icon.png')}
// style={{ width: 100, height: 100 }}
width={100}
height={100}
/>
require
關鍵字導入的靜態圖像資源需要用 style={{ width: 100, height: 100 }}
這種方式設置圖片的寬高。
width, height | style |
---|---|
所以通常來說不管什麼圖像來源我都會使用 style 來設置圖像大小,而不是直接用 width, height 的 prop。
Image 組件中有一個屬性 resizeMode
可以用於縮放圖片,可以設置以下幾種類型:
// https://reactnative.dev/img/tiny_logo.png 為 64*64
<View
style={{
width: 400,
height: 700,
backgroundColor: '#aaa',
justifyContent: 'center',
alignItems: 'center'
}}
>
<Text>Center</Text>
<Image
source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
style={{ width: 200, height: 100, backgroundColor: 'white' }}
resizeMode="center"
/>
<Text>Cover</Text>
<Image
source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
style={{ width: 200, height: 100, backgroundColor: 'white' }}
resizeMode="cover"
/>
<Text>Contain</Text>
<Image
source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
style={{ width: 200, height: 100, backgroundColor: 'white' }}
resizeMode="contain"
/>
<Text>Stretch</Text>
<Image
source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
style={{ width: 200, height: 100, backgroundColor: 'white' }}
resizeMode="stretch"
/>
<Text>Repeat</Text>
<Image
source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
style={{ width: 200, height: 100, backgroundColor: 'white' }}
resizeMode="repeat"
/>
</View>
假設要限制多張尺寸不一的圖片最大只能為 100*100
且多餘部分不能被裁剪的話,可以將圖片縮放至最大,並在圖片外加個 View 限制其大小為 100*100
,然後圖片組件的 resizeMode
設為 contain
<View style={{ width: 100, height: 100 }}>
<Image
source={{ uri: data.assets?.image }}
style={{ width: '100%', height: '100%' }}
resizeMode="contain"
/>
</View>
還有另外一種方式是使用 onLayout
ratio
// ResponsiveImage.tsx
import React, { useMemo, useState } from "react"
import {
StyleSheet,
View,
Image,
ImageSourcePropType,
LayoutChangeEvent,
} from "react-native"
interface ResponsiveImageProps {
src: ImageSourcePropType
imgWidth: number
imgHeight: number
}
const ResponsiveImage = ({ src, imgWidth, imgHeight }: ResponsiveImageProps) => {
const [containerWidth, setContainerWidth] = useState<number>(0)
const onViewLayoutChange = (event: LayoutChangeEvent) => {
const { width } = event.nativeEvent.layout
setContainerWidth(width)
}
const imageStyles = useMemo(() => {
const ratio = containerWidth / imgWidth
return {
width: containerWidth,
height: imgHeight * ratio
}
}, [containerWidth])
return (
<View style={styles.container} onLayout={onViewLayoutChange}>
<Image source={src} style={imageStyles} />
</View>
);
};
const styles = StyleSheet.create({
container: {
width: "100%"
}
})
export default ResponsiveImage
如此一來圖片便能依據視圖大小自動縮放了:
400
400 / 1024 = 0.390625
1024 * 0.390625 = 400
{ width: 400, height: 400 }
import { View } from "react-native"
import ResponsiveImage from "../components/ResponsiveImage"
// ...
<View
style={{
width: 400,
height: 700,
backgroundColor: '#aaa',
justifyContent: 'center',
alignItems: 'center'
}}
>
<ResponsiveImage
src={require("../../assets/icon.png")}
imgWidth={1024}
imgHeight={1024}
/>
</View>
如果希望圖片在加載過一次之後就不再重複加載,能更快速地顯示,那就需要為圖片做快取。
內建的 Image 組件有提供 cache control,不過僅支持 iOS,等到第十九天資料持久化存儲的時候會再詳細的分享在 iOS & Android 做圖片快取的方法。
<Image
style={{ width: 350, height: 150 }}
source={{
uri: 'https://unsplash.it/350/150',
cache: 'only-if-cached',
}}
/>
RN 默認不支持 SVG,但有現成的庫可以提供 SVG 支持,比如 react-native-svg
import * as React from 'react';
import Svg, { Circle, Rect } from 'react-native-svg';
export default function SvgComponent(props) {
return (
<Svg height="50%" width="50%" viewBox="0 0 100 100" {...props}>
<Circle cx="50" cy="50" r="45" stroke="blue" strokeWidth="2.5" fill="green" />
<Rect x="15" y="15" width="70" height="70" stroke="red" strokeWidth="2" fill="yellow" />
</Svg>
);
}
如果需要將一般的 svg 轉為 RN 可以用的 JSX 可以使用這個網站 SVGR Playground
import Svg, { SvgProps, Rect, Circle, Text } from "react-native-svg"
const SvgComponent = (props: SvgProps) => (
<Svg {...props}>
<Rect width="100%" height="100%" fill="red" />
<Circle cx={150} cy={100} r={80} fill="green" />
<Text x={150} y={125} fill="#fff" fontSize={60} textAnchor="middle">
{"SVG"}
</Text>
</Svg>
)
// Usage
<SvgComponent width={320} height={240} />