cropper.jsを使ってActiveStorageで画像アップロード時に切り抜きを行う

ググってもあんまり出てこなかったのでメモを残しておきます。
JSは Cropper.js を利用しています。ライセンスはMITです。
 

こんな感じ

やり方

cropper.jsを使って画像を表示し、保存ボタン押下時に切り抜くための情報をRailsに送ります。 Rails側では保存前に切り抜き処理を行い、保存するだけ。

view

ライブラリ

yarn add で cropperjs を入れます。

file inputを用意します

Stimulusでmodalを表示する設定も入れておきます。

# show.html.haml
      .uploading-images.pt-3
        = form_with url: user_images_path(@user), local: true, data: { controller: "preview" } do |f|
          .input-group
            = f.file_field :images, class: "form-control override-bs-file image-selection", "data-action": "input->preview#show"

modalで表示

ポイントはhidden_fieldに cropper.js で取得できる切り抜くための値をセットします。
他にもrotateやscaleも取れます。

          #preview-modal.modal{"aria-hidden" => "true", tabindex: "-1", "data-preview-target": "modal"}
            .modal-dialog
              .modal-content
                .modal-header
                  %button.btn-close{"aria-label": "Close", "data-bs-dismiss": "modal", type: "button"}
                .modal-body
                  .modal-preview-content{ "data-preview-target": "content" }
                .modal-footer
                  = f.submit "保存", class: "btn btn-outline-dark small modal-submit"
                  = f.hidden_field :image_x
                  = f.hidden_field :image_y
                  = f.hidden_field :image_w
                  = f.hidden_field :image_h

stimlus

modal表示する際に、input fileから画像を読み取りcropper.jsに渡します。

# app/javascript/controllers/preview_controller.js
import { Controller } from "@hotwired/stimulus"
import * as bootstrap from "bootstrap"
import Cropper from "cropperjs"

export default class extends Controller {
  static targets = ["content", "modal"]
  static bsModal

  initialize() {
    this.bsModal = new bootstrap.Modal(this.modalTarget)
  }

  show(e) {
    if (e.target.value !== "") {
      this.bsModal.show()
      const file = e.target.files[0]
      const blob = window.URL.createObjectURL(file)

      const img = document.createElement("img")
      img.setAttribute("src", blob)
      img.classList.add("preview-img")

      this.contentTarget.innerHTML = ''
      this.contentTarget.appendChild(img)

      new Cropper(img, {
        scalable: false,
        rotatable: false,
        zoomable: false,
        crop(event) {
          document.getElementById("image_x").value = event.detail.x;
          document.getElementById("image_y").value = event.detail.y;
          document.getElementById("image_w").value = event.detail.width;
          document.getElementById("image_h").value = event.detail.height;
        }
      })
    }
  }
}

Rails

ライブラリ

画像編集のために、image_processing をインストールします。
かつてはImageMagickが利用されていましたが、今はlibvipsがデフォルトで利用されるとのとこ。
macbrew install vips でインストールできます。

gem "image_processing", ">= 1.2"

Rails Controller

crop を使います。
crop! で切り抜きが実行されます。

  def create
    user = User.find(params[:user_id])

    uploaded_image = params[:images]
    cropped_image = ImageProcessing::Vips
      .source(uploaded_image.tempfile.path)
      .crop!(params[:image_x].to_f, params[:image_y].to_f, params[:image_w].to_f, params[:image_h].to_f)
    uploaded_image.tempfile = cropped_image

    user.images.attach(uploaded_image)

    redirect_to user_path(user), notice: "画像を登録しました。"
  end

参考

Rails,Cropper,CarrierWave,MiniMagickで画像を任意の位置でトリミングして登録するまで - Qiita