Skip to main content

Performance Tuning

Throttling Key Presses

On Smart TVs, holding down a direction key fires many keydown events per second. Without throttling, the library processes each event, potentially moving focus dozens of times per second and causing janky animations.

throttle

Sets a minimum number of milliseconds between navigation events. When a key is held, only one navigation event fires per throttle period.

import { init } from '@noriginmedia/norigin-spatial-navigation-core';

init({
throttle: 150 // navigate at most once every 150ms
});

A value of 0 (the default) disables throttling.

throttleKeypresses

When true, the throttle also applies to key repeat events (the repeated keydown fired by the OS when a key is held):

init({
throttle: 100,
throttleKeypresses: true
});

Set both when you want to prevent rapid-fire navigation while a key is held.

Adjusting Throttle at Runtime

Use setThrottle to change throttle settings without reinitializing:

import { setThrottle } from '@noriginmedia/norigin-spatial-navigation-core';

// Slow down while a video is scrubbing
setThrottle({ throttle: 300, throttleKeypresses: true });

// Remove throttle when done
setThrottle({ throttle: 0 });

Layout Measurement

The library measures each component's position and size to calculate navigation distances. The measurement method affects both accuracy and performance.

Default: offsetLeft / offsetTop (faster)

By default the library reads element.offsetLeft, element.offsetTop, element.offsetWidth, and element.offsetHeight. This is fast and unaffected by CSS transforms.

useGetBoundingClientRect: true (viewport-relative)

init({ useGetBoundingClientRect: true });

Switches measurement to element.getBoundingClientRect(). Use this when:

  • Elements are CSS-transformed (e.g., transform: scale(0.9))
  • Elements are inside a scaled or rotated container
  • You need coordinates relative to the viewport rather than the document

getBoundingClientRect is slightly slower because it triggers a layout reflow, but it accounts for CSS transforms that offsetLeft/Top ignores.


DOM Node Focus (shouldFocusDOMNode)

By default, the library manages focus entirely in JavaScript without calling HTMLElement.focus(). Enabling shouldFocusDOMNode makes the library also call .focus() on the underlying DOM node.

init({ shouldFocusDOMNode: true });

This is useful when:

  • You need native browser focus behavior (:focus CSS, screen reader compatibility).
  • The focused element should receive native browser events.
  • WCAG screen reader compatibility.

domNodeFocusOptions

Pass options to the underlying HTMLElement.focus() call:

init({
shouldFocusDOMNode: true,
domNodeFocusOptions: { preventScroll: true }
});

The preventScroll option is particularly useful on Smart TVs where browser auto-scrolling on focus conflicts with your custom scroll logic.


Reducing Component Count

The library stores a flat map of all registered focusable components and iterates through it on every key event. Keeping the number of registered components reasonable (in the hundreds, not thousands) helps maintain performance on lower-powered TV hardware.

Tips:

  • Use focusable: false or conditional rendering to exclude off-screen items.
  • For very long lists, consider virtualizing the list and only rendering visible rows.
  • Nested FocusContext.Provider boundaries help the library prune the candidate set faster.

Pausing Navigation

Temporarily stop processing key events when they are not needed (e.g., while a full-screen video plays):

import { pause, resume } from '@noriginmedia/norigin-spatial-navigation-react';

function onVideoPlay() {
pause();
}

function onVideoEnd() {
resume();
}

pause() and resume() are instant and have no overhead.