CSS3之2D与3D转换

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_41694291/article/details/102639499

在CSS3中新增了很多关于2D和3D转换的标准,它允许将页面元素在2D和3D空间内进行移动、缩放、旋转、倾斜等。所谓的2D转换,指的是元素可以在平面内进行位置或形状的转换,而3D转换指的是元素可以在三维空间(也就是立体空间)内进行位置或形状的转换。

我们先来看一下几个2D和3D转换的简单效果:
在这里插入图片描述
我们知道,在浏览器模型中,网页元素通常都被描述为一个矩形的“盒子”,也就是上图中第一个图形的样式。但是这对于有着艺术追求的网页设计师来说是极大的限制,它意味着网页中所有的艺术字、变形效果、动态特效都必须用图片甚至一些更复杂的技术(如flash)实现,但是这些外部资源会严重损耗网页的性能。为了使设计师设计的网页兼具艺术感和高性能,CSS工作组决定推出一些规范,利用浏览器自身的渲染能力来支持设计师的艺术设计,2D和3D转换就是其中令人津津乐道的两个模块。

2D、3D和坐标空间

学习2D和3D变换最重要的基础,就是一定要理解CSS3所基于的坐标空间。2D变换基于以下的坐标系:
在这里插入图片描述
向右为x轴很容易理解,在数学中通常默认是这样。但是y轴的方向却与数学中的默认方向是相反的(其实只要两个轴是垂直的,哪个方向为正方向并没有差别),这是为什么呢?答案是与网页的布局方式有关。

由于现在的网页布局通常采用“流式布局”,它从网页的左上角向右开始放置元素,一行放置不下再继续转到下方,比如下图,4个网页元素将按图中的序号进行排列(假设这里的元素都有固定宽高,并且为inline-block):
在这里插入图片描述
从布局模式中可以看出,网页元素在垂直方向上是从上向下布局的,所以自然取向下的方向为正方向。同理,由于元素是从左向右排列的,所以取向右为x轴的正方向。

那么3D是基于什么样的坐标空间呢?其实它只是比上面的坐标系多了一个垂直指向屏幕外的z轴(这可能需要一点想象能力),下面的立体图片可以帮助你想象z轴的存在:
在这里插入图片描述
图中的矩形可以视作手机或电脑屏幕,z轴就是垂直于屏幕平面的一条假想的坐标轴。三个坐标轴的空间关系为,从左向右为x轴(元素的水平排列方向),从上向下为y轴(元素的垂直排列方向),从屏幕指向你的眼睛的是z轴。由于网页目前还是二维空间的,因此该坐标轴不用于布局元素,但是它对3D转换有着直接的影响。

此外还有一个很重要的概念,就是透视,它也是3D转换中的一个概念。看下图:
在这里插入图片描述
以图中下方的图片来理解:左侧为人眼,中间的矩形是屏幕,虚线的圆就是假想的网页上的元素(虽然它实际是处于屏幕的2D空间中,但我们假想它位于立体空间中)。这时候,屏幕就仿佛透明的一般,我们可以透过它看到“屏幕后方”的网页元素。它到屏幕的距离使用z轴的刻度值来表示:当它位于屏幕后方时,它的z轴的值为负,此时元素看起来会比设置的宽高小(距离越远,看上去越小);当它位于屏幕前方时,z轴的值为正,元素看起来比实际的宽高要大。

在对一个元素进行2D和3D转换时,一般以矩形的中心点为坐标原点进行转换,也可以通过参数transform-origin来修改坐标原点,实现不同的转换效果。

理解了这些概念,就可以来看一下CSS3中常用的2D、3D转换。

2D转换类

1. translate位移

这个非常好理解,就是让元素沿着x轴或y轴移动一定的距离。但是要注意,应用了2D和3D效果的元素已经脱离了正常的文档流,所以在移动元素时,有可能会盖住其他元素。如上面的第二个div,我们让它沿x轴移动200像素,沿y轴移动30像素:
在这里插入图片描述
我们看到,它部分盖住了下方的其他元素,代码如下:

.div2{
  transform: translate(200px, 50px);
}

translate的两个参数分别表示沿x轴和y轴的移动距离,为负数时表示沿负方向移动(也就是向左或向上)。

这两个参数也可以拆分成两个:

.div2{
  transform: translateX(200px) translateY(50px);
}

也可以只写一个:transform: translateY(50px)。

2. scale缩放

表示将当前元素放大或缩小,传入的参数为相对当前大小的倍数。如:

.div{
  transform: scale(2);  //放大两倍
  //transform: scale(0.5);  //缩小一半
}

它可以接收两个参数,第一个参数为宽度的缩放比例,第二个参数为高度的缩放比例,省略第二个参数时默认等于第一个参数。

注意,scale函数接收负数,此时它会先在该方向进行一次翻转,然后按参数的绝对值来缩放元素,如scale(-2)和scale(-2, 2)的效果分别如下:
在这里插入图片描述
左侧两个方向都进行了镜像翻转,右侧则只在水平方向进行了翻转,然后分别把宽高放大了两倍。

3. rotate旋转

顾名思义,将元素旋转一定角度。使用以下语句进行旋转:

.div2{
  transform: rotate(30deg);
}

代码效果见最上面的图中第二个div。对于2D旋转,它只支持一个参数,就是旋转的度数。但是该参数允许三种单位:

  1. 度,reg。即常说的角度,顺时针为正,逆时针为负(ps:钟表走动的方向为顺时针),一圈为360度。
  2. 弧度,rad。这是数学中常用的旋转单位,180度转换为弧度值为π,这里不再详述。
  3. 圈数,turn。通俗来讲就是转多少圈,如0.25turn就表示顺时针旋转90度。

4. skew倾斜

相对来说,这是2D变换中最不容易理解的一个变换。所谓的倾斜,就是沿x轴或y轴方向变形,下图分别是沿x轴倾斜和沿y轴倾斜的效果:
在这里插入图片描述
左侧为沿x轴倾斜30度的效果,如果把原矩形看做一个可以变形的长方形,你可以理解为:左手拿着矩形的左上角,右手拿着矩形的右下角,以矩形的中心点为中心,沿x轴方向向两侧拉,直到矩形的左右两边与垂直方向产生30度夹角。此时矩形看上去是水平倾斜的。

同理,沿y轴倾斜是分别拿着矩形的左上角和右下角沿y轴方向拉,直到与x轴产生30度的夹角。

如果传入负数,那么就是沿着相反的方向进行。如果同时沿两个方向倾斜,就可能产生下面的效果:
在这里插入图片描述
这是skew(20deg, 20deg)的效果,是由两个方向的倾斜效果复合出来的。

同样的,skew由skewX和skewY组成,可以写成分开的形式,或只定义一个方向上的倾斜效果。

5. matrix矩阵运算和坐标原点设置

矩阵运算是上面四个变化的综合版本。它接收6个参数,分别是x轴方向上的缩放、x轴方向上的倾斜、y轴方向上的缩放、y轴方向上的倾斜、x轴方向上的位移、y轴方向上的位移。综合使用前四个参数,可以组合出旋转效果。

由于这里没有新的变换,因此不再详述。

坐标原点的变换是为了使变换更加灵活。上面的所有变换默认都是基于矩形的中心点,你也可以使用transform-origin来改变坐标原点的位置,它接收两个参数,分别是坐标原点在水平方向和垂直方向的位置,初始值是50% 50%,也就是位于矩形正中间。它的默认值如下:

.div2{
  transform-origin: 50% 50%;
}

第一个参数除了接收百分比值外,还可以接收left、center、right等,表示左、中、右;第二个参数则可以接受top、middle、bottom,表示上、中、下。使用这两个参数就可以确定坐标原点的位置。该元素的转换效果将基于这个原点进行。

3D转换类

理论基础

相对于2D变换来说,3D变换是增加了一条坐标轴z轴。也就是说,此时的网页元素不光有左右、上下的概念,还有了前后的概念。虽然只是增加了一条坐标轴,而且3D变换的种类并不多,但是要真正理解3D变换其实并不容易。

为了实现一次3D变换,需要先构建一个3D场景。而关于这个3D场景是如何构建的,很多学习网站都是举个例子粗略带过,对于原理几乎没有任何讲解。自己翻阅了很多博客之后,也没有组织出一个非常合理的理论体系,只是从中大致理解了3D变换中的若干个概念。经过思考,我总结出一套自己的理论,不能保证正确,但是它解决了我关于3D转换的大部分疑惑,希望与大家分享。

对于坐标体系,前面已经讲到,就是新增了一条垂直于屏幕向外的z轴。我们知道,在三维空间中,人的视觉有一个特点,就是近大远小。如果你玩过3D游戏(比如绝地求生这类)就会知道,计算机是通过把某些物体绘制得更小来表现远处的物体的,但是一个物体究竟要绘制的多么小,才能从视觉上让人距离感呢?这里面牵扯到一个非常重要的概念,就是透视。来看一张典型的透视图(图片来自网络):
在这里插入图片描述
我们看到,小路在离我们最近的地方看起来是最宽的,越远看起来就越窄,直到最后几乎消失于一点。小路消失的那个点称为消失点(也叫透视点),这是透视中最重要的概念,它决定了在当前的3D环境中我们的视野相对于这条小路的最大可视距离(超过可视距离后你几乎无法识别这条小路的任何细节,在视觉上就认定为不可见)。

假如我希望绘制上图中的这条3D小路,除了临摹之外,有没有更科学一点的办法呢?当然是有的。假如我们需要在一张白纸上用简笔画绘制这条小路,我们需要这么几个参数:

  1. 图片的宽高。也就是上面这张图片的宽高,我们要保证白纸的宽高和图片的宽高一致,这样才能1:1复现。
  2. 消失点到视口的距离(也叫透视距离)。这里所说的视口,就是我们拍摄这张图片时所处的位置,它同时也是我们观察这张图片的入口,所以称为视口。这里所说的距离并不是实际场景的距离,而是抽象模型中的距离(可以想象当前有一块空白的电影幕布,它的宽高等于图片的宽高。投影仪中的图像是从一个点发出来的,最终将投满整个屏幕,那么投影仪到幕布的距离就是这里所说的透视距离)。
  3. 消失点的位置。也就是消失点位于图片的哪个位置,以图片左上角为坐标原点来表示,可以使用百分比或实际像素值。比如当消失点位于图片正中央时,这个位置用(50%, 50%)表示。

有了这三个值,你就可以在白纸上绘制这条3D的小路了,它大概长这样(虽然看不出来是条小路,但它的确存在3D效果):
在这里插入图片描述
如果把上面的矩形看做电影屏幕,消失点就类似于投影仪的发射源。你可以在图像投影到幕布之前,在两者之前放置一张白纸,这样就可以看到距离你更远,看起来更小的图像了。

理解了上面的理论后,我们来看浏览器的3D场景模型,它与上面的场景也比较类似。

在进行3D转换时,需要设置几个重要的参数,分别是perspective、perspective-origin、transform-style: perserve-3d。注意,这几个参数都不是设置给需要进行3D转换的网页元素的(进行3D转换的元素可以看做是上图中的小路,但是我们必须首先为它构建出一个3D场景才能看到3D效果,也就是需要确定上面的三个参数)。

要把一个元素进行3D转换,通常需要设置两个外部容器:视口容器和包裹容器。视口容器就是我们看这个3D元素的截面(它类似于投影的例子中的幕布,是观察3D元素的入口),被转换的元素最终将会呈现在这个容器中。所谓包裹容器,顾名思义,就是包裹着被转换元素的容器,我们通过设置transform-style: perserve-3d来告诉浏览器,它内部的元素需要进行3D转换,以保证浏览器绘制图层时顺序正确。来看一个简单的3D转换,将元素沿z轴的负方向移动500像素(也就是移动到屏幕后方700像素的位置,这样它看起来更小):

<style>
  .viewport{
    width: 100%;
    height: 100%;
    perspective: 900px;         /*消失点距离屏幕900像素*/
    perspective-origin: 0 20%;  /*消失点位于最左侧距离顶部20%的位置*/
  }

  .wrapper{
    transform-style: perserve-3d;
  }

  .target{
    width: 200px;
    height: 200px;
    background-color: red;
    border-radius: 5px;
    margin: 100px auto;
    transform: translateZ(-700px);   /*向屏幕后方移动500像素*/
  }
</style>
<body class="viewport">
  <div class="wrapper">
    <div class="target">我是要进行3D转换的元素</div>
  </div>
</body>

效果图如下:
在这里插入图片描述
我们看到,元素宽高明显小于200像素,并且由于消失点位于左上角,所以我们设置的margin: auto并没有让元素水平居中(想象一下,如果将其投射到屏幕上,它仍然是居中的,但是现在它位于屏幕的后方,我们用一张纸拦截这个影像在观察它)。

如果只是静态的图片,你很难观察到3D效果。现在让我们试着连续改变元素到屏幕的距离,下面分别是-400px、-200px、0像素的效果:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
可以看到,从视觉上,元素的宽高逐渐接近200像素,并且逐渐靠近中间位置,这就是投影的效果。

再来看一下我们设置了哪些参数:

  1. perspective: 900px。透视距离为900像素,表示消失点(或看做投影点)距离屏幕900像素(这里有待商榷,该参数更像是作为一个基准参数(或叫参考值)存在的,当向屏幕后方移动900像素时,视觉大小将变为实际的一般,移动1800像素时,将变为三分之一,以此类推)。
  2. perspective-orign: 0 20%。将消失点设置在最左侧,距离顶部20%的位置。
  3. transform-style: perserve-3d。设置其内部元素以3D效果显示。实际上如果不设置该参数,元素仍然具有透视效果。但是如果该元素要显示的地方有其他元素,这个3D元素可能会盖住它,而按照我们的理解,现在该元素位于屏幕后方,它不可能盖住任何元素,也就是说此时浏览器的图层绘制顺序出了问题。设置该参数就可以让该元素不会盖住屏幕上的其他元素,实现真正的3D效果。
  4. transform: translateZ(-700px)。将元素沿z轴向消失点移动700像素,这时元素看上去就靠近左上角消失点的位置,并且宽高都变小了。

到了这里,我的3D模型就介绍完了,下面我们使用该模型来讲解目前支持的3D转换。

1. 3D位移

3D位移分为三类,沿x轴、沿y轴和沿z轴。前两者的用法与2D位移没有任何差别(可以说3D位移只是在2D位移上的扩展而已),这里不再详述。沿z轴的位移也就是上面讲到的,元素距离屏幕的距离变化。不过这里所说的沿z轴并不是严格按照z轴的垂直方向移动,而是沿着消失点到元素的实际位置的路线进行移动。

你可以使用transform: translateZ(-700px)这样的语法进行沿z轴的移动。这样元素就会变得更小,并且靠近消失点的方向。

3D位移还可以使用一个统一的函数:translate3d(x, y, z)。显然它是translateX、translateY、translateZ这三个函数的复合版本,这里不再详述。

2. 3D旋转

在二维变换中,旋转非常简单。因为在二维空间中,元素可以进行的旋转无非就是顺时针和逆时针旋转。但是在三维空间中,旋转就稍微复杂一些(想象一下,如果你要旋转一张放在桌面上的白纸,无非就是顺时针和逆时针两种,这是二维空间。但如果把它从桌子表面拿起来,你就上升到了三维空间,这时就可以沿很多方向旋转了)。

在三维空间中,无论多么复杂的旋转,最终都归为三类旋转的复合:沿x轴的旋转、沿y轴的旋转和沿z轴的旋转。再看之前的一张空间图:
在这里插入图片描述
这个矩形假设就是我们要旋转的元素,它所在的平面就是电脑屏幕。沿x轴旋转就是保持x轴不动进行旋转,通俗来讲就是前后翻滚。看一张沿x轴翻转60度的截图( transform: ratateX(60deg) ):
在这里插入图片描述
想象一下,元素现在正在从顶部向后翻转。同理沿y轴的旋转就是左右旋转(transform: ratateY(60deg)):
在这里插入图片描述
沿z轴的旋转则更简单,三维空间中沿z轴的旋转与二维空间的旋转是一致的,也就是“原地打转”(transform: ratateZ(60deg)):
在这里插入图片描述
如果你能想象屏幕上有一条垂直指向你的坐标轴,那么这个旋转行为将非常好理解。旋转也有一个复合方法rotate3d(x, y, z),与位移函数类似,不再详解。

3. 3D缩放

这个比较好理解,就是新增了一个z轴方向上的缩放行为。但是从效果来看,这个行为似乎并没有什么意义。因为二维平面上的元素在z轴上的宽度为0(也就是“厚度”为0),我们暂时只需要知道它通过scaleZ(2)这样来设置即可,这里就跳过。

关于2D和3D转换到这里就介绍完了,可能很多人看到这里会觉得很失望,因为并没有看到非常绚丽的网页效果,这主要是因为2D和3D转换一般需要结合css动画才能实现比较复杂的效果。下面我们提供一段实现旋转的立方体的代码,它结合了css3的animation动画实现,看上去非常绚丽(在线录屏转的gif,画质粗糙请见谅)。
在这里插入图片描述
你可以新建一个txt文本文件,把下面的代码粘贴进去,保存并关闭,然后把文件后缀修改为.html,双击它,立即就可以在浏览器看到上面的两个旋转的立方体。

<div class="wrapper">
        <div class="wrapper-left">
            <div class="front">1</div>
            <div class="back">2</div>
            <div class="left">3</div>
            <div class="right">4</div>
            <div class="top">5</div>
            <div class="bottom">6</div>
        </div>
        <div class="wrapper-right">
            <div class="front">1</div>
            <div class="back">2</div>
            <div class="left">3</div>
            <div class="right">4</div>
            <div class="top">5</div>
            <div class="bottom">6</div>
        </div>
    </div>

<style>
.wrapper > div{
            margin-left: 100px;
            position: relative;
            float: left;
            width: 500px;
            height: 500px;
            perspective: 5000px;
            transform-style: preserve-3d;
            transform: rotateX(-20deg) rotateY(20deg) rotateZ(20deg);
            animation: round 10s linear infinite;
        }
        
        .wrapper-left>div,
        .wrapper-right>div {
            top: 150px;
            left: 150px;
            position: absolute;
            width: 200px;
            height: 200px;
            box-sizing: border-box;
            opacity: 0.7;
            border: 1px solid #000;
            float: left;
            margin: 0 auto;
            text-align: center;
            line-height: 200px;
            font-size: 30px;
        }
        .wrapper-right>div{
            backface-visibility: hidden;
        }
        .front {
            background-color: bisque;
            transform: translateZ(100px);
        }
        .back{
            background-color: brown;
            transform: translateZ(-100px) rotateX(180deg);
        }
        .left{
            background-color: chocolate;
            transform: translateX(-100px) rotateY(-90deg);
        }
        .right{
            background-color:coral;
            transform: translateX(100px) rotateY(90deg);
        }
        .top{
            background-color: cornflowerblue;
            transform: translateY(-100px) rotateX(90deg) ;
        }
        .bottom{
            background-color: violet;
            transform: translateY(100px) rotateX(-90deg) ;
        }
 
        @keyframes round{
            0%{
                transform: rotateY(0deg) rotateX(0deg) rotateZ(0deg);
            }
            100%{
                transform:  rotateX(360deg) rotateZ(360deg);
            }
        }
</style>

关于perspective的补充猜想

上面说到,在当前的理论下存在着一个无法解释的问题,就是当元素沿z轴负方向移动的距离超过perspective时,按理说元素应该消失了,但实际上它并没有消失。这里提出一个猜想,就是这个perspective的值并不是消失点到屏幕的实际距离,它只是一个参考值。因为我们的3D模型只是一个抽象模型,它不具有实际的像素大小,只有相对值对模型来说才是有意义的。

假设perspective的值设置为900px,那么当translateZ的值为-900px时,元素的视觉大小将变为实际大小的一半。同理,当值为-1800像素时,元素的大小变为实际大小的三分之一,这样,值越大,视觉上的大小就越趋近于0(因为分母趋于无穷大)。而当传入的是正值时,表示元素向屏幕前方移动,经过测试,在perspective为900px的情况下,这个值最大只能传入892px,一旦超过这个值,元素将立即不能显示(该行为还是比较诡异的,因为它并不是恰好在900像素时不能显示)。

总结

以上就是关于2D和3D转换的介绍,目前2D和3D转换的支持性还不算特别好,在使用的时候需要注意目标客户所用的浏览器。关于它们的原理暂时并没有查到非常权威的理论,因此只能是结合现有的一些资料和实际的例子进行推测,好在该理论可以解释大部分的行为。我在网上也看到一些理论,其中一部分甚至完全无法解释3D模型。我个人提出的这个理论,虽然有一些没有思考清楚的地方,但总归不会自相矛盾,如果大家觉得有什么建议,欢迎在评论区讨论。

猜你喜欢

转载自blog.csdn.net/qq_41694291/article/details/102639499