QQ 风格分组好友列表
一个两级有序结构:分组 (group) 有序、分组内好友也有序。重排类交互——分组重排与好友跨分组移动——最终都归约成对 groups[] 与各分组 memberIds[] 的 splice;自动滚动、悬停展开、重命名则各自只改一个标量字段。拖拽由 Pointer Events 自绘。
1 · 交互与数据模型
注 · 好友本体存全局 friendsById,分组只持有 memberIds 引用。移动好友是先把该 id 从所有分组的 memberIds 里摘掉、再插进目标分组,数据本体不搬动。分组头上的计数从 memberIds 实时算,其中「在线」的判据是
status !== 'offline'——离开态计入在线,与头像圆点的三态着色口径并不相同。
警示 · 两处实现边界。其一,moveFriend 先摘除再查找目标分组,因此传入不存在的 toGid 时该好友会从所有分组里消失(本页 lab 的目标分组只来自现有列表,撞不到)。其二,拖拽路径没有声明 touch-action、也没有处理
pointercancel,而列表本身是竖向滚动容器——触摸端的竖向拖拽会与滚动手势相争,此处未做移动端适配。
2 · 命中判定的两套策略
好友级落点用 document.elementFromPoint 按坐标命中,分组级落点则遍历各分组头的 getBoundingClientRect 与中线比较——两级用了不同的命中策略,前者省事,后者在分组折叠、行高不一时更稳。拖拽期间的指针事件挂在 window 上,未用 setPointerCapture,两条路线等效。
相关链接
- Pointer events developer.mozilla.org 统一鼠标 / 触摸 / 触控笔的指针事件模型,自绘拖拽的基础。
- Element.setPointerCapture() developer.mozilla.org 把后续指针事件捕获到指定元素;本页改用 window 级监听达到同样效果, 指针捕获是另一条路线。
- Array.prototype.splice() developer.mozilla.org 原地删除 / 插入元素:本页两级重排的唯一底层操作。
- Document.elementFromPoint() developer.mozilla.org 按坐标命中元素;本页只有好友级落点用它, 分组级落点走 getBoundingClientRect 比中线。