React實現前端選區的示例代碼

什麼是選區

前端選區非常常見,通過鼠標的點擊和移動來創建一個選中頁面,在多個元素需要被選中的情況下,一個個點擊顯的非常拖沓,所以需要通過建立選區來應對多個元素的操作。以下是簡單的建立選區圖片例子:

image.png

如何建立選區

瀏覽器繪制選區方式

在瀏覽器中繪制一個矩形,通常是通過其元素的top和left來決定,瀏覽器通過確定元素的top和left位置,並監聽鼠標在移動過程中的偏移量offsetX和offsetY來計算元素的總寬高,最後繪制成元素的總體形狀,固定好元素的top和left極為重要。如下是瀏覽器繪制的示意圖:

image.png

瀏覽器在繪制時,總是以左上角的頂點作為元素的起始位置,也就是說如果你的選區是從右往左或者從下往上建立,瀏覽器都會以最終圖形的左上角頂點作為元素的起始位置進行繪制。所以確定左上角頂點位置尤為關鍵。

React計算選區范圍

通過監聽瀏覽器的鼠標移動事件來獲取鼠標移動的范圍,在這裡我們需要先獲取鼠標初始位置,通過movedown事件確定鼠標起始位置,通過isMove來判斷是否鼠標落下並開始移動,記錄鼠標落點位置。代碼如下圖所示:

const [isMove, setIsMove] = useState<boolean>(false);
const [downAndUpPosition, setDownAndUpPosition] = useState<Position>();
const handleMouseDown = (event: React.MouseEvent) => {
    setIsMove(true);
    let mouseDownPosition: Position = {
      offsetX: event.pageX - left,
      offsetY: event.pageY,
    };
    setDownAndUpPosition(mouseDownPosition);
  };

記錄鼠標落點後,通過mousemove事件記錄鼠標移動坐標點,如果鼠標未落下則不觸發移動事件,避免重復渲染,最後在moveup事件中取消移動標記即可:

const [movePosition, setMovePosition] = useState<Position>();
const handleMouseMove = (event: React.MouseEvent) => {
    if (!isMove) return;
    let movePosition: Position = {
      offsetX: event.pageX - left,
      offsetY: event.pageY,
    };
    setMovePosition(movePosition);
  };
const handleMouseUp = () => {
    setIsMove(false);
  };

得到鼠標落點位置和鼠標移動坐標後,我們需要去計算鼠標移動坐標與起始位置的坐標象限,如果起始位置的left與top均小於鼠標移動後坐標,則選區在第四象限。以起始位置為坐標原點去計算。代碼如下所示:

const returnDivPosition = (
    downAndUpPosition: Position,
    movePosition: Position
  ) => {
    const downPageX = downAndUpPosition.offsetX;
    const downPageY = downAndUpPosition.offsetY;
    const movePageX = movePosition.offsetX;
    const movePageY = movePosition.offsetY;
    if (downPageX >= movePageX && downPageY >= movePageY) {
      return 1;
    }
    if (downPageX <= movePageX && downPageY >= movePageY) {
      return 2;
    }
    if (downPageX >= movePageX && downPageY <= movePageY) {
      return 3;
    }
    if (downPageX <= movePageX && downPageY <= movePageY) {
      return 4;
    }
 };

計算出鼠標最終位置處於哪個象限後,我們就可以計算出選區的top和left。如果為第一象限則鼠標移動的最終位置就是元素偏移量,如果為第二象限則鼠標移動最終位置的top為元素偏移top,鼠標落點left為元素偏移left,以此類推即可。代碼如下圖:

const offsetPosition = useMemo(() => {
    if (downAndUpPosition && movePosition) {
      const quadrant = returnDivPosition(downAndUpPosition, movePosition);
      switch (quadrant) {
        case 1:
          return {
            top: movePosition.offsetY,
            left: movePosition.offsetX,
          };
        case 2:
          return {
            top: movePosition.offsetY,
            left: downAndUpPosition.offsetX,
          };
        case 3:
          return {
            top: downAndUpPosition.offsetY,
            left: movePosition.offsetX,
          };
        case 4:
          return {
            top: downAndUpPosition.offsetY,
            left: downAndUpPosition.offsetX,
          };
      }
    }
    return {
      top: 0,
      left: 0,
    };
 }, [downAndUpPosition, movePosition]);

最後我們計算選區的寬度和高度,通過計算落點位置與鼠標移動後最終位置的差值可獲取寬高。代碼如下:

const offsetSize = useMemo(() => {
    if (downAndUpPosition && movePosition) {
      return {
        width: Math.abs(downAndUpPosition.offsetX - movePosition.offsetX),
        height: Math.abs(downAndUpPosition.offsetY - movePosition.offsetY),
      };
    }
    return {
      width: 0,
      height: 0,
    };
 }, [downAndUpPosition, movePosition]);

這樣就能夠創建一個選區,完整代碼如下圖:

type Position = {
  offsetX: number;
  offsetY: number;
};
const boardStyle: CSSProperties = {
  width: "100%",
  height: "calc(100vh - 10px)",
  position: "relative",
};
const SelectArea = ()=>{
const [isMove, setIsMove] = useState<boolean>(false);
const [downAndUpPosition, setDownAndUpPosition] = useState<Position>();
const [movePosition, setMovePosition] = useState<Position>();
const handleMouseDown = (event: React.MouseEvent) => {
    setIsMove(true);
    let mouseDownPosition: Position = {
      offsetX: event.pageX - left,
      offsetY: event.pageY,
    };
    setDownAndUpPosition(mouseDownPosition);
  };
const handleMouseMove = (event: React.MouseEvent) => {
    if (!isMove) return;
    let movePosition: Position = {
      offsetX: event.pageX - left,
      offsetY: event.pageY,
    };
    setMovePosition(movePosition);
  };
const handleMouseUp = () => {
    setIsMove(false);
  };
const returnDivPosition = (
    downAndUpPosition: Position,
    movePosition: Position
  ) => {
    const downPageX = downAndUpPosition.offsetX;
    const downPageY = downAndUpPosition.offsetY;
    const movePageX = movePosition.offsetX;
    const movePageY = movePosition.offsetY;
    if (downPageX >= movePageX && downPageY >= movePageY) {
      return 1;
    }
    if (downPageX <= movePageX && downPageY >= movePageY) {
      return 2;
    }
    if (downPageX >= movePageX && downPageY <= movePageY) {
      return 3;
    }
    if (downPageX <= movePageX && downPageY <= movePageY) {
      return 4;
    }
 };
const offsetSize = useMemo(() => {
    if (downAndUpPosition && movePosition) {
      return {
        width: Math.abs(downAndUpPosition.offsetX - movePosition.offsetX),
        height: Math.abs(downAndUpPosition.offsetY - movePosition.offsetY),
      };
    }
    return {
      width: 0,
      height: 0,
    };
  }, [downAndUpPosition, movePosition]);
  const offsetPosition = useMemo(() => {
    if (downAndUpPosition && movePosition) {
      const quadrant = returnDivPosition(downAndUpPosition, movePosition);
      switch (quadrant) {
        case 1:
          return {
            top: movePosition.offsetY,
            left: movePosition.offsetX,
          };
        case 2:
          return {
            top: movePosition.offsetY,
            left: downAndUpPosition.offsetX,
          };
        case 3:
          return {
            top: downAndUpPosition.offsetY,
            left: movePosition.offsetX,
          };
        case 4:
          return {
            top: downAndUpPosition.offsetY,
            left: downAndUpPosition.offsetX,
          };
      }
    }
    return {
      top: 0,
      left: 0,
    };
  }, [downAndUpPosition, movePosition]);
  return (
    <div
      onMouseDown={handleMouseDown}
      onMouseMove={handleMouseMove}
      onMouseUp={handleMouseUp}
      style={boardStyle}
    >
        <Electorate
          top={offsetPosition.top}
          left={offsetPosition.left}
          width={offsetSize.width}
          height={offsetSize.height}
        />
    </div>
  );
}
type Props = {
  top: number;
  left: number;
  width: number;
  height: number;
};
const Electorate: FC<Props> = ({ top, left, width, height }) => {
  return (
    <div
      style={{
        top: `${top}px`,
        left: `${left}px`,
        width: `${Math.abs(width)}px`,
        height: `${Math.abs(height)}px`,
      }}
      className="electorate"
    />
  );
};
//electorate樣式
.electorate {
    position: absolute;
    border: 1px solid rgba(33, 127, 235, 0.534);
    background-color: rgba(33, 127, 235, 0.3);
}

到此這篇關於React實現前端選區的示例代碼的文章就介紹到這瞭,更多相關React 前端選區內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: