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>