LoginSignup
7
6

More than 1 year has passed since last update.

【React】ドラッグアンドドロップでS3に画像アップしたい

Posted at

ドラッグアンドドロップでフロントエンド(React)からサーバーサイド(Node.js)に
リクエストを送ってS3に画像を保存するやり方を纏めました。

やること

フロントエンドで画像データを取得、サーバーサイドで受け取った画像データをS3に保存する

※リクエストを投げる、受け取る辺りの処理は省いています

使用ライブラリ

フロントエンドで画像データを取得する

画像をドロップアンドドラッグする領域を作成

App.tsx
import "./styles.css";

export default function App() {
  return (
    <div className="drop-area">
      <h1>画像をドラッグ</h1>
    </div>
  );
}

styles.css
.drop-area{
  color: gray;
  font-weight: bold;
  font-size: 1.2em;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 400px;
  height: 250px;
  border: 5px solid gray;
  border-radius: 15px;
  margin-bottom: 20px;
}
img{
  height: 240px;
}

react-dropzoneでドラッグアンドドロップできるようにする

1.react-dropzoneをインストールしuseDropzoneをインポートする
2.画像アップで発火するonDropイベントを作成する。ここではアップされた画像の情報をログに出してみます。
3.ドラッグアンドドロップで使用するgetRootPropsとボタンでファイル選択をするgetInputPropsuseDropzoneから取得
4.ドロップする領域のタグの属性に{...getRootProps()}を追加する事でドラッグアンドドロップしてファイル情報を取得する
5.選択して画像をアップするボタンを作成し属性に{...getRootProps()}を設定しタグ内にinputタグを設置。
inputタグにの属性には{...getInputProps()}を設定する
6.画像がアップされている状態かを判別するフラグを用意しアップされていれば画像を表示する(ここでは一旦空にします)

App.tsx
import "./styles.css";
import { useDropzone } from "react-dropzone";
import { useCallback, useState } from "react";

export default function App() {
  const [isUpload, setIsUpload] = useState(false);

  const onDrop = useCallback((acceptedFiles) => {
    const file = acceptedFiles[0];
    setIsUpload(true);

    console.log(file);
  }, []);
  const { getRootProps, getInputProps } = useDropzone({ onDrop });

  return (
    <div>
      <div className="drop-area" {...getRootProps()}>
        {isUpload ? <img src="" /> : <h1>画像をドラッグ</h1>}
      </div>
      <button {...getRootProps()}>
        画像を選択
        <input {...getInputProps()} />
      </button>
    </div>
  );
}

ここまでで画像の情報が取得できました
画面収録 2021-08-06 8.20.33.mov.gif

ドラッグした画像をbase64に変換する

取得したファイルの内容にアクセスする為に、FileReaderAPIを使用する必要があります。

  • FileReaderAPIからbase64にエンコードして画像を読み込みます
App.tsx
  const encodeToBase64 = async (file: File) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => {
      const base64 = reader.result as string;
      console.log(base64)
    };
  };

画像をリサイズしてプレビューを表示する

  • base64に変換した画像をそのままサーバーサイドに送ると容量が大きすぎてHTTP 413 Payload Too Largeエラーが発生するのでcanvasを使ってリサイズします
  • リサイズしたbase64をimgタグに割り当てる変数に格納すればプレビューが表示されるはずです
App.tsx
  const resizeUpload = (base64: string) => {
    const imgType = base64.substring(5, base64.indexOf(';'))
    const img = new Image()
    img.onload = () => {
      const canvas = document.createElement('canvas')
      const width = img.width * 0.5
      const height = img.height * 0.5
      canvas.width = width
      canvas.height = height
      const ctx = canvas.getContext('2d')!
      ctx.drawImage(img, 0, 0, width, height)
      const reSizeData = canvas.toDataURL(imgType)
      setImgSrc(reSizeData);
      console.log(reSizeData)
    }
    img.src = base64
  }

あとは変数imgSrcに格納した画像データをサーバに送ればフロント側の実装は完了

App.tsx
import "./styles.css";
import { useDropzone } from "react-dropzone";
import { useCallback, useState } from "react";

export default function App() {
  const [isUpload, setIsUpload] = useState(false);
  const [imgSrc, setImgSrc] = useState("");

  const resizeUpload = (base64: string) => {
    const imgType = base64.substring(5, base64.indexOf(';'))
    const img = new Image()
    img.onload = () => {
      const canvas = document.createElement('canvas')
      const width = img.width * 0.5
      const height = img.height * 0.5
      canvas.width = width
      canvas.height = height
      const ctx = canvas.getContext('2d')!
      ctx.drawImage(img, 0, 0, width, height)
      const reSizeData = canvas.toDataURL(imgType)
      setImgSrc(reSizeData);
    }
    img.src = base64
  }

  const encodeToBase64 = async (file: File) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => {
      const base64 = reader.result as string;
      resizeUpload(base64)
      console.log(reSizeData)
    };
  };

  const onDrop = useCallback((acceptedFiles) => {
    const file = acceptedFiles[0];
    setIsUpload(true);
    encodeToBase64(file);
  }, []);


  const { getRootProps, getInputProps } = useDropzone({ onDrop });

  return (
    <div>
      <div className="drop-area" {...getRootProps()}>
        {isUpload ? <img className="image" alt="投稿画像" src={imgSrc} /> : <h1>画像をドラッグ</h1>}
      </div>
      <button {...getRootProps()}>
        画像を選択
        <input {...getInputProps()} />
      </button>
    </div>
  );
}

こんな感じの動きになるはずです

画面収録 2021-08-07 11.47.42.mov.gif

このリサイズしたbase64をサーバーサイドにリクエストを投げます

サーバーサイドでフロントから受け取った画像データをS3に保存する

aws-sdkでプログラムからAWSにアクセス

  1. AWS SDK for JavaScriptをインストール
  2. S3にバケットを作成
  3. フロントエンドから受け取ったbase64をデコードする
  4. AWSのアクセスキーとシークレットキーを使ってS3にアクセスする
index.ts
import AWS, { AWSError } from 'aws-sdk'

const s3 = new AWS.S3({
  accessKeyId:アクセスキー,
  secretAccessKey: シークレットキー
})

const uploadS3 = (image: string) => {
  const fileData = image.replace(/^data:\w+\/\w+;base64,/, '')
  const decodedFile = new Buffer(fileData, 'base64')
  const fileExtension = createFileExtension(image)
  const contentType = image
    .toString()
    .slice(image.indexOf(':') + 1, image.indexOf(';'))

  const params = {
    Bucket: バケット名,
    Key: [`KeyName`, fileExtension].join('.'),
    Body: decodedFile,
    ContentType: contentType,
    ACL: 'public-read-write'
  }
  s3.putObject(params, (err: AWSError) => {
    if (err) {
      throw err
    }
  })
}

備考

※S3に保存するparamsオブジェクトの中のKeyは一意でないといけないので複数のデータを保存するときはidなどをKeyに付与しておく
これでS3のバケットのにオブジェクトが作成できたのでurlをimgタグのsrcに指定すればフロントエンドでS3の画像が読み込める。
直接URLを指定することは少ないと思うので、DBにidと拡張子を保存しておいて下記のようにURLを作成すれば動的に画像を読み込めます。
https://バケット名.amazonaws.com/KeyName${id}.${fileExtension}

参考

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6