やりたいこと
コンビニ食品の組み合わせ作成し成分表示するポートフォリオを作成しています。
携帯で撮った画像を添付しアップロードしたいのですが、携帯で撮った画像は容量がとても多くサーバーにかなり負担をかけてしまいます。
そこでブラウザで画像表示時に、リサイズしてからサーバーに送信することでかなり負担を減らせると思ったのでこの実装をしたい。
テーブル
テーブル設計は以下を確認。
メイン料理:mainsテーブル
プラス一品料理:subsテーブル
ActiveStorage使って画像をアップロード
mainsテーブルとsubsテーブルの内容を同時にdbに送りたいのでform_objectを使用
調査
javascript canvasを使った画像リサイズで検索。
実装
mainsとsubsの両方に画像を添付したいので、classを用いて作成しました。
view
# mainのfile_field <label class="d-block"> <span class="btn p-2 w-100 food_create_img_btn"> <i class="fas fa-camera-retro"></i> 画像を選択する <%= main.file_field :image, class: "form-control", style: "display:none" %> </span> </label> <canvas id="main_canvas" width="0" height="0"></canvas> # subのfile_field <label class="d-block"> <span class="btn p-2 w-100 food_create_img_btn"> <i class="fas fa-camera-retro"></i> 画像を選択する <%= main.file_field :image, class: "form-control", style: "display:none" %> </span> </label> <canvas id="sub_canvas" width="0" height="0"></canvas>
jsファイル
class Preview { constructor(obj) { this.MAX_WIDTH = 400; this.MAX_HEIGHT = 400; this.$input = document.getElementById(obj.inputId); this.$canvas = document.getElementById(obj.canvasId); this.$input.addEventListener('change', () => { this.previewImg() }) } previewImg() { const $file = this.$input.files[0]; // 画像をリサイズする const image = document.createElement('img') const reader = new FileReader(); reader.onload = (e) => { image.onload = () => { if(image.width > image.height){ // 横長の画像は横のサイズを指定値にあわせる const ratio = image.height/image.width; width = this.MAX_WIDTH; height = this.MAX_WIDTH * ratio; } else { // 縦長の画像は縦のサイズを指定値にあわせる const ratio = image.width/image.height; width = this.MAX_HEIGHT * ratio; height = this.MAX_HEIGHT; } // canvasにwidthとheightの値を代入 this.$canvas; this.$canvas.setAttribute('width', width); this.$canvas.setAttribute('height', height); const ctx = this.$canvas.getContext('2d'); ctx.clearRect(0,0,width,height); // canvasにサムネイルを描画 ctx.drawImage(image,0,0,image.width,image.height,0,0,width,height); } image.src = e.target.result; } reader.readAsDataURL($file); }; } // メインのプレビュー const MainPreview = new Preview({ inputId: 'main_sub_form_image', canvasId: 'main_canvas' }) // サブのプレビュー const SubPreview = new Preview({ inputId: 'main_sub_form_subs_attributes_image', canvasId: 'sub_canvas' })
処理の流れ
reader.onload = (e) => { image.onload = () => { } }
ファイルのロードが完了した時と、画像のロードが完了した時の、それぞれのコールバック関数
const ctx = this.$canvas.getContext('2d');
canvas 要素に描画するための 2D レンダリングコンテキストを提供します。図形、文字、画像、その他のオブジェクトを描画するのに使用します。 canvas 要素には、自分で描画する能力を持っていないので指定したwidthとheightを元にgetContext('2d')リファレンスを用いて画像を表示していきます。
CanvasRenderingContext2D - Web API | MDN
getContext('2d')のリファレンス一覧があります。