valor-software/ng2-dragula

Delay before dragging

Closed this issue · 11 comments

Is it possible to set delay before dragging?
Because on tablet we cant scroll over the drag section, because the drag event kicks in.

Thinking of adding something like this as standard. The mousemove/down/up listeners I've found to be unnecessary, as you have a much higher degree of control with a mouse than a finger. I would much rather, however, have dragging actually start after a delay, rather than being able to start after a delay. That way if you have .gu-mirror set to 'zoom' an element (and/or add a drop-shadow) as if picked up off the page, then you can signal to the user that their finger is controlling an object before they actually move it. This is a common pattern in touch UI design. Closing for now.

<div [dragula]="'bag'">
    <div delayDragLift *ngFor="...">...</div>
</div>
import { Directive, ElementRef, HostListener } from '@angular/core';

@Directive({ selector: '[delayDragLift]' })
export class DelayDragLiftDirective {

    dragDelay: number = 200; // milliseconds
    draggable: boolean = false;
    touchTimeout: any;

    @HostListener('touchmove', ['$event'])
    // @HostListener('mousemove', ['$event'])
    onMove(e: Event) {
        if (!this.draggable) {
            e.stopPropagation();
            clearTimeout(this.touchTimeout);
        }
    }

    @HostListener('touchstart', ['$event'])
    // @HostListener('mousedown', ['$event'])
    onDown(e: Event) {
        this.touchTimeout = setTimeout(() => {
            this.draggable = true;
        }, this.dragDelay);
    }

    @HostListener('touchend', ['$event'])
    // @HostListener('mouseup', ['$event'])
    onUp(e: Event) {
        clearTimeout(this.touchTimeout);
        this.draggable = false;
    }

    constructor(private el: ElementRef) {}
}

https://github.com/cormacrelf/dragula-touch-demo

This solution you presented does not work.
This is how I implemented it (in the view I'm passing a delay of 150 ms).
The behaviour of your solution is: when the event 'touchend' if fired before 150ms the background colour stays the same and I can normally scroll up and down. When I hold for 150ms the background colour changes to gray but when I try to drag the element the page just scrolls as before.

So I implemented it this way and it works as expected:

import { Directive, ElementRef, HostListener, Input, OnInit } from '@angular/core';

const ALIAS = 'delayDragLift';

@Directive({ selector: `[${ALIAS}]` })
export class DelayDragLiftDirective implements OnInit {
  @Input(ALIAS)
  public dragDelay: number;
  private touchTimeout: any;
  private originalBackground: string;

  constructor(private el: ElementRef) {}

  public ngOnInit(): void {
    this.originalBackground = this.el.nativeElement.style.backgroundColor;
  }

  @HostListener('mousedown', ['$event'])
  public onMouseDown(evt: Event): void {
    this.touchTimeout = setTimeout(() => {
      this.draggable = true;
      this.setBackgroundColor('#D3D3D3');
    }, this.dragDelay);
  }

  @HostListener('mousemove', ['$event'])
  public onMouseMove(evt: Event): void {
    this.stopPropagationAndClearTimeout(evt);
  }

  @HostListener('touchmove', ['$event'])
  public onTouchMove(evt: Event): void {
    this.stopPropagationAndClearTimeout(evt);
  }

  @HostListener('touchend', ['$event'])
  public onTouchEnd(evt: Event): void {
    clearTimeout(this.touchTimeout);
    this.draggable = false;
    this.setBackgroundColor(this.originalBackground);
  }

  private get draggable(): boolean {
    return this.el.nativeElement.draggable;
  }

  private set draggable(value: boolean) {
    this.el.nativeElement.draggable = value;
  }

  private setBackgroundColor(colour: string): void {
    this.el.nativeElement.style.backgroundColor = colour;
  }

  private stopPropagationAndClearTimeout(evt: Event): void {
    if (!this.draggable) {
      evt.stopPropagation();
      clearTimeout(this.touchTimeout);
    }
  }

}

[Edited]

My solution does work perfectly absent the background colour stuff. Yours doesn't work very well for me, it's making me click before dragging becomes available on a phone, and on a desktop it leaves the gray background in place after dragging.

I think dragula itself uses the mouse* events and will sometimes get called before the directive's own handlers do. With the touch events, they're handled first, and then turn into mouse events if allowed to propagate, so we can be the gatekeeper.

I made a demo project for using dragula with touchscreens. cormacrelf/dragula-touch-demo

For your purposes, you'd use the .drag-delay-lifted class like I do in the demo to override the background colour in CSS, and it'd go pretty well.

I'm having a hard time trying to implement the drag and drop functionality between <ion-slide>s. Almost there but I have to figure out how to drag to the left and the right

Set the direction property to 'horizontal' in a drake?

i'll try it and let you know

Thanks @cormacrelf that works perfectly !

I have an issue in ionic between <ion-item-sliding> and ng2-dragula.
I cannot 'slide' an item-list until I press longed enough for the item to be draggable.
this.draggable = false also disable the 'swipe' in ionic 3.

I tried setting the direction to 'horizontal' in 'setOptions' with no success.

drag and scroll on a touch device is inherently conflicting. You need to separate out the two intents as much as possible. The workaround I implemented is on touchstart, I make the container's overflow to hidden, to disable the scrolling. on touchend, the no-scroll css is removed. While the no-scroll css is applied to the container, scrolling within the container is achieved by collision detection of the dragged item against the container's edges. When the item approaches the edge, scroll in that direction.

It's working perfectly in windows. But when I'm using it with chrome in Ubuntu the dragging won't work. Is there any workaround?

In 2020, especially new browsers sadly doesn't support this solution anymore. Draggability gets removed but the whole screen doesn't scroll...

The solution is, adding the touch scrolling functionality manually:

  @HostListener('touchmove', ['$event'])
  onMove(e: TouchEvent) {
    if (!this.draggable) {
      e.stopPropagation();
      window.scrollBy(0, -1 * (e.touches.item(0).clientY - this.dragStartPosition));
      this.dragStartPosition = e.touches.item(0).clientY;
      clearTimeout(this.touchTimeout);
    }
  }

  @HostListener('touchstart', ['$event'])
  onDown(e: TouchEvent) {
    this.dragStartPosition = e.touches.item(0).clientY;
    this.touchTimeout = setTimeout(() => {
      this.draggable = true;
    }, this.dragDelay);
  }