JavaScript

Roving tabindex for keyboard navigation around JavaScript widgets

Setting the tabindex of the focused element to “0” ensures that if the user tabs away from the widget and then returns, the selected item within the group retains focus. Note that updating the tabindex to “0” requires also updating the previously selected item to tabindex="-1". This technique involves programmatically moving focus in response to key events and updating the tabindex to reflect the currently focused item. To do this:

Bind a key down handler to each element in the group, and when an arrow key is used to move to another element:

  1. programmatically apply focus to the new element,
  2. update the tabindex of the focused element to “0”, and
  3. update the tabindex of the previously focused element to “-1”.

Here’s an example of a WAI-ARIA tree view using this technique.

For a more visual explanation, see the following video by Rob Dodson:

The problems with feature detection

The principle of feature detection is really simple. Before you use a particular API you test if it is actually available. If it is not, you can provide an alternative or fail gracefully. Why is this necessary? Well, unlike HTML and CSS, JavaScript can be very unforgiving. If you would use an API without actually testing for its existence and assume it just works you risk that your script will simply throw an error and die when it tries to call the API.

Take the following example:

Code language: JavaScript

if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(function(pos) {
    alert('You are at: ' + pos.coords.latitude + ', ' + pos.coords.longitude);
  });
}

Before we call the getCurrentPosition() function, we actually check if the Geolocation API is available. This is a pattern we see again and again with feature detection.

If you look carefully you will notice that we don’t actually test if the getCurrentPosition() function is available. We assume it is, because navigator.geolocation exists. But is there actually a guarantee? No.

[…]

Cutting the mustard

There is another principle that has gotten very popular lately. By using some very specific feature tests you can make a distinction between old legacy browsers and modern browsers.

Code language: JavaScript

if ('querySelector' in document
  && 'localStorage' in window
  && 'addEventListener' in window) 
{
  // bootstrap the javascript application
}

In itself it is a perfectly valid way make sure the browser has a certain level of standards support. But at the same time also dangerous, because supporting querySelector, localStorage and addEventListener doesn’t say anything about supporting other standards.

Even if the browser passes the test, you really still need to do proper feature detection for each and every API you are depending on.

[…]

There are features where the whole premise of feature detection just fails horribly. Some browsers ship features that are so broken that they do not work at all. Sometimes it is a bug, and sometimes it is just pure laziness or incompetence. That may sound harsh, but I’m sure you agree with me at the end of this article.

The most benign variants are simply bugs. Everybody ships bugs. And the good browsers quickly fix them. Take for example Opera 18 which did have the API for Web Notifications, but crashed when you tried to use it. Blink, the rendering engine, actually supported the API, but Opera did not have a proper back-end implementation. And unfortunately this feature got enabled by mistake. I reported it and it was fixed in Opera 19. These things happen.

More Proof We Don't Control Our Web Pages

I’ve talked about this before: As web designers, we can’t trust the network. Sure, we have to contend with mobile data “dead zones” and dropped connections as our users move about throughout the day, but there’s a lot more to the network that’s beyond our control.

Here’s a roundup of some of my “favorite” network issue related headlines from the last few years:

Some of these issues can be avoided by serving content over HTTPS, but that still won’t enable you to bypass things like firewall blacklists (which led to the jQuery outage on Sky). Your best bet is to design defensively and make sure your users can still accomplish their goals on your site when some resources are missing or markup is altered.

We can’t control what happens to us in this world, we can only control our reaction to it.

You Can't Detect A Touchscreen

Whatever you may think, it currently isn’t possible to reliably detect whether or not the current device has a touchscreen, from within the browser.

And it may be a long time before you can.

Let me explain why…

Boxed in

The browser environment is a sandbox. Your app’s code can only get at things the browser wants you to, in order to limit the damage a malicious website can cause.

This means that the only information about the system you can get is what the browser exposes to you, in the form of HTML, CSS and JavaScript APIs. To determine if a system supports a certain feature, we can a) see if a certain API is present, or b) see if it actually does the right thing.

Historically, two browser features have been used for “touchscreen detection”: media queries and touch APIs. But these are far from foolproof.

Walk with me.

Device width media queries

Mobiles have small screens and mobiles have touchscreens, so small screen equals touchscreen, right?

[…]

So, so very wrong. Large tablets and touchscreen laptops/desktops have clearly proven this wrong. Plus thousands of older mobile handset models had small non-touch screens. Unfortunately, sites applying the mantra “If it’s a small screen, it’s touch; if it’s a big screen, it’s mouse-driven” are now everywhere, leaving tablet and hybrid users with a rubbish experience.

Touch APIs

[…]

If the browser supports events like touchstart (or other events in the Touch Events API spec) it must be a touchscreen device, right?

[…]

Well, maybe. The problem is, no one ever said that a non-touch device can’t implement touch APIs, or at least have the event handlers in the DOM.

Chrome 24.0 shipped with these APIs always-on, so that they could start supporting touchscreens without having separate “touch” and “non-touch” builds. But loads of developers had already used detects like the example above, so it broke a lot of sites. The Chrome team “fixed” this with an update, which only enables touch APIs if a touch-capable input device is detected on start-up.

So we’re all good, right?

Not quite.

An API for an API

The browser is still quite a long way from the device itself. It only has access to the devices via the operating system, which has it’s own APIs for letting the browser know what devices are connected.

While these APIs appear to be fairly reliable for the most part, we recently came across cases whereby they’d give incorrect results in Chrome on Windows 8… they were reporting presence of a touchscreen (“digitizer”), when no touchscreen was connected.

Firefox also does some kind of similar switching and it appears to fail in the same cases as Chrome, so it looks like it might use the same cues – although I can’t profess to know for sure.

It appears certain settings and services can mess with the results these APIs give. I’ve only seen this in Windows 8 so far, but theoretically it could happen on any operating system.

Some versions of BlackBerry OS have also been known to leave the touch APIs permanently enabled on non-touch devices too.

So it looks like the browser doesn’t know with 100% confidence either. If the browser doesn’t know, how can our app know?

Drawing a blank

Assuming the presence of one of these touch APIs did mean the device had a touchscreen… does that mean that if such a touch API isn’t present then there definitely isn’t a touchscreen?

Of course not. The original iPhone (released in 2007) was the first device to support Touch Events, but touchscreens have been around in one form or another since the 1970s. Even recently, Nokia’s Symbian browser didn’t support touch events until version 8.2 was released last year.

IE 10 offers the (arguably superior) Pointer Events API on touch devices instead of the Touch Events spec, so would return false for the ontouchstart test.

[…]

Neither Safari nor Opera has implemented either touch API in their desktop browsers yet, so they’ll draw a blank on touch devices too.

Without dedicated touch APIs, browsers just emulate mouse events… so there are loads of devices kicking about with touchscreens which you simply can’t detect using this kind of detection.

[…]

You’re doing it wrong

In my opinion, if you’re trying to “detect a touchscreen” in the first place, you’re probably making some dangerous assumptions.

[…]

So what should I do?

For layouts, assume everyone has a touchscreen. Mouse users can use large UI controls much more easily than touch users can use small ones. The same goes for hover states.

For events and interactions, assume anyone may have a touchscreen. Implement keyboard, mouse and touch interactions alongside each other, ensuring none block each other.

Don't go single-page-app too soon, or how GitHub reimplementing navigation in JavaScript loses streaming capability

A few weeks ago I was at Heathrow airport getting a bit of work done before a flight, and I noticed something odd about the performance of GitHub: It was quicker to open links in a new window than simply click them.

[…]

When you load a page, the browser takes a network stream and pipes it to the HTML parser, and the HTML parser is piped to the document. This means the page can render progressively as it’s downloading. The page may be 100k, but it can render useful content after only 20k is received.

This is a great, ancient browser feature, but as developers we often engineer it away. Most load-time performance advice boils down to “show them what you got” - don’t hold back, don’t wait until you have everything before showing the user anything.

GitHub cares about performance so they server-render their pages. However, when navigating within the same tab navigation is entirely reimplemented using JavaScript. Something like…

Code language: JavaScript

// …lots of code to reimplement browser navigation…
const response = await fetch('page-data.inc');
const html = await response.text();
document.querySelector('.content').innerHTML = html;
// …loads more code to reimplement browser navigation…

This breaks the rule, as all of page-data.inc is downloaded before anything is done with it. The server-rendered version doesn’t hoard content this way, it streams, making it faster. For GitHub’s client-side render, a lot of JavaScript was written to make this slow.

I’m just using GitHub as an example here - this anti-pattern is used by almost every single-page-app.

Switching content in the page can have some benefits, especially if you have some heavy scripts, as you can update content without re-evaluating all that JS. But can we do that without losing streaming?

[…]

Newline-delimited JSON

A lot of sites deliver their dynamic updates as JSON. Unfortunately JSON isn’t a streaming-friendly format. There are streaming JSON parsers out there, but they aren’t easy to use.

So instead of delivering a chunk of JSON:

Code language: JavaScript

{
  "Comments": [
    {"author": "Alex", "body": "…"},
    {"author": "Jake", "body": "…"}
  ]
}

…deliver each JSON object on a new line:

Code language: JavaScript

{"author": "Alex", "body": "…"}
{"author": "Jake", "body": "…"}

This is called “newline-delimited JSON” and there’s a sort-of standard for it. Writing a parser for the above is much simpler. In 2017 we’ll be able to express this as a series of composable transform streams:

Code language: JavaScript

const response = await fetch('comments.ndjson');
const comments = response.body
  // From bytes to text:
  .pipeThrough(new TextDecoder())
  // Buffer until newlines:
  .pipeThrough(splitStream('\n'))
  // Parse chunks as JSON:
  .pipeThrough(parseJSON());
 
for await (const comment of comments) {
  // Process each comment and add it to the page:
  // (via whatever template or VDOM you're using)
  addCommentToPage(comment);
}

…where splitStream and parseJSON are reusable transform streams. But in the meantime, for maximum browser compatibility we can hack it on top of XHR.

Again, I’ve built a little demo where you can compare the two, here are the 3g results:

A table of initial load times for the various methods tried: XHR + innerHTML at 2s, Streaming iframe hack at 0.5s, XHR + JSON at 2.1s, and XHR + ND-JSON at 0.6s.

Versus normal JSON, ND-JSON gets content on screen 1.5 seconds sooner, although it isn’t quite as fast as the iframe solution. It has to wait for a complete JSON object before it can create elements, you may run into a lack-of-streaming if your JSON objects are huge.

Don’t go single-page-app too soon

As I mentioned above, GitHub wrote a lot of code to create this performance problem. Reimplementing navigations on the client is hard, and if you’re changing large parts of the page it might not be worth it.

[…]

[A] simple no-JavaScript browser navigation to a server rendered page is roughly as fast. The test page is really simple aside from the comments list, your mileage may vary if you have a lot of complex content repeated between pages (basically, I mean horrible ad scripts), but always test! You might be writing a lot of code for very little benefit, or even making it slower.

How Medium does progressive image loading

A screenshot of a Medium article, with the header image still loading, shown as a very blurry placeholder.

Recently, I was browsing a post on Medium and I spotted a nice image loading effect. First, load a small blurry image, and then transition to the large image. I found it pretty neat and wanted to dissect how it was done.

[…]

I have performed a WebPageTest test against this page on Medium where you can see how it loads too. And if you want to see it by yourself, open Medium’s post in your browser, disable the cache and throttle the response so it takes longer to fetch the images and you can see the effect.

Here is what is going on:

  1. Render a div where the image will be displayed. Medium uses a <div/> with a padding-bottom set to a percentage, which corresponds to the aspect ratio of the image. Thus, they prevent reflows while the images are loaded since everything is rendered in its final position. This has also been referred to as intrinsic placeholders.

  2. Load a tiny version of the image. At the moment, they seem to be requesting small JPEG thumbnails with a very low quality (e.g. 20%). The markup for this small image is returned in the initial HTML as an <img/>, so the browser starts fetching them right away.

  3. Once the image is loaded, it is drawn in a <canvas/>. Then, the image data is taken and passed through a custom blur() function You can see it, a bit scrambled, in the main-base.bundle JS file. This function is similar, though not identical, to StackBlur‘s blur function. At the same time, the main image is requested.

  4. Once the main image is loaded, it is shown and the canvas is hidden.

All the transitions are quite smooth, thanks to the CSS animations applied.