Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lift event / start dragging after touch delay #289

Open
pleaseshutup opened this issue Dec 31, 2015 · 26 comments
Open

Lift event / start dragging after touch delay #289

pleaseshutup opened this issue Dec 31, 2015 · 26 comments

Comments

@pleaseshutup
Copy link

In order to make this library a bit more touch friendly it will help to be able to delay dragging until after a second of holding the finger down without moving. This way a user can drag if they want or also scroll an area on a touch device.

I see there was a commit with a new method called "lift" which will work perfectly for me. When will this be available in dist folder?

@fabsor
Copy link

fabsor commented Jan 12, 2016

I'm also interested in this feature. Our use case is the same; we have an area where you can scroll, and it would be nice a delay. There is an old closed PR, #225

Are there any changes that would have to be done to the PR to be a viable solution?

@ramiabraham
Copy link

Same UC here, fwiw. Hacked in a setTimeout workaround for now, but I admittedly don't feel great about that solution :)

Edit: see better option below.

@reid-rigo
Copy link

I'm in the same boat. Trying to make scrolling on mobile not kick off drags. @ramiabraham how does your workaround work?

@ramiabraham
Copy link

@Rigoleto

  • Create a fixed or absolute-positioned element mask over the scrolling area.
  • Detect scroll-intent (100% doable as vanilla, but here's a somewhat concise adaptation of the old jQuery mousewheel lib that does that with some good catches in place: https://github.com/sparksm/jquery-scroll-intention)
  • On desired scroll intent certainty, allow the scroll event to continue. If scroll intent is not detected, hide the scroll-mask, and allow the dragula object to continue .
  • This can be fired in a number of areas within dragula with some re-tooling, but you'll need to be wary of the scroll-mask preventing triggers within dragula from occurring. I opted to use the excellent drake object in this lib as a closure, and added the scroll-intent within.

Hope that helps!

@avand
Copy link

avand commented Apr 1, 2016

@Rigoleto this is a great workaround. In my case, I'm building a todo list, and pretty much the whole UI is draggable. Does this mean putting a mask over the whole viewport? And if I do that, won't I affect normal link clicks? And did you adapt the jQuery scroll intention plugin?

@linearza
Copy link

linearza commented Apr 6, 2016

I would also prefer if we had control over triggering drags with different events. We use hammerjs for example and so would prefer to activate drags on press rather. The lift api could be the solution

@regislutter
Copy link

regislutter commented Apr 20, 2016

I also needed this feature in my project to manage scroll and drag'n drop on iPad.

For anyone interested, I managed to make it work and referred to this commit: b604c08

I edited the last dragula.js version (3.6.8) and added this code before startBecauseMouseMoved function:

function lift (el) {
                var grabbed = _grabbed = canStart(el);
                if (!grabbed) {
                    return;
                }
                _offsetX = _offsetY = 0; // we could calc these on mousemove but 0,0 is simpler
                startOnLift();
            }

            function startOnLift () {
                var grabbed = _grabbed; // call to end() unsets _grabbed
                eventualMovements(true);
                movements();
                end();
                start(grabbed);
                classes.add(_copy || _item, 'gu-transit');
                renderMirrorImage();
            }

and lift: lift, before cancel: cancel,.

Also, add this in your JavaScript :

document.body.addEventListener('mousedown', function (e) {
       setTimeout(function(){
            if(e.target.hasClass('slide')){
                lifted = true;
                drake.lift(e.target);
            }
        }, 1000);
        });

and as a dragula option :

moves: function(el, source, handle, sibling) {
                if (lifted) { // Manage the drag after 1 second
                    lifted = false;
                    return true;
                }
            }

Don't forget to initialize dragula like this:
var drake = dragula(containers, options);

I don't know why @bevacqua removed this from the API, it's working completely fine on my side 👍 .

@linearza
Copy link

Great stuff thanks @zetura ! Ill give it a shot once I dev this further, I've also forked dragula and started customizing the code.

@skitterm
Copy link

Delay is crucial for my workflow as well. Can it be reinstated as an option?

@xsegrity
Copy link

xsegrity commented May 3, 2016

I need it as well

@patrickfatrick
Copy link

@zetura I haven't played with that code but what do you think about submitting a pull request on that? Seems like plenty of folks want this functionality.

linearza added a commit to linearza/dragula that referenced this issue May 3, 2016
@linearza
Copy link

linearza commented May 3, 2016

I quickly created a PR for this using the logic @zetura provided - I still need to test it properly

@regislutter
Copy link

@patrickfatrick @linearza I'm actually looking to implement the functionality as a unique option (liftDelay). I just have an issue that if you wait and then scroll, you have the scroll + the mirror object. I think that @bevacqua removed the functionality because you need to put a lot of code in your implementation (that's why i'm looking to have a single option). :)

@regislutter
Copy link

regislutter commented May 3, 2016

@patrickfatrick @linearza Take a look at this commit : regislutter@d246524
Let me know if it works for you. I can do a pull request if it's fine for you 👍

You just have to put the option "liftDelay" for this to work:
var drake = dragula(lists, { revertOnSpill: true, direction: 'horizontal', liftDelay: 700 });

@linearza
Copy link

linearza commented May 3, 2016

Aha - I havent worked on this in a while - but I just remembered why this implementation wasnt working for us. In our case we need to trigger a lift on press - but dragula is tightly coupled with its own gestures and the mapping of mouse to touch events. This causes a problem in our case since _grabbed doesnt get set when our press event (handled by hammerjs) gets fired. So really - what we need is to be able to trigger and maybe explicitly set the _grabbed element based on whatever function we choose to call. Ill see if I can figure something out as well

@xsegrity
Copy link

xsegrity commented May 9, 2016

@zetura I tried out your change on a new ipad and it almost works but not quite. It seems to be jammed up with a scroll event where the item is trying to drag and the container is trying to scroll all at the same time making it kinda unusable.

My setup has multiple container columns that scroll horizontally off the screen (horz scroll on the columns parent container). Each column has zero or many vertically stacked items (a column can be vert scroll if it overflows). I tap and hold an item to be dragged, wait for the lift to kick in and then start dragging around. Although the drag kinda works it seems to hook with the scroll and as I drag, the container(s) are scrolling as well or at least attempting to.

I'm glad you gave this a shot though. I don't see anything inherently wrong with the code and it is how I would have gone about it as well. I can't think of a different approach right now but if I do I will chime back in on this thread. Happy to give other ideas a whirl to.

Oh, and I did discover a kinda workaround to the whole thing. On an iPad/iPhone you can simply do a 2 finger scroll to scroll the containers. Dunno about other touch devices though.

@ben-girardet
Copy link

ben-girardet commented May 10, 2016

In case it helps anyone, I've been able to manage this problem by exposing the grab method from the dragula API.

I've created a pull request for this change and written a short explanation of my workaround. You can read it here: #375

@haschu
Copy link

haschu commented Nov 9, 2016

I had the same problem and came up with the following solution, perhaps this is helpful for others.

Since in 3.0.0 drag events won't start if mousemove or touchmove aren't fire, you could solve this issue by using stopPropagation. Here is an angular directive I wrote (should be easy to apply to vanilla.js - you get the idea). It uses a 1000ms press delay after which the dragging starts:

return {
    restrict: "A",
    link: function( scope, element ) {

        var touchTimeout,
            draggable = false;

        element.on( "touchmove mousemove", function( e ) {
            if ( !draggable ) e.stopPropagation();
        });

        element.on( "touchstart mousedown", function( event ) {
            touchTimeout = $timeout( function() {
                draggable = true;
            }, 1000 );
        });

        element.on( "touchend mouseup", function( event ) {
            $timeout.cancel( touchTimeout );
            draggable = false;
        });

    }
};

@cormacrelf
Copy link

@haschu's solution worked pretty well for me, with some modifications. Mainly, dragging before the delay timer makes it 'draggable' will then cancel the timer, which solves an issue where a mouse dragging the item would appear to have no effect at first, and then further mousemoves would have the item 'catch up'.

And this one's for Angular 2 and TypeScript, also obviously it's enabled only for touch devices.

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

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

    dragDelay: number = 200; // milliseconds
    draggable: boolean = false;
    touchTimeout: NodeJS.Timer;

    @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) {
    }
}
<!-- in your template -->
<div class="some-draggable-div" delayDragLift></div>

@rohan-deshpande
Copy link

rohan-deshpande commented Aug 2, 2018

Have implemented this in an ES5 react project and I think it's broken in >= iOS 11.3, I cannot stop the propagation of the touchmove event, possibly related to #426

Class methods:

    handleTouchStart: function () {
      var _this = this;

      this.touchTimeout = setTimeout(function () {
        _this.isDraggable = true;
      }, this.dragDelay);
    },

    handleTouchMove: function (e) {
      if (!this.isDraggable) {
        // this doesn't seem to do anything
        e.stopPropagation();
      }
    },

    handleTouchEnd: function () {
      clearTimeout(this.touchTimeout);
      this.isDraggable = false;
    },

Within the render method:

<div
  className='draggable-item'
  onTouchMove={this.handleTouchMove}
  onTouchStart={this.handleTouchStart}
  onTouchEnd={this.handleTouchEnd}
>
  {this.props.children}
</div>

The call to stop the propagation of the event in handleTouchMove simply doesn't do anything and so dragging is never blocked. Quite frustrating! Right now I'm wondering if reverting to [email protected] will solve this by getting the delay option back.

@rohan-deshpande
Copy link

Reverted to v2.1.1, added delay via the option and it now works. I’ve seen some PRs open to add this option back, imo this is a much cleaner API than having to manually stop the propagation of another event. Not sure why it was removed.

@rohan-deshpande
Copy link

rohan-deshpande commented Aug 9, 2018

aaaand have discovered that this breaks a all of my ordering flows. Back to the drawing board. Am now back on v3.7.2 and will continue to investigate how to get this working.

@radenkozec
Copy link

@rohan-deshpande @cormacrelf @haschu Tried your solution but could not make it work.
I attached to mousemove and touchmove events on draggable item and call e.stopPropagation();
but
dragApi.on('drag', (el) => { console.log('drag');
Still shows that drag is performed and shadow is shown. Am I missing something? Thanks

@haschu
Copy link

haschu commented Jul 19, 2020

@radenkozec Sorry, my workaround is almost 4 years old and I've never used dragula since... 😕

@TotoTN
Copy link

TotoTN commented Dec 10, 2020

any updates on this issue ?

@DarthSonic
Copy link

any way to use delay in newest dragula?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests