Skip to content
storybook-addon-performance-panel

Collectors reference

The addon uses modular collector classes for metrics gathering. Each collector implements the MetricCollector<T> interface and uses the most accurate available API.

Collection methods

Each collector uses one of two approaches:

  • Direct measurement — reads from a browser API that was designed specifically for the metric being measured. These produce definitive numbers. Examples: Event Timing API for INP, Layout Instability API for CLS, performance.memory for heap size.
  • Indirect measurement — infers metrics from general-purpose APIs when no dedicated API exists. These are useful approximations, but accuracy can vary. Examples: requestAnimationFrame deltas for FPS, MutationObserver for style write detection, property getter patching for forced reflow detection.

Overview

CollectorBrowser APIMethod
ElementTimingCollectorElement Timing APIDirect
ForcedReflowCollectorProperty getter patchingIndirect
FrameTimingCollectorrequestAnimationFrame loopIndirect
InputCollectorEvent Timing APIDirect
LayoutShiftCollectorLayout Instability APIDirect
LongAnimationFrameCollectorLong Animation Frames APIDirect
MainThreadCollectorLong Tasks APIDirect
MemoryCollectorperformance.memoryDirect
PaintCollectorPaint Timing APIDirect
ReactProfilerCollectorReact Profiler APIDirect
StyleMutationCollectorMutationObserverIndirect

ElementTimingCollector

Uses the Element Timing API to track render timing for elements marked with the elementtiming attribute.

observer.observe({ type: 'element', buffered: true })

Works like LCP but for elements you choose. Useful for measuring when hero images, key content, or specific UI elements become visible.

Browser support: Chrome 77+, Edge 79+. Not available in Firefox or Safari.


ForcedReflowCollector

Patches HTMLElement.prototype property getters to detect layout property reads that occur after style writes.

Tracked properties: offsetTop, offsetLeft, offsetWidth, offsetHeight, scrollTop, scrollLeft, scrollWidth, scrollHeight, clientTop, clientLeft, clientWidth, clientHeight

Detection is gated by a layoutDirty flag set by the StyleMutationCollector.

Limitations: Detects only JavaScript-triggered reflows. May produce false positives if layout was already computed before the style write.


FrameTimingCollector

Measures frame-level timing using a requestAnimationFrame loop. No dedicated browser API exists for frame-by-frame metrics, so RAF timestamp deltas are the standard approach.

const now = performance.now()
const delta = now - lastTime
lastTime = now
processFrame(delta)
requestAnimationFrame(measure)

Produces: frameTimes[], maxFrameTime, droppedFrames, frameJitter, frameStability

Limitations: Cannot detect frames where RAF was not called. Background tabs may throttle RAF callbacks.


InputCollector

Uses the Event Timing API — the W3C standard for interaction latency measurement.

observer.observe({ type: 'event', buffered: true, durationThreshold: 16 })

Each event entry provides:

PropertyWhat it means
durationTotal time from input to next paint (8ms granularity)
startTimeWhen the user input occurred
processingStartWhen event handlers started
processingEndWhen event handlers finished
interactionIdGroups events from the same logical interaction
targetSelectorCSS selector for the target element

INP is calculated as the p98 of worst interactions, matching the Web Vitals definition.

Browser support: Chrome 96+, Edge 96+, Firefox 144+. Not available in Safari.


LayoutShiftCollector

Uses the Layout Instability API with session windowing per the evolved CLS specification.

Session window rules:

  • Gap from previous shift ≥ 1 second → start a new session
  • Session duration ≥ 5 seconds → start a new session
  • Otherwise → add to the current session

CLS equals the maximum session window value (not the sum across all sessions), matching CrUX and Lighthouse methodology.

Browser support: Chrome 77+, Edge 79+. Not available in Firefox or Safari.


LongAnimationFrameCollector

Uses the Long Animation Frames API for detailed frame attribution — more granular than Long Tasks.

observer.observe({ type: 'long-animation-frame', buffered: true })

Includes full script attribution: source URL, function name, and invoker type. Essential for diagnosing INP issues.

AspectLong TasksLong Animation Frames
ScopeAny main thread workAnimation frame callbacks
AttributionNoneFull script attribution
Timing breakdownDuration onlyRender, style, and layout phases

Browser support: Chrome 123+, Edge 123+. Not available in Firefox or Safari.


MainThreadCollector

Uses the Long Tasks API to detect tasks blocking the main thread for more than 50ms.

observer.observe({ type: 'longtask' })
// TBT = sum of (duration - 50ms) for all long tasks

The 50ms threshold follows the RAIL model and aligns with the Lighthouse TBT calculation.

Browser support: Chrome 58+, Edge 79+. Not available in Firefox or Safari.


MemoryCollector

Uses performance.memory — the only JavaScript API for JS heap introspection.

Produces: baselineMemoryMB, peakMemoryMB, lastMemoryMB, gcPressure (allocation rate in MB/s)

Limitations: Chrome only. Values may be quantized for security.


PaintCollector

Combines the Paint Timing API for paint events and Resource Timing for script evaluation metrics. Compositor layer detection uses an indirect technique based on computed styles (will-change, transform).

Browser support: All modern browsers support paint timing. Compositor layer detection is a Chrome-only approximation.


ReactProfilerCollector

Uses the React Profiler API via a <Profiler> wrapper component.

Detects three phases:

  • mount — initial renders
  • update — re-renders after mount
  • nested-updatesetState during the commit phase (render cascades — very expensive)

Availability: Only included with the default React addon entry. Omitted when using ./universal.


StyleMutationCollector

Uses MutationObserver to track inline style attribute changes and CSS variable mutations.

Produces: styleWrites, cssVarChanges, domMutationFrames[], thrashingScore

Limitations: Detects only inline style changes. Does not observe stylesheet modifications or CSSOM manipulations.


Adding a collector

Implement the MetricCollector<T> interface:

export interface MetricCollector<T> {
  start(): void
  stop(): void
  reset(): void
  getMetrics(): T
}
  1. Create a new file in collectors/
  2. Define a metrics interface for the data you want to collect
  3. Add threshold constants to constants.ts
  4. Integrate the collector in performance-decorator.tsx
  5. Add the metrics type to PerformanceMetrics in performance-types.ts
  6. Add a UI section in performance-panel.tsx
Navigation