How to show images before uploading them in Vue.js?

Viewed 4132

Following this tutorial, I've made this component which uploads multiple images at once, and shows the filenames to be uploaded.

That works great, but I'm wondering how can I show the photos (ideally resized to certain max width) instead of their filenames?

      <div v-if="showUploadPhotoModal">
        <transition name="modal">
          <div class="modal-mask">
            <div class="modal-wrapper">

            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header"><strong>{{username}}</strong>&nbsp;&nbsp;
                        <h6 class="modal-title">Upload Photos <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-image"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
                        </h6>
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true" @click="showUploadPhotoModal = false">&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">


                        <form @submit.prevent="sendFile" enctype="multipart/form-data">
                              <div class="form-group">
                                  <textarea class="form-control" rows="5" v-model="title" placeholder="Title"></textarea>

                              </div>

                              <div v-if="fileError" class="alert alert-danger">{{message}}</div>


                             <label class="upload-btn"><br>
                             <span>
                               <img class="logo img-responsive" src="../assets/images/upload.png">
                               <br>
                             </span>
                                Upload Files <input multiple type="file" @change="selectFile" ref="files" hidden>
                                 <br>

                             </label>
                            </form>   

                    </div>

                  <div class="field">
                    <div v-for="(file, index) in files"  :key="index" class="row photo-list" >

                      <div class="col-10 col-push-6">
                        <div :class="`text-right ${file.invalidMessage ?'red':'black'}`">{{file.name}} 

                          <span v-if="file.invalidMessage!=''">&nbsp; <small>{{file.invalidMessage}}</small> </span>
                        </div>

                      </div>
                      <div class="col-2">
                        <div class="text-left">
                          <a @click.prevent="files.splice(index, 1);uploadFiles.splice(index, 1)" class="remove-btn"> <strong >&times;</strong></a>
                        </div>
                      </div>
                    </div>

                  </div>

                    <span @click="sendFile" class="btn btn-primary">Send</span>

                </div>
            </div>

            </div>
          </div>
        </transition>
      </div>





 data () {
    return { 
        showUploadPhotoModal: false,
        files: [],
        uploadFiles: [],
        title: "",
        message: "",
        fileError: false,


    }
  },
  methods: {
    selectFile() {
      const files = this.$refs.files.files;
      this.uploadFiles = [...this.uploadFiles, ...files];
  this.files = [
       ...this.files,
       ..._.map(files, file => ({
         name: file.name,
         type: file.type,
         invalidMessage: this.validate(file)

       }))
  ];

},

validate(file) {
  const allowedTypes = ["image/jpeg", "image/png", "image/gif"];
  if(!allowedTypes.includes(file.type)) { 
    return "Not an image"
  }
  return "";
},

sendFile() {
  const formData = new FormData();

  formData.append('title', this.title);
  formData.append('token', this.token);

  _.forEach(this.uploadFiles, file => {
    if(this.validate(file)==="") {
      formData.append('files', file);
    }
  });

  axios.post(this.BASE_URL + "/api/post", formData)
  .then(res => {
    console.log('res data is:', res.data);
    this.files=[];
    this.uploadFiles=[];
    })
  .catch(err => {
    console.log('error is', err.response.data.error)
    this.fileError = true;
    });
}


  },
1 Answers

In order to handle this, I began with this layout:

<div id="app" v-cloak>
  <input type="file" multiple accept="image/*"
     @change="handleSelects" name="images">
  <div v-for="image in images">
    <img :src="image" class="preview">
  </div>
</div>

I've got an input tag using @change to run a method whenever the user selects some files. Beneath it I've got a v-for to iterate and provide thumbnails for the images.

Here is the JavaScript:

const app = new Vue({
  el:'#app',
  data:{
    images:[]
  },
  methods:{
    handleSelects(e) {
      this.images = [];
      let fileList = Array.prototype.slice.call(e.target.files);
      fileList.forEach(f => {

        if(!f.type.match("image.*")) {
          return;
        }

        let reader = new FileReader();
        let that = this;

        reader.onload = function (e) {
          that.images.push(e.target.result);
        }
        reader.readAsDataURL(f); 
      });
    }
  }
})

So handleSelects does a few things. First, it resets the thumbnails (this.images). It then looks at the files selected in the input, checks to see they are images, and sets up a FileReader for each. For each file it gets a data url which is then added to that images array so previews can be created.

You can see a full CodePen of this here: https://codepen.io/cfjedimaster/pen/VqrGQw?editors=1111

Related