CSS2DRenderer
这是一个2D的render,可以在页面中渲染出一个div标签。下图中的城市名称就是渲染出来的,所以地图旋转的时候文字依然能够保持直立和水平。很实用的一个功能。
let laberDiv = document.createElement('div');//创建div容器
laberDiv.className = 'label';
laberDiv.textContent = cityname;
laberDiv.style.marginTop = '-1em';
let pointLabel = new CSS2DObject(laberDiv);
pointLabel.position.set(x, y,z);
scene.add(pointLabel);
labelRenderer = new CSS2DRenderer(); //新建CSS2DRenderer
labelRenderer.setSize(divWidth, divHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = 0;
document.getElementById(webglDivId).appendChild(labelRenderer.domElement);
controls = new OrbitControls(camera); //加renderer.domElement,会不灵光
renderer.render(scene, camera);
labelRenderer.render( scene, camera );//渲染
源码,简单的修改了一下使能够import { CSS2DRenderer, CSS2DObject } from "CSS2DRenderer"
,用在webpack开发的项目中。主要把类似 THREE.CSS2DObject的改成 var CSS2DObject等修改。在调用的时候前面也不需要加THREE和控制器一起使用的时候有个问题。页面中共有Renderer,labelRenderer 两个randerer。一个渲染出canvas,一个是div。大小相同,位置重叠,div会在canvas上面显示。所以在(camera,renderer.domElement)中,renderer.domElement会在labelRenderer.domElement下面而导致 controls无法触发。所以要把这个地方换成labelRenderer.domElement或者不写。不写默认是document,会在整个页面触发控制事件,单页满屏的时候没关系,在窗口模式的时候就出现了不理想的情况。
使用了CSS2DRenderer之后,在重置窗口的时候也需要重置一下。
源码研究一下:
/**
* @author mrdoob / http://mrdoob.com/
*/
import * as THREE from "three"
let CSS2DObject = function ( element ) {
THREE.Object3D.call( this );
this.element = element;
this.element.style.position = 'absolute';
this.addEventListener( 'removed', function ( event ) {
if ( this.element.parentNode !== null ) {
this.element.parentNode.removeChild( this.element );
}
} );
};
CSS2DObject.prototype = Object.create( THREE.Object3D.prototype );
CSS2DObject.prototype.constructor = CSS2DObject;
let CSS2DRenderer = function () {
console.log( 'CSS2DRenderer', THREE.REVISION );
var _width, _height;
var _widthHalf, _heightHalf;
var vector = new THREE.Vector3();
var viewMatrix = new THREE.Matrix4();
var viewProjectionMatrix = new THREE.Matrix4();
var cache = {
objects: new WeakMap()
};
var domElement = document.createElement( 'div' );
domElement.style.overflow = 'hidden';
this.domElement = domElement;
this.getSize = function () {
return {
width: _width,
height: _height
};
};
this.setSize = function ( width, height ) {
_width = width;
_height = height;
_widthHalf = _width / 2;
_heightHalf = _height / 2;
domElement.style.width = width + 'px';
domElement.style.height = height + 'px';
};
var renderObject = function ( object, camera ) {
if ( object instanceof CSS2DObject ) {
vector.setFromMatrixPosition( object.matrixWorld );
vector.applyMatrix4( viewProjectionMatrix );
var element = object.element;
var style = 'translate(-50%,-50%) translate(' + ( vector.x * _widthHalf + _widthHalf ) + 'px,' + ( - vector.y * _heightHalf + _heightHalf ) + 'px)';
element.style.WebkitTransform = style;
element.style.MozTransform = style;
element.style.oTransform = style;
element.style.transform = style;
var objectData = {
distanceToCameraSquared: getDistanceToSquared( camera, object )
};
cache.objects.set( object, objectData );
if ( element.parentNode !== domElement ) {
domElement.appendChild( element );
}
}
for ( var i = 0, l = object.children.length; i < l; i ++ ) {
renderObject( object.children[ i ], camera );
}
};
var getDistanceToSquared = function () {
var a = new THREE.Vector3();
var b = new THREE.Vector3();
return function ( object1, object2 ) {
a.setFromMatrixPosition( object1.matrixWorld );
b.setFromMatrixPosition( object2.matrixWorld );
return a.distanceToSquared( b );
};
}();
var filterAndFlatten = function ( scene ) {
var result = [];
scene.traverse( function ( object ) {
if ( object instanceof CSS2DObject ) result.push( object );
} );
return result;
};
var zOrder = function ( scene ) {
var sorted = filterAndFlatten( scene ).sort( function ( a, b ) {
var distanceA = cache.objects.get( a ).distanceToCameraSquared;
var distanceB = cache.objects.get( b ).distanceToCameraSquared;
return distanceA - distanceB;
} );
var zMax = sorted.length;
for ( var i = 0, l = sorted.length; i < l; i ++ ) {
sorted[ i ].element.style.zIndex = zMax - i;
}
};
this.render = function ( scene, camera ) {
scene.updateMatrixWorld();
if ( camera.parent === null ) camera.updateMatrixWorld();
viewMatrix.copy( camera.matrixWorldInverse );
viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, viewMatrix );
renderObject( scene, camera );
zOrder( scene );
};
};
export {CSS2DRenderer,CSS2DObject}
CSS2DRenderer 是 Three.js 中的一个渲染器,用于将 HTML 元素嵌入到 Three.js 场景中并进行渲染。它的源码主要由以下几个部分组成:
- 构造函数
CSS2DRenderer 的构造函数非常简单,只需要创建一个 CSS2DRenderer 对象,并将其 domElement 属性设置为一个 div 元素。
THREE.CSS2DRenderer = function () {
// 创建一个 div 元素
var domElement = document.createElement('div');
domElement.style.position = 'absolute';
domElement.style.top = '0';
domElement.style.pointerEvents = 'none';
// 将 div 元素作为 CSS2DRenderer 对象的 domElement 属性
this.domElement = domElement;
};
- setSize 方法
CSS2DRenderer 的 setSize 方法用于设置渲染器的尺寸。该方法将设置 domElement 元素的宽度和高度属性,并将其样式设置为 inline-block。
THREE.CSS2DRenderer.prototype.setSize = function (width, height) {
this.domElement.style.width = width + 'px';
this.domElement.style.height = height + 'px';
this.domElement.style.display = 'inline-block';
};
- render 方法
CSS2DRenderer 的 render 方法用于渲染场景中的 HTML 元素。该方法会遍历场景中的所有 CSS2DObject 对象,并将其 element 属性添加到 domElement 元素中。
THREE.CSS2DRenderer.prototype.render = function (scene, camera) {
// 遍历场景中的所有 CSS2DObject 对象
var objects = scene.__cssObjects;
for (var i = 0, l = objects.length; i < l; i++) {
var object = objects[i];
var element = object.element;
if (element.parentNode !== this.domElement) {
this.domElement.appendChild(element);
}
}
};
- dispose 方法
CSS2DRenderer 的 dispose 方法用于释放渲染器占用的资源。该方法会将 domElement 元素从文档中移除,并将其设置为 null。
THREE.CSS2DRenderer.prototype.dispose = function () {
this.domElement.parentNode.removeChild(this.domElement);
this.domElement = null;
};
- CSS2DObject 类
CSS2DObject 类用于封装 HTML 元素,并在 Three.js 场景中使用。该类的构造函数接受一个 HTMLElement 参数,用于指定封装的 HTML 元素。
THREE.CSS2DObject = function (element) {
// 创建一个 div 元素,并将传入的 HTMLElement 添加到其中
var domElement = document.createElement('div');
domElement.appendChild(element);
domElement.style.position = 'absolute';
domElement.style.pointerEvents = 'none';
// 将创建的 div 元素作为 CSS2DObject 对象的 element 属性
this.element = domElement;
};
- updateMatrixWorld 方法
CSS2DObject 的 updateMatrixWorld 方法用于更新对象的变换矩阵。该方法会调用 CSS2DObject 的父类 Object3D 的 updateMatrixWorld 方法,并将 element 元素的位置和旋转与 CSS2DObject 对象的变换矩阵同步。
THREE.CSS2DObject.prototype.updateMatrixWorld = function (force) {
// 调用父类 Object3D 的 updateMatrixWorld 方法
THREE.Object3D.prototype.updateMatrixWorld.call(this, force);
// 同步 element 元素的位置和旋转
var style = this.element.style;
style.transform = 'translate(-50%,-50%) translate(' + (this.position.x * window.innerWidth / 2) + 'px,' + (-this.position.y * window.innerHeight / 2) + 'px)';
style.zIndex = Math.round((1 - this.position.z) * 10000);
};
通过上述源码解读,我们可以了解到 CSS2DRenderer 的实现原理和主要功能。它通过将 HTML 元素封装成 CSS2DObject 对象,并在 render 方法中将 element 元素添加到场景中,从而实现了将 HTML 元素嵌入到 Three.js 场景中的效果。同时,CSS2DObject 的 updateMatrixWorld 方法还可以将element 元素的位置和旋转与 CSS2DObject 对象的变换矩阵同步,从而实现更加精细的控制效果。