如何在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项目如何引入bootstrap、elementUI、echarts
Nov 26 Vue.js
Vue Elenent实现表格相同数据列合并
Nov 30 Vue.js
vue中利用three.js实现全景图的完整示例
Dec 07 Vue.js
vue祖孙组件之间的数据传递案例
Dec 07 Vue.js
vue3.0中友好使用antdv示例详解
Jan 05 Vue.js
vue实现防抖的实例代码
Jan 11 Vue.js
Vue常用API、高级API的相关总结
Feb 02 Vue.js
vue3.0封装轮播图组件的步骤
Mar 04 Vue.js
vue中利用mqtt服务端实现即时通讯的步骤记录
Jul 01 Vue.js
vue3引入highlight.js进行代码高亮的方法实例
Apr 08 Vue.js
vue 给数组添加新对象并赋值
Apr 20 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
PHPExcel在linux环境下导出报500错误的解决方法
2017/01/26 PHP
php7下的filesize函数
2019/09/30 PHP
原生javascript获取元素样式属性值的方法
2010/12/25 Javascript
javascript阻止浏览器后退事件防止误操作清空表单
2013/11/22 Javascript
js简单的表格添加行和删除行操作示例
2014/03/31 Javascript
浅析js预加载/延迟加载
2014/09/25 Javascript
在JavaScript中处理字符串之link()方法的使用
2015/06/08 Javascript
TypeScript Type Innference(类型判断)
2016/03/10 Javascript
JS函数定义方式的区别介绍
2016/03/22 Javascript
BootStrap Table 分页后重新搜索问题的解决办法
2016/08/08 Javascript
jquery获取下拉框中的循环值
2017/02/08 Javascript
jQuery事件与动画基础详解
2017/02/23 Javascript
小程序实现多选框功能
2018/10/30 Javascript
python有证书的加密解密实现方法
2014/11/19 Python
python判断图片宽度和高度后删除图片的方法
2015/05/22 Python
python实现寻找最长回文子序列的方法
2018/06/02 Python
对Xpath 获取子标签下所有文本的方法详解
2019/01/02 Python
Python使用POP3和SMTP协议收发邮件的示例代码
2019/04/16 Python
Python之NumPy(axis=0 与axis=1)区分详解
2019/05/27 Python
python中使用while循环的实例
2019/08/05 Python
如何修复使用 Python ORM 工具 SQLAlchemy 时的常见陷阱
2019/11/19 Python
在python3中实现更新界面
2020/02/21 Python
Python爬虫之爬取淘女郎照片示例详解
2020/07/28 Python
详解Python中第三方库Faker
2020/09/25 Python
python实现图片转字符画
2021/02/19 Python
css3实现wifi信号逐渐增强效果实例
2017/08/09 HTML / CSS
在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern "C"
2014/08/09 面试题
清明节网上祭英烈活动总结
2014/04/30 职场文书
施工工地安全标语
2014/06/07 职场文书
会计岗位职责
2015/02/03 职场文书
死者家属慰问信
2015/03/24 职场文书
2016情人节宣传语
2015/07/14 职场文书
mysql知识点整理
2021/04/05 MySQL
MongoDB安装使用并实现Python操作数据库
2021/06/28 MongoDB
MySql 缓存查询原理与缓存监控和索引监控介绍
2021/07/02 MySQL
oracle删除超过N天数据脚本的方法
2022/02/28 Oracle