marquee-selection
v0.0.13
Published
A TypeScript library for creating a marquee selection effect
Downloads
31
Readme
groupjs · DOM 矩形圈选与编组
一个轻量的浏览器侧 DOM 圈选库:支持鼠标拖拽矩形圈选、精度模式(相交/包含/中心点 + 面积阈值/IoU)、多次圈选合并、编组与覆盖层、组框四边/四角拖动实时重算、双击快速成组、组间合并、悬浮高亮,以及结果快照与事件回调。内置组级嵌套关系快照,便于构建“组嵌套查看”之类的 UI。
特性
- 容器内拖拽矩形圈选(fixed 覆盖层,不受容器 overflow 影响)
- 精度策略:intersects/contains/center + minOverlapRatio + overlapMetric(element/IoU)
- 冲突处理:none/leaf/best(避免误选大容器)
- 多次圈选:replace/add/subtract/toggle/auto(Shift/Alt/Ctrl/⌘)
- 编组模式:每次拖拽生成一组;组覆盖层可见/隐藏、删除
- 组覆盖层:四边+四角拖动,拖动过程中实时重算命中
- 组合并:新建组与其它组相交时,工具条“合并”并选择目标组合并
- 双击快速成组:以双击元素外框作为选择框直接生成一组
- 悬浮高亮:hoverHighlight+hoverClass
- 选择约束:allowIntersectionSelection / allowContainmentSelection / allowUnionSelection 控制是否允许“与已有选择部分交叉”“包含/被包含(父子)”与“新增并集”;当不允许时,拖拽/双击/覆盖层尺寸调整都会以红框提示并取消本次操作
- 完整快照:单选/编组含 flat 并集、hidden、groupRects、groupNesting(组级嵌套)
- 事件:onChange、onSelectionEnd;控制器:onSelectionEnd(订阅)与 waitForSelectionEnd(一次性)
安装
可直接拷贝 src/index.ts 使用,或自行打包。
位置注入与 position.js 用法
本项目支持为 HTML 元素自动注入源码位置信息(如行号、列号、起止 offset),便于调试、可视化、AST 映射等高级用例。
1. 生成位置信息注入脚本
运行以下命令,会自动解析 index.html,为每个 DOM 元素生成唯一选择器及其源码位置信息,并输出注入脚本 src/position-injector.js:
npm run position
开发/打包:
npm i
npm run dev # 本地预览 demo(Vite)
npm run build # 产出 dist(es/umd/types)快速上手(浏览器)
import enableMarqueeSelection from "/src/index.ts";
const ctrl = enableMarqueeSelection({
container: document.getElementById("container")!,
selectable: "*",
selectionMode: "intersects",
minOverlapRatio: 0.2,
overlapMetric: "element",
conflictStrategy: "best",
multi: true,
combineMode: "auto",
groupMode: true,
groupOverlayClass: undefined,
hoverHighlight: true,
hoverClass: "hovered",
quickGroupOnDblClick: true,
// 结果回调(一次完整拖拽/操作结束)
onSelectionEnd: (snapshot) => {
console.log("snapshot", snapshot);
},
});API
enableMarqueeSelection(options) => controller
options(主要项):
- container: HTMLElement 必填,圈选容器
- selectable?: string 默认 'img',可被圈选的选择器
- selectionMode?: 'intersects' | 'contains' | 'center' 默认 'intersects'
- minOverlapRatio?: number 相交模式下的最小重叠阈值(0~1)
- overlapMetric?: 'element' | 'iou' 重叠指标
- selectedClass?: string 默认 'selected'
- onChange?: (payload) => void 实时变更(单选/编组)
- preventAncestorSelection?: boolean 默认 true,避免误选父容器
- conflictStrategy?: 'none' | 'leaf' | 'best' 父子冲突处理
- multi?: boolean 多次圈选
- combineMode?: 'replace' | 'add' | 'subtract' | 'toggle' | 'auto'
- groupMode?: boolean 开启编组
- groupOverlayClass?: string 组覆盖层类名(可自定义样式)
- hoverHighlight?: boolean 悬浮高亮
- hoverClass?: string 悬浮高亮类
- onSelectionEnd?: (snapshot) => void 一次交互结束
- quickGroupOnDblClick?: boolean 双击快速成组
- quickGroupSelector?: string 双击匹配祖先选择器后再成组
- allowIntersectionSelection?: boolean 默认 true。若为 false,则只禁止“部分交叉”关系:即新集合与已有集合间存在交集且双方都还有各自独有的元素。
- allowContainmentSelection?: boolean 默认 true。仅当 allowIntersectionSelection=false 时生效;true 表示仍允许“包含/被包含”关系(A 是 B 的子集或父集),false 表示包含也禁止,仅保留完全不相交或完全相等两种情况。
- allowUnionSelection?: boolean 默认 true。若为 false,则当已存在选择/组时,禁止新增元素(并集)。
- toolbarButtons?: { label; title?; className?; onClick?(ctx) }[] 追加自定义工具栏按钮(ctx.label/ctx.title 为可写属性,可在同步或异步回调中赋值以实时更新按钮展示)
controller:
- destroy(): void 卸载
- getGroups(): Element[][] 获取所有组
- clearGroups(): void 清空所有组
- removeGroup(index: number): boolean 删除指定组
- setGroupVisibility(index: number, hidden: boolean): boolean 设置组可见
- toggleGroupVisibility(index: number): boolean 切换可见
- getSelectionResult(): MarqueeSelectionSnapshot 获取当前快照
- setToolbarButtonProps(groupIndex: number, buttonIndex: number, props: { label?: string; title?: string }): boolean 直接修改指定组的自定义工具栏按钮文案(label/title 任意可选)
- onSelectionEnd(handler): () => void 订阅下一次结束
- waitForSelectionEnd(): Promise 等待下一次结束
- getGroupNesting(): { parents: (number|null)[]; children: number[][]; roots: number[] } | null 获取组级嵌套(groupMode 下有效)
快照结构 MarqueeSelectionSnapshot
- 单选:
{ type: 'single'; selected: Element[] } - 编组:
{ type: 'groups' groups: Element[][] // 每组元素集 flat: Element[] // 全部并集 hidden: boolean[] // 各组隐藏状态 groupRects: ({ left:number; top:number; width:number; height:number } | null)[] // 各组外接框 groupNesting: { parents: (number|null)[] children: number[][] roots: number[] } }
groupNesting 说明:依据组外接矩形完全包含关系,给每个组选取“面积最小的包含者”作为其最近父组,从而形成多层嵌套树(roots 为无父组)。这使得你能在 UI 中构建“组嵌套查看”。
高级能力
组覆盖层拖动:
- 覆盖层四边/四角(nw/ne/sw/se)句柄拖动,实时更新圈选命中与 DOM 选中态;
- 鼠标松开后固定结果,并触发 onSelectionEnd。
- 当禁用交叉/包含/并集时(allowIntersectionSelection / allowContainmentSelection / allowUnionSelection),若本次调整会产生被禁止的部分交叉、包含或新增元素,将以红色边框与浅红底提示,并在松开时取消本次调整。
组合并:
- 当新建组与其他组外接框相交时,工具条显示“合并”;
- 点击弹出合并面板,勾选要合并的组后确认;
- 被合并组删除,当前组元素替换为并集,自动刷新覆盖层与快照。
双击快速成组:
- 开启 quickGroupOnDblClick 后,双击元素(或其 closest(quickGroupSelector) 祖先)会以该元素外框作为选择框迅速生成一组。
- 亦会遵守 allowIntersectionSelection / allowContainmentSelection / allowUnionSelection;违规则提示并取消。
自定义工具栏按钮:
- 通过 options.toolbarButtons 追加按钮到每个组的工具栏;
- onClick(ctx) 回调提供:
- ctx.index 当前组索引
- ctx.group 当前组元素数组
- ctx.controller 控制器实例
- ctx.getSnapshot() 获取最新快照
- ctx.refresh() 触发重绘覆盖层并广播 onSelectionEnd
- ctx.label / ctx.title 支持在回调中赋值(含异步场景),立即刷新按钮文本与 title;ctx.buttonEl 可用于附加自定义 DOM
- 若需在回调外(例如异步完成后、或来自其它交互)修改按钮文案,可调用
controller.setToolbarButtonProps(groupIndex, buttonIndex, { label?: string, title?: string }),该方法会持久更新对应组的按钮状态并刷新 UI。 - 示例:
enableMarqueeSelection({ // ...existing options... toolbarButtons: [ { label: "导出", title: "导出该组元素信息", onClick: ({ index, group, getSnapshot }) => { const ids = group.map((el) => (el as HTMLElement).id).join(","); console.log("组", index + 1, "元素ID:", ids); }, }, { label: "反选本组", onClick: ({ index, controller, getSnapshot, refresh }) => { const snap = getSnapshot(); if (snap.type !== "groups") return; // 简单示例:把该组设置为隐藏/显示切换 controller.toggleGroupVisibility(index); refresh(); }, }, ], });悬浮高亮:
- hoverHighlight + hoverClass,非拖拽时鼠标悬浮可提示可圈选元素。
Demo
- 项目内置
index.html演示:右上角浮层面板展示结果; - 面板支持“平铺查看 / 组嵌套查看”切换;嵌套查看直接使用库返回的
snapshot.groupNesting。
开发
npm run dev # 起 Vite 开发服务
npm run build # 产出 dist(es/umd/types)License
MIT
