Prioritizing Visual Stability on the Web

One of the theories behind the benefits of JavaScript single-page applications (and often used with other types of apps as well) is that by relying more heavily on JavaScript, some content can be rendered earlier without waiting for slower content. Not to focus too much on JavaScript, ads, web fonts, and images can have a similar effect. There are solutions to all of these problems, but they’re frequently overlooked.

In the case of JavaScript delaying the rendering of slower content, that can be the result of an external system, complex data queries, or a variety of other reasons. Then, the slower content can be rendered as it comes in without blocking the faster content. The idea is great in theory, but few implementations execute on it well.

In practice, while some content is rendered sooner, it isn’t visually stable. While most of the drawing happens in a matter of seconds, it’s just slow enough to disrupt the experience as elements are nudged around by the newly-rendered content. And that “matter of seconds” is entirely dependent on your viewer’s connection which is wholly unpredictable.

From an accessibility standpoint, the movement is jarring. While CSS supports prefers-reduced-motion, it doesn’t apply to herky-jerky rendering of delayed content. Just because motion isn’t intentional doesn’t mean it can’t have the same effects on viewers.

If you’re reading prose and a paragraph shifts vertically, you lose your place and either have to start re-reading or looking through the content to find your place again. When the content shifts significantly or all of the way out of the view port, it’s even worse. While this isn’t the worst thing that can happen, it’s disruptive enough to undermine the experience.

Links and buttons are another story entirely. If you aim for a link or button, go to click/tap/select it, and then it moves right before you initiate the action, you’ve got a problem. If you’re lucky, you click on a non-interactive space, and it’s just tedious. However, if another link or button shifts into that location, you can end up clicking on the wrong action. Best case scenario, you need the back button. Worst case scenario, you just unintentionally performed a horribly destructive and undo-able action.

At the heart of it all, the underlying problem relates to how browsers draw. If a browser doesn’t know the dimensions of an element, it doesn’t create space for that element. With images, you can solve this by declaring dimensions on the server and using placeholders. With web fonts, it’s tougher, because you either wait to render prose until the font has loaded, or you load the prose, and the browser redraws it once the font is loaded. With ads, you can usually follow a similar approach as with images, but it’s not always that easy.

With JavaScript-rendered content, it’s simply more difficult. If you’re pulling dynamic content from somewhere, you don’t know what the content is until it shows up. So you usually don’t have a clue about the size of the content. You can guess or approximate to mitigate shifting, but it can’t be solved perfectly every time.

With any web site or application, speed is important, but it doesn’t live in a vacuum. If you get someone the content sooner, but it’s moving around so much that they can’t read it or interact with it, is it really worth getting it to them a little faster? And that’s the best case scenario. More frequently, the movement can cause problems reading or interacting with the page. It’s an objective failure, and as developers, we should never let it slide.

This isn’t to say that everything should just be server-rendered static text, but we have to think about the full experience. Whether images, ads, fonts, or JavaScript, when we see herky-jerky rendering, it’s one of those things that should immediately raise red flags and require investigation. Unfortunately, there’s not a single solution that will work everywhere, but there are options to at least reduce the unnecessary movement if not stop it entirely. We just have to make the time implement them.