jquery-cropper を使ってみる。

画像のcrop(切取)を、GitHub - fengyuanchen/jquery-cropper: A jQuery plugin wrapper for Cropper.js.
を使って必要最低限の機能を試してみる。
HTMLページは以下のような画面レイアウト
f:id:posturan:20190107222911j:plain
BootStrap を使う。アイコンには fontawesome を使う。
HTMLのヘッダは、以下のようにする。
必要なのは、、
cropper.min.css
cropper.min.js
jquery-cropper.min.js

<meta charset="UTF-8">
<meta http-equiv="content-language" content="ja">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>cropper.html</title>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" >
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/>
<link href="css/cropper.min.css" rel="stylesheet">
<link href="sample.css" rel="stylesheet">
<script src="../js/jquery-3.3.1.min.js" type="text/javascript"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script src="js/cropper.min.js" type="text/javascript"></script>
<script src="js/jquery-cropper.min.js" type="text/javascript"></script>
<script src="sample.js" type="text/javascript"></script>

HTMLコンテンツ部分、
イメージタグ img wicket:id="img" id="img" src="picture.jpg" は、Wicket 使用した場合の画像初期表示で
Java Page クラスで適切に画像リソースを割り当てる。静的ページなら、wicket:id 不要で id属性のみ
div class="docs-data" は、敢えて非表示になるように、CSSで設定する。
切り取りダイアログは、BootStrap のモーダルダイアログである。
div class="modal fade docs-cropped" id="getCroppedCanvasModal"
モーダルダイアログ表示サイズは、切取りサイズによって変わってしまうので
ある程度レスポンシブ表示であることが要求されるので、BootStrap のモーダルダイアログを
使用するのは、良いセンスである。
でもBootStrap のモーダルダイアログは、表示したダイアログをドラッグして動かせそうもないので、
(やり方があるのかもしれないが、まだ良くわからない)
Bootstrap モーダルをドラッグ移動可能にする - Oboe吹きプログラマの黙示録

困るのだが、そもそも画面を表示して切取り(CROP)を行うこと自体、
大きい画像に対して切り取るケースがよく想定されるであろう。ドラッグして隠れた部分を再確認、
見比べるにもドラッグ可能領域も狭いことが想定されてドラッグの意味もあまりないかもしれない。

<div class="container nav docs-buttons">
   <div>
      <button id="resetcrop" type="button" class="btn btn-primary">reset</button>
   </div>
   <div>
      <button id="setcrop" type="button" class="btn btn-primary">crop</button>
   </div>
   <div>
      <button id="getData" type="button" class="btn btn-primary" disabled="disabled">Get</button>
   </div>
   <div class="docs-toggles btn-group d-flex flex-nowrap" data-toggle="buttons">
      <label class="btn btn-primary active">
         <input type="radio" name="aspectRatio"
             value="1.7777777777777777" class="sr-only">
         <span> 16:9 </span>
      </label>
      <label class="btn btn-primary">
         <input type="radio" name="aspectRatio"
             value="1.3333333333333333" class="sr-only">
         <span> 4:3 </span>
      </label>
      <label class="btn btn-primary">
         <input type="radio" name="aspectRatio" value="1" class="sr-only">
         <span> 1:1 </span>
      </label>
      <label class="btn btn-primary">
         <input type="radio" name="aspectRatio"
             value="0.6666666666666666" class="sr-only">
         <span> 2:3 </span>
      </label>
      <label class="btn btn-primary">
         <input type="radio" name="aspectRatio" value="NaN" class="sr-only">
         <span> Free </span>
      </label>
   </div>
   <div>
      <label class="btn btn-primary btn-upload"
             for="inputImage" title="Upload image file">
         <input type="file" class="sr-only" id="inputImage"
             name="file" accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff">
         <i class="fa fa-upload"></i>
      </label>
   </div>
</div>
<div class="img-target">
   <img wicket:id="img" id="img" src="picture.jpg">
</div>
<!-- ============= modal ============= -->
<div class="modal fade docs-cropped" id="getCroppedCanvasModal" 
aria-hidden="true" 
aria-labelledby="getCroppedCanvasTitle" role="dialog" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="getCroppedCanvasTitle">Cropped</h5>
        <button type="button" class="close" 
         data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body"></div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" 
         data-dismiss="modal">Close</button>
        <a class="btn btn-primary" id="download" 
href="javascript:void(0);" download="cropped.jpg">Download</a>
      </div>
    </div>
  </div>
</div>
<!-- ===============================  -->
<div class="docs-data">
    <input type="text" id="dataX">
    <input type="text" id="dataY">
    <input type="text" id="dataWidth">
    <input type="text" id="dataHeight">
    <input type="text" id="dataRotate">
    <input type="text" id="dataScaleX">
    <input type="text" id="dataScaleY">
</div>

次に示す JSソース(jquery-cropper 配布元サンプルを流用)を見てのとおり、
crop 表示、切り取りダイアログ表示用に div class="nav docs-buttons"元のソースでは、 配下のHTMLタグ属性に、jquery-cropper の制御属性を書くのが、ミソであった。
でも、私は HTMLにロジックそのものが書かれるのは嫌なので、getData は別にした。
aspect 比率変更が前のソースのままだ。
sample.js ソース

$(function(){
   'use strict';
   var URL = window.URL || window.webkitURL;
   var $image = $('#img');
   var $download = $('#download');
   var $dataX = $('#dataX');
   var $dataY = $('#dataY');
   var $dataHeight = $('#dataHeight');
   var $dataWidth = $('#dataWidth');
   var $dataRotate = $('#dataRotate');
   var $dataScaleX = $('#dataScaleX');
   var $dataScaleY = $('#dataScaleY');
   var uploadedImageType = 'image/jpeg';
   var uploadedImageName = 'cropped.jpg';
   var options = {
      aspectRatio : 16 / 9,
      //viewMode: 3,  // viewMode 無しで端を含められる
      crop : function(e){
         $dataX.val(Math.round(e.detail.x));
         $dataY.val(Math.round(e.detail.y));
         $dataHeight.val(Math.round(e.detail.height));
         $dataWidth.val(Math.round(e.detail.width));
         $dataRotate.val(e.detail.rotate);
         $dataScaleX.val(e.detail.scaleX);
         $dataScaleY.val(e.detail.scaleY);
      }
   };
   var result;
   var uploadedImageURL;

   $('#setcrop').click(function(){
      $image.cropper(options);
      $('#getData').prop('disabled', false);
   });

   $('#resetcrop').click(function(){
      $image.cropper('destroy');
      $(".docs-toggles label:nth-child(1) input[name='aspectRatio']").trigger('click');
      options['aspectRatio'] = 1.7777777777777777
      $('#getData').prop('disabled', true);
   });
   // aspect 比率変更
   $('.docs-toggles').on('change', 'input', function(){
      var $this = $(this);
      var name = $this.attr('name');
      var type = $this.prop('type');
      var cropBoxData;
      var canvasData;
      if (!$image.data('cropper')){
         return;
      }
      if(type === 'radio'){
         options[name] = $this.val();
      }
      $image.cropper('destroy').cropper(options);
   });

   $('#getData').click(function(){
      if ($(this).prop('disabled') || $(this).hasClass('disabled')){
         return;
      }
      result = $image.cropper('getCroppedCanvas');
      // Bootstrap's Modal
      $('#getCroppedCanvasModal').modal().find('.modal-body').html(result);
      if (!$download.hasClass('disabled')){
         download.download = uploadedImageName;
         $download.attr('href', result.toDataURL(uploadedImageType));
      }
   });

   // Import image
   var $inputImage = $('#inputImage');
   $inputImage.change(function(){
      var files = this.files;
      var file;
      if (files && files.length){
         file = files[0];
         if (/^image\/\w+$/.test(file.type)){
            uploadedImageName = file.name;
            uploadedImageType = file.type;
            if (uploadedImageURL){
               URL.revokeObjectURL(uploadedImageURL);
            }
            uploadedImageURL = URL.createObjectURL(file);
            $image.cropper('destroy').attr('src', uploadedImageURL).cropper(options);
            $inputImage.val('');
         }else{
            window.alert('Please choose an image file.');
         }
      }
   });
   // モーダルドラッグ可能にする
   $("#getCroppedCanvasModal").draggable({ cursor: "move" });
});

CSSソース

@charset "UTF-8";

.nav{
   display: -webkit-flex;
   display: -moz-flex;
   display: -ms-flex;
   display: -o-flex;
   display: flex;
}
.nav div{ margin: 10px; }
.docs-data{
   width: 300px;
}

/****************/
.img-target{
   width: 800px;
   height: 400px;
}
img{
   width: 100%;
   height: 100%;
}
.docs-cropped .modal-body > canvas {
  max-width: 100%;
}

/* x,y,scale,width,height 値を非表示にする */
.docs-data{
   display: none;
}

切り取り実行、「Get」ボタンを押した時に以下のように
モーダルダイアログが表示される。
f:id:posturan:20190107225556j:plain

(注意)
jquery-cropper は、IE11 では動かない。
静的に作って、file::/ ~ で試す場合、最初に、 img タグ指定した初期表示では
DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
は、避けられない。ローカルPCでなくて、やはりサーバに置いて、http or https プロトコル
表示しないとならない。
静的に作って、file::/ ~ で試す場合でも、上に書いた // Import image 以下の
input type=file で画像を読み込ませる処理が走れば、HTMLの imgタグも、BLOBオブジェクトでDOMが生成されて

<img id="img" src="blob:null/225c8363-4b91-43ed-b36e-6b2b016a8470" class="cropper-hidden">

CORS の問題、キャンバスの汚染なく無事、切取り画像を downloadボタンに href にセットできる。