This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
处理平台差异
编辑页面
在本教程中,学习在创建通用应用时如何处理原生和 Web 之间的平台差异。
Android、iOS 和 Web 具有不同的能力。在我们的案例中,Android 和 iOS 都可以使用 react-native-view-shot 库来捕获屏幕截图。然而,Web 浏览器不能。
在本章中,我们将学习如何处理 Web 浏览器的截图捕获,这样我们的应用就能在所有平台上拥有相同的功能。

通过使用 dom-to-image 实现平台特定的截图捕获,处理 Android、iOS 和 Web 之间的平台差异。
1
安装并导入 dom-to-image
为了在 Web 上捕获截图并将其保存为图片,我们将使用一个名为 dom-to-image 的第三方库。它可以截取任意 DOM 节点,并将其转换为矢量(SVG)或栅格(PNG 或 JPEG)图像。
停止开发服务器并运行以下命令来安装该库:
- npm install dom-to-image注意: 这里使用
dom-to-image库仅用于演示目的。对于生产环境应用,你可能需要探索其他更适合你具体用例的解决方案或 API。
安装完成后,请务必重启开发服务器,并在终端中按 W。
2
添加平台特定代码
使用 React Native 中的 Platform 模块,我们可以实现平台特定的行为。在 app/(tabs)/index.tsx 中:
- 从
react-native导入Platform模块。 - 从
dom-to-image导入domtoimage库。 - 更新
onSaveImageAsync()函数,通过Platform.OS属性检查当前平台是否为'web'。如果是'web',我们将使用domtoimage.toJpeg()方法把当前<View>转换并捕获为 JPEG 图像。否则,我们将继续使用为原生平台添加的相同逻辑。
import * as ImagePicker from 'expo-image-picker'; import * as MediaLibrary from 'expo-media-library'; import { useEffect, useRef, useState } from 'react'; import { ImageSourcePropType, View, StyleSheet, Platform } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { captureRef } from 'react-native-view-shot'; import domtoimage from 'dom-to-image'; import Button from '@/components/Button'; import ImageViewer from '@/components/ImageViewer'; import IconButton from '@/components/IconButton'; import CircleButton from '@/components/CircleButton'; import EmojiPicker from '@/components/EmojiPicker'; import EmojiList from '@/components/EmojiList'; import EmojiSticker from '@/components/EmojiSticker'; const PlaceholderImage = require('@/assets/images/background-image.png'); export default function Index() { const [selectedImage, setSelectedImage] = useState<string | undefined>(undefined); const [showAppOptions, setShowAppOptions] = useState<boolean>(false); const [isModalVisible, setIsModalVisible] = useState<boolean>(false); const [pickedEmoji, setPickedEmoji] = useState<ImageSourcePropType | undefined>(undefined); const [permissionResponse, requestPermission] = ImagePicker.useMediaLibraryPermissions(); const imageRef = useRef<View>(null); useEffect(() => { if (!permissionResponse?.granted) { requestPermission(); } }, []); const pickImageAsync = async () => { let result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], allowsEditing: true, quality: 1, }); if (!result.canceled) { setSelectedImage(result.assets[0].uri); setShowAppOptions(true); } else { alert('You did not select any image.'); } }; const onReset = () => { setShowAppOptions(false); }; const onAddSticker = () => { setIsModalVisible(true); }; const onModalClose = () => { setIsModalVisible(false); }; const onSaveImageAsync = async () => { if (Platform.OS !== 'web') { try { const localUri = await captureRef(imageRef, { height: 440, quality: 1, }); await MediaLibrary.saveToLibraryAsync(localUri); if (localUri) { alert('Saved!'); } } catch (e) { console.log(e); } } else { try { const dataUrl = await domtoimage.toJpeg(imageRef.current, { quality: 0.95, width: 320, height: 440, }); let link = document.createElement('a'); link.download = 'sticker-smash.jpeg'; link.href = dataUrl; link.click(); } catch (e) { console.log(e); } } }; return ( <GestureHandlerRootView style={styles.container}> <View style={styles.imageContainer}> <View ref={imageRef} collapsable={false}> <ImageViewer imgSource={PlaceholderImage} selectedImage={selectedImage} /> {pickedEmoji && <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />} </View> </View> {showAppOptions ? ( <View style={styles.optionsContainer}> <View style={styles.optionsRow}> <IconButton icon="refresh" label="Reset" onPress={onReset} /> <CircleButton onPress={onAddSticker} /> <IconButton icon="save-alt" label="Save" onPress={onSaveImageAsync} /> </View> </View> ) : ( <View style={styles.footerContainer}> <Button theme="primary" label="Choose a photo" onPress={pickImageAsync} /> <Button label="Use this photo" onPress={() => setShowAppOptions(true)} /> </View> )} <EmojiPicker isVisible={isModalVisible} onClose={onModalClose}> <EmojiList onSelect={setPickedEmoji} onCloseModal={onModalClose} /> </EmojiPicker> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#25292e', alignItems: 'center', }, imageContainer: { flex: 1, }, footerContainer: { flex: 1 / 3, alignItems: 'center', }, optionsContainer: { position: 'absolute', bottom: 80, }, optionsRow: { alignItems: 'center', flexDirection: 'row', }, });
修复 dom-to-image TypeScript 模块错误
由于我们使用的是 TypeScript,在导入 domtoimage 库后还需要添加一个类型定义。我们可以通过在项目目录根目录下创建一个 types.d.ts 文件并添加以下声明来实现:
declare module 'dom-to-image';
在 Web 浏览器中运行应用后,我们现在可以保存截图了:
概要
Chapter 8: Handle platform differences
The app does everything we set out for it to do, so it's time to shift our focus toward the purely aesthetic..
In the next chapter, we will customize the app's status bar, splash screen, and app icon.