React 選択した画像ファイルを画面に表示する
React
+TypeScript
の環境で、選択した画像ファイルを画面に表示するプレビュー機能を実装します。
React クリップボードにコピーしたファイルを貼り付けるで作成したコンポーネントに対して修正します。
画像表示用コンポーネントの作成
ファイル情報をパラメータに受け取り、画像を表示するためのコンポーネントを作成します。
ポイントは、file
オブジェクトからimg
要素のsrc
へ設定する値を取得する部分です。
URL.createObjectURL(file)
で、file
からオブジェクトのurl
を取得できますので、この値をimg
要素のsrc
へ設定します。
Image.tsx
import { useMemo } from 'react';
type Props = {
file: File;
};
const Image = ({ file }: Props) => {
const src = useMemo(() => URL.createObjectURL(file), [file]);
return <img src={src} alt={file.name}></img>;
};
export default Image;
作成したコンポーネントは以下のように使用します。
このサンプルでは、画像ファイル以外のファイルも選択することが可能です。
画像ファイル以外のファイルを選択した場合は画像のプレビューは機能しません。
import { useCallback, useRef, useState } from 'react';
import Image from './Image';
const App = () => {
const [files, setFiles] = useState<File[]>([]);
const attachRef = useRef<HTMLInputElement>(null);
const [dragging, setDragging] = useState<number>(0);
const handleInpuFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files == null) return;
const files = Array.from(e.target.files);
setFiles((current) => current.concat(files));
if (attachRef.current) attachRef.current.value = '';
}, []);
const onDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setDragging((current) => current + 1);
}, []);
const onDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setDragging((current) => current - 1);
}, []);
const onDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = 'copy';
}, []);
const onDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
const files = Array.from(e.dataTransfer.items)
.map((item) => item.getAsFile())
.filter((file): file is File => file !== null);
setFiles((current) => current.concat(files));
}, []);
const onPaste = useCallback(
(e: React.ClipboardEvent<HTMLDivElement>) => {
const file = e.clipboardData.items[0].getAsFile() ?? undefined;
if (file === undefined) return; // ファイルでない場合は処理終了
setFiles((current) => current.concat(file));
},
[]
);
return (
<>
<div>
{files.map((f) => (
<div key={f.name}>
{f.name}
<Image file={f}></Image>
</div>
))}
</div>
<div>
<input type="button" value="参照" onClick={() => attachRef.current?.click()}></input>
<input type="file" style={{ display: 'none' }} ref={attachRef} multiple onChange={handleInpuFileChange}></input>
</div>
<div
style={{ border: 'dashed 1px #000', padding: 20, backgroundColor: dragging > 0 ? '#ccc' : undefined }}
tabIndex={0}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDragOver={onDragOver}
onDrop={onDrop}
onPaste={onPaste}
>
ここにファイルをドロップしてください。
</div>
</>
);
};
export default App;