首页 > 代码库 > CSS 3 学习——transform 3D转换渲染

CSS 3 学习——transform 3D转换渲染

以下内容根据官方规范翻译,没有翻译关于SVG变换的内容和关于矩阵计算的内容。

一般情况下,元素在一个无景深无立体感的平面(flat plane)上渲染,这个平面就是其包含块所处的平面。同时,页面上的其他元素也共享这个平面。2D变换函数虽然能改变元素的表现,但是这个被改变的元素仍然是在其包含块所处的平面内被渲染。

3D变换会产生一个变换矩阵,该变换矩阵在Z轴上的分量不为0。结果是把元素渲染到一个不同于其包含块所处的平面内。这将影响到通常情况下的“后来居上”的渲染规则:变换元素可能会和其相邻的其他元素相互交叉。

例子

这个例子演示了3D变换对一个元素的影响。

 1 <style>
 2 div {
 3     height: 150px;
 4     width: 150px;
 5 }
 6 .container {
 7     border: 1px solid black;
 8 }
 9 .transformed {
10     transform: rotateY(50deg);
11 }
12 </style>
13 
14 <div class="container">
15     <div class="transformed"></div>
16 </div>

技术分享

例子中蓝色div进行了一个绕着Y轴旋转50deg的旋转变换,从结果来看,蓝色div仅仅是变窄了,并没有3D效果,因为变换还是在无景深无立体感的2D平面上进行的,还需接着往下看。

 

Perspective 透视

perspective和perspective-origin属性能给舞台(scene,变换元素所处的空间)添加纵深感,结果就是元素距离观看者越近就表现得越大,越远就表现得越小(通过变换可以改变元素在Z轴上的位置)。

perspective属性指定观看者的眼睛(假设的)与屏幕 (drawing plane)之间的距离。如果将perspective属性的值设为d,则元素的缩放比例就等于d/(d ? z),z是元素在Z轴上的位置,更准确的说是变换前元素所在的与Z轴垂直的平面在Z轴上的坐标位置。

技术分享

图中演示了元素如何根据perspective属性和z position进行缩放。

图的上半部分,zd的一半。从假设的眼睛的位置看,为了让drawing surface上的黑色实线圆看起来好像在图中虚线圆的位置,黑色实线圆被放大了2倍,结果就是drawing surface上的蓝色圆。图的下半部分,黑色实线圆被缩小为原来的1/3,让它看起来好像在drawing surface后面的虚线位置。

默认情况下,观看者的眼睛正对着的位置在drawing(surface)的中心。然而可以通过perspective-origin属性改变这个位置(for example, if a web page contains multiple drawings that should share a common perspective property,这句英语是理解perspective与perspective()区别的关键)。

技术分享

图中演示了perspective origin上移后对元素的影响。

透视变换矩阵(perspective matrix)根据下列规则计算:

  1. 以单位矩阵(identity matrix)开始
  2. 计算perspective-origin的X值和Y值并按计算值进行平移(translate)
  3. 乘以变换函数perspective()所用的矩阵,其中长度值由perspective属性提供。矩阵如图所示:
  4. 用perspective-origin的X值和Y值的相反数进行平移(translate)

步骤3用到的矩阵:

技术分享

例子

这个例子演示了应用perspective属性后可以让3D变换看起来更真实。

 1 <style>
 2 div {
 3   height: 150px;
 4   width: 150px;
 5 }
 6 .container {
 7   perspective: 500px;
 8   border: 1px solid black;
 9 }
10 .transformed {
11   transform: rotateY(50deg);
12 }
13 </style>
14 
15 <div class="container">
16   <div class="transformed"></div>
17 </div>

技术分享

蓝色div与前面例子中的蓝色div进行了相同的3D变换,但是这个例子中的蓝色div的渲染受到其父元素的perspective属性值的影响。考虑到景深,perspective属性使在Z轴上有正坐标值的点在X轴和Y轴上放大了(距离观看者更近了),在Z轴上有负坐标的点在X轴和Y轴上缩小了,距离观看者更远了。

补充:

perspective属性

可取值:none | <length>

该属性能应用于可变换的元素(transformable elements)。

<length>只能为正值,是观看者与z=0平面的距离,使具有3D变换的元素产生透视效果(当值为0或负值时,无透视效果,变换元素表现为扁平化)。

值为none时,无透视效果,元素在画布上扁平化呈现。

perspective属性值不为none的元素,创建一个层叠上下文和一个包含块(规范上说和相对定位有点类似,和transform很像)。

perspective和perspective-origin属性的值被用于计算透视矩阵(perspective matrix)。

 

perspective-origin属性

perspective-origin用于创建perspective属性的起始点。事实上,该属性设置了观看者的眼睛在舞台元素上的投影位置。

可取值:<percentage> | <length> | 关键字

默认值为:50% 50%

第一个值表示与border box左边界的距离,第二个值表示与border box上边界的距离。当只指定一个值时,第二个值作为50%处理。

<percentage>相对于舞台元素(reference box)的border box的尺寸计算。

该属性的语法:

1 perspective-origin: x-position;            /* one-value syntax */
2 
3 perspective-origin: x-position y-position; /* two-value syntax */
4 
5  
6 /*当 x-position 和 y-position 为关键字时,以下写法是允许的:*/
7 
8  perspective-origin: y-position x-position;

x-position

  • <percentage> 百分比,相对于元素宽度,可为负值。
  • <length> 长度值,可为负值。
  • left,关键字,0值的简记。
  • center,关键字,50%的简记。
  • right,关键字,100%的简记。

y-position

  • <percentage> 百分比,相对于元素的高度,可为负值。
  • <length> 长度值,可为负值。
  • top,关键字,0值得简记。
  • center,关键字,50%的简记。
  • bottom,关键字,100%的简记。

3D rendering context  3D渲染上下文

这部分内容主要讲3D变换的渲染模型和transform-style属性。

一个3D渲染上下文本质上是一个三维坐标系(a common three-dimensional coordinate system),这个三维坐标系由一组具有共同祖先(舞台元素)并且进行3D变换的元素共享。

在3D渲染上下文中的元素在渲染时层次关系由他们在Z轴上的位置决定。如果3D变换使他们相互交叉,那么在渲染时就让他们交叉着渲染。

首先transform-style属性值为flat的元素创建一个3D渲染上下文,把这个元素称为祖先元素(舞台元素)。

其次,如果后代元素的transform-style属性值为auto或preserve-3d,则该后代元素将其所处的3D渲染上下文(enclosing 3D rendering context)共享给它包含的后代元素。

再次,如果一个后代元素的transform-style属性值为flat,它虽然参与到包含他的父3D渲染上下文(containing 3D rendering context)中,但是同时对于它包含的后代元素,它也创建一个新的3D渲染上下文。不过,对于这个新创建的3D渲染上下文,在渲染时不是作为一个三维空间渲染,而是作为一个平面渲染。

注意:3D渲染上下文的概念类似于层叠上下文的概念。一个有明确z-index值的定位元素自身创建一个层叠上下文,但是他还是参与到他所处的祖先元素创建的层叠上下文中。相似地,一个元素能为他的后代元素创建一个3D渲染上下文,但是他自身还是参与到他的祖先元素创建的3D渲染上下文中。就像元素在层叠上下文中按照z-index属性决定的层次渲染一样,元素在3D渲染上下文中按照z-depth顺序渲染而且可以互相交叉。

一些CSS属性值使一个元素及其后代元素在渲染时作为一个整体渲染(这些属性及值在transform-style属性的介绍中查看)。本质上这些CSS属性值强制将元素的transform-style属性的值重设为flat,这些元素被称为扁平元素(flattening elements)。所以这些元素都会创建一个新的3D渲染上下文。根元素的transform-style属性的值为flat。

在3D渲染上下文中元素的渲染遵循以下规则(规则中提到的数字步骤参见CSS 2.1, Appendix E, Section E.2 Painting Order):

     A、步骤1和2提到的创建元素(establishing element)的background,border和其他的盒子装饰。

     B、按照步骤3—7的顺序把创建元素的内容和后代元素中没有进行3D变换的元素渲染到z = 0的平面内。

     C、将3D变换的元素按照各自最终的3D变换矩阵(accumulated 3D transformation matrix)渲染到他们各自所在的平面内。

     D、按照Newell’s algorithm渲染步骤B和C导致的不同平面之间的交叉。

     E、平面的结果集渲染到步骤A提到的background和其他盒子装饰的上层。共面的3D变换元素按照painting order渲染。

要注意到的是拥有负的z轴向分量(negative z-component)的变换元素会渲染到创建元素(establishing element)的内容和后代非变换元素的后面(3维空间的后面)。也就是说,3D变换的元素可能会贯穿创建元素的内容和后代非变换元素。

注意:因为3D变换元素在同一个3D渲染上下文中可能按深度排序和相互交叉,所以实际上好像是把它们当做兄弟元素进行渲染。transform-style: perserve-3d可以被看作是将所有3D变换元素提升到了创建元素创建的同一个3D渲染上下文中,但是他们在进行3D变换时还是按照各自最终的3D变换矩阵(accumulated 3D transformation matrix)进行变换。

例子

 1 <style>
 2 .container {
 3   background-color: rgba(0, 0, 0, 0.3);
 4   perspective: 500px;
 5 }
 6 .container > div {
 7   position: absolute;
 8   left: 0;
 9 }
10 .container > :first-child {
11   transform: rotateY(45deg);
12   background-color: orange;
13   top: 10px;
14   height: 135px;
15 }
16 .container > :last-child {
17   transform: translateZ(40px);
18   background-color: rgba(0, 0, 255, 0.6);
19   top: 50px;
20   height: 100px;
21 }
22 </style>
23 
24 <div class="container">
25   <div></div>
26   <div></div>
27 </div>

技术分享

这个例子演示了在同一个3D渲染上下文中的元素可以相互交叉。容器元素为它本身和它的两个子元素创建了一个3D渲染上下文。两个子元素相互交叉,同时橙色的子元素也和容器元素的文字内容交叉。

perspective属性可以为3D变换上下文中的后代变换元素提供一个共同的透视变换矩阵(perspective matrix),从而被用来确保这些3D变换元素好像处在同一个有深度的三维空间中。这个透视变换矩阵在计算最终的3D矩阵( accumulated 3D matrix computation)时被考虑在内。

默认情况下,perspective属性值不为none的元素是扁平的(flattening),因此它创建一个3D渲染上下文。然而,把transform-style属性的值设置为preserve-3d可以让这个透视元素(perspective element)扩展包含他的3D渲染上下文的范围至他的后代元素(provided no other grouping property values are in effect)。

例子

 1 <style>
 2 div {
 3   height: 150px;
 4   width: 150px;
 5 }
 6 .container {
 7   perspective: 500px;
 8   border: 1px solid black;
 9 }
10 .transformed {
11   transform: rotateY(50deg);
12   background-color: blue;
13 }
14 .child {
15   transform-origin: top left;
16   transform: rotateX(40deg);
17   background-color: lime;
18 }
19 </style>
20 
21 <div class="container">
22   <div class="transformed">
23     <div class="child"></div>
24   </div>
25 </div>

技术分享

这个例子演示了内嵌的3D变换元素是如何渲染的。就像前面的例子一样,蓝色div的渲染受到了他的父元素的perspective属性值的影响。柠檬色的div同样也进行了3D变换,绕着X轴旋转40deg(通过transform-origin属性,X轴被固定在了顶部)。然而,柠檬的的div被渲染到了他的父元素的平面内,因为他不在他父元素所在的3D渲染上下文中。他的父元素是平的(flattening)。所以柠檬色的div仅仅是看起来短了一些,他没有从蓝色div内“翘出来”。

Transformed element hierarchies 变换元素的层次

默认情况下,变换元素是平的(flattening),因此他们创建一个3D渲染上下文。然而,在同一个三维空间中构造层次结构是很有用的。通过将transform-style属性的值设为preserve-3d可以使同一三维空间内的变换元素区分各自的层次,同时也允许变换的后代元素共享同一个3D渲染上下文。在3D渲染上下文中,非3D变换的后代元素被渲染到前文步骤C中的平面内,而3D变换的元素会从这个平面内“翘出来”,翘到他们变换后所在的平面内。

例子

 1 <style>
 2 div {
 3   height: 150px;
 4   width: 150px;
 5 }
 6 .container {
 7   perspective: 500px;
 8   border: 1px solid black;
 9 }
10 .transformed {
11   transform-style: preserve-3d;
12   transform: rotateY(50deg);
13   background-color: blue;
14 }
15 .child {
16   transform-origin: top left;
17   transform: rotateX(40deg);
18   background-color: lime;
19 }
20 </style>

技术分享

这个例子中蓝色div的transform-style属性的值被设为了preserve-3d,其余的代码和前一个例子完全相同。现在,蓝色的div将容器元素的3D渲染上下文的范围扩展了,蓝色div和柠檬色的div共享同一个三维空间。同时受到容器元素perspective属性的影响,柠檬色的div从他的父元素的平面内翘出来了。

transform-style属性

该属性能应用于可变换的元素(transformable elements)。

该属性要在父元素上设置,对该父元素的子元素(或者说后代元素)起作用。

transform-style的可取值为:auto   |   flat | preserve-3d

默认值为 auto,不可继承。

当transform-style的值为“flat”时,元素创建一个层叠上下文(stacking context)和一个3D渲染上下文(3D rendering context)。

transform-style属性值为“auto”的元素在计算3D渲染上下文时会被忽略。

transform-style属性值为“preserve-3d”的元素会扩大其所处的3D渲染上下文的范围,即使transform 或 preserve属性的值会导致扁平化。同时,transform-style属性值为“preserve-3d”的元素会创建一个层叠上下文和一个包含块。

Grouping property values

以下CSS属性值会导致后代元素(descendant elements)扁平化显示,也就是说强制父元素transform-style属性的值转变为“flat”:

  • overflow: any value other than visible.

  • opacity: any value less than 1.

  • filter: any value other than none.

  • clip: any value other than auto.

  • clip-path: any value other than none.

  • isolation: used value of isolate.

  • mask-image: any value other than none.

  • mask-border-source: any value other than none.

  • mix-blend-mode: any value other than normal.

以下CSS属性值会使transform-style的默认值重设为flat:

  • transform: any value other than none.

  • perspective: any value other than none.

Accumulated 3D Transformation Matrix Computation

在3D渲染上下文中,用来渲染一个元素的最终变换值是通过累加accumulated 3D transformation matrix得到的。累加规则如下:

  1. Let transform be the identity matrix.

  2. Let current element be the transformed element.

  3. Let ancestor block be the element that establishes the transformed element’s containing block.

  4. While current element is not the element that establishes the transformed element’s 3D rendering context:

    1. If current element has a value for transform which is not none, pre-multiply current element’s transformation matrix with the transform.

    2. Compute a translation matrix which represents the offset of current element from its ancestor block, and pre-multiply that matrix into the transform.

    3. If ancestor block has a value for perspective which is not none, pre-multiply the ancestor block’s perspective matrix into the transform.

    4. Let ancestor block be the element that establishes the current element’s containing block.

    5. Let current element be the ancestor block.

注意:accumulated 3D transformation matrix把视觉格式化模型(visual formatting model)在变换元素上产生的偏移量计算在内,而且也把创建3D渲染上下文的元素(舞台元素)与变换元素之间树形图上的元素考虑在内。

Backface Visibility 背面可见性

利用三维变换,使看到变换元素的背面成为可能。在背面可见的情况下,不管是哪一面,3D变换元素都显示同样的内容,背面内容是前面内容的镜像(就好像元素被投影到一面镜子上)。默认情况下,当元素的背面朝向观看者时,观看者可以看到这个背面的内容。事实上,当元素的背面朝向观看者时,开发者可以通过backface-visibility属性,让该元素的内容不可见。

如果一个动画元素的backface-visibility属性的值为hidden,那么他的内容是交替可见的。只有当他的前面朝向观看者时,他的内容才是可见的。

例子

这个例子演示了如何制作一张可通过点击进行翻转的卡片。为了避免翻转时出现扁平化,#card元素的transform-style: preserve-3d属性是必须的。

 

 1 <style>
 2 .body { perspective: 500px; }
 3 #card {
 4   position: relative;
 5   height: 300px; width: 200px;
 6   transition: transform 1s;
 7   transform-style: preserve-3d;
 8 }
 9 #card.flipped {
10   transform: rotateY(180deg);
11 }
12 .face {
13   position: absolute;
14   top: 0; left: 0;
15   width: 100%; height: 100%;
16   background-color: silver;
17   border-radius: 40px;
18   backface-visibility: hidden;
19 }
20 .back {
21   transform: rotateY(180deg);
22 }
23 </style>
24 <div id="card" onclick="this.classList.toggle(‘flipped‘)">
25   <div class="front face">Front</div>
26   <div class="back face">Back</div>
27 </div>

backface-visibility属性

可取值:visible | hidden

默认值:visible

该属性对2D变换无效。

visible 表示背面可见,允许显示正面的镜像。

hidden 表示背面不可见。

The visibility of an element with backface-visibility: hidden is determined as follows:

  1. Compute the element’s accumulated 3D transformation matrix.

  2. If the component of the matrix in row 3, column 3 is negative, then the element should be hidden. Otherwise it is visible.

3D Transform Functions

MDN上讲的比较详细,不过是英文。地址:transform-function

元素变换时所用的坐标系是局部坐标系

matrix3d()

该函数接受16个参数,这16个参数是一个4*4的矩阵。具体看规范Mathematical Description of Transform Functions。

translate3d()

该函数接受3个参数,前两个参数可以为<length>,也可以为<percentage>,第三个参数只能为<length>。百分数相对于变换元素的border box的尺寸计算。

三个参数组成一个三维向量,三个值分别表示这个向量在相应坐标轴上的坐标,变换元素根据这个向量进行平移。

translateX()和translateY()在2D变换中已经介绍过了,这里只介绍translateZ()。

translateZ()

该函数只接受一个参数,使变换元素沿着Z轴移动指定的长度,相当于translate3d(0,0,tz),而且只能用在3D变换中。

scale3d()

该函数接受3个<number>参数,分别代表变换元素在X轴,Y轴和Z轴上的缩放比例。如果对应某一坐标轴上的参数值在(-1,1)范围内,则元素在该坐标轴方向上缩小,超出上述范围,则元素在该坐标轴方向上放大。如果等于1或-1,元素在该坐标轴上的尺寸不变。另外,负号表示对称变换。

scaleY()

该函数只接受一个参数,相当于scale3d(1,1,sz)。

注意:单独使用3D的缩放变换,除了能在X轴和Y轴上看到效果,Z轴上是看不到效果的。

rotate3d()

该函数可以使元素绕着坐标轴进行3D旋转变换,正值表示顺时针旋转,负值表示逆时针旋转。

在3D空间中,旋转有三个自由度。旋转轴可以通过一个经过坐标原点(transform-origin指定)的三维向量 [x,y,z] 表示。如果这个向量不是标准化的向量(单位向量),浏览器会在内部自动将其转换为标准化的向量。如果这个向量不能被标准化(比如[0,0,0]),那么元素本次的旋转变换将无效,而不是将整个transform属性无效。

注意:与2D的旋转变换相比,交换同一元素的不同的3D旋转变换的次序得到的结果是完全不同的,所以对所应用的3D旋转变换的顺序要引起重视。

语法:rotate3d(x, y, z, a)

x  表示旋转轴在x轴上的坐标

y  表示旋转轴在y轴上的坐标

z  表示旋转轴在z轴上的坐标

a  是一个<angle>值,表示旋转的角度,正值表示顺时针旋转;负值表示逆时针旋转。

rotateX()

只接受一个<angle>参数,相当于rotate3D(1, 0, 0, a)

rotateY()

只接受一个<angle>参数,相当于rotate3D(0, 1, 0, a)

rotateZ()

只接受一个<angle>参数,相当于rotate3D(0, 0, 1, a)

perspective()

该函数接受一个<length>参数,本质是指定一个透视投影矩阵(perspective projection matrix),使元素进行透视投影变换。这个矩阵根据坐标点的z坐标对x坐标和y坐标进行缩放:放大z坐标为正的点,使该点远离原点;缩小z坐标为负的点,使该点靠近原点;z=0平面上的点不变。传入该函数的参数代表的是观看者的眼睛(假设的)与z=0平面之间的距离。值越小,得到的视锥体越扁平,透视效果越明显。比如,传入参数为1000px时,结果是一个适中的透视缩短效果;传入参数为200px时,结果是一个极端的透视缩短效果。传入的值必须大于0,否则无效。

总结

要实现3D变换,要用到下面几个属性:

属性

描述

CSS

transform

向元素应用 2D 或 3D 转换。

3

transform-origin

设置变换基点(局部坐标系原点)的位置。

3

transform-style

规定被嵌套元素如何在 3D 空间中显示。

3

perspective

规定 3D 元素的透视效果。

3

perspective-origin

规定观看者眼睛的投影位置。

3

backface-visibility

定义元素内容在不面对屏幕时是否可见。

3

 

 

参考资料和相关文章:

1、transform-function

2、CSS3 transform

3、为何使用了 css3 translate3d 会导致显示模糊?

4、CSS3 3D Transform

5、使用 CSS 转换为你的网页带来活力让你的网页栩栩如生

6、CSS Transforms Module Level 1

7、深入探索透视投影变换

 

CSS 3 学习——transform 3D转换渲染