angular 拖動功能

weiewiyi發表於2022-04-25

前言

寫到表單功能的時候,就考慮如果表單能夠拖動式地新增,無疑能夠加強使用者的使用體驗感。所以就嘗試著實現拖動功能。如下圖,能夠將一個表單拖動到另一個表單。
image.png

環境配置

使用到了angular的cdk:@angular/cdk/drag-drop

Angualr drag-drop裡面的功能能讓我們非常方便的處理頁面上檢視的拖拽(自由拖拽、列表排序拖拽、列表之間拖拽)問題。

官網:https://material.angular.io/c...

cdk是Angular Material 下的一個模組.我們安裝一下Material。

安裝material

ng add @angular/material

ng add命令將安裝 Angular Material、 元件開發工具包 (CDK)

Module匯入

import { DragDropModule } from '@angular/cdk/drag-drop';

imports: [

...

DragDropModule

]

功能實現

講一下最常用的幾種用法

拖拽

html程式碼:
最主要的就是加上了 cdkDrag

<div cdkDrag> drag me</div>

效果:
dragme.gif

排序

使用cdkDropList,它新增在一組元素新增cdkDrag可拖動元素的集合外面。隨著元素的移動,專案將自動重新排列。

html:

<div class="list-group" cdkDropList 
(cdkDropListDropped)="drop($event)">
  <div class="list-group-item row" *ngFor="let customer of customers" cdkDrag>
    {{customer.name}}
  </div>
</div>

ts:

drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.customers, event.previousIndex, event.currentIndex);
  }

效果圖:
list1.gif





當然還可以橫向排序,只需要新增cdkDropListOrientation="horizontal"

<div class="box-list-horizontal" cdkDropList 
cdkDropListOrientation="horizontal">

新增動畫

看起來上面的效果圖有些不太好看,我們可以給它新增一點動畫。

css:

// 拖拽時顯示的佔位符元素,而不是實際的元素
.cdk-drag-placeholder {
  opacity: 0;
}

// 從動畫的位置到最終把它放在列表的位置上時的動畫
.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

// 拖動元素時,看到的預覽元素
.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
  0 8px 10px 1px rgba(0, 0, 0, 0.14),
  0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

// 拖動元素時,其他元素改變位置看到的動畫
.list-group.cdk-drop-list-dragging .list-group-item:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}



效果圖:

list.gif


還有其他的其他的一些功能將不再一一講述,比如拖放位置鎖定、設定拖放邊界、拖放禁用功能。
可以看看官網:https://material.angular.io/c...

兩個列表之間的拖拽

目前需要使用的是列表之間的拖拽,所以重點講一下這個。

列表之間的連線

使用cdkDropListConnectedTo和id,可以連線兩個表,使它們之間能夠拖動自己的元素到另一個表中。

<div cdkDropList id="list-one" [cdkDropListConnectedTo]="['list-two']"></div>
<div cdkDropList id="list-two" [cdkDropListConnectedTo]="['list-one']"></div>



也可以使用cdkDropListGroup。cdkDropListGroup在下的所有表都將自動連線到所有其他列表。我採用的是這種。

<div cdkDropListGroup>
  <!-- All lists in here will be connected. -->
  <div cdkDropList *ngFor="let list of lists"></div>
</div>

根據需求寫程式碼

首先,定義了一個表,需求是:不能排序,另一個不能拖動元素到此表單

  <div class="row">
    <div class="col-2">
      <h2>可用表單</h2>
      <div
        cdkDropList
        [cdkDropListData]="availableItems" // 拖動時所帶的元素
        cdkDropListSortingDisabled // 禁用排序
        [cdkDropListEnterPredicate]= "noReturnPredicate" // 定義了一個函式,返回false, 表示別的表單不能拖動元素到此表單
        class="list-group">
        <div class="list-group-item" *ngFor="let item of availableItems" cdkDrag>
          <label> {{item.content}}</label>
        </div>
      </div>
    </div>
  </div>


定義了另一個表,需求是:可以排序,另一個表的元素拖動過來的時候,原來的元素不會消失。

<div class="col-6">
      <h2>目前表單</h2>
      <div
        cdkDropList
        class="list-group"
        [cdkDropListData]="nowItem"
        (cdkDropListDropped)="dropNowList($event)">
        <div class="list-group-item row" cdkDrag *ngFor="let item of nowItem">
          {{item.content}}
        </div>
      </div>
    </div>

效果圖:

form.gif



ts:
當元素從左邊的表拖動到右邊的表的時候,或者右邊的表排序的時候,右邊的表會執行dropNowList()函式。函式中進行了判斷,如程式碼中註釋所示

/**
   * 現有表單
   * @param event
   */
  dropNowList(event: CdkDragDrop<FormItem[], any>) {
    // 如果前容器等於現容器,說明是在排序,交換元素的位置
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
    // 如果不同,說明是從另一個表過來的元素,獲取元素前容器的資料,複製元素,插入到本表中
    // 如果使用transferItem(),則源元素會消失
      copyArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex)
    }
  }

遇到的問題

動態渲染

其實最開始想的是,元素一拖過去就動態渲染。但是,動態元件是一個整體,我們並不能拖動其中的某一個元件,只能拖動整體。如下效果圖:

form2.gif



所以最後放棄了動態渲染,讓使用者拖動好了表單再點選預覽按鈕。

拖動效果

從一個表拖動元素到另一個表到時候,這個元素在源表中會消失,雖然拖動過去之後會回來,但是看起來邏輯並不好。

示例:

form1.gif

所以需要實現的是,拖動元素的時候,這個元素在源表單不動。

但似乎DragModule並沒有屬性或者函式可以讓我們呼叫來解決這個問題。

在github有一場對這個問題的大型討論:
https://github.com/angular/co...

但似乎實現起來有點麻煩。

這個問題目前還不影響功能的實現,待後期有時間的時候再解決。

相關文章