アップロードした画像のリサイズを手軽にするために cropper.js というライブラリを使ってみました。
やりたかったこと
- フォームから画像ファイルをアップロード
- cropper.js で決まったアスペクト比・規定のサイズでトリム
- トリムした画像を自動ダウンロード
前提条件
- JS ライブラリを扱ったことがある
- HTML・CSS の基礎知識がある
かみーゆ/フロントエンドエンジニア
JS でファイルのアップロード機能を実装
まずはアップロード機能を作ります。今回はinput[type=file]
にid
を振って操作します。
accept
属性でアップロードできる画像の種類(png,jpg)を限定します。
<input type="file" id="fileUpload" accept="image/png, image/jpeg">
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 で画像のトリムを行います。操作と、実際にトリムした画像データを出力するまでを解説します。
今回は CDN を使います。
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.1/cropper.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.1/cropper.min.css">
Cropper.js でトリム操作できるようにする
Cropper 追加方法はこんな感じ。
new Cropper(要素,{オプション});
すでにある画像要素にCropper()
させたい場合は要素を getElementById
や querySelector
などで引っ張ってきて第一引数に入れれば OK。
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 = 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 変換します。
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 化できたかを確認します。
base64 データをバイナリ化して自動ダウンロード
リサイズした画像を実際の JPG ファイルに変換してダウンロードします。
//省略
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 に聞いてみました。
私の知識の範囲では、2022年1月までの情報しか含まれておらず、最新の情報は提供できません。代わりに、File や Blob を直接使用して処理する方法が推奨されています。
え、、2023年更新の記事にも検証結果が載ってたんですが。。。
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 のボックスにもつけれないかなーと思ったらあっさり出来たのでその方法のご紹介しておきます。
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%
}