狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

【JavaScript】手摸手教你實現一個自己的拖拽排序

admin
2024年4月12日 23:19 本文熱度 844
01
寫在前面

拖拽排序是一種在網頁設計和應用程序中常見的交互方式,允許用戶通過鼠標或觸摸操作來重新排列頁面或界面上的元素。這種交互方式對于提升用戶體驗和操作效率具有重要意義。


在拖拽排序中,用戶可以用鼠標或手指按住某個元素,然后將其拖動到新

的位置,從而實現對元素的重新排列。這種操作直觀且靈活,使得用戶可以根據自己的需求隨時調整頁面或界面的布局,提升了個性化體驗。同時,拖拽排序也增加了用戶的參與度和粘性,用戶可以通過自由選擇和排序感興趣的內容,提升留存率和活躍度。


從技術實現的角度來看,拖拽排序主要依賴于前端技術的支持。例如,基于JavaScript的實現方法主要是通過監聽鼠標或觸摸事件來實現。在拖拽開始時,需要記錄拖拽元素的位置,然后在拖拽過程中更新元素的位置,最后在拖拽結束時判斷元素與其他元素的位置關系并進行排序。


在拖拽排序的應用場景中,列表排序和圖片排序是兩個典型的例子。在列表排序中,用戶可以通過拖動列表項來改變它們的順序,這在任務管理應用、待辦事項列表等場景中非常常見。在圖片排序中,用戶可以通過拖動圖片來改變它們的順序,這在圖片庫或相冊應用中較為常見。


02
實現


在HTML中,我們給需要拖動的元素加上draggable="true"就可以實現拖拽效果了。在CSS中,我們設置了列表和拖拽項的樣式。

<div class="list">
    <div draggable="true" class="list-item">1</div>
    <div draggable="true" class="list-item">2</div>
    <div draggable="true" class="list-item">3</div>
    <div draggable="true" class="list-item">4</div>
    <div draggable="true" class="list-item">5</div>
    <div draggable="true" class="list-item">6</div>
</div>
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
body {
    display: flex;
    justify-content: center;
}
.list {
    width: 600px;
    margin-top: 100px;
}
.list-item {
    margin: 6px 0;
    padding: 0 20px;
    line-height: 40px;
    height: 40px;
    background: #409eff;
    color: #fff;
    text-align: center;
    cursor: move;
    user-select: none;
    border-radius: 5px;
}

效果如下:

元素是可以拖拽了,但是拖拽時元素本身的樣式要改變,我們只需要給元素加上一個類樣式就可以了。那么,什么時候添加這個類呢?當然是開始拖動的時候,我們使用了HTML5的拖放API ondragstart,它是在用戶開始拖動元素時觸發。


2.1 拖拽開始

我們找到拖拽項的父元素,用事件委托的方式找到父元素,也就.list并給它注冊一個ondragstart事件,當拖拽開始時,可以使用event.target來獲取被拖拽的元素,給它的類型樣式添加一個moving。

.list-item.moving {
    background: transparent;
    color: transparent;
    border: 1px dashed #ccc;
}
const list = document.querySelector('.list');

list.ondragstart = (e) => {
    setTimeout(() => {
        e.target.classList.add('moving')
    }, 0)
}

為什么要加setTimeout呢?因為跟隨鼠標的樣式取決于拖拽開始時元素本身的樣式,拖轉開始時把元素的樣式改變了,那就意味著跟隨鼠標的樣式也改變了,我們可以加一個setTimeout變成異步,在拖拽開始時還是保持原來的樣式,然后過一點點時間在變成添加moving的樣式。


2.2 拖拽過程

(1)當被拖拽的元素移動到另一個列表項上方時,會不斷觸發dragover事件。

(2)默認情況下,瀏覽器不允許放置(drop)操作,因此需要阻止這個事件的默認行為。這可以通過調用event.preventDefault()方法來實現。

ondragover: 當某被拖動的對象在另一對象容器范圍內拖動時觸發此事件。

list.ondragover = (e) => {
    e.preventDefault();
}

(3)當用戶釋放鼠標按鈕,且被拖拽的元素位于一個有效的放置目標上方時,drop事件被觸發。

(4)在drop事件處理程序中,首先需要獲取拖拽源元素,接著取放置目標元素,這通常是觸發drop事件的元素。

(5)然后,需要更新DOM來反映新的排序。這通常涉及改變元素的位置,可以通過直接操作DOM(如insertBeforeappendChild)來實現。

ondragenter:當被鼠標拖動的對象進入其容器范圍內時觸發此事件。

const list = document.querySelector('.list');
// 記錄被拖拽的元素
let sourceNode;

list.ondragstart = (e) => {
    setTimeout(() => {
        e.target.classList.add('moving')
    }, 0)
    // 記錄被拖拽的元素
    sourceNode = e.target;
}

list.ondragover = (e) => {
    e.preventDefault();
}

list.ondragenter = e => {
    e.preventDefault();
    // 判斷拖拽元素進入的元素等于父元素list或等于拖拽元素本身,
    // 不做受任何處理,直接結束
    if(e.target === list || e.target === sourceNode) {
        return;
    }
    // 判斷元素拖拽進入的位置是在目標的上面還是下面,
    // 比如拖動3進入到4時,4要移動到上面,
    // 當拖動3進入到2時,2要移動到下面,
    // 通過元素所處的下表既可判斷。

    // 首先,拿到元素list所有的子元素
    const children = [...list.children];
    // 接著,拿到要拖拽元素在整個子元素里面的下標
    const sourceIndex = children.indexOf(sourceNode);
    // 然后,拿到要進入目標元素在整個子元素里面的下標
    const targetIndex = children.indexOf(e.target);
    if(sourceIndex < targetIndex) {
        // 進入目標元素大于拖拽元素的下標,
        // 此時要插入目標元素的下方位置,
        // 也就是目標元素下一個元素的前面
        list.insertBefore(sourceNode, e.target.nextElementSibling);
    } else {
        // 進入目標元素小于拖拽元素的下標,
        // 此時要插入目標元素的上方位置,
        // 也就是目標元素前面的位置
        list.insertBefore(sourceNode, e.target);
    }
}

2.3 拖拽結束

ondragend:用戶完成元素拖動后觸發。

list.ondragend = () => {
  sourceNode.classList.remove('moving');
}

拖拽結束時,只需要把moving的樣式移除即可。


03
Flip動畫


為了使元素位置改變時不那么生硬,可能需要提供一些額外的反饋,可以通過動畫來平滑地展示元素位置的改變。那么我們來了解一種動畫——Flip動畫。什么是Flip動畫呢?

Flip技術可以讓我們的動畫更加流暢,同時也能降低復雜動畫的開發難度。其實,Flip是幾個英文單詞的縮寫。

FFist —— 一個元素的起始位置。

L:Last —— 另一個元素的終止位置,注意另一個這個詞,后面會有具體代碼的體現。

I:Invert —— 計算"F"與"L"的差異,包括位置,大小等,并將差異用transform屬性,添加到終止元素上,讓它回到起始位置,也是此項技術的核心。

P:Play —— 添加transtion 過渡效果,清除Invert階段添加進來transform,播放動畫。


直接上帶代碼:

// Flip.js
const Flip = (function () {
 class FlipDom {
  constructor(dom, duration = 0.5) {
   this.dom = dom;
   this.transition =
    typeof duration === 'number' ? `${duration}s` : duration;
   this.firstPosition = {
    x: null,
    y: null,
   };
   this.isPlaying = false;
   this.transitionEndHandler = () => {
    this.isPlaying = false;
    this.recordFirst();
   }
  }

  getDomPosition() {
   const rect = this.dom.getBoundingClientRect();
   return {
    x: rect.left,
    y: rect.top,
   }
  }

  recordFirst(firstPosition) {
   if (!firstPosition) {
    firstPosition = this.getDomPosition()
   }
   this.firstPosition.x = firstPosition.x;
   this.firstPosition.y = firstPosition.y;
  }

  * play() {
   if (!this.isPlaying) {
    this.dom.style.transition = 'none';
    const lastPosition = this.getDomPosition();
    const dis = {
     x: lastPosition.x - this.firstPosition.x,
     y: lastPosition.y - this.firstPosition.y,
    }
    if (!dis.x && !dis.y) {
     return;
    }
    this.dom.style.transform = `translate(${-dis.x}px, ${-dis.y}px)`;
    yield 'moveToFirst';
    this.isPlaying = true;
   }
   this.dom.style.transition = this.transition;
   this.dom.style.transform = 'none';
   this.dom.removeEventListener('transitionend', this.transitionEndHandler);
   this.dom.addEventListener('transitionend', this.transitionEndHandler);
  }
 }

 class Flip {
  constructor(doms, duration = 0.5) {
   this.flipDoms = [...doms].map((it) => new FlipDom(it, duration));
   this.flipDoms = new Set(this.flipDoms);
   this.duration = duration;
   this.flipDoms.forEach((it) => it.recordFirst());
  }

  addDom(dom, firstPosition) {
   const flipDom = new FlipDom(dom, this.duration);
   this.flipDoms.add(flipDom)
   flipDom.recordFirst(firstPosition)
  }

  play() {
   let gs = [...this.flipDoms].map((it) => {
     const generator = it.play();
     return {
      generator,
      iteratorResult: generator.next()
     }
    })
    .filter((g) => !g.iteratorResult.done);

   while (gs.length > 0) {
    document.body.clientWidth;
    gs = gs.map((g) => {
      g.iteratorResult = g.generator.next();
      return g;
     })
     .filter((g) => !g.iteratorResult.done);
   }
  }
 }
 return Flip;
})();


完整代碼如下:

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title></title>
  <style>
   * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
   }
   body {
    display: flex;
    justify-content: center;
   }
   .list {
    width: 600px;
    margin-top: 100px;
   }
   .list-item {
    margin: 6px 0;
    padding: 0 20px;
    line-height: 40px;
    height: 40px;
    background: #409eff;
    color: #fff;
    text-align: center;
    cursor: move;
    user-select: none;
    border-radius: 5px;
   }
   .list-item.moving {
    background: transparent;
    color: transparent;
    border: 1px dashed #ccc;
   }
  </style>
 </head>
 <body>
  <div class="list">
   <div draggable="true" class="list-item">1</div>
   <div draggable="true" class="list-item">2</div>
   <div draggable="true" class="list-item">3</div>
   <div draggable="true" class="list-item">4</div>
   <div draggable="true" class="list-item">5</div>
   <div draggable="true" class="list-item">6</div>
  </div>
 </body>
 <script src="./flip.js"></script>
 <script>
  const list = document.querySelector('.list');
  // 記錄被拖拽的元素
  let sourceNode;
  let flip;
  
  list.ondragstart = (e) => {
   setTimeout(() => {
    e.target.classList.add('moving')
   }, 0)
   sourceNode = e.target;
   flip = new Flip(list.children, 0.5);
  }
  
  list.ondragover = (e) => {
   e.preventDefault();
  }
  
  list.ondragenter = e => {
   e.preventDefault();
   // 判斷拖拽元素進入的元素等于父元素list或等于拖拽元素本身,
   // 不做受任何處理,直接結束
   if(e.target === list || e.target === sourceNode) {
    return;
   }
   // 判斷元素拖拽進入的位置是在目標的上面還是下面,
   // 比如拖動3進入到4時,4要移動到上面,
   // 當拖動3進入到2時,2要移動到下面,
   // 通過元素所處的下表既可判斷。
   
   // 首先,拿到元素list所有的子元素
   const children = [...list.children];
   // 接著,拿到要拖拽元素在整個子元素里面的下標
   const sourceIndex = children.indexOf(sourceNode);
   // 然后,拿到要進入目標元素在整個子元素里面的下標
   const targetIndex = children.indexOf(e.target);
   if(sourceIndex < targetIndex) {
    // 進入目標元素大于拖拽元素的下標,
    // 此時要插入目標元素的下方位置,
    // 也就是目標元素下一個元素的前面
    list.insertBefore(sourceNode, e.target.nextElementSibling);
   } else {
    // 進入目標元素小于拖拽元素的下標,
    // 此時要插入目標元素的上方位置,
    // 也就是目標元素前面的位置
    list.insertBefore(sourceNode, e.target);
   }
   // 調用flip動畫play方法
   flip.play();
  }
  
  list.ondragend = () => {
   sourceNode.classList.remove('moving');
  }
 </script>
</html>


該文章在 2024/4/12 23:19:02 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved