拖放 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"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <title><%= htmlWebpackPlugin.options.title %></title> </head>
在看板中构建 UI 组件
看板的样子应该是这样的:
通常看板要有列和卡片。卡片是要执行的单个我的项目或工作,列用来显示特定卡片的状态。
所以须要创立三个 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 javascriptconst 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 元素时十分有用。
实现上述操作后,你的利用应该是上面这样了:
增加拖放性能
增加拖放性能的第一步是辨认可拖动组件和搁置指标。
用户应该可能依照卡片中的流动进度将卡片从一列拖到另一列。所以可拖动组件应该是 Card
组件,而搁置指标是 Column
组件。
使卡片可拖动
须要执行以下操作能力使卡组件可拖动:
- 将
draggable
属性设置为true
- 用
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 中应用。
本文首发微信公众号:前端先锋
欢送扫描二维码关注公众号,每天都给你推送陈腐的前端技术文章
欢送持续浏览本专栏其它高赞文章:
- 深刻了解Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13个帮你进步开发效率的古代CSS框架
- 疾速上手BootstrapVue
- JavaScript引擎是如何工作的?从调用栈到Promise你须要晓得的所有
- WebSocket实战:在 Node 和 React 之间进行实时通信
- 对于 Git 的 20 个面试题
- 深刻解析 Node.js 的 console.log
- Node.js 到底是什么?
- 30分钟用Node.js构建一个API服务器
- Javascript的对象拷贝
- 程序员30岁前月薪达不到30K,该何去何从
- 14个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩大插件
- Node.js 多线程齐全指南
- 把HTML转成PDF的4个计划及实现
- 更多文章...