该示例是在3D场景中使用html绘制了一个立方体,立方体前后左右四面都是挂载展示视频。该示例实现起来并不复杂,但通过学习该示例,我们能比较熟练地在3D场景中灵活运用dom元素。
一、示例介绍
二、实现讲解
1. 实现思路
立方体前后左右四面,实际上是四个div元素,通过使用CSS3DObject将这几个div元素渲染成3D对象,控制其position 和 rotation 属性,这样看起来就是立方体的四个面了。此外,每个面的div 元素里,使用iframe内嵌了youtebe的视频。
2. 视频墙方法封装
(1)使用原生document.createElement 方法,生成一个div元素
(2)生成一个iframe对象,并改变其src值。并在iframe设置在div层内
(3)使用 CSS3DObject 对div进行3D对象处理。这样就可以灵活控制其在3D场景中的position和 rotation
(4)Element( id, x, y, z, ry ) 接受的参数分别是:
id: 视频ID号
x: position 的 x 轴位置
y: position 的 y 轴位置
z: position 的 z 轴位置
ry: rotation 中绕y轴旋转角度
function Element( id, x, y, z, ry ) {
const div = document.createElement( 'div' );
div.style.width = '480px';
div.style.height = '360px';
div.style.backgroundColor = '#000';
const iframe = document.createElement( 'iframe' );
iframe.style.width = '480px';
iframe.style.height = '360px';
iframe.style.border = '0px';
iframe.src = [ 'https://www.youtube.com/embed/', id, '?rel=0' ].join( '' );
div.appendChild( iframe );
const object = new CSS3DObject( div );
object.position.set( x, y, z );
object.rotation.y = ry;
return object;
}
3. 主体方法实现
(1)常规步骤:声明相机、场景
(2)使用轨迹球控制器TrackballControls
(3)使用Element()方法生成四个视频墙,并加入到Group对象中,形成对象组合
(4)使用scene.add( group ) 将视频墙组加入到scene中,方便一起旋转、平移、缩放
(5)id为blocker的dom遮罩,通过式设置其全屏覆盖画布上。当操控3D场景时候显示遮罩,不操控3D场景时候就隐藏遮罩。目的: 防止在操控场景时候,触发了视频播放等功能。
function init() {
const container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 5000 );
camera.position.set( 500, 350, 750 );
scene = new THREE.Scene();
// CSS 3D渲染器
renderer = new CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
// THREE.Group:对象组合,把零散模型组合在一起,方便一起旋转、缩放、平移等操作
const group = new THREE.Group();
group.add( new Element( 'SJOz3qjfQXU', 0, 0, 240, 0 ) );
group.add( new Element( 'Y2-xZ-1HE-Q', 240, 0, 0, Math.PI / 2 ) );
group.add( new Element( 'IrydklNpcFI', 0, 0, - 240, Math.PI ) );
group.add( new Element( '9ubytEsCaS0', - 240, 0, 0, - Math.PI / 2 ) );
scene.add( group );
// TrackballControls 轨迹球控制器
controls = new TrackballControls( camera, renderer.domElement );
controls.rotateSpeed = 4;
// Block iframe events when dragging camera
const blocker = document.getElementById( 'blocker' );
blocker.style.display = 'none';
controls.addEventListener( 'start', function () {
blocker.style.display = '';
});
controls.addEventListener( 'end', function () {
blocker.style.display = 'none';
});
}
三、一些识点讲解
1. Group 对象组的使用
它几乎和Object3D是相同的,目的是使得组中对象在语法上的结构更加清晰。
通过 THREE.Group 类创建一个组对象 group,然后通过 add 方法把网格模型 mesh1、mesh2 作为设置为组对象 group 的子对象,然后在通过执行 scene.add(group)把组对象 group 作为场景对象的 scene 的子对象。
以本示例为例,形成了三层结构: scene >> group >> mesh
const group = new THREE.Group();
group.add( new Element( 'SJOz3qjfQXU', 0, 0, 240, 0 ) );
group.add( new Element( 'Y2-xZ-1HE-Q', 240, 0, 0, Math.PI / 2 ) );
group.add( new Element( 'IrydklNpcFI', 0, 0, - 240, Math.PI ) );
group.add( new Element( '9ubytEsCaS0', - 240, 0, 0, - Math.PI / 2 ) );
scene.add( group );
(1) group.children
以下截图是对group对象的打印,children就是使用group.add()方法所添加的内容
(2) add() 添加多个场景,也可以使用简写形式 group.add(mesh1, mesh2);
group.add(mesh1, mesh2);
(3) 对group对象进行位移操作 如果使用以下操作,则整个立方体都会沿着y轴正方向移动100
group.translateY(100);
(4) name属性 我们可以对层级模型对象设置name属性,如:
group.name = "积木房";
mesh1.name = "积木房1层";
2. CSS3DObject、CSS3DSprite(精灵)、CSS2DObject 的区别
- CSS3DObject 不面向摄像机,场景缩放时,缩小放大跟随着,不被模型遮挡,通过DOM事件点击
- CSS2DObject 面向摄像机,场景缩放时,缩小放大都一样大,不被模型遮挡,通过DOM事件点击
- CSS3DSprite 面向摄像机,场景缩放时,缩小放大跟随着,可以被射线拾取
注意:CSS3DObject 和 CSS3DSprite 需要使用 CSS3DRenderer 渲染器, 而 CSS2DObject 使用 CSS2DRenderer 渲染器。其中正方体和标签position位置都是在原点。
下面借助坐标系、正方体来辅助观察,分别使用上面三个API绘制的标签的区别。
CSS3DObject: 跟随缩放,不被遮挡,旋转时候相对正方体位置始终不变
CSS2DObject: 不跟随缩放,不被遮挡
CSS3DSprite 旋转时候文字相对正方体会改变位置
三、完整代码
<!DOCTYPE html>
<html>
<head>
<title>three.js css3d - youtube</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
/>
<link type="text/css" rel="stylesheet" href="main.css" />
<style>
body {
background-color: #ffffff;
}
#blocker {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="blocker"></div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
import {
CSS3DRenderer,
CSS3DObject,
} from "three/addons/renderers/CSS3DRenderer.js";
let camera, scene, renderer;
let controls;
// 生成一面视频墙
function Element(id, x, y, z, ry) {
const div = document.createElement("div");
div.style.width = "480px";
div.style.height = "360px";
div.style.backgroundColor = "#000";
const iframe = document.createElement("iframe");
iframe.style.width = "480px";
iframe.style.height = "360px";
iframe.style.border = "0px";
iframe.src = ["https://www.youtube.com/embed/", id, "?rel=0"].join("");
div.appendChild(iframe);
const object = new CSS3DObject(div);
object.position.set(x, y, z);
object.rotation.y = ry;
return object;
}
init();
animate();
function init() {
const container = document.getElementById("container");
camera = new THREE.PerspectiveCamera(
50,
window.innerWidth / window.innerHeight,
1,
5000
);
camera.position.set(500, 350, 750);
scene = new THREE.Scene();
// CSS 3D渲染器
renderer = new CSS3DRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
// THREE.Group:对象组合,把零散模型组合在一起,方便一起旋转、缩放、平移等操作
const group = new THREE.Group();
group.add(new Element("SJOz3qjfQXU", 0, 0, 240, 0));
group.add(new Element("Y2-xZ-1HE-Q", 240, 0, 0, Math.PI / 2));
group.add(new Element("IrydklNpcFI", 0, 0, -240, Math.PI));
group.add(new Element("9ubytEsCaS0", -240, 0, 0, -Math.PI / 2));
scene.add(group);
// TrackballControls 轨迹球控制器
controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 4;
window.addEventListener("resize", onWindowResize);
// Block iframe events when dragging camera
const blocker = document.getElementById("blocker");
blocker.style.display = "none";
controls.addEventListener("start", function () {
blocker.style.display = "";
});
controls.addEventListener("end", function () {
blocker.style.display = "none";
});
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
</script>
</body>
</html>