以下内容根据官方规范翻译,没有翻译关于SVG变换的内容和关于矩阵计算的内容。
一般情况下,元素在一个无景深无立体感的平面(flat plane)上渲染,这个平面就是其包含块所处的平面。同时,页面上的其他元素也共享这个平面。2D变换函数虽然能改变元素的表现,但是这个被改变的元素仍然是在其包含块所处的平面内被渲染。
3D变换会产生一个变换矩阵,该变换矩阵在Z轴上的分量不为0。结果是把元素渲染到一个不同于其包含块所处的平面内。这将影响到通常情况下的“后来居上”的渲染规则:变换元素可能会和其相邻的其他元素相互交叉。
例子
这个例子演示了3D变换对一个元素的影响。
<style>
div {
height: 150px;
width: 150px;
}
.container {
border: 1px solid black;
}
.transformed {
transform: rotateY(50deg);
}
</style> <div class="container">
<div class="transformed"></div>
</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进行缩放。
图的上半部分,z是d的一半。从假设的眼睛的位置看,为了让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)根据下列规则计算:
- 以单位矩阵(identity matrix)开始
- 计算perspective-origin的X值和Y值并按计算值进行平移(translate)
- 乘以变换函数perspective()所用的矩阵,其中长度值由perspective属性提供。矩阵如图所示:
- 用perspective-origin的X值和Y值的相反数进行平移(translate)
步骤3用到的矩阵:
例子
这个例子演示了应用perspective属性后可以让3D变换看起来更真实。
<style>
div {
height: 150px;
width: 150px;
}
.container {
perspective: 500px;
border: 1px solid black;
}
.transformed {
transform: rotateY(50deg);
}
</style> <div class="container">
<div class="transformed"></div>
</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的尺寸计算。
该属性的语法:
perspective-origin: x-position; /* one-value syntax */ perspective-origin: x-position y-position; /* two-value syntax */ /*当 x-position 和 y-position 为关键字时,以下写法是允许的:*/ 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)进行变换。
例子
<style>
.container {
background-color: rgba(0, 0, 0, 0.3);
perspective: 500px;
}
.container > div {
position: absolute;
left: 0;
}
.container > :first-child {
transform: rotateY(45deg);
background-color: orange;
top: 10px;
height: 135px;
}
.container > :last-child {
transform: translateZ(40px);
background-color: rgba(0, 0, 255, 0.6);
top: 50px;
height: 100px;
}
</style> <div class="container">
<div></div>
<div></div>
</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)。
例子
<style>
div {
height: 150px;
width: 150px;
}
.container {
perspective: 500px;
border: 1px solid black;
}
.transformed {
transform: rotateY(50deg);
background-color: blue;
}
.child {
transform-origin: top left;
transform: rotateX(40deg);
background-color: lime;
}
</style> <div class="container">
<div class="transformed">
<div class="child"></div>
</div>
</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变换的元素会从这个平面内“翘出来”,翘到他们变换后所在的平面内。
例子
<style>
div {
height: 150px;
width: 150px;
}
.container {
perspective: 500px;
border: 1px solid black;
}
.transformed {
transform-style: preserve-3d;
transform: rotateY(50deg);
background-color: blue;
}
.child {
transform-origin: top left;
transform: rotateX(40deg);
background-color: lime;
}
</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”:
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得到的。累加规则如下:
Let transform be the identity matrix.
Let current element be the transformed element.
Let ancestor block be the element that establishes the transformed element’s containing block.
-
While current element is not the element that establishes the transformed element’s 3D rendering context:
If current element has a value for transform which is not none, pre-multiply current element’s transformation matrix with the transform.
Compute a translation matrix which represents the offset of current element from its ancestor block, and pre-multiply that matrix into the transform.
If ancestor block has a value for perspective which is not none, pre-multiply the ancestor block’s perspective matrix into the transform.
Let ancestor block be the element that establishes the current element’s containing block.
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属性是必须的。
<style>
.body { perspective: 500px; }
#card {
position: relative;
height: 300px; width: 200px;
transition: transform 1s;
transform-style: preserve-3d;
}
#card.flipped {
transform: rotateY(180deg);
}
.face {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background-color: silver;
border-radius: 40px;
backface-visibility: hidden;
}
.back {
transform: rotateY(180deg);
}
</style>
<div id="card" onclick="this.classList.toggle('flipped')">
<div class="front face">Front</div>
<div class="back face">Back</div>
</div>
backface-visibility属性
可取值:visible | hidden
默认值:visible
该属性对2D变换无效。
visible 表示背面可见,允许显示正面的镜像。
hidden 表示背面不可见。
The visibility of an element with backface-visibility: hidden is determined as follows:
Compute the element’s accumulated 3D transformation matrix.
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,元素在该坐标轴上的尺寸不变。另外,负号表示对称变换。
scaleZ()
该函数只接受一个参数,相当于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 |
参考资料和相关文章:
3、为何使用了 css3 translate3d 会导致显示模糊?