如何在vue中使用HTML 5 拖放API


Posted in Vue.js onJanuary 14, 2021

拖放 API 将可拖动元素添加到 HTML,使我们可以构建包含可以拖动的具有丰富 UI 元素的 Web 应用。

在本文中我们将用 Vue.js 构建一个简单的看板应用。看板是一种项目管理工具,使用户可以从头到尾直观地管理项目。 Trello、Pivotal Tracker 和 Jira 等工具都属于看板应用。

设置看板

运行以下命令创建我们的看板项目:

vue create kanban-board

在创建项目时,该选择只包含 Babel 和 ESlint 的默认预设。

完成后,删除默认组件 HelloWorld ,将 App 组件修改为空,仅包含裸组件模板:

<template> <div></div> </template>
<script>
export default {
 name: 'App',
 components: {},
};
</script>
<style></style>

接下来用 Bootstrap 进行样式设置,只需 Bootstrap CSS CDN 就够了。将其添加到 public/index.html 的 head 重。

<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width,initial-scale=1.0">
 <link rel="icon" href="<%= BASE_URL %>favicon.ico" rel="external nofollow" >
 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="external nofollow" 
 integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
 <title><%= htmlWebpackPlugin.options.title %></title>
 </head>

在看板中构建 UI 组件

看板的样子应该是这样的:

如何在vue中使用HTML 5 拖放API

通常看板要有列和卡片。卡片是要执行的单个项目或任务,列用来显示特定卡片的状态。

所以需要创建三个 Vue 组件:一个用于列,一个用于卡片,最后一个用于创建新卡片。

创建 card 组件

先来创建 card 组件。在 /component 目录中创建一个新文件 Card.vue。

把下面的代码添加到组件中:

<template>
 <div class="card">
 <div class="card-body">A Sample Card</div>
 </div>
</template>
<script>
export default {};
</script>
<style scoped>
div.card {
 margin-bottom: 15px;
 box-shadow: 0 0 5px #cccccc;
 transition: all ease 300ms;
 background: #fdfdfd;
}
div.card:hover {
 box-shadow: 0 0 10px #aaaaaa;
 background: #ffffff;
}
</style>

这样就创建并设置了卡片组件的样式。不过还没有向组件添加可拖动功能,因为这只是组件的框架。

创建 AddCard 组件

顾名思义,这个组件将负责创建新卡片并将其添加到列中。

在 /components 目录中创建一个 AddCard.vue 文件,并添加以下代码:

<template>
 <div class="">
 <button
  class="btn btn-sm btn-info w-100"
  v-if="!inAddMode"
  @click="inAddMode = true"
 >
  Add Card
 </button>
 <form action="#" class="card p-3" ref="form" v-else>
  <div class="form-group">
  <input
   type="text"
   name="title"
   id="title"
   class="form-control"
   placeholder="Something interesting..."
   v-model="cardData"
  />
  </div>
  <div class="d-flex justify-content-center">
  <button type="submit" class="btn w-50 btn-primary mr-3">Save</button>
  <button type="reset" class="btn w-50 btn-danger">
   Cancel
  </button>
  </div>
 </form>
 </div>
</template>
<script>
export default {
 data() {
 return {
  inAddMode: false,
  cardData: '',
 };
 },
 methods: {},
};
</script>
<style></style>

具体功能将在后面进行构建。

创建 Column 组件

这是最后一个组件,它用来显示卡列表,还会包含 AddCard 组件,以便可以将新卡片直接创建到列中。

在 components 目录中创建一个 Column.vue 文件,并添加以下代码:

<template>
 <div class="col-md-3 card column" ref="column">
 <header class="card-header">
  <h3 class="col">Column Name</h3>
 </header>
 <div class="card-list"></div>
 </div>
</template>
<script>
export default {};
</script>
<style scoped>
div.column {
 padding: 0;
 padding-bottom: 15px;
 margin: 0 15px;
 box-shadow: 0 0 10px #cccccc;
}
div.card-list {
 padding: 0 15px;
}
header {
 margin-bottom: 10px;
}
header h3 {
 text-align: center;
}
</style>

现在项目的框架搭好了,接下来先概述一下拖放功能在浏览器中是怎样工作的。

HTML5 拖放 API 是什么?

当用户将鼠标移到可拖动元素上时,拖动操作开始,然后将元素移动到启用拖放的元素上。

再默认情况下,唯一可拖动的 HTML 元素是图像和链接。为了使其他元素可拖动,需要通过将 draggable 属性添加到元素;也可以在 JavaScript 中选择元素并将 draggable 属性设置为 true 来显式创建功能。

在元素上将 draggable 属性设置为 true 之后,你会注意到 draggable 属性已添加到该元素。

<!-- Making an element draggable in HTML -->
<div draggable="true">This is a draggable div in HTML</div>

<script>
// Making an element draggable in javascript
const div = document.querySelector('div');
div.draggable = true;
</script>

拖动元素的目的是将数据从页面的一个部分传输到另一部分。

对于图像,要传输的数据是图像 URL 或它的 base 64 表示形式。如果是链接,传输的数据是 URL。可以将链接移动到浏览器的 URL 栏中,这样使浏览器跳转到该 URL。

所以,如果没有数据传输的能力,那么拖动元素就毫无用处了。可以通过 DataTransfer API 把通过拖动操作传输的数据保存在拖动数据存储区中,这个 API 提供了在拖放操作期间存储和访问数据的方式。

DataTransfer 提供了添加要通过拖放传输的项目的位置。可以在开始拖动操作时(调用 dragstart 事件时)将数据添加到拖动数据存储中,并且只能在完成拖放操作后(调用 drop 事件时)才能接收数据。

从拖动到释放元素的这段时间中,元素被拖放后,将会在被拖动的元素上触发两个事件:dragstart 和 dragend。

现在还不能把可拖动元素拖放到任何地方。与需要显式的使元素可拖动一样,它也需要启用放置。

要启用元素拖放功能需要侦听 dragover 事件并阻止默认的浏览器操作。

<!-- Make a section drop-enabled -->
<section class="section"></section>
<script>
const section = document.querySelector('.section');
section.addEventListener('dragover', (e) => {
 e.preventDefault();
});
</script>

将元素拖动到启用拖放的元素上时,将会在启用拖放的元素上触发以下事件:

Dragenter:当一个元素被拖动到启用拖放的元素上时触发一次
Dragover:只要元素仍然位于启用了 drop 的元素上,就会连续触发
Drop:在把拖动的元素拖放到启用了拖放的元素上之后触发。

需要注意的是,仅在触发放置事件时才能访问存储在 DataTransfer 对象中的数据,而不能在 dragenter 或 dragover 上访问。

组合所有的组件

在向组件添加拖放功能之前,先讨论一下 app state。

这里的 app state 将存储在 App 组件中,然后可以作为 props 向下传递到 Column 组件。另一方面,列组件在渲染时会将所需的 props 传递给卡片组件。

修改 App.vue 使其能够反映状态和组件组成:

// App.vue
<template>
 <div class="container-fluid">
 <h2 class="m-5">
  Vue Kanban Board
 </h2>
 <div class="row justify-content-center">
  <Column
  v-for="(column, index) in columns"
  :column="column"
  :key="index"
  />
 </div>
 </div>
</template>
<script>
import Column from './components/Column';
export default {
 name: 'App',
 components: {
 Column,
 },
 data() {
 return {
  columns: [
  {
   name: 'TO-DO',
   cards: [
   {
    value: 'Prepare breakfast',
   },
   {
    value: 'Go to the market',
   },
   {
    value: 'Do the laundry',
   },
   ],
  },
  {
   name: 'In Progress',
   cards: [],
  },
  {
   name: 'Done',
   cards: [],
  },
  ],
 };
 },
};
</script>
<style>
h2 {
 text-align: center;
}
</style>

在这里,我们导入了列组件,并在状态为 columns 的状态下循环访问数据时,将每一列的数据传递给 column 组件。在这种情况下,只有 “To-Do”,“In Progress” 和 “Done” 三列,每列都有一个卡片数组。

接下来,更新 Column 组件来接收 props 并显示它:

// Column.vue
<template>
 <div class="col-md-3 card column" ref="column">
 <header class="card-header">
  <h3 class="col">{{ column.name }}</h3>
  <AddCard />
 </header>
 <div class="card-list">
  <Card v-for="(card, index) in column.cards" :key="index" :card="card" />
 </div>
 </div>
</template>
<script>
import Card from './Card';
import AddCard from './AddCard';
export default {
 name: 'Column',
 components: {
 Card,
 AddCard,
 },
 props: {
 column: {
  type: Object,
  required: true,
 },
 },
};
</script>

...

Column 组件从 App 组件接收 props,并用 props 渲染 Card 组件列表。在这里还会使用 AddCard 组件,因为应该可以将新卡直接添加到列中。

最后更新 Card 组件显示从 Column 接收的数据。

// Card.vue
<template>
 <div class="card" ref="card">
 <div class="card-body">{{ card.value }}</div>
 </div>
</template>
<script>
export default {
 name: 'Card',
 props: {
 card: {
  type: Object,
  required: true,
 },
 },
};
</script>

Card 组件仅从 Column 接收它需要的所有数据并显示出来。我们还在此处添加了对 card 元素的引用,这样在用 JavaScript 访问 card 元素时非常有用。

完成上述操作后,你的应用应该是下面这样了:

如何在vue中使用HTML 5 拖放API

添加拖放功能

添加拖放功能的第一步是识别可拖动组件和放置目标。

用户应该能够按照卡片中的活动进度将卡片从一列拖到另一列。所以可拖动组件应该是 Card 组件,而放置目标是 Column 组件。

使卡片可拖动

需要执行以下操作才能使卡组件可拖动:

  1. 将 draggable 属性设置为 true
  2. 用 DataTransfer 对象设置要传输的数据

应该先把 draggable 设置为 true,根据 Vue 生命周期 hook,安全的位置应该是已安装的 hook。把以下内容添加到 Card 组件的已安装 hook 中:

// Card.vue
<script>
export default {
 name: 'Card',
 props: {...},

 mounted() {
 this.setDraggable();
 },

 methods: {
 setDraggable() {
  // Get Card element.
  const card = this.$refs.card;
  card.draggable = true;
  // Setup event listeners.
  card.addEventListener('dragstart', this.handleDragStart);
  card.addEventListener('dragend', this.handleDragEnd);
 },
 },
</script>

在上面,我们创建了一个 setDraggable 方法来使卡片组件可拖动。

在 setDraggable 中,从上一节中添加的引用中得到卡片,并将 draggable 属性设置为 true 。

同时还需要设置事件监听器:

// Card.vue
<script>
export const CardDataType = 'text/x-kanban-card';

export default {
...
 methods: {
 setDraggable() {...},
 handleDragStart(event) {
  const dataTransfer = event.dataTransfer;
  // Set the data to the value of the card which is gotten from props.
  dataTransfer.setData(CardDataType, this.card.value);
  dataTransfer.effectAllowed = 'move';
  // Add visual cues to show that the card is no longer in it's position.
  event.target.style.opacity = 0.2;
 },
 handleDragEnd(event) {
  // Return the opacity to normal when the card is dropped.
  event.target.style.opacity = 1;
 }
 }
}
</script>

在前面提到,只有在 dragstart 事件被调用时,数据才可以被添加到拖动数据存储中。所以需要在 handleDragStart 方法中添加数据。

设置数据时要用到的重要信息是格式,可以是字符串。在我们的例子中,它被设置为 text/x-kanban-card。存储这个数据格式并导出它,因为在删除卡后获取数据时,Column 组件将会用到它。

最后,将 card 的透明度降低到 0.2 ,以便向用户提供一些反馈,表明该卡实际上已被拉出其原始位置。拖动完成后,再把透明度恢复为 1。

现在可以拖动卡片了。接下来添加放置目标。

把 dragover 设置为 drop-enabled

将卡片拖到列组件上时,会立即触发 dragover 事件,将卡放入列中后会触发 drop 事件。

要使卡片掉落到列中,需要侦听这些事件。

// Column.vue
<template>...</template>
<script>
import Card { CardDataType } from './Card';
import AddCard from './AddCard';
export default {
 name: 'Column',
 components: {...},
 props: {...},
 mounted() {
 this.enableDrop();
 },
 methods: {
 enableDrop() {
  const column = this.$refs.column;
  column.addEventListener('dragenter', this.handleDragEnter);
  column.addEventListener('dragover', this.handleDragOver);
  column.addEventListener('drop', this.handleDrop);
 },
 /**
  * @param {DragEvent} event
  */
 handleDragEnter(event) {
  if (event.dataTransfer.types.includes[CardDataType]) {
  // Only handle cards.
  event.preventDefault();
  }
 },
 handleDragOver(event) {
  // Create a move effect.
  event.dataTransfer.dropEffect = 'move';
  event.preventDefault();
 },
 /**
  * @param {DragEvent} event
  */
 handleDrop(event) {
  const data = event.dataTransfer.getData(CardDataType);
  // Emit a card moved event.
  this.$emit('cardMoved', data);
 },
 },
};
</script>

在这里将设置在挂载 Column 组件之后启用 drop 所需的所有事件侦听器。

在这三个事件中,第一个被触发的是 dragenter ,当可拖动元素被拖到列中时会立即被触发。对于我们的程序,只希望将卡片放入一列中,所以在 dragenter 事件中,只阻止数据类型的默认值,数据类型包括在 card 组件中所定义的 card 数据类型。

在 dragover 事件中,把放置效果设置为 move。

在 drop 事件中获得从 dataTransfer 对象传输的数据。

接下来,需要更新状态并将卡片移动到当前列。因为我们的程序状态位于 App 组件中,所以在 drop 侦听器中发出 cardMoved 事件,传递已传输的数据,并在 App 组件中侦听 cardMoved 事件。

更新 App.vue 来监听 cardMoved 事件:

// App.vue

<template>
 <div class="container-fluid">
 ...
 <div class="row justify-content-center">
  <Column
  v-for="(column, index) in columns"
  :column="column"
  :key="index"
  @cardMoved="moveCardToColumn($event, column)"
  />
 </div>
 </div>
</template>

<script>
import Column from './components/Column';
export default {
 name: 'App',
 components: {...},
 data() {
 return {...}
 },
 methods: {
 moveCardToColumn(data, newColumn) {
  const formerColumn = this.columns.find(column => {
  // Get all the card values in a column.
  const cardValues = column.cards.map((card) => card.value);
  return cardValues.includes(data);
  })
  // Remove card from former column.
  formerColumn.cards = formerColumn.cards.filter(
  (card) => card.value !== data
  );
  // Add card to the new column.
  newColumn.cards.push({ value: data });
 },
 },
}
</script>

在这里通过 @cardMoved 侦听 cardMoved 事件,并调用 moveCardToColumn 方法。 cardMoved 事件发出一个值(卡片数据),可以通过 $event 访问这个值,另外还传递了放置卡的当前列(这是调度事件的位置)。

moveCardToColumn 函数做了三件事:找到卡偏先前所在的列,从该列中取出卡片,最后把卡片加到新列中。

完成看板

现在我们已经实现了拖放功能,最后只剩下添加卡片的功能了。

在 AddCard.vue 中添加以下代码:

<template>
 <div class="">
 <button
  class="btn btn-sm btn-info w-100"
  v-if="!inAddMode"
  @click="inAddMode = true"
 >
  Add Card
 </button>
 <form
  action="#"
  class="card p-3"
  @submit.prevent="handleSubmit"
  @reset="handleReset"
  ref="form"
  v-else
 >
  ...
 </form>
 </div>
</template>
<script>
export default {
 data() {
 return {...};
 },
 methods: {
 handleSubmit() {
  if (this.cardData.trim()) {
  this.cardData = '';
  this.inAddMode = false;
  this.$emit('newcard', this.cardData.trim());
  }
 },
 handleReset() {
  this.cardData = '';
  this.inAddMode = false;
 },
 },
};
</script>

上面的代码是在提交“add card”表单或重置时运行的函数。

重置后清除 cardData,并将 inAddMode 设置为 false。

在提交表单后还要清除 cardData ,以便在添加新项目时不会显示以前的数据,并且还要将 inAddMode 设置为 false 并发出 newcard 事件。

Column组件中使用了AddCard组件,所以需要在 Column 组件中监听 newcard 事件。在 Column 组件中添加侦听 newcard 事件的代码:

<template>
 <div class="col-md-3 card column" ref="column">
 <header class="card-header">
  <h3 class="col">{{ column.name }}</h3>
  <AddCard @newcard="$emit('newcard', $event)"></AddCard>
 </header>
 ...
</template>
...

在这里重新发出 newcard 事件,这样可以使它到达 App 组件,实际的动作将在该组件上发生。

自定义 Vue 事件不会冒泡,因此 App 组件无法侦听 AddCard 组件中发出的 newcard 事件,因为它不是直接子组件。
更新 App 组件处理 newcard 事件的代码:

// App.vue

<template>
 <div class="container-fluid">
 ...
 <div class="row justify-content-center">
  <Column
  v-for="(column, index) in columns"
  :column="column"
  :key="index"
  @cardMoved="moveCardToColumn($event, column)"
  @newcard="handleNewCard($event, column)"
  />
 </div>
 </div>
</template>

<script>
import Column from './components/Column';
export default {
 name: 'App',
 components: {...},
 data() {
 return {...}
 },
 methods: {
 moveCardToColumn(data, newColumn) {...},
 handleNewCard(data, column) {
  // Add new card to column.
  column.cards.unshift({ value: data });
 },
 },
};
</script>

在这里侦听从 Column 组件调用的 newcard 事件,在获取数据后,创建一个新卡片并将其添加到创建该卡的列中。

总结

在本文中,我们介绍了什么是 HTML 5 拖放 API ,如何使用,以及如何在 Vue.js 中实现。

拖放功能也可以在其他前端框架和原生 JavaScript 中使用。

以上就是如何在vue中使用HTML 5 拖放API的详细内容,更多关于vue中拖放api的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
vue 表单输入框不支持focus及blur事件的解决方案
Nov 17 Vue.js
vuex的数据渲染与修改浅析
Nov 26 Vue.js
Vue组件简易模拟实现购物车
Dec 21 Vue.js
Vue 集成 PDF.js 实现 PDF 预览和添加水印的步骤
Jan 22 Vue.js
如何封装Vue Element的table表格组件
Feb 06 Vue.js
vue中data改变后让视图同步更新的方法
Mar 29 Vue.js
Vue项目中如何封装axios(统一管理http请求)
May 02 Vue.js
vue实现滑动解锁功能
Mar 03 Vue.js
解决vue-router的beforeRouteUpdate不能触发
Apr 14 Vue.js
关于vue-router-link选择样式设置
Apr 30 Vue.js
vue使用element-ui按需引入
May 20 Vue.js
Vue深入理解插槽slot的使用
Aug 05 Vue.js
Vue中引入svg图标的两种方式
Jan 14 #Vue.js
vue+element table表格实现动态列筛选的示例代码
Jan 14 #Vue.js
vue 递归组件的简单使用示例
Jan 14 #Vue.js
vue element和nuxt的使用技巧分享
Jan 14 #Vue.js
vue动态设置路由权限的主要思路
Jan 13 #Vue.js
vue组件是如何解析及渲染的?
Jan 13 #Vue.js
vue实现一个获取按键展示快捷键效果的Input组件
Jan 13 #Vue.js
You might like
php防盗链的常用方法小结
2010/07/02 PHP
php使用mysqli向数据库添加数据的方法
2015/03/20 PHP
ThinkPHP和UCenter接口冲突的解决方法
2016/07/25 PHP
PHP多进程编程实例详解
2017/07/19 PHP
用JavaScript实现UrlEncode和UrlDecode的脚本代码
2008/07/23 Javascript
js 面向对象的技术创建高级 Web 应用程序
2010/02/25 Javascript
从零开始学习jQuery (六) jquery中的AJAX使用
2011/02/23 Javascript
js过滤特殊字符输入适合输入、粘贴、拖拽多种情况
2014/03/22 Javascript
jQuery Validate插件实现表单强大的验证功能
2015/12/18 Javascript
JavaScript的事件机制详解
2017/01/17 Javascript
js实现网页定位导航功能
2017/03/07 Javascript
修改UA在PC中访问只能在微信中打开的链接方法
2017/11/27 Javascript
angular6 利用 ngContentOutlet 实现组件位置交换(重排)
2018/11/02 Javascript
微信小程序 拍照或从相册选取图片上传代码实例
2019/08/28 Javascript
node 解析图片二维码的内容代码实例
2019/09/11 Javascript
vue学习笔记之作用域插槽实例分析
2020/02/01 Javascript
基于vue和bootstrap实现简单留言板功能
2020/05/30 Javascript
Python实现批量把SVG格式转成png、pdf格式的代码分享
2014/08/21 Python
利用Python实现图书超期提醒
2016/08/02 Python
python不换行之end=与逗号的意思及用途
2017/11/21 Python
python pandas dataframe 按列或者按行合并的方法
2018/04/12 Python
用python3教你任意Html主内容提取功能
2018/11/05 Python
值得收藏的10道python 面试题
2019/04/15 Python
python获取当前文件路径以及父文件路径的方法
2019/07/10 Python
利用python画出AUC曲线的实例
2020/02/28 Python
python如何用matplotlib创建三维图表
2021/01/26 Python
Europcar西班牙:全球汽车租赁领域的领导者
2018/09/17 全球购物
MAC Cosmetics官方网站:魅可专业艺术彩妆
2019/04/10 全球购物
说明书怎么写
2014/05/06 职场文书
汽车维修专业自荐书
2014/05/26 职场文书
期末个人总结范文
2015/02/13 职场文书
防汛通知
2015/04/25 职场文书
2015年预算员工作总结
2015/05/14 职场文书
复兴之路纪录片观后感
2015/06/02 职场文书
《小摄影师》教学反思
2016/02/18 职场文书
Vue图片裁剪组件实例代码
2021/07/02 Vue.js