the industrial

ブログと言うより自分のためのメモ以外の何モノでもないです。でも読んでくださってありがとうございます。

Rails5 + Cropper.js + carrierwave で作る画像クロップ処理

作ったもの

よく、SNSで利用するようなアイコンをアップロードする際、好きな箇所で切り取る処理を作成してみたのでメモがてら書いていく。

こんな感じの動き。

f:id:omiend:20171003152633g:plain

開発環境について

前回作成した下記エントリーの開発環境をそのまま利用。

omiend.hatenablog.jp

変更点として、users Model と employees Modelの使い方を、思っていたのと間違えていたっぽいので修正。

  • users : deviseで利用するアカウント情報として利用
  • employees : 社員管理

ソース

コチラ に配置しているので、よかったらツッコミおなしゃす。

docker−compose環境があれば、my_strongest_ror/docker/start.sh を叩くだけで動きます(たぶん)。

準備 - Cropperのダウンロード&配置

Cropper.js から cropper.min.jscropper.min.css をダウンロードして、下記に配置。

$ cd my_strongest_ror/
$ tree
.
├── app
│   ├── assets
│   │   ├── javascripts
│   │   │   ├── cropper.min.js
│   │   └── stylesheets
│   │       └── cropper.min.css

Cropper.js本体を アバター編集画面のみで読み込ませたいので、 my_strongest_ror/config/initializers/assets.rb に、下記を追記。

Rails.application.config.assets.precompile += %w(
  *.js        # JSはすべてAsset Pipeline対象とする
  cropper.css # 画像Crop用
)

画面作成

ちょっと適当だけど、下記の様にアバター変更画面を作成。

<h1>アイコン変更画面</h1>

<form>
  <%= hidden_field_tag "employee_id", @employee.id %>
  <%= file_field_tag 'employee[avatar]' %>
  <%= button_tag :submit, :id => "submitBtn" %>
  <% if @employee.persisted? && @employee.avatar? %>
    <%= link_to "削除", delete_avatar_path, method: :delete %>
  <% end %>
  <%= link_to "キャンセル", employees_path %>
  <% if @employee.persisted? && @employee.avatar? %>
    <div id="crop_area_box">
      <%= image_tag @employee.avatar, :id => "crop_image", :html => [:width => "600px"] %>
    </div>
  <% else %>
    <div id="crop_area_box"><img id="crop_image" /></div>
  <% end %>
  <div id="crop_preview"></div>
</form>

<!-- Cropper.jsは、当該ページ個別に読み込む -->
<%= stylesheet_link_tag    'cropper.min' %>
<%= javascript_include_tag 'cropper.min' %>
<%= javascript_include_tag 'edit_avatar' %>

<style>
  #crop_area_box {
    display: block;
    width: 600px;
    height: 600px;
    overflow: hidden;
  }
</style>

Cropper.jsを利用するアバター変更画面用のJSを作成する。

$(function(){

  var fileName;

  // 画像ファイル選択後のプレビュー処理
  $('form').on('change', 'input[type="file"]', function(event) {
    var file = event.target.files[0];
    fileName = file.name;
    var reader = new FileReader();
    var $crop_area_box = $('#crop_area_box');
    // 画像ファイル以外の場合は何もしない
    if(file.type.indexOf('image') < 0){
      return false;
    }
    // ファイル読み込みが完了した際のイベント登録
    reader.onload = (function(file) {
      return function(event) {
        //既存のプレビューを削除
        $crop_area_box.empty();
        // .prevewの領域の中にロードした画像を表示するimageタグを追加
        $crop_area_box.append($('<img>').attr({
          src: event.target.result,
          id: "crop_image",
          title: file.name
        }));
        // プレビュー処理に対して、クロップ出来る処理を初期化設定
        initCrop();
      };
    })(file);
    reader.readAsDataURL(file);
  });

  var cropper;
  function initCrop() {
    cropper = new Cropper(crop_image, {
      dragMode: 'move', // 画像を動かす設定
      aspectRatio: 1 / 1, // 正方形やで!
      restore: false,
      guides: false,
      center: false,
      highlight: false,
      cropBoxMovable: false,
      cropBoxResizable: false,
      toggleDragModeOnDblclick: false,
      minCropBoxWidth: 300,
      minCropBoxHeight: 300,
      ready: function () {
        croppable = true;
      }
    });
    // 初回表示時
    crop_image.addEventListener('ready', function(e){
      cropping(e);
    });
    // 画像をドラッグした際の処理
    crop_image.addEventListener('cropend', function(e){
      cropping(e);
    });
    // 画像を拡大・縮小した際の処理
    crop_image.addEventListener('zoom', function(e){
      cropping(e);
    });
  }

  // クロップ処理した画像をプレビュー領域に表示
  var croppedCanvas;
  function cropping(e) {
    croppedCanvas = cropper.getCroppedCanvas({
      width: 300,
      height: 300,
    });
    // `$('<img>'{src: croppedCanvas.toDataURL()});` 的に書きたかったけど、jQuery力が足りず・・・
    var croppedImage = document.createElement('img');
    croppedImage.src = croppedCanvas.toDataURL();
    crop_preview.innerHTML = '';
    crop_preview.appendChild(croppedImage);
  }

  // Submit時に実行するPOST処理
  $('#submitBtn').on('click', function(event){
    // クロップ後のファイルをblobに変換し、AjaxでForm送信
    croppedCanvas.toBlob(function (blob) {
      const fileOfBlob = new File([blob], fileName);
      var formData = new FormData();
      // `employee[avatar]` は `employee` modelに定義した `mount_uploader :avatar, AvatarUploader` のコト
      formData.append('employee[avatar]', fileOfBlob);
      // EmployeeのID取得
      const employee_id = $('#employee_id').val();
      $.ajax('/avatar/' + employee_id + '/update', {
        method: "PATCH", // POSTの方が良いのかな?
        data: formData,
        processData: false, // 余計な事はせず、そのままSUBMITする設定?
        contentType: false,
        success: function (res) {
          // DOM操作にしたほうがいいのかな?その場合、アップロード後に実行するなどのポーリング処理的なサムシングが必要になりそう・・・
          // なので、とりあえず簡単に`location.reload`しちゃう
          location.reload();
        },
        error: function (res) {
          console.error('Upload error');
        }
      });
    // S3にアップロードするため画質を50%落とす
    }, 'image/jpeg', 0.5);
  });

});

正直...

まだちょっとバギーだし、jQuery使いまくってるし、もうちょっとなんとかしたい感満載。

潜水服は蝶の夢を見る

潜水服は蝶の夢を見る (字幕版)

脳梗塞で「ロックトイン・シンドローム」となり、左目しか動かせなくなった実在の人物、ELLEの編集長、ジャン・ドミニック・ボービーを題材にした映画。

その後、左目の”まばたきだけで”自伝を書き上げたのは本当にすごい。

主演はマチュー・アマルリック」という俳優さんで、グランド・ブダペスト・ホテルなどに出演。

結構泣ける。

ダンケルク

https://www.instagram.com/p/BYYG7ycAr22MZBssfBvfL0jbItdap0SACPvwHg0/

※物語の核心には触れないが、ややネタバレあり。ご注意を。

---

なんと、公開前から早くも話題のクリストファー・ノーラン監督+そして監督キャリア史上初の実話を題材にした映画、『ダンケルク』の試写会に当選!

 

というわけで、霞ヶ関辺りにあるワーナーさんの試写室で鑑賞。

 

冒頭から約7割に渡って重圧に包まれる様な音楽をバックに、戦争という悲惨な状況へと観客を敢えて放り込んだようなストーリー展開(つまり、映画の中の当事者でも今何が起きているのかわからない様な状況)。希望と絶望を織り交ぜながら、叙情的なラストまで駆け抜ける流れに、終始、全身の力が入り、とにかく観入った。

 

ダンケルクの戦い」と言うのは、第二次世界大戦中、迫り来るドイツ軍から逃れつつ、最後はフランスのダンケルクに追い込まれた連合軍の、歴史に残る"撤退劇"のこと。

 

それをノーラン監督が映像化したわけだけど、嫌が応にも期待は高まる。

 

ノーラン監督と言えば、「メメント」「ビギンズ」「ダークナイト」「ダークナイト ライジス」「インセプション」そして「インターステラー」などなどに繋がるわけだけど、個人的にそれらの監督作品で共通する事が、どれも映画の作り+それからストーリーにギミックとして「時間軸」が密接に関わっている事だと考えている。

 

ダークナイト」や「ライジス」では少し説明つけにくいのは申し訳無いのだけど、「インセプション」で言うと、夢の階層によって流れる時間の速度が変わったり、「インターステラー」なんかは星毎の重量に関わる時間の流れの違いや、そもそも超ひも理論からくると思われる次元自体の話だったり…と言った具合。

 

今回の『ダンケルク』にもそう言った時間軸をギミックとした演出があり、ノーラン監督ファンにはたまらない映画であることの一因になるのでは?と、感じた。

 

やはり、ノーラン監督は凄い。

パッセンジャー

パッセンジャー [Blu-ray]

クリス・プラットジェニファー・ローレンス主演のSF映画

およそ100年かけ、移住先の星へと航行する宇宙船が舞台。

移住先の星に到着するまであと90年もあるというのに、何故かクリプラだけが目覚めてしまった…という話。

SF好きなのもあるのだけど、非常に面白かった。

何故ココに居るのか(冷凍睡眠していたので)、何が起きてしまったのかと、目覚めてしまうシーンはパニックモノ

なんとか人工知能が案内するも、想定外の事態には対応できず、もう一度冷凍睡眠しようにも不可能。

まあ、というか100年以上も宇宙を航行するのだから、これくらい想定+訓練しておけよ・・・と言ったらこの映画自体作れないか笑

ジェニファー・ローレンスが何故目覚めるのかなどは物語(というか映画)の核心に近いので伏せる。

ちなみに、パッセンジャーズは別の映画で、そちらも観たい笑

パッセンジャーズ(字幕版)