7

给定:响应式扩展拖放示例,您将如何仅订阅 drop 事件?

我已修改代码以订阅“已完成”回调,但未完成。

    (function (global) {

    function main () {
        var dragTarget = document.getElementById('dragTarget');
        var $dragTarget = $(dragTarget);

        // Get the three major events
        var mouseup  = Rx.Observable.fromEvent(document, 'mouseup');
        var mousemove = Rx.Observable.fromEvent(document,    'mousemove');
        var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown');

        var mousedrag = mousedown
            .filter(function(md){                   
                //console.log(md.offsetX + ", " + md.offsetY);
                return  md.offsetX <= 100
                        ||
                        md.offsetY <= 100;
            })
            .flatMap(function (md) {

                // calculate offsets when mouse down
                var startX = md.offsetX, startY = md.offsetY;

                // Calculate delta with mousemove until mouseup
                return mousemove.map(function (mm) {
                    mm.preventDefault();

                    return {
                        left: mm.clientX - startX,
                        top: mm.clientY - startY
                    };
                }).takeUntil(mouseup);
            });


        // Update position
        var subscription = mousedrag.subscribe(
        function (pos) {                    
            dragTarget.style.top = pos.top + 'px';
            dragTarget.style.left = pos.left + 'px';
        },
        function(errorToIgnore) {},
        function() {    alert('drop');});

    }

    main();

}(window));

我读过热的可观察对象,例如从鼠标事件创建的那些,永远不会“完成”。它是否正确?我怎样才能在“drop”时获得回调?

4

2 回答 2

6

像这样的东西应该可以解决问题。

(function (global) {

    function main () {
        var dragTarget = document.getElementById('dragTarget');

        // Get the three major events
        var mouseup = Rx.Observable.fromEvent(document, 'mouseup');
        var mousemove = Rx.Observable.fromEvent(document, 'mousemove');
        var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown');

        var drop = mousedown
                .selectMany(
                    Rx.Observable
                        .concat(
                            [
                                mousemove.take(1).ignoreElements(),
                                mouseup.take(1)
                            ]
                        )
                );
    }

    main();

}(window));

编辑:

如果您将 observable 视为一个异步函数,它产生多个值,然后可能完成或出错,您会立即认识到只能有一个完成事件。

当您开始组合多个函数时,最外层的函数仍然只完成一次,即使该函数内部包含多个函数。所以即使“完成”的总数是 3,最外层的函数仍然只完成一次。

基本上,这意味着如果假设每次拖动完成时最外层的函数都返回一个值,那么您需要一种实际执行此操作的方法。您需要将拖动完成转换为最外层可观察对象的“onNext”事件。

任何你能做到的方式都会得到你想要的。也许这是最外层函数返回的唯一类型的事件,或者它也可能返回拖动开始和移动,但只要它返回拖动完成,你就会得到你需要的(即使你必须稍后过滤)。

我上面给出的示例只是返回最外层可观察对象中的拖放的一种方法。

于 2014-06-13T22:21:52.993 回答
3

OP与官方示例的链接已关闭,它在这里:

https://github.com/Reactive-Extensions/RxJS/blob/master/examples/dragndrop/dragndrop.js

原始问题有两种解决方案,使用在官方示例上扩展的原始鼠标事件,或者使用原生 HTML5 拖放事件。

由“mouseup”、“mousedown”和“mousemove”组成

首先,我们用于mousedown1.switchMapTo(mousemove.takeUntil(mouseup).take(1))获取“拖动开始”流。switchMapTo参见文档)使用扁平化(将“mousedown1”映射到“鼠标移动直到鼠标向上”的第一个发射中的每一个,也称为“拖动”)和switch参见文档),为我们提供了最新的mousedown1发射,然后是拖动,即不仅仅是任何时候你在盒子上点击鼠标。

另一个“mousedown”流mousedown2用于组合mousedrag流,因此我们可以在拖动时不断地渲染框。 mousedown1 以上优先mousedown2。有关更多信息,请参阅https://stackoverflow.com/a/35964479/232288

一旦我们有了“拖动开始”流,我们就可以通过以下方式获得“拖动停止”流:mousedragstart.mergeMapTo(mouseup.take(1))mergeMapTo请参阅文档)将“mousedragstart”映射到“mouseup”的每个第一次发射,为我们提供最新的“mousedragstart”,然后是“mouseup”,这本质上是“拖动停止”。

下面的演示适用于 RxJS v5:

const { fromEvent } = Rx.Observable;
const target = document.querySelector('.box');
const events = document.querySelector('#events');

const mouseup = fromEvent(target, 'mouseup');
const mousemove = fromEvent(document, 'mousemove');
const [mousedown1, mousedown2] = prioritisingClone(fromEvent(target, 'mousedown'));

const mousedrag = mousedown2.mergeMap((e) => {

  const startX = e.clientX + window.scrollX;
  const startY = e.clientY + window.scrollY;
  const startLeft = parseInt(e.target.style.left, 10) || 0;
  const startTop = parseInt(e.target.style.top, 10) || 0;

  return mousemove.map((e2) => {
    e2.preventDefault();

    return {
      left: startLeft + e2.clientX - startX,
      top: startTop + e2.clientY - startY
    };
  }).takeUntil(mouseup);
});

// map the latest mouse down emit to the first mouse drag emit, i.e. emits after pressing down and
// then dragging.
const mousedragstart = mousedown1.switchMapTo(mousemove.takeUntil(mouseup).take(1));

// map the mouse drag start stream to first emit of a mouse up stream, i.e. emits after dragging and
// then releasing mouse button.
const mousedragstop = mousedragstart.mergeMapTo(mouseup.take(1));

mousedrag.subscribe((pos) => {
  target.style.top = pos.top + 'px';
  target.style.left = pos.left + 'px';
});

mousedragstart.subscribe(() => {
  console.log('Dragging started');
  events.innerText = 'Dragging started';
});

mousedragstop.subscribe(() => {
  console.log('Dragging stopped');
  events.innerText = 'Dragging stopped';
});

function prioritisingClone(stream$) {
  const first = new Rx.Subject();
  const second = stream$.do(x => first.next(x)).share();

  return [
    Rx.Observable.using(
      () => second.subscribe(() => {}),
      () => first
    ),
    second,
  ];
}
.box {
  position: relative;
  width: 150px;
  height: 150px;
  background: seagreen;
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>
<div class="box"></div>

<h3 id="events"></h3>

由 HTML5 原生 'dragstart'、'dragover' 和 'drop' 组成

本机事件使事情变得更容易一些。只需确保拖动观察者调用e.preventDefault(),以便 body 元素可以成为有效的放置区域。查看更多信息

我们以与上面使用方式类似的方式使用switchMap参见文档switchMapTo) :展平并将最新的“dragstart”映射到最新的“drop”以获取我们的“drag then drop”流。

不同的是,只有当用户放下 div 时,我们才会更新位置。

const { fromEvent } = Rx.Observable;
const target = document.querySelector('.box');
const events = document.querySelector('#events');

const dragstart = fromEvent(target, 'dragstart');
const dragover = fromEvent(document.body, 'dragover');
const drop = fromEvent(document.body, 'drop');

const dragthendrop = dragstart.switchMap((e) => {
  const startX = e.clientX + window.scrollX;
  const startY = e.clientY + window.scrollY;
  const startLeft = parseInt(e.target.style.left, 10) || 0;
  const startTop = parseInt(e.target.style.top, 10) || 0;
  // set dataTransfer for Firefox
  e.dataTransfer.setData('text/html', null);
  console.log('Dragging started');
  events.innerText = 'Dragging started';

  return drop
    .take(1)
    .map((e2) => {
      return {
        left: startLeft + e2.clientX - startX,
        top: startTop + e2.clientY - startY
      };
    });
});

dragover.subscribe((e) => {
  // make it accepting drop events
  e.preventDefault();
});

dragthendrop.subscribe((pos) => {
  target.style.top = `${pos.top}px`;
  target.style.left = `${pos.left}px`;
  console.log('Dragging stopped');
  events.innerText = 'Dragging stopped';
});
.box {
  position: relative;
  width: 150px;
  height: 150px;
  background: seagreen;
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>
<div class="box" draggable="true"></div>

<h3 id="events"></h3>

于 2017-01-17T17:20:51.720 回答