npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@uptimizr/three

v1.0.4

Published

three.js connector for Uptimizr — captures camera, pointer, mesh, and perf events as an sdk-core collector.

Readme

@uptimizr/three

The three.js connector for Uptimizr. It registers as an @uptimizr/sdk-core collector and captures:

  • camera pose (position + forward direction) → view-direction heatmap
  • pointer move / click / button transitions (normalized screen + optional raycast hit) → screen heatmaps
  • camera gestures → navigation-intent analytics
  • mesh picks → object-engagement analytics
  • FPS and context loss → performance / reliability
  • mesh visibility / hover dwell / gaze / resource samples / node transforms (opt-in) → attention, footprint, and replay fidelity
  • WebXR controller/gaze input (via trackScene, enabled by default) → XR interaction analytics

three is a peer dependency: the connector reads from the host application's three.js instance and never bundles or mutates the scene. It tears down all DOM listeners, timers, and animation-frame callbacks on stop. World-space data is normalized from three's native right-handed, y-up frame to the canonical wire frame (left-handed, y-up) at the emission boundary.

Install

npm install @uptimizr/three three

three is a peer dependency — the connector reads your existing three.js instance and never bundles its own.

Usage

The quickest integration is a single call. Because three.js has no scene.activeCamera and the connector reads FPS and the canvas from the renderer, camera and renderer are explicit arguments:

import { trackScene } from "@uptimizr/three";

const client = trackScene(scene, camera, renderer, {
  projectId: "your-project",
  endpoint: "https://collect.example.com",
});

// ... later, on teardown
await client.stop("manual");

trackScene creates the client, registers the collector, reads device/GPU caps, and starts the session. It returns the @uptimizr/sdk-core UptimizrClient, so you can read client.sessionId, emit custom events, or stop it.

Advanced: wire it up yourself

For a custom transport, a beforeSend hook, or registering multiple collectors, compose the pieces directly:

import { UptimizrClient } from "@uptimizr/sdk-core";
import { threeCollector, readDeviceCaps } from "@uptimizr/three";

const client = new UptimizrClient({
  projectId: "your-project",
  endpoint: "https://collect.example.com",
});

client.use(threeCollector({ scene, camera, renderer }));

// device/GPU caps ride along on session_start
client.start({ device: readDeviceCaps(renderer) });

// ... later, on teardown
client.stop("manual");

Options

trackScene accepts the one-call project/endpoint, sampling, capture, gaze, actors, keyBindings, cameraType, connector, and xr options. Use threeCollector directly for collector-only tuning such as meshVisibility, hoverDwell, resourceSample, raycast, or cameraGestureSensitivity:

threeCollector({
  scene,
  camera,
  renderer,
  sampleCameraMs: 1000, // camera-pose sampling interval
  samplePerfMs: 2000, // FPS sampling interval (derived from renderer.info)
  pointerMoveThrottleMs: 250, // min gap between pointer_move samples
  capture: {
    camera: true,
    pointerMove: true,
    clicks: true,
    buttons: true,
    cameraGesture: true,
    meshPicks: true,
    perf: true,
  },
});

Capture fidelity

The sampling profile sets the per-channel fidelity dial in Hz (0 = off, "frame" = every render tick). It governs continuous channels only — camera pose, pointer move, and perf; discrete events (clicks, picks, custom) are always captured. "frame"-cadence channels are driven by requestAnimationFrame (rAF ≈ render cadence), since three exposes no per-frame hook the connector owns.

threeCollector({ scene, camera, renderer, sampling: { camera: 10, pointerMove: 60, perf: 0.5 } });

WebXR

trackScene registers xrCollector by default. It stays idle until renderer.xr enters an immersive session, then maps controller/gaze rays plus select/squeeze actions onto the shared pointer and mesh_interaction events. Pass xr: false to disable it, or xr: { sampleMs, capture, raycast } to tune XR pose sampling and hit resolution. createXrRaycaster(scene) builds a ready-made raycast probe (world-space ray → nearest named hit) over the live scene graph, so controller/gaze rays carry hitPoint/hitMesh and select/squeeze attach to the object hit.

Opt-in dwell capture (mesh_visibility #37, hover_dwell #48)

Two attention signals are off by default (privacy) and emit one bucketed summary per window/episode:

  • mesh_visibility — per-object on-screen time, time near the view centre (a gaze proxy), and the max screen fraction reached. Enable capture.meshVisibility and tune via meshVisibility. With boundingBox: true it rides each object's world AABB along (#53) so the dashboard can draw a coarse scene "ghost"; the box is sent once and only re-sent when it moves. three has no mesh.isInFrustum or world-AABB reader (Babylon does), so the connector computes both import-free from geometry.boundingBox + matrixWorld and a clip-space frustum test.
  • hover_dwell — fires when the pointer rests on an object for at least minDwellMs without clicking it (a click is engagement, not hesitation). Enable capture.hoverDwell and tune via hoverDwell.
threeCollector({
  scene,
  camera,
  renderer,
  capture: { meshVisibility: true, hoverDwell: true },
  meshVisibility: { windowMs: 5000, boundingBox: true, centeredAngleDeg: 12, maxMeshes: 50 },
  hoverDwell: { minDwellMs: 500 },
});

three.js adaptations

three.js differs from Babylon in a few places; each is handled at the connector boundary so the emitted events are identical:

  • Camera forward: three cameras look along local −Z (canonical looks along +Z). The connector reads the true world-space forward via camera.getWorldDirection(...) before converting, so the canonical Z-negation is correct (it never reconstructs orientation from the local quaternion).
  • Pointer/raycast: three has no pointer observable, so DOM listeners are attached to renderer.domElement and hits are resolved with THREE.Raycaster.
  • Pointer lock (ADR 0034): when renderer.domElement holds the pointer lock (PointerLockControls, first-person/FPS scenes), the OS cursor freezes, so the connector reports pointer_move/pointer_click from the viewport centre (screen = [0.5, 0.5]) and raycasts from NDC (0, 0) — the crosshair. Read the spatial story from the gaze/floor-plan heatmaps, not the 2D pointer heatmap.
  • FPS: derived from the renderer.info.render.frame delta over the sample interval (three has no getFps()).
  • Mesh visibility: three has no mesh.isInFrustum(...) or getBoundingInfo().boundingBox with world min/max. The connector accumulates on-screen time once per requestAnimationFrame (so dwell pauses with the tab, matching Babylon's onBeforeRender), computes each object's world AABB from geometry.boundingBox + matrixWorld, and tests the view frustum from projectionMatrix · matrixWorldInverse — all without importing three.
  • Coordinate frame: fixed right-handed, y-up (three has no per-scene handedness flag like Babylon's useRightHandedSystem).
  • Compile stalls (compile_stall, #42): not captured. Babylon exposes an engine-level onBeforeShaderCompilationObservable / onAfterShaderCompilationObservable pair the connector can time; three's WebGLRenderer has no equivalent public compile hook and compiles lazily on first render, so there is no boundary the connector can measure without monkey-patching the renderer (which would violate the "never mutate the engine" rule). Compile-stall capture is therefore Babylon-only for now.
  • Resource footprint (resource_sample, #44): captured, but with fewer metrics than Babylon. three's renderer.info.render.triangles gives the triangles submitted last frame; there is no public per-frame vertex count, and resident texture/geometry bytes aren't exposed, so those fields are omitted (the read API's averages skip unreported metrics). jsHeapBytes comes from the Chromium-only performance.memory. Opt-in (capture.resourceSample), low-rate, and read-only like the rest.
  • Capability changes (capability_change, #49): not auto-captured — by design. A WebGPU→WebGL2 downgrade, a quality/LOD auto-downgrade, or a re-init after a lost device is an app/engine decision with no reliable runtime hook the connector could observe (three picks its backend at construction). Report these transitions from your app with the engine-neutral client.reportCapabilityChange({ kind, from?, to?, reason? }) (sdk-core). The raw GPU lifecycle (context_lost / context_restored) is still captured by the connector; capability_change is the higher-level companion.
  • Engine diagnostics — WebGPU device.lost (graphics_diagnostic, #20): opt-in via captureGraphicsDiagnostics: true on the client (off by default). On a WebGPURenderer, the connector subscribes to renderer.backend.device.lost and emits one graphics_diagnostic with category: "device-lost" and backend: "webgpu" — severity is info for a requested loss (reason: "destroyed") and fatal otherwise; the optional length-capped message runs through beforeSend. Engine-parity with the Babylon connector. A WebGLRenderer is a no-op (no device-lost concept; its interruption is the always-on context_lost).
  • Engine diagnostics — WebGPU uncapturederror (rate-limited rollup, #19): also under captureGraphicsDiagnostics, the connector listens for uncapturederror on renderer.backend.device and aggregates a burst into one graphics_diagnostic with count: N + first message, flushed on an interval and on dispose — never N events. Subtype maps to out-of-memory (GPUOutOfMemoryError, severity: error) or validation (severity: warning); message is length-capped via beforeSend. WebGL no-op.
  • Engine diagnostics — context-creation failure (graphics_diagnostic, #18): also opt-in via captureGraphicsDiagnostics: true. At init the connector checks whether the renderer obtained a GL context (getContext() returns null on failure) and, if not, emits one graphics_diagnostic with category: "context-loss", severity: "fatal", and backend: "unknown". It fires before the first flush yet queues in order after session_start, so the decisive marker always lands.
  • Engine diagnostics — shader compile/link failures + sampled gl.getError() (#17): also under captureGraphicsDiagnostics, shader compile/link failures → category: "shader-compile" (error; WebGL info logs, WebGPU shader-module compilation info), and sampled WebGL gl.getError() → category: "validation" as a low-rate rollup (never per-frame — it forces a sync GPU stall; no-op on WebGPU). Raw shader source is stripped unless the separate captureShaderSource: true sub-opt-in is set (off by default — application IP). All text is length-capped via beforeSend. Engine-parity with the Babylon connector.

License

Apache-2.0 © Uptimizr.