2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rails・jQueryで画像プレビュー機能の実装をしてみた

Last updated at Posted at 2020-11-02

はじめに

ポートフォリオのサイトを制作中、画像アップロード機能をつけていたのですが、プレビュー機能がないため、ちゃんと任意の画像が選択できていたのか分かりづらい問題がありました。

そのため画像プレビュー機能をつけてみようと思い、実装してみました。

課題は何点かあるものの、実装できたので、まとめていきたいと思います。

前提

  • Ruby on Rails 6.0.2
  • jQiery 3.5.1
  • Bootstrap4

機能詳細

画像アップロードの流れ

  1. ファイル入力のinputタグ押下
  2. 画像選択
  3. プラビュー画像を選択した画像に差替

バリデーション時・投稿編集時

  • 選択したファイルは保存された画像として大きく表示
  • 「画像を変更する」ボタン押下→プレビュー画像とinputタグをtoggleで表示・非表示

実装

View

app/views/posts/_form.html.erb

<%= form_with(model: post, local: true) do |form| %>

	<!--・・・省略・・・-->

	<div class="form-file custom-file mb-3">
    <% if post.image.present? %>
      <div class="form-image-uploader__saved-img">
        <span class="form-image-uploader__saved-img-inner">
          <%= image_tag post.image.to_s %>
        </span>
      </div>
      <div class="block-toggle">
        <div class="block-toggle__press">
          <div class="btn btn-outline-secondary">
            <i class="fas fa-arrow-circle-down"></i> 画像を変更する
          </div>
        </div>
        <div class="block-toggle__content" style="display:none;">
          <label for="post_image" class="form-image-uploader__preview">
            <%= image_tag 'nophoto.jpg' %>
          </label>
          <%= form.file_field :image, class:'form-image-uploader__save' %>
          <%= form.hidden_field :image_cache, class:'form-image-uploader__cache' %>
        </div>
      </div>
    <% else %>
      <label for="post_image" class="form-image-uploader__preview">
        <%= image_tag 'nophoto.jpg' %>
      </label>
      <%= form.file_field :image, class:'form-image-uploader__save' %>
      <%= form.hidden_field :image_cache, class:'form-image-uploader__cache' %>
    <% end %>
  </div>

	<!--・・・省略・・・-->

<% end %>

JS

ImgUploraderクラスのthis.noPhotoImgPathは任意のパスを設定します。

app/javascript/packs/application.js

/*Slide Toggle
------------------------------------------------------*/
$(document).on('turbolinks:load', () =>{
  $('.block-toggle__press .btn').on('click', event =>{
    $(event.currentTarget).parent('.block-toggle__press').next('.block-toggle__content').slideToggle(700);
  });
});

/*Image uplorader
------------------------------------------------------*/
$(document).on('turbolinks:load', () =>{

  const imgUplorader = new ImgUplorader;
  imgUplorader.copyToSaveInput();

});

class ImgUplorader{
  constructor(){
    this.selectorPreview = '.form-image-uploader__preview';
    this.selectorSave = '.form-image-uploader__save';
    this.selectorCache = '.form-image-uploader__cache';
    this.noPhotoImgPath = '/assets/nophoto.jpg';// <= nophoto画像のパスを設置

  }

  /*
  * Change preview image to nophoto image when image is not selected
  * @param input : Element of current target
  */
  copyToSaveInput(){
    $(document).on('change', this.selectorSave, event => {

      const input = $(event.currentTarget);
      const filesLength = input[0].files.length;
      const cacheDefaultVal = $(input).next(this.selectorCache)[0].defaultValue;

      // Change preview image to nophoto image when image is not selected
      if (this.hasNotImg(filesLength)) {
        this.changeNoPhotoImg(input);
        return;
      }

      // Change preview image to selected image when image is selected
      this.changeSelectedImg(input);

    });
  }

  /*
   * Return true when input doesn't have file
   * @param filesLength : file length of input
   * @return bool
  */
  hasCacheDefaultImg(filesLength){
    if(filesLength == 0){
      return true;
    }

    return false;
  }

  /*
   * Return true when input doesn't have file
   * @param filesLength : file length of input
   * @return bool
  */
  hasNotImg(filesLength){
    if(filesLength == 0){
      return true;
    }

    return false;
  }

  /*
   * Change preview image to nophoto image when image is not selected
   * @param input : Element of current target
  */
  changeNoPhotoImg(input){
    $(input).prev(this.selectorImg).children('img').attr('src', this.noPhotoImgPath);
  }

  /*
   * Change preview image to selected image when image is selected
   * @param input : Element of current target
  */
  changeSelectedImg(input){
    const reader = new FileReader();
    reader.onload = (progressEvent) => {
      $(input).prev(this.selectorImg).children('img').attr('src', progressEvent.currentTarget.result);
    }

    const file = input[0].files[0];
    reader.readAsDataURL(file);
  }
}

SCSS

app/assets/stylesheets/application.scss

/*form-image-uploader
------------------------------------------------------*/
.form-image-uploader {
  @at-root {
    #{&}__saved-img {
      margin-bottom: 1em;

      @at-root {
        #{&}-inner {
          border: 1px solid #ced4da;
          border-radius: 0.25rem;
          display: inline-block;
        }
      }

      img {
        max-height: 300px;
      }
    }

    #{&}__preview {
      display: inline-block;
      border: 1px solid #ced4da;
      border-radius: 0.25rem;
      position: relative;
      cursor: pointer;

      img {
        max-width: 100px;
        width: auto;
        max-height: 100px;
        height: 100%;

        &:hover {
          opacity: 0.7;
        }
      }
    }
  }
}

/*block-toggle
------------------------------------------------------*/
.block-toggle {
  @at-root {
    #{&}__press {
      cursor: pointer;
      margin-bottom: 0.5em;
    }

    #{&}__content {
      border: 1px solid #ced4da;
      border-radius: 0.25rem;
      padding: 0.5em;
    }
  }
}

検証

下記の動作検証をChrome、Firefox、Safariで行いました。

新規投稿時

_2020-11-03_6.39.46.mov.gif

投稿編集時

_2020-11-03_6.41.47.mov.gif

課題

  • 選択した画像名が長いと、ファイル名が横に長くなり、wrapしているHTML要素を超えてしまうこと
  • 画像選択後、「取消」ボタン押下→プレビュー画像をnopthoに戻す機能を追加できると良い

さいごに

次回は上記の課題を克服した、画像プレビュー機能を実装してみたいです。

参考記事

【Rails】画像プレビュー機能の実装 - Qiita

Bootstrap4 custom-file mutiple 画像プレビュー

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?