Skip to content

Instantly share code, notes, and snippets.

@paulirish
Forked from anniesullie/InteractionsEventTiming.js
Last active September 16, 2023 11:11
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paulirish/041f92c312c0eaddc6a1de91cb34a026 to your computer and use it in GitHub Desktop.
Save paulirish/041f92c312c0eaddc6a1de91cb34a026 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.delay > interaction.latency) {
interaction.latency = entry.delay;
}
}
}
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});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment