maple-leaf/blog

Rx.js使用第二弹之拖拽与自动滚动

maple-leaf opened this issue · 0 comments

首先上demo地址:

Rx.js - DnD & autoScroll by tjf (@maple-leaf) on CodePen.

demo演示gif:
untitled

本次要实现的功能点有两个:

  1. 基本的拖拽
  2. 拖拽到顶部或底部的时候触发自动滚动

码代码开始--->>>

Html结构:

<div id="scrollTopTip">drag to here to auto scroll</div>
<div id="wrapper">
  <div id="handler">drag me</div>
  <div id="content"></div>
</div>
<div id="scrollBottomTip">drag to here to auto scroll</div>

Js初始化部分:

const wrapper = document.querySelector('#wrapper');
const handler = document.querySelector('#handler');

const initialStyleOfHandler = {
  left: 0,
  top: 0
};
const styleOfHandler = {
  left: 0,
  top: 0
};

const mousedown$ = Rx.Observable.fromEvent(handler, 'mousedown');
const mousemove$ = Rx.Observable.fromEvent(document, 'mousemove');
const mouseup$ = Rx.Observable.fromEvent(document, 'mouseup');

上面做好了拖拽目标的样式变量初始化,鼠标相关事件转化为Rx的流。

实现基本的拖拽 (删掉了自动滚动的代码)

mousedown$
.switchMap(mousedownEvt => {
  const startPoint = {
    x: mousedownEvt.pageX,
    y: mousedownEvt.pageY
  };
 
  const drag$ = mousemove$.throttleTime(10).takeUntil(mouseup$);  // 定义拖拽流为截流10ms鼠标移动的流,在鼠标释放时停止

  return drag$
  .map(mouseMoveEvt => {
    const diff = {
      x: mouseMoveEvt.pageX - startPoint.x,
      y: mouseMoveEvt.pageY - startPoint.y
    };  // 计算与初始位置的位移

    return diff;
  });
})
.subscribe(diff => {
  // 更新样式
  styleOfHandler.left = initialStyleOfHandler.left + diff.x;
  styleOfHandler.top = initialStyleOfHandler.top + diff.y;
  handler.style.left = `${styleOfHandler.left}px`;
  handler.style.top = `${styleOfHandler.top}px`;
});

mouseup$
.subscribe(() => {
  Object.assign(initialStyleOfHandler, styleOfHandler);  // 鼠标释放后将当前的样式作为下次的初始值
});

实现自动滚动功能 (注意注释部分)

const wrapper = document.querySelector('#wrapper');
const handler = document.querySelector('#handler');

const initialStyleOfHandler = {
  left: 0,
  top: 0
};
const styleOfHandler = {
  left: 0,
  top: 0
};

const mousedown$ = Rx.Observable.fromEvent(handler, 'mousedown');
const mousemove$ = Rx.Observable.fromEvent(document, 'mousemove');
const mouseup$ = Rx.Observable.fromEvent(document, 'mouseup');

mousedown$
.switchMap(mousedownEvt => {
  const startPoint = {
    x: mousedownEvt.pageX,
    y: mousedownEvt.pageY
  };
  const maxScrollTop = wrapper.scrollHeight - wrapper.clientHeight;
  const drag$ = mousemove$.throttleTime(10).takeUntil(mouseup$);
  
  const autoScroll$ = new Rx.Subject()
  .switchMap(({ action, mousemoveEvt }) => {

   // 定义自动滚动流,当流中有值时,根据`action`进行流转换
   // stop: 不做任何事情,直接转换为只发送一次`mousemove`事件的流
  // scrollTop 或 scrollBottom: 每10ms更新`wrapper`的scrollTop值,实现向上或向下自动滚动,然后将`interval`这个流的值转为`mousemove`事件值

    if (action === 'stop') return Rx.Observable.of(mousemoveEvt);
    const scrollDiff = 10;
    return Rx.Observable.interval(10)  // 定义间隔10ms的无限流
      .takeUntil(mouseup$)  // 无限流在鼠标释放时停止
      .do(() => {
        // 自动滚动。
        // 更新scrollTop, 由于这个是副作用,所以放在`do`内 (Rx.js 中的do是在流中会产生副作用使用)
        // 这里将自动滚动定位为副作用是因为这个流最终是为了向后面的拖拽流发送`mousemove`事件的,
        // 使`handler`的位置跟随滚动
        if (action === 'scrollTop') {
          wrapper.scrollTop = Math.max(wrapper.scrollTop - scrollDiff, 0);
        } else {
          wrapper.scrollTop = Math.min(wrapper.scrollTop + scrollDiff, maxScrollTop);
        }
      })
      .map(() => {
        return mousemoveEvt;
      })
  });
  
  drag$
  .subscribe(mousemoveEvt => {
    // 监听拖拽流,每有一次值就检察`handler`的位置和外框顶部、底部的关系,向`autoScroll$`值发送指令和`mousemove`事件
    const rectOfHandler = handler.getBoundingClientRect();
    if (rectOfHandler.top < 10) {
      autoScroll$.next({
        action: 'scrollTop',
        mousemoveEvt
      });
    } else if (rectOfHandler.bottom - wrapper.clientHeight > -10) {
      autoScroll$.next({
        action: 'scrollBottom',
        mousemoveEvt
      });
    } else {
      autoScroll$.next({
        action: 'stop',
        mousemoveEvt
      });
    }
  });

  return drag$
  .merge(autoScroll$)  // 当自动滚动流发送值时也更新`handler`的位置,使其跟随滚动
  .map(mouseMoveEvt => {
    const diff = {
      x: mouseMoveEvt.pageX - startPoint.x,
      y: mouseMoveEvt.pageY - startPoint.y
    };

    return diff;
  });
})
.subscribe(diff => {
  styleOfHandler.left = initialStyleOfHandler.left + diff.x;
  styleOfHandler.top = initialStyleOfHandler.top + diff.y + wrapper.scrollTop;  // 计算Y方向时加上滚动值
  handler.style.left = `${styleOfHandler.left}px`;
  handler.style.top = `${styleOfHandler.top}px`;
});

mouseup$
.subscribe(() => {
  Object.assign(initialStyleOfHandler, styleOfHandler);
});