CSDGN Tale of the Lazy Programmer.

23Nov/093

Scrollbar Math and Mechanics

Well I found out very annoyingly there is not very much information on the Internet about the actual mathematics behind a scrollbar. What a scrollbar really, how hard could a scrollbar really be anyway. Surprisingly, pretty hard, if you do not take the time to think about it anyway. Explaining the actual math behind a scrollbar may take a little work so I am going to provide pictures!

Scrollbar

First off lets get some terminology out of the way, here I have a image explaining some of it, I realize not entirely perfect, but its enough for an explanation on scrollbars. The grip rides within the track. The track plus the grip is called a slider, the combined buttons plus the slider compose the scrollbar.

Now that we have that out of the way, now to time to discuss the actual mathematics. There are a few ways to handle the math involved, however most of them require the use of floating point numbers, which can be problematic on some systems without FPUs. Luckily most systems that require the use of scrollbars have fpus. But if it is a problem you can use fixed point decimals just as easily (but there is more work involved in converting to and from on a programmers part).

First off the size of the grip is determined based on the relative size of the window compared to the content that it slides over. This is very simple, it uses the same ratio, but in comparison to the track instead. You must keep in mind that a scrollbar has a minimum and a maximum size however. Luckily at this point, it is very easy deal with the size of the grip by just limiting the maximum size to the size of the track, and the minimum size to some convenient number specific to your implementation, I will use 24.

Note I am only using floats, this code can easily be optimized!
This is for demonstration purposes only!

Here is some psudo-code for your enjoyment:

float trackSize = 180;
float windowSize = 200;
float contentSize = 520;

float minGripSize = 24;
float maxGripSize = trackSize;

//0.4
float gripRatio = windowSize / contentSize;

//72
float gripSize = trackSize * gripRatio;

if(gripSize < minGripSize) {
        gripSize = minGripSize;
}
if(gripSize > maxGripSize) gripSize = maxGripSize;

Now that we have the grip size out of the way, things start to get a bit harder. We must now determine the position of the grip in the track. This is more complicated then it sounds. Because of the minimum and maximum size of the grip, and the fact that the grip is positioned based off its center, not off its top. Otherwise to scroll to the bottom of the content the scrollbar would fly right off the track! The region the scrollbar actually ‘scrolls’ is a subset of the area of the track, not the entirety of the track itself! Its actually trackSize-gripSize. This is the optimized form of trackSize – (gripSize/2 + gripSize/2), since each side of the grip cuts off scrollable area on the track. But since we will work with the top position of the scrollbar, we can ignore this and use the optimized value.

The positioning again requires a ration of the window and content, but in this case we determine how far down the content the window is displaying. I visualize it as a window overlooking the content, which is sorta the idea, and why windows are called windows. Just how you define how the window displays over your content is left up to you. For a list it could be for each item in the list, or for say a web browser it could be for every pixel down from the top of the content it is. In the end it doesn’t matter, the math is the same for any given unit.

Now for more psudo-code, please note I am carrying over variables from the previous set of code:

float windowPosition = 40;

//108
float scrollSize = trackSize - gripSize;

//0.08
float scrollRatio = windowPosition / contentSize;

//Take Note: This is for the top of the grip
//8.64
float gripPosition = scrollSize * scrollRatio;

Now that we have both the Grip Position and the Grip Size, we can easily render it without problems, and because it was limited above in the first bit of code, we do not have to worry about figuring out what if its at the bottom of the scrollbar and also the minimum size, and other special cases, since that has already been covered! It looks fairly simple now that I have the code down right, but just trying figuring it out the first time yourself, its not as simple, especially if your new to working with hand made user interfaces.

There is obviously more to this, for reference the buttons normally move the scroll position up and down by a single unit. Clicking above or below the grip in the track causes the window to move up by the size of the window, or alternatively by the size of the window minus some magic number. This is so it is easier for people to keep track of where they are and the like.

Also keep in mind when a mouse clicks the grip, it rarely clicks the exact top of the grip or the exact center, or the very bottom, remember this when calculating the moved distance for the grip drag. For these cases I supply you with some code to help you out with grip dragging. Generally we take the movement the mouse moved along the axis of dragging. Then we do the reverse of what we did before to get the new window position.

Even more psudo-code:

//This is the distance between the last mouse position and the new one.
//This only matters along the axis of the slider.
float mouseDelta = newMousePosition - oldMousePosition;

float newGripPosition = gripPosition + mouseDelta;

//Limit it so it is not out of bounds
if(newGripPosition < 0) newGripPosition = 0;
if(newGripPosition > scrollSize) newGripPosition = scrollSize;

//Just reverse the algorithms we did before to get back
//to the windowPosition
float newScrollRatio = newGripPosition / scrollSize;
windowPosition = newScrollRatio * contentSize;

I hope you enjoyed my first informative posting here on CSDGN! Feel free to comment.