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

React クリップボードにコピーしたファイルを貼り付ける

React+TypeScriptの環境でクリップボードにコピーしたファイルを貼り付ける方法を紹介します。

React ドラッグ&ドロップでファイルを選択するで紹介したコンポーネントの、ドラッグアンドドロップの要素にフォーカスを当てた状態で、貼り付けをしたときに、そのファイルを追加します。

以下が完成品です。

import { useCallback, useRef, useState } from 'react';

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}</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;

解説

div要素はデフォルトではフォーカスを当てることができないので、tabindex=0の属性を設定して、フォーカスを当てられるようにします。

フォーカスが当たっている状態でctrl+cなどで貼り付けを行うと、onPasteイベントが発生します。

      <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}

onPasteイベントでは、e.clipboardData.items[0]で貼り付けたデータを取得できます。

getAsFileで、ファイルとして取得します。ファイルでない場合はundefinedになるので、その場合は処理を終了します。

  const onPaste = useCallback(
    (e: React.ClipboardEvent<HTMLDivElement>) => {
      const file = e.clipboardData.items[0].getAsFile() ?? undefined;
      if (file === undefined) return; // ファイルでない場合は処理終了
      setFiles((current) => current.concat(file));
    },
    []
  );

以上で、クリップボードから貼り付けでのファイル選択は完了です。

クリップボードからファイルを選択するという用途はあまりないかもしれませんが、例えばslackやteamsなどで実装されているような、スクリーンショットでクリップボードをコピーした画像を貼り付けるような形で、実装するケースがありそうです。


関連記事