Implement Drag and Drop on Images in Vue

Banner for a MediaJam post


Many apps you'll encounter have images that you drag and drop in different locations for a number of reasons. It may help users reorganize their photo libraries or it can simplify a process. That's why we're going to build a front-end app to handle this type of drag and drop functionality.

We're going to build a Vue app that lets us change the order of images using drag and drop and the images will come from your own Cloudinary account. By the time you finish, you'll have a Vue app that calls the Cloudinary API for images and lets you move them around.

Set up Vue project

Make sure you have Vue installed globally first. You can check that you have it installed with the following command:

1$ vue --version
3@vue/cli 5.0.8

If you don't have Vue installed, check out the instructions here. After this check, we can generate a new Vue project with this command:

1$ vue create drag-and-drop
3Vue CLI v5.0.8
4? Please pick a preset: Default ([Vue 3] babel, eslint)

Select the Vue 3 version because this is the most up-to-date version. Once the project has been set up, go to the drag-and-drop directory and briefly look through the files to get a sense of what we're starting with. Go to src > components > HelloWorld.vue. This is the file we're going to modify to implement the drag and drop functionality with the images we fetch from Cloudinary.

If you don't already have a Cloudinary account, go set one up for free here. Upload a few images that you want to play with in this app. Then take note of your cloud name, API key, and API secret in the Dashboard. You'll need these to fetch the images we'll drag around. Make a .env file in the root of your project similar to this to hold those values:

1# .env

One last thing we need to do is install the packages we'll be working with. Run the following command to do that:

1npm i express cors axios vuedraggable

The reason we need some of these packages is that we're going to make a tiny proxy app that makes the request to the Cloudinary API. You can't call this API directly from the front-end because it would expose your API secret, allowing anyone to make requests to your Cloudinary account. So we're going to start by making this proxy.

Making the Cloudinary request for the images

In the root of your project, add a new file called proxy.js and add the following code:

1// proxy.js
2const express = require('express');
3const cors = require('cors');
4const axios = require('axios');
6const app = express();
7const port = 3004;
11app.get('/images', async (req, res) => {
12 const results = await axios.get(
13 `${process.env.CLOUDINARY_CLOUD_NAME}/resources/image`,
14 {
15 headers: {
16 Authorization: `Basic ${Buffer.from(
17 process.env.CLOUDINARY_API_KEY +
18 ":" +
20 ).toString("base64")}`,
21 },
22 }
23 ).then((response) => {
24 const { resources } =;
26 const allImgs = => ({
27 url: resource.secure_url,
28 title: resource.public_id,
29 }));
31 res.json({images: allImgs});
32 });
35app.listen(port, () => {
36 console.log(`Proxy listening on port ${port}`);

We start by importing a few packages and setting up the Express app. Then we create a GET endpoint that makes the Cloudinary API request and fetches the data we need. The Cloudinary API uses those credentials we got from the dashboard earlier. Then we handle the response and get the values we need from it to send to our Vue front-end.

To wrap up this proxy, we listen to the app on the defined port and print a message to the console so we know it's working. That's all we need to get our Cloudinary images. So let's turn our attention to the front-end.

Render the images

A new Vue app comes with a good amount of boilerplate and we're going to take advantage of that. The only file we'll need to edit is src > components > HelloWorld.vue. This will hold the code that fetches images from our proxy, render them, and let us drag them around. So open the HelloWorld.vue file and replace all of the contents with this code:

1// HelloWorld.vue
3 <div class="hello">
4 <h1>{{ msg }}</h1>
5 <draggable
6 :list="images"
7 item-key="title"
8 @start="drag=true"
9 @end="drag=false"
10 >
11 <template #item="{element}">
12 <div>
13 <img :src="element.url" width="150" height="150" />
14 </div>
15 </template>
16 </draggable>
17 </div>
21import draggable from 'vuedraggable'
22export default {
23 name: 'HelloWorld',
24 props: {
25 msg: String
26 },
27 components: {
28 draggable,
29 },
30 data() {
31 return {
32 drag: false,
33 images: []
34 }
35 },
36 methods: {
37 async getImages() {
38 await fetch("http://localhost:3004/images").then(async (data) => {
39 const imageData = (await data.json()).images;
40 this.images = imageData;
41 });
42 }
43 },
44 mounted() {
45 this.getImages()
46 }
50<!-- Add "scoped" attribute to limit CSS to this component only -->
51<style scoped>
52h3 {
53 margin: 40px 0 0;
55ul {
56 list-style-type: none;
57 padding: 0;
59li {
60 display: inline-block;
61 margin: 0 10px;
63a {
64 color: #42b983;

We'll start by walking through what's going on in the <script> first and come back to the <template> in a bit. Everything in the <script> handles the logic, data, and functions that get called. We start by importing the draggable component from a package we installed earlier. Then we define the props for the HelloWorld component, which is some leftover from the boilerplate. This allows us to pass a msg to the component when it's called in App.vue.

Next, we add draggable to components so that it will be accessible when we're ready to render our images in the <template>. Then we set up the data the app will use. These are our state variables. drag is how we'll keep track of when a specific element is being moved around and images is the array that will store the images we fetch from the Cloudinary API.

Fetch images from the Cloudinary proxy

The methods contains all of the functions that we want to use in this component. We only have getImages here and it's an async function that calls the proxy app we created. It returns the image data and updates the images state we declared. Next, there is the mounted lifecycle method. This method executes on the initial page load of a Vue app. We're getting the images from the proxy each time this page is initially rendered.

That covers everything happening in the <script>. Now we can discuss the actual drag and drop implementation displayed to users.

Add drag and drop functionality

Let's look at the <template> at the top of the file to cover what gets rendered to the user. There are only 2 main elements: the <div> that displays the msg prop passed to the component and <draggable> which has a few props we need to set. First, we set the :list prop to our images state to loop through all of the images and make them draggable. We're using :list instead of :model so that the order of images updates when they get moved.

Then we define the key as the image title for the elements we're about to generate. Finally, we toggle the drag/drop state based on our drag state variable. Inside of <draggable>, there's another <template> with the #item defined as element. This element is the current object from the images array. That's how we're able to display all of the images to the user.

Now run your app with and proxy with the following commands:

1npm run serve # starts Vue app
2node proxy.js # starts Express proxy

You should be able to see all of the images load and change their order.

all of the images on the page

image being dragged

This gives you some simple drag and drop functionality that you can expand on to fit any Vue app you're working with.

Finished code

You can take a look at the complete code in the camera-filter folder of this repo. Or you can check it out in this Code Sandbox.


It's always good to learn about different frameworks and how they work compared to ones you're more familiar with. Implementing drag and drop functionality is a common request for all kinds of applications. Learning how to do these kinds of common tasks in different frameworks or using different packages or approaches will help you grow into a more senior developer quickly.


Software Team Lead

Milecia is a senior software engineer, international tech speaker, and mad scientist that works with hardware and software. She will try to make anything with JavaScript first.