如何在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下载后台传过来的乱码流的问题
Dec 05 Vue.js
vue祖孙组件之间的数据传递案例
Dec 07 Vue.js
Vue解决移动端弹窗滚动穿透问题
Dec 15 Vue.js
如何在vue 中使用柱状图 并自修改配置
Jan 21 Vue.js
Vue实现摇一摇功能(兼容ios13.3以上)
Jan 26 Vue.js
WebStorm无法正确识别Vue3组合式API的解决方案
Feb 18 Vue.js
vue引入Excel表格插件的方法
Apr 28 Vue.js
vue如何批量引入组件、注册和使用详解
May 12 Vue.js
HTML+VUE分页实现炫酷物联网大屏功能
May 27 Vue.js
vue cli4中mockjs在dev环境和build环境的配置详情
Apr 06 Vue.js
vue数据字典取键值项目的字典问题
Apr 12 Vue.js
ant design vue的form表单取值方法
Jun 01 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/12/29 PHP
浅析is_writable的php实现
2013/06/18 PHP
PHP APC配置文件2套和参数详解
2014/06/11 PHP
PHP编程实现计算抽奖概率算法完整实例
2017/08/09 PHP
JavaScript性能优化 创建文档碎片(document.createDocumentFragment)
2010/07/13 Javascript
一个js控制的导航菜单实例代码
2013/12/03 Javascript
JS刷新当前页面的几种方法总结
2013/12/24 Javascript
js中substr,substring,indexOf,lastIndexOf,split,replace的用法详解
2015/11/09 Javascript
jQuery实现宽屏图片轮播实例教程
2015/11/24 Javascript
基于JavaScript实现带缩略图的轮播效果
2017/01/12 Javascript
ES6新特性之Object的变化分析
2017/03/31 Javascript
js遍历获取表格内数据的方法(必看)
2017/04/06 Javascript
Vue.js教程之axios与网络传输的学习实践
2017/04/29 Javascript
vue.js全局API之nextTick全面解析
2017/07/07 Javascript
vue+echarts实现动态绘制图表及异步加载数据的方法
2018/10/17 Javascript
彻底弄懂 JavaScript 执行机制
2018/10/23 Javascript
微信小程序单选radio及多选checkbox按钮用法示例
2019/04/30 Javascript
关于JS模块化的知识点分享
2019/10/16 Javascript
ant design vue嵌套表格及表格内部编辑的用法说明
2020/10/28 Javascript
如何在vue 中使用柱状图 并自修改配置
2021/01/21 Vue.js
py中的目录与文件判别代码
2008/07/16 Python
Python lambda和Python def区别分析
2014/11/30 Python
详解Python中的文本处理
2015/04/11 Python
Python中的pass语句使用方法讲解
2015/05/14 Python
详解python 字符串和日期之间转换 StringAndDate
2017/05/04 Python
python使用BeautifulSoup与正则表达式爬取时光网不同地区top100电影并对比
2019/04/15 Python
python 含子图的gif生成时内存溢出的方法
2019/07/07 Python
为什么说Python可以实现所有的算法
2019/10/04 Python
Python Pandas 对列/行进行选择,增加,删除操作
2020/05/17 Python
使用HTML5 IndexDB存储图像和文件的示例
2018/11/05 HTML / CSS
英国领先的维生素和补充剂品牌:Higher Nature
2019/08/26 全球购物
专科毕业生自我鉴定
2013/12/01 职场文书
亲戚结婚的请假条
2014/02/11 职场文书
入党积极分子自我鉴定
2014/02/18 职场文书
个人对照检查材料思想汇报
2014/09/26 职场文书
Java时间工具类Date的常用处理方法
2022/05/25 Java/Android