COBAKURA.COM

cropper.jsの使い方 〜sweetalert2モーダルに乗せて〜

cropper.jsというライブラリで画像をクロップする方法です。
sweetalert2というモーダルを表示するライブラリと組み合わせて、モーダル上でクロップさせます。
完成イメージはこのサンプルに近いです。

この記事では、SNSのプロフィール編集画面で使う場面を想定しています。

まずはHTMLを以下のように書きます。

sample.html
<!-- 1. 画像ファイル選択 -->
<label>
    <div>
        <figure>
            <img src="現在保存されているアイコンの画像URL" id="js-cropped-icon">
        </figure>
    </div>
    <p>ファイルを選択</p>

    <!-- 画像ファイル選択用inputタグ(画面には非表示) -->
    <input
        type="file"
        accept="image/*"
        class="is-hidden"
        onclick="this.value=null;"
        onchange="cropIconImage(event);"
        id="input-1"
        >
</label>

<!-- 2. クロップ後画像保持用のinputタグ -->
<input type="file" name="icon" id="input-2" accept="image/*" class="is-hidden">

1のlabelタグで囲われた範囲をクリックするとアップロードする画像ファイルを選択を出来ます。
labelタグの中にinputタグを入れていますが、画像ファイルを選択するとそのファイルがこのinputタグの値としてセットされます
このinputタグは非表示にし、画面上は現在保存されているアイコンを表示します。
ファイルを選択しようとしたタイミングonclick イベント、ファイルを選択したタイミングonchange イベントが発火します。

input-2 のinputにクロップ後の画像がセットをするようにします。これをフォームで送信します。

次に、JavaScriptファイルは以下のようになります。画像ファイルが選択されたタイミングで実行される cropIconImage 関数を定義しています。

sample.js
function cropIconImage(event) {
  const fileReader = new FileReader()
  fileReader.onload = function () {  // 選択した画像が読み込まれたら
    Swal.fire({  // sweetalertのモーダルを表示する
      html: `
          <!-- プレビュー (モーダル内のHTML) -->
          <figure class="image is-128x128 mb-3 mx-auto">
            <img id="js-cropper-preview" class="is-rounded is-bordered">  <!-- クロップされた画像のプレビュー -->
          </figure>
          <img id="js-cropper-target">  <!-- クロップ対象 -->
        `,
      confirmButtonText: '確定',
      confirmButtonColor: '#485fc7',
      showCancelButton: true,
      cancelButtonText: 'キャンセル',
      willOpen: () => {  // モーダル表示前に実行される処理
        const cropperTarget = document.getElementById('js-cropper-target')
        // 選択された画像をクロップ対象として表示.
        cropperTarget.src = fileReader.result;
        // クロップ対象画像にcropperを適用.
        new Cropper(cropperTarget, {
          // cropper.jsのオプション
          aspectRatio: 1,
          viewMode: 1,
          dragMode: 'move',
          guides: false,
          center: false,
          crop(event) {
            const croppedCanvas = cropperTarget.cropper.getCroppedCanvas()
            document.getElementById('js-cropper-preview').src = croppedCanvas.toDataURL()
          },
        })
      },
    }).then((result) => { 
      if (result.isConfirmed) {  // クロップ処理が確定されたとき
        const cropperTarget = document.getElementById('js-cropper-target')
        const croppedCanvas = cropperTarget.cropper.getCroppedCanvas()
        // クロップ後の画像を表示
        document.getElementById('js-cropped-icon').src = croppedCanvas.toDataURL()
        // クロップ後の画像をinputタグの値として設定
        croppedCanvas.toBlob(function (imgBlob) {
          const croppedImgFile = new File([imgBlob], 'cropped.jpeg', { type: 'image/jpeg' })
          const dt = new DataTransfer()
          dt.items.add(croppedImgFile)
          document.getElementById('input-2').files = dt.files
        })
      }
    })
  }
  fileReader.readAsDataURL(event.target.files[0])
}

モーダルの確定ボタンが押された時に、 result.isConfirmed の処理が実行されます。
document.getElementById('js-cropped-icon').src = croppedCanvas.toDataURL() でアイコン画像が更新され、 document.getElementById('input-2').files = dt.files でinputの値も更新されます。これにより、form送信時にクロップ後の画像が送信されるようになります。

おまけ

labelでinputを装飾する今回のやり方はベストなやり方とは言えないので参考記事のような対応をすると良い。