Skip to content

Instantly share code, notes, and snippets.

@anniesullie
Last active April 2, 2024 16:41
Show Gist options
  • Save anniesullie/16814ef9e888fec45e0946da303f55ac to your computer and use it in GitHub Desktop.
Save anniesullie/16814ef9e888fec45e0946da303f55ac to your computer and use it in GitHub Desktop.
This gist pokes around with interactions in the EventTiming API. It tries to get the interaction latency, delay, processing time breakdown, type, and target.
const interactionMap = new Map();
function logInteraction(interaction) {
const clamp = val => Math.round(val * 100) / 100; // clamp to 2 decimal places
console.groupCollapsed(`${interaction.type} interaction`, clamp(interaction.latency));
console.log(`total latency`, clamp(interaction.latency));
console.log('delay:', clamp(interaction.delay));
console.groupCollapsed(`processing time in ${Object.entries(interaction.processingTimes).length} entries:`, clamp(interaction.processingTime));
for (const [e, t] of Object.entries(interaction.processingTimes)) {
console.log(clamp(t), `in ${e}`);
}
console.groupEnd();
console.log(`time to next paint: `, clamp(interaction.latency - (interaction.delay + interaction.processingTime)));
console.log('interacted with: %O', interaction.target);
console.groupEnd();
}
function getInteractionType(eventName) {
// From https://web.dev/better-responsiveness-metric/#interaction-types
if (['keydown', 'keypress'].includes(eventName)) {
return 'Key pressed';
} else if (['keyup'].includes(eventName)) {
return 'Key released';
} else if (['pointerdown', 'touchstart'].includes(eventName)) {
return 'Tap start or drag start';
} else if (['pointerup', 'mouseup', 'touchend', 'click'].includes(eventName)) {
return 'Tap up or drag end';
}
else if (eventName == 'mousedown') {
if(/Android/i.test(navigator.userAgent)) {
return 'Tap up or drag end';
} else {
return 'Tap start or drag start';
}
}
return null;
}
new PerformanceObserver((entries) => {
let newInteractions = [];
for (const entry of entries.getEntries()) {
// Ignore entries without an interaction ID.
if (entry.interactionId > 0) {
// Get the interaction for this entry, or create one if it doesn't exist.
const interactionType = getInteractionType(entry.name);
const interactionId = entry.interactionId + ' - ' + interactionType;
let interaction = interactionMap.get(interactionId);
if (!interaction) {
interaction = {
latency: entry.duration,
delay: entry.processingStart - entry.startTime,
target: entry.target,
processingTimes: {}
};
interactionMap.set(interactionId, interaction);
newInteractions.push(interactionId);
}
if (interactionType) {
interaction.type = interactionType;
}
entryDelay = entry.processingStart - entry.startTime;
if (entryDelay < interaction.delay) {
interaction.delay = entryDelay;
}
interaction.processingTimes[entry.name] = entry.processingEnd - entry.processingStart;
interaction.processingTime = Object.values(interaction.processingTimes).reduce((a, b) => a + b);
if (entry.duration > interaction.latency) {
interaction.latency = entry.duration;
}
}
}
for (const newInteraction of newInteractions) {
logInteraction(interactionMap.get(newInteraction));
}
// Set the `durationThreshold` to 50 to capture keyboard interactions
// that are over-budget (the default `durationThreshold` is 100).
}).observe({type: 'event', buffered: true, durationThreshold: 50});
@anniesullie
Copy link
Author

Awesome! Thanks so much! Updated mine to include the better logging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment