Shell-like Switch Widget

A recent SweetTooth screenshot

I’ve been tinkering with web development a lot lately as part of my work with SweetTooth, the GNOME Shell extensions repository I’m working on for GNOME.

At the suggestion of Vinicius Depizzol, who’s been doing a wonderful job redesigning the GNOME web sites, I tried my hand at creating a Shell-like switch widget to allow a user to click a button to turn extensions on and off. Using some precious combination of JS and CSS, I’ve finally made a switch widget I think looks nice. Go ahead: grab it, click on it, fling it around, try and break it! It should just feel “right”.

Figuring out whether the widget is activated and setting the appropriate style classes, the drag logic, and figuring out where to put the switch handle are the only responsibilities of the JS code: everything else is done with CSS, using a combination of border-radius, position: absolute, floats. The triple-line on the grab handle is “drawn” with CSS and manipulates the border styling properties to do its bidding. The animations to fade the background-color and slider position are all CSS3 transitions.

There’s still one unsolved problem, which is the flash and fade of the background color when loading the page, and as much as I tried, I couldn’t solve it. It’s a fallout from CSS thinking it has transitioned from one class to another, but in actuality, it’s just jQuery adding a style class after the node has been constructed. Feel free to yell at me I’m being stupid.

The unnecessary details of my adventure

As usual, the hard part in concocting something like this is compatibility. The web truly isn’t meant for something pixel-perfect like this, and it shows. Browsers like IE, where compatibility is usually a pain, weren’t involved at all here (I haven’t bothered to test them — again, yell at me). Through poking people on IRC, I found that OS X has different font metrics for the “ON” and “OFF” labels

(A mini rant to the CSS WG: why can’t I create a new flow root/block formatting context to contain floats by asking for one, instead having to rely on the side effects of other properties?)

Originally, the DOM looked like this image to the left, if you forget about the switch handle. That is, there were two sub elements that were absolutely positioned inside the switch: the switch was hardcoded to a 64px width. Here, everything worked fine and the code was relatively simple.

The problem was that on some browsers and platforms, the text metrics are inconsistent. Unfortunately, the HTML specification gives no guarantee about text measurement, explicitly stating it to be agent-specific. I could deal with this, but ti would be nice if CSS had relative units for glyph widths or JS let you get at some arbitrary measurements of text. <canvas> lets you get at a TextMetrics object, but the only current attribute there is width. For instance, even though I included a web font, WebKit on Mac (which uses CoreGraphics) looked terrible.

Just to make me more irritated, a normal font-weight made it work perfectly, and was almost pixel-perfect to the way bold looked on every other browser/OS. “Go eat a ham”, said Apple. So, obviously, I can’t rely on things which aren’t meant to be relied on.

Welp. So, using some complicated trickery using width: 50%, floats, float containment, I finally got a DOM I wanted, and should hopefully be a bit more flexible. This one should make the box stretch in accordance to font weights and such: float containment means that the parent will expand to the container, and width: 50% means that the children will expand to fill half their container. Perfect.

I simultaneously love and hate web development because of things like this.

Leave a Reply

Your email address will not be published. Required fields are marked *