Cropper.js でアップロードした画像をリサイズしたファイルをダウンロードする

Cropper.js でアップロードした画像をリサイズしたファイルをダウンロードする

JavaScript

アップロードした画像のリサイズを手軽にするために cropper.js というライブラリを使ってみました。

やりたかったこと

  • フォームから画像ファイルをアップロード
  • cropper.js で決まったアスペクト比・規定のサイズでトリム
  • トリムした画像を自動ダウンロード

前提条件

  • JS ライブラリを扱ったことがある
  • HTML・CSS の基礎知識がある
この記事を書いた人

かみーゆ/フロントエンドエンジニア

資金ゼロからフィリピンで起業した海外ノマドエンジニア。IT業界10年以上でテクニカルディレクター(技術責任者)・エンジニア講師・ブリッジSEを経てLenzTechnologies Inc.を設立し、代表を務める。CMS concreteCMSエバンジェリスト。テックブログ以外も「磨耗しない人生」や「海外ノマド」のライフスタイルについて発信。好きなものは肉とハイボール。

JS でファイルのアップロード機能を実装

まずはアップロード機能を作ります。今回はinput[type=file]idを振って操作します。

accept属性でアップロードできる画像の種類(png,jpg)を限定します。

HTML
<input type="file" id="fileUpload" accept="image/png, image/jpeg">
JS
  const fileUpload = document.querySelector('#fileUpload');

  fileUpload.addEventListener('change', (e) => {
    const file = e.target.files[0];
    const blobUrl = URL.createObjectURL(file);
    //パスを格納する要素を作成
    const img = document.createElement('img')
    img.src = blobUrl
    //imgタグをinputタグの後ろに追加
    fileUpload.parentNode.insertBefore(img, fileUpload.nextElementSibling);
  });

JS でimg タグを追加してcreateObjectURL()でパスを生成、追加します。挙動としてはアップロードするとどんな画像がアップロードされたかビジュアル的に確認できます。

画像のアップロード

createObjectURL() 静的メソッド | MDN

CANIUSE や ChatGPT によるとcreateObjectURL()現在非推奨 とされているそうです。あえて使った 理由 はこちら。

Cropper.js でトリム機能を追加、データ書き出し

Cropper.js で画像のトリムを行います。操作と、実際にトリムした画像データを出力するまでを解説します。

Cropper.js 公式サイト

Cropper.js 公式サイト

今回は CDN を使います。

cropper.min.js
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.1/cropper.min.js"></script>
cropper.min.css
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.1/cropper.min.css">

cropperjs | cdnjs

Cropper.js でトリム操作できるようにする

Cropper 追加方法はこんな感じ。

Cropper追加
new Cropper(要素,{オプション});

すでにある画像要素にCropper()させたい場合は要素を getElementByIdquerySelector などで引っ張ってきて第一引数に入れれば OK。

Cropper追加
const img = document.querySelector('img');
const cropper = new Cropper(img);
トリム操作できるようにする
const fileUpload = document.querySelector('#fileUpload');
let cropper, img; //変数追加fileUpload.addEventListener('change', (e) => {
  //ファイルを選んでいないときのCropperJS発火防止
  if (e.target.files[0] === undefined) {    return false;  }  const file = e.target.files[0];
  const blobUrl = window.URL.createObjectURL(file);

  if (cropper === undefined) { //インスタンス化されていないときの処理    //パスを格納する要素を作成
    const img = document.createElement('img')
    img.src = blobUrl
    fileUpload.parentNode.insertBefore(img, fileUpload.nextElementSibling);
    cropper = new Cropper(img, {      aspectRatio: 1 / 1,      scalable: false,      zoomable: false,    });  } else { //インスタンス化されており画像の変更があったときの処理    cropper.replace(blobUrl);  }})

画像アップロードと同時にトリム操作可能になります。

Cropper.js でトリム操作できるようにする

オプションとメソッド

オプション
cropper = new Cropper(img, {
  aspectRatio: 1 / 1,
  scalable: false,
  zoomable: false,
});

今回設定したオプションは以下です。固定でアスペクト比 1:1 に切り取りたかったのでaspectRatioをセットしました。

オプション説明初期値
aspectRatioボックスのアスペクト比の固定NaN
scalable画像の拡大・縮小操作できるかtrue
zoomable画像のズーム操作できるかtrue

公式のOptions(英語)です。

画像が再アップロードされて入れ替わったとき、一度インスタンス化したcropperの 画像パスを切り替える処理です。replaceメソッドを使います。

画像の切替処理
let cropper; // インスタンス化前の初期値はundefined

if (cropper === undefined) {
  cropper = new Cropper(要素)
} else {
  cropper.replace(画像パス);
}

公式のMethods(英語)です。

リサイズしたデータを base64 変換

getCroppedCanvasを使って、トリムしたデータを一度canvasにします。toDataURLを使って base64 変換します。

リサイズしたデータをbase64変換
const fileUpload = document.querySelector('#fileUpload');
let cropper, img, trimBtn, base64Data;//trimBtn, base64Data追加fileUpload.addEventListener('change', (e) => {
  //省略
  if (cropper === undefined) {
    // トリムボタンを作成
    trimBtn = document.createElement('button')    trimBtn.type = 'button';    trimBtn.textContent = 'トリム'    // トリムボタンをimgタグの下に挿入
    img.parentNode.insertBefore(trimBtn, img.nextElementSibling);
    cropper = new Cropper(img, {
      aspectRatio: 1 / 1,
      scalable: false,
      zoomable: false,
    });
  } else {
    cropper.replace(blobUrl);
  }
  //トリムボタンをクリックしたらデータ取得  trimBtn.addEventListener('click', () => {    const croppedCanvas = cropper.getCroppedCanvas({ width: 300, height: 300 });    base64Data = croppedCanvas.toDataURL("image/jpeg");    console.log(base64Data);//データ出力を確認  })})
console.log で base64 化できたかを確認します。  console.log出力

base64 データをバイナリ化して自動ダウンロード

リサイズした画像を実際の JPG ファイルに変換してダウンロードします。

base64データをバイナリ化
//省略
trimBtn.addEventListener('click', () => {
  //省略
  base64Data = croppedCanvas.toDataURL("image/jpeg");  const bin = atob(base64Data.replace(/^.*,/, ''));  const buffer = new Uint8Array(bin.length);  for (let i = 0; i < bin.length; i++) {    buffer[i] = bin.charCodeAt(i);  }  //バイナリファイル化  const blob = new Blob([buffer.buffer], { type: "image/jpeg" })})
//省略
  • atob…Base64 文字列をバイナリ文字列へ変換
  • Uint8Array…8 ビット符号なし整数値の配列を生成
  • charCodeAt… メソッドは、指定された位置にある UTF-16 コード単位を表す 0 から 65535 までの整数を返す。
  • Blob(source, option)… Blob コンストラクターは、新たな Blob(Binary Large OBject) を返す。

データがバイナリ化出来たらaタグを生成し画像へのリンクを貼り、強制的にクリックして画像をダウンロードさせます。

バイナリ化したファイルをダウンロード
//省略
  trimBtn.addEventListener('click', () => {
    //省略

    //バイナリファイル化
    const blob = new Blob([buffer.buffer], { type: "image/jpeg" })    //ダウンロード用のaタグを生成    const linkTag = document.createElement('a');    const link = URL.createObjectURL(blob)    linkTag.href = link;    //ファイル名用    const now = new Date();    linkTag.download = `resize-${now.getTime()}.jpg`;    linkTag.textContent = "ダウンロードする";    trimBtn.parentNode.insertBefore(linkTag, trimBtn.nextElementSibling);    //強制的にクリックしてダウンロード    linkTag.click();  })
//省略

300✕300px で resize-1703724887608.jpg とリネームされた JPG ファイルがダウンロードされました。

バイナリ化したファイルをダウンロード

createObjectURL() を使う理由

createObjectURL() は、非推奨という記事も見かけたので ChatGPT に聞いてみました。

ChatGPT
ChatGPT

私の知識の範囲では、2022年1月までの情報しか含まれておらず、最新の情報は提供できません。代わりに、File や Blob を直接使用して処理する方法が推奨されています。

かみーゆ
かみーゆ

え、、2023年更新の記事にも検証結果が載ってたんですが。。。

caniuse

CANIUSE で調べると思いっきり createObjectURL() is no longer available within the context of a ServiceWorker.(ServiceWorker のコンテキスト内では使用できなくなりました。) って書いてありました。

Service Worker はブラウザがバックグラウンドで実行するスクリプト。

今回は Service Worker は関係ないので、あえてcreateObjectURL()を使います。次回また、fileReader() を使った方法をご紹介します。

まとめ・画像は JS で直感的にリサイズできると ◎

今回実は会員証の画像のアップロードさせたくて Cropper.JS を試してみました。

ファイル名、拡張子、アスペクト比、サイズがバラバラだったら受け取ったあとの管理が難しいですしね。

使ってみたら想像以上に便利だったので、使い方をご紹介させていただきました。

この記事が、みなさんのコーディングライフの一助となれば幸いです。

最後までお読みいただきありがとうございました。

おまけ・Cropper.JS のボックスに顔の位置のガイドを付ける

証明写真撮影マシーン(?)で撮影すると顔の位置ガイドってありますよね?

Cropper.JS のボックスにもつけれないかなーと思ったらあっさり出来たのでその方法のご紹介しておきます。

caniuse

CSS だけで OK。サイズ等は適宜変更してください。

顔の位置ガイド
/**ガイドの部分がタッチできなくなるので順番を上に持ってくる */
.cropper-face.cropper-move {
  z-index: 1;
}
/**ガイド */
.cropper-crop-box::before {
  content: "";
  width: 50%;
  display: block;
  height: 60%;
  position: absolute;
  background: rgba(255,255,255,.3);
  z-index: 1;
  left: 25%;
  top: 20%;
  border-radius: 50%
}