Last active
April 2, 2024 16:41
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Awesome! Thanks so much! Updated mine to include the better logging.