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

@new-immersion/ngx-threejs

v2.1.1

Published

Angular services and components for running one application-wide Three.js runtime with WebGPURenderer, TSL, optional Lenis/GSAP integration, resource loading and the native Three.js Inspector.

Readme

@new-immersion/ngx-threejs

Angular services and components for running one application-wide Three.js runtime with WebGPURenderer, TSL, optional Lenis/GSAP integration, resource loading and the native Three.js Inspector.

Version 2 is intended for new projects. It assumes that an application owns one runtime, which may contain several scenes.

Requirements

  • Angular 22
  • Three.js 0.185
  • RxJS 7.8
  • GSAP 3.15
  • Lenis 1.3
  • detect-gpu 5

GSAP and Lenis are peer dependencies because their public types and optional runtime integrations must resolve in consuming applications.

Installation

npm install @new-immersion/ngx-threejs \
  three@^0.185.1 @types/three@^0.185.4 \
  rxjs@^7.8 gsap@^3.15 lenis@^1.3 detect-gpu@^5

Configure the application

Register the library once in the application providers. Every service is provided in root; provideNgxThreejs() only supplies configuration and Inspector parameter extensions.

import { ApplicationConfig } from '@angular/core';
import { provideNgxThreejs } from '@new-immersion/ngx-threejs';

export const appConfig: ApplicationConfig = {
  providers: [
    provideNgxThreejs({
      renderLoopDriver: 'auto',
      loaders: {
        dracoDecoderPath: '/draco/',
      },
      debug: {
        enable: false,
        localStoragePrefix: 'my-app',
      },
    }),
  ],
};

Calling provideNgxThreejs() is optional when the defaults are sufficient.

Create and mount the runtime

Call preload() in the component constructor to start resource and GPU work before its view and canvas are initialized. Its first call stores the options; subsequent calls, including the internal call made by mount(), reuse the same promise and must omit options. Guard the constructor call in applications that use SSR because Three.js asset loaders require browser APIs.

import { isPlatformBrowser } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  inject,
  PLATFORM_ID,
  viewChild,
} from '@angular/core';
import {
  POST_PROCESSING_DEFAULTS,
  ThreeRuntime,
  WorldContext,
} from '@new-immersion/ngx-threejs';
import * as THREE from 'three';

@Component({
  selector: 'app-three-scene',
  template: '<canvas #canvas></canvas>',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ThreeSceneComponent {
  private readonly runtime = inject(ThreeRuntime);
  private readonly world = inject(WorldContext);
  private readonly platformId = inject(PLATFORM_ID);
  private readonly canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');

  constructor() {
    if (!isPlatformBrowser(this.platformId)) return;

    void this.runtime.preload({
      sources: [
        { name: 'noise', type: 'texture', path: '/textures/noise.webp', weight: 1 },
        { name: 'scene', type: 'gltfModel', path: '/models/scene.glb', weight: 4 },
      ],
      detectGPU: true,
    });
  }

  async ngAfterViewInit(): Promise<void> {
    if (!isPlatformBrowser(this.platformId)) return;

    await this.runtime.mount({
      canvas: this.canvas().nativeElement,
      camera: { fov: 45, near: 0.1, far: 100, position: { z: 5 } },
      postProcessing: {
        ...POST_PROCESSING_DEFAULTS,
        enable: true,
      },
      antialias: true,
    });

    this.world.scene.add(new THREE.AmbientLight(0xffffff, 1));
  }
}

ready$ emits after the camera, renderer, scroll integration and render loop are ready.

Mount options

| Option | Purpose | | --- | --- | | canvas | Required canvas owned by the runtime. | | canvasColor | Clear color. Omit it to keep the canvas transparent. | | camera | Initial perspective camera values. | | postProcessing | Initial post-processing state, independent of debug mode. | | lenis | Enables Lenis and optionally ScrollTrigger integration. | | antialias | Enables renderer antialiasing. | | forceWebGL | Forces WebGL through WebGPURenderer. | | render | Replaces the default main-scene render callback. | | alertLowFps | Enables the sustained low-FPS warning. | | fpsAlertComponentOpts | Styles or replaces the built-in FPS warning. |

Scenes, camera and renderer

WorldContext owns the main scene and any additional named scenes:

const overlayScene = world.addScene('overlay');
world.destroyScene('overlay');

The runtime renders world.scene with CameraService.camera by default. Use a custom mount render callback for several scenes, render targets or post-processing:

await runtime.mount({
  canvas,
  render: () => {
    renderer.renderer.render(world.scenes['background'], camera.camera);
    renderer.renderer.render(world.scene, camera.camera);
  },
});

Useful camera APIs include:

  • enableOrbitControls() and disableOrbitControls();
  • isInView() for frustum checks;
  • syncCameraToScroll() and desyncCameraFromScroll();
  • computeElementWorldRect() for DOM-to-world projection.

Render loop, Timer and GSAP

Subscribe to RenderLoop.frame$ for per-frame updates. It emits immediately before rendering.

renderLoop.frame$.subscribe(({ elapsed, delta, frameId, timestamp }) => {
  mesh.rotation.y = elapsed * 0.25;
});
  • elapsed and delta are expressed in seconds.
  • timestamp is the raw animation timestamp in milliseconds.
  • Three.js Timer is connected to document, preventing a large delta after returning to a hidden tab.
  • frameId increases once per rendered frame.

The renderLoopDriver configuration accepts:

  • auto: uses GSAP when mount({ lenis: { useScrollTrigger: true } }) is enabled; otherwise it uses WebGPURenderer.setAnimationLoop();
  • renderer: uses the renderer loop and supports WebXR;
  • gsap: synchronizes Three.js rendering, GSAP and Lenis on the GSAP ticker.

Choose gsap explicitly when the application uses GSAP animations without the library's ScrollTrigger integration.

Resources and loading progress

Supported resource types are texture, cubeTexture and gltfModel. Resources are cached by name and can be retrieved with a type-safe expected type:

const texture = resources.getValue('noise', 'texture');
const gltf = resources.getValue('scene', 'gltfModel');

weight represents the relative cost of a source when byte progress is unavailable. For example, a model with weight: 4 contributes four times more to the overall progress than a texture with weight: 1.

resources.progress$.subscribe(({ status, ratio, sourceName }) => {
  const percentage = Math.round(ratio * 100);
});

Loading batches are atomic: a failed batch does not add partial results to the resource cache. Configure loaders.dracoDecoderPath with provideNgxThreejs(), or set it to null to disable DRACO.

Native scrolling, Lenis and ScrollTrigger

Omit lenis from mount() to use native scrolling. In both modes, ScrollService exposes:

  • scroll$ for position, velocity and direction;
  • input$ for raw wheel and touch input;
  • scrollTo() for programmatic scrolling;
  • refreshed$ after a completed ScrollTrigger refresh.

To enable Lenis, load the supplied global stylesheet in the application's angular.json:

{
  "styles": [
    "node_modules/@new-immersion/ngx-threejs/styles/_ngx-threejs-with-lenis.scss",
    "src/styles.scss"
  ]
}

Create the required layout using the exported IDs:

<div [id]="SCROLL_EVENTS_TARGET">
  <div [id]="SCROLL_WRAPPER">
    <main [id]="SCROLL_CONTENT">
      <router-outlet />
    </main>
  </div>
</div>
readonly SCROLL_EVENTS_TARGET = SCROLL_EVENTS_TARGET;
readonly SCROLL_WRAPPER = SCROLL_WRAPPER;
readonly SCROLL_CONTENT = SCROLL_CONTENT;

Then mount with Lenis:

await runtime.mount({
  canvas,
  lenis: {
    options: { lerp: 0.1 },
    useScrollTrigger: true,
  },
});

When ScrollTrigger is enabled, the Lenis wrapper becomes its default scroller. Individual triggers do not need to repeat the scroller option. ScrollControllerService adds scroll locks, anchor handling and user-scroll direction detection.

Pointer input and raycasting

PointerService tracks positions relative to the runtime canvas in screen and normalized device coordinates. All supported pointer events are enabled initially; disable unused events when a project does not need them. Events can be enabled or disabled independently:

pointer.togglePointerEvent('pointermove', true);
pointer.togglePointerEvent('pointerdown', true);
pointer.togglePointerEvent('pointercancel', false);

pointer.getEvent$('pointerdown').subscribe(() => {
  const current = pointer.pointers[0];
  if (!current) return;

  const hits = pointer.intersectObjects(current, selectableObjects);
});
  • getRay() creates a world-space ray without using Raycaster.
  • getWorldPosition() projects onto a world XY plane at a selected Z value.
  • intersectPlane() works with any THREE.Plane.
  • intersectObjects() uses the reusable raycaster exposed by pointer.raycaster.

Pass reusable targets to the intersection methods when avoiding per-call allocations matters.

TSL DOM images

ThreejsImageComponent synchronizes an HTML image and a Three.js plane. The application owns the TSL material:

import {
  ThreejsImageComponent,
  ThreejsImageMaterialFactory,
} from '@new-immersion/ngx-threejs';
import { MeshBasicNodeMaterial } from 'three/webgpu';

readonly materialFactory: ThreejsImageMaterialFactory = ({ texture }) => {
  const material = new MeshBasicNodeMaterial();
  material.colorNode = texture;
  return material;
};
<ngx-threejs-image
  resourceName="hero"
  imageAlt="Product preview"
  [materialFactory]="materialFactory"
/>

Use syncMode="fast" for stable layouts and syncMode="accurate" for sticky, pinned or otherwise dynamic layouts. The created output exposes the mesh and generated image element.

Three.js Inspector

Debug mode uses the Inspector distributed with Three.js. Tweakpane is not required.

provideNgxThreejs({
  debug: {
    enable: true,
    localStoragePrefix: 'my-project',
    parameterServices: [
      CameraParametersService,
      CanvasParametersService,
      PostProcessingParametersService,
      ToneMappingParametersService,
      SceneParametersService,
    ],
  },
});

CameraParametersService, CanvasParametersService, PostProcessingParametersService and ToneMappingParametersService are optional built-in groups. Import and register only the ones the application needs. The post-processing group enables a RenderPipeline with an optional Bloom effect, Pixelation, chromatic aberration and FXAA. Each effect can be toggled live. Pixelation is always composed first. Intermediate effects expose an Order priority in their folder and are composed from the lowest value to the highest; equal values keep their declaration order. FXAA is always last because it requires color-transformed sRGB input. When the global group is disabled, rendering falls back to the main scene directly.

The Bloom mask toggle controls the default bloomMask MRT output (0 or 1). Set it to false for selective Bloom, then opt individual node materials in from the application:

postProcessing.setBloomMask(material);

The service removes the material MRT automatically whenever Bloom or the complete post-processing pipeline is disabled, and restores it when Bloom is enabled again. Call clearBloomMask(material) when the material should no longer participate.

Application parameter groups extend BaseInspectorParameters:

import { Injectable } from '@angular/core';
import { BaseInspectorParameters } from '@new-immersion/ngx-threejs';

@Injectable()
export class SceneParametersService extends BaseInspectorParameters<
  typeof SceneParametersService.DEFAULTS
> {
  static readonly DEFAULTS = { speed: 1, bloom: 0.25 };

  constructor() {
    super(SceneParametersService.DEFAULTS, 'Scene', 'scene');
  }

  protected createParameters(): void {
    this.mainGroup.add(this.values, 'speed', 0, 2, 0.01).name('Speed');

    const effects = this.createFolder('effects', 'Effects');
    effects.add(this.values, 'bloom', 0, 1, 0.01).name('Bloom');
  }
}

Built-in groups can be extended without copying their implementation. Subclass the service, call its implementation first, then add controls to mainGroup or retrieve one of its folders with getGroup():

import { Injectable, inject } from '@angular/core';
import { CameraParametersService, CameraService } from '@new-immersion/ngx-threejs';

@Injectable()
export class ProjectCameraParametersService extends CameraParametersService {
  private readonly camera = inject(CameraService);
  private readonly projectValues = { fieldOfView: 50 };

  protected override createParameters(): void {
    super.createParameters();

    this.getGroup('transform')
      .add(this.projectValues, 'fieldOfView', 20, 100, 1)
      .name('Field of view')
      .onChange((value) => {
        this.camera.camera.fov = value;
        this.camera.camera.updateProjectionMatrix();
      });
  }
}

Register the subclass instead of CameraParametersService. Camera folder IDs are orbitControls, transform, transform.position and transform.rotation. The canvas and tone mapping services currently expose only their main group. The post-processing service exposes the pixelation, bloom, chromaticAberration, chromaticAberration.center and fxaa folders.

Values owned by a BaseInspectorParameters service, folder states and the main Parameters panel state are persisted in localStorage. Extra controls that bind another service keep that service as their source of truth. The Inspector keeps native scrolling when Lenis is active.

TransformControlsParametersService can also be registered in parameterServices and populated with addAvailableTarget().

Low-FPS warning

The warning is enabled by default and can be disabled with alertLowFps: false. It appears after sustained performance around 30 FPS or below. It ignores hidden tabs, initial startup and isolated long frames. The message presents energy-saving mode as a possible cause; the browser does not expose a reliable way to detect that mode directly.

await runtime.mount({
  canvas,
  alertLowFps: true,
  fpsAlertComponentOpts: {
    cssBgColor: '#ffffff',
    cssColor: '#18181b',
    cssMoreBtnColor: '#2563eb',
  },
});

Use customComponent to replace the default design or localize its content. The custom component must accept text, textMore and componentRef inputs.

Device capability

Enable GPU detection during preload, then inject DetectGpuService to read gpuTier or the simplified devicePower value (low, medium or high). Detection failures resolve to null instead of failing runtime initialization.

Public services

| Service | Responsibility | | --- | --- | | ThreeRuntime | Preload and mount orchestration. | | WorldContext | Main scene, named scenes and disposal helpers. | | RendererService | WebGPU/WebGL renderer access and rendering. | | CameraService | Camera, controls, frustum and DOM projection. | | RenderLoop | Shared Three.js, GSAP and Lenis clock. | | Viewport | Stabilized canvas size, resolution and resize notifications. | | ResourcesService | Resource batches, cache and weighted progress. | | ScrollService | Native or Lenis scrolling and ScrollTrigger integration. | | ScrollControllerService | Locks, anchors and high-level scroll flows. | | PointerService | Canvas pointer state, rays and object intersections. | | DebugService | Native Three.js Inspector lifecycle. | | PostProcessingService | Main scene render pipeline and post-processing effects. | | Built-in parameter services | Optional Camera, Canvas, Post-processing and Tone-mapping Inspector groups. | | DetectGpuService | Optional GPU-tier detection. |

Build the library

From the workspace root:

npm run build -- ngx-threejs

The publishable package is generated in dist/ngx-threejs.