Angular Material Drag CDK滑鼠拖曳效果API介紹與實作

江秋霖 2019/12/24 16:42:12
220

前言

在前端網頁的操作上,有時為了增進使用者體驗,設計者必須構思有趣的視覺或實用的功能呈現。其中直覺又方便的拖曳效果,就是個可以滿足此兩個需求的設計內容。在JavaScript被廣為使用的現在,已有許多JS前端框架,支援拖曳效果的設計實現。

 

本篇要介紹的是三大框架之一的AngularJS2,所衍生出的UI組件庫 - Angular Material。在Angular Material提供的CDK裡,有一個@angular/cdk/drag-drop,提供了幾個拖曳相關的API供設計者參考使用。

 

而這也是本篇文章的重點。就讓筆者以此功能常見的十二招為大家好好介紹與實作吧!

 

第一招:cdkDrag

凡事任何標籤加上cdkDrag,就可以進行拖曳!

 

<div class="example-boundary" id="myBoundary">
  <img src="../assets/mei.jpeg" cdkDrag>
</div>

 

蛤?有這麼簡單?
沒錯!directive就是這麼好用!

 

一起來看看初步的效果吧!

  

圖1-1                                                           圖1-2

 

第二招:cdkDragBoundary

基本上,預設的拖曳效果,是可以把物件拖曳到網頁上任何一個角落。


如果限制物件的拖曳範圍呢?cdkDragBoundary是另一個好用的directive


只需要在被拖曳的標籤加上cdkDragBoundary="class名稱"


如下圖:

 

<div class="example-boundary">
  <img src="../assets/mei.jpeg" cdkDrag cdkDragBoundary=".example-boundary">
</div>


雙引號""裡除了可以是class名稱,也可以是ID:

 

<div class="example-boundary" id="myBoundary">
  <img src="../assets/mei.jpeg" cdkDrag cdkDragBoundary="#myBoundary">
</div>


當然,標籤也是可以:

 

<h3 class="example-boundary">
  <img src="../assets/mei.jpeg" cdkDrag cdkDragBoundary="h3">
</h3>

 

讓我們看看這個API的效果

 

  

圖2-1                                                           圖2-2 滑鼠往下拖曳至黑線範圍下方後,圖片仍被侷限在上方下不來。

 

範例一:NBA球星名單

為了讓範例更吸睛有趣 (希望啦),我們來選一下NBA明星隊吧!

開門見山,讓我們直接看成果好了。

 

     

圖3-1                                                                                                         圖3-2 可將球員換至東西區任何一隊,只須要用滑鼠拖曳物件到指定位置放開即可。

 

    

圖3-3  拖曳物件時,可以預覽球員照片。詳見後面介紹的cdkDragPreview效果。      圖3-4


我們也來先瞄一下程式吧!

因為兩段程式大同小異,就以東區明星隊的部份的HTML標籤為例。

 

<div class="example-container">
  <h2>東區明星隊</h2>

  <div
    cdkDropList
    #eastList="cdkDropList"
    [cdkDropListData]="east"
    [cdkDropListConnectedTo]="[westList]"
    class="example-list"
    (cdkDropListDropped)="drop($event)">
    <div class="example-box east-box" *ngFor="let item of east" cdkDrag>{{item.name}}
      <img *cdkDragPreview [src]="item.photo" [alt]="item.name">
    </div>
  </div>
</div>

 

第三招:cdkDropList

我們來個別API拆開來看,首先是cdkDropList



可以把cdkDropList當作是辦公桌上的實體檔案夾,只要標籤加上cdkDropList就能把cdkDrag物件拖曳至此,且放置該物件於這個「檔案夾」內。

 

第四招:cdkDropListData

再來,是cdkDropListData屬性。

 


這個部份主要在指定這個檔案夾的預設資料,也就是可供拖曳的物件,指定一個名為east的陣列資料給它。

 

第五招:cdkDropListConnectedTo
cdkDropListConnectedTo屬性,在於指定有哪些其它檔案夾,可以讓這個檔案夾內的物件互相拖曳放置。

 

 

指定的名稱加上#字號,所以我們再回過頭去看剛剛的程式。

 



有沒有看到加上#字號的eastList,這就是檔案夾的名稱,並指定它為一個cdkDropList。

而另一邊西區明星隊的程式,當然就是加上#westList="cdkDropList"啦!

 

而餵給cdkDropListConnectedTo的資料,可以是一個陣列,也就是可以讓這個檔案夾,與其它1至多個檔案夾產生聯結。

 

第六招:cdkDropListDropped

而檔案夾有一個預設cdkDropListDropped事件,在於接收拖曳放開於此檔案夾後的物件資訊。

 

 

我們把它指定為放開之後,觸發事件並進入drop函式,同時傳入此拖曳物件的資訊$event。

 

那drop函式內又做了哪些事呢?

 

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(event.previousContainer.data,
                        event.container.data,
                        event.previousIndex,
                        event.currentIndex);
    } 
  }


判斷式event.previousContainer === event.container,
在於判斷傳入函式內的event物件,前一個與現在放置的檔案夾是否為同一個,並呼叫drag CDK第三方開源函式,同時把這個物件的資料與排序資訊傳入函式內。

如果是,進入moveItemInArray函式,也就是在同一個檔案夾內改變該物件的陣列資訊;

如果不是,則進入transferArrayItem函式,將物件資訊從原本的另一個檔案夾,移動到新的檔案夾陣列內。

 

第七招:*cdkDragPreview

接著介紹拖曳物件的部份,剛剛說只要加上cdkDrag directive就可以實現拖曳的效果。

 

    <div class="example-box east-box" *ngFor="let item of east" cdkDrag>{{item.name}}
      <img *cdkDragPreview [src]="item.photo" [alt]="item.name">
    </div>

 

但這個地方在拖曳物件內,還有一個加上*cdkDragPreview directive的img圖片。
*cdkDragPreview的效果是什麼呢?
相信大家都猜到了,顧名思義就是在使用者拖曳物件的時候,呈現圖片的外觀。

 

看到這裡大家有沒有疑問,為什麼*cdkDragPreview與*ngIf或*ngFor等一樣,要加上星號*呢?
星號*將cdkDragPreview改為一個綁定的元素,並綁定一個ng-template。

 

也就是說,img圖片的外層多加上了一層ng-template。

那如果我們拿掉星號,也就是拿掉這一層ng-template會呈現什麼面貌呢?

 

 

圖4-1 將星號拿掉後,少掉一層template,預覽圖片不隱藏直接呈現。                圖4-2

 

欸,其實還滿好看的,是不是?

 

第八招:cdkDropListConnectedTo
相信大家可以舉一反三,如果要讓檔案夾可以互相連結的話,只要在cdkDropListConnectedTo這個屬性內,加上其它檔案夾的名稱就可以了。
所以我們也可以輕鬆的模擬出Trello這樣的線上工作管控工具:

 

 

範例二:Trello模擬

沒錯,只要將其它四個檔案夾的名稱,加入到自己的連結名單中就可以了。

呈現效果如下:

 

 

  圖5-1 以cdkDropListConnectedTo連結其它4個陣列名稱,即可拖拉至任意位置。

 

  圖5-2 在驗收完成清單中計算完成度。

 

接下來,讓我們再來看看其它API功能。

 

第九招:cdkDragHandle
可拖曳的物件內標籤,加上cdkDragHandle的directive之後,使用者只能點選此標籤進行拖曳。
程式碼部份如下:


0

 

呈現的效果部份如下:

  

圖6-1

 

圖6-2 若無點選cdkDragHandle拖曳桿,沒辦法進行拖曳。

 

  

圖6-3 

 

圖6-4 點選加上cdkDragHandle的拖曳桿,便可進行拖曳。

 

第十招:cdkDragLockAxis

另外,drag CDK也提供另一個限制拖曳物件的API方法 - 控制軸向。

只要在拖曳物件的directive標籤內,也加上cdkDragLockAxis這個API,就可以限制該物件拖曳的方向是x軸還是y軸。

用法很簡單,要限制只能在x軸上拖曳的話,加上cdkDragLockAxis="x"即可;y軸亦然。

 

 

呈現效果如下:

 

  

圖7-1                                                                                                     圖7-2 東區明星隊清單只能往x軸拖曳,因此滑鼠往上下拖曳時,該物件不會照做。

 

  

圖7-3 只能往x軸拖曳的東區明星隊,往左右拖曳則是允許的。                                  圖7-4 西區明星隊清單只能往y軸拖曳。

 

   

圖7-5 只能往y軸拖曳的西區明星隊,往上下方向進行拖曳是允許的。                       圖7-6 西區明星隊清單只能往y軸拖曳,因此滑鼠往左右拖曳時,該物件不會照做。

 

第十一招:cdkDropListEnterPredicate

最後一個要介紹的API屬性為cdkDropListEnterPredicate,此屬性在於決定是否可以讓該物件拖曳放置於指定的檔案夾內。
先來看程式碼吧:

 

 

cdkDropListEnterPredicate指定一個為noReturnPredicate的函式。
該函式如下:

 

  noReturnPredicate() {
    return false;
  }

 

效果如下:

 

  

圖8-1                                                                                                       圖8-2 將東區明星隊的球員拖曳至西區。

 

  

圖8-3 滑鼠放開後,不允許被放置於西區清單中,因此圖片自動回到原位置。      圖8-4

 

不管怎樣就是不給拖曳就對了。
當然,此功能也可應用在不同的地方,給予不同情境的拖曳限制條件。

 

第十二招:cdkDragStartDelay

 

最後一個要介紹的API屬性為cdkDragStartDelay,此為8.2.3版本加上的新功能。加上此屬性後,可設定要在滑鼠按下之後幾秒,物件才會進行拖曳。單位為毫秒,以此範例為例,預設延遲1秒,則是在html標籤加上[cdkDragStartDelay]="1000"。


先來看程式碼吧:

 

    <li class="card" cdkDrag [cdkDragStartDelay]="1000"
    *ngFor="let item of nonHandleList">{{item.name}}
  </li>

 

接著我們看看呈現的效果:

 

    

圖9-1                                                                  圖9-2 設定延遲秒數後,無法被直接拖曳至其它清單。    圖9-3 當滑鼠按下等1秒之後,即可進行拖曳。

 

後記

介紹完十二個好用的拖曳功能API,其實Angular Material中的Drag,還有其它的API功能,有興趣的讀者可以上官網看看。個人覺得Angular Material提供的拖曳功能算成熟,可應用在網頁前端各種需求,像是網頁小遊戲之類的。祝福大家練功愉快,這篇就先介紹到這裡,感謝!

江秋霖