DomElement3D.js

import * as THREE from 'three';
import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer';
import { Color } from 'three';

/**
 * Material making an "hole" in a {@link THREE.Scene} to see html css3D behind
 *
 * @type {THREE.MeshBasicMaterial}
 */
const BLANK_MATERIAL = new THREE.MeshBasicMaterial({
  side: THREE.DoubleSide,
  opacity: 0,
  transparent: true,
  blending: THREE.NoBlending,
  color: new Color(0, 0, 0),
});

/** @class */
export class DomElement3D extends THREE.Object3D {
  /**
   * Composed of a {@link CSS3DObject} containing html and a {@link THREE.Object3D} superposing each other
   *
   * @param {HTMLElement} domElement - dom element
   * @param {number} scalar - scale domelement content
   */
  constructor(domElement, scalar = 1) {
    super();

    /**
     * uuid
     *
      @type {string} */
    this.uuid = THREE.MathUtils.generateUUID();

    /**
     * html element of css3Dobject
     *
      @type {HTMLElement} */
    this.domElement = domElement;

    /** @type {number} */
    this.scalar = scalar;

    /**
     * css3D object
     *
      @type {CSS3DObject}  */
    this.css3DObject = new CSS3DObject(this.domElement);

    /**
     * mask superposing css3DObject
     *
      @type {THREE.Object3D} */
    this.maskObject = new THREE.Mesh(new THREE.PlaneGeometry(), BLANK_MATERIAL);
    this.add(this.maskObject);

    /**
     * selected (css style is different if true or false)
     *
      @type {boolean} */
    this.isSelected = false;
    this.select(this.isSelected);
  }

  updateMatrixWorld(...args) {
    super.updateMatrixWorld(...args);

    const worldPosition = new THREE.Vector3();
    const worldQuaternion = new THREE.Quaternion();
    const worldScale = new THREE.Vector3();

    this.maskObject.matrixWorld.decompose(
      worldPosition,
      worldQuaternion,
      worldScale
    );

    // update also css element
    this.css3DObject.position.copy(worldPosition);
    this.css3DObject.quaternion.copy(worldQuaternion);
    this.css3DObject.scale.copy(
      new THREE.Vector3(1 / this.scalar, 1 / this.scalar, 1 / this.scalar)
    );

    this.domElement.style.width = this.scalar * worldScale.x + 'px';
    this.domElement.style.height = this.scalar * worldScale.y + 'px';
  }

  /**
   * Set if this is selected or not and update css style
   *
   * @param {boolean} value - new selected value
   */
  select(value) {
    this.isSelected = value;
    if (value) {
      this.domElement.style.filter = 'grayscale(0%)';
    } else {
      this.domElement.style.filter = 'grayscale(100%)';
    }
  }
}