拖放 API 将可拖动元素添加到 HTML,使我们可以构建包含可以拖动的具有丰富 UI 元素的 Web 应用。
在本文中我们将用 Vue.js 构建一个简单的看板应用。看板是一种项目管理工具,使用户可以从头到尾直观地管理项目。 Trello、Pivotal Tracker 和 Jira 等工具都属于看板应用。
设置看板
运行以下命令创建我们的看板项目:
1
|
vue create kanban-board
|
在创建项目时,该选择只包含 Babel 和 ESlint 的默认预设。
完成后,删除默认组件 HelloWorld ,将 App 组件修改为空,仅包含裸组件模板:
1
2
3
4
5
6
7
8
|
<template> <div></div> </template>
<script>
export default {
name: 'App' ,
components: {},
};
</script>
<style></style>
|
接下来用 Bootstrap 进行样式设置,只需 Bootstrap CSS CDN 就够了。将其添加到 public/index.html 的 head 重。
1
2
3
4
5
6
7
8
9
|
< 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 组件:一个用于列,一个用于卡片,最后一个用于创建新卡片。
创建 card 组件
先来创建 card 组件。在 /component 目录中创建一个新文件 Card.vue。
把下面的代码添加到组件中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
< 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 文件,并添加以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
< 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 文件,并添加以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
< 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 属性已添加到该元素。
1
2
3
4
5
6
7
8
|
<!-- 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 事件并阻止默认的浏览器操作。
1
2
3
4
5
6
7
8
|
<!-- 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 使其能够反映状态和组件组成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
// 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 并显示它:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// 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 接收的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 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 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// 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 。
同时还需要设置事件监听器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 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 事件。
要使卡片掉落到列中,需要侦听这些事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
// 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 事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
// 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 中添加以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
<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 事件的代码:
1
2
3
4
5
6
7
8
9
|
<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 事件的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// 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的资料请关注服务器之家其它相关文章!
原文链接:https://segmentfault.com/a/1190000038952838