30

I created a demo: http://pastebin.me/584b9a86d715c9ba85b7bebf0375e237

When the scroll bar is at the bottom and you drag items to sort them, it causes the scroll bar to jump up. It seems to do this in FF, Safari, Chrome, and IE (at least IE8).

In Firefox on my Mac, it does the jump up when the scroll bar is at the bottom, but also adds a nice flash to the whole list. It just flashes the whole list right as it scrolls it up. I believe that if I figure out what is causing the scroll up (and if I can stop it), the flashing will stop as well.

I don't like it jumping up like that b/c I find it jarring and confusing. I know this is a bit of a corner case, but I'd like to fix it if I could.

So, is there any way to prevent it from scrolling up? Alternately, what is causing it to scroll up?

Thanks

2
  • I'm in FF 3.6, and I don't see any real problems. If I grab the bottom one and drag it up, the box doesn't scroll until it reaches the top.
    – Bialecki
    Commented Mar 30, 2010 at 0:05
  • Try this solution before messing with javascript
    – PeterM
    Commented Feb 7, 2017 at 11:50

13 Answers 13

51

The problem is very simple.

First let's set up the scenario. You have a list of sortable elements, your page is higher than your screen so it has a scrollbar, your page is at the very bottom, the scroll bar all the way down.

The issue, you go to drag an element which causes jQuery to remove it from the dom then add in a placeholder. Let’s slow it down, it removes the element first, now the list is shorter causing the page to be shorter, causing the scrollbar to be shorter. Then it adds the placeholder, now the page is longer, now the srollbar is longer (but the window doesnt scroll back down, so it appears the scroll bar jumped up).

Best way I found to counteract this would be to ensure the sortable list has a static height, so removing an element doesn't cause it to go shorter. One method of doing this would be

create:function(){
    jQuery(this).height(jQuery(this).height());
}

The above will be called upon the creation of the sortable list, staticly setting its height to it's current height.

If you have pictures in one of the sortable elements, the above code will not work unless you pre-define the dimensions in the tag. Reason is that when the height() is called and set, it's calculating before the image is filled out. No dimension, then the image accounts for no space. Once the image loads, then will it take up space.

Here is a way around having to define dimensions. Simply resize the list height every time an image within is detected to be loaded.

create:function(){
    var list=this;
    jQuery(list).find('img').load(function(){
        jQuery(list).height(jQuery(list).height());
    });
}

Final version:

jQuery(document).ready(function(){  
    jQuery("div#todoList").sortable({
        handle:'img.moveHandle',
        opacity: 0.6,
        cursor: 'move',
        tolerance: 'pointer',
        forcePlaceholderSize: true,
        update:function(){
            jQuery("body").css('cursor','progress');
            var order = jQuery(this).sortable('serialize');
            jQuery.post("/ajax.php?do=sort&"+order,function(data){jQuery("body").css('cursor','default');});
        },
        create:function(){
            var list=this;
            var resize=function(){
                jQuery(list).css("height","auto");
                jQuery(list).height(jQuery(list).height());
            };
            jQuery(list).height(jQuery(list).height());
            jQuery(list).find('img').load(resize).error(resize);
        }
    });
});
4
  • 1
    This code fixed my problem too. Thank you. but there is another problem from this fix. In case that sortable list has remove row option. Once a single row was removed from list there is space left there because the list's height is fixed.
    – vee
    Commented May 27, 2014 at 20:38
  • 1
    and one more problem. This cannot be fix if sortable list is not visible. ex: sortable list is in non-active tabs.
    – vee
    Commented May 27, 2014 at 20:49
  • Same here, make sure you also resize when the window is resized, especially for responsive websites. Because content height might change. But +1 for handling the image case.
    – RaphKomjat
    Commented Sep 9, 2015 at 9:55
  • I wrote a complement answer to handle the window resize case.
    – RaphKomjat
    Commented Sep 9, 2015 at 10:50
24

I had the same issue when using a div container and div elements and setting the container height didn't help me, but explicitly setting overflow:auto css property on the containing div did fix the issue. Only tested in Chrome though.

3
  • 2
    "You're my savior, man!" It works just brilliant. (Works in Firefox too)
    – gorodezkiy
    Commented Mar 26, 2014 at 18:08
  • I add "overflow: auto;" to my "ol" container an it Works. Thanks !!
    – rjurado01
    Commented Jul 21, 2016 at 11:48
  • The accepted answer didn't work for me but this did. Thanks. (Yes, still using jQuery UI in 2019) Commented Jan 24, 2019 at 12:10
13

I have experienced this issue as well. It happens when the list dictates the length of your page. When you start sorting, the list height changes for a split second, causing the page to jump. Here is an odd, but very functional solution to this problem:

$('ul').height($('ul').height());
3
  • Agreed. The reason why the issue is happening is explained clearly, the fix works perfectly, it's quick and simple.
    – Tony M
    Commented Nov 16, 2012 at 17:17
  • 1
    @Matt End of the page, and when the document is ready.
    – Jonathan
    Commented Jun 30, 2014 at 23:13
  • This is an ok solution, but keep in mind that your 'ul' inner content could become higher when resizing the window. In this case you may want to listen to the window 'resize' event and set your 'ul' height accordingly.
    – RaphKomjat
    Commented Sep 9, 2015 at 9:39
6

Carl M. Gregory explains the issue perfectly, but I think his solution is unnecessarily complicated.

Setting list height to fixed value fixes the issue, but doing so at the moment of creating the list is way too early. You can use .mousedown() function to set the height right before the sorting starts. By this time any potential images are already loaded, so there is no need in checking their sizes.

So the jump doesn't occur, but now please consider this: You have multiple connected list and they are arranged vertically. Now this happens:

  1. You start dragging from the upper list
  2. The upper list now has fixed height
  3. Move the item to the bottom list
  4. Bottom list nicely adjusts, because its' height is auto
  5. The upper list still has the same fixed height though it should be one item shorter

So at some point, you should return its' height back to auto. When? On mouseup is too late (see https://jsfiddle.net/937vv4tx/1/), but it's okay, you can return the correct value almost immediately after the jump is no more a threat. Using the start() event

Update: But you still want to set height to auto on .mouseup() because if you just click on item and don't start dragging, it still sets fixed height because the .mousedown() has already happened.

$('.sortable').mousedown(function() {
    // set fixed height to prevent scroll jump
    // when dragging from bottom
    $(this).height($(this).height());
}).mouseup(function () {
    // set height back to auto 
    // when user just clicked on item
    $(this).height('auto');
}).sortable({
    connectWith: '.sortable',
    placeholder: 'placeholder',
    start: function() {
        // dragging is happening
        // and scroll jump was prevented,
        // we can set it back to auto
        $(this).height('auto');
    }
});

This code works perfectly for me.

5

Lol, looks like this still isn't patched 4 years later. Here's the ticket.

Adding onto @Carl M. Gregory's answer, to fully take advantage of a dynamic container instead of setting it on a fixed height, i would edit the jquery.ui.sortable.js core file.

Simply move back line 228. this._createPlaceHolder... before line 208. this.helper.css("position", "absolute");

Thanks to Honda on the jquery ticket portal. Am using Sortable.js version 1.10.3.

2
  • 2
    If somebody's using another version, or even a customized jQuery UI build (with different line numbers), just look for the Sortable widget code and within the function _mouseStart() move the line this._createPlaceHolder... before the line this.helper.css("position", "absolute");. Commented Feb 17, 2014 at 21:35
  • Hmm...that still does a small jump although not as high as before.
    – flu
    Commented Jun 18, 2015 at 14:18
4

So, complex! All you have to do is add overflow: auto; to the parent (ul, #element, whatever_you_call_it), and it should work. This ensures that even if the document changes height slightly, that when resizing, the overflow auto will retain the element's height.

1
  • 1
    I searched for a lot of time for this problem, your answer fixed my problem, thank you Commented Dec 25, 2018 at 6:14
3

Thanks for the great solution but I recommend using the following instead:

create:function(){
    var list=this;
    resize=function(){
        jQuery(list).css("min-height","0");
        jQuery(list).height(jQuery(list).height());
    };
    jQuery(list).css('min-height', jQuery(list).height());
}
2
  • Bit late to the game for comments, but I also prefer this method...especially when you're adding sortable elements on the fly. +1 Commented Jan 10, 2016 at 19:33
  • 4
    what is this? resize is not used at all
    – John Smith
    Commented Jan 12, 2016 at 19:53
3

Carl M. Gregory is explaining the 'why this is happening' very clearly.

However fixing the height doesn't seem enough, since the inner content can become taller when you resize the window.

What I wish could be done:

  1. Listen to sortable 'start' event, and fix the height of the sortable container
  2. Listen to sortable 'stop' event, and set back the height to auto (to make sure that there is no problem when resizing the window)

What happens in practice: The jump happens before we catch the start event, so this cannot be used

The workaround: listen to the jquery mousedown and mouseup events on the sortable items. It's very straightforward.

$(".sortable_item").mousedown(function(){
    $(".sortable_container").height($(".sortable_container").height());
}).mouseup(function(){
    $(".sortable_container").height('auto');
});

I suggest you still consider Carl's point about images loading. His answer is worth a read.

1

Seems like jQuery UI 1.9.2 solved the issue.

If you are unable to change the library, there's a workaround including a simple scroll bar operation. The idea is simple:

  • Keep the previous scroll position every time you scroll.
  • Set scroll back to the previous position when you start dragging your element.

Here you go;

var lastScrollPosition = 0; //variables defined in upper scope
var tempScrollPosition = 0;

window.onscroll = function () { // Up to you requirement you may change it to another elemnet e.g $("#YourPanel").onscroll
    clearTimeout($.data(this, 'scrollTimer'));
    $.data(this, 'scrollTimer', setTimeout(function () {
        tempScrollPosition = lastScrollPosition; // Scrolls don't change from position a to b. They cover some numbers between a and b during the change to create a smooth sliding visual. That's why we pick the previous scroll position with a timer of 250ms.
    }, 250));

    lastScrollPosition = $(document).scrollTop(); // Up to you requirement you may change it to another elemnet e.g $("#YourPanel").onscroll
};

$("#YourSortablePanel").sortable({
    start: function (event, ui) {
        $(document).scrollTop(tempScrollPosition);
    }
});
2
  • 1
    This feels like a bit of a hack since I have to set a semi-global variable, but it is the only thing that worked for me on this thread. I'm using a side-scrolling div where content is hidden, so most of the other solutions do not apply to me. I'm also using the rails-ui gem so I can't edit the core file. Thanks!
    – CHawk
    Commented Nov 23, 2015 at 1:47
  • I had similar conditions with you and had come up with this idea. I remember looking through tons of solutions without any luck for days. I'm very glad that this has saved someone else's time. Commented Nov 23, 2015 at 2:25
0

I had this issue in Chrome. And all sortable divs had fixed height.

Problem was in css property position: relative of these divs. When div was dragged and removed from DOM, it's coordinates were calculated wrong due to relative position.

Inherit or absolute positioning should be used for sortable elements.

0

This is how i solve the issue.

   $(".groups").sortable({
    ...
    helper: function(event, ui){
      lastScrollPosition =  $("body").scrollTop();
    },
    start: function(e, ui){
        // avoid scroll jump
        $("body").scrollTop(lastScrollPosition);
    },
   });
0

I was loading my sortable divs VIA Ajax into a wrapper with an auto height.

Instead of trying to link into sortable events, I just made the height auto when loading and changing the list and then set it to a static height afterwards.

Vit's solution is similar, but I didn't want to add unnecessary overhead by doing this on every mousedown and mouseup event. Instead, I just do it when the list is changing on the load or when new elements are added (I use a slideDown animation and just set the height to static when it is complete).

$("#wrapper").load("list.php",function() {
    $("#wrapper").css({ "height":"auto" });
    $("#wrapper").css({ "height": $("#wrapper").height() });
}

The other solutions weren't working for me when I reload data as I only made them sortable once during the page load.

My list/div changes with a dropdown so I am able to select different lists and my wrapper resizes accordingly without the annoying jump.

If you need to add elements or reload anything, just throw those two lines of code into a function and call the function.

Hope this helps someone!

0

Here is another easy option to solve this problem.

  1. when mouseenter your sorting Icon,

    $("body").css("overflow-y","hidden");
    
  2. when mouseleave your sorting Icon

    $("body").css("overflow-y","scroll");
    
1
  • And if you found switching overflow-y affects your container width, try ::-webkit-scrollbar { width: 0px; }
    – jetsai
    Commented Sep 13, 2018 at 14:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.