devboell
devboell

Reputation: 1190

Absolutely positioned div inside scroll div moves jumpily

I know there have been similar questions, but so far I have not found a way to fix my problem. This jsfiddle reflects my requirement.

To explain, there are several vertically-stacked view-divs inside a div (which overflows). Each view-div has a label-div which should be displayed in the top-left corner. If the view-divs are scrolled horizontally, the labels should remain in view. When the views are scrolled vertically, the view that is disappearing upwards should have it label pushed down until it completely disappears.

Another requirement which is not in this jsfiddle example is that the view-divs are resizable vertically (I have that code ready, but I thought it too large for this example).

Now, the way I implemented it doesn't work. The labels do not move smoothly enough. I really want them glued to the edge of the container div. Also when you scroll upwards quickly, the labels don't land in the top-left corner.

Some of the other SO questions/answers suggest I should toggle between fixed and absolute positioning, depending on the scroll direction. But I don't think this will work, because the user can drag the scrolling background horizontally and vertically at the same time.

I was hoping the $labels.css({"left" : scrLeft}) approach would simply work, it seemed reasonable. :-) I tried simplifying my example but there too the movement is way too jumpy.

Any ideas? Thanks!!

Upvotes: 1

Views: 1156

Answers (2)

Marc Audet
Marc Audet

Reputation: 46785

Proof of Concept Solution

This is a neat little problem that really forces you to think in about the various overlapping coordinate systems that control the layout geometry.

Here is the jQuery:

//handle scrolling
var $labels = $(".label");

$("#container").on("scroll", function () {

    //horizontal
    var scrLeft = $(this).scrollLeft();
    $labels.css({
        "left": scrLeft
    });

    //vertical
    var scrollTop    = $("#container").scrollTop();
    var containerTop = $('#container').position().top;
    var innerTop = $('#inner_container').position().top;
    var views = $("#inner_container .view");
    var diff = 0;
    var heightAggr = 0;

    if (scrollTop == 0) {
        views.children(".label").css({
            "top": 0
        });
    }

    views.children(".label").removeClass('highlight');
    for (var i = 0; i < views.length; i += 1) {
        var view = $(views[i])
        var viewHeight = view.outerHeight();
        var viewTop = view.offset().top;

        /* This is the key parameter to test... 
           getting this relationship right took a lot of effort,
           everything else was relatively easy... */
        diff = scrollTop - viewTop + innerTop;

        if (diff>0) {
            view.children(".label").addClass('highlight');
            var labelOffset = scrollTop - heightAggr;
            var maxOffset = view.height() 
                            - view.children(".label").outerHeight();

            /* The following pins the label to the bottom of the
               view when view is about to scroll off the screen... */
            var labelPosition = Math.min(labelOffset,maxOffset); 

            view.children(".label").css({
            "top": labelPosition
            });
        } else {
            view.children(".label").css({ /* Clean up when         */
            "top": 0                      /* reverse scrolling ... */
            });
        }
        /* will allow .view with variable heights... */
        heightAggr += viewHeight; 
    }
});

The demo fiddle is at: http://jsfiddle.net/audetwebdesign/HRnCf/

Browser Disclaimer

I only tested this in Firefox...

Upvotes: 1

A. Wolff
A. Wolff

Reputation: 74420

You should use position fixed:

http://jsfiddle.net/mHWJH/1/

var $label1 = $("#label1"),
    offsetTop = $label1.offset().top,
    offsetLeft = $label1.offset().left,
    $label2 = $("#label2"),
    offsetTop2 = $label2.offset().top,
    offsetLeft2 = $label2.offset().left;

$("#container").on("scroll", function() {

    var scrLeft = $(this).scrollLeft();
    $label1.css({
        position:'fixed',
        top:offsetTop,
        left:offsetLeft
    });
     $label2.css({
        position:'fixed',
        top:offsetTop2,
        left:offsetLeft2
    });
});

Upvotes: 0

Related Questions