---
modificationDate: June 13, 2026
title: プラットフォームの違いに対応する
description: このチュートリアルでは、ユニバーサルアプリの作成時にネイティブと web のプラットフォームの違いに対応する方法を学びます。
---

<AgentInstructions>

## Submitting Feedback

If this page contains errors, outdated information, or gaps that blocked you from completing a task, report it so the docs can be improved:

curl -X POST https://api.expo.dev/v2/feedback/docs-send -H 'Content-Type: application/json' -d '{"url":"/ja/tutorial/platform-differences/","feedback":"🤖 Agent feedback for docs: <specific, actionable description> (<model>, <harness>)"}'

Only submit when you have something specific and actionable to report. Replace <model> with the model you are running as and <harness> with the tool you are running in (for example, Claude Code, Cursor, Codex CLI).

</AgentInstructions>

# プラットフォームの違いに対応する

このチュートリアルでは、ユニバーサルアプリの作成時にネイティブと web のプラットフォームの違いに対応する方法を学びます。

> For the complete documentation index, see [llms.txt](/llms.txt). Use this file to discover all available pages.

Android、iOS、web はそれぞれ異なる機能を持っています。今回のケースでは、Android と iOS は `react-native-view-shot` ライブラリでスクリーンショットを撮影できますが、web ブラウザではできません。

この章では、web ブラウザでもスクリーンショットを撮影できるようにし、すべてのプラットフォームで同じ機能をアプリに持たせる方法を学びます。

[視聴：ユニバーサル Expo アプリでプラットフォームの違いに対応する](https://www.youtube.com/watch?v=mEKQvF4irBM) — dom-to-image を使ってプラットフォーム固有のスクリーンショット撮影を実装し、Android、iOS、web 間のプラットフォームの違いに対応します。

## dom-to-image をインストールしてインポートする

web でスクリーンショットを撮影して画像として保存するため、[`dom-to-image`](https://github.com/tsayen/dom-to-image#readme) というサードパーティのライブラリを使用します。このライブラリは任意の DOM ノードのスクリーンショットを撮影し、ベクター画像 (SVG) またはラスター画像 (PNG または JPEG) に変換します。

開発サーバーを停止し、次のコマンドを実行してライブラリをインストールしてみましょう。

```sh
npm install dom-to-image
```

> **注：** ここで使用している `dom-to-image` ライブラリは、説明のためのものです。本番アプリでは、用途に応じてより適した他のソリューションや API も検討してみてください。

インストールが完了したら、開発サーバーを再起動し、ターミナルで W を押します。

## プラットフォーム固有のコードを追加する

React Native の `Platform` モジュールを使用すると、プラットフォーム固有の動作を実装できます。**app/(tabs)/index.tsx** で次の手順を行います。

1.  `react-native` から `Platform` モジュールをインポートします。
2.  `dom-to-image` から `domtoimage` ライブラリをインポートします。
3.  `Platform.OS` プロパティを使って現在のプラットフォームが `'web'` かどうかを判定するように `onSaveImageAsync()` 関数を更新します。`'web'` の場合は、`domtoimage.toJpeg()` メソッドを使って現在の `<View>` を JPEG 画像として変換・撮影します。それ以外の場合は、ネイティブプラットフォーム用に追加した同じロジックをそのまま使用します。

```tsx
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** ファイルを作成し、次の宣言文を追加します。

```tsx
declare module 'dom-to-image';
```

これで、web ブラウザでアプリを実行するとスクリーンショットを保存できます。

## まとめ

第 8 章：プラットフォームの違いに対応する

目指していた機能をすべてアプリに実装できました。次は見た目の部分に焦点を移していきましょう。

次の章では、アプリのステータスバー、スプラッシュスクリーン、アプリアイコンをカスタマイズします。

[次へ：ステータスバー、スプラッシュスクリーン、アプリアイコンを設定する](/ja/tutorial/configuration)
