4
2

はじめに

この記事ではRailsでアップロードした画像をJavascriptでプレビュー表示させる方法についてまとめています。すでに画像のアップロード自体はできている方を前提として書いておりますのでご了承ください。JSについては不慣れなところがあり恐縮です。

対象者

環境

  • Ruby 3.2.3
  • Rails 7.1.3.2
  • esbuildで開発
  • Tailwindcss使用

前提

  • Active Storageの導入が完了している
  • 画像のアップロードができている
  • 1つのフォームでなく複数のフォームでプレビュー表示させたい方

↓参考記事

完成品

1つだけでなく複数のフォームに対応できます。

新規作成画面

Image from Gyazo

プレビュー画像を1枚のみ表示します。

編集画面

Image from Gyazo

すでに登録されている画像を表示させて、新しくアップロードされると変更されます。

プレビュー表示

ここではまずnew.html.erbにプレビュー表示させる方法についてまとめます。

フォーム

フォームとプレビュー表示させる箇所にIDをつけます。

フォームにIDをつける

<%= form_with model: インスタンス変数, id: '任意_form' do |f| %>

プレビュー表示用

<div id="previews_任意" class="">
    <% if @インスタンス変数.persisted? && @インスタンス変数.image.attached? %>
        <div class="preview">
            <%= image_tag @インスタンス変数.image, class: '' %>
        </div>
    <% end %>
</div>

persisted?」と「attached?」は「もし保存されていて、画像がついていたら、」という意味で、条件に一致するならコードが実行されます。

このコードの記述箇所としては、アップロードフィールド(file_field)の近くで良いかと思います。

私の書いたコードはこちらです。

_form.html.erb
<!-- form_withにidをつけます -->
<%= form_with model: @tackle, id: 'tackle_form', class: 'md:mt-10 mt-5' do |f| %>

    <!-- バリデーションエラーメッセージ -->
    <%= render 'shared/error_messages', object: f.object %>
    
    <div class='md:mt-10 mt-5'>
        <%= f.label :name, class: 'text-black font-bold md:text-xl text-lg required' %>
        <%= f.text_field :name, class: 'shadow appearance-none border-2 border-blue rounded w-full md:py-2 py-1 md:px-3 px-2 text-black leading-tight focus:outline-none focus:shadow-outline', placeholder: t('.placeholder.name') %>
    </div>

    <!-- ファイルのアップロードフィールド -->
    <div class='md:mt-10 mt-5'>
        <%= f.label :image, class: 'text-black font-bold md:text-xl text-lg' %>
        <%= f.file_field :image, class: 'shadow appearance-none border-2 border-blue rounded w-full md:py-2 py-1 md:px-3 px-2 text-black leading-tight focus:outline-none focus:shadow-outline' %>
    </div>

    <!-- プレビュー表示用に記述 -->
    <div class="md:mt-10 mt-5" id="previews_tackle">
        <% if @tackle.persisted? && @tackle.image.attached? %>
            <div class="preview">
                <%= image_tag @tackle.image, class: 'preview-image' %>
            </div>
        <% end %>
    </div>

    <div class='md:mt-8 mt-4 text-center'>
        <button type="submit" class="md:text-base text-sm btn font-bold bg-blue text-white hover:bg-blue-700 md:py-2 py-1 md:px-10 px-5 rounded focus:outline-none focus:shadow-outline"><%= t('.submit') %></button>
    </div>
<% end %>

こちらを参考に、ご自身のインスタンス変数、クラスに合わせてください。

また、こちらのフォームはnew.html.erbedit.html.erbで使うために「パーシャル/レンダリング」していますのでご注意ください。

JS

プレビューファイルを作成して読み込みます。

preview.js

app>javascript>preview.jsを作成します。

そして下記のように記述してプレビューを機能させます。

preview.js
document.addEventListener('turbo:load', function() {
  const forms = document.querySelectorAll('form[id$="_form"]');

  forms.forEach(form => {
    const fileField = form.querySelector('input[type="file"][name$="[image]"]');
    if (!fileField) return;

    const previewList = form.querySelector(`#previews_${form.id.split('_')[0]}`);

    fileField.addEventListener('change', function(e) {
      const alreadyPreview = previewList.querySelector('.preview');
      if (alreadyPreview) {
        alreadyPreview.remove();
      }

      const file = e.target.files[0];
      const blob = window.URL.createObjectURL(file);

      const previewWrapper = document.createElement('div');
      previewWrapper.setAttribute('class', 'preview');

      const previewImage = document.createElement('img');
      previewImage.setAttribute('class', 'preview-image');
      previewImage.setAttribute('src', blob);

      previewWrapper.appendChild(previewImage);
      previewList.appendChild(previewWrapper);
    });
  });
});

書いている内容はこちらの記事とほぼ同じですが、複数のフォームで使うために変えています。

一部のみ解説すると

  • 2行目:id_formで終わるフォームを全て取得
const forms = document.querySelectorAll('form[id$="_form"]');
  • 4行目:input[type="file"][name$="[image]"]を持つファイル入力フィールドを探す
const fileField = form.querySelector('input[type="file"][name$="[image]"]');

具体的に<%= f.file_field :image, class: '' %>を取得します。

  • 6行目:プレビューを表示するためのエリアを特定し、idpreviews_で始まる要素を取得
const previewList = form.querySelector(`#previews_${form.id.split('_')[0]}`);

同じWebページにおいてID一意(ユニーク) である必要があります。重複がないようにします。

preview.js読み込み

app>javascript>application.jsで読み込みます。

import "./preview"

プレビューサイズの調整

Image from Gyazo

検証モードよりpreview-imageというクラスが当たっているので、直接CSSファイルに指定します。

application.tailwind.css
.preview-image {
  width: 100%;
  height: auto;
}

ここは好きなようにCSSを当ててください。

おわりに

JSは難しいですが、なんとかできました。この記事がお役に立てば嬉しいです。

この記事を通じて気づいたことやご意見がありましたら教えてくださいますと幸いです。

参考

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2