React Native で Expo Camera と MaskedView を使って切り抜きカメラ効果を実装する
Expo Camera を使ってテンプレートのくり抜き効果を作る方法を解説します。リアルタイム映像をすりガラス風の背景に変えます。
日本語
コピー

この記事は Mehdi Davoodi によるゲスト投稿です。彼は React Native と Expo で UI の実験をしていて、その知見を 𝕏 で共有しています:@mehdi_made
...
カメラ映像をテキストや UI 要素越しに表示させて、くり抜きのような効果を作りたいと思った。考え方はシンプルで、ライブカメラ映像を動的な背景として使いながら、UI の特定の部分からだけ透けさせる、というもの。
以下が Expo Camera、MaskedView、Expo Blur を使った実装方法だ。
作るもの
要はユーザー一覧を表示する画面だ。ただし背景は単色ではなく、カメラ映像をテキストとアバター越しに透けさせる。その上にぼかしを重ねて、すりガラスの質感を出す。全体として、生き生きとした、唯一無二で記憶に残る UI になる。
主要なパーツはこの3つ:
-
expo-cameraがライブカメラ映像を担当 -
@react-native-masked-view/masked-viewがくり抜き効果を作る -
expo-blurがすりガラスの層を提供する
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つだけではない。
カメラ権限を理解する
useCameraPermissions の permission オブジェクトには重要なプロパティが2つある:
-
granted——権限を持っているかどうか -
canAskAgain——ユーザーに再度ダイアログを出せるかどうか
これで実際には4つの状態が生まれる:
permission が null → まだ確認中
permission.granted が true → カメラの準備完了
permission.granted が false かつ canAskAgain が true → ユーザーが拒否したが、もう一度聞ける
permission.granted が false かつ canAskAgain が false → ユーザーが設定で完全に無効化した
最後のケースが重要だ。ユーザーが権限を拒否して「今後表示しない」にチェックを入れると、もうダイアログを出せない。設定から手動で有効にしてもらうしかない。
このロジックをフックにまとめた:
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 —— 型紙を通して表示される内容

マスクは不透明度に基づいて機能する。マスクの中で完全に不透明な部分(例えば黒)は子ビューを表示し、透明な部分はそれを隠す。グレーの値は部分的な透明に対応する。
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>
カメラ映像はテキストからだけ透け、それ以外の部分は透明になる。
ぼかしを加える
欲しかったのは生のカメラ映像ではなく、すりガラスの質感だ。そこで BlurView を expo-blur からカメラの上に重ねた:
<MaskedView
style={{ flex: 1 }}
maskElement={<MaskContent />}
>
<BlurView style={StyleSheet.absoluteFillObject} intensity={100} />
<CameraView style={StyleSheet.absoluteFillObject} />
</MaskedView>
ぼかし層はカメラの上にあり(zIndex かレンダリング順で実現)、マスクは両方を貫通する。つまりマスクの形を通して見えるのは、ぼかされたカメラ映像になる。

リポジトリはこちら。役に立てば幸いだ。この記事で学んだことをもとに何か作ったなら、ぜひ共有してほしい。
他にも実装して紹介してほしい UI 実験があれば、DM で教えてほしい。読んでくれてありがとう!