Using the Paint Timing API

Avatar of Jeremy Wagner
Jeremy Wagner on (Updated on )

It’s a great time to be a web performance aficionado, and the arrival of the Paint Timing API in Chrome 60 is proof positive of that fact. The Paint Timing API is yet another addition to the burgeoning Performance API, but instead of capturing page and resource timings, this new and experimental API allows you to capture metrics on when a page begins painting.

If you haven’t experimented with any of the various performance APIs, it may help if you brush up a bit on them, as the syntax of this API has much in common with those APIs (particularly the Resource Timing API). That said, you can read on and get something out of this article even if you don’t. Before we dive in, however, let’s talk about painting and the specific timings this API collects.

Why do we need an API for measuring paint times?

If you’re reading this, you’re likely familiar with what painting is. If not, it’s a simple concept to grasp: Painting is any activity by the browser that involves drawing pixels to the browser window. It’s a crucial part of the rendering pipeline. When we talk about painting in performance parlance, we’re often referring to the time at which the browser begins to paint a page as it loads. This moment is appropriately called “time to first paint”.

Why is this metric important to know? Because it signifies to us the earliest possible point at which something appears after a user requests a page. A lot goes on as a page is loading, but one thing we know is that the sooner we can get something to appear for the user, the sooner they’ll realize that something is happening. Sort of like your LDL cholesterol, most performance-oriented goals involve lowering your numbers. Until you know what your numbers are to begin with, though, reaching those goals can be an exercise in futility.

Thankfully, this is where the Paint Timing API can help us out. This API allows you to capture how fast a page is painting for your site’s visitors using JavaScript. Synthetic testing in programs such as Lighthouse or sitespeed.io is great in that it gives us a baseline to work with for improving the performance of sites in our care, but all of that testing is in a vacuum. It doesn’t tell you how your site is performing for those who actually use it.

Compared to similar performance APIs, the Paint Timing API is much more simplified. It provides us with only two metrics:

first-paint: This is likely what you think it is. The point at which the browser has painted the first pixel on the page. It may look something like this:

What `first-paint` might look like.

first-contentful-paint: This is a bit different than first-paint in that it captures the time at which the first bit of content is painted, be it text, an image, or whatever isn’t some variation of non-contentful styling. That scenario may look something like this:

What a `first-contentful-paint` event might look like.

It’s important to point out that these two points in time may not always be so distinct from one another. Depending on the client-side architecture of a given website, first-paint and first-contentful-paint metrics may not differ. Where faster and lighter web experiences are concerned, they’ll often be nearly (or even exactly) the same thing. On larger sites where client-side architecture involves a lot of assets (and/or when connections are slower), these two metrics may occur further apart.

In any case, let’s get an eye on how to use this API, which has landed in Chrome 60.

A straightforward use case

There are a couple ways you can use this API. The easiest way is to attach the code to an event that occurs some time after the first paint. The reason you might want to attach this to an event instead of running it immediately is so the metrics are actually available when you attempt to pull them from the API. Take this code for example:

if("performance" in window){
  window.addEventListener("load", ()=>{
    let paintMetrics = performance.getEntriesByType("paint");

    if(paintMetrics !== undefined && paintMetrics.length > 0){
      paintMetrics.forEach((paintMetric)=>{
        console.log(`${paintMetric.name}: ${paintMetric.startTime}`);
      });
    }
  });
}

This code does the following:

  1. We do a simple check to see if the performance object is in the window object. This prevents any of our code from running if performance is unavailable.
  2. We attach code using addEventListener to the window object’s load event, which will fire when the page and its assets are fully loaded.
  3. In the load event code, we use the performance object’s getEntriesByType method to retrieve all event types of "paint" to a variable called paintMetrics.
  4. Because only Chrome 60 (and later) currently implements the paint timing API, we need to check if any entries were returned. To do this, we check if paintMetrics is undefined and if its length is greater than 0.
  5. If we’ve made it past those checks, we then output the name of the metric and its start time to the console, which will look something like this:
Paint timings exposed in the console.

The timings you see in the console screenshot above are in milliseconds. From here, you can send these metrics someplace to be stored and analyzed for later.

This works great and all, but what if we want to have access to these metrics as soon as the browser collects them? For that, we’ll need PerformanceObserver.

Capturing paint metrics with PerformanceObserver

If you absolutely, positively need to access timings as soon as they’re available in the browser, you can use PerformanceObserver. Using PerformanceObserver can be tricky, especially if you want to make sure you’re not breaking behavior for browsers that don’t support it, or if browsers do support it, but don’t support "paint" events. This latter scenario is pertinent to our efforts here because polling for unsupported events can throw a TypeError.

Because PerformanceObserver gathers metrics and logs them asynchronously, our best bet is to use a promise, which helps us handle async’y stuff without the callback hell of yesteryear. Take this code, for example:

if("PerformanceObserver" in window){
  let observerPromise = new Promise((resolve, reject)=>{
    let observer = new PerformanceObserver((list)=>{
      resolve(list);
    });

    observer.observe({
      entryTypes: ["paint"]
    });
  }).then((list)=>{
    list.getEntries().forEach((entry)=>{
      console.log(`${entry.name}: ${entry.startTime}`);
    });
  }).catch((error)=>{
    console.warn(error);
  });
}

Let’s walk through this code:

  1. We check for the existence of the PerformanceObserver object in window. If PerformanceObserver doesn’t exist, nothing happens.
  2. A Promise is created. In the first part of the promise chain, we create a new PerformanceObserver object and store it in the observer variable. This observer contains a callback that resolves the promise with a list of paint timings.
  3. We have to get those paint timings from somewhere, right? That’s where the observer method kicks in. This method lets us define what types of performance entries we want. Since we want painting events, we just pass in an array with an entry type of "paint".
  4. If the browser supports gathering "paint" events with PerformanceObserver, the promise will resolve and the next part of the chain kicks in where we then have access to the entries through the list variable’s getEntries method. This will produce console output much like the previous example.
  5. If the current browser doesn’t support gathering "paint" events with PerformanceObserver, the catch method provides access to the error message. From here, we can do whatever we want with this information.

Now you have a way to gather metrics asynchronously, instead of having to wait for the page to load. I personally prefer the previous method, as the code is more terse and readable (to me, anyway). I’m sure my methods aren’t the most robust, but they are illustrative of the fact that you can gather paint timings in the browser in a predictable way that shouldn’t throw errors in older browsers.

What would I use this for?

Depends on what you’re after. Maybe you want to see just how fast your site is rendering for real users out in the wild. Maybe you want to gather data for research. At the time of writing, I’m conducting a image quality research project that gauges participants on how they perceive lossy image quality of JPEGs and WebP images. As part of my research, I use other timing APIs to gather performance-related information, but I’m also gathering paint timings. I don’t know if this data will prove useful, but collecting and analyzing it in tandem with other metrics may be helpful to my findings. However, you use this data is really up to you. In my humble opinion, I think it’s great that this API exists, and I hope more browsers move to implement it soon.

Some other stuff you might want to read

Reading this short piece might have gotten you interested in some other pieces of the broader performance interface. Here’s a few articles for you to check out if your curiosity has been sufficiently piqued:

  • The surface of this API is shared with the established Resource Timing API, so you should brush up on that. If you feel comfortable with the code in the article, you’ll be able to immediately benefit from this incredibly valuable API.
  • While this API doesn’t share much of a surface with the Navigation Timing API, you really ought to read up on it. This API allows you to collect timing data on how fast the HTML itself is loading.
  • PerformanceObserver has a whole lot more to it than what I’ve illustrated here. You can use it to get resource timings and user timings. Read up on it here.
  • Speaking of user timings, there’s an API for that. With this API, you can measure how long specific JavaScript tasks are taking using highly accurate timestamps. You could also use this tool to measure latency in how users interact with the page.

Now that you’ve gotten your hands dirty with this API, head out and see what it (and other APIs) can do for you in your quest to make the web faster for users!


Cover of Web Performance in Action

Jeremy Wagner is the author of Web Performance in Action, available now from Manning Publications. Use promo code sswagner to save 42%.

Check him out on Twitter: @malchata