web-vitals: Target in FID and INP is null
Hi guys,
Im using your nice script to measuer web vitals on our website. To make optimization easier, we are also persisting the target-elemtent. It works fine with CLS and LCP but in FID and INP I get “null” for the target Element as you can see in this screenshot:
This is how I use it:
<script type="module">
import {onCLS, onFID, onLCP, onINP, onFCP, onTTFB} from "https://unpkg.com/web-vitals@3/dist/web-vitals.attribution.js?module";
function getIdOrNodeNamePlusClassList(node) {
return (node && node.nodeType !== 9) ? (node.id ?
"#" + node.id :
node.nodeName.toLowerCase() +
((node.className && node.className.length) ?
"." + Array.from(node.classList.values()).join(".") :
"")) : "";
}
function getLargestLayoutShiftEntry(entries) {
return entries.reduce((a, b) => a && a.value > b.value ? a : b);
}
function getLargestLayoutShiftSource(sources) {
return sources.reduce((a, b) => {
return a.node && a.previousRect.width * a.previousRect.height >
b.previousRect.width * b.previousRect.height ? a : b;
});
}
function getCausingElementIdentifier(name, entries = []) {
// In some cases there won't be any entries (e.g. if CLS is 0,
// or for LCP after a bfcache restore), so we have to check first.
if (entries.length && name !== "FCP" && name !== "TTFB") {
if (name === "LCP") {
const lastEntry = entries[entries.length - 1];
return getIdOrNodeNamePlusClassList(lastEntry.element);
} else if (name === "FID" || name === "INP") {
const firstEntry = entries[0];
return getIdOrNodeNamePlusClassList(firstEntry.target);
} else if (name === "CLS") {
const largestEntry = getLargestLayoutShiftEntry(entries);
if (largestEntry && largestEntry.sources && largestEntry.sources.length) {
const largestSource = getLargestLayoutShiftSource(largestEntry.sources);
if (largestSource) {
return getIdOrNodeNamePlusClassList(largestSource.node);
}
}
}
}
return "";
}
function sendRUMData({name, value, attribution, entries}) {
if (name === "FID" || name === "INP") {
console.log("--- " + name + "(" + value +") ---");
console.log("- Entries -");
console.log(entries);
console.log("- Attribution -");
console.log(attribution);
console.log("- Causing -");
console.log(getCausingElementIdentifier(name, entries));
}
}
onCLS(sendRUMData);
onFCP(sendRUMData);
onFID(sendRUMData);
onINP(sendRUMData);
onLCP(sendRUMData);
onTTFB(sendRUMData);
</script>
Do you know why? Am I doing something wrong?
About this issue
- Original URL
- State: open
- Created a year ago
- Reactions: 3
- Comments: 15 (9 by maintainers)
As noted in https://github.com/GoogleChrome/web-vitals/issues/335#issuecomment-1625311650 there were two main outstanding issues that we were aware of - both issues in Chromium rather than anything this library can necessarily resolve.
The change we made with 3.5.2 to workaround this was to look at other events in the same interaction. For example, and interaction might be made up of a pointer down, pointer up, and click events. If either of the pointer events are the longest events they may be reported as the “INP event” and its target given. As of 3.5.2, if that target is null, we will look at the other events in that interaction to try to find a non-null target.
This is a workaround and though and in some cases there may not be a target even then. This particularly affects short events (where the FID event is reported as INP and it only has the first). We were unable to estimate how much this workaround would help but it was definitely known this would not solve all the issues.
The good news is we’ve made significant progress on the underlying bug where and think these may be reported in a future version of Chrome (probably Chrome 123). This is the better fix rather than the workaround we tried in this library.
This is technically correct according to spec, but obviously painful for those trying to identify INP causes!
We have further good news here, in that we think we have a way forward here too. Basically we will grab some details at the time of the interaction to use later even if the element doesn’t exist. This may not be the same selector that the web-vitals library would have used, but hopefully will be enough to identify the element (particularly if a unique id, or a class that makes it identifiable is present on the element!).
Not sure when exactly this will land as still some details to be worked out but at earliest Chrome 123.
so to your example, yes in theory it could happen to any element, but it’s more likely to happen to specific ones (ones that have pointer events, very low INP, or for elements which are then removed from the DOM as part of the interaction or shortly afterwards).
Hopefully the workaround in 3.5.2 has helped a little (and certainly shouldn’t have made it any worse), but we need those two fixes implemented to really resolve this (and hopefully not end up revealing other issues we have not considered so far!).
I’ll keep this issue open and update it as the situation changes.
Update on this issue:
We’ve released 3.5.2 which attempts to get the first non-null element for the INP event which should work around around the first issue. Will be interesting to see how much this improves the situation and how much the second issue is still a pain point.
@tunetheweb thanks for quick reply. So, at the moment, if there is any re-mounting of a Node (delete and recreate) which happens to be the LCP element, then the
element
reported inmetric.attribution
object could benull
? cc: @AbhiPanseriya @girdhariag @jsliang @simon-parisThis issue is when an element is is disconnected from the DOM tree the Performance Observer entry returns
null
. So it’s not that we can’t look up the element, it’s that we don’t have the element to look up. This needs to be fixed in Chrome before this library can return it.However, there are a number of different issue here that are easy to confuse: