vue-cropperjsで遊ぼう【画像切り抜き / Vue.js / Composition API】

はじめに

お久しぶりです!

クロスマート株式会社でフロントエンドのタスクを主に担当しております。ナイスガイの福留です。
大腿骨頸部骨折して緊急搬送されたエンジニアが3ヶ月半の入院中でも仕事が出来た訳の執筆後、特に大腿骨周りに異常はなく健康に過ごせております。 日々の散歩とカルシウムは大事ですね。

本日は、弊社プロダクトでも使用しているライブラリであるvue-cropperjsを色々調べてみる記事になります。

また以下から弊社のエンジニアチームによって投稿している記事一覧が閲覧可能です。

xmart-techblog.hatenablog.com xmart-techblog.hatenablog.com

ぜひ読者になるもクリックいただけると嬉しいです!

vue-cropperjsの概要

vue-cropperjsとは、Vue用の画像切り抜きツール(ライブラリ)です。

Cropper.jsというライブラリをVueに最適化させたものであり、画像のサイズを変更したり、回転したり、トリミングしたりすることができます。

具体的には、商品の画像をアップロードし、切り抜き・回転を行い登録するような処理に使用しています。

調査

まずは、あらためて、READMEから情報を攫ってみようかと思います。

(気になったところ、目についたところを適当に抜粋します。)

README

github.com

バージョンについて

Vue Version Package Version
3.x.x >=5.0.0
2.x.x 4.2.0
1.x.x 1.0.3

vue 1系だと1.0.3まで、 vue 2系だと4.2まで、 3系だと5(latest)を使用出来るようです。 対応しないバージョンをinstallしても動作しないのでご注意ください

Props

名称 デフォルト値 概要
containerStyle Object - コンテナのスタイル
src String '' 画像のソース(URL、Base64、Blobなど)
alt String - 画像の代替テキスト
imgStyle Object - 画像のスタイル
viewMode Number - 表示モード
dragMode String - ドラッグモード
initialAspectRatio Number - 初期のアスペクト比
aspectRatio Number - クロップボックスのアスペクト比(幅/高さ)
data Object - クロップデータ
preview previewPropType - プレビュー要素
responsive Boolean true ウィンドウのサイズ変更に応じるかどうか
restore Boolean true キャンバスの状態を復元するかどうか
checkCrossOrigin Boolean true クロスオリジンチェックを行うかどうか
checkOrientation Boolean true Exifオリエンテーション情報をチェックするかどうか
crossorigin String - CORS設定(anonymous、use-credentials)
modal Boolean true モーダルの表示(オーバーレイ)
guides Boolean true ガイド線の表示
center Boolean true センターマーカーの表示
highlight Boolean true クロップボックスのハイライト表示
background Boolean true キャンバスの背景表示
autoCrop Boolean true 自動クロップの有効化
autoCropArea Number - 自動クロップ領域のサイズ
movable Boolean true 画像の移動可能かどうか
rotatable Boolean true 画像の回転可能かどうか
scalable Boolean true 画像の拡大縮小可能かどうか
zoomable Boolean true 画像のズーム可能かどうか
zoomOnTouch Boolean true タッチデバイスでズーム可能かどうか
zoomOnWheel Boolean true マウスホイールでズーム可能か
wheelZoomRatio Number - マウスホイールでのズームの速さを設定
cropBoxMovable Boolean true クロップボックスを移動できるかどうか
cropBoxResizable Boolean true クロップボックスのサイズを変更できるかどうか
toggleDragModeOnDblclick Boolean true ダブルクリックでドラッグモードを切り替えるかどうか
minCanvasWidth Number - キャンバスの最小幅を設定
minCanvasHeight Number - キャンバスの最小高さを設定
minCropBoxWidth Number - クロップボックスの最小幅を設定
minCropBoxHeight Number - クロップボックスの最小高さを設定
minContainerWidth Number - コンテナの最小幅を設定
minContainerHeight Number - コンテナの最小高さを設定

おおよそ、よく使うオプションは以下ではないかと思います。

  • src
    • 画像のソースを指定(URL、Base64、Blob形式)
  • aspectRatio
  • viewMode
    • 画像とクロップボックスの相互作用を制御
  • dragMode
    • ドラッグ操作のモードを選択("crop"、"move"、"none")
  • autoCrop
    • 画像読み込み時に自動クロップボックス作成を設定
  • zoomOnTouch
    • タッチデバイスでピンチジェスチャによるズーム設定
  • zoomOnWheel
    • マウスホイールでのズーム設定

コールバック

名称 概要
ready 画像が読み込まれたときに呼び出されるコールバック関数
cropstart クロップが開始されたときに呼び出されるコールバック関数
cropmove クロップが移動されたときに呼び出されるコールバック関数
cropend クロップが終了されたときに呼び出されるコールバック関数
crop クロップが完了したときに呼び出されるコールバック関数
zoom ズームイベントが発生したときに呼び出されるコールバック関数

それぞれの挙動についてですが、codesandboxを用意しました。

codesandbox.io

consoleから、各コールバックの挙動や、カスタムイベントの型について観察することが出来ます。

実装編

とりあえずsandboxで公式exampleを動かしてみました。

codesandbox.io

Vue3 (Composition API ver.)

codesandbox.io

これらを見るとわかりますが、vue-cropperの最大の旨味としては、簡単に画像編集機能を実装できるだけでなく、refを使用してコンポーネントにアクセスし、イベントを取得したり、現在の画像の状況を取得できることです。

画像編集機能において、柔軟性や拡張性をもたらすことが出来ます。

応用編

またそれらを利用し、以下のような実装を行うことが可能です。

円形切り取り

円形切り取りとは言いますが、実際に円形の画像を生成するというより、正方形に画像を出力・表示する際に円形表示するという手法を取ります。

さまざまなSNSプロフィール画像等が円形切り取りの例ですが、画像URLを直接表示すると四角形の画像が表示されることがあります。

具体的には上のようなExampleから、切り取りエリア・プレビューを円形に表示するようなCSSを実装します。

例えば、以下のようなものです。

.cropper-view-box,
.cropper-face {
  border-radius: 50%;
}

Cropper.jsの機能で追加される切り取りエリアに、.cropper-view-box, .cropper-face等のクラスが振られるように設定されているため、上書きすることが可能です。

プレビューの表示も同様に、受け取った画像の表示をborder-radius: 50%することでSNSのアイコンの設定画面を実装することが可能です。

画像サイズの自動調整

ユーザーのアップロードフォームにvue-cropperjsのコンポーネントを設置する場合、およその割合でアップロードされた画像・作成された画像をデータベースないしストレージサービスに保存すると思われます。

その時、ユーザーが好き勝手に画像をアップロードしてしまうと、サーバーの容量を圧迫したり、追加で料金が必要となる場合があります。

対策としては、アップロードされた時や登録時にファイルの容量をチェック・一定量を超える容量であればエラーメッセージをアラート形式で表示する、等があります。

しかし、画像をアップするために選択・切り取りを行った後にそのようなエラーが出て、作業がやり直しとなった場合はやるせない気持ちになります。私なら悲しく感じます。

よって、容量や通信がシビアな場合、vue-cropperjsやその周辺で画像のリサイズをおこなってしまうのが良いかと考えます。

例えば、以下のような実装になります。

      async cropHandler() {
        // 切り取り時に切り取られた画像を取得
        const cropImage = this.$refs.cropper.getCroppedCanvas().toDataURL();

        this.resize(
          cropImage,
          70,
          70,
          await function (image) {
            // リサイズされた画像データが入っている
            return image;
          }
        );
      },

      resize(cropImage, width, height, callback) {
        const imageType = cropImage.substring(5, cropImage.indexOf(";"));
        const image = new Image();
        image.onload = function () {
          let canvas = document.createElement("canvas");
          canvas.width = width;
          canvas.height = height;
          let ctx = canvas.getContext("2d");
          ctx.drawImage(image, 0, 0, width, height);
          const ImageDst = canvas.toDataURL(imageType);
          callback(ImageDst);
        };
      },

切り取られた画像を取得し、仮想のcanvasに特定の画像サイズで描画のみせずに出力・仮想のcanvasから再度取得し画像サイズをある程度削る、というものです。

こういった応用的な実装を行うことで、vue-cropperjsを使い一段階上のアプリケーションを開発することが可能かと思います。

最後に

最後まで読んでいただいて、ありがとうございました!
弊社ではバックエンド、フロントエンドエンジニアの方を募集しています。
社員を第一に考える、とても働きやすくチャレンジしやすい・スキルを上げながら働くことの出来る会社です。
クロスマート株式会社について気になった方がいらっしゃいましたら、以下のリンクから「話を聞きに行きたい」をお願いします!
www.wantedly.com