介绍:css3D标签作为three中最重要的部分之一,却无法实现3D空间的有效遮挡顺序。本文参考babylonjs的源代码实现了threejs的可遮挡css3D标签,具体思路是用利用材质的noblending特性作为遮罩,使得下层的css3Ddom变成可见。
源代码:
import {
Matrix4,
Object3D,
Quaternion,
Vector3,
Mesh,
PlaneGeometry,
MeshBasicMaterial,
NoBlending,
DoubleSide,
Color
} from 'three'
/**
* Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs
*/
const _position = new Vector3()
const _quaternion = new Quaternion()
const _scale = new Vector3()
class JCSS3DObject extends Object3D {
constructor(element = document.createElement('div')) {
super()
this.isJCSS3DObject = true
this.element = element
this.element.style.position = 'absolute'
this.element.style.pointerEvents = 'auto'
this.element.style.userSelect = 'none'
const width = Number(this.element.style.width.replace('px', ''))
const height = Number(this.element.style.height.replace('px', ''))
console.log(width,height)
const maskGeo = new PlaneGeometry(width,height)
const maskMaterial = new MeshBasicMaterial({
transparent:true,
opacity:0,
blending:NoBlending,
color:new Color(0x000000),
side:DoubleSide
})
const mask = new Mesh(maskGeo,maskMaterial)
this.add(mask)
this.element.setAttribute('draggable', false)
this.addEventListener('removed', function() {
this.traverse(function(object) {
if (
object.element instanceof object.element.ownerDocument.defaultView.Element &&
object.element.parentNode !== null
) {
object.element.remove()
}
})
})
}
copy(source, recursive) {
super.copy(source, recursive)
this.element = source.element.cloneNode(true)
return this
}
}
class JCSS3DSprite extends JCSS3DObject {
constructor(element) {
super(element)
this.isJCSS3DSprite = true
this.rotation2D = 0
}
copy(source, recursive) {
super.copy(source, recursive)
this.rotation2D = source.rotation2D
return this
}
}
//
const _matrix = new Matrix4()
const _matrix2 = new Matrix4()
class JCSS3DRenderer {
constructor(parameters = {}) {
const _this = this
let _width, _height
let _widthHalf, _heightHalf
const cache = {
camera: { style: '' },
objects: new WeakMap()
}
const domElement = parameters.element !== undefined ? parameters.element : document.createElement('div')
domElement.style.overflow = 'hidden'
domElement.style.position = 'absolute'
domElement.style.top = 0
domElement.style.zIndex = -1
this.domElement = domElement
const viewElement = document.createElement('div')
viewElement.style.transformOrigin = '0 0'
viewElement.style.pointerEvents = 'none'
domElement.appendChild(viewElement)
const cameraElement = document.createElement('div')
cameraElement.style.transformStyle = 'preserve-3d'
viewElement.appendChild(cameraElement)
this.getSize = function() {
return {
width: _width,
height: _height
}
}
this.render = function(scene, camera) {
const fov = camera.projectionMatrix.elements[ 5 ] * _heightHalf
if (camera.view && camera.view.enabled) {
// view offset
viewElement.style.transform = `translate( ${-camera.view.offsetX * (_width / camera.view.width)}px, ${-camera.view.offsetY * (_height / camera.view.height)}px )`
// view fullWidth and fullHeight, view width and height
viewElement.style.transform += `scale( ${camera.view.fullWidth / camera.view.width}, ${camera.view.fullHeight / camera.view.height} )`
} else {
viewElement.style.transform = ''
}
if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld()
if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld()
let tx, ty
if (camera.isOrthographicCamera) {
tx = -(camera.right + camera.left) / 2
ty = (camera.top + camera.bottom) / 2
}
const scaleByViewOffset = camera.view && camera.view.enabled ? camera.view.height / camera.view.fullHeight : 1
const cameraCSSMatrix = camera.isOrthographicCamera
? `scale( ${scaleByViewOffset} )` + 'scale(' + fov + ')' + 'translate(' + epsilon(tx) + 'px,' + epsilon(ty) + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse)
: `scale( ${scaleByViewOffset} )` + 'translateZ(' + fov + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse)
const perspective = camera.isPerspectiveCamera ? 'perspective(' + fov + 'px) ' : ''
const style = perspective + cameraCSSMatrix +
'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)'
if (cache.camera.style !== style) {
cameraElement.style.transform = style
cache.camera.style = style
}
renderObject(scene, scene, camera, cameraCSSMatrix)
}
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'
viewElement.style.width = width + 'px'
viewElement.style.height = height + 'px'
cameraElement.style.width = width + 'px'
cameraElement.style.height = height + 'px'
}
function epsilon(value) {
return Math.abs(value) < 1e-10 ? 0 : value
}
function getCameraCSSMatrix(matrix) {
const elements = matrix.elements
return 'matrix3d(' +
epsilon(elements[ 0 ]) + ',' +
epsilon(-elements[ 1 ]) + ',' +
epsilon(elements[ 2 ]) + ',' +
epsilon(elements[ 3 ]) + ',' +
epsilon(elements[ 4 ]) + ',' +
epsilon(-elements[ 5 ]) + ',' +
epsilon(elements[ 6 ]) + ',' +
epsilon(elements[ 7 ]) + ',' +
epsilon(elements[ 8 ]) + ',' +
epsilon(-elements[ 9 ]) + ',' +
epsilon(elements[ 10 ]) + ',' +
epsilon(elements[ 11 ]) + ',' +
epsilon(elements[ 12 ]) + ',' +
epsilon(-elements[ 13 ]) + ',' +
epsilon(elements[ 14 ]) + ',' +
epsilon(elements[ 15 ]) +
')'
}
function getObjectCSSMatrix(matrix) {
const elements = matrix.elements
const matrix3d = 'matrix3d(' +
epsilon(elements[ 0 ]) + ',' +
epsilon(elements[ 1 ]) + ',' +
epsilon(elements[ 2 ]) + ',' +
epsilon(elements[ 3 ]) + ',' +
epsilon(-elements[ 4 ]) + ',' +
epsilon(-elements[ 5 ]) + ',' +
epsilon(-elements[ 6 ]) + ',' +
epsilon(-elements[ 7 ]) + ',' +
epsilon(elements[ 8 ]) + ',' +
epsilon(elements[ 9 ]) + ',' +
epsilon(elements[ 10 ]) + ',' +
epsilon(elements[ 11 ]) + ',' +
epsilon(elements[ 12 ]) + ',' +
epsilon(elements[ 13 ]) + ',' +
epsilon(elements[ 14 ]) + ',' +
epsilon(elements[ 15 ]) +
')'
return 'translate(-50%,-50%)' + matrix3d
}
function hideObject(object) {
if (object.isCSS3DObject) object.element.style.display = 'none'
for (let i = 0, l = object.children.length; i < l; i++) {
hideObject(object.children[ i ])
}
}
function renderObject(object, scene, camera, cameraCSSMatrix) {
if (object.visible === false) {
hideObject(object)
return
}
if (object.isJCSS3DObject) {
const visible = (object.layers.test(camera.layers) === true)
const element = object.element
element.style.display = visible === true ? '' : 'none'
if (visible === true) {
object.onBeforeRender(_this, scene, camera)
let style
if (object.isJCSS3DSprite) {
// http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/
_matrix.copy(camera.matrixWorldInverse)
_matrix.transpose()
if (object.rotation2D !== 0) _matrix.multiply(_matrix2.makeRotationZ(object.rotation2D))
object.matrixWorld.decompose(_position, _quaternion, _scale)
_matrix.setPosition(_position)
_matrix.scale(_scale)
_matrix.elements[ 3 ] = 0
_matrix.elements[ 7 ] = 0
_matrix.elements[ 11 ] = 0
_matrix.elements[ 15 ] = 1
style = getObjectCSSMatrix(_matrix)
} else {
style = getObjectCSSMatrix(object.matrixWorld)
}
const cachedObject = cache.objects.get(object)
if (cachedObject === undefined || cachedObject.style !== style) {
element.style.transform = style
const objectData = { style: style }
cache.objects.set(object, objectData)
}
if (element.parentNode !== cameraElement) {
cameraElement.appendChild(element)
}
object.onAfterRender(_this, scene, camera)
}
}
for (let i = 0, l = object.children.length; i < l; i++) {
renderObject(object.children[ i ], scene, camera, cameraCSSMatrix)
}
}
}
}
export { JCSS3DObject, JCSS3DSprite, JCSS3DRenderer }
使用:
import { JCSS3DObject, JCSS3DSprite, JCSS3DRenderer } from 'JCSS3DRenderer'
标签渲染器挂载dom元素
this.cssRenderer = new JCSS3DRenderer();
this.cssRenderer.setSize(window.innerWidth, window.innerHeight);
this.cssRenderer.domElement.style.position = 'absolute';
this.cssRenderer.domElement.style.top = 0;
this.cssRenderer.domElement.style.zIndex = -1;
document.body.appendChild(this.cssRenderer.domElement);
添加css3ddbject或者css3dspite
const element = document.getElementById('text')
const cssObject = new JCSS3DObject(element);
this.scene.add(cssObject);
帧渲染
function animate () {
renderer.render(scene, camera);
cssRenderer.render(scene, camera);
}
render.setAnimationLoop(animate)