RxJS实现鼠标拖拽时标记首次与末次事件的方案咨询
实现思路
- 标记首次拖拽:利用
map操作符的第二个索引参数,判断当前是不是第一个mousemove事件,设置isFirst: index === 0 - 标记末次拖拽:不用等待拖拽流程结束再发值,而是在
mouseUp触发时,基于最后一次mousemove的状态生成一个isLast: true的事件,拼接到mousemove事件流的末尾 - 避免组合流的等待问题:用
concat拼接两个流,保证事件按「首次拖拽→中间拖拽→末次拖拽」的顺序发射
修改后的完整代码
import { fromEvent, concat } from 'rxjs'; import { switchMap, map, takeUntil, withLatestFrom } from 'rxjs/operators'; function createDraggable$(element) { const mouseDown$ = fromEvent(element, 'mousedown'); const mouseMove$ = fromEvent(window, 'mousemove'); const mouseUp$ = fromEvent(window, 'mouseup'); return mouseDown$.pipe( switchMap((start) => { const { offsetLeft: initialLeft, offsetTop: initialTop, } = element; // 处理mousemove流,生成普通拖拽事件 const dragMove$ = mouseMove$.pipe( // 第二个参数是当前事件在流中的索引,从0开始 map((move, index) => { const deltaX = move.pageX - start.pageX; const deltaY = move.pageY - start.pageY; return { isFirst: index === 0, // 第一个mousemove就是首次拖拽 isLast: false, start, move, deltaX, deltaY, initialLeft, initialTop, left: initialLeft + deltaX, top: initialTop + deltaY, }; }), takeUntil(mouseUp$) ); // 处理mouseUp流,生成末次拖拽事件 const dragEnd$ = mouseUp$.pipe( // 拿到最后一次拖拽的状态 withLatestFrom(dragMove$, (_, lastDrag) => ({ ...lastDrag, isFirst: false, isLast: true // 标记为末次拖拽 })) ); // 拼接两个流,先发射所有拖拽中事件,最后发射结束事件 return concat(dragMove$, dragEnd$); }), ); } // 订阅逻辑和原有写法完全兼容,不需要修改 createDraggable$(el).subscribe((drag) => { if (drag.isFirst) { dragstart.emit(); } else if (drag.isLast) { dragend.emit(); } else { drag.emit(); } if (isHorizontal) { el.style.top = `${drag.top}px`; } else { el.style.left = `${drag.left}px`; } });
关键逻辑说明
concat会严格按顺序订阅流,只有前一个流结束后才会订阅后一个流:dragMove$在mouseUp触发时就会因为takeUntil结束,之后dragEnd$就会触发,刚好发射结束事件withLatestFrom会始终保存dragMove$最后一次发射的值,所以mouseUp触发时能拿到最新的拖拽位置,不会出现坐标丢失的问题- 不需要修改原来的订阅逻辑,完全兼容原有事件分发写法
内容的提问来源于stack exchange,提问作者avioli
相关产品推荐
相关产品推荐

