この記事ではReactを用いた画像のダウンロード・コピー機能についてお話しします。
ごりごりの初心者向けです。
使用技術について
今回使用する技術は以下です。
- React(v18.2.15)
- TypeScript(v5.0.2)
- axios(v1.5.1)
画像のダウンロード処理
初めは画像のダウンロード処理についてです!
全部のコード載せちゃいます。
これです。
export const downloadImage = async (src: string) => {
try {
const response = await axios.get(src, {
responseType: "blob",
});
const fileName = src.substring(src.lastIndexOf("/") + 1);
saveAs(response.data, fileName);
} catch (error) {
console.error("Image download failed", error);
}
};
意外と短いですね。
引数としてsrcを受け取っているのはダウンロードした時の画像ファイルの名前を指定するためです。
データを非同期で取得
以下の箇所では、srcからデータを非同期で取得しています。
const response = await axios.get(src, {
responseType: "blob",
});
responseTypeをblobとしているので、サーバーから返ってくる値の型はBlobとなります。
主に画像やビデオといった大きなバイナリデータを扱うファイル形式です。
もちょっとBlobについて詳しく
1 : 🐶 にーな
「responseType: blobっていうのは、WebAPIやサーバーからデータを取得するときに使うオプションの一つだよ。Blobっていうのは、Binary Large OBjectの略で、画像や動画などの大きなバイナリデータを扱うためのフォーマットなんだ。このオプションを使うと、テキストではなくバイナリデータとしてデータを直接受け取れるから、画像ダウンロードなどに便利なんだよ。」
2 : 🦊 もんた
「バイナリデータって、具体的にはどういうものなの?」
3 : 🐶 にーな
「バイナリデータっていうのは、0と1のみで表されるデータのことだよ。テキストファイルと違って、人が直接読んで理解することは難しいけど、画像や動画、音声ファイルなど、多くのファイル形式がこのバイナリデータで構成されているんだ。だから、responseType: blobを使うと、こういったファイルを効率良くダウンロードして扱えるようになるんだ。」
4 : 🧑🏾🦱 のぎお
「へぇ〜、blobってなんかスライムみたいだね!でも、どうやってそれで画像をダウンロードするの?」
5 : 🐶 にーな
「いい質問だね!例えば、JavaScriptでaxiosを使って画像をダウンロードするときはこんな感じに書くんだ。」
// axiosを使って画像のURLからデータを取得する
axios.get('画像のURL', {
responseType: 'blob' // レスポンスタイプとしてblobを指定
}).then(function (response) {
// ダウンロードしたblobデータはresponse.dataに格納されている
// ここでダウンロードしたデータを扱う処理を書く
});
6 : 🐭 せんぱい
「responseTypeをblobにすると、どんなメリットがあるの?」
7 : 🐶 にーな
「blobにするメリットとしては、まずデータの損失なく元の状態でファイルをダウンロードできることが挙げられるよ。テキストとして受け取ると、エンコーディングの問題でデータが壊れるリスクがあるけど、blobならその心配がないんだ。さらに、大きなデータも効率的に扱えるから、高画質の画像や長い動画でもスムーズにダウンロードできるよ。」
8 : 🐶 にーな
「まとめると、responseType: blobを指定することで、バイナリデータとして直接ファイルをダウンロードできるんだ。これにより、データの損失なく、効率的に大きなファイルを扱うことができるようになる。画像や動画などのメディアファイルを扱うときに特に便利だね。」
ファイル名を取得する
続いて、以下の箇所ではsrcの最後に出現する/
の位置を探します。
const fileName = src.substring(src.lastIndexOf("/") + 1);
例えばsrcの中身が以下だとします。
https://example.com/images/photo.jpg
そうすると、lastIndexOf("/")
はhttps://example.com/images
と/photo.jpg
の間のスラッシュのインデックスを返します。
これだとsubstring()
した時に、/photo.jpg
を返すのでfilenameとして使えません。
そこで+1
することで開始位置をp
とし、それをsubstring()
することでphoto.jgp
を返し、filenameとして使えるようになるというわけです。
取得したファイル名で画像を保存
サイトはsaveAs()
ですがめちゃくちゃ簡単です。
saveAs(response.data, fileName);
これでユーザーのデバイスにresponse.data(画像のバイナリデータ)
をfileName
で保存します。
画像のダウンロード周りは以上!
画像のコピー処理
続いて画像のコピーですね。
全体はこんな感じです。
export const copyImageToClipboard = async (src: string) => {
try {
const response = await axios.get(src, { responseType: "blob" });
const blob = response.data;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const image = await createImageBitmap(blob);
canvas.width = image.width;
canvas.height = image.height;
if (ctx) {
ctx.drawImage(image, 0, 0);
}
canvas.toBlob(async (newBlob) => {
if (newBlob) {
const clipboardItem = new ClipboardItem({ [newBlob.type]: newBlob });
await navigator.clipboard.write([clipboardItem]);
}
}, blob.type);
} catch (err) {
console.error("Failed to copy on clipboard", err);
}
};
以下より詳しく説明していきます!
画像を取得する
以下では画像のデータを非同期で取得しています。
先ほどの画像ダウンロードの説明でもありましたね。
非同期で取得した画像データをblobという変数に代入しています。
const response = await axios.get(src, { responseType: "blob" });
const blob = response.data;
画像をCanvasに描画する
以下ではcanvas要素を作成し、blobの内容を描画するという作業を行なっています。
canvas.getContext("2d")
では、2Dグラフィックを描画するためのツールを取得しています。
最後のctx.drawImage(image, 0, 0);
で描画する際に使います。
await createImageBitmap(blob)
は先ほど取得したblobデータから画像ビットマップを作成します。
このビットマップは、描画に使用できる画像データです。
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const image = await createImageBitmap(blob);
canvas.width = image.width;
canvas.height = image.height;
if (ctx) {
ctx.drawImage(image, 0, 0);
}
ちなみにcanvasを使用している理由ですが、Blobデータを直接クリップボードにコピーする機能はブラウザによってはサポートされていないからです。
サポートされていないと画像データが不安定になる可能性があるらしく、それを防ぐためにcanvasを使用しています。
ビットマップってなんじゃ?
1 : 🐶 にーな
「画像ビットマップっていうのはね、簡単に言うと画像が点々(ピクセル)でできているということだよ。コンピューターの画面上で見る写真や絵は、全部小さな点の集まりからできているんだ。ビットマップとは、それらの点の色や位置を記録したデータのことを指すんだよ。」
2 : 🦊 もんた
「へー、じゃあ画面上で見えてる画像って、全部小さな点々でできてるんだ。でも、どうやってそれぞれの点の色を記録してるの?」
3 : 🐶 にーな
「いい質問だね!各ピクセルには色情報があって、RGB(赤、緑、青)の値を使って、そのピクセルがどんな色をしているかを表しているんだ。たとえば、RGBで(255, 0, 0)っていう値だったら、そのピクセルは赤色ってことになるよ。」
4 : 🧑🏾🦱 のぎお
「RGBって何?なんか飲み物?」
5 : 🐶 にーな
「はは、飲み物じゃないよ、のぎお。RGBっていうのは Red(赤)、Green(緑)、Blue(青) の頭文字を取ったもので、これらの色を組み合わせることで、私たちが普段見ている様々な色を作り出しているんだ。」
6 : 🐭 せんぱい
「ビットマップのデータはどれくらいの大きさになるの?」
7 : 🐶 にーな
「それは画像の解像度と色深度によって変わってくるよ。解像度が高く、色深度が深いほど、ファイルのサイズは大きくなるんだ。色深度っていうのは、1つのピクセルが表現できる色の数のこと。たとえば、24ビット色深度なら、約1677万色を表現できるよ。」
8 : 🧑🏾🦱 のぎお
「1677万色もあるの?全部見たことある?」
9 : 🐶 にーな
「実際には、私たち人間の目で区別できる色はそれよりも少ないんだけど、コンピューターはとても細かい色の違いを扱えるんだよ。」
10 : 🐶 にーな
「まとめると、画像ビットマップとは、画面上の画像を構成する小さな点(ピクセル)の集まりで、それぞれのピクセルが特定の色情報(RGB値)を持っているってこと。この方式で、デジタルデバイスは様々な画像を表示できるんだよ。」
Canvasの内容をクリップボードにコピーする
最後です。
最後は先ほどcanvas上に画像データをblobデータとして取得します。
非同期処理でnewBlobが取得できたら、それをクリップボードに書き込んでいます。
const clipboardItem = new ClipboardItem({ [newBlob.type]: newBlob });
でコピー前の準備を行い、await navigator.clipboard.write([clipboardItem]);
でクリップボードにコピーをしています。
canvas.toBlob(async (newBlob) => {
if (newBlob) {
const clipboardItem = new ClipboardItem({ [newBlob.type]: newBlob });
await navigator.clipboard.write([clipboardItem]);
}
}, blob.type);
navigatorってなんや?
1 : 🐶 にーな
「navigator
っていうのは、ブラウザが提供するオブジェクトの一つで、ブラウザ自体やその機能に関する情報を扱うためのものだよ。navigator
オブジェクトを使うと、ブラウザのバージョンやユーザーエージェント(ブラウザが自身をどう識別しているか)の情報を取得できるんだ。」
2 : 🦊 もんた
「じゃあ、navigator.clipboard.write
っていうのはどういう意味?」
3 : 🐶 にーな
「それはね、navigator
オブジェクトのclipboard
プロパティを通じて、ブラウザのクリップボードAPIにアクセスする方法の一つだよ。write
メソッドを使うと、ユーザーのクリップボードにデータを書き込むことができる。この場合、[clipboardItem]
という配列に格納されたデータをクリップボードにコピーしているんだ。」
4 : 🧑🏾🦱 のぎお
「クリップボードって何?」
5 : 🐶 にーな
「クリップボードっていうのは、コンピューターで一時的にデータを保存しておく場所のこと。例えば、テキストや画像をコピーして、別の場所にペーストするときに使うんだ。ブラウザが提供するクリップボードAPIを使うと、ウェブページからもそのクリップボードにアクセスできるようになるんだ。」
6 : 🐭 せんぱい
「セキュリティは大丈夫なの?」
7 : 🐶 にーな
「いい質問だね。クリップボードAPIを使うときは、ユーザーの許可が必要だったり、HTTPSで暗号化された安全な接続が必要になることが多いよ。これは、ユーザーのデータを保護するための措置だね。ウェブページが勝手にクリップボードにアクセスして、ユーザーの情報を読み取ったり書き込んだりすることを防ぐためだよ。」
8 : 🐶 にーな
「要するに、navigator
はブラウザの機能にアクセスするためのオブジェクトで、navigator.clipboard.write
はその中のクリップボードAPIを使って、ユーザーのクリップボードにデータを安全に書き込むためのメソッドってわけ。セキュリティのために、ユーザーの許可が必要になることがあるから、使うときは注意が必要だよ。」
オワリ
意外と簡単でしたね。
余談ですが、チャットGPT使ってキャラクター同士の会話形式で説明してもらうのにハマってます。