Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[译]接受前端挑战:用CSS实现3D立方体 #7

Open
camiler opened this issue Sep 8, 2016 · 0 comments
Open

[译]接受前端挑战:用CSS实现3D立方体 #7

camiler opened this issue Sep 8, 2016 · 0 comments

Comments

@camiler
Copy link
Owner

camiler commented Sep 8, 2016

原文: https://www.smashingmagazine.com/2016/07/front-end-challenge-accepted-css-3d-cube/

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

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

这是一个绕着一个轴旋转的3D物体(准确地说是个立方体)。对于用CSS 3D工作我已经有一些经验了,于是我的脑海里开始形成一个解决方案。我goggle搜索了像“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实践中我收获了宝贵的经验,并且发现了许多有意思的属性。更重要的是,我懂得了一个人不应该轻言放弃,很可能你会找到一个方法来完成。

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


ps: 有些地方作者是引经据典,类似中国的谚语,翻译不当请见谅!本文的3D旋转效果确实很赞!

@camiler camiler changed the title 接受前端挑战:用CSS实现3D立方体 [译]接受前端挑战:用CSS实现3D立方体 Sep 8, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant