zukucode
主にWEB関連の情報を技術メモとして発信しています。

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;

関連記事