views:

728

answers:

3

Hi, 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!

A: 

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.

David Morrissey
I did more analysing in Firebug. Whats happening is that upon DIV refresh, the scrollLeft value resets to zero until it needs a scrollbar. If the scrollbar is needed, it restores the previous value (you'll see it toggle). If the scrollbar isn't needed the value stays at zero, and subsequent refreshes will use this new stored value of zero whether or not a scrollbar is needed. Now I want to figure out a way to zero or block the old value.
@rcon: I've added some more suggestions to reset the scrollbar, please reply if you're still having problems
David Morrissey
@David M: I thought doing a `remove()` would work too, but unfortunately it doesn't. One would think removing an element from the DOM would clear the persistent data. Its a good idea, so I'm going to try playing around with `clone()` next. Regarding the other suggestion, see my reply to Már Örlygsson's answer. Thanks for the ideas!
A: 

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);
            });
    });
});
David Morrissey
@Davin M: Yes, you are absolutely correct. The element should be `display: block`. Your function works, but only as long as `#content` is not hidden. See my answer. Thanks for your help!
A: 

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