使用 three.js 中的 CSS3DRenderer 實現 3d 卡片的效果

前言

最近要作一個 3D 卡片的效果,設計圖以下: javascript

第一次嘗試

第一次嘗試選擇了我比較熟悉的 PixiJS,關於我如何用 PixiJS 中的 Sprite3d 作了一個失敗的 3D 卡片,能夠 戳這裏查看css

第二次嘗試

有了第一次失敗的經歷,果斷老實選擇使用 three.js 來實現 3d 效果。html

但爲何選擇使用 CSS3DRenderer 實現,多是相中了 CSS3DRenderer 與 CSS 有關聯。java

CSS3DRenderer 能夠直接經過 THREE.CSS3DObject(DOMElement) 將 Dom 元素轉換爲 3d 元素,而後控制該對象的 positionrotation 屬性中的 xyz 來實現動畫效果。git

效果

效果圖以下,在線預覽 戳這裏github

實現過程

首先定義並初始化相機(camera)、場景(scene)、渲染器(renderer)和控制器(controls)。web

核心代碼

引入組件json

<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 場景的搭建,後續只須要往場景中添加元素便可app

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

  1. 經過 THREE.CSS3DObject(DOMElement) 能夠將 Dom 元素轉換爲 object3D,這樣 Dom 元素就能夠直接享受 three.js 提供的 3d 場景了;
  2. 能夠藉助動畫庫 tween.js 來控制 object3D 對象的 positionrotation 屬性中的 xyz ,能夠實現流暢的動畫。使用 js 來控制動畫比 css 更加靈活,且可使用 three.js 中的 lookat() 等現成的方法。
  3. 能夠經過 css 中的 transform-origin 來改變旋轉中心,由於建立的 object3D 默認居中,所以改變中心位置後調試位置會有些麻煩。

使用心得

  1. 必須認可, three.js 搭建的 3d 場景確實比 PixiJS 搭建的場景炫酷;
  2. 雖然 object3D 能夠實現嵌套,但在控制檯查看 Dom ,能夠看到父子 dom 元素是同級,這讓我剛開始覺得沒法實現嵌套;
  3. 經過 css 中的 transform-origin 來改變旋轉中心後,會出現一些沒法理解的狀況,儘可能減小改變旋轉中心;
  4. 雖然場景搭建好了,但也是一次失敗的嘗試。由於我嵌套的層級較多,改變了旋轉中心後,旋轉出現元素偏移的狀況,這個問題我尚未理解,致使此次嘗試止步於此,只能另起爐竈再想一種辦法嘗試了;
  5. 若是隻是想用圖片或簡單的圖片搭建一個 3d 場景,CSS3DRenderer 是一個不錯的選擇;
  6. Ziben 同窗在實際開發中發現,在場景較大,或者添加 CSS 序列動畫,低端的安卓機會出現渲染較慢的狀況,感謝 Ziben 同窗的反饋。若是須要兼容低端安卓設備,可能須要謹慎選擇。

更多文章

相關文章
相關標籤/搜索