前言
最近要做一個 3D 卡片的效果,設計圖如下:
第一次嘗試
第一次嘗試選擇了我比較熟悉的 PixiJS,關於我如何用 PixiJS 中的 Sprite3d 做了一個失敗的 3D 卡片,可以 戳這裡檢視。
第二次嘗試
有了第一次失敗的經歷,果斷老實選擇使用 three.js 來實現 3d 效果。
但為什麼選擇使用 CSS3DRenderer 實現,可能是相中了 CSS3DRenderer 與 CSS 有關聯。
CSS3DRenderer 可以直接通過 THREE.CSS3DObject(DOMElement)
將 Dom 元素轉換為 3d 元素,然後控制該物件的 position
和 rotation
屬性中的 x
、y
、 z
來實現動畫效果。
效果
效果圖如下,線上預覽 戳這裡。
實現過程
首先定義並初始化相機(camera)、場景(scene)、渲染器(renderer)和控制器(controls)。
核心程式碼
引入元件
<script src="./js/three.js"></script>
<script src="./js/tween.min.js"></script>
<script src="./js/TrackballControls.js"></script>
<script src="./js/CSS3DRenderer.js"></script>
複製程式碼
搭建 three.js 框架,以下程式碼就完成了 3D 場景的搭建,後續只需要往場景中新增元素即可
let camera,scene,renderer; // 定義相機、場景和渲染器
let controls; // 定義控制器
window.onload = ()=>{
init();
animate();
};
function init() {
// 相機初始化
camera = new THREE.PerspectiveCamera(40,window.innerWidth/window.innerHeight,1,10000);
camera.position.z = 3000;
// 場景初始化
scene = new THREE.Scene();
// 場景中的元素在此處新增,程式碼在下一個片段
....
// 渲染器初始化,這裡使用的是 CSS3DRenderer 渲染
renderer = new THREE.CSS3DRenderer();
renderer.setSize(window.innerWidth,window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);
// 控制器初始化
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.addEventListener('change',render);
window.addEventListener('resize',onWindowResize,false);
render();
}
function onWindowResize(){
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
render();
}
function animate(){
requestAnimationFrame(animate);
controls.update();
}
function render(){
renderer.render(scene,camera);
}
複製程式碼
往場景中新增元素
// 這裡我將元素的 css 屬性放到 json 當中,方便遍歷建立
const objectData = [
// 卡片
{
verticalBg:{
url:'./assets/bg_0.jpg',
width: 1203,
height: 589,
},
ground:{
url:'./assets/bg_1.jpg',
width:1203,
height: 589,
rotation:-Math.PI/180*70,
},
thingsRotation:Math.PI/180*70,
things:[
{
url:'./assets/card1_thing_0.png',
width:403,
height: 284,
x:-80,
y:-445,
},
...
]
}
];
複製程式碼
// 最外層元素
const container = document.createElement('div'); // 使用 js 動態建立 DomElement
container.className = 'container';
const objectContainer = new THREE.CSS3DObject(container); // 使用 CSS3DObject 將 DomElement 轉換為 3d 元素
scene.add(objectContainer); // 將轉換好的 3d 元素新增到場景
objectData.forEach((cardItem,cardIndex)=>{
// 卡片
const cardContainer = document.createElement('div');
cardContainer.style.width = 1448+'px';
cardContainer.style.height = 750+'px';
const objectCardContainer = new THREE.CSS3DObject(cardContainer);
objectContainer.add(objectCardContainer); // 通過 object3D 的 add 方法實現巢狀
//豎直背景
const card_bg_vertical = document.createElement('div');
card_bg_vertical.style.width = cardItem.verticalBg.width+'px';
card_bg_vertical.style.height = cardItem.verticalBg.height+'px';
card_bg_vertical.style.background = 'url('+cardItem.verticalBg.url+') no-repeat';
const objectCardBgVertical = new THREE.CSS3DObject(card_bg_vertical);
objectCardBgVertical.position.y = -80; // 通過 object3D 的 position 屬性改變元素位置
objectCardContainer.add(objectCardBgVertical);
// 地面
const card_groud = document.createElement('div');
card_groud.style.width = cardItem.ground.width+'px';
card_groud.style.height = cardItem.ground.height+'px';
card_groud.style.transformOrigin = 'center top'; // 通過 css 中的 transform-origin 來改變旋轉中心
card_groud.style.background = 'url('+cardItem.ground.url+') no-repeat';
const objectCardGround = new THREE.CSS3DObject(card_groud);
objectCardGround.position.y = 80;
objectCardGround.rotation.x = cardItem.ground.rotation; // 通過 object3D 的 rotation 屬性來旋轉元素
objectCardContainer.add(objectCardGround);
// 元素
cardItem.things.forEach((item,index)=>{
const thing = document.createElement('div');
thing.style.width = item.width+'px';
thing.style.height = item.height+'px';
thing.style.background = 'url('+ item.url +') no-repeat';
const objectThing = new THREE.CSS3DObject(thing);
objectThing.rotation.x = cardItem.thingsRotation;
objectThing.position.y = -(index+1)*68;
objectThing.position.x = item.x;
objectThing.position.z = -item.y-300;
objectCardGround.add(objectThing);
});
});
複製程式碼
完整程式碼
可以直接 戳連結 檢視,程式碼沒有壓縮。
總結
關於 API
- 通過
THREE.CSS3DObject(DOMElement)
可以將 Dom 元素轉換為object3D
,這樣 Dom 元素就可以直接享受 three.js 提供的 3d 場景了; - 可以藉助動畫庫 tween.js 來控制 object3D 物件的
position
和rotation
屬性中的x
、y
、z
,可以實現流暢的動畫。使用 js 來控制動畫比 css 更加靈活,且可以使用 three.js 中的lookat()
等現成的方法。 - 可以通過
css
中的transform-origin
來改變旋轉中心,因為建立的object3D
預設居中,因此改變中心位置後除錯位置會有些麻煩。
使用心得
- 必須承認, three.js 搭建的 3d 場景確實比 PixiJS 搭建的場景炫酷;
- 雖然
object3D
可以實現巢狀,但在控制檯檢視 Dom ,可以看到父子 dom 元素是同級,這讓我剛開始以為無法實現巢狀; - 通過
css
中的transform-origin
來改變旋轉中心後,會出現一些無法理解的情況,儘量減少改變旋轉中心; - 雖然場景搭建好了,但也是一次失敗的嘗試。因為我巢狀的層級較多,改變了旋轉中心後,旋轉出現元素偏移的情況,這個問題我還沒有理解,導致這次嘗試止步於此,只能另起爐灶再想一種辦法嘗試了;
- 如果只是想用圖片或簡單的圖片搭建一個 3d 場景,CSS3DRenderer 是一個不錯的選擇。