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:
- The user clicks on
.base-image-input
. - We call
click()
on<input type="file">
to open up the file picker. - Once the user selects an image, we listen for
@input
on<input type="file">
, read that file, and then display it asbackground-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 asbackground-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 callchooseImage
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!