6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactで画像のコピーとダウンロード機能を実装してみた

Last updated at Posted at 2024-02-14

この記事では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使ってキャラクター同士の会話形式で説明してもらうのにハマってます。

6
1
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
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?