RxJS:锁定水平或垂直鼠标拖动

时间:2015-06-29 15:36:59

标签: javascript mouseevent reactive-programming rxjs

我想构建一个界面,您可以使用在一定距离后选择的模式向各个方向拖动。例如,如果您水平拖动25px,它会锁定到该模式并保持不变直到您释放鼠标。如果你垂直拖动它会做同样的事情。如果您单击或按住很长时间,可能会发生其他操作。

以下简要说明了我的目标:https://jsfiddle.net/ud37p0y2/2/

似乎反应性编程对此非常完美,但我似乎无法弄清楚如何启动这些模式然后坚持它们直到你释放鼠标。我的出发点是许多拖放示例,但我似乎无法进一步考虑......

一些代码(TypeScript):

var mouseDown = Rx.Observable.fromEvent($element[0], 'mousedown').select((event: MouseEvent): IPoint => {
    event.preventDefault();
    return { x: event.clientX, y: event.clientY };
});
var mouseUp   = Rx.Observable.fromEvent($element[0], 'mouseup');
var mouseMove = Rx.Observable.fromEvent($element[0], 'mousemove');
var mouseDrag = mouseDown.selectMany((mouseDownPos: IPoint) => {
    return mouseMove.select((event: MouseEvent) => {
        return {
            x: event.clientX - mouseDownPos.x,
            y: event.clientY - mouseDownPos.y
        };
    }).takeUntil(mouseUp);
});

var horizontalDrag = mouseDrag.filter((pos: IPoint) => {
    return pos.x < -25 || pos.x > 25;
});
// How would i continue from here?

horizontalDrag.subscribe((pos: IPoint) => {
    console.log('drag'); // This fires all the time, i'd like to do it once when the mode starts and then something else to be called every time the mouse has moved
});

从这里开始,我想获得水平拖动,垂直拖动和保持事件的可观察性。模式启动后,应该禁用其他模式,例如拖动不会触发长按事件。

1 个答案:

答案 0 :(得分:1)

我会使用amb + skipWhile的组合。

  • amb将为您提供锁定状态的行为
  • skipWhile将阻止事件触发,直到它们超过阈值 一段时间。

核心逻辑看起来像这样:

//Waits for either X or Y to emit then only propagates that one
return Rx.Observable.amb(
    mouseMove
    .pluck('clientX')
    //Wait until the threshold is reached
    .skipWhile(function (x) {
        return Math.abs(startAt.clientX - x) < 25;
    })
    //Transform the outgoing event
    .map(function (x) {
        return {
            prop: 'clientX',
            delta: x - startAt.clientX
        };
    }),

    mouseMove
    .pluck('clientY')
    .skipWhile(function (y) {
        return Math.abs(startAt.clientY - y) < 25;
    })
    .map(function (y) {
        return {
            prop: 'clientY',
            delta: y - startAt.clientY
        };
    }),
    //If neither propagates for a second, then subscribe to this instead
    mouseMove
    .startWith(startAt)
    .delaySubscription(1000)
    .tap(function (e) {
         box.className = 'press';
         prop = 'timeStamp';
         box.innerHTML = '';
    })
    .map(function (e) {
         return {
            prop: 'timeStamp',
            delta: e.timeStamp - startAt.timeStamp
         };
    }))
    .takeUntil(mouseUp);

编辑1

将延续Observable移至timeout并使用amb代替delaySubscription

以下是代码的完全修改版本:

var box = document.getElementById('box');

var mouseDown = Rx.Observable.fromEvent(box, 'mousedown');

var mouseUp = Rx.Observable.fromEvent(document.body, 'mouseup');

var mouseMove = Rx.Observable.fromEvent(box, 'mousemove')
.tap(function(e) { e.preventDefault(); });

mouseDown.flatMapLatest(function (start) {

    var startAt = start;
    
    box.className = 'waiting';
    box.innerHTML = 'waiting...';

    return Rx.Observable.amb(
        mouseMove
        .pluck('clientX')
        .skipWhile(function (x) {
            return Math.abs(startAt.clientX - x) < 25;
        })
        .map(function (x) {
            return {
                prop: 'clientX',
                delta: x - startAt.clientX
            };
        }),
    
        mouseMove
        .pluck('clientY')
        .skipWhile(function (y) {
            return Math.abs(startAt.clientY - y) < 25;
        })
        .map(function (y) {
            return {
                prop: 'clientY',
                delta: y - startAt.clientY
            };
        }),
        mouseMove
        .startWith(startAt)
        .delaySubscription(1000)
        .tap(function (e) {
          box.className = 'press';
          prop = 'timeStamp';
          box.innerHTML = '';
        }).map(function (e) {         
          return {
           prop: 'timeStamp',
           delta: e.timeStamp - startAt.timeStamp
          };
        }))
        .takeUntil(mouseUp);
})
.subscribe(function (x) {
    box.innerHTML = x.prop + ': ' + x.delta;
});


mouseUp.subscribe(function() {
    box.className = '';
    box.innerHTML = '';
});
body {
    font: 12px sans-serif;
}
#box {
    width: 300px;
    height: 300px;
    border: 1px #000 solid;
    text-align: center;
    padding: 20px;
    transition: 0.2s background-color;
    cursor: pointer;
}
#box.waiting {
    background-color: gray;
    cursor: move;
}
#box.dragX {
    background-color: red;
    cursor: ew-resize;
}
#box.dragY {
    background-color: green;
    cursor: ns-resize;
}
#box.press {
    background-color: yellow;
    cursor: progress;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.3/rx.all.js"></script>
<ol>
    <li>Drag horizontally</li>
    <li>Release</li>
    <li>Drag vertically</li>
    <li>Relase</li>
    <li>Press and hold</li>
</ol>
<div id="box"></div>