记录--实现一个鼠标框选的功能,要怎么实现和设计 api?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
前言
前两年在一家做电商的公司做了一个需求:鼠标框选商品卡片,开始拖拽的时候合成一个然后改变位置,页面上有几千个所以还要结合虚拟列表。当时不知道怎么做,就在 github 上到处找现成的库,最后找到了 react-selectable-fast,并结合 react-virtualized、react-sortable-hoc 完成了需求。虽然该库已经很久不维护了,但大致上能满足我的需求了,尽管它是以 dom 的方式,很不 react,但秉承着能用就行
的原则。意料之中,开发过程中遇到了 bug,最后只能 fork 一份修改源码后自己发了个 npm 包来使用。
项目介绍
前几个月在空闲时间突然来了兴致,自己找点事做,就想自己开发一个框选的库吧,万一也有人有这个需求不知道怎么办呢?写完后发到了 antd 的社区共建群里,有的人觉得不错也 star 了。先献上项目地址 react-selectable-box,文档完整,使用 dumi 编写。api 友好,支持自定义一些功能。
api 设计
一个组件在设计时,首先思考的应该是 api 如何去设计,最好符合大家平常的习惯,并具有一定的自定义和拓展能力。再加上了解 react-selectable-fast
这个库的缺点和痛点,对我的设计就更加有帮助了。大家在看下面的文章之前也可以思考一下,如果是你,你会怎么设计?这里只选取几个 api 来进行介绍。
主组件 Selectable
选中的值
defaultValue
和 value
,类型为 any[]
,每个方块一般都有一个唯一 id 来标识,2024-1-31 更新后
后支持任意类型,因为考虑到很多情况你可能需要一个对象或数组来标识,文章后面提供了 compareFn
来自定义比较值相等。
禁用
disabled
,大部分有值的组件应该都会有此属性,能直接禁用框选功能。
模式
mode
,类型为 "add" | "remove" | "reverse"
。模式,表明当前框选是增加、减少还是取反。这个 api 感觉是设计的最好的,用户会框选来选择目标,肯定也会需要删除已经框选的目标,可能是按住 shift 来删除等等之类的操作。用户可以自己编写自定义逻辑来修改 mode
的值来控制不同的行为,反观 react-selectable-fast
,则是提供了 deselectOnEsc
、allowAltClick
、allowCtrlClick
、allowMetaClick
、allowShiftClick
等多个 api。
开始框选的条件
selectStartRange
,类型 "all" | "inside" | "outside"
,鼠标从方块内部还是外部可以开始框选,或都可以。
可以进行框选的容器
dragContainer
,类型 () => HTMLElement
,例如你只希望某个卡片内才可以进行框选,不希望整个页面都可以进行框选,这个 api 就会起到作用了。
滚动的容器
scrollContainer
,类型 () => HTMLElement
,如果你的这些方块是在某个容器中并且可滚动,就需要传入这个属性,就可以在滚动的容器中进行框选操作了。
框选矩形的 style 与 className
boxStyle
和 boxClassName
,使用者可以自定义颜色等一些样式。
自定义 value 比较函数
compareFn
,类型 (a: any, b: any) => boolean,默认使用 === 进行比较(因为 value 支持任意类型,比如你使用了对象或数组类型,所以你可能需要自定义比较)
框选开始事件
onStart
,框选开始时,使用者可能需要做一些事情。
框选结束事件
onEnd
,类型为 (selectingValue: (string | number)[], { added: (string | number)[], removed: (string | number)[] }) => void
。selectingValue
为本次框选的值,added
为本次增加的值,removed
为本次删除的值。例如你想在每次框选后覆盖之前的操作,直接设置 selectingValue
成 value 即可。如果你想每次框选都是累加,加上 added
的值即可,这里就不再说明了。
方块可选 - useSelectable
怎么让方块可以被选择呢?并且一一绑定上对应的值?react-selectable-fast
则是提供 clickableClassName
api,传入可以被选择的目标的 class,这种方式太不 react 了。此时我的脑海里想到了 dnd-kit,我认为是 react 最好用的拖拽库,它是怎么让每个方块可以被拖拽的呢?优秀的东西应该借鉴,于是就有了 useSelectable
。
const {
setNodeRef, // 设置可框选元素
isSelected, // 是否已经选中
isAdding, // 当前是否正在添加
isRemoving, // 当前是否正在删除
isSelecting, // 当前是否被框选
isDragging // 是否正在进行框选操作
} = useSelectable({
value, // 每个元素的唯一值,支持任意类型
disabled, // 这个元素是否禁用
rule, // "collision" | "inclusion" | Function,碰撞规则,碰到就算选中还是全部包裹住才算选中,也可以自定义
});
如何使用?
const Item = ({ value }: { value: number }) => {
const { setNodeRef, isSelected, isAdding } = useSelectable({
value,
}); return (
<div
ref={setNodeRef}
style={{
width: 50,
height: 50,
borderRadius: 4,
border: isAdding ? '1px solid #1677ff' : undefined,
background: isSelected ? '#1677ff' : '#ccc',
}}
/>
);
};
实现
这里只简单讲一下思路,有兴趣的同学可以直接前往源码进行阅读。
主组件 Selectable
相当于一个 context,一些状态在这里进行保存,并掌管每个 useSelectable
,将其需要的值通过 context 传递过去。
在设置的可被框选的容器内监听鼠标 mousedown
事件,记录其坐标,根据 mousemove
画出框选矩形,再根据 setNodeRef
收集的元素和框选矩形根据碰撞检测函数计算出是否被框选了,并将值更新到 Selectable
中去,最后在 mouseup
时触发 onEnd
,将值处理完之后并丢出去。
演示
这里演示一下文章开头所说的框选拖拽功能,配合 dnd-kit
实现,代码在文档的 example 中。
遇到的坑
这里分享一下遇到的坑的其中之一:框选的过程中会选中文字,很影响体验,怎么让这些文字不能被框选呢?
方案1: 用 user-select: none
来控制文本不可被选中,但是这是在用户侧来做,比较麻烦。并且发现在 chrome
下设置此属性后,拖拽框选到浏览器边缘或容器边缘后不会自动滚动,其它浏览器则正常
方案2: 在 mousedown
时设置 e.preventDefault()
,这样选中时文字就不会被选中,但是拖拽框选到浏览器边缘或容器边缘后不会自动滚动,只能自己实现了滚动逻辑。后面又发现在移动端的 touchstart
设置时,会发现页面上的点击事件都失效了,查资料发现没法解决,只能另辟蹊径。
方案3: 在 mousemove
和 touchmove
时设置 e.preventDefault()
也是可以的,但也需要自己实现滚动逻辑。
最终也是采取了方案3。
后续目标
目前只能进行矩形的碰撞检测,不支持圆形(2024.1.26 更新支持自定义已经可以实现)及一些不规则图形(2024.1.26 更新提供自定义碰撞检测(dom 下太难,canvas 比较好做碰撞检测),剩下的就是使用者的事了!)。这是一个难点,如果有想法的可以在评论区提出或者 pr 也可。
2024-1-24 更新
添加 cancel
方法,试一试。可以调用 ref.current?.cancel()
方法取消操作。这样可以自定义按下某个键来取消当前操作。有想需不需要添加一个属性传入 keyCode 数组内置取消,但是感觉会使 api 太多而臃肿,也欢迎留下你的想法。
2024-1-26 更新一
添加 items
api 以优化虚拟滚动滚动时框选范围增加或减小时,已经卸载的 Item 的判断框选。(可选)试一试
优化前:滚动到下面时,加大框选面积,上面已经被卸载的不会被选中
2024-1-26 更新二
支持自定义碰撞规则检测,试一试自定义圆形碰撞检测
2024-1-31 更新
value 支持任意类型 any
,不再只是 string | number
类型,因为很多情况需要是一个对象或数组来当唯一标识,并提供了 compareFn
来支持自定义值的比较,默认使用 ===
,如果你的 value 是对象或数组,需要此属性来比较值。
本文转载于:
https://juejin.cn/post/7326979670485123110
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
记录--实现一个鼠标框选的功能,要怎么实现和设计 api?的更多相关文章
- ListView鼠标框选实现蓝色蒙板
此问题留心已久,今日方悉心求之,记录心得. ListView控件,不论Delphi中的TListView还是c#中的ListView,在开启其MultiSelect属性时,鼠标框选只是显示框张,如下图 ...
- 原生js实现在表格用鼠标框选并有反选功能
今天应同学要求,需要写一个像Excel那样框选高亮,并且实现框选区域实现反选功能.要我用原生js写,由于没什么经验翻阅了很多资料,第一次写文章希望各位指出不足!! 上来先建表 <div clas ...
- Javascript实现鼠标框选元素后拖拽被框选的元素
之前需要做一个框选元素后拖拽被框选中的元素功能,在网上找资料做了一些修改,基本达到了需要的效果,希望对也需要实现框选后拖拽元素功能的人有用. 页面加载后效果 框选后的内容可以拖拽,如下图: 代码下载
- vue封装一个简单的div框选时间的组件
记录一下我前段时间封装的一个vue组件吧.技术需要积累,有时间我把我之前写的还不错的组件都开源出来.并尝试vue和react 两种方式的组件封装.今天简单写下鼠标框选div选中效果的封装吧. div框 ...
- unity实现框选效果
思路: 在uinity中既可以将屏幕坐标转换为世界坐标,也可以将世界坐标转换为屏幕坐标.这样的话我们就可以通过判断物体在世界坐标转换为平幕坐标是否在鼠标框选的矩形区域坐标内,来判断物体是否在框选范围. ...
- WPF Grid新增框选功能
有时候会有框选的需求,类似EXCEL一样,画一个框选择里面的子控件. 选择后比如可以将子控件的Border设置为红色边框 说下这个功能的大致原理.背景是一个Grid,比如里面放了很多的Button. ...
- QT 创建一个具有复选功能的下拉列表控件
最近研究了好多东西,前两天突然想做一个具有复选功能的下拉列表框.然后在网上"学习"了很久之后,终于发现了一个可以用的,特地发出来记录一下. 一.第一步肯定是先创建一个PROJECT ...
- js实现鼠标拖动框选元素小狗
方法一: <html> <head></head> <style> body{padding:100px;} .fileDiv{float:left;w ...
- Visual Studio的框选代码区块功能
要从Visual Studio里复制代码粘贴到其他地方,会因为对齐的问题,造成粘贴的时候,代码左边带有大量的空格. 而VS有一个很好的功能就是框选功能,使用方法是,将光标放置在要框选代码的最左边,然后 ...
- js做全选,用一个checkbox复选框做多个checkbox复选框的全选按钮,有一个复选框未被选择时,全选按钮的checked就为false
用一个checkbox复选框做多个checkbox复选框的全选按钮,有一个复选框未被选择时,全选按钮的checked就为false,当所有checkbox都被选中时,全选按钮也被选中. 详解: 有两种 ...
随机推荐
- 多个request接口的功能优化处理速度
一.原始代码功能如下,包含两个request接口的调用,耗时情况约4秒 import datetime import time import requests start_time = datetim ...
- NC20325 [SDOI2009]HH的项链
题目链接 题目 题目描述 HH有一串由各种漂亮的贝壳组成的项链. HH相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一 段贝壳,思考它们所表达的含义. HH不断地收集新的贝壳,因此他的项链 ...
- Python 装饰器解析(二)
前面一篇文章介绍了python装饰器,最后引入了functools.wraps的使用,本篇文章将对它进行深入的探究. functools模块提供了一系列的高阶函数以及对可调用对象的操作,其中为人熟知的 ...
- OpenWrt 21.02.2 记录: PPPoE, Wireguard
OpenWrt 21.02 OpenWrt 21.02.2 是 OpenWrt 当前最新的稳定版, 内核 5.4.179, 这个内核已经内置了 Wireguard 模块 root@OpenWrt:~# ...
- Js文件异步加载
Js文件异步加载 浏览器中渲染引擎与Js脚本引擎是互斥的,在浏览器开始渲染页面时,如果遇到<script>标签,会停止渲染当前页面,也就是说在脚本加载与执行的过程中会阻塞页面的渲染,在网速 ...
- SPA单页应用的优缺点
SPA单页应用的优缺点 Single Page Web Application是一种特殊的Web应用,其所有的活动局限于一个Web页面中,仅在该Web页面初始化时加载相应的HTML.JavaScrip ...
- vmware之NAT模式配置
题外话之前的题外话,本文迁移自别的社区,三年前大学实习时写下本文,过了几年再回过头来看,虽然讲得浅显,作为入门笔记也勉强合格. ---------------------------------- ...
- Redis分布式锁的正确使用姿势
前言 分布式锁在日常开发中,用处非常的多.包括但不限于抢红包,秒杀,支付下单,幂等,等等场景. 分布式锁的实现方式有多种,包括redis实现,mysql实现,zookeeper实现等等.而其中redi ...
- 编译 windows 上的 qt 静态库
记录命令行编译过程: 针对 Qt 5.15.2 版本, 只需要 Source 文件就行 打开 x86 Native Tools Command Prompt for VS 2019,如果需要编译 x6 ...
- Go微服务框架go-kratos实战学习08:负载均衡基本使用
微服务框架 go-kratos 中负载均衡使用 一.介绍 在前面这篇文章 负载均衡和它的算法介绍,讲了什么是负载均衡以及作用.算法介绍. go-kratos 的负载均衡主要接口是 Selector,它 ...