10
votes

I'm experiencing a weird scrollbar issue. I'm building a page that uses jQuery and PHP to dynamically load images into a DIV sequentially. This DIV is a fixed height but uses a scrollbar for its variable width. The problem is that the scrollbar does not reset after a dynamic refresh of the DIV. So when the user scrolls and then refreshes with new content, the scroll bar position stays persistent instead of resetting back to the left.

This seems to only happen in FF3. The scrollbar resets perfectly fine in Chrome, Safari, and IE8.

For each refresh, the DIV is hidden, emptied, sized with CSS, then sequentially appended with images.

I've tried resetting white-space: normal before the nowrap, playing around with overflow, and also jQuery's scrollLeft to no avail. It still behaves strangely in FF3, and only FF3.

Click a thumbnail, move the scrollbar then click another thumb.

Thanks for any help!

5
Wow, that's very strange. My best guess would be to simply grab the element and set it's scrollTop and scrollLeft to zero, this way there should be no fuss. btw, nice site! I love the hash control. - Tom
Yeah, I tried doing ScrollLeft before. I might revisit that again. The hashchange plugin is great, eh?. I should donate some beer money to Ben Alman 'cause it saved me from having to write all that polling stuff! - rcon

5 Answers

5
votes

OK, after contemplating David M's suggestions, I figured it out. Since #interiors is a child of #content it was also being hidden. So I had to show it first, set scrollLeft then hide it again. A bit kludgy, but whatever works...

$('#landing, #interiors, #caption').empty();
$('#content').show()
$('#interiors').scrollLeft(0);
$('#interiors, #caption').hide();

Regarding the cached data in FF3, I'm still not clear on that. Save that one for a rainy day...

Thanks

3
votes
0
votes

When I type this into the firebug interactive console:

var e = $('#interiors')[0]
e.scrollLeft = 0
e.scrollTop = 0

it seems to reset the scrollbars correctly. The element may also need to be shown with a display of block before setting scrollLeft but I'm not sure - I think that when I tried to do this that Firefox restored the last values when the elements with overflow: auto changed from hidden to shown.

EDIT: There are a couple of things which you could try to force Firefox to reset the values. First of all, you could remove and then re-add the "content" element when the hash changes:

var e = $('#interiors')[0]
var p = e.parentNode
p.removeChild(e)
p.insertBefore(e, p.firstChild) // insert again before the caption

Secondly, you could reset the scrollLeft/scrollTop values to 0 before using $('#interiors').empty() or $('#interiors').hide() so it doesn't save the values.

0
votes

I edited the javascript to reset the scrollLeft/scrollTop values before hiding/clearing/setting the HTML. I put all of those operations into a single function to try to figure out what was happening.

I've tested this in Firefox and it seems to fix the scrolling problems, but I haven't tested any other browsers. It should work though.

It seems I was right in my other answer that you need to reset the scrollLeft and scrollTop values in Firefox while the element with an overflow of auto is shown with a display of block though, as it does seem to restore the old values when shown regardless of whether the scroll values changed while hidden:

function setInteriors(html, hide) {
    var i = $('#interiors');

    // Reset the scrollbar positions BEFORE clearing/setting the HTML
    i.scrollLeft(0);
    i.scrollTop(0);

    // Set the HTML if provided, otherwise empty
    if (html) i.html(html);
    else i.empty();

    // Hide the element if hide is `true`
    if (hide) i.hide();
}

function showContent(nav) {
    if($.browser.safari) // webkit browsers
    { 
        bodyelement = $("body")
    }
    else
    { 
        bodyelement = $("html, body")
    }
    bodyelement.animate({ scrollTop: 0 }, 300);

    setInteriors(null, true);
    $('#caption').hide();
    $('#caption').empty();
    $('#landing').empty();

    // Detect document window size and use appropriate image heigh images

    if ($(window).height() < 832 ) // size of the document window, not browser window
    {                              // threshold for 600px images + 5 caption lines
        var imageHeight = 500;
    }
    else
    {
        var imageHeight = 600;
    }

    // Show #content so we can show/hide #interiors and #caption individually

    $('#content').show();
    if ((nav == "about") || (nav == "contact"))
    {
        setInteriors(null); // for fast back/forward button mashing

        switch(nav)
        {
            case "about":
                setInteriors($('#hidden-about').html()); // Load from hidden div
                break;
            case "contact":
                setInteriors($('#hidden-contact').html());
                break;
        }
        $('#interiors').css('height', '100%');  // Dimensions for "about" and "contact"
        $('#interiors').css('width', '645px');
        $('#interiors').css('white-space', 'normal');
        $('#interiors').fadeIn(200);
    }
    // TO DO: Maybe separate #interiors to two classes for dynamic changes?
    else
    {
        switch(imageHeight)
        {
            case 500:
                $('#interiors').css('height', '520px');  // Dimensions for gallery
                                                         // Extra 20px for scrollbar
                break;
            case 600:
                $('#interiors').css('height', '620px');
                break;
        }
        $('#interiors').css('width', '100%');
        setInteriors(null); // for fast back/forward button mashing
        $('#interiors').show();
        nav = (location.hash).substring(1); // for fast back/forward button mashing
        $('#caption').html('<P class="caption">' + $('#hidden-' + nav).html() + '</P>'); // load hidden captions
        $('#caption').fadeIn(300);  // show caption before images

        getImages = "http://www.shadowshapes.com/uttwerk/getImages.php?id=" + nav + "&height=" + imageHeight;
        $.getJSON(getImages, function(json) {
            var max = json.length;
            if(max > 0)
            {
                loadImage(0, max, nav);
            }

            function loadImage(index, max, nav) {
                if ((location.hash).substring(1) == nav) // until hash changes, load current nav
                {
                    if(index < max)
                    {
                        var newimg = new Image();
                        $(newimg).load(function () {
                            if ((location.hash).substring(1) == nav) // after current image loads
                            {                                        // continue if no hashchange
                                $('#interiors').append(this);
                                $('#interiors').css('white-space', 'nowrap');
                                $(this).hide();
                                if (max - index > 1)  // add space after each image except last one
                                {
                                    $(this).css('margin-right', '20px');
                                }
                                $(this).css('vertical-align', 'top');
                                $(this).fadeIn(200, function() {
                                        loadImage(index + 1, max, nav);
                                });
                            }
                        }).attr('src', json[index]);
                    }
                }
            }
        });
    }
}

function arrangeStars() {
    $('img.star').each(function () {
        thumbposition = $(this).siblings('a.nav').children('img').position();
        $(this).css("top", (thumbposition.top - 9));
        $(this).css("left", (thumbposition.left - 9));
    });
}

function placeStar(nav) {
    // clear all stars on hash change

    if ($('div.thumb').children('img').hasClass("visiblestar")) {
        $('div.thumb').children('img').removeClass("visiblestar").addClass("hiddenstar");
    }
    // attach star to selected thumbnail

    var test = $('div#_' + nav);
    if ($(test).children('img').hasClass("hiddenstar")) {
        $(test).children('img').removeClass("hiddenstar").addClass("visiblestar");
    }
}

$(document).ready(function() {

    //$.imgpreload(['', ''], {each: null, all:null});  

    // bind hover event for empty/contact/about hash only

    $(arrangeStars());  // positions stars in the corner of each thumbnail

    $('img.thumb, img.thumbwithborder').hover(
        function () {  
            var nav = (location.hash).substring(1);
            if ((nav == '') || (nav == "about") || (nav =="contact")) {
                nav = $(this).parent().parent().attr("id");
                $('div.thumb#' + nav).children('img').removeClass('hiddenstar').addClass('visiblestar');
            }
        },
        function () {  
            var nav = (location.hash).substring(1);
            if ((nav == '') || (nav == "about") || (nav =="contact")) {
                nav = $(this).parent().parent().attr("id");
                $('div.thumb#' + nav).children('img').removeClass('visiblestar').addClass('hiddenstar');
            }
        }
    );

    // hash change event triggers all the navigation and content switching

    jQuery.hashchangeDelay = 50;

    $(function() { 
        $(window).bind('hashchange', function() {
            var nav = (location.hash).substring(1);
            if (nav != '')
            {
                placeStar(nav);
                $('#content').fadeOut(200, function() {
                    showContent(nav);
                });
            }
        });
    })

    if (location.hash != '')
    {
        $(window).trigger('hashchange');
    }

    // load landing content

    $(function() {
        $('#content').hide(function() {
            var landingdiv = $(document.createElement('div')).attr({id: 'landing'});
            landingdiv.html($('#hidden-landing').html());
            landingdiv.clone().appendTo('#interiors');
            $(this).fadeIn(200);
            });
    });
});
0
votes

Just encountered the same issue and solved it by setting a timeout AFTER the #interiors has become visible on page.

$("#interiors").show(function(){
    setTimeout(function(){$(this).scrollLeft(0);},10);});
});