用兩種不一樣的姿式來實現拖動排序

前言

魯迅說過:「若是你的技術很差,那麼你的姿式要多。」html

魯迅

公司的項目最近給側邊欄加了拖動排序的效果,相似於下面這種效果(魯迅:不是個人下面),是下面的圖?:
動畫html5

drag拖動排序

說來慚愧,上圖不是我寫的,否則我還能吹一波,沒有看代碼,但閉着眼就能猜到用的是html5的drag系列的api,相關的API能夠看MDN上的介紹,我把文檔拷一下作個介紹吧==git

  1. drag: 當元素或者選擇的文本被拖動時觸發drag事件 (每幾百毫秒)。
  2. dragstart: 當用戶開始拖動一個元素或者一個選擇文本的時候dragstart事件就會觸發。通常在這個動做裏面處理被拖動元素的樣式。
  3. dragend: 拖動操做結束時觸發,即鬆開鼠標操做。
  4. dragenter: a拖到b中的時候觸發。
  5. dragover: 拖動過程不斷觸發。
  6. dragexit: 元素不是被拖動目標時。
  7. dragleave: a拖到b,不釋放鼠標,再拖到c,離開b的時候觸發。
  8. drop: 當一個元素或是選中的文字被拖拽釋放到一個有效的釋放目標位置時

上面的大部分都是對樣式之類的處理,官方也給了例子,排除掉dragdragover這兩個觸發大戶,大概的順序是這樣的:
順序github

首先,我確定是實現了效果的,以下圖所示的:
jijiDEchrome

粗略介紹下流程吧,首先須要肯定的是應該是什麼效果,打個比方,我把a拖到了b上,b能很明顯的感受到,「啊,有什麼奇怪的東西進入個人體內了」。可是b並不知道a是想在前面仍是想在後面...這時候你就發現問題了吧,對!提問:「a和b到底誰在上面誰在下面?」因此咱們剛開始要約定一個規範,把上面的a拖向下面的b,那麼就把a排在b的下面,反之,把下面的a拖向上面的b,那麼就把a放在b的上面~首先我須要這四個對象:api

let dragObj, enterObj, dragIndex, enterIndex;
// dragObj 被拖動的對象a,整個拖動過程它是不會變的
// enterObj 最終進入的對象b,在drop裏能夠獲取到
// dragIndex a在列表中的下標
// enterIndex b在列表的下標

經過比較dragIndexenterIndex的大小來決定a和b的關係,剩下的就是對dom進行操做了:數組

if (dragIndex < enterIndex) {
  dragObj.remove();
  enterObj.after(dragObj);
} else if (dragIndex > enterIndex) {
  dragObj.remove();
  enterObj.before(dragObj);
}   

是否是至關簡單了效果預覽 (chrome下觀看)
源碼安全

更好的實現--mouse

直接看看效果吧,也能夠作個比較:
效果dom

預覽效果
使用到的api:onmousedown, onmousemove, onmouseup,其實這些你們多多少少應該會用過,但我仍是在這裏粗略介紹下:動畫

  1. onmousedown: 在當前元素上點擊鼠標按鍵時會觸發mousedown事件,有人問了,那這個和click有什麼區別呢,區別是 click = mousedown + mouseup,而後是,click鼠標左鍵觸發,mousedown鼠標點擊即會觸發。
  2. onmousemove: 當用戶在當前元素上移動鼠標時會觸發mousemove事件,就是「拖動元素。
  3. onmouseup: 當用戶在當前元素上放開鼠標某個按鍵時會觸發mouseup事件。

整個實現過程也比上面的那一種要複雜的多,不一樣與drag,drag本身能感知元素從a移動到了b中或者從b中離開,而鼠標事件不行。

拖動注意點

這樣一個列表:
仍是他們

假設我點了「小強」,「小強」被我選中,我怎麼去實現拖動的效果呢?元素自己是沒有拖動這個說法的,我能作到的就是動態的改變它的位置,那就是在onmousemove的時候動態的去改變了,改變元素位置且不影響到其餘元素排布的,那就是absolute了。先實現拖動的效果,大概就是這樣:

//在onmousedown的時候記下必要的信息,將小強的position改爲absolute
const startY = event.clientY;
//在onmousemove記錄下鼠標在y軸上移動的距離,橫行的話就是x
const currentTop = parseInt(startTop) + (moveY - startY);

這樣基本的拖動問題是解決了,元素最起碼是能夠拖動起來了。這裏須要注意的是,只有「小強」的位置是absolute,這時候「小明」就會被小強遮住,因此咱們這裏須要一個元素「佔位」:

<div class="item hold"><div>

--「進來了嗎?」 --「不是早就進來了」

各位乘客繫好安全帶,我要開車了...
理想的效果應該是這樣的,「小強」向下拖動,距離「小明」底部小於一半時,「小明」就應該給「小強」讓路,「小明」要知道小強何時進來了,而後hold元素向下移動一格,說明小強「此時」已經在「小明」下面了:

currentTop > itemHeight + itemHeight / 2;

記錄高度實在是不必,我只須要記錄「小強」 當前 被拖動到「第幾位」就能夠了:

currentIndex = Math.ceil((currentTop - itemHeight / 2) / itemHeight);

只要拿到onmousedown時的「小強」的「index」,與當前的比較,只要二者不相同,就須要移動hold的位置,hold元素的位置受當前index的影響:

if (previousIndex !== currentIndex) { //... }

此處省略的代碼都是dom的操做,不作太多說明。此時咱們並無對拖動的元素作限制,元素可以被拖到盒子的外面,這樣確定是不能夠的,作一個限制:

if (currentIndex < 0) {
  currentIndex = 0;
} else if (currentIndex > listLength - 1) {
  currentIndex = listLength - 1;
}

這樣不會限制的用戶拖動的操做,咱們只須要控制住index就行了,這樣處理也方便的多。
在用戶onmouseup的時候須要對監聽器進行銷燬:

document.onmousemove = null;
document.onmouseup = null;

注意事項

相似於$$querySelectorAll獲取到的"數組"並非「數組」,它有數組的部分特性,經過下圖咱們能夠知道它是一個NodeList,並非Array,它是有forEach方法的,可是它並無mapfind等一系列方法,使用的時候還需注意。
list

源碼

細節仍是看源碼吧 我在剛開始寫的時候碰到不少坑,dom操做的時候更是,尤爲在上下來回拖動的時候,具體的再也不多講,由於實在有點晚了。對不起,我今晚玩遊戲玩到十一點半,我有罪==,洗個澡啥的,弄的很晚。
享受coding,熱愛生活,拜~
coding

相關文章
相關標籤/搜索