Forked from anniesullie/InteractionsEventTiming.js
Last active
September 16, 2023 11:11
Star
You must be signed in to star a gist
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.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