NgtsHTML
import { ChangeDetectionStrategy, Component } from '@angular/core';import { SobaWrapper } from '@soba/wrapper.ts';import { NgtCanvas, provideNgtRenderer } from 'angular-three/dom';import { SceneGraph } from './scene-graph';
@Component({ selector: 'app-html', template: ` <ngt-canvas [camera]="{ position: [0, 0, 5], fov: 50 }"> <app-soba-wrapper *canvasContent> <app-scene-graph /> </app-soba-wrapper> </ngt-canvas> `, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'html-demo relative block h-full' }, imports: [NgtCanvas, SobaWrapper, SceneGraph],})export default class Html { static clientProviders = [provideNgtRenderer()];}import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, viewChild } from '@angular/core';import { beforeRender, NgtArgs } from 'angular-three';import { NgtsHTML } from 'angular-three-soba/misc';import * as THREE from 'three';
@Component({ selector: 'app-scene-graph', template: ` <!-- Rotating cube with HTML label that follows it --> <ngt-mesh #cube [position]="[0, 0, 0]"> <ngt-box-geometry *args="[1.5, 1.5, 1.5]" /> <ngt-mesh-standard-material color="#4ecdc4" />
<!-- HTML label attached to the mesh --> <ngts-html [options]="{ position: [0, 1.2, 0], center: true }"> <div htmlContent style=" background: rgba(78, 205, 196, 0.95); color: white; padding: 8px 16px; border-radius: 8px; font-size: 14px; font-weight: bold; white-space: nowrap; box-shadow: 0 4px 12px rgba(0,0,0,0.3); " > I follow the cube! </div> </ngts-html> </ngt-mesh>
<!-- Static HTML annotation in 3D space --> <ngts-html [options]="{ position: [-2.5, 0, 0], transform: true }"> <div htmlContent [distanceFactor]="8" style=" color: white; padding: 12px 20px; border-radius: 12px; font-size: 16px; font-weight: bold; text-align: center; " > 3D Transform Mode <div style="font-size: 11px; opacity: 0.9; margin-top: 4px;">Scales with distance</div> </div> </ngts-html>
<!-- Another annotation --> <ngts-html [options]="{ position: [2.5, 0, 0] }"> <div htmlContent [center]="true" style=" background: rgba(162, 155, 254, 0.95); color: white; padding: 8px 16px; border-radius: 8px; font-size: 14px; white-space: nowrap; box-shadow: 0 4px 12px rgba(0,0,0,0.3); " > Screen-space HTML </div> </ngts-html> `, schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgtArgs, NgtsHTML],})export class SceneGraph { private cubeRef = viewChild<ElementRef<THREE.Mesh>>('cube');
constructor() { beforeRender(({ delta }) => { const cube = this.cubeRef()?.nativeElement; if (cube) { cube.rotation.y += delta * 0.5; cube.rotation.x += delta * 0.3; } }); }}NgtsHTML renders HTML content positioned in 3D space. It creates a THREE.Group anchor point in the scene and projects HTML onto the canvas using CSS positioning or CSS 3D transforms.
Usage
import { NgtsHTML } from 'angular-three-soba/misc';
@Component({ imports: [NgtsHTML], template: ` <ngt-mesh [position]="[0, 2, 0]"> <ngts-html [options]="{ transform: true }"> <div [htmlContent]="{ distanceFactor: 10 }">Label</div> </ngts-html> </ngt-mesh> `})class MyComponent {}NgtsHTMLContentOptions (for div[htmlContent])
| Property | Description | Default |
|---|---|---|
distanceFactor | Scales HTML based on distance from camera | undefined |
center | Centers the HTML element on the projected point | false |
sprite | When true (with transform), HTML always faces the camera | false |
zIndexRange | Range for automatic z-index calculation [max, min] | [16777271, 0] |
containerClass | CSS class applied to the inner container div | '' |
containerStyle | Inline styles applied to the inner container div | {} |
pointerEvents | CSS pointer-events value | 'auto' |
Outputs
| Output | Description |
|---|---|
occluded | Emits when occlusion state changes (true = hidden, false = visible) |
Options
Properties
| name | type | description |
|---|---|---|
| occlude | boolean | 'raycast' | 'blending' | Object3D[] | Controls occlusion behavior. Default: false |
| transform | boolean | When true, uses CSS 3D transforms. When false, projects to 2D screen coordinates. Default: false |
| castShadow | boolean | Forward shadow casting to occlusion mesh (blending mode only). Default: false |
| receiveShadow | boolean | Forward shadow receiving to occlusion mesh (blending mode only). Default: false |
A component for rendering HTML content positioned in 3D space. Creates a THREE.Group anchor point in the scene and projects HTML onto the canvas using CSS positioning or CSS 3D transforms.
Import NgtsHTML which includes both NgtsHTMLImpl (the 3D anchor) and NgtsHTMLContent (the HTML container).
import { NgtsHTML } from 'angular-three-soba/misc';
@Component({ imports: [NgtsHTML], template: ` <ngt-mesh [position]="[0, 2, 0]"> <ngts-html [options]="{ transform: true }"> <div [htmlContent]="{ distanceFactor: 10 }">Label</div> </ngts-html> </ngt-mesh> `,})class MyComponent {}NgtsHTMLOptions (for ngts-html)
Section titled âNgtsHTMLOptions (for ngts-html)â| Property | Description | Default |
|---|---|---|
occlude | Controls occlusion: false, true, 'raycast', 'blending', or array of Object3D refs | false |
transform | When true, uses CSS 3D transforms. When false, projects to 2D screen coordinates | false |
castShadow | Forward shadow casting to occlusion mesh (blending mode only) | false |
receiveShadow | Forward shadow receiving to occlusion mesh (blending mode only) | false |
NgtsHTMLContentOptions (for div[htmlContent])
Section titled âNgtsHTMLContentOptions (for div[htmlContent])â| Property | Description | Default |
|---|---|---|
eps | Epsilon for position/zoom change detection | 0.001 |
zIndexRange | Range for automatic z-index calculation [max, min] | [16777271, 0] |
center | Centers the HTML element on the projected point | false |
prepend | Prepends to parent instead of appending | false |
fullscreen | Makes the container fill the entire canvas size | false |
containerClass | CSS class applied to the inner container div | '' |
containerStyle | Inline styles applied to the inner container div | {} |
pointerEvents | CSS pointer-events value | 'auto' |
calculatePosition | Custom function to calculate screen position | defaultCalculatePosition |
sprite | When true (with transform), HTML always faces the camera | false |
distanceFactor | Scales HTML based on distance from camera | undefined |
parent | Custom parent element for the HTML content | undefined |
Outputs
Section titled âOutputsâ| Output | Description |
|---|---|
occluded | Emits when occlusion state changes (true = hidden, false = visible) |
Examples
Section titled âExamplesâBasic Label
Section titled âBasic Labelâ<ngt-mesh [position]="[0, 1, 0]"> <ngt-box-geometry /> <ngt-mesh-standard-material /> <ngts-html> <div [htmlContent]="{ center: true }"> <span class="label">Hello World</span> </div> </ngts-html></ngt-mesh>3D Transform Mode
Section titled â3D Transform Modeâ<ngts-html [options]="{ transform: true }"> <div [htmlContent]="{ distanceFactor: 10, sprite: true }"> <div class="card"> <h2>Title</h2> <p>Description text</p> </div> </div></ngts-html>Occlusion Handling
Section titled âOcclusion Handlingâ<ngts-html [options]="{ occlude: true }"> <div [htmlContent]="{}" (occluded)="isHidden = $event" [class.faded]="isHidden"> Content with custom occlusion handling </div></ngts-html>Occlusion with Specific Objects
Section titled âOcclusion with Specific Objectsâ<ngts-html [options]="{ occlude: [wallRef(), floorRef()] }"> <div [htmlContent]="{}">Hidden when wall or floor blocks view</div></ngts-html>Fullscreen Overlay
Section titled âFullscreen Overlayâ<ngts-html> <div [htmlContent]="{ fullscreen: true, pointerEvents: 'none' }"> <div class="hud"> <div class="score">Score: 100</div> </div> </div></ngts-html>Custom Position Calculation
Section titled âCustom Position Calculationâconst customCalculatePosition = (el, camera, size) => { // Custom positioning logic return [x, y];};<ngts-html> <div [htmlContent]="{ calculatePosition: customCalculatePosition }">Custom positioned content</div></ngts-html>