loading...
Cover image for Performance timing: wait for the next frame (when using the User Timing API)

Performance timing: wait for the next frame (when using the User Timing API)

borisschapira profile image Boris Schapira Originally published at boris.schapira.dev ・3 min read

The usual web performance metrics (First Byte, Speed Index…) are very interesting but I often need to add custom temporal markers, based on events that make sense from a business point of view.

I use the User Timing API (see on MDN) to create named timestamps that belong to the browser's performance timeline. As I can retrieve this timeline in Dareboost, I can track their evolution over time.

"Two curves in a graph. On the x-axis, a date range of one month. On the Y-axis, the time between 0 and 3s. The orange curve shows timing "mark_declare_js_end" while the green curve corresponds to timing "mark_switchlang_end"."

Some Custom Timings of this site

Using the API in JavaScript is quite simple (and broadly supported), for example:

    // The thing you want to monitor the occurrence of
    document.getElementById("content").classList.add("blue");
    // The timestamp
    performance.mark("mark-blue");

You can then retrieve the value in your browser developers tools, using:

performance.getEntriesByType('mark');

However, I recently1 realized that I often forgot something important: just because the browser execute some JavaScript code does not mean that the user can see the results of this execution. The browser must update the display, and its ability to do so depends on the JavaScript code that follows the piece of code we're interested in.

In the following example, I inject a 1-second blocking loop after my performance mark, preventing the browser to update of the display.

    // The thing you want to monitor the occurrence of
    document.getElementById("content").classList.add("blue");
    // The timestamp
    performance.mark("mark-blue-sync");

    // A snippet of code that consumes resources during 1 second
    let n = performance.now();
    while (performance.now() - n < 1000) {}

Of course, in real life, you won't have a simple loop. You will have other tasks that may or may not take a long time. You can't know it and, in fact, you can't even test it, because it all depends on the customer context that you don't control.

To get around this problem, we can use requestAnimationFrame() to ask for our mark to be put set before the browser performs the next repaint:

    // The thing you want to monitor the occurrence of
    document.getElementById("content").classList.add("blue");
    performance.mark("mark-blue-sync");
    // The timestamp, before the next repaint
    window.requestAnimationFrame(() => {
      performance.mark("mark-blue-frame");
    });

    // A snippet of code that consumes resources during 1 second
    // but we don't care anymore
    let n = performance.now();
    while (performance.now() - n < 1000) {}

If you want to see this for yourself, here's a test page.

IN A NUTSHELL

Call the User Timings API to create custom business-related timestamps.

Encapsulate them inside requestAnimationFrame() callbacks to get to understand how the UI behaves, rather than the code execution.

If you want to have an idea of the display latency, attach two marks: one after the event to be followed, the other in a requestAnimationFrame() callback.


  1. Reading "Performance metrics for blazingly fast web apps", by Conrad Irwin 

Posted on by:

borisschapira profile

Boris Schapira

@borisschapira

#WebPerf & #Quality aficionado · @Dareboost Customer Success Manager · EN/FR

Discussion

pic
Editor guide