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 前端选区内容请搜索好代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持好代码网!

标签: React 选区