RxJS - обнаружить длинное нажатие мыши

Я хочу определить, когда 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. Как заставить работать каждый раз?

ДЕМО


person feerlay    schedule 22.07.2018    source источник
comment
Возможный дубликат Подписаться на takeUntil/skipUntil   -  person Simon Groenewolt    schedule 22.07.2018


Ответы (3)


Запускайте TimerObservable на 500 мс при каждом событии mouseDown$. Если mouseUp$ запускается в течение 500 мс unsubscribe от TimerObservable.

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'))

person SplitterAlex    schedule 23.07.2018

Пример директивы для удержания мыши:

@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() наблюдаемых.

person Netanel Draiman    schedule 18.08.2020
comment
отличное решение! небольшой неожиданный stale inc/dec возникает, если пользователь удаляет удерживаемую кнопку мыши за пределами кнопки html. Я добавил @HostListener("mouseout") onMouseOut() { this._stop.next(); } - person Michal.S; 29.01.2021

Ответ SplitterAlex хорош, но takeUntil() завершается наблюдаемо, и вы больше не можете обрабатывать события, поэтому мой обходной путь для этого был (он не завершает наблюдаемый)

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

person Alexander Nosov    schedule 01.07.2020