接受前端挑战:CSS 3D Cube
已发表: 2022-03-10你喜欢挑战吗? 你愿意承担你以前从未遇到过的任务,并在截止日期前完成吗? 如果在执行任务的过程中遇到一个似乎无法解决的问题怎么办? 我想分享我在实际项目中第一次使用 CSS 3D 效果的经验,并激励您接受挑战。
这是一个普通的日子,CreativePeople 的经理 Eugene 给我写信。 他给我发了一段视频,并解释说他正在为一个新项目开发一个概念,并想知道我是否有可能开发出类似于视频中的东西。
关于 SmashingMag 的进一步阅读:
- Beercamp:CSS 3D 实验
- 使用 Clip-Path 创建响应式形状并打破常规
- 让我们玩一下硬件加速的 CSS
它是一个围绕其中一个轴旋转的 3D 对象(准确地说是长方体)。 我已经有了一些使用 CSS 3D 的经验,并且开始在我的脑海中形成一个解决方案。 我在 Google 上搜索了“CSS 3D 立方体”之类的关键字来确认我的想法,并回答 Eugene 说这是可能的。
尤金的下一个问题是我是否愿意接手这个项目? 我喜欢棘手的任务,所以我无法拒绝。 当时,我并没有意识到自己在做什么,但我已经下定了决心。
磨砺你的斧头
让我们提醒自己关于轴——不是战争轴,而是数轴,与我们在学校学习的三维笛卡尔坐标系中的轴相同。 正如维基百科告诉我们的:
三维空间的笛卡尔坐标系是有序的三重线(轴),它们成对垂直,所有三个轴都有一个长度单位,每个轴都有一个方向。
下图显示了轴在 Web 浏览器中的方向。

x 轴是水平的,y 轴是垂直的,z 轴看起来像是从屏幕上朝你走来的。 z 轴的零值是屏幕的平面。 记住这一点。
清理视角
要创建一个 3D 对象,我需要一个带有透视图的元素(我们称之为“场景”)。 透视是场景的深度,它取决于它包含的对象的大小。
.scene { perspective: 800px; }
如果视角太小,物体可能会变形。 如果太大,则 3D 效果将降低到零。
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen jqgMvL。
此外,场景中的所有对象只有一个视角。 3D 效果取决于视点的位置。
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen oxKzKv。
那么,我们如何计算透视呢? 我发现它取决于旋转轴。 对于 x 轴,高度值乘以 4 即可。 对于 y 轴,它将是宽度值乘以 4。这是我的神奇公式:
const perspective = dimension * 4;
全方位考虑
确定透视后,我开始创建一个 3D 对象。 我选择了一个立方体,因为它简单且可预测。 一个立方体元素被创建为一个相对定位的常规 div,并定义了宽度和高度(例如200px
)。 它通过值为preserve-3d
的transform-style
属性转换为 3D 对象。 它告诉浏览器根据 3D 世界的规则渲染所有嵌套元素。
在我的例子中,立方体有六个绝对定位的 div(或“边”)。 类名对应于边的初始位置( back
, left
, right
, top
, bottom
, front
)。 这是标记:
<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>
默认情况下,所有边都在一个平面上。 所以,我需要重新排列它们。 看起来是这样的:
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen mPNwPx。
这是生成的 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轴旋转立方体,所以我不需要左侧或右侧。 我添加了标题以与剩余边的初始位置对齐。
我开始旋转立方体,发现底部和背面的字幕显示是倒置的:

请参阅 CodePen 上 Anna Selezniova (@askd) 的 Pen GZVvMR。
为了解决这个问题,我将这些边沿 x 轴旋转了 180 度:
.back { transform: translateZ(-100px) rotateX(180deg); } .bottom { transform: translateY(100px) rotateX(270deg); }
超越屏幕
我开始用真实的内容填充两边,然后立即遇到了另一个问题。 我需要显示 1 像素的虚线,但它们很模糊而且看起来很糟糕。
请参阅 CodePen 上 Anna Selezniova (@askd) 的 Pen VjeBPg。
我很快意识到问题出在哪里。 你还记得那个图像超出屏幕的 3D 电视广告吗? 我的立方体就是这样。
如果您可以从左侧或右侧查看立方体,您会看到它的中心位于屏幕平面上(z 轴为零),并且正面超出屏幕。 因此,它在视觉上增加和模糊。
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen WwVEMR。
为了解决这个问题,我沿 z 轴移动了立方体以将正面与屏幕平面对齐:
.cube { transform:translateZ(-100px); }
现在是立方体,几乎准备好了:
请参阅 CodePen 上 Anna Selezniova (@askd) 的 Pen Xdvery。
使用幻数
我想你已经注意到我正在使用幻数100
来沿轴移动边。 值100
正好是我的测试立方体高度的一半。 为什么是一半的高度? 因为那将是内接在立方体一侧的圆的半径(显然是正方形)。
const offset = dimension / 2;
如果我需要旋转一个三棱柱,这个圆将被内接在一个三角形中。 在这种情况下,偏移量的公式如下:
const offset = dimension / (2 * Math.sqrt(3));
吹走立方体
为了完成任务,我不得不在不同的浏览器中测试结果。
我在 Internet Explorer 中看到的图片让我陷入了沮丧。 要了解我在说什么,请在您喜欢的浏览器中查看下面的演示。 我更改了一个属性,导致多维数据集在 Internet Explorer 中显示不正确。 但是,在您阅读下面演示下的段落之前,请不要偷看源代码。
请参阅 CodePen 上 Anna Selezniova (@askd) 的 Pen XKWMwV。
事实上,Internet Explorer 不支持值为preserve-3d
的transform-style
属性。 我通过查看我的可信赖资源 Can I Use(见注 1)了解了这一点。 在上面的演示中,我将preserve-3d
替换为flat
。 你已经知道了吗? 嘿,我告诉过你不要偷看!
我很沮丧,但我不打算放弃。 问题是学习新事物的机会。 此外,我已经接受了挑战。
寻找支点
我一直在寻找一种不使用transform-style: preserve-3d
创建 3D 对象的方法,最终我发现了一个有用的属性: transform-origin
。 它决定了元素变换的中心点。 我在下面创建了一个交互式演示,它将帮助您了解它的工作原理:
在 CodePen 上查看 Anna Selezniova (@askd) 的 Pen rLNmBp。
演示中元素的 3D 旋转非常类似于立方体的正面,不是吗? 这就是我用的。
(顺便问一下,您是否尝试选择复选框backface-visibility: hidden
during the 3D 旋转?此属性用于在 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
属性,我不需要移动它们,只需要围绕轴旋转它们。 就像魔术一样! 让我们看看它的外观:
请参阅 CodePen 上 Anna Selezniova (@askd) 的 Pen zBYwEm。
这是侧面放置的CSS:
.back { transform: perspective(800px) rotateY(180deg); } .top { transform: perspective(800px) rotateX(90deg); } .bottom { transform: perspective(800px) rotateX(-90deg); } .front { transform: perspective(800px); }
在这里您可以看到正在运行的新立方体:
请参阅 CodePen 上 Anna Selezniova (@askd) 的 Pen wWvdXd。
还给凯撒什么是凯撒的
第二个立方体的外观和旋转与第一个相同。 但在这种情况下,您需要单独转换每一面。 这可能不是很容易,特别是如果您想控制中间旋转角度。
此外,如果您在 Chrome 中打开演示,您会看到旋转过程中两侧会闪烁——非常令人沮丧。
最后,我使用transform-style: preserve-3d
的简单测试应用了这两种方法。 第一个立方体是默认的。 第二个多维数据集用于 Internet Explorer 和不支持preserve-3d
浏览器。
使用数学的力量
最后,我必须实现视差效果。 通常,此效果会响应用户的操作,无论是鼠标光标的位置还是滚动条的位置。 在这种情况下,效果取决于旋转角度。
请参阅 CodePen 上 Anna Selezniova (@askd) 的 Pen QENyqm。
那么,我有什么数据? 首先,我有字幕位置的起点和终点,或者简单地说,就是它相对于边中心的上下offset
。 其次,我有立方体的旋转angle
。
我花了几个小时试图开发一个公式。 然后,我恍然大悟。 这就是我想到的:

在正弦和余弦的帮助下,我很容易根据角度计算出每个字幕的偏移量。 以下是我想出的公式:
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;
加起来
作业现已完成,我可以享受结果并与您分享。 亲自看看它是如何工作的。 使用滚动或箭头键来旋转促销块。 另外,请尝试上下拉动右侧的黑色三角形以手动控制旋转角度(不幸的是,此功能在 Internet Explorer 中不起作用)。 看起来很不错,不是吗? 并且性能相当高(大约每秒 60 帧)。
我很高兴参与了这个网站的开发。 我在使用 CSS 3D 方面获得了有用的经验,并发现了许多有趣的属性。 更重要的是,我知道一个人永远不应该放弃; 您很可能会找到完成任务的方法。
我希望你喜欢我的故事,现在准备好迎接新的挑战。