使用 three.js 中的 CSS3DRenderer 实现 3d 卡片的效果

Posted 掘金开发者社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 three.js 中的 CSS3DRenderer 实现 3d 卡片的效果相关的知识,希望对你有一定的参考价值。

前言

最近要做一个 3D 卡片的效果,设计图如下:

第一次尝试

第一次尝试选择了我比较熟悉的 PixiJS,关于我如何用 PixiJS 中的 Sprite3d 做了一个失败的 3D 卡片,可以 戳这里查看

第二次尝试

有了第一次失败的经历,果断老实选择使用 three.js 来实现 3d 效果。

但为什么选择使用 CSS3DRenderer 实现,可能是相中了 CSS3DRenderer 与 CSS 有关联。

CSS3DRenderer 可以直接通过 THREE.CSS3DObject(DOMElement) 将 Dom 元素转换为 3d 元素,然后控制该对象的 positionrotation 属性中的 xyz 来实现动画效果。

效果

实现过程

首先定义并初始化相机(camera)、场景(scene)、渲染器(renderer)和控制器(controls)。

核心代码

引入组件

 
   
   
 
  1. <script src="./js/three.js"></script>

  2. <script src="./js/tween.min.js"></script>

  3. <script src="./js/TrackballControls.js"></script>

  4. <script src="./js/CSS3DRenderer.js"></script>

搭建 three.js 框架,以下代码就完成了 3D 场景的搭建,后续只需要往场景中添加元素即可

 
   
   
 
  1. let camera,scene,renderer; // 定义相机、场景和渲染器

  2. let controls; // 定义控制器


  3. window.onload = ()=>{

  4. init();

  5. animate();

  6. };


  7. function init() {

  8. // 相机初始化

  9. camera = new THREE.PerspectiveCamera(40,window.innerWidth/window.innerHeight,1,10000);

  10. camera.position.z = 3000;


  11. // 场景初始化

  12. scene = new THREE.Scene();


  13. // 场景中的元素在此处添加,代码在下一个片段

  14. ....


  15. // 渲染器初始化,这里使用的是 CSS3DRenderer 渲染

  16. renderer = new THREE.CSS3DRenderer();

  17. renderer.setSize(window.innerWidth,window.innerHeight);

  18. document.getElementById('container').appendChild(renderer.domElement);


  19. // 控制器初始化

  20. controls = new THREE.TrackballControls(camera, renderer.domElement);

  21. controls.addEventListener('change',render);


  22. window.addEventListener('resize',onWindowResize,false);


  23. render();

  24. }


  25. function onWindowResize(){

  26. camera.aspect = window.innerWidth/window.innerHeight;

  27. camera.updateProjectionMatrix();

  28. renderer.setSize(window.innerWidth,window.innerHeight);

  29. render();

  30. }


  31. function animate(){

  32. requestAnimationFrame(animate);

  33. controls.update();

  34. }


  35. function render(){

  36. renderer.render(scene,camera);

  37. }

往场景中添加元素

 
   
   
 
  1. // 这里我将元素的 css 属性放到 json 当中,方便遍历创建

  2. const objectData = [

  3. // 卡片

  4. {

  5. verticalBg:{

  6. url:'./assets/bg_0.jpg',

  7. width: 1203,

  8. height: 589,

  9. },

  10. ground:{

  11. url:'./assets/bg_1.jpg',

  12. width:1203,

  13. height: 589,

  14. rotation:-Math.PI/180*70,

  15. },

  16. thingsRotation:Math.PI/180*70,

  17. things:[

  18. {

  19. url:'./assets/card1_thing_0.png',

  20. width:403,

  21. height: 284,

  22. x:-80,

  23. y:-445,

  24. },

  25. ...

  26. ]

  27. }

  28. ];

 
   
   
 
  1. // 最外层元素

  2. const container = document.createElement('div'); // 使用 js 动态创建 DomElement

  3. container.className = 'container';

  4. const objectContainer = new THREE.CSS3DObject(container); // 使用 CSS3DObject 将 DomElement 转换为 3d 元素

  5. scene.add(objectContainer); // 将转换好的 3d 元素添加到场景


  6. objectData.forEach((cardItem,cardIndex)=>{

  7. // 卡片

  8. const cardContainer = document.createElement('div');

  9. cardContainer.style.width = 1448+'px';

  10. cardContainer.style.height = 750+'px';

  11. const objectCardContainer = new THREE.CSS3DObject(cardContainer);

  12. objectContainer.add(objectCardContainer); // 通过 object3D 的 add 方法实现嵌套


  13. //竖直背景

  14. const card_bg_vertical = document.createElement('div');

  15. card_bg_vertical.style.width = cardItem.verticalBg.width+'px';

  16. card_bg_vertical.style.height = cardItem.verticalBg.height+'px';

  17. card_bg_vertical.style.background = 'url('+cardItem.verticalBg.url+') no-repeat';

  18. const objectCardBgVertical = new THREE.CSS3DObject(card_bg_vertical);

  19. objectCardBgVertical.position.y = -80; // 通过 object3D 的 position 属性改变元素位置

  20. objectCardContainer.add(objectCardBgVertical);


  21. // 地面

  22. const card_groud = document.createElement('div');

  23. card_groud.style.width = cardItem.ground.width+'px';

  24. card_groud.style.height = cardItem.ground.height+'px';

  25. card_groud.style.transformOrigin = 'center top'; // 通过 css 中的 transform-origin 来改变旋转中心

  26. card_groud.style.background = 'url('+cardItem.ground.url+') no-repeat';

  27. const objectCardGround = new THREE.CSS3DObject(card_groud);

  28. objectCardGround.position.y = 80;

  29. objectCardGround.rotation.x = cardItem.ground.rotation; // 通过 object3D 的 rotation 属性来旋转元素

  30. objectCardContainer.add(objectCardGround);


  31. // 元素

  32. cardItem.things.forEach((item,index)=>{

  33. const thing = document.createElement('div');

  34. thing.style.width = item.width+'px';

  35. thing.style.height = item.height+'px';

  36. thing.style.background = 'url('+ item.url +') no-repeat';

  37. const objectThing = new THREE.CSS3DObject(thing);

  38. objectThing.rotation.x = cardItem.thingsRotation;

  39. objectThing.position.y = -(index+1)*68;

  40. objectThing.position.x = item.x;

  41. objectThing.position.z = -item.y-300;

  42. objectCardGround.add(objectThing);

  43. });

  44. });

完整代码

可以直接 戳链接 查看,代码没有压缩。

总结

关于 API

  1. 通过 THREE.CSS3DObject(DOMElement) 可以将 Dom 元素转换为 object3D,这样 Dom 元素就可以直接享受 three.js 提供的 3d 场景了;

  2. 可以借助动画库 tween.js 来控制 object3D 对象的 position 和 rotation 属性中的 x 、 y 、 z ,可以实现流畅的动画。使用 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 是一个不错的选择。


以上是关于使用 three.js 中的 CSS3DRenderer 实现 3d 卡片的效果的主要内容,如果未能解决你的问题,请参考以下文章

Three.js 中的操纵杆、游戏手柄或 3D 鼠标支持

修复了 Three.js 中的纹理大小

three.js 中的分层纹理

动画 GIF 作为 THREE.js 中的纹理

解决 three.js / webGL 中的 gl_PointSize 限制

WebGL 模型查看器使用 Three.js 作为 HTML 中的 DOM 元素