Tahir Hassan
Tahir Hassan

Reputation: 5817

JQuery Sortable and automatic scrolling

I am trying to get JQuery Sortable to work but I have run into a slight usability problem.

The list that I am trying to sort is quite large (about 200 items). If the user tries to drag the top item right to the bottom, once the item reaches the bottom of the visible part of the screen, the page scrolls a tiny amount, then stops. To trigger more downward scrolling, you have to move the mouse in circular motions about until the item reaches the bottom.

Is there any method of tracking the position of the mouse while it is dragging an item and automatically scrolling the screen down?

Upvotes: 19

Views: 29545

Answers (9)

Arnaud D
Arnaud D

Reputation: 81

Old topic but here is an example of scroll with a custom scrollable element

var sortScrollInterval = null;
var scrollDelta = 0;
$('.selector').sortable({
    // [..]
    scroll: false,
    sort: function(event, ui) {
        var scrollContainer = $('#container');
        var vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);

        // Scroll top if cursor is into the first 20% of screen
        // Scroll bottom if cursor is into the last 20% of screen
        var top20 = vh * 0.2;
        var bottom20 = vh * 0.8;
        if ((ui.position.top <= top20) || (ui.position.top > bottom20)) {
            if (ui.position.top <= top20) {
                // Closer to the top = quicker scroll
                scrollDelta = -40 * ((top20 - ui.position.top) / top20);
            }
            else {
                // Closer to the bottom = quicker scroll
                scrollDelta = 40 * (1 - (ui.position.top - bottom20) / bottom20);
            }

            // Set interval
            if (null === sortScrollInterval) {
                sortScrollInterval = setInterval(function() {
                    if (Math.abs(scrollDelta) > 10) {
                            $(scrollContainer).scrollTop($(scrollContainer).scrollTop() + scrollDelta);
                        }
                }, 50);
            }
        }
        else if (null !== sortScrollInterval) {
            clearInterval(sortScrollInterval);
            sortScrollInterval = null;
        }
    },
    over: function(event, ui) {
        if (null !== sortScrollInterval) {
            clearInterval(sortScrollInterval);
            sortScrollInterval = null;
        }
    },
    deactivate: function(event, ui) {
        if (null !== sortScrollInterval) {
            clearInterval(sortScrollInterval);
            sortScrollInterval = null;
        }
    }
});

Upvotes: 1

Joshua Pinter
Joshua Pinter

Reputation: 47481

Scroll window when an item is dragged close to the top or bottom.

I could not get any of the other answers working. Using Chrome and a sortable grid that needs to scroll vertically when an item is being dragged to the top or bottom edge of the window.

NOTE: This only works for scrolling the entire window. This will not work if you have a scrollable section inside of the window and need to scroll that.

I was able to get the following working flawlessly:

var currentlyScrolling = false;

var SCROLL_AREA_HEIGHT = 40; // Distance from window's top and bottom edge.

$(".sortable").sortable({
    scroll: true,

    sort: function(event, ui) {

      if (currentlyScrolling) {
        return;
      }

      var windowHeight   = $(window).height();
      var mouseYPosition = event.clientY;

      if (mouseYPosition < SCROLL_AREA_HEIGHT) {
        currentlyScrolling = true;

        $('html, body').animate({
          scrollTop: "-=" + windowHeight / 2 + "px" // Scroll up half of window height.
        }, 
        400, // 400ms animation.
        function() {
          currentlyScrolling = false;
        });

      } else if (mouseYPosition > (windowHeight - SCROLL_AREA_HEIGHT)) {

        currentlyScrolling = true;

        $('html, body').animate({
          scrollTop: "+=" + windowHeight / 2 + "px" // Scroll down half of window height.
        }, 
        400, // 400ms animation.
        function() {
          currentlyScrolling = false;
        });

      }
    }
});

Coffeescript Version

currentlyScrolling = false
SCROLL_AREA_HEIGHT = 40

sort: (event, ui) ->

  return if currentlyScrolling

  windowHeight = $( window ).height()

  mouseYPosition = event.clientY

  if mouseYPosition < SCROLL_AREA_HEIGHT # Scroll up.
    currentlyScrolling = true
    $( 'html, body' ).animate( { scrollTop: "-=" + windowHeight / 2 + "px" }, 400, () -> currentlyScrolling = false )

  else if mouseYPosition > ( windowHeight - SCROLL_AREA_HEIGHT ) # Scroll down.
    currentlyScrolling = true
    $( 'html, body' ).animate( { scrollTop: "+=" + windowHeight / 2 + "px" }, 400, () -> currentlyScrolling = false )

Upvotes: 4

TBE
TBE

Reputation: 1133

Based on @marty 's answer, here is a fine tuned code that will: 1. Control speed of the scrolling 2. Will scroll down and scroll up without glitches. 3. Default speed is 7px at a time 4. movements of less than 7px will be ignored

var previousLocation, previousDelta;
    $( ".selector" ).sortable({
        sort: function(event, ui) {
            var currentScrollTop = $(window).scrollTop(),
                topHelper = ui.position.top,
                delta = topHelper - currentScrollTop;
            setTimeout(function() {
                if((delta < 7 && delta >=0) || (delta > -7 && delta <=0))
                    return;
                if(delta > 7){
                    delta = 7;
                    if((topHelper - previousDelta) < previousLocation){
                        delta = (delta * -1);
                    }
                }
                if(delta < -7){
                    delta = -7;
                    if((topHelper - previousDelta) > previousLocation){
                        delta = (delta * -1);
                    }
                }

                $(window).scrollTop(currentScrollTop + delta);
                previousLocation = topHelper; previousDelta = delta;
            }, 5);
        }
    });

Upvotes: 2

I had a responsive table with bootstrap, this would not let it work.

not like this:

<div class="table-responsive">
   <table>
      ...
   </table>
</div>

like this yes:

 <table>
   ...
 </table>

and use these options:

scroll, scrollSensitivity, scrollSpeed

Upvotes: 3

Daniel
Daniel

Reputation: 31

I have removed overflow-y: scroll from body to resolve it.

Upvotes: 3

Saram
Saram

Reputation: 1510

I think that you can consider handling scrolling external to sortable. I suggest to use timer and 'out' event of sortable.

Here is piece of code based on jQueryUI demo page, you can use it as start point, if want to go this way:

$(function() {
   var scroll = '';
   var $scrollable = $("#sortable");
   function scrolling(){
     if (scroll == 'up') {
       $scrollable.scrollTop($scrollable.scrollTop()-20);
       setTimeout(scrolling,50);
     }
     else if (scroll == 'down'){
       $scrollable.scrollTop($scrollable.scrollTop()+20);
       setTimeout(scrolling,50);
     }
   }

   $( "#sortable" ).sortable({
      scroll:false,
      out: function( event, ui ) {
        if (!ui.helper) return;
        if (ui.offset.top>0) {
          scroll='down';
        } else {
          scroll='up';
        }
        scrolling();
      },
      over: function( event, ui ) {
        scroll='';
      },
      deactivate:function( event, ui ) {
        scroll='';
      }
    });
    $( "#sortable").disableSelection(); 
});

Here is also working example: JSBIN

sorry
I did not lock example code and was destroyed incidentally. Now it's back to work.

Upvotes: 3

marty
marty

Reputation: 4015

While the value of the scrollSensitivity controls the scroll behaviour when the helper item approaches an edge (top or bottom), you can use the "sort" event to unconditionally scroll while you drag if you use the following code:

$(".sortable").sortable({
    scroll: true,
    scrollSensitivity: 80,
    scrollSpeed: 3,
    sort: function(event, ui) {
        var currentScrollTop = $(window).scrollTop(),
            topHelper = ui.position.top,
            delta = topHelper - currentScrollTop;
        setTimeout(function() {
            $(window).scrollTop(currentScrollTop + delta);
        }, 5);
    }
});

Not sure if this completely addresses the issue you're seeing, but I found that usability with larger list improves with this approach. Here is a link to jsfiddle.

Upvotes: 8

digitaldreamer
digitaldreamer

Reputation: 55372

I would take a look at the scroll, scrollSensativity, and scrollSpeed options.

You can do something like:

$("#sort").sortable({ scroll: true, scrollSensitivity: 100 });

or

$("#sort").sortable({ scroll: true, scrollSpeed: 100 });

or even

$("#sort").sortable({ scroll: true, scrollSensitivity: 100, scrollSpeed: 100 });

Upvotes: 28

jonathanatx
jonathanatx

Reputation: 1613

You can trigger events based on the position returned by mouseMove. Here's a simple tutorial: http://jquery-howto.blogspot.com/2009/07/identifying-locating-mouse-position-in.html

This tutorial might help you get started: http://valums.com/vertical-scrolling-menu/ And this walks through the same effect: http://www.queness.com/resources/html/scrollmenu/index.html

Upvotes: 0

Related Questions