【问题标题】:RxJS - detect long mousedownRxJS - 检测长鼠标按下
【发布时间】:2018-07-22 19:02:38
【问题描述】:

我想检测mousedown 何时被触发超过 500 毫秒,如果是这样 - 做点什么。我的尝试:

const button = document.querySelector('button')
const stream = Rx.Observable.fromEvent(button, 'mousedown')
const mouseUp$ = Rx.Observable.fromEvent(button, 'mouseup')
stream.delay(500).takeUntil(mouseUp$).subscribe(() => console.log(1))

它可以工作,但只是第一次运行。然后,由于takeUntil 运算符,流被取消。如何让它每次都能正常工作?

DEMO

【问题讨论】:

标签: javascript rxjs reactive


【解决方案1】:

在每个mouseDown$ 事件上启动一个TimerObservable 500 毫秒。如果 mouseUp$ 在 500 毫秒内从 TimerObservable 触发 unsubscribe

const button = document.querySelector('button')
const mouseDown$ = Rx.Observable.fromEvent(button, 'mousedown')
const mouseUp$ = Rx.Observable.fromEvent(button, 'mouseup')

const stream$ = mouseDown$.switchMap(() => Rx.Observable.TimerObservable(500).takeUntil(mouseUp$));

stream$.subscribe(() => console.log('Only Fired after 500ms'))

RxJS >= 6.0.0

import { switchMap, takeUntil } from 'rxjs/operators';
import { timer, fromEvent } from 'rxjs';

const button = document.querySelector('button')
const mouseDown$ = fromEvent(button, 'mousedown')
const mouseUp$ = fromEvent(button, 'mouseup')

const stream$ = mouseDown$.pipe(
  switchMap(() => timer(500).pipe(takeUntil(mouseUp$)))
);

stream$.subscribe(() => console.log('Only Fired after 500ms'))

【讨论】:

    【解决方案2】:

    鼠标按住指令示例:

    @Directive({ selector: "[appMouseHold]" })
    export class MouseHoldDirective implements OnInit, OnDestroy {
      @Input() set appMouseHold(tick: string | number) {
        if (typeof tick === 'string') {
          tick = parseInt(tick, 10);
        }
        
        this.tick = tick || 500;
      }
      private tick: number;
      private readonly _stop = new Subject<void>();
      private readonly _start = new Subject<void>();
      private subscription: Subscription;
    
      @Output() mousehold = new EventEmitter<number>();
      @Output() mouseholdstart = new EventEmitter<void>();
      @Output() mouseholdend = new EventEmitter<void>();
    
      ngOnInit() {
        this.subscription = this._start
          .pipe(
            tap(() => this.mouseholdstart.emit()),
            switchMap(() =>
              timer(500, this.tick).pipe(
                takeUntil(this._stop.pipe(tap(() => this.mouseholdend.emit())))
              )
            )
          )
          .subscribe((tick) => {
            this.mousehold.emit(tick);
          });
      }
    
      ngOnDestroy() {
        this.subscription.unsubscribe();
      }
    
      @HostListener("mousedown", ["$event"])
      onMouseDown($event) {
        if ($event.button === 0) {
          this._start.next();
        }
      }
    
      @HostListener("mouseup")
      onMouseUp() {
        this._stop.next();
      }
    }
    

    Stackblitz

    对于非角度使用,您可以简单地将 @HostListener 处理程序替换为 fromEvent() observables

    【讨论】:

    • 很好的解决方案!如果用户在 html 按钮之外移除按住的鼠标按钮,则会发生小的意外陈旧 inc/dec。我添加了@HostListener("mouseout") onMouseOut() { this._stop.next(); }
    【解决方案3】:

    SplitterAlex's answer 很好,但是 takeUntil() 完成了 observable,你不能再处理事件,所以我的解决方法是(它不能完成 observable)

    public touchStartSubject: Subject<any> = new Subject<any>();
    public touchStartObservable: Observable<any> = this.touchStartSubject.asObservable();
    
    public touchEndSubject: Subject<any> = new Subject<any>();
    public touchEndObservable: Observable<any> = this.touchEndSubject.asObservable();
    
    @HostListener('touchstart', ['$event'])
    public touchStart($event: TouchEvent): void {
        this.touchStartSubject.next($event);
    }
    
    @HostListener('touchend', ['$event'])
    public touchEnd(): void {
        this.touchEndSubject.next(null);
    }
    
    this.touchStartObservable
            .pipe(
                mergeMap((res) => race(
                    timer(1500).pipe(map(() => res)),
                    this.touchEndObservable,
                )),
            )
            .subscribe((res: TouchEvent) => {
                 if (!res) return;
                 // do stuff
            })
    

    如果有人可以在没有if (!res) return; 条件的情况下改进我的答案,那就太好了,例如使用.error 而不是.next 代表touchEndSubject

    【讨论】:

      【解决方案4】:

      同样触发鼠标事件的 Angular 指令示例

      import {Directive, ElementRef, Output} from "@angular/core";
      import {fromEvent, merge, timer} from "rxjs";
      import {filter, skip, switchMap} from "rxjs/operators";
      
      @Directive({
        selector: '[appLongClick]'
      })
      export class LongClickDirective {
      
        /**
         * Minimum time between mouse button down to mouse button up
         */
        private readonly DUE_TIME = 500;
      
        /**
         * Mouse down event (only left button)
         */
        private mousedown = fromEvent(this.el.nativeElement, 'mousedown').pipe(
          filter((ev: MouseEvent) => ev.button === 0)
        );
      
        /**
         * Click event (mouse left button up)
         */
        private click = fromEvent(this.el.nativeElement, 'click');
      
        /**
         * After a mouse button down, take the click only if it comes after the due time
         */
        @Output('appLongClick') longClick = this.mousedown.pipe(
          switchMap(() =>
            merge(this.click, timer(this.DUE_TIME)).pipe(
              skip(1),
              filter(this.isPointerEvent),
            ),
          ),
        );
      
        constructor(private el: ElementRef) {}
      
        private isPointerEvent(v: unknown): v is PointerEvent {
          return v instanceof PointerEvent;
        }
      
      }
      

      使用示例:

      <div (appLongClick)="doSomething($event)"></div>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-26
        • 2022-01-18
        • 1970-01-01
        • 2014-11-14
        • 2011-04-20
        • 2015-10-23
        相关资源
        最近更新 更多