【转载】接受前端挑战:用CSS实现3D立方体

本文转载自:众成翻译 译者:camiler 链接:http://www.zcfy.cc/article/872 原文:https://www.smashingmagazine.com/2016/07/front-end-challenge-accepted-css-3d-cube/

你喜欢挑战么?你愿意承担一项以前从没遇到过的任务并且按时完成么?如果在进行任务中,你碰到来一个似乎无法解决的问题呢?我想分享我使用CSS 3D效果的经历,那是第一次用于实际项目中,以此来激励你接受挑战。

那是平常的一天,当Eugene( CreativePeople的经理)写信给我的时候。他寄给我一个视频,说他正在为一个新项目开发一个概念,而且想知道我是否可能开发一个像视频里那样的东西。

这是一个绕着一个轴旋转的3D物体(准确地说是个立方体)。对于用CSS 3D工作我已经有一些经验了,于是我的脑海里开始形成一个解决方案。我Google搜索了像“CSS 3D cube”这样的关键词来确认我的想法,随后我回复Eugene说我可以。

Eugene下一个问题是问我是否愿意承担这个项目?我喜欢复杂的任务,所以我不能拒绝。在这一刻,我还没有意识到我正陷入其中,但我无法确定是否可以完成。

理解轴(字面翻译是磨斧)

提醒下这个axes不是战斧,而是 数轴的意思,正如我们在学校学到的三维直角坐标系一样的轴线。维基百科定义:

直角坐标系是一个两两垂直有序的三元线行成的三维空间,三条轴都有一个单独的单位长度并且每一条轴线有一个方向。

下面的图片展示了在Web浏览器中怎样确定轴线方向。 A right-handed three-dimensional Cartesian coordinate system with the Z-axis pointing towards the viewer.

一个以z轴朝向观察者的右手三维直角坐标系。 (图片来自: 维基共享资源)。

x轴平行,y轴垂直,z轴指向正对你的屏幕。z轴的零点就是屏幕所在的平面。记住这一点。

理解透视值

要创建一个3D物体,我需要一个具有透视效果的元素(我们称之为“scene”)。透视大小就是这个场景的深度,并且它取决于它包含的物体大小。

.scene {
    perspective: 800px;
}

如果透视距离太小,物体可能会被扭曲。如果太大,3D效果将减少到没有。

由Anna Selezniova (@askdCodePen)上编写.

此外,在这个场景中对于所有物体而言只有一个视野角度。3D效果取决于观察点的位置。

由Anna Selezniova (@askdCodePen)上编写。

那么,怎么计算透视值呢?我发现它取决于轴的旋转。对于x轴,高度值乘以4应该合适。对于y轴,应该是宽度值乘以4。这是我的魔法公式:

const perspective = dimension * 4;

考虑所有侧面

决定透视值后,我开始创建3D对象。我选择了一个立方体,因为它简单可预测。立方体元素由普通的div创建,相对定位,宽度和高度都定义(200px)。通过具有preserve-3d值的transform-style 属性使它转变成一个3D对象。它告诉浏览器通过3D世界的规则来渲染所有内嵌元素。

在我的例子中,这个立方体有6个绝对定位的div(或者说是侧面)。类名相当于几个侧面(后面,左边,右边,上面,下面,前面)的初始位置。标记如下:

<div class="scene">
  <div class="cube">
    <div class="side back"></div>
    <div class="side left"></div>
    <div class="side right"></div>
    <div class="side top"></div>
    <div class="side bottom"></div>
    <div class="side front"></div>
  </div>
</div> 

默认情况下,所有侧面都在一个平面上。所以,我需要将它们重新排列。演示如下:

由Anna Selezniova (@askdCodePen)上编写。

由此产生CSS如下:

.cube {
    position:relative;
    width: 200px;
    height: 200px;
    transform-style: preserve-3d;
}
.side {
    position: absolute;
    width: 200px;
    height: 200px;
}
.back {
    transform: translateZ(-100px);
}
.left {
    transform: translateX(-100px) rotateY(90deg);
}
.right {
    transform: translateX(100px) rotateY(90deg);
}
.top {
    transform: translateY(-100px) rotateX(90deg);
}
.bottom {
    transform: translateY(100px) rotateX(90deg);
}
.front {
    transform: translateZ(100px);
}

要旋转这个立方体,我在这个元素上设置 transform属性值是X轴旋转任意角度:

.cube {
    transform: rotateX(42deg);
}

克服缺点

根据任务要求,我打算只沿着x轴旋转这个立方体,所以我不需要左侧或者右侧。我添加了标注来将剩下侧面的初始位置对齐。

我开始旋转立方体时发现底部和背面的标注说明都显示颠倒了:

由Anna Selezniova (@askdCodePen)上编写。

为了解决这个问题,我把每个侧面都围绕x轴旋转了180度:

.back {
    transform: translateZ(-100px) rotateX(180deg);
}
.bottom {
    transform: translateY(100px) rotateX(270deg);
}

超越屏幕

我开始用真实内容填充侧面了,随即就遇到了另一个问题。我需要展示1个像素的虚线,但看起来很糟糕模糊。

由Anna Selezniova (@askdCodePen)上编写。

我立马认识到问题出在哪了。你记得图片延伸到屏幕之外的3D TV广告么?这跟我这个立方体是同一回事。

如果你可以从左侧或者右侧看下这个立方体,就会看到它的中心在屏幕所在的平面上(z轴的零点)并且正面超出了屏幕。因此,在视觉上增大了也模糊了。

由Anna Selezniova (@askdCodePen)上编写。

为了解决这个问题,我沿着z轴移动这个立方体使得正面对齐到屏幕所在的平面:

.cube {
    transform:translateZ(-100px);
}

现在,这个立方体准备的差不多了:

由Anna Selezniova (@askdCodePen)上编写。

使用神奇数字

我猜你已经注意到我使用了这个神奇的数字100来沿着轴移动这些侧面。而100这个值正好是我测试的立方体高度的一半。为什么是一半?因为那个值是立方体侧面(显然是一个正方形)一个内切圆的半径。

const offset = dimension / 2;

如果我需要旋转一个三棱柱,这个圆就是三角形的内切圆。这种情况下,偏移公式就会如下:

const offset = dimension / (2 * Math.sqrt(3));

消除立方体

要想把任务完成,我必须在不同的浏览器中进行测试。 在IE中看到的画面让我陷入沮丧。为了让你知道我在说什么,在你最爱的浏览器中打开这个样例。我改变了一个属性导致在IE中这个立方体显示完全不正确。无论如何,不要偷看源码直到你读了在这个样例下面的那段文字。

由Anna Selezniova (@askdCodePen)上编写。

现实就是IE不支持值是preserve-3dtransform-style属性。通过查看可靠资源Can I Use(notes中第一点)我了解到这一点。在上面的样例中,我将preserve-3d换成了flat。你是不是已经知道了?哼!让你不要偷看了!

我很烦躁,但我并不打算放弃。遇到一个问题就是获得一次学习新东西的机会。再说,我已经接收了这次挑战。

寻找支点

我在找寻一种可以不通过使用transform-style: preserve-3d来创建一个3D对象的方法,最终我发现一个有用的属性:transform-origin。它决定了一个元素变换的中心点。我建了一个可以交互的样例,可以帮助你理解这个属性是如何工作的:

由Anna Selezniova (@askdCodePen)上编写。

在这个例子中,元素的3D旋转是不是和立方体正面很像?这正是我要用的。

(顺便问一下,你尝试过在三维旋转过程中选择多选框backface-visibility:hidden么?这个属性用来在3D变换中隐藏元素的背面)。

重新出发

我开始重做这个立方体。我不必让整个场景进行交互,所以我去掉了scene元素的 perspective属性然后将该属性添加到每个3D变换,这样每个元素的变换就是独立的了。同时,我给每个侧面设置了新属性:transform-origin,其值是立方体中心的位置,以及backface-visibility: hidden。样式改变如下:

.scene {

}
.cube {
    position: relative;
    width: 200px;
    height: 200px;
    transform: perspective(800px) translateZ(-100px);
}
.side {
    position: absolute;
    transform-origin: 50% 50% -100px;
    backface-visibility: hidden;
}

我必须将这些侧面放在正确的位置。由于transform-origin属性,我不用再改变它们的位置,只需要围绕轴旋转它们。这就像魔术一样!我们来目睹一下它的神奇:

由Anna Selezniova (@askdCodePen)上编写。

这些侧面位置的CSS如下:

.back {
    transform: perspective(800px) rotateY(180deg);
}
.top {
    transform: perspective(800px) rotateX(90deg);
}
.bottom {
    transform: perspective(800px) rotateX(-90deg);
}
.front {
    transform: perspective(800px);
}

这里你能看到在运行中的全新立方体:

由Anna Selezniova (@askdCodePen)上编写。

桥是桥路是路,做好自己的事

第二个立方体看起来旋转和第一个一样。但在这个例子中,你需要单独变换每一个侧面。这可能不太容易,尤其是你想控制旋转的中间角度。

此外,如果你在Chrome浏览器打开这个例子,会看到这些侧面在旋转的时候会闪烁,这让我感觉很沮丧。

最后,我将transform-style: preserve-3d属性的简单测试应用在这两个实现立方体的方式中。第一个立方体是默认的,第二个是针对IE浏览器以及不支持preserve-3d的浏览器。

运用数学的力量

最终,我必须实现一个视差效果。通常,这种效果根据用户行为响应,无论是鼠标光标还是滚动条的位置。在这个例子中,这个效果取决于旋转的角度。

由Anna Selezniova (@askdCodePen)上编写。

我有什么数据呢?首先,我有标注文字位置的起点和终点,或者简单说来就是从侧面中心位置到上边和下边的偏移量。其次,我有它旋转的角度

我花了几个小时试图定义一个公式。随后,我恍然大悟。这就是我的灵感:

Graphs of the sine and cosine functions

正弦余弦函数图 (图片: 维基共享资源)。

在正弦余弦函数的帮助下,通过角度我轻松地计算出了每个标注的偏移。这是我提出的公式:

const front_offset = offset * sin(angle) * -1;
const bottom_offset = offset * cos(angle);
const back_offset = offset * sin(angle);
const top_offset = offset * cos(angle) * -1;

总结

现在任务完成了,我很享受这个结果并且将它分享给你。看一下它展示的如何。使用鼠标滚动或者箭头键旋转广告块。同样,你也可以尝试拉出左边的黑三角上下拖动来手动控制旋转的角度(遗憾的是,这个特征在IE浏览器中无法工作)。看起来确实不错吧?而且性能也相当高(大概每秒60帧)。

我很高兴参与了这个网站的开发。在CSS 3D实践中我收获了宝贵的经验,并且发现了许多有意思的属性。更重要的是,我懂得了一个人不应该轻言放弃,很可能你会找到一个方法来完成。

我希望你喜欢我的故事,也希望你现在做好准备迎接新的挑战!

扩展阅读

返回顶部