React Native で Expo Camera と MaskedView を使って切り抜きカメラ効果を実装する

Expo Camera を使ってテンプレートのくり抜き効果を作る方法を解説します。リアルタイム映像をすりガラス風の背景に変えます。

日本語
コピー
How to build cutout camera effects in React Native with Expo Camera and MaskedView

この記事は Mehdi Davoodi によるゲスト投稿です。彼は React Native と Expo で UI の実験をしていて、その知見を 𝕏 で共有しています:@mehdi_made

...

カメラ映像をテキストや UI 要素越しに表示させて、くり抜きのような効果を作りたいと思った。考え方はシンプルで、ライブカメラ映像を動的な背景として使いながら、UI の特定の部分からだけ透けさせる、というもの。

以下が Expo Camera、MaskedView、Expo Blur を使った実装方法だ。

作るもの

要はユーザー一覧を表示する画面だ。ただし背景は単色ではなく、カメラ映像をテキストとアバター越しに透けさせる。その上にぼかしを重ねて、すりガラスの質感を出す。全体として、生き生きとした、唯一無二で記憶に残る UI になる。

主要なパーツはこの3つ:

Expo Camera の設定

まずパッケージをインストール:

npx expo install expo-camera

expo-camera パッケージが主に提供するのは CameraView コンポーネントと useCameraPermissions フックの2つ。肝心なのはフックの方で、権限を先に取得しないとカメラをレンダリングできない。

基本的な設定はこうなる:

import { CameraView, useCameraPermissions } from 'expo-camera';

const App = () => {
  const [permission, requestPermission] = useCameraPermissions();

  if (!permission) {
    // Still loading
    return <View />;
  }

  if (!permission.granted) {
    // Need to ask
    return (
      <Button title="Grant Access" onPress={requestPermission} />
    );
  }

  return <CameraView style={{ flex: 1 }} />;
};

これは最小限の書き方だ。ただ一つ問題がある。権限オブジェクトの状態は「許可済み」と「未許可」の2つだけではない。

カメラ権限を理解する

useCameraPermissionspermission オブジェクトには重要なプロパティが2つある:

  • granted——権限を持っているかどうか

  • canAskAgain——ユーザーに再度ダイアログを出せるかどうか

これで実際には4つの状態が生まれる:

permissionnull → まだ確認中

permission.grantedtrue → カメラの準備完了

permission.grantedfalse かつ canAskAgaintrue → ユーザーが拒否したが、もう一度聞ける

permission.grantedfalse かつ canAskAgainfalse → ユーザーが設定で完全に無効化した

最後のケースが重要だ。ユーザーが権限を拒否して「今後表示しない」にチェックを入れると、もうダイアログを出せない。設定から手動で有効にしてもらうしかない。

このロジックをフックにまとめた:

import { useEffect, useCallback } from 'react';
import { useCameraPermissions } from 'expo-camera';

export const useCamera = (autoRequest = true) => {
  const [permission, requestCameraPermission] = useCameraPermissions();

  const getStatus = () => {
    if (!permission) return 'loading';
    if (permission.granted) return 'granted';
    if (!permission.canAskAgain) return 'blocked';
    return 'denied';
  };

  const status = getStatus();

  const requestPermission = useCallback(async () => {
    try {
      await requestCameraPermission();
    } catch (error) {
      console.error('Failed to request camera permission:', error);
    }
  }, [requestCameraPermission]);

  // Auto-request on mount
  useEffect(() => {
    if (autoRequest && status === 'denied') {
      requestPermission();
    }
  }, [autoRequest, status, requestPermission]);

  return {
    status,
    isReady: status === 'granted',
    requestPermission,
  };
};

これでコンポーネント側は isReady を確認するだけでよく、細かいことを気にしなくて済む。

MaskedView と組み合わせる

MaskedView は、別のコンポーネントをマスクとして使えるコンポーネントだ。型紙だと思えばいい。いくつか形を切り抜いて、その形から後ろのものが透けて見える。

仕組み:

MaskedView は2つの部分から成る:

maskElement —— 型紙(形を定義する部分)

children —— 型紙を通して表示される内容

MaskedView

マスクは不透明度に基づいて機能する。マスクの中で完全に不透明な部分(例えば黒)は子ビューを表示し、透明な部分はそれを隠す。グレーの値は部分的な透明に対応する。

import MaskedView from '@react-native-masked-view/masked-view';

<MaskedView
  maskElement={
    <View style={{ backgroundColor: 'transparent', flex: 1 }}>
      <Text style={{ color: 'black', fontSize: 60 }}>Hello</Text>
    </View>
  }
>
  {/* This is what shows through */}
  <View style={{ flex: 1, backgroundColor: 'blue' }} />
</MaskedView>

カメラと組み合わせて使う

ここからが面白いところ。マスクの背後が単色ではなく、ライブカメラ映像になる:

<MaskedView
  maskElement={
    <Text style={{ color: 'black', fontSize: 40 }}>Hello</Text>
  }
>
  <CameraView style={{ flex: 1 }} />
</MaskedView>

カメラ映像はテキストからだけ透け、それ以外の部分は透明になる。

ぼかしを加える

欲しかったのは生のカメラ映像ではなく、すりガラスの質感だ。そこで BlurViewexpo-blur からカメラの上に重ねた:

<MaskedView
  style={{ flex: 1 }}
  maskElement={<MaskContent />}
>
  <BlurView style={StyleSheet.absoluteFillObject} intensity={100} />
  <CameraView style={StyleSheet.absoluteFillObject} />
</MaskedView>

ぼかし層はカメラの上にあり(zIndex かレンダリング順で実現)、マスクは両方を貫通する。つまりマスクの形を通して見えるのは、ぼかされたカメラ映像になる。

ぼかし効果

リポジトリはこちら。役に立てば幸いだ。この記事で学んだことをもとに何か作ったなら、ぜひ共有してほしい。

他にも実装して紹介してほしい UI 実験があれば、DM で教えてほしい。読んでくれてありがとう!

出典: Expo Blog← ホームへ戻る