• 基于HTML5和CSS3构建3D模型


    关于CSS 3D的研究,其实早在2013年就开始了。无奈受限于当时的浏览器兼容性以及硬件性能等原因,对3D的一些探索也只是停留在DEMO阶段。这里可以参考我之前Github上的一个3D房屋模型DEMO

    CSS 3D的应用是我们一直在思考的,虽然近几年来浏览器和硬件有了很大提升,但基于CSS 3D做复杂应用还是比较受限。目前基于CSS 3D更多的是做一些美感和效果展现,以及一些轻量级的应用。

    我们聚划算前端团队在慕课网上开通了自己的主页,希望把我们的一些技术和想法拿出来跟大家进行分享和沟通交流,借此机会我先抛砖引玉,跟大家做这次分享。


    言归正传。

    今天要分享的话题是基于HTML5和CSS3构建3D模型,希望通过这次的分享,每个人都能够开启自己的3D之旅。主要有以下3点内容:

    1. CSS3基础知识
    2. 构建一个3D模型
    3. 3D模型模块化及扩展

    CSS3基础知识

    对于css的二维世界,相信大家都不陌生。在二维的世界里,我们可以对元素设置宽高、位置、旋转、背景等等。

    在css三维世界里,扩展出了一个z轴,这个z轴垂直于屏幕并指向外面。如图,

    三维坐标系

    基于这个三维坐标系,在二维基础上扩展一下我们的想象,如果一个元素可以绕着三个坐标轴进行平移、旋转会出线什么效果呢?

    聪明的你肯定能够想象出来,在三维世界里,这个元素就变成立体的了。我们先不做演示,先来解释几个概念,Perspective,Transform、Translate和Transition(简称“三蠢”)

    Perspective

    Perspective在3D世界里是一个非常重要的概念,这个属性的设置决定了我们看到的是三维效果还是二维效果。Perspective的字面意思是透视、视角,计算机要模拟现实世界的三维效果,必须有一个虚拟摄像机(Camera)的概念,摄像机的移动代表现实世界中我们眼睛的移动,我们眼睛看到的真实物体有近大远小的效果,同样,虚拟摄像机的也要模拟出这样的场景。W3C解释是下图这样,透视点是物体到摄像机的距离(d), perspective设置的大小不同,人眼所看到的物体也会发生变化。

    Perspective

    perspective-origin指摄像机的中心点位置,默认值是center center(或50% 50%)。换句话说,作为摄像机镜头的三个维度,perspective-origin代表了X和Y轴,而perspective代表Z轴。摄像机在三维空间进行移动,也就告诉浏览器也进行不同的渲染,而呈现给我们的就是不同的视角效果。

    perspective-origin

    构建一个3D缺少不了3个要素,摄像机(Camera)、舞台(stage)和物体(Object)本身。我们先来创建这三个要素,代码如下:

    <div class="camera">
            <div class="stage">
                <div class="box"></div>
            </div>
        </div>
        .camera {
             200px;
            height: 200px;
            perspective: 500px;
            perspective-origin: center center;
        }

    Transform

    Transform指3D变换,主要的方法有旋转(Rotate)和平移(Translate)。Rotate可以绕X、Y、Z三个轴旋转,对应rotateX、rotateY、rotateZ三个方法。同样,坐标平移Translate也有tranlateX、tranlateY、translateZ三个方法。

    在刚才的摄像机Camera设置完成后,我们需要设置一个立体空间,这个立体空间只有一个属性需要设置transform-style,默认清空下这个属性值被设置为flat,即在这个空间下的元素是没有z轴概念的,为了让元素都是立体元素,需要在这个立体空间上设置tranform-style的值为preserve-3d。

    .stage {
            width:100%;
            height:100%;
            border:1px dashed #000;
            transform-style:preserve-3d;
        }

    空间设置完成后,我们就可以来添加物体了,如上,我们设置了一个叫box的物体,下面我们来设置它的3D属性。

    首先,我们对这个物体做3D平移

    .box {
            width:100px;
            height:100px;
            background:#069;
            transform: translateX(50px) translateY(50px);
        }

    translate

    然后,改变camera的perspective属性

    perspective

    无论我们如何修改perspective或者perspective-origin,竟然没有任何效果!这是为什么呢???

    原因是在没有设置box的rotate、translateZ的情形下,box的深度没有变化,导致摄像机Camera透过perspective看出去的位置都是相同的,也造成不论怎么去看这个box都是一样的大小。

    OK,那我们来设置box的translateZ,让box沿着Z轴做一些平移

    .box {
            width:100px;
            height:100px;
            background:#069;
            transform: translateX(50px) translateY(50px) translateZ(150px);
        }

    translateZ

    改变perspective属性

    po

    按照人眼睛看到的物体近大远小的原理,摄像机离物体的距离(perspective)越近,看到的物体越大,离得越远,物体越小。当摄像机已经透过物体之后,就看不到了。想象你有穿墙术,当你离墙很远的时候,你能看到墙的边缘,当你离墙越近,墙也就越大,当你穿过墙之后,墙在你身后,也就看不到了。

    我们让box发生一些旋转,绕Y轴旋转45度

    .box {
            width:100px;
            height:100px;
            background:#069;
            transform: translateX(50px) translateY(50px) rotateY(45deg);
        }

    rotateY

    改变perspective-origin属性

    perspective-origin

    Transition

    最后我们来说一下Transition这个概念,Transition的意思是过渡,我们在做一些动画效果的时候会用到它。Transition是一个简写属性,用于设置4个过渡属性

    • transition-property(规定设置过渡效果的 CSS 属性的名称)
    • transition-duration (规定完成过渡效果需要多少秒或毫秒)
    • transition-timing-function(规定速度效果的速度曲线)
    • transition-delay (定义过渡效果何时开始)

    语法

    transition: property duration timing-function delay;

    我们来做一个例子

    .tst {
            transition: transform 2s linear 1s;
        }

    然后把这个class设置在物体box上,任意改变box上一个Transform属性

    transition

    可以看到这个变化是在设置完1s后(delay)在2s(duration)的时间内线性(linear)完成的((⊙o⊙)…有点绕口)。

    构建一个3D模型

    有了上面的概念之后,再来构建一个3D模型就非常简单了。通过对多个平面的平移、旋转,我们来构建一个Cube模型。

    一个立方体有上、下、左、右、前、后6个面,我们先创建这6个面

    <div class="camera">
            <div class="stage">
                <div class="cube up">up</div>
                <div class="cube down">down</div>
                <div class="cube left">left</div>
                <div class="cube right">right</div>
                <div class="cube front">front</div>
                <div class="cube back">back</div>
            </div>
        </div>

    然后再设置每个面的Transform属性

    .camera {
          width: 400px;
          height: 400px;
          perspective: 1000px;
          perspective-origin: center center;
        }
        .stage {
          width:100%;
          height:100%;
          border:1px dashed #000;
          transform: rotateY(0deg) rotateX(0deg);
          transform-style:preserve-3d;
        }
        .cube {
          position: absolute;
          left: 75px;
          top: 100px;
          margin-left: 30px;
          width: 200px;
          height: 200px;
          color: white;
          background-color: rgba(255, 85, 85, 1);
        }
        .up {
            background-color: rgba(48, 44, 102,1);
            transform: rotateX(90deg) translateZ(100px);
        }
        .down {
            transform: rotateX(-90deg) translateZ(100px);
        }
        .left {
            background-color: rgba(254, 56, 69,1);
            transform: rotateY(-90deg) translateZ(100px);
        }
        .right {
            transform: rotateY(90deg) translateZ(100px);
        }
        .front {
            background-color: rgba(100, 225, 180,1);
            transform: translateZ(100px);
        }
        .back {
            transform: rotateY(180deg) translateZ(100px);
        }

    关于如何旋转及平移,大家可以自行思考,比如把front面作为参考面,其他面应该如何平移和旋转?这里不做细述,有问题可以留言。

    注意:在进行旋转的时候,物体的坐标系也是跟着旋转的,所以先平移再旋转和先旋转再平移的效果是不一样的!这时候一定要分清楚当前坐标系!

    最后,一个Cube模型就显示出来了!

    Cube

    是不是So easy?!老板再也不担心我做不出炫酷的效果啦!


    3D模型模块化及扩展

    道生一,一生二,二生三,三生万物。

    像这样一个简单的立方体我们可以通过几个面的平移、旋转构建出来。那如果是一个复杂模型,如果也用这样的方式构建,可能就会带来很大的工作量。当遇到这样问题的时候,我们自然而然会考虑到代码的模块化与自动化实现,有了这个“道”,万事万物就可以由此衍生出来了。

    下面我们尝试用代码的方式来生成一个立方体,Let's go!

    class Cube {
            constructor(id, l, w, h, x, y, z) {
                this.id = id;//ID
                this.l = l;//长
                this.w = w;//宽
                this.h = h;//高
                this.x = x;//x坐标
                this.y = y;//y坐标
                this.z = z;//z坐标
            }
        }

    通过ES6语法创建一个Cube类,构造参数分别有id、长宽高l、w、h和坐标x、y、z,然后需要创建这个Cube的DOM元素。

    class Cube {
            constructor(id, l, w, h, x, y, z) {
                this.id = id;//ID
                this.l = l;//长
                this.w = w;//宽
                this.h = h;//高
                this.x = x;//x坐标
                this.y = y;//y坐标
                this.z = z;//z坐标
    
                this._create();
            }
    
            _create() {
                this.cube = document.createElement('div');//构建DOM节点
                this.faces = new Array(6);//构建六个面
                for(let i = 0; i < this.faces.length; i++) {
                    this.faces[i] = document.createElement('div');
                    this.faces[i].id = `${this.id}-face-${i}`;
                    this.faces[i].style.position = 'absolute'; //必须
                    this.faces[i].style.backgroundColor = "#"+((1<<24)*Math.random()0).toString(16);//生成随机颜色
                    this.cube.appendChild(this.faces[i]);
                }
                this._setCubeStyle();
                this._setFrontFace();
                this._setBackFace();
                this._setLeftFace();
                this._setRightFace();
                this._setUpFace();
                this._setDownFace();
            }
        }

    OK,现在我们Cube对象基本元素都有了。接下来我们需要设置6个面的样式,我们约定前、后、左、右、上、下分别对应faces数组中的6个面。

    //设置cube样式
        _setCubeStyle() {
            let style = {
                'display': 'flex', //为计算方便,设置flex布局,想想为什么?
                'justify-content': 'center',
                'align-items': 'center',
                'transform-style': 'preserve-3d',
                'transform': `translateX(${this.x}px) translateY(${this.y}px) translateZ(${this.z}px)`
            }
            Object.assign(this.cube.style, style)
        }
    
        //设置Front面样式
        _setFrontFace() {
          let style = {
              'width' : this.l + 'px',
              'height': this.h + 'px',
              'transform': `translateZ(${this.w/2}px)`
          }
          Object.assign(this.faces[0].style, style)
        }
        //设置Back面样式
        _setBackFace() {
          let style = {
              'width' : this.l + 'px',
              'height': this.h + 'px',
              'transform': `rotateY(180deg) translateZ(${this.w/2}px)`
          }
          Object.assign(this.faces[1].style, style)
        }
        //设置Left面样式
        _setLeftFace() {
          let style = {
              'width' : this.w + 'px',
              'height': this.h + 'px',
              'transform': `rotateY(-90deg) translateZ(${this.l/2}px)`
          }
          Object.assign(this.faces[2].style, style)
        }
        //设置Right面样式
        _setRightFace() {
          let style = {
              'width' : this.w + 'px',
              'height': this.h + 'px',
              'transform': `rotateY(90deg) translateZ(${this.l/2}px)`
          }
          Object.assign(this.faces[3].style, style)
        }
        //设置Up面样式
        _setUpFace() {
          let style = {
              'width' : this.l + 'px',
              'height': this.w + 'px',
              'transform': `rotateX(90deg) translateZ(${this.h/2}px)`
          }
          Object.assign(this.faces[4].style, style)
        }
        //设置Down面样式
        _setDownFace() {
          let style = {
              'width' : this.l + 'px',
              'height': this.w + 'px',
              'transform': `rotateX(-90deg) translateZ(${this.h/2}px)`
          }
          Object.assign(this.faces[5].style, style)
        }

    这样,一个Cube类就创建好了。我们来运行验证一下。

    注意:代码采用ES6语法,在浏览器运行时需要Babel转码。

    <div class="camera">
            <div id="stage" class="stage">
            </div>
        </div>
    
        .camera {
           400px;
          height: 400px;
          perspective: 1000px;
          perspective-origin: center center;
        }
        .stage {
          100%;
          height:100%;
          border:1px dashed #000;
          transform: rotateY(30deg) rotateX(20deg);
          transform-style:preserve-3d;
        }

    HTML代码很简单,这个时候我们只需要摄像机camera和空间stage两个元素,物体则由我们刚刚创建的Cube类生成。

    let c1 = new Cube('c1', 100, 100, 100, 100, 200, 0);
        document.getElementById('stage').appendChild(c1.cube)

    效果如下:

    3D模型
    3D模型2

    来创建一堆Cube,查看效果

    let c1 = new Cube('c1', 20, 30, 40, 0, 100, 0);
        let c2 = new Cube('c1', 10, 20, 30, 0, 200, 0);
        let c3 = new Cube('c1', 20, 20, 50, 0, 250, 0);
        let c4 = new Cube('c1', 50, 50, 50, 0, 320, 0);
        let $stage = document.getElementById('stage');
        $stage.appendChild(c1.cube)
        $stage.appendChild(c2.cube)
        $stage.appendChild(c3.cube)
        $stage.appendChild(c4.cube)

    cube

    俄罗斯方块出现了!

    扩展

    这次分享的内容就要结束了,回顾一下,我们讲解了CSS的基本概念、手动创建一个Cube模型以及如何通过模块化创建Cube对象。看完之后是不是觉得很简单?实际上这里面的原理确实不难,难的是我们如何应用这些来解决实际问题或者提升用户体验。

    最后分享几个有意思的DEMO:

    1. 3D魔方(在线地址
      3D魔方

    2. 3D俄罗斯方块(在线地址
      3D俄罗斯方块

    3. 3D立体书(在线地址)
      3D立体书


    作者:阿里聚划算技术团队
    链接:https://www.imooc.com/article/12670
    来源:慕课网

  • 相关阅读:
    [翻译]ASP.NET MVC 3 开发的20个秘诀(六)[20 Recipes for Programming MVC 3]:找回忘记的密码
    No.9425 [翻译]开发之前设计先行
    [翻译]ASP.NET MVC 3 开发的20个秘诀(五)[20 Recipes for Programming MVC 3]:发送欢迎邮件
    内联(inline)函数与虚函数(virtual)的讨论
    内联函数、模板、头文件杂记(转)
    gdb使用整理
    data structure and algorithm analysis上一点摘录
    lsof by example 例子
    linux core文件机制
    转一篇shared_ptr的小文
  • 原文地址:https://www.cnblogs.com/xiaolucky/p/12427433.html
Copyright © 2020-2023  润新知