📸
Reactでカメラを使って画像を撮影するダイアログを実装してみた
スマホやタブレットのカメラを使って画像を撮るダイアログを実装してみた。
最近仕事で扱ったため、備忘録として記事に残しておく。
要件
- 想定端末はAndroid, iOSのモバイル端末
- Webアプリ上でカメラを起動する
- 撮影ボタンを押して、画像をDataURL形式で保持できる
コンポーネント構成と役割
下記の3つのコンポーネントに分けて実装した。
-
App.tsx
- ダイアログの開閉
- 撮影した画像データのstateを保持
- 撮影した画像を表示
-
WebcamDialog.tsx
- フルスクリーンのダイアログの要素
- 画面の向きによってWebcamコンポーネントを表示・非表示を切り替え
- ボタンを押すと、ref経由で
Webcam.tsx
に定義してあるcapture
メソッドを呼び出して、App.tsxのstateに画像データを格納する
-
Webcam.tsx
- propsで渡された
width
とheight
のカメラ画像を写す要素を表示 - 撮影データを返すことができる
capture
メソッドを持ち、親コンポーネントからref経由で呼び出しが可能
- propsで渡された
カメラの機能に関わるやり取りは全てWebcam
コンポーネント内に処理を書くことができたので、他のコンポーネントは特に難しいことはしていない。
Webcamコンポーネントの解説
Webcamコンポーネントでは、mount時にカメラのstreamをvideo要素に流し込むようにしており、unmount時にstreamを停止させるようにしている。
モバイルデバイスで縦長画像を取得するときgetUserMedia
に渡すaspectRatio
に指定する比率のwidthとheightを入れ替えて指定する必要があります。
Webcam.tsx
const Webcam: ForwardRefRenderFunction<WebcamHandles, Props> = (
{ width, height },
ref
) => {
const videoRef = useRef<HTMLVideoElement>(null);
// 親コンポーネントからref経由で実行できるメソッドを定義
useImperativeHandle(ref, () => ({
// video要素で表示している画像のdataURLを返すメソッド
capture() {
let canvas = document.createElement("canvas");
if (videoRef === null || videoRef.current === null) {
return null;
}
const { videoWidth, videoHeight } = videoRef.current;
canvas.width = videoWidth;
canvas.height = videoHeight;
const context = canvas.getContext("2d");
if (context === null || videoRef.current === null) {
return null;
}
context.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL("image/jpeg");
}
}));
// カメラのstreamを取得して返すメソッド
const getStream = useCallback(async () => {
// モバイルデバイスが縦向きの場合はアスペクト比を縦横入れ替えて指定する
const aspectRatio = isMobile ? height / width : width / height;
return await navigator.mediaDevices.getUserMedia({
video: {
facingMode: "environment",
width: {
ideal: IDEAL_VIDEO_WIDTH
},
aspectRatio
},
audio: false
});
}, [width, height]);
useEffect(() => {
let stream: MediaStream | null = null;
let video = videoRef.current;
// 取得したstreamをvideo要素に流す
const setVideo = async () => {
stream = await getStream();
if (video === null || !stream) {
return;
}
video.srcObject = stream;
video.play();
};
setVideo();
// streamを停止させる
const cleanupVideo = () => {
if (!stream) {
return;
}
stream.getTracks().forEach((track) => track.stop());
if (video === null) {
return;
}
video.srcObject = null;
};
return cleanupVideo;
}, [getStream]);
return <video ref={videoRef} playsInline width={width} height={height} />;
};
export default forwardRef(Webcam);
ちょっと設定を変えたら録画や録音などもできそうで、何かいいアプリが作れる可能性を感じますね。
Discussion