I need to create a webpage that will generate a bad First Input Delay (FID) value.
In case you aren't aware, FID is part of Google's Web Core Vitals.
I want to simulate a bad FID because I am testing a website scanning tool that is supposed to flag a bad FID value. Therefore I want to simulate a bad value on a webpage to make sure it works.
To be clear - I am NOT trying to fix my First Input Delay. I want to create a webpage that gives a bad First Input Delay value on purpose.
But I'm not sure how to do that.
I have a HTML page with <button id="button">Click Me</button>
. And in the <head>
I have added this script:
<script type="module">
// Get the First Input Delay (FID) Score
import {onFID} from 'https://unpkg.com/web-vitals@3/dist/web-vitals.attribution.js?module';
// Get the button element
const button = document.getElementById('button');
// Add a click event listener to the button
button.addEventListener('click', async () => {
// Make a delay
await new Promise((resolve) => setTimeout(resolve, 5000));
// Print the FID score to the console
onFID(console.log);
});
</script>
The imported onFID
method is what Google uses from Web Vitals to report the FID value.
You can see a live version of the above script here: http://seosins.com/extra-pages/first-input-delay/
But when I click the button, 5000 milliseconds later it only prints a FID of about 3 milliseconds.
The 5000 millisecond delay is not included in the FID value.
Why doesn't it report the FID value as 5003 milliseconds?
When I try to simulate a bad FID value I am doing something wrong.
What could it be?
Update:
As suggested in the comments, I have also tried adding a delay on the server using a Cloudflare Worker. That worker delayed the server response by 5000 milliseconds. But it didn't work, because the FID value was unchanged.
Also I do not think this is the correct approach because FID measures the time from when a user first interacts with your site (i.e. when they click a link, tap on a button, etc) to the time when the browser is actually able to respond to that interaction. While the Cloudflare Worker was only slowing down the initial server response. Therefore I have since removed this experiment from the page.
I think you misunderstand what FID is
From web.dev's page on First Input Delay (FID):
and
also:
Here is my understanding: Actual FID measurement is built into Chrome. The
web-vitals
library simulates this using browser measurement APIs. The measurement isn't based on whenonFID
is called;onFID
simply sets up a measurement event listener with those APIs. What is measured is the time between when a user clicks on something (e.g. the button) and when its event handler is triggered, not how long that handler takes to complete (see second quote above).First, we need something that occupies (i.e. blocks) the JS Event Loop
setTimeout
does not do that. It just delays when something happens. In the meantime the event loop is free to do other work, e.g. process user input. Instead you need code that does exactly what you're not supposed to do: Do some lengthy blocking CPU-bound work synchronously, thus preventing the event loop from handling other events including user input.Here is a function that will block a thread for a given amount of time:
or maybe just:
Now the question is: When/where do we block the event loop?
Before I reached the understanding above, my first thought was to just modify your approach: block the event loop in the button
click
event listener. Take your code, remove theasync
, and callblockThread
instead of setting a timer. This runnable demo does that:I'd give the above a try to confirm, but I'd expect it to NOT impact the FID measurement because:
Both your version and mine blocks during the execution of the event handler, but does NOT delay its start.
I'm sure we need to block the event loop before the user clicks on an input, and for long enough that it remains blocked during and after that click. How long the event loop remains blocked after the click will be what the FID measures.
I'm also pretty sure we need to import and call
onFID
before we block the event loop.The
web-vitals
library simulates Chrome's internal measurement. It needs to initialize and attach itself to the browser's measurement APIs as a callback in order for it to be able to measure anything. That's what callingonFID
does.So let's try a few options...
start blocking the event loop while the page is loaded
Looking at the Basic usage instructions for web-vitals I arrived at this:
But I suspect that calling blockThread as above will actually also block the page/DOM from loading so you won't even have a button to click until it's too late. In that case:
start blocking after the page is loaded and before the
DOMContentLoaded
event is triggered (my bet is on this one)If that still doesn't work, try this:
start blocking when the
DOMContentLoaded
event is triggeredLet me know if none of these work, or which one does. I don't have the time right now to test this myself.