Scrollbar Mechanics

I wrote this a long time ago for my own benefit, so forgive me for any minor errors.

Scrollbar Mathematics and Mechanics. I also sometimes just call it Scrollbar Math or the Math behind your Scrollbar. I had an article like this on my last site, I had received a great deal of positive feedback from it. So I decided to write it again. I will be discussing how the internal mechanics of a scrollbar work and include sample code that perform those functions.

Scrollbars are the under appreciated work horse of you User Interface.

The first time I had to write a scrollbar for something I was like "Whoa! This is.. This is hard!" You would think something that is part of our basic user interface these days would be incredibly simple! Truth be told, it really is simple! But it is only simple after you know how and why it works. If you are writing a scrollbar for the first time in your UI, they can be annoying, buggy and really hard!

First off, let me show you a few basic images. This will be the outline for my scrollbar discussion.

The Anatomy

diag-scrollbar-0diag-scrollbar-1

Anatomy of a basic scrolling setup

I am going to try and describe a scrollbar on simplest terms. These first two parts are not part of the scrollbar, but are integral to understanding the mechanics behind how one works.

Window - Also called a viewport. The window is what we see, it is what we look through to view the content. This is why it is called a window in well Windows and many other operating systems today.

Content - This is also called a document. We see this through the window. We usually only see part of the content if it is very large, this is where a scrollbar comes into play. You can think of the content moving under the window or the window moving over the content. However I think the content moving under the window is less confusing, as the window does not appear to move to me when I use a scrollbar.

Slider - This is what we usually think of when someone says scrollbar, but a scrollbar almost always has buttons as well. If it does not have buttons it may still be considered a scrollbar if it is used to move content under a window.

Button - A button, this is usually used to move it up or down a single unit.

Scrollbar - The combination and what we want in the end.

Anatomy of a scrollbar

Button - Again a button, this is usually used to move it up or down a single unit, depending on which side it is on.

Grip - This is what we grab onto and pull up and down to make the content move under the window or adversely the window over the content.

Track - This is what the grip rides in, it is very simply the empty space around the grip. Clicking in the track above or below the grip usually moves the grip up or down a page. A page being usually the size of the window, and conversely because of such, the size of the scrollbar.

Wait, wait, I am getting confused. Does the content move under the window, or the window move over the content?

Good question, and in practice, it doesn't matter really. Whatever helps you visualize it is what happens. Just remember that a window in the interface doesn't usually move with the scrollbar. I personally think the window moves over the content, but stays still in the UI. That just helps me, and while I like to believe that was the original idea for it, I really don't know. Some people I know say that no, the scrollbar moves the content under the window, like on a conveyor belt. Indeed, I know that this makes far more sense, but it is harder for me personally to think of it that way. So whatever helps you the most to understand it, is what it does.

The Mechanics

Time for the mechanics part of things. This is probably what you are here for.

I half want to just paste a whole bunch of code and say figure it out yourself. But I know better!

I will walk you through the code a step at a time. Allowing you time to understand the concepts. All code will be simplified as much as possible for your reading and understanding pleasure. Lets get started with the simplest bit of the code.

Displaying a scrollbar

Determining grip size

  1 //Determine how large the content is, and how big our window is
  2 float contentSize = 400; //400 unknown units
  3 float windowSize = 100; //100 unknown units
  4  
  5 //Determine how large our track is
  6 float trackSize = 80; //80 unknown units
  7  
  8 //Divide the window size by the content size to get a ratio
  9 // ratio is 100 / 400 which is 1 / 4 which is 0.25
 10 float windowContentRatio = windowSize / contentSize;
 11  
 12 //Multiply the trackSize by the ratio to determine how large our grip will be
 13 //grip size is 80 * 0.25 which is 20
 14 float gripSize = trackSize * windowContentRatio;
 15 

Before we go any further I want to flesh out a few things. First of all I am using floats here, you can use doubles, you can use integers with fixed point division, it all comes down to the same thing. You could even use an alternative method using only division, using contentSize/windowSize and then dividing the trackSize by that ratio. The implementation of how you get it doesn't matter to much, as long as it is based on a ratio of the window and content size.

Also note that I use the track size, this is important because the track may be of any size, and indeed could be much larger or shorter then the window or its content. Not to mention we may or may not have buttons that take up space in the UI. I am going to be sure I have no magic numbers in my code.

Limiting minimal grip size

But the code now has a problem, what if the content is so massive and the window is so small that our grip is microscopic or invisible because its something like 0.00001 units in size? What then? Well we limit the minimal size to something we can always grab onto.

  1 //The minimal size of our grip
  2 float minimalGripSize = 20;
  3  
  4 //If the grip is too small, set it so that it is at our
  5 //predetermined minimal size!
  6 if (gripSize < minimalGripSize) {
  7 	gripSize = minimalGripSize;
  8 }
  9 

Limiting maximum grip size

Alright, now we don't have to worry about minimal size of the scrollbar, it will always be at least that large.

But wait, there is another problem! What, yes! What if our content is smaller then our window, our grip will explode off our track, we don't want that! We have to limit the maximum size of the grip. However we probably don't need a new variable for this one!

  1 //The maximum size of our grip
  2 float maximumGripSize = trackSize;
  3  
  4 //If the grip is too large, set it so that it is at our maximum size!
  5 if (gripSize > maximumGripSize) {
  6 	gripSize = maximumGripSize;
  7 }
  8 

You can't just set it to the track size... can you? It turns out, you can.

Pitfall- What if your track has margins?

Alright, yes I realize sometimes your specific implementation might have margins on each side of the track, however, those really shouldn't be counted in the size of the track, since our trackSize is determined to be the full area the grip can scroll in. So actually you can set it to the track size! It is perfectly safe! So if you have a super special implementation with margins, just be sure to subtract that margin area from your trackSize first.

Determining grip position

Time for grip positioning. This is very similar to the determining the size, but has a little bit more to it!

  1 //Determine the distance that the window can scroll over
  2 float windowScrollAreaSize = contentSize - windowSize; //400 - 100 is 300
  3 

This bit of code is very important. It is actually a common pitfall in and of itself. The maximum area your window can scroll over is not equal to the length of the content size, but rather of the content size minus the size of the window. It might be a more if you want your window to have white space at the bottom, but it should never be less then this!

  1 //The position of our window in accordance to its top on the content.
  2 //The top of the window over the content.
  3 float windowPosition = 100; //100 units
  4  
  5 //The ratio of the window to the scrollable area.
  6 //100 / 300 is 1 / 3 which is 0.333_
  7 float windowPositionRatio = windowPosition / windowScrollAreaSize;
  8  
  9 //Just like we did for the window
 10 //we do this to keep the grip from flying off from the end of the track.
 11 //80 - 20 which is 60
 12 float trackScrollAreaSize = trackSize - gripSize;
 13  
 14 //Determine the location by multiplying the ratio
 15 //60 * 0.333_ is 20
 16 float gripPositionOnTrack = trackScrollAreaSize * windowPositionRatio;
 17 

Just like that we have the grips location. Now as we move the grip from its top, we need to subtract a single size of the grip from the total area it can move to keep it from flying off the end of the track. This also makes it so if the grip is as large as the track, the scrollable area will be equal to zero. Meaning the final position will be zero times the ratio. Anything times zero is zero, so we do not have to bother with checking for that later.

You now have all the information you need to display the grip on the scrollbar. Its location on the scrollbar and its size will make it very simple to display it.

Pitfall- Without the length adjustment.

Many people who write a scrollbar, they initially forget to subtract the windowSize from the contentSize, forget to subtract the gripSize from the trackSize, or both. This pitfall note is just here to remind you to not to forget those.

Scrolling the content

Displaying was the easy part, and many of you probably already figured out how to do that part. The hard part is actually dragging and scrolling. First things first, it all depends on the mouse control.

When you have determined the mouse has gripped your scrollbars grip you will use code similar to the following to update the position of the window or content

  1 //psudo-code, you will have to fill in the missing variables yourself from
  2 //your mouse controller! This only matters along the axis of the slider.
  3 float mousePositionDelta = newMousePosition - oldMousePosition;
  4  
  5 //Determine the new location of the grip
  6 float newGripPosition = gripPositionOnTrack + mousePositionDelta;
  7  
  8 //Limit the grip so that it is not flying off the track
  9 if (newGripPosition < 0) {
 10 	newGripPosition = 0;
 11 }
 12 if (newGripPosition > trackScrollAreaSize) {
 13 	newGripPosition = trackScrollAreaSize;
 14 }
 15  
 16 //Now we use the same algorithm we used in the windowPositionRatio,
 17 //for the grip and track.
 18 float newGripPositionRatio = newGripPosition / trackScrollAreaSize;
 19  
 20 //and we apply it in the same way as we did to determine the grips
 21 //location, but to the window instead.
 22 windowPosition = newGripPositionRatio * windowScrollAreaSize;
 23 

This is the basic code used to scroll the window or content. It is almost exactly the same as the code we used to determine the scrollbars position earlier, but in reverse. Such that we are using the scrollbars location and scrollable area to determine the windows location.

You must keep in mind that determining the mousePositionDelta is key in determining things. Keep in mind that we want the mouses location, not the location of the scrollbar. People almost never click at the top, middle or bottom of a scrollbar.

Pitfall- Scrolling 1 unit/pixel makes the window move more then a single page!

Sometimes your scrollbar is small, no really small, and sometimes your content is really really big. Unfortunately there is no real way around this issue, its a failing of scrollbars themselves, we usually never notice because our screens are large enough that we never approach this limit. However over the years clever designers and programmers have found ways around it.

First of all there is the buttons, these allow you to scroll 1 unit down. That is not one unit of the scroll bar, but one unit in the content/window. This unit may or may not be the same as the unit that defines the length of the window or scrollbar itself. In the case of pixels, it usually isn't, in the case of a list of items, it usually is.

Then there is the track click, clicking the track above or below the grip usually moves the content or window by one page (size of the window).

Scrolling by clicks

I don't think it needs much explanation, but pressing the buttons on your scroll bar or clicking in the track cause your scrollbar to move and thus the content as well. These also usually cover the up and down arrow keys if they are associated with the scrollbar.

When clicking a button the window moves rather then the scrollbar, however the scrollbar moves as a result of the window moving. The variable aSingleUnit here, can mean any one unit, the unit is undetermined and is specific to the implementation.

  1 float aSingleUnit = 10; //10 unknown units
  2  
  3 //On pressing the Up Button (Up Directional Arrow)
  4 windowPosition = windowPosition - aSingleUnit;
  5 if (windowPosition < 0) {
  6 	windowPosition = 0;
  7 }
  8  
  9 //Update the Scrollbar
 10 updateScrollbar();
 11 

The down arrow is very similar.

  1 //On pressing the Down Button (Down Directional Arrow)
  2 windowPosition = windowPosition + aSingleUnit;
  3 if (windowPosition > windowScrollAreaSize) {
  4 	windowPosition = windowScrollAreaSize;
  5 }
  6  
  7 //Update the Scrollbar
  8 updateScrollbar();
  9 

As such the buttons are a very simple addition to a scrollbar, just keep in mind it moves the window directly, rather then the scrollbar. It is similar for clicking in the scrollbars track. These also usually cover the page up and page down keys if they are associated with the scrollbar.

Also like the buttons, clicking in the track moves the window rather then the scrollbar, however the scrollbar moves as a result of the window moving. However in this case, the window usually moves an entire page/screen/window instead of a single unit.

  1 //On clicking above the grip in the track (Page Up)
  2 windowPosition = windowPosition - windowSize;
  3 if (windowPosition < 0) {
  4 	windowPosition = 0;
  5 }
  6  
  7 //Update the Scrollbar
  8 updateScrollbar();
  9 

Click below the grip is very similar.

  1 //On clicking below the grip in the track (Page Down)
  2 windowPosition = windowPosition + windowSize;
  3 if (windowPosition > windowScrollAreaSize) {
  4 	windowPosition = windowScrollAreaSize;
  5 }
  6  
  7 //Update the Scrollbar
  8 updateScrollbar();
  9 

More on scrolling

That is it on the actual implementation of the scrollbar itself. There are a few other notes on writing a scrollbar that you should keep in mind when you go to design your own.

Do you know your content/platform?

If you do know what your content is, adjust your scrollbar to be smarter about its choices in regards to it. For example if you know if your writing a scrollbar on an embedded machine, keep in mind that it might have a small screen size and thus you can use a smaller minimal size, because a smaller resolution means everything will appear larger. Lower end screens also have larger pixels then higher end ones, meaning that they will be larger still.

If you know what platform it is for, you can test how scrollbars on that system work when designing it, meaning you can implement special characteristics to it so that it works and feels the same as others on that platform.

Scrollbars should be seen, not heard.

What I mean is, that they should be there, they should perform an under appreciated task and should not make the user complain or even notice them. This is very important.

Buttons, Paging, Scrollwheel, and so on.

Button Clicking - Buttons on your scrollbar cause it to go up or down by a certain amount. This amount usually is determined by the content, if you don't know the content, make it settable! Nothing is more annoying then a useless feature.

Scroll Wheel - Many systems these days have scroll wheels, if you think your platform might have one of these devices, take it into account! Even if you do not perform the scrollwheel interrupt yourself, leave some kind of function, method, dial or switch someone can use to make it usable with the scrollwheel. Your scrollbar should be natural in its use. Scroll wheels usually scroll 3 or so button clicks at a time, however this is not a rule, and if in doubt, make it settable.

Track Clicking - When you click the track you click it in one of two places. You either click it above or below the grip. I seem like a broken record, as I have mentioned track clicking a few times already. But it is very important. Many games or other places with custom scrollbars leave it completely unimplemented, which can be very counter-intuitive to the end user. Clicking in the track will usually cause the content or window to move up or down an entire page (screen size). Sometimes they move a little less, so you can see a little of where you were before.

F.A.Q.

You keep saying Page, like it should be defined somewhere or we should know what it is. What are you talking about?

First look down at your keyboard, now look a bit right, no further right. There between the number pad and the main keyboard are the arrow keys and some other misc keys you may or may not have ever used before. On the right side (usually) there are keys called Page Up and Page Down or PgUp or PgDn.

Now a long long time ago these were actually used to move up or down an entire page in a word processor. As far as I am aware these keys did not exist on type writers and are new thing with computers.

However many people soon discovered these keys were far more useful to move up and down a single 'screen' rather then an entire page, and made reading large documents much easier. There is where the word page got associated with moving up and down a screen. However saying page is easier then saying screen, and less ambiguous when used as a unit of measure then 'screen' would be, as a computer has a screen which may or may not actually be an entire screen.

For the purposes of this article, a page is a unit of measure equal to the size of the window itself. I don't say window because that would could get even more confusing then saying page, as it is used in many other contexts as well.

This is only for vertical scrollbars, what about horizontal scrollbars?

Those are more or less identical to their vertical siblings. The values for min, max, position and so forth just come from the other axis. In the case of horizontal ones, the left side of the screen is usually where the origin is for the x axis.