Saturday, January 30, 2016

Scrollbar Merry-Go-Round

I didn't have too much time yesterday to work on this, but what I did manage to get done was the drawing code, so I now have a class that draws its own scrollbars correctly.

Meanwhile, I noticed that my state class was having to guess whether to leave space for scrollbars or not.  I opened up the scrollbar class to make this a gettable property, which helped, but there was a still a snag...

The Scrollbar Merry-Go-Round...of doom

I guess I'm not surprised to see this in my own basic code, because I've seen this for years, and still do, in various (otherwise professional) web pages.  Not often (ever?) in a scrolling context, but in a CSS :hover selector.  Once triggered, the merry-go-round functions automatically without moving the mouse pointer.  What happens is this:

  1. You mouse over an element, near its edge.
  2. The :hover effect causes a change in proportions in the element (e.g., the text goes bold, so the element has to get wider, or rewraps and gets taller, etc.).
  3. Because the element's edges move, it is now is no longer under the mouse pointer.
  4. The :hover selector no longer applies.
  5. The element returns to its original dimensions.
  6. The mouse pointer is suddenly hovering over it again, like it was in the first step, because we haven't moved it.
  7. The browser processes the above steps repeatedly in the most jittery, jarring rapid succession.
It's perhaps not clear how to solve such a problem at the CSS level, other than to make your selector style not change the size of the element in any way.  The only other option would be a bunch of JavaScript or browser hacking so that, until the mouse pointer actually moves, you cannot return to a non-hovering state.  (I'm sure that fixing this at the browser level would break lots of pages, e.g. games that rely on the hover effect even when the mouse cursor is not moving, because elements might be moving on their own.)

Unfortunately, in my case, neither of these is the answer that I'm looking for.

In my case, I have some content that I would like to take up the full height of the window, unless the content does not fit the width, in which case I would like the content to take up the full height minus the scrollbar thickness.  Sounds simple enough.  However, in practice, there is a set of window sizes that lead to the Scrollbar Merry-Go-Round.  It's not quite as automatic as the CSS case above, but it's visually at least as bad, because of how big scrollbars normally are.

If you're simplistic enough in the logic in the state class, and just have something like this in onResize:

content.setGraphicSize( 0, Lib.current.stage.stageHeight - (_scrollable.horizontalScrollbarIsVisible?_scrollbarThickness:0) );

...then you will find that, depending on whether you are resizing up or down, and/or the results of the last resize, your visual result will alternate between having scrollbars and not having them, and in both cases not look very good.

This is because we're basing our new content size on whether scrollbars are needed for the current content size.

Even if you try to make it slightly smarter (if that much more inefficient) and call onResize again if and only if the need for scrollbars changed after you resized the content, it's at least as visually quirky.

So what can be done?  On a small display, the first solution for the CSS problem (don't change the size) means that I will have a scrollbar's thickness taking up a chunk of the window whether I need it to or not (and this gets worse when the scrollable area is not the whole window.)  The second solution doesn't apply because unlike the CSS merry-go-round, my scrollbar problem requires mouse movement to show its full glitchy glory.

I had written this comment detailing my thoughts, which are hopefully possible to implement:

The parent state shouldn't have to worry about whether or not a scrollbar is showing, or even about resizing content.  After all, we have cameras and they can zoom.

We have to calculate the content's ratio and find the point at which it will fit with unneeded scrollbars (or each one independently?  even more complex...).  We also need the point at which it will fit without scrollbars.  In between these two points is the merry-go-round, and instead of this, we should just take away the scrollbars (or a given scrollbar?) and use up part of its space, but not enough to require scrollbars again.

Time to figure out some calculations...

Although, this solution seems to assume my particular context, that I want the content to take up as much vertical space as possible, and no vertical scrollbar should ever show.  Probably a more normal context is the other way around.  And then there's a third context, where the content would not change, and you might have any combination of scrollbars but no merry-go-round.  Perhaps the camera subclass can have these three as options.

No comments:

Post a Comment