While creating the portfolio site, I had an image upload function, but there was a problem that it was difficult to understand whether any image could be selected properly because there was no preview function.
Therefore, I decided to add an image preview function and implemented it.
Although there are some issues, I have implemented it, so I would like to summarize it.
--The selected file is displayed in large size as a saved image --Press the "Change image" button → Show / hide the preview image and input tag with toggle
View
erb:app/views/posts/_form.html.erb
<%= form_with(model: post, local: true) do |form| %>
<!--···abridgement···-->
<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>Change the image
</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>
<!--···abridgement···-->
<% end %>
JS
ʻThe this.noPhotoImgPath
of the ImgUplorader` class sets any path.
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 Set image path
}
/*
* 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;
}
}
}
The following operation verification was performed on Chrome, Firefox, and Safari.
--If the selected image name is long, the file name will be long horizontally and exceed the wrapping HTML element. --After selecting an image, press the "Cancel" button → It would be nice to be able to add a function to return the preview image to noptho.
Next time, I would like to implement an image preview function that overcomes the above issues.
[Rails] Implementation of image preview function --Qiita
Bootstrap4 custom-file mutiple image preview
Recommended Posts