2
\$\begingroup\$

The problem


I'm trying to position a chosen element so that it's always horizontally aligned against the right-hand side of the page, and vertically aligned with the center of the web browser's vertical inner scrollbar, i.e. the part of the scrollbar that moves as you scroll up and down the page. The "handlebar", if you will.

I've been tweaking this code for so long that it's starting to do my head in, so would like a sanity check, i.e. am I going about this the right way?

The key part of this function is the line beginning offset = scroll_position ...

The code

barFollower = function (e) {
  var target = $(e.data.target),
    scrollbar_button_height = 20,
    window_height = $(window).height(),
    window_height_adj = window_height - (scrollbar_button_height * 2),
    scroll_position = $(window).scrollTop(),
    body_height = $('body').height(),

  offset = scroll_position // The top of window
    + ((scroll_position / body_height) * window_height_adj) // The position of the top of scrollbar
    + (((window_height_adj / body_height) * window_height_adj) / 2) // Half the height of the scrollbar
    + scrollbar_button_height // The scrollbar button height (depends on browser chrome, unfortunately) - see notes below
    - element_offset; // offset for chosen 'position' based on element height - see notes below

  /* Some catches for when element height might mean it would get positioned outside the window */
  if (body_height < window_height) { // If there's no scrollbar.
    offset = (window_height / 2) + (target.height() / 2); // Position halfway down the window
  } else if (offset < scroll_position) { // Top bounds
    offset = scroll_position; // Fix it to the top of the window
  } else if (offset > scroll_position + window_height - target.height()) { // Bottom bounds
    offset = scroll_position + window_height - target.height(); // Fix it to the bottom of the window
  }
  target.css('top', offset);
};

Notes

Triggering

This function is triggered once on page load, and subsequently on every window scroll event.

Scrollbar button size

The tricky part here is that I can't find a way to determine the height of the scrollbar 'up' and 'down' buttons, so I have made an assumption (20px), although this will potentially vary from browser to browser, and depends the browser chrome. If anyone knows a way to accurately determine scrollbar button sizes, please let me know.

The element_offset variable

The element_offset is set outside this function, and can have one of three values to offset the element so that either the top, middle, or bottom of the element align with the middle of the vertical scrollbar. How it is set is not relevant at this point, so I've left it out to keep things simple.

\$\endgroup\$
  • \$\begingroup\$ Is this a typo: settings.scrollbar_button_height vs. just scrollbar_button_height? \$\endgroup\$ – Paul Martel Feb 15 '12 at 5:44
  • \$\begingroup\$ @PaulMartel: Yes - sorry - I've fixed it now! \$\endgroup\$ – Daniel Willis Feb 16 '12 at 19:55
  • \$\begingroup\$ Is there css something like #myelement { position: fixed; right: 0;}? \$\endgroup\$ – James Khoury Feb 17 '12 at 2:07
1
\$\begingroup\$

I got mixed results from researching whether "scroll bar" properly refers to the movable part or the larger range. I had originally thought the latter, and had called the movable part a "thumb" -- that may be a java-ism. I opted to use the OP's original definition (movable part) for "scroll bar" -- for the "handlebar", if you prefer. And I used "scroll bar area" for the larger range -- the "scrollbar", if you prefer. Sorry for any confusion.

Refactoring common expressions exposed some quirks in the OP. The original formula for the no-scrollbar case used + target.height() in place of the - target.height() used everywhere else, but taller targets should always get smaller offsets, yes?

Also, that case strangely ignored element_offset -- wouldn't that cause target to suddenly jump when the body or window is resized to make the scroll-bar appear/disappear? I brought element_offset and (largely as a result) bounds testing into this case.

The window_height_adj value was an odd name for the scroll area height. It was being mistaken for the simpler window_height in one calculation. I factored it into the more useful scroll_scale_factor to avoid such confusion.

Scroll position plays two roles:

  • a baseline for the mid-window position calculation that gets scaled down to produce the relative mid-scrollbar position, and

  • a baseline for the final offset.

Calculating only relative offsets until the very end more clearly separates out this second role and simplifies the bounds testing.

barFollower = function (e) {
    var target = $(e.data.target),
    scrollbar_button_height = 20, // (depends on browser chrome, unfortunately)
    window_height = $(window).height(),
    max_target_offset = window_height - target.height(),
    scroll_position = $(window).scrollTop(),
    body_height = $('body').height(),
    // ratio of full body height to full height of scroll area 
    // which does not include the buttons.
    scroll_scale_factor = body_height / (window_height - (scrollbar_button_height * 2)),
    offset = 0;

    if (body_height <= window_height) { // If there's no scrollbar.
        offset = max_target_offset / 2 - element_offset; // Position halfway down the window
    } else {
        offset = scrollbar_button_height 
            + (scroll_position + window_height/2) / scroll_scale_factor; // mid-window position reduced to scroll area scale
            - element_offset; // offset for chosen 'position' based on element height - see notes below
    }

    /* Some catches for when element height might mean it would get positioned outside the window */
    if (offset < 0) { // Top bounds
        offset = 0; // Fix it to the top of the window
    } else if (offset > max_target_offset) { // Bottom bounds
        offset = max_target_offset; // Fix it to the bottom of the window
    }
    target.css('top', scroll_position + offset);
};
\$\endgroup\$
2
\$\begingroup\$

What is wrong with:

<body>
    ...
    <div id="myfloatingElement">This is the element</div>
</body>

css:

#myfloatingElement
{
    position: fixed;
    right: 0;
    top: 50%;
}
\$\endgroup\$
  • \$\begingroup\$ So that will fix the element to the middle of the page as you scroll, which is fine, but that's not what I'm trying to do. I've just re-read my first paragraph, and perhaps it's not clear, but what I'm trying to do is position the element against the center of the inner scrollbar, i.e. the part of the scrollbar that moves as you scroll up and down the page. I'll edit the first paragraph to make it more clear - sorry for the ambiguity! \$\endgroup\$ – Daniel Willis Feb 19 '12 at 19:55
  • \$\begingroup\$ You mean the grey "handlebar" bit inside the scrollbar? \$\endgroup\$ – James Khoury Feb 20 '12 at 0:11
  • \$\begingroup\$ Yes - precisely. "Handlebar" works very well. Thank you! \$\endgroup\$ – Daniel Willis Feb 20 '12 at 2:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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