How to Create HTML5 Drag and Drop UI in Vue 3

How to Create HTML5 Drag and Drop UI in Vue 3

Drag and drop is a common feature in many applications. It improves a website's interactivity and the user experience by allowing users to move items around by dragging them from one place and dropping them in another.

In this article, we will be building a Vue 3 feature list app with the HTML5 Drag and Drop API. We will be using the Vite build tool to scaffold our project.

Prerequisites

Only a basic knowledge of HTML, CSS, Javascript and Vue.js is required to follow along with this tutorial. You will also need to have Node.js installed on your machine.

Project setup

npm create vue@latest

Press enter to proceed, enter a project name, and say no to other options except for Typescript, Eslint and Prettier.

Run the following commands to install dependencies, format code and run the app:

  cd project-name
  npm install
  npm run dev

Update tsconfig, so we can write javascript

To ensure everyone can follow along, we will disable typescript in this project. We will be adding typescript and typing our code at the end of the project. Edit your tsconfig.app.json file, under the compilerOptions, and add "allowJs": true to enable us to write javascript without types. Your tsconfig.app.json should look like this:

{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "composite": true,
    "allowJs": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Let Vue know we are here for Drag and Drop

Edit the msg props value passed to HelloWorld component in App.vue file to Drag and Drop and save the file. The app should reload and display the new value.

<HelloWorld msg="Drag and Drop" />

Then edit the HelloWorld.vue file and change the h3 tag contents to this:

<h3>
  An Html5 Drag and Drop API project with
  <a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
  <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>

Add Data to render in the app

Our drag and drop functionality will be implemented in TheWelcome.vue component. So, let's add some data to render in the app. Edit the TheWelcome.vue file, import ref from vue and add the data we will use to render our list in the script tag:

import { ref } from 'vue'

const items = ref([
  {
    icon: DocumentationIcon,
    heading: 'Documentation',
    content:
      'Vue’s official documentation provides you with all information you need to get started.'
  },
  {
    icon: ToolingIcon,
    heading: 'Tooling',
    content:
      'This project is served and bundled with Vite. The recommended IDE setup is VSCode + Volar. '
  },
  {
    icon: EcosystemIcon,
    heading: 'Ecosystem',
    content:
      'Get official tools and libraries for your project: Pinia, Vue Router, Vue Test Utils, and Vue Dev Tools.'
  },
  {
    icon: CommunityIcon,
    heading: 'Community',
    content:
      'Got stuck? You should also subscribe to our mailing list and follow the official @vuejs twitter account for latest news in the Vue world.'
  },
  {
    icon: SupportIcon,
    heading: 'Support Vue',
    content:
      'As an independent project, Vue relies on community backing for its sustainability. You can help us by becoming a sponsor.'
  }
])

To prevent typescript errors, remove the lang="ts" attribute from the script tag.

Render the data in the app

The Vue template of TheWelcome.vue component currently contains a list of WelcomeItem components. We will replace this with a list of WelcomeItem components we will be rendering using the data we added in the previous step. Replace the contents of the template tag with this:

<WelcomeItem v-for="(item, index) in items" :key="index">
  <template #icon>
    <component :is="item.icon" />
  </template>
  <template #heading>{{ item.heading }}</template>

  {{ item.content }}
</WelcomeItem>

If you check your browser console, you will notice that the app is throwing an error, '[Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead and should be avoided by marking the component with markRaw or using shallowRef instead of ref'. Replace ref with shallowRef to fix this error.

Update the style of the WelcomeItem component

The WelcomeItem component is currently using the WelcomeItem.vue file. We will update the style of this component to make it look like a card. First, we will add a hover effect to the WelcomeItem component which will also reflect when an item is being dragged over another.

.dragenter, .item:hover {
  outline: 2px solid var(--color-border);
  background-color: var(--color-background-mute);
  border-radius: 15px;
  cursor: pointer;
}

We will also remove the line linking the icons, replace the media query styles in the style tag with this:

@media (min-width: 1024px) {
  .item {
    margin-top: 0;
    border-radius: 0.5rem;
    padding: 0.4rem 1rem 1rem calc(var(--section-gap) / 2);
  }

  i {
    top: 15px;
    left: 20px;
    position: absolute;
    border: 1px solid var(--color-border);
    background: var(--color-background);
    border-radius: 8px;
    width: 50px;
    height: 50px;
  }
}

Add the Drag and Drop attributes

We will use the draggable attribute to make the WelcomeItem component draggable. Edit the TheWelcome.vue file and add the draggable attribute to the WelcomeItem tag like this:

<WelcomeItem
    v-for="(item, index) in items"
    :key="index"
    draggable="true"
  >
    <template #icon>
      <component :is="item.icon" />
    </template>
    <template #heading>{{ item.heading }}</template>

    {{ item.content }}
  </WelcomeItem>

You can now drag the WelcomeItem components around. However, we need to add the drop functionality to make the drag and drop work. Edit the TheWelcome.vue file and add the dragstart, dragover and drop event handlers to the WelcomeItem. The dragstart event fires when an item gets dragged while the dragover with .prevent ensures that we can drop the dragged item. The drop event fires when an item is dropped on another item. Add the event handlers like this:

<WelcomeItem
  v-for="(item, index) in items"
  :key="index"
  draggable="true"
  @dragstart="handleDragStart(index)"
  @dragover.prevent
  @drop="handleDrop(index)"
>
  <template #icon>
    <component :is="item.icon" />
  </template>
  <template #heading>{{ item.heading }}</template>

  {{ item.content }}
</WelcomeItem>

Add the Drag and Drop methods

First, we will create a variable to store the item being dragged. Edit the TheWelcome.vue file and add the draggedItem variable to the script tag like this:

const draggedItem = ref(0)

We will then add the handleDragStart and handleDrop methods to the TheWelcome.vue component. The handleDragStart method will be called when an item is dragged. It will set the draggedItem to the item being dragged. The handleDrop method will be called when an item is dropped. It will remove the draggedItem from the list of items and insert it at the index of the item it was dropped on. Edit the TheWelcome.vue file and add the methods like this:

const handleDragStart = (startIndex) => {
  draggedItem.value = startIndex
}

const handleDrop = (dropIndex) => {
  let tempArr = [...items.value]
  tempArr.splice(draggedItem.value, 1)
  tempArr.splice(dropIndex, 0, items.value[draggedItem.value])
  items.value = tempArr
}

Conclusion

We have successfully implemented drag and drop functionality in our app. You can now drag and drop the WelcomeItem components around. You can find the complete code for this project here. To learn more about the HTML5 drag and drop API, you can check out the MDN docs. You can find the complete code for this project here.