jquerytouchdragmomentum

jquery apply momentum of inertia on drag


I write a little script to drag a div container. It will works for touch and mouse events.

I have some difficulties to write the momentum script in order to have my container moving after touchend. I know how to calculate a momentum but I don't know what is the right way to implement it...

Here my script :

var divToDrag = $('.container');
var divOnDrag = $(document);

var dragging = false;
var swipe = false;
var scrollY = false;
var threshold = {x:30, y:10};
var swipeLeft = false;
var swipeRight = false;
var swipeUp = false;
var swipeDown = false;
var threshx = false;
var threshy = false;

var maxSpeed = 5;

function prefix() {
  styles = window.getComputedStyle(document.documentElement, ''),
    pre = (Array.prototype.slice
      .call(styles)
      .join('') 
      .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
    )[1],
    dom = ('WebKit|Moz|MS|O').match(new RegExp('(' + pre + ')', 'i'))[1];
}
prefix();

function pointerEventXY(e) {
      out = {x:0, y:0};
      if(e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend'){
        touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
        out.x = touch.pageX;
        out.y = touch.pageY;
      } else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove') {
        out.x = e.pageX;
        out.y = e.pageY;
      }
      return out;
};

divOnDrag.on('mousedown touchstart', function(event){
    dragging = true;
    divToDrag.stop();
    pointerEventXY(event);
    startCoordsX = prevCoordsX = out.x;
    startCoordsY = prevCoordsY = out.y;
    divCoordsX = divToDrag.position().left;
    divCoordsY = divToDrag.position().top;
    initialCoordsX = startCoordsX - divCoordsX;
    initialCoordsY = startCoordsY - divCoordsY;
    divToDrag.data("mouseEvents",[event]);
})

divOnDrag.on('mousemove touchmove', function(event){    
    if (dragging == true) {
        pointerEventXY(event);
        currentCoordsX = out.x;
        currentCoordsY = out.y;
        xthreshold = Math.abs(currentCoordsX - startCoordsX)
        ythreshold = Math.abs(currentCoordsY - startCoordsY)

        var mouseEvents = divToDrag.data( "mouseEvents" );  
        if ((event.timeStamp - mouseEvents[ mouseEvents.length - 1 ].timeStamp ) > 40){
            mouseEvents.push(event);
            if (mouseEvents.length > 2){
                mouseEvents.shift();
            }
        }

        if(xthreshold > threshold.x && ythreshold < threshold.y && scrollY == false || swipe == true ) {
            event.preventDefault();
            swipe = true;
            dragDirection();
            x = currentCoordsX - initialCoordsX - threshx;
            y = currentCoordsY - initialCoordsY - threshy;
            divToDrag.attr('style', '-'+pre+'-transition: all 0s ease-in !important; -'+pre+'-transform: translate3d('+x+'px, 0px, 0px)');
        } else if (xthreshold < threshold.x && ythreshold > threshold.y) {
            scrollY = true;
        }
    }
})

divOnDrag.on('mouseup touchend',  function(event){
    dragging = false;
    scrollY = false;
    swipe = false;
    swipeLeft = false;
    swipeRight = false;
    swipeUp = false;
    swipeDown = false;
    momentum();
})

function dragDirection() { 
    if (prevCoordsX < currentCoordsX) {
        swipeRight = true;  
        threshx = threshold.x;
    } else if (prevCoordsX > currentCoordsX) {
        swipeLeft = true;
        threshx = -threshold.x;
    } 
    if (prevCoordsY < currentCoordsY) {
        swipeDown = true;   
        threshy = threshold.y;
    } else if (prevCoordsY > currentCoordsY) {
        swipeUp = true; 
        threshy = -threshold.y;
    }
    if (swipeRight == true && swipeLeft == true || swipeUp == true && swipeDown == true) {
        threshx = 0;    
        threshy = 0;
        prevCoordsX = currentCoordsX - initialCoordsX;
        prevCoordsY = currentCoordsY - initialCoordsY;
    }
    prevCoordsX = currentCoordsX;
    prevCoordsY = currentCoordsY;
}

function momentum() { 
    var lastEvent = divToDrag.data( "mouseEvents" ).shift();
    if (!lastEvent){
        return;
    }
    var deltaX = (event.pageX - lastEvent.pageX);
    var deltaY = (event.pageY - lastEvent.pageY);
    var deltaMS = Math.max((event.timeStamp - lastEvent.timeStamp),1);
    var speedX = Math.max(Math.min( (deltaX / deltaMS), maxSpeed ),-maxSpeed);
    var speedY = Math.max(Math.min( (deltaY / deltaMS), maxSpeed ),-maxSpeed);  
    var lastStepTime = new Date();      

    divToDrag.animate(
        {textIndent: 0},
        {duration: (Math.max(Math.abs( speedX ), Math.abs( speedY )) * 3000),
        step: function( currentStep ){
                speedX *= (currentStep / 100);
                speedY *= (currentStep / 100);
                var now = new Date();
                var stepDuration = (now.getTime() - lastStepTime.getTime());
                lastStepTime = now;
                var position = divToDrag.position();
                var newLeft = (position.left + (speedX * stepDuration));
                var newTop = (position.top + (speedY * stepDuration));  
                console.log(newLeft)
                divToDrag.css({transform: 'translateX('+newLeft+'px)'});    
            }
        }
    );
}

and the fiddle : http://jsfiddle.net/V5Jmu/2/

UPDATE

http://jsfiddle.net/RdYQb/12/

I made a new fiddle where it seems to work better thanks to matjazek answer


Solution

  • I would recommend that you solve the problem with a different aproach, but if you insist in using your method, something like this should get the job done:

    http://jsfiddle.net/RdYQb/

    You can set the drag coeficient with

    var drag=0.95; // Drag coeficient