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

目录
  • 什么是选区
  • 如何建立选区
    • 浏览器绘制选区方式
    • React计算选区范围

什么是选区

前端选区非常常见,通过鼠标的点击和移动来创建一个选中页面,在多个元素需要被选中的情况下,一个个点击显的非常拖沓,所以需要通过建立选区来应对多个元素的操作。以下是简单的建立选区图片例子:

如何建立选区

浏览器绘制选区方式

在浏览器中绘制一个矩形,通常是通过其元素的top和left来决定,浏览器通过确定元素的top和left位置,并监听鼠标在移动过程中的偏移量offsetX和offsetY来计算元素的总宽高,最后绘制成元素的总体形状,固定好元素的top和left极为重要。如下是浏览器绘制的示意图:

浏览器在绘制时,总是以左上角的顶点作为元素的起始位置,也就是说如果你的选区是从右往左或者从下往上建立,浏览器都会以最终图形的左上角顶点作为元素的起始位置进行绘制。所以确定左上角顶点位置尤为关键。

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

(0)

相关推荐

  • 详解React 的几种条件渲染以及选择

    对于一个展示页面来讲, 通常有好几种展示状态(以列表页为例): 数据为空, 空页面 取数据时发生错误, 错误页面 数据正常 加载状态 针对以上三种情况, react渲染列表的时候要正确判断并渲染出相应的视图, 也就是条件渲染. 不同于vue的v-if, v-show等框架提供的api, react的条件渲染都是js原生的再加上一点点的hack. 比如react文档提到的. if/else, && 和三目等等. 当然上面的都是常用的一些方法, 但是也存在着各种问题, 比如条件分支过多的的事时

  • react实现点击选中的li高亮的示例代码

    虽然只是一个简单的功能,还是记录一下比较好.页面上有很多个li,要实现点击到哪个就哪个高亮.当年用jq的时候,也挺简单的,就是选中的元素给addClass,然后它的兄弟元素removeClass,再写个active的样式就搞定了.那现在用react要实现类似的操作,我想到的就是用一个currentIndex,通过判断currentIndex在哪个元素实现切换. 先上一下效果图: 代码: class Category extends React.Component { constructor(pr

  • React实现表格选取

    本文实例为大家分享了React实现表格选取的具体代码,供大家参考,具体内容如下 在工作中,遇到一个需求,在表格中实现类似于Excel选中一片区域的,然后拿到选中区域的所有数据. 1.实现需求和效果截图 1.获取选中区域的数据2.选择的方向是任意的3.支持几行 / 几列的选取4.通过生产JSON给后台进行交互5.标记出表头和第一行的数据 2.核心代码解析 2.1区域选择 onClick={() => {      // 区间选取       if (itemIndex != 0) {      

  • React鼠标多选功能的配置方法

    一般列表都有选择功能,单选复选多选都很常见.在自定义循环的列表,图像中,实现鼠标单选,多选,反选功能. # React mousemultiples # React 鼠标多选组件 React 鼠标多选组件 局限性 > 主要实现鼠标多选的效果, 在不破坏原有的列表情况下,嵌入组件拥有鼠标多选功能. npm包地址 [链接](https://www.npmjs.com/package/mousemultiples) 安装 npm i mousemultiples 使用配置项 /**  * wrappe

  • React实现全选功能

    本文实例为大家分享了React实现全选功能的具体代码,供大家参考,具体内容如下 1.主要就是使用state状态管理 2.jsx写的时候要多留心,return 的时候最好用一个()包着元素部分,不然有可能编译不过. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" conten

  • 详解React Native开源时间日期选择器组件(react-native-datetime)

    项目介绍 该组件进行封装一个时间日期选择器,同时适配Android.iOS双平台,该组件基于@remobile/react-native-datetime-picker进行开发而来 配置安装 npm install react-native-datetime --save 1.1.iOS环境配置 上面步骤完成之后,直接前台写js代码即可 1.2.Android环境配置 在android/setting.gradle文件中如下配置 ... include ':react-native-dateti

  • react实现动态选择框

    本文实例为大家分享了react实现动态选择框的具体代码,供大家参考,具体内容如下 小需求 在工作中,我们也会碰到这种需求: 为了提高用户的体验,在搜索的时候,采用灵活查询.用户可以自己选择查询项并且填写对应的值. 这篇博文涉及知识点在这篇博文“react+antd 动态编辑表格数据”中提及过.大家可以先去这篇学习一下然后这里. 示例代码 import React, { Component, useState } from 'react'; import { Button, Col, messag

  • ReactJS实现表单的单选多选和反选的示例

    本文介绍了ReactJS实现表单的单选多选和反选的示例,分享给大家,希望对大家有所帮助. 需求是对列表实现单选,反选和多选,全部清除的操作 ...... this.state = { //初始化空数组,表示已经选择的 selectedStores:[], } ...... handleClick(e){ const newSelection = e.target.value;//拿到点击的具体一项 let newSelectionArray;//新建一个空数组 //判断点击项是否为选择状态,是的

  • React中前端路由的示例代码

    目录 一. url是什么 二. 使用步骤 一. url是什么 访问不同url, 展示不同的组件 二. 使用步骤 安装React路由:命令行中执行npm install react-router-dom --save(注意此处的版本为npm install react-router-dom@4.3.1 --save) 两个js文件,分别为list.js和newButton.js,要实现访问localhost:3000/button的时候就显示button.js:访问localhost:3000/l

  • React+EggJs实现断点续传的示例代码

    技术栈 前端用了React,后端则是EggJs,都用了TypeScript编写. 断点续传实现原理 断点续传就是在上传一个文件的时候可以暂停掉上传中的文件,然后恢复上传时不需要重新上传整个文件. 该功能实现流程是先把上传的文件进行切割,然后把切割之后的文件块发送到服务端,发送完毕之后通知服务端组合文件块. 其中暂停上传功能就是前端取消掉文件块的上传请求,恢复上传则是把未上传的文件块重新上传.需要前后端配合完成. 前端实现 前端主要分为:切割文件.获取文件MD5值.上传切割后的文件块.合并文件.暂

  • 利用React实现虚拟列表的示例代码

    目录 列表项高度固定 代码实现 列表项高度动态 代码实现 思路说明 一些需要注意的问题 结尾 大家好,我是前端西瓜哥.这次我们来看看虚拟列表是什么玩意,并用 React 来实现两种虚拟列表组件. 虚拟列表,其实就是将一个原本需要全部列表项的渲染的长列表,改为只渲染可视区域内的列表项,但滚动效果还是要和渲染所有列表项的长列表一样. 虚拟列表解决的长列表渲染大量节点导致的性能问题: 一次性渲染大量节点,会占用大量 GPU 资源,导致卡顿: 即使渲染好了,大量的节点也持续占用内存.列表项下的节点越多,

  • react实现Radio组件的示例代码

    本文旨在用最清楚的结构去实现一些组件的基本功能.希望和大家一起学习,共同进步 效果展示: 测试组件: class Test extends Component { constructor(props) { super(props) this.state = { active:1 } } onGroupChange(value) { this.setState({ active: value }) } render() { return ( <div> <RadioGroup onChan

  • 30行代码实现React双向绑定hook的示例代码

    目录 使用Proxy代理数据 使用useRef创建同一份数据引用 添加更新handler 去除多次Proxy 添加缓存完善代码 总结 Sandbox 示例 Vue和MobX中的数据可响应给我们留下了深刻的印象,在React函数组件中我们也可以依赖hooks来实现一个简易好用的useReactive. 看一下我们的目标 const CountDemo = () => { const reactive = useReactive({ count: 0, }); return ( <div onCl

  • React Native 集成jpush-react-native的示例代码

    jpush-React-native是极光推送官方维护的一个插件,github地址:https://github.com/jpush/jpush-react-native 一.手动配置 1.集成插件到项目中 npm install jpush-react-native --save rnpm link jpush-react-native Android 使用 android Studio import 你的 react Native 应用(选择你的 React Native 应用所在目录下的

  • React复制到剪贴板的示例代码

    本文介绍了React复制到剪贴板可以使用插件copy-to-clipboard,分享给大家,具体如下: 参考API文档 安装 npm install --save react react-copy-to-clipboard 使用 const App = React.createClass({ getInitialState() { return {value: '', copied: false}; }, onChange({target: {value}}) { this.setState({

  • 在react中使用vuex的示例代码

    前言 笔者最近在学习使用react,提到react就绕不过去redux.redux是一个状态管理架构,被广泛用于react项目中,但是redux并不是专为react而生,两者还需要react-redux建立一座桥梁.同时,redux架构规定只能发送同步action,要想发送异步action就需要结合中间件如redux-thunk.redux-saga等,所以说要想搞定redux还真是不容易啊,光名词就这么多.笔者以前也接触过一点vuex,vuex对笔者这样的菜鸡相对友好,但是vuex是和vue配

  • React 实现车牌键盘的示例代码

    vehicle-plate-keyboard React 实现的车牌键盘. https://github.com/LiuuY/vehicle-plate-keyboard

  • React Native悬浮按钮组件的示例代码

    React Native悬浮按钮组件:react-native-action-button,纯JS组件,支持安卓和IOS双平台,支持设置子按钮,支持自定义位置和样式和图标. 效果图 安装方法 npm i react-native-action-button --save react-native link react-native-vector-icons 因为用到了react-native-vector-icons图标组件,需要做下link.如果你项目中已经使用了react-native-ve

随机推荐

其他