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

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

【JavaScript】手摸手教你實(shí)現(xiàn)一個(gè)自己的拖拽排序

admin
2024年4月12日 23:19 本文熱度 1390
01
寫(xiě)在前面

拖拽排序是一種在網(wǎng)頁(yè)設(shè)計(jì)和應(yīng)用程序中常見(jiàn)的交互方式,允許用戶通過(guò)鼠標(biāo)或觸摸操作來(lái)重新排列頁(yè)面或界面上的元素。這種交互方式對(duì)于提升用戶體驗(yàn)和操作效率具有重要意義。


在拖拽排序中,用戶可以用鼠標(biāo)或手指按住某個(gè)元素,然后將其拖動(dòng)到新

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


從技術(shù)實(shí)現(xiàn)的角度來(lái)看,拖拽排序主要依賴于前端技術(shù)的支持。例如,基于JavaScript的實(shí)現(xiàn)方法主要是通過(guò)監(jiān)聽(tīng)鼠標(biāo)或觸摸事件來(lái)實(shí)現(xiàn)。在拖拽開(kāi)始時(shí),需要記錄拖拽元素的位置,然后在拖拽過(guò)程中更新元素的位置,最后在拖拽結(jié)束時(shí)判斷元素與其他元素的位置關(guān)系并進(jìn)行排序。


在拖拽排序的應(yīng)用場(chǎng)景中,列表排序和圖片排序是兩個(gè)典型的例子。在列表排序中,用戶可以通過(guò)拖動(dòng)列表項(xiàng)來(lái)改變它們的順序,這在任務(wù)管理應(yīng)用、待辦事項(xiàng)列表等場(chǎng)景中非常常見(jiàn)。在圖片排序中,用戶可以通過(guò)拖動(dòng)圖片來(lái)改變它們的順序,這在圖片庫(kù)或相冊(cè)應(yīng)用中較為常見(jiàn)。


02
實(shí)現(xiàn)


在HTML中,我們給需要拖動(dòng)的元素加上draggable="true"就可以實(shí)現(xiàn)拖拽效果了。在CSS中,我們?cè)O(shè)置了列表和拖拽項(xiàng)的樣式。

<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;
}

效果如下:

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


2.1 拖拽開(kāi)始

我們找到拖拽項(xiàng)的父元素,用事件委托的方式找到父元素,也就.list并給它注冊(cè)一個(gè)ondragstart事件,當(dāng)拖拽開(kāi)始時(shí),可以使用event.target來(lái)獲取被拖拽的元素,給它的類型樣式添加一個(gè)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呢?因?yàn)?span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; font-size: var(--articleFontsize); letter-spacing: 0.034em;">跟隨鼠標(biāo)的樣式取決于拖拽開(kāi)始時(shí)元素本身的樣式,拖轉(zhuǎn)開(kāi)始時(shí)把元素的樣式改變了,那就意味著跟隨鼠標(biāo)的樣式也改變了,我們可以加一個(gè)setTimeout變成異步,在拖拽開(kāi)始時(shí)還是保持原來(lái)的樣式,然后過(guò)一點(diǎn)點(diǎn)時(shí)間在變成添加moving的樣式。


2.2 拖拽過(guò)程

(1)當(dāng)被拖拽的元素移動(dòng)到另一個(gè)列表項(xiàng)上方時(shí),會(huì)不斷觸發(fā)dragover事件。

(2)默認(rèn)情況下,瀏覽器不允許放置(drop)操作,因此需要阻止這個(gè)事件的默認(rèn)行為。這可以通過(guò)調(diào)用event.preventDefault()方法來(lái)實(shí)現(xiàn)。

ondragover: 當(dāng)某被拖動(dòng)的對(duì)象在另一對(duì)象容器范圍內(nèi)拖動(dòng)時(shí)觸發(fā)此事件。

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

(3)當(dāng)用戶釋放鼠標(biāo)按鈕,且被拖拽的元素位于一個(gè)有效的放置目標(biāo)上方時(shí),drop事件被觸發(fā)。

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

(5)然后,需要更新DOM來(lái)反映新的排序。這通常涉及改變?cè)氐奈恢茫梢酝ㄟ^(guò)直接操作DOM(如insertBeforeappendChild)來(lái)實(shí)現(xiàn)。

ondragenter:當(dāng)被鼠標(biāo)拖動(dòng)的對(duì)象進(jìn)入其容器范圍內(nèi)時(shí)觸發(fā)此事件。

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();
    // 判斷拖拽元素進(jìn)入的元素等于父元素list或等于拖拽元素本身,
    // 不做受任何處理,直接結(jié)束
    if(e.target === list || e.target === sourceNode) {
        return;
    }
    // 判斷元素拖拽進(jìn)入的位置是在目標(biāo)的上面還是下面,
    // 比如拖動(dòng)3進(jìn)入到4時(shí),4要移動(dòng)到上面,
    // 當(dāng)拖動(dòng)3進(jìn)入到2時(shí),2要移動(dòng)到下面,
    // 通過(guò)元素所處的下表既可判斷。

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

2.3 拖拽結(jié)束

ondragend:用戶完成元素拖動(dòng)后觸發(fā)。

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

拖拽結(jié)束時(shí),只需要把moving的樣式移除即可。


03
Flip動(dòng)畫(huà)


為了使元素位置改變時(shí)不那么生硬,可能需要提供一些額外的反饋,可以通過(guò)動(dòng)畫(huà)來(lái)平滑地展示元素位置的改變。那么我們來(lái)了解一種動(dòng)畫(huà)——Flip動(dòng)畫(huà)。什么是Flip動(dòng)畫(huà)呢?

Flip技術(shù)可以讓我們的動(dòng)畫(huà)更加流暢,同時(shí)也能降低復(fù)雜動(dòng)畫(huà)的開(kāi)發(fā)難度。其實(shí),Flip是幾個(gè)英文單詞的縮寫(xiě)。

FFist —— 一個(gè)元素的起始位置。

L:Last —— 另一個(gè)元素的終止位置,注意另一個(gè)這個(gè)詞,后面會(huì)有具體代碼的體現(xiàn)。

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

P:Play —— 添加transtion 過(guò)渡效果,清除Invert階段添加進(jìn)來(lái)transform,播放動(dòng)畫(huà)。


直接上帶代碼:

// 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();
   // 判斷拖拽元素進(jìn)入的元素等于父元素list或等于拖拽元素本身,
   // 不做受任何處理,直接結(jié)束
   if(e.target === list || e.target === sourceNode) {
    return;
   }
   // 判斷元素拖拽進(jìn)入的位置是在目標(biāo)的上面還是下面,
   // 比如拖動(dòng)3進(jìn)入到4時(shí),4要移動(dòng)到上面,
   // 當(dāng)拖動(dòng)3進(jìn)入到2時(shí),2要移動(dòng)到下面,
   // 通過(guò)元素所處的下表既可判斷。
   
   // 首先,拿到元素list所有的子元素
   const children = [...list.children];
   // 接著,拿到要拖拽元素在整個(gè)子元素里面的下標(biāo)
   const sourceIndex = children.indexOf(sourceNode);
   // 然后,拿到要進(jìn)入目標(biāo)元素在整個(gè)子元素里面的下標(biāo)
   const targetIndex = children.indexOf(e.target);
   if(sourceIndex < targetIndex) {
    // 進(jìn)入目標(biāo)元素大于拖拽元素的下標(biāo),
    // 此時(shí)要插入目標(biāo)元素的下方位置,
    // 也就是目標(biāo)元素下一個(gè)元素的前面
    list.insertBefore(sourceNode, e.target.nextElementSibling);
   } else {
    // 進(jìn)入目標(biāo)元素小于拖拽元素的下標(biāo),
    // 此時(shí)要插入目標(biāo)元素的上方位置,
    // 也就是目標(biāo)元素前面的位置
    list.insertBefore(sourceNode, e.target);
   }
   // 調(diào)用flip動(dòng)畫(huà)play方法
   flip.play();
  }
  
  list.ondragend = () => {
   sourceNode.classList.remove('moving');
  }
 </script>
</html>


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