React实现前端选区的示例代码

 更新时间:2023年05月05日 15:31:09   作者:sole  
本文主要介绍了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 前端选区内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Taro React自定义TabBar使用useContext解决底部选中异常

    Taro React自定义TabBar使用useContext解决底部选中异常

    这篇文章主要为大家介绍了Taro React底部自定义TabBar使用React useContext解决底部选中异常(需要点两次才能选中的问题)示例详解,有需要的朋友可以借鉴参考下
    2023-08-08
  • React-Router如何进行页面权限管理的方法

    React-Router如何进行页面权限管理的方法

    本篇文章主要介绍了React-Router如何进行页面权限管理的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • 路由react-router-dom的基本使用教程

    路由react-router-dom的基本使用教程

    在React中,路由是一套映射规则,是URL路径与组件的对应关系。使用React路由,就是配置路径和组件的对应关系,这篇文章主要介绍了路由react-router-dom的使用,需要的朋友可以参考下
    2023-02-02
  • React如何使用refresh_token实现无感刷新页面

    React如何使用refresh_token实现无感刷新页面

    本文主要介绍了React如何使用refresh_token实现无感刷新页面,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • React中闭包陷阱的几种情及解决方案

    React中闭包陷阱的几种情及解决方案

    在react中我们使用其提供的Hooks中的useState,useEffect,useCallback 时,可能会造成闭包陷阱,下面我们来看一下出现的情况以及如何解决,感兴趣的小伙伴跟着小编一起来看看吧
    2024-07-07
  • 理解react中受控组件和非受控组件及应用场景

    理解react中受控组件和非受控组件及应用场景

    当涉及到React框架时,了解受控组件和非受控组件是非常重要的概念,本文主要介绍了理解react中受控组件和非受控组件及应用场景,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • react中实现将一个视频流为m3u8格式的转换

    react中实现将一个视频流为m3u8格式的转换

    这篇文章主要介绍了react中实现将一个视频流为m3u8格式的转换方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • 更强大的React 状态管理库Zustand使用详解

    更强大的React 状态管理库Zustand使用详解

    这篇文章主要为大家介绍了更强大的React 状态管理库Zustand使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • 浅谈React多个setState会调用几次

    浅谈React多个setState会调用几次

    在React的生命周期钩子和合成事件中,多次执行setState,会被调用几次,本文就详细的介绍一下,感兴趣的可以了解一下
    2021-11-11
  • React Native从类组件到函数组件详解

    React Native从类组件到函数组件详解

    这篇文章主要介绍了React Native从类组件到函数组件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-03-03

最新评论