ドラッグアンドドロップでフロントエンド(React)からサーバーサイド(Node.js)に
リクエストを送ってS3に画像を保存するやり方を纏めました。
やること
フロントエンドで画像データを取得、サーバーサイドで受け取った画像データをS3に保存する
※リクエストを投げる、受け取る辺りの処理は省いています
使用ライブラリ
フロントエンドで画像データを取得する
画像をドロップアンドドラッグする領域を作成
import "./styles.css";
export default function App() {
return (
<div className="drop-area">
<h1>画像をドラッグ</h1>
</div>
);
}
.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
とボタンでファイル選択をするgetInputProps
をuseDropzone
から取得
4.ドロップする領域のタグの属性に{...getRootProps()}
を追加する事でドラッグアンドドロップしてファイル情報を取得する
5.選択して画像をアップするボタンを作成し属性に{...getRootProps()}
を設定しタグ内にinputタグを設置。
inputタグにの属性には{...getInputProps()}
を設定する
6.画像がアップされている状態かを判別するフラグを用意しアップされていれば画像を表示する(ここでは一旦空にします)
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>
);
}
ドラッグした画像をbase64に変換する
取得したファイルの内容にアクセスする為に、FileReaderAPIを使用する必要があります。
- FileReaderAPIからbase64にエンコードして画像を読み込みます
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タグに割り当てる変数に格納すればプレビューが表示されるはずです
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
に格納した画像データをサーバに送ればフロント側の実装は完了
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>
);
}
こんな感じの動きになるはずです
このリサイズしたbase64をサーバーサイドにリクエストを投げます
サーバーサイドでフロントから受け取った画像データをS3に保存する
aws-sdkでプログラムからAWSにアクセス
- AWS SDK for JavaScriptをインストール
- S3にバケットを作成
- フロントエンドから受け取ったbase64をデコードする
- AWSのアクセスキーとシークレットキーを使ってS3にアクセスする
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}