当我们需要在Web中渲染HTML元素的3d效果,transform转换属性就能派上用场,该属性也有人称之为形变或变形属性。transform属性允许我们将HTML元素进行旋转(rotate)、缩放(scale)、移动(translate)和扭曲(skew)或以更复杂而精确的 matrix 方式实现前述的元素转换效果。CSS的transform属性提供两种转换场景:① 基于二维(xy)的2d转换,② 基于三维(xyz)的3d转换。
通过transform实现3d转换,首先需要容器元素设置有perspective属性,该属性用于定义三维透视视图,以营造景深效果,属性值为长度数值,例如,perspective: 1200px,这表示观者所能看到的场景其纵深为1200个像素。一般而言,perspective属性值越小3d效果越容易呈现出来但也容易失真、越大3d效果越不明显但总体效果更为细腻。以下代码演示如何在CSS中设置perspective属性:
.wrapper {
perspective: 1200px;
}
这样,假如有一个class="wrapper"的元素,则该元素内的子元素就具备了以3d渲染自己的机会,前提是它或它们要使用transform属性的3d相关的转换参数——
<style>
.wrapper {
perspective: 1200px;
}
.inner {
transform: rotateX(45deg) rotateY(15deg) rotateZ(60deg);
}
</style>
<div class="wrapper">
<div class="inner"></div>
</div>
上面的代码由于缺少诸如尺寸、背景颜色等相关细节,运行起来不会有什么结果,这些代码只是演示元素基于3d转换的组织方式,其核心是,父元素即容器元素设置三维透视视图(perspective),子元素应用3d transform转换(例如上述代码中的 rotateX/Y/Z)。
元素实现3d转换还有两个重要的属性:
.wrapper {
/* 其它代码,例如 perspective: 1200px; */
transfrom-origin: center; /* 转换原点 : 缺省默认是 center */
transform-style: flat; /* 转换形态 : 缺省默认是 flat | 可选值 preserve-3d */
}
以上两个属性都需要在容器元素中设置。其中:
① 属性 transform-origin 转换原点可以使用单值、双值、三值,单值时表示xy都是用同一个值、z值为0,双值表示xy值各自设计、z值为0,三值表示xyz各自设计,属性值支持百分比、距离单位、关键字(left、right、top、bottom、center)。
② 属性 transform-style 转换形态只有两个值可选,属性缺省时默认值为flat,可选值 preserve-3d。flat意为平面,但这里它并不是表示2d的意思,它的确切含义是将Z轴挤压在xy组成的面上,呈现出来的结果是3d的效果,但由于挤压在一个平面之上,其效果并不真实自然,可以将之理解为伪3d;preserve-3d一旦启用,渲染结果则是真正的3d效果,其规范的是 在3d场景中建立3d对象的真实遮挡关系而非将z轴挤压在xy平面之上。换言之,flat展现形态所渲染的3d效果没有真实3d世界对象间的遮挡关系,彼此间的遮挡行为所依循的是元素在代码流中的先后出现次序以及在CSS中所设置的 z-index 层级关系,而preserve-3d则模拟真实世界对象间的现实位置关系,其中元素对象的Z轴正负值是遮挡关系的主要依据(元素在代码流中的出现次序、CSS的z-index层级关系依然存在但仅在Z轴的值为0或彼此该值相等的时候有效)。
最后我们给出一组可以在线运行的代码,通过比对代码和运行效果将有助于对本文介绍的知识的理解。代码中,有一个容器元素 id="papa",其内有两个子元素,其一是 id="daughter" 的长方形,运行时它保持固定不变,其二是 id="son" 的圆球,运行后可以通过滑杆操作它在xyz三轴上的位置,此外,3d场景展现模式默认为flat,可以在运行后启用preserve-3d:
<style>
#papa {
margin: 20px auto;
width: 800px;
height: 560px;
border: 1px solid gray;
transform-style: flat;
perspective: 600px;
display: grid;
place-items: center;
position: relative;
}
#son {
position: absolute;
width: 50px;
height: 50px;
border-radius: 50%;
background: linear-gradient(35deg, green, skyblue);
filter: drop-shadow(2px 4px 8px gray);
transform: rotateZ(var(--a)) translate3d(var(--x), var(--y), var(--z));
--x: 0;
--y: 0;
--z: 0;
--a: 0;
}
#daughter {
position: absolute;
width: 400px;
height: 160px;
background: linear-gradient(olive, pink);
opacity: 0.9;
transform: rotateX(45deg) rotateY(15deg) rotateZ(30deg);
}
.wrap { margin: auto; width: 800px; height: fit-content; position: relative; }
.wrap button, .wrap label, .wrap input { color: green; cursor: pointer; }
.wrap output { color: red; }
</style>
<div id="papa">
<div id="daughter"></div>
<div id="son"></div>
</div>
<div class="wrap">
<label for="rangeX">小球 X 坐标: </label>
<input id="rangeX" type="range" min="-400" max="400" value="0" />
<output>0</output><br>
<label for="rangeY">小球 Y 坐标: </label>
<input id="rangeY" type="range" min="-300" max="300" value="0" />
<output>0</output><br>
<label for="rangeZ">小球 Z 坐标: </label>
<input id="rangeZ" type="range" min="-600" max="600" value="0" />
<output>0</output><br>
<label for="checkBox">启用 preserve-3d : </label>
<input id="checkBox" type="checkbox" value="chk" />
<output>transform-style: flat</output><br>
</div>
<script>
const ranges = [ [rangeX, '--x'], [rangeY, '--y'], [rangeZ, '--z'] ];
const outputs = document.querySelectorAll('output');
ranges.forEach( (range, idx) => {
range[0].oninput = () => {
outputs[idx].value = range[0].value + 'px';
son.style.setProperty(range[1], range[0].value + 'px');
};
});
checkBox.onclick = () => {
papa.style.setProperty('transform-style', checkBox.checked ? 'preserve-3d' : 'flat');
outputs[3].value = 'transform-style: ' + (checkBox.checked ? 'preserve-3d' : 'flat');
};
</script>
注意:演示代码故意设置Z轴的最大值为600px,和景深设置值一致。调整Z轴的值为600px,将什么都看不到,原因是此时观者的眼睛和屏幕表面同在一个水平线上了。
3d转换不止这些内容,本文仅揭示其冰山的一角,还有更多精彩留待大家探索。