React 自定義一個交換類型拖放

前言

本質上是實現一個矩形內的列表進行數據交換,並配套必要的樣式。
交換類型拖放具體分爲三部分git

  1. 鼠標點擊某個元素
  2. 鼠標拖動該元素
  3. 拖到另一個元素的上面後,鼠標鬆開並與之交換位置

實現

必要的數據預約義

/*
* 必要參數,可考慮從外部接收
* lineHeight是單格高度
* lineWidth是單格寬度
* lineNumber是一行的最大個數
* proportion 是交換位置的最低比例
*/
const lineHeight:number = 40;
const lineWidth:number = 102 ;
const lineNumber:number = 5;
const proportion:number = 0.6;

第一步 鼠標點擊某個元素

這個很簡單,只要監聽鼠標點擊事件就好了。github

onMouseDown={e => handleMouseDown(e, index)}

同時須要對拖放過程當中須要用到的數據進行初始化。ui

const handleMouseDown = (e: React.MouseEvent, index: number) => {
      setDragging(true);
      setDraggingIndex(index);
      setStartPage({"x": e.pageX,"y": e.pageY});
  }

爲了讓使用者看出來進入了拖放模式,樣式也須要微調(具體想成什麼樣子能夠自定義)code

style={getDraggingStyle(index)}`

須要注意的是,拖放的樣式效果會在這經過藉助transform的偏移實現orm

const getDraggingStyle = (index: number) => {
    if(index !== draggingIndex) {
      return;
    }
    return {
        transform:`translate(${offsetPage.x}px,${offsetPage.y}px),
        opacity:0.5,
    }
  }

第二步 鼠標拖動該元素

關鍵點有兩點事件

1. 是如何移動

以前已經提到過了實現方式是transform的偏移,其中的變量則是能夠經過監聽鼠標移動事件。ci

onMouseMove={e => handleMouseMove(e)}

並在移動過程當中計算偏移量,包括X軸(左右)和Y軸(上下)get

const handleMouseMove = (e: React.MouseEvent) => {
      setOffsetPage({"x": e.pageX - startPage.x, "y": e.pageY - startPage.y});
  }

2. 在哪裏移動

爲方便移動過程當中不受到其餘因素干擾,能夠建立一個覆蓋層,元素在覆蓋層上移動。覆蓋層隨移動而生,隨放下而去。源碼

{dragging && (
        <div 
          className="drag-mask"
          onMouseMove={e => handleMouseMove(e)}
          onMouseUp={e => handleMouseUp(e)}
        />
      )}

覆蓋層的CSSit

.drag-mask {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  background:rgba(0, 0, 0, 0);
}

第三步 鼠標鬆開並與之交換位置

關鍵點有兩點

1. 移動位置

以前的偏移位置是實時計算出來的,正負表明方向,大小表示距離。
移動的位置在第一步就定義了,還須要的是被交換元素的位置。
這裏用最少移動距離來判斷是哪一個元素須要被交換。舉例來講,若是一個方向上移動的距離沒有超過一個元素的一半距離(proportion,可自定義),則和前一個元素交換,不然和當前元素交換。

let leastX = lineWidth * proportion; //X軸最少移動距離
let leastY = lineHeight * proportion; //Y軸最少移動距離

以前的移動格數用向下取整就能夠得到

let offsetNumberX = Math.floor(Math.abs(lengthX / lineWidth)); //X軸移動格數
let offsetNumberY = Math.floor(Math.abs(lengthY / lineHeight)); //Y軸移動格數

最後的計算

if(offsetLastX >= leastX) {
  offsetNumberX++;
}
if(offsetLastY >= leastY){
  offsetNumberY++;
}

2. 邊界條件

1. 列表元素個數限制
if(currentIndex < list.length && currentIndex >= 0) {
    [list[startIndex],list[currentIndex]] = [list[currentIndex],list[startIndex]];
}
2. 矩形區域限制

超出列表範圍。
列表看上去多是一個不規則區域,可是最後一行空位的能夠在第一個限制中計算,因此能夠合併成一個矩形區域。
矩形的範圍經過ref得到Dom元素

const rangeUI = useRef<HTMLUListElement>(null);
<ul ref={ rangeUI }>

得到元素的相關數據,計算得到位置數據

const [mainDirection, setMainDirection] = useState<normalType>({"top": 0, "left": 0, "right": 0 ,"bottom": 0});

useEffect(() => {
    if(rangeUI.current) {
        setMainDirection({"top": rangeUI.current.offsetTop, "left":rangeUI.current.offsetLeft, 
        "right": rangeUI.current.offsetLeft + rangeUI.current.offsetWidth ,"bottom": rangeUI.current.offsetTop + rangeUI.current.offsetHeight})
}
},[])

判斷是否超出範圍

if((flagX === 1 && lengthX > (mainDirection.right - startPage.x))
    || (flagX === -1 && -lengthX > (startPage.x - mainDirection.left))
    || (flagY === 1 && lengthY > (mainDirection.bottom - startPage.y))
    || (flagY === -1 && -lengthY > (startPage.y - mainDirection.top))
  )

3. 交換位置

被交換元素

currentIndex = startIndex+(offsetNumberX * flagX)+(offsetNumberY * flagY)*lineNumber;

交換位置,即交換列表中的元素位置

[list[startIndex],list[currentIndex]] =[list[currentIndex],list[startIndex]];

最後把數據都初始化

setDragging(false);
setDraggingIndex(-1);
setStartPage({"x": 0, "y": 0});
setOffsetPage({"x": 0, "y": 0})

結尾

歡迎交流
源碼:
https://github.com/SunGuistar...

相關文章
相關標籤/搜索