ActiveStorageで登録画面や編集画面でプレビューする方法
自分の環境をご紹介
ActiveStorageのセットアップは行っている前提
- rails7
- ruby3.0.2
- Bootstrap5とicon
- JavaScriptはバニラJSです
- AWS S3で画像を保存してます(ここはassets/imagesで保存した画像でもできます)
- ActiveStorageでhas_one_attachedで1つのモデルに1つの画像です。
結論からどんなのができるのか?
新規登録側
新規登録ではS3に保存したデフォルトの画像が最初に表示されます
デフォルトで設定しないとArgumentErrorが出ますので必ずデフォは用意してください
デフォルトは、assets/imagesで保存した画像でもできます。
画像を選択するとプレビューがパッと変わります。
これの何がいいかと言いますと個人的にかっこいいからです。
ってのは冗談で、単純に選択した画像がその場ですぐわかるからですね!
編集画面
もちろんこのように元々保存されている画像もプレビューできます!
画像選択後は新規登録と同じです!
やり方について1つずつ説明します
フォームから
一様報告しておきます、画像のinput部分のみです。
<div class='mb-3'>
<i class="bi bi-image"></i>
<%= f.label :image %>
<%= f.file_field :image, accept: "image/*", id: 'image' %>
<div class='mt-3'>
<% if product.image.attached? %>
<%= image_tag(product.image.variant(resize_to_limit: [200, 200]), id: 'image-preview', class: 'rounded image-resize') %>
<% else %>
<%= image_tag(Rails.application.credentials.dig(:aws, :s3, :default_image_url), id: 'image-preview', class: 'rounded image-resize') %>
<% end %>
</div>
<%= f.file_field :image, accept: "image/*", id: 'image' %>
これで画像を選択する
部分ができます、has_one_attached :image
で設定しているため:image
です
accept: "image/*"
これは、クライアント側で全ての画像形式のみ受け付けるようにしています。
動画とかはダメですよ〜!と検証ができます。
id: 'image'
はJavaScriptでDOM操作で使います。
<% if product.image.attached? %>
は、attached?メソッドでActiveRecordオブジェクトが
データベースに保存されているかどうかを確認してくれます。
編集用と新規用で条件分岐しています。
新規は登録されていないのでfalse
を返します、よって
<%= image_tag(Rails.application.credentials.dig(:aws, :s3, :default_image_url), id: 'image-preview', class: 'rounded image-resize') %>
ここのコードの処理が行われます。
Rails.application.credentials.dig(:aws, :s3, :default_image_url)
ここの部分はS3で保存した
画像をrails credentials
でcredentials.yml.enc
という暗号化された設定ファイルを使って
セキュアな情報を保存しています。(そのままURLを見せないようにする対策ですね!)
id: 'image-preview', class: 'rounded image-resize'
一気に説明します!
idは先ほどと同じJSのDOM操作で使います。
classはBootstrapを使用し、画像に少し丸みのある角にしています。
image-resize
は下で説明してます、カスタムCSSです!
お次はDBに保存されている方、persisted?
メソッドでtrue
を返している方ですね
編集画面の時のプレビューです。
<%= image_tag(product.image.variant(resize_to_limit: [200, 200]), id: 'image-preview', class: 'rounded image-resize') %>
product.image.variant(resize_to_limit: [200, 200])
は、productモデルから
保存された画像を200x200にリサイズしてくれます。
後はほぼ先ほどと同じです!
<%= image_tag(product.image.variant(resize_to_limit: [200, 200]), id: 'image-preview', class: 'rounded image-resize') %>
なんでこっちもリサイズをカスタムで設定しているの??
variant(resize_to_limit: [200, 200]
でリサイズしてますよね?
説明しよう!
variantはActiveStorageが画像を処理する際に使用するメソッドで、
保存される画像の大きさをリサイズしてくれます
着目して欲しいところは保存です
画面を選択しているときは、保存されておりません。
よって選択によってプレビューしている画像も一緒の大きさにしたいのでカスタムCSS使ってます。
保存されていない画像サイズ変更用のCSS
.image-resize {
max-width: 200px;
max-height: 200px;
object-fit: cover;
}
最大横縦幅を200pxにしています。
object-fit: cover
で画像のアスペクト比が保持しつつ指定した
幅に気持ちよくフィットするようにしています
JavaScriptです
document.addEventListener('turbo:load', () => {
const imageInput = document.querySelector('#image');
const imagePreview = document.querySelector('#image-preview');
imageInput.addEventListener('change', (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onloadend = () => {
imagePreview.src = reader.result;
};
if (file) {
reader.readAsDataURL(file);
} else {
imagePreview.src = "";
}
});
});
turbo:load
にすることで,Hotwireが新しいページを読み込み終えた時に発生しその後に実行する関数を定義してます。
これは非常に大事でturbo:load
を設定しないと
いちいち画面をリロードしないとJavaScriptが反映されません。
const imageInput = document.querySelector('#image');
const imagePreview = document.querySelector('#image-preview');
この部分は先ほどのHTMLで設定した、idを指定してそれぞれの定数に保存しています
imageInput.addEventListener('change', (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onloadend = () => {
imagePreview.src = reader.result;
};
imageInput要素(画像を選択する方)でchangeイベントを発生したときに実行する関数を定義しています
ここでのchangeイベントは、userが新しいファイルを選択したときに発生するイベントです。
const file = e.target.files[0];
インデックス番号0番、よって最初に選択された
ファイルを取得しfile
という定数に保存してます
const reader = new FileReader();
FileReaderオブジェクトは、ユーザーが選択したファイルを読み込むために使用されます。
それをrender
定数に保存
reader.onloadend = () => {
imagePreview.src = reader.result;
};
ファイルを読み込んだ後に実行する関数を書いています
FileReaderオブジェクト
が読み込んだ結果(画像URL)をimagePreview要素
(img要素)の
src
属性に設定しています。これにより、選択された画像のプレビューが表示されます。
まとめると、読み込んだファイルの結果をsrc
属性に設定しプレビューしてます。
<img src=''>
srcの中身を読み込んだ画像のURLを動的に変えてるイメージです!
次はuserがファイルを選択している時としていない時の条件分岐です。
if (file) {
reader.readAsDataURL(file);
} else {
imagePreview.src = "<%= Rails.application.credentials.dig(:aws, :s3, :default_image_url) %>";
}
簡単にいうと、選択しているとファイルのデータをURLとして読み込むように
選択していないときはデフォルト画像を表示するようにしています。
今回の場合選択していない時というのは新規登録の時です!
最後に
前から実装してみたい機能だったので無事に実装できてよかったです!!
お疲れ様でした!