You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 02:45:03