23 Dec

Stupid CSS Part 1: Double-Click Handler

This post is part of a series diving into the concepts behind the Stupid CSS demo page, a collection of interactive elements implemented in pure CSS at various levels of absurdity. Check it out on GitHub!

One of my favorite thing to do with code has always been making it do what it’s not meant to do. My first experience with programming was on a graphing calculator, making games that were progressively grosser abuses of a math-teaching device; maturing into other areas of software development, I’ve carried with me that attitude of making the most satisfying interface out of the fewest resources possible. It’s part consideration for not-so-high-end client machines (including my own), part self-imposed challenge to keep things interesting.

A fun way to apply this challenge in web development is to build an interactive site without JavaScript. It’s useful, too: Not only does invoking the JavaScript engine incur a bit more network and processing power to run a webpage, but it also excludes users who can’t run scripts in their browser at all, whether for performance reasons, hardware limitations, or privacy concerns. Giving old, JavaScript-less browsers as close to the full experience as possible is the ultimate form of graceful degradation.

Organic JavaScript-Free Eggs

That got me thinking: What’s something quintessentially JavaScript that I could attempt to recreate in CSS?

An event handler.

In particular, a dblclick (double-click) event handler would be something particularly stupid in CSS. Not only does CSS not have any notion of events—the :hover, :checked, and other pseudo-classes being selectors of state—it also doesn’t have any way to express time intervals, much less to condition behavior on the passage of a certain amount of time. These two ingredients are exactly what make up a double-click event, after all: two clicks occurring on the same element within a threshold time interval.

As it turns out, CSS does—sort of—have a way to describe time intervals. It’s just wrapped up in a neat little CSS3 module called CSS Animations.

<marquee /> Is Dead, Long Live <marquee />

Animations in CSS are a way to describe how CSS properties should change with time. They’re similar to CSS Transitions, which describe how properties transition from one state to another, usually smoothly. The difference is that animations are much more powerful, allowing us to do such things as repeat, pause, or set keyframes on the animation.

For example, a common pattern in web design is to have a button that changes in color when hovered over:

Sprinkling on some transitions can make it feel more slick:

If you’re not into the whole subtlety thing, adding animations can really spice it up:

Many properties in CSS can be animated. Most relevant to us are the properties of position: top, right, bottom, and left.

Accessibility Issues

As you can gather from the last demo above, a terrible animation can make it hard to click what would otherwise be a straightforward button. What if we used that to our advantage?

Here’s the big idea: When the user clicks on a button, we give them a small amount of time—400 milliseconds seemed about right from trial and error—before an intervening animation makes it not just hard but impossible to click the next button in the sequence. If the user manages to click a second time before the 400 milliseconds are up, we congratulate them on performing a double click. Otherwise, they get left in the single-click zone.

Voilà.

The way it works is that the button you see is actually just a viewport into an absurdly long column 4,800,000 pixels high. This giant column is split equally into three zones: the “Click Fast” zone, the “Single-Clicked” zone, and the “Double-Clicked” zone. Each of the Single-Clicked and Double-Clicked zones starts with a 400-pixel tall block, both of which link to the Double-Clicked zone; the rest of these two zones, as well as the entirety of the Click Fast zone, link to the Single-Clicked zone.

With a CSS animation on the top property, this whole column is constantly zooming upwards relative to the button viewport at a rate of 1,333 pixels per second.

The demo starts off with the button viewport showing the top of the Click Fast zone. A single click takes the user to the top of the Single-Clicked zone (at the top of the first blue section). What happens next depends on how quickly the user performs a second click:

  • If the second click comes quickly enough after the first (as on the left side of the recording), it happens on the double-click (blue) element and the user is taken to the top of the Double-Clicked zone.
  • Otherwise (as on the right side of the recording), it happens on the single-click (yellow) element and the user is left at the top of the Single-Clicked zone.

Hash/Tag Problems

The way these blue and yellow elements jump to the tops of their respective target zones takes advantage of the fact that linking to a fragment scrolls to the target even if the target is in an unscrollable parent, which makes it seem like the page is loading new content on the fly.

This is my favorite way to create dynamic content on pages without JavaScript—it’s similar to a technique sometimes termed the checkbox hack, but it works in a much wider array of browsers, including ones from the last century. (Check out my write-up of the File Browser demo for an in-depth look at this effect and how it can be used for pure-CSS dynamic galleries compatible with Internet Explorer 5!)

Unfortunately, this approach carries a few problems that leave the demo with much to be desired:

  • Because we want the label of the current zone to show up in the middle of the button regardless of how far into the zone we’ve scrolled, it needs to be position: fixed. This means that the button needs to be in a predictable location on the page (so the page can’t scroll) and that this location needs to be calculable using CSS units (so the button can’t flow with text).
  • The only content that can be conditionally shown has to be inside the button, so while we can have a neat demo where the button’s label reflects the fact that a double-click took place, we can’t use it to control anything else. So much for a double-click handler.
  • Although Internet Explorer 10 and 11 support all of the features required by the demo, they also treat double clicks on a link as a single click, so it actually takes three clicks within 400 milliseconds to get the “Double-Clicked” label to show up.

The State Machine

Avoiding the checkbox hack also doesn’t do us much good since none of the browsers it excludes (i.e., Internet Explorer 8 and earlier) support CSS Animations anyway, so I went ahead and rebuilt the demo using hidden radio buttons:

This version is a bit simpler to explain. There are three radio buttons set to display: none and four sets of labels: one for “Click Fast,” two for “Single-Clicked,” and one for “Double-Clicked.”

  • When either of the first two radio buttons is selected, the button shows one of the Single-Clicked labels.
  • When the third radio button is selected, the button shows the Double-Clicked label.
  • When none of the radio buttons are selected (on the initial load), the button shows the Click Fast label.

As soon as the selected radio button changes, the newly loaded label gives the user 400 milliseconds to click and toggle the double-click radio button before the label is replaced with a visually identical one that toggles one of the single-click radio buttons. (Since we don’t need things to scroll upwards anymore, I set this animation to animation-timing-function: step-end, which makes the transition instantaneous.) The reason there are two single-click radio buttons is that only a change in which radio button is selected can trigger the animation to run again, so each of the Single-Clicked radio buttons needs to be able to trigger the other. Otherwise, the user would never leave the single-click state after the 400 milliseconds are up.

This whole business can be summarized in one small state machine:

Thus began my quest to build a demo of the most egregious abuses of CSS on a single page.

Tags:

13 Dec

Hello, What’s This?

This is my blog, where I post all sorts of nonsense.

What’s Here?

This blog covers a few different topics, each of which can be followed individually.

  • Updates (RSS), for major site changes and long-term project updates.
  • Territorial Musings (RSS), for one-off analyses of topics to which I’m adjacent.
  • Stupid Hacks (RSS), for technical dives into programming hacks ranging from the useful to the absurd.
  • Shitposts (RSS).

Each post has a color-coded tab to indicate the category it belongs to.

What Happened to ClrHome?

It’s still around!

ClrHome is the site I’ve been running for the last decade or so, alongside ACagliano, who’s taken up the mantle of posting most of the recent updates. It’s been my outlet for posting about calculator projects and programming resources for calculator programmers. Nothing’s changing over there—I’ll still update ClrHome when I make progress on calculator stuff—but I’ve wanted a separate blog for a while where I can post about my broader interests.

This is that blog.