Search code examples
javascriptscrolldimensionsclip

Scroll a div when hovering over a clipped item


This is a tricky problem to describe...I have a fixed height <div> on my page with a bunch of content items inside. Each item is a square <div>, floated left, so they fill in the area left to right and top to bottom. If there are too many items to fit in the view, then the content area will scroll (overflow-y: auto).

Forgive the ASCII art:

---------------------
| [ 1 ] [ 2 ] [ 3 ] |
| [ 4 ] [ 5 ] [ 6 ] |
| [ 7 ] [ 8 ] [ 9 ] |
---------------------

The thing is, the items inside don't fit perfectly in the area without the bottom row getting clipped. So what I want to do is somehow automatically scroll the view down when the user hovers over an item that's clipped off the bottom of the content area.

I can't figure out how I would go about determining whether a particular item is clipped or not.

Is this totally wacky? Or is there a logical method for doing this?


Solution

  • After first experimenting with @Geuis' method, I realized I was solving the wrong problem, because the last row of items isn't necessarily the same row that's getting clipped.

    For example, say I have 12 items in a 3x4 grid: 3 items per row, 4 rows in total. Then say my container is only tall enough to show the first two rows and the top half of the third row. The last row is the fourth row, but the row that's getting clipped is the third row, assuming I'm scrolled to the top. Or, what if I scroll to the bottom of the container? Now the second row is getting clipped, and off the top rather than the bottom.

    So I realized that rather than looking at the rows, I need to look at the particular item that's being hovered over and determine if that single item is being shown in full or not. If it is, do nothing; if it's not, scroll up or down depending on which end of the item is clipped.

    Here's what I came up with. On hover:

    var containerHeight = $container.height(),
        itemHeight = $(this).height(),
        itemOffset = Math.floor($(this).position().top),
        itemVisible = containerHeight - itemOffset,
        itemClip = itemHeight - itemVisible;
    
    if (itemClip > 0){
        $container.scrollTo('+=' + itemClip, 600);
    } else if (itemOffset < 0){
        $container.scrollTo('-=' + Math.abs(itemOffset), 600);
    }
    

    ($container is defined elsewhere in my script as the containing div)

    Line by line:

    1. Get the height of the container that holds all the items
    2. Get the height of the item being hovered
    3. Get the distance from the top of the container to the top of the hovered item
    4. Subtract the distance (line 3) from the height of the container (line 1)
    5. Take the difference from line 4 and subtract it from the height of the item being hovered (line 2)

    Now, this tells you two things:

    • If the result of line 3 is negative, the item is being clipped by that many pixels past the top of the container
    • If the result of line 5 is positive, the item is being clipped by that many pixels past the bottom of the container

    Knowing this, you can then scroll the container in the correct direction and by the correct distance to reveal the whole item.

    The actual scrolling itself requires the jQuery ScrollTo plugin in order to scroll up or down x number of pixels from the current scroll position (not from the top of the container, which is what jQuery's built-in .scrollTop() function does).

    (ScrollTo does not take a negative number as a value, so in order to scroll up, you need to get the absolute value of itemOffset - hence Math.abs(itemOffset)).