最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - RxJS: distinguish single click from drag - Stack Overflow

programmeradmin0浏览0评论

I have an AngularJS ponent that should react to either a single click or a drag (resizing an area).

I started to use RxJS (ReactiveX) in my application, so I try to find a solution using it. The Angular side of the request is minor...

To simplify the problem (and to train myself), I made a slider directive, based on the rx.angular.js drag'n'drop example: See the Slide.js file (the other code is for other experiments). The code of this logic is:

    function(scope, element, attributes)
    {
      var thumb = element.children(0);
      var sliderPosition = element[0].getBoundingClientRect().left;
      var sliderWidth = element[0].getBoundingClientRect().width;
      var thumbPosition = thumb[0].getBoundingClientRect().left;
      var thumbWidth = thumb[0].getBoundingClientRect().width;

      // Based on drag'n'drop example of rx-angular.js
      // Get the three major events
      var mousedown = rx.Observable.fromEvent(thumb,     'mousedown');
      var mousemove = rx.Observable.fromEvent(element,   'mousemove');
      var mouseup   = rx.Observable.fromEvent($document, 'mouseup');

      // I would like to be able to detect a single click vs. click and drag.
      // I would say if we get mouseup shortly after mousedown, it is a single click;
      // mousedown.delay(200).takeUntil(mouseup)
      //   .subscribe(function() { console.log('Simple click'); }, undefined, function() { console.log('Simple click pleted'); });

      var locatedMouseDown = mousedown.map(function (event) 
      {
        event.preventDefault();
        // console.log('Click', event.clientX - sliderPosition);
        // calculate offsets when mouse down
        var initialThumbPosition = thumb[0].getBoundingClientRect().left - sliderPosition;
        return { from: initialThumbPosition, offset: event.clientX - sliderPosition };
      });

      // Combine mouse down with mouse move until mouse up
      var mousedrag = locatedMouseDown.flatMap(function (clickInfo) 
      {
        return mousemove.map(function (event)
        {
          var move = event.clientX - sliderPosition - clickInfo.offset;
          // console.log('Move', clickInfo);
          // calculate offsets from mouse down to mouse moves
          return clickInfo.from + move;
        }).takeUntil(mouseup);
      });

      mousedrag 
        .map(function (position)
        {
          if (position < 0)
            return 0;
          if (position > sliderWidth - thumbWidth)
            return sliderWidth - thumbWidth;
          return position;
        })
        .subscribe(function (position) 
        {
          // console.log('Drag', position);
          // Update position
          thumb.css({ left: position + 'px' });
        });
    }

That's mostly D'n'D constrained horizontally and to a given range.

Now, I would like to listen to mousedown, and if mouse up happens within a short while (say 200 ms, to adjust), I see it as a click and I do a specific treatment (eg. resetting the position to zero).

I tried with delay().takeUntil(mouseup), as seen in another SO answer, without success. Perhaps a switch() might be needed, too (to avoid going the drag route).

Any idea? Thanks in advance.

I have an AngularJS ponent that should react to either a single click or a drag (resizing an area).

I started to use RxJS (ReactiveX) in my application, so I try to find a solution using it. The Angular side of the request is minor...

To simplify the problem (and to train myself), I made a slider directive, based on the rx.angular.js drag'n'drop example: http://plnkr.co/edit/UqdyB2 See the Slide.js file (the other code is for other experiments). The code of this logic is:

    function(scope, element, attributes)
    {
      var thumb = element.children(0);
      var sliderPosition = element[0].getBoundingClientRect().left;
      var sliderWidth = element[0].getBoundingClientRect().width;
      var thumbPosition = thumb[0].getBoundingClientRect().left;
      var thumbWidth = thumb[0].getBoundingClientRect().width;

      // Based on drag'n'drop example of rx-angular.js
      // Get the three major events
      var mousedown = rx.Observable.fromEvent(thumb,     'mousedown');
      var mousemove = rx.Observable.fromEvent(element,   'mousemove');
      var mouseup   = rx.Observable.fromEvent($document, 'mouseup');

      // I would like to be able to detect a single click vs. click and drag.
      // I would say if we get mouseup shortly after mousedown, it is a single click;
      // mousedown.delay(200).takeUntil(mouseup)
      //   .subscribe(function() { console.log('Simple click'); }, undefined, function() { console.log('Simple click pleted'); });

      var locatedMouseDown = mousedown.map(function (event) 
      {
        event.preventDefault();
        // console.log('Click', event.clientX - sliderPosition);
        // calculate offsets when mouse down
        var initialThumbPosition = thumb[0].getBoundingClientRect().left - sliderPosition;
        return { from: initialThumbPosition, offset: event.clientX - sliderPosition };
      });

      // Combine mouse down with mouse move until mouse up
      var mousedrag = locatedMouseDown.flatMap(function (clickInfo) 
      {
        return mousemove.map(function (event)
        {
          var move = event.clientX - sliderPosition - clickInfo.offset;
          // console.log('Move', clickInfo);
          // calculate offsets from mouse down to mouse moves
          return clickInfo.from + move;
        }).takeUntil(mouseup);
      });

      mousedrag 
        .map(function (position)
        {
          if (position < 0)
            return 0;
          if (position > sliderWidth - thumbWidth)
            return sliderWidth - thumbWidth;
          return position;
        })
        .subscribe(function (position) 
        {
          // console.log('Drag', position);
          // Update position
          thumb.css({ left: position + 'px' });
        });
    }

That's mostly D'n'D constrained horizontally and to a given range.

Now, I would like to listen to mousedown, and if mouse up happens within a short while (say 200 ms, to adjust), I see it as a click and I do a specific treatment (eg. resetting the position to zero).

I tried with delay().takeUntil(mouseup), as seen in another SO answer, without success. Perhaps a switch() might be needed, too (to avoid going the drag route).

Any idea? Thanks in advance.

Share Improve this question asked Oct 23, 2015 at 16:10 PhiLhoPhiLho 41.2k6 gold badges99 silver badges136 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 10

You can use timeout (timeoutWith if you are using ReactiveX/RxJS)

var click$ = mousedown.flatMap(function (md) {
  return mouseup.timeoutWith(200, Observable.empty());
});

If the mouseup doesn't occur before the timeout it will just propagate an empty Observable instead. If it does then the downstream observer will receive an event.

Isn't the trick with delay(Xms).takeUntil(mouseup) doing the opposite of what you want? I mean, you want to detect when the mouseup event happens before the countdown, while the aformentioned trick detect when the mouseup event happens after.

I would try something around those lines (untested for now, but hopefully it will orient you in some positive direction):

var click$ = mousedown.flatMap(function ( mouseDownEv ) {
  return merge(
      Rx.just(mouseDownEv).delay(Xms).map(function ( x ) {return {event : 'noclick'};}),
      mouseup.map(function ( mouseUpEv ) {return {event : mouseUpEv};})
      ).first();
});

The idea is to race the mouseup event against a dummy emission happening after your delay, and see who wins. So if click$ emits 'noclick' then you can consider that no click happened.

Hopefully that works, i will test soon but if you do before me, let me know.

发布评论

评论列表(0)

  1. 暂无评论