How we’re optimising the performance on the ITV Hub by Windowing our content

Oliver Wilson
ITV Technology
Published in
5 min readFeb 11, 2022

The ITV Hub TV application is a client-side, React, single-page application that runs in the browser. The Hub has to run on a wide range of devices such as any number of Smart TVs, Amazon Fire TV sticks, and a variety of set top boxes. The interesting challenge when programming an application to run on these devices is that you don’t have as much freedom to be as frivolous with the way that certain aspects of the application are implemented, in the same way you might do when programming for a mobile device, for example. This is partly because of the resource limitations that are imposed by some of the most popular platforms that the Hub needs to run on. While less performant than a Smart TV, Smart TV dongles (e.g. Amazon Fire TV) are a hugely popular choice for many people, so the Hub needs to run smoothly on these devices too.

An opportunity to optimise

The Sliders on ITV Hub

On the homepage we’ve got rows of tiles which are called Sliders. Each Slider has 12 tiles. The Sliders sit above and beneath each other and the user can navigate left and right along the Sliders and can also move up and down to neighbouring Sliders.

Let’s say we’ve got 10 Sliders on the homepage. We’ll only really ever have two Sliders visible to the user at any given time. This means that we’ll have 8 Sliders (96 tiles — 8 sliders * 12 tiles) that are rendered to the DOM (Document Object Model) that the user simply cannot see. Now, factor in how often the user scrolls all the way down to the 10th Slider and factor in that once the user has scrolled past a Slider, they rarely go back up to revisit that Slider. That’s a lot of DOM nodes that are in the tree which don’t need to be there on the initial page load, or even for many user journeys.

Why does this matter?
Remember when we mentioned that the Hub has to run on a lot of low-resource devices? Well, it turns out that having an excessive DOM size really is quite bad for consuming memory unnecessarily; this can in some cases cause the app to lag or appear to be slow and even crash the application entirely.

Windowing to the rescue!

Windowing is the practice of not rendering DOM nodes that aren’t in the direct view (or within an acceptable buffer above and below) of whatever the user can see or interact with on the page in its current state. We ensure that only the two Sliders in view, plus one Slider above and one Slider below are written to the DOM at any given time. This means that the maximum number of rendered Sliders on the page is now 4.

We also only render the number of tiles that are on the screen for each Slider, as we only show 5 tiles per Slider on the screen at any given time. Each tile has an image and various event listeners attached to it, so this frees up a significant amount of resources.

This means that instead of showing 120 tiles we now only have 20 tiles across 4 Sliders. We have reduced the total number of tile-related DOM elements by roughly 80%.

This solution is also future-proof as the number of Sliders doesn’t really matter. A content editor could decide that we need to have 20 sliders on the homepage and this solution would still only write 4 sliders to the DOM, while still providing the illusion to the user that there is the specified number of sliders.

Focus

Imagine you’re scrolling through the list of Sliders on the homepage. Each Slider that you scroll past eventually gets removed from the DOM. If you want to then navigate back up, all of the Sliders that you navigated past will need to be re-added. That’s fine and can be done fairly easily with React just based on the index of the currently focused slider, the bit that gets tricky is the focus management.

Connected Television applications are baked with a lot of logic to determine what part of the UI should be in focus. Given the user presses some input (UP, DOWN, LEFT, RIGHT) then the new place that should be focused is calculated based on where the user currently is focused, and what direction they pressed. We need to remember the order of the Sliders when they were added and removed from the DOM so that the application would be able to work out where to move focus to next.

To do this, we built a concept of focus memory in ITV Hub which allows a given focusable item to have an ID assigned to it. With that ID we can attach certain properties that allow the focus algorithm to remember things about that given focusable element. This meant that when it was time to re-add an element to the DOM, the order in which it was able to be navigated to, remained the same.

Is React your friend?
The framework used to build the Hub is React.js which does some lovely heavy-lifting for us in many scenarios. One feature of React allows the previous and current states of UI elements to be compared against each other and we can then perform some fancy logic based on the result. Sometimes however, this can have unintended consequences. React keeps previous renders of elements around in memory for comparisons, which when dealing with many elements can result in quite a lot of memory being consumed.

To make matters worse, most of the memory accumulated on to the JavaScript heap is consolidated much less frequently on Smart TV devices as this is a relatively expensive operation. So, garbage collection doesn’t occur until memory capacity is becoming a real concern. This results in the garbage collection being quite resource intensive and can end up in the application looking “jumpy” as the user moves around the app.

When we’re adding and removing sliders and tiles in the way we’ve detailed above we can short-circuit this unintended memory consumption by saying, “Hey React, forget about these elements!” and thus preventing memory from bloating unnecessarily, relieving or curing the issues listed above.

Summary

Overall this windowing technique yielded great performance improvements for the ITV Hub homepage, so much so that we’ve implemented it in other parts of the ITV Hub to yield similar results. We reduced the consumed runtime memory by just over 20%. The number of DOM nodes also decreased dramatically from 20,000+ in a single session to below 4,000.

--

--