拖放 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