How to Preview Images Before Uploading Them in Vue

Jan 15 2019 | By Taha Shashtari

Most users these days expect to see the image they've chosen to upload before clicking the upload button.

Not providing this behavior sometimes implies that your app isn't modern or good enough.

You can search for a package that has this behavior. But if you want to have a custom solution for your app, here's how you can do that in Vue.

One of the best ways to provide a reusable solution in Vue is creating a component. So, we're going to create a component called BaseImageInput — which we'll use as a regular input for images.

Before you start, you can check out this CodePen to see what we're going to build:

Preparing the project

Let's get things prepared quickly with Instant Prototyping. We'll need two vue files: App.vue and BaseImageInput.vue.

After creating them, run it from the terminal using this:

vue serve App.vue

In case you're not familiar with "Instant Prototyping", I've already explained it in this article: Test out Your Vue Component Ideas in No Time with Instant Prototyping.

Implementing App.vue

Before we even start implementing BaseImageInput, let's see how it would be used in App.vue.

So, put the following into App.vue:

<template>
  <div class="app">
    <base-image-input v-model="imageFile"/>
  </div>
</template>

<script>
import BaseImageInput from './BaseImageInput'

export default {
  components: { BaseImageInput },
  data () {
    return {
      imageFile: null
    }
  }
}
</script>

In this component we have imageFile data property to hold the value of the chosen file. Also note that we're using v-model here instead of passing imageFile through :value prop and updating it with @input.

How would BaseImageInput work?

Before we start adding any code to BaseImageInput.vue, let's first see how it would work.

As we all know, the way to select a file in HTML is using <input type="file"> element. Since this element doesn't show a preview of the selected image, we need to have another element to display the selected image.

Here's a quick overview of how our HTML would be structured:

<template>
  <div class="base-image-input">
    <span class="placeholder">Choose an Image</span>
    <input type="file">
  </div>
</template>

The user wouldn't interact directly with <input type="file">. We won't show this element, but we'll use it to show the file picker and to read the selected file.

We can programmatically perform a click action on any element we want by calling click() on it.

With that in mind, we'll hide <input type="file"> with display: none and call click() on it when the user clicks on .base-image-input.

That leads us to our next element, .base-image-input. This element isn't only the root element of our component, it's also the element that the user will see and click to select an image. And when the image is selected, this element would display the image as a preview.

So here's how the workflow would be:

  1. The user clicks on .base-image-input.
  2. We call click() on <input type="file"> to open up the file picker.
  3. Once the user selects an image, we listen for @input on <input type="file">, read that file, and then display it as background-image for .base-image-input.

Lastly, we have <div class="placeholder"> which we'll use to display a placeholder when the user hasn't selected any image yet.

So initially, the user would see the placeholder. When he/she chooses an image, we'll hide the placeholder and display the selected image as the background image on .base-image-input.

Implementing BaseImageInput.vue

Now we understand how this component would work, let's implement it.

Let's start with the HTML section:

<template>
  <div
    class="base-image-input"
    :style="{ 'background-image': `url(${imageData})` }"
    @click="chooseImage"
  >
    <span
      v-if="!imageData"
      class="placeholder"
    >
      Choose an Image
    </span>
    <input
      class="file-input"
      ref="fileInput"
      type="file"
      @input="onSelectFile"
    >
  </div>
</template>

Notes about this code:

  • We are using imageData data property to display the selected image as background-image on .base-image-input. We also use it to show and hide the placeholder element.
  • When the user clicks on .base-image-input, we should call chooseImage method, in which we would click <input type="file"> programmatically.
  • When the user chooses an image, we'll call onSelectFile method.

According to these notes we have to define imageData data property and chooseImage & onSelectFile methods.

So, let's put this into <script> section:

export default {
  data () {
    return {
      imageData: null
    }
  },

  methods: {
    chooseImage () {},
    onSelectFile () {}
  }
}

Let's start with the easier one: chooseImage.

chooseImage () {
  this.$refs.fileInput.click()
}

We're just calling click() on <input type="file"> to show the file picker.

Now, let's implement onSelectFile method:

onSelectFile () {
  const input = this.$refs.fileInput
  const files = input.files
  if (files && files[0]) {
    const reader = new FileReader
    reader.onload = e => {
      this.imageData = e.target.result
    }
    reader.readAsDataURL(files[0])
    this.$emit('input', files[0])
  }
}

In this method, we're using FileReader to read the selected file as DataURL. Once the reading is done, we store the result into imageData. Again, we use this value to display the image and hide the placeholder.

We don't actually use this data to upload the image (we can if we want). Instead, we pass the selected file through input event. And that's what we're storing/using in App.vue.

The last thing we have to do is add the styling for this component:

<style scoped>
.base-image-input {
  display: block;
  width: 200px;
  height: 200px;
  cursor: pointer;
  background-size: cover;
  background-position: center center;
}

.placeholder {
  background: #F0F0F0;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #333;
  font-size: 18px;
  font-family: Helvetica;
}

.placeholder:hover {
  background: #E0E0E0;
}

.file-input {
  display: none;
}
</style>

Now, our custom image input component is ready to be used!