创建 3D 立方体图片画廊

Avatar of Kushagra Gour
Kushagra Gour

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

以下是 Kushagra Gour (@chinchang457) 的一篇客座文章。Kushagra 联系我向我展示了他制作的一个有趣的互动演示。它涉及 CSS 中许多 3D 变换的概念,这是我们在这里没有过多介绍的一个主题。因此,以下是 Kushagra 接手通过演示来解释这些概念。

我最近重新设计了 我的网站,并为首页和页眉想出了一个 2 面 3D 立方体的创意。悬停时,它会在我的头像和 Twitter 链接之间旋转。在这样做的过程中,我想,为什么不将这个创意扩展到一个完整的 6 面立方体,它可以用作图片画廊呢?这是我想到的!

查看 Kushagra Gour (@chinchang) 在 CodePen 上的笔 3D 立方体图片画廊

本教程概述了如何创建类似这样的东西,重点介绍 CSS3 3D 概念。

分解立方体

从立方体上可以清楚地看出,立方体的 6 个面将是 6 个不同的 HTML 元素。在这种情况下,是六个 <div> 元素。因为它们需要作为一个立方体旋转,所以它们需要在一个容器元素中。如果我们编写这个基本结构,我们将得到如下内容

<div class="cube">
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
    <div class="cube-face"></div>
 </div>

此外,由于我们需要引用每个面来对其进行样式设置,因此我们应该向其添加相应的类。

<div class="cube">
    <div class="cube-face  cube-face-front"></div>
    <div class="cube-face  cube-face-back"></div>
    <div class="cube-face  cube-face-left"></div>
    <div class="cube-face  cube-face-right"></div>
    <div class="cube-face  cube-face-top"></div>
    <div class="cube-face  cube-face-bottom"></div>
 </div>

设置面的样式

我们还没有看到任何东西。所以让我们给这些面一些尺寸和样式。

$size: 150px; // cube length
.cube {
  width: $size;
  height: $size;
  position: relative;
}
.cube-face {
  width: inherit;
  height: inherit;
  position: absolute;
  background: red;
  opacity: 0.5;
}

查看 Kushagra Gour (@chinchang) 在 CodePen 上的笔 3d 立方体画廊教程 – P1

请注意,每个立方体面都将 position 设置为 absolute,因此它们在一个位置上彼此堆叠。现在我们可以选择每个面并相应地定位它们。

此外,我还为每个面设置了不透明度,以便我们可以透过它们看到发生了什么。

CSS3 3D 概念

让我们了解一些 CSS3 3D 的概念。为了将正面稍微靠近眼睛,我们将其沿 Z 轴平移

.cube-face-front {
  color: blue;
  transform: translate3d(0, 0, 20px);
}

您还没有看到任何区别。让我们了解一下原因。

透视

MDN 上所述

perspective CSS 属性确定 z=0 平面与用户之间的距离,以便为 3D 定位元素提供一些透视。

简单来说,它的值决定了空间中 3D 效果的程度。此属性的值越小,3D 效果就越明显。如果没有此属性,则使用 平行投影 在屏幕上呈现元素,其中投影线彼此平行。因此,无论元素向您靠近或远离多少,它看起来都将具有相同的尺寸,这与现实世界不同。(有关透视的更多信息)。

我们将为立方体的父容器设置此属性,以便其所有子元素(面)都受到共同透视的影响,如下所示

.cube {
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
}

如预期的那样,现在正面确实看起来更大。但仍然缺少一些东西。

transform-style

即使我们为场景提供透视,我们仍然存在问题。理想情况下,正面应该出现在所有其他面的上方,将它们隐藏在其后面。但事实并非如此。

原因是我们的立方体容器没有 3D 渲染上下文,它在 CSSWG 上定义如下

一个或多个级别的包含块层次结构,由具有计算值为“preserve-3d”的“transform-style”属性的元素实例化,其元素共享一个公共的三维坐标系。

如果没有将 transform-style 属性设置为 preserve-3d 的元素,则其子元素将被呈现为扁平化,没有堆叠上下文。因此,即使我们将正面沿 Z 轴靠近,它也会继续将其渲染在其原始 z-index 上,而不会考虑其在 3D 空间中的位置。

尝试为 .cube 元素提供此属性,看看会发生什么。

.cube {
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
  transform-style: preserve-3d;
}

成功了。现在我们已经设置了 3D 系统,让我们将其转换为一个立方体!

定位面

我们将一次处理一个面,并将其放置在适当的位置。首先让我们了解 CSS 中的坐标系。如果我们要取其中一个立方体面,它将是这样的

正面平放在 Z 轴朝向我们的方向

如您所见,Z 轴从元素直接从屏幕中伸出。因此,当我们沿 Z 轴正向平移正面时,它会更靠近我们。需要注意的是,此坐标系是相对于此元素的局部坐标系。让我们更仔细地看看这一点。

我们将再次使用我们的正面并围绕其 Y 轴稍微旋转它。

.cube-face-front {
  transform: rotateY(40deg);
}

现在,这是我们的正面旋转后的样子

轴随面一起旋转。

请注意轴是如何随元素一起旋转的。这意味着 Z 轴不再直接朝向我们。相反,它位于元素的方向上。因此,如果我们现在沿 Z 轴移动它,它将沿元素面向的方向移动。

我们将使用此概念来定位立方体的每个面。假设立方体的中心位于屏幕的 2D 位置。然后需要在 3D 空间中围绕它定位立方体面。请记住,**我们旋转面使其面向所需方向,然后沿 Z 轴平移**。

正面

这里不需要旋转。只需将正面向前移动立方体长度的一半即可。

.cube-face-front {
  transform: translate3d(0, 0, $size/2);
}

背面

背面将与正面相反。这意味着它需要围绕 Y 轴旋转 180 度,然后像这样平移

.cube-face-back {
  transform: rotateY(180deg) translate3d(0, 0, $size/2);
}

完成了 2 个面,还有 4 个要完成。

左面

如果您仍然对变换是如何完成的存疑,让我们通过一些视觉效果来了解这个面。

这就是左面的当前状态,平放在 2D 平面 (z=0) 上

左面:在 2d 平面中

由于左侧需要面向左侧,因此我们将其旋转 90 度

左面:旋转后

与每个面一样,我们将其沿 Z 轴移动

左面:平移后

这是左面的最终 CSS

.cube-face-left {
  transform: rotateY(-90deg) translate3d(0, 0, $size/2);
}

右面

这与左侧面相似,除了正向旋转之外。

.cube-face-right {
  transform: rotateY(90deg) translate3d(0, 0, $size/2);
}

顶面

这个面需要绕 X 轴旋转 90 度,使其朝上。

.cube-face-top {
  transform: rotateX(90deg) translate3d(0, 0, $size/2);
}

底面

类似地,通过给出一个负向旋转,我们定位底面。

.cube-face-bottom {
  transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}

这完成了面的定位,现在我们已经完成了立方体,并具有以下最终 CSS(我还为每个面添加了随机颜色以区分它们)。

$size: 150px; // cube length
.cube {
  margin: 100px;
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
  transform-style: preserve-3d;
}
.cube-face {
  width: inherit;
  height: inherit;
  position: absolute;
  background: red;
  opacity: 0.8;
}
.cube-face-front {
  background: yellow;
  transform: translate3d(0, 0, $size/2);
} 
.cube-face-back {
  background: orange;
  transform: rotateY(180deg) translate3d(0, 0, $size/2);
} 
.cube-face-left {
  background: green;
  transform: rotateY(-90deg) translate3d(0, 0, $size/2);
} 
.cube-face-right {
  background: magenta;
  transform: rotateY(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-top {
  background: blue;
  transform: rotateX(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-bottom {
  background: red;
  transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}

现在要旋转立方体,我们只需在 .cube 元素上应用旋转即可。尝试围绕 Y 轴(垂直)旋转 180 度

.cube {
  margin: 100px;
  width: $size;
  height: $size;
  position: relative;
  
  perspective: 600px;
  transform-style: preserve-3d;
  transform: rotateY(180deg);
}

你应该看到类似的东西。

查看 Kushagra Gour(@chinchang)在 CodePen 上的笔 3d cube gallery tutorial – P2

你有没有注意到什么问题?我们将立方体围绕其垂直轴旋转了 180 度。现在我们应该看到背面而不是正面。我们确实看到了它,但由于某种原因,它显示得更小。我们做错了什么?

修复透视

如果你还记得,我们为立方体容器(.cube)提供了 perspective 属性。当我们刚刚旋转该元素时,消失点标记也随之旋转。因此,最初位于 2D 平面后面的消失点在旋转后来到了 2D 平面的前面,导致了这个问题。

我们理想中希望的是,无论我们转换哪个元素,透视都不会改变,保持不变。

我们如何解决这个问题?我们用另一个 DIV 包裹所有元素,并为其提供 perspective 属性。

<div class="scene">
  <div class="cube">
    <div class="cube-face  cube-face-front"></div>
    <div class="cube-face  cube-face-back"></div>
    <div class="cube-face  cube-face-left"></div>
    <div class="cube-face  cube-face-right"></div>
    <div class="cube-face  cube-face-top"></div>
    <div class="cube-face  cube-face-bottom"></div>
  </div>
</div>
.scene {
  margin: 100px;
  width: $size;
  height: $size;
  
  perspective: 600px;
}
.cube {
  position: relative;
  width: inherit;
  height: inherit;
  
  transform-style: preserve-3d;
  transform: rotateY(180deg);
}

现在检查结果,一切应该按预期出现。

尝试给出不同的旋转,例如 transform: rotateX(30deg) rotateY(30deg) 来稍微玩一下。完成后,删除 transform 属性。

添加交互性

我们现在添加一些控件来浏览画廊。为此,我们将使用一个名为 **复选框技巧** 的巧妙技巧。虽然我们将使用单选按钮(因为一次只能选择一个)而不是复选框,但概念保持不变。你可以在 Chris Coyier 的文章中 阅读更多关于复选框技巧的内容

不深入细节,我们向 HTML 中添加了单选按钮。

<!-- CONTROLS -->      
<input type="radio" checked id="radio-front" name="select-face"/>    
<input type="radio" id="radio-back" name="select-face"/>
<input type="radio" id="radio-left" name="select-face"/>
<input type="radio" id="radio-right" name="select-face"/>
<input type="radio" id="radio-top" name="select-face"/>
<input type="radio" id="radio-bottom" name="select-face"/>
<div class="scene">
  <div class="cube">
    <div class="cube-face  cube-face-front"></div>
    <div class="cube-face  cube-face-back"></div>
    <div class="cube-face  cube-face-left"></div>
    <div class="cube-face  cube-face-right"></div>
    <div class="cube-face  cube-face-top"></div>
    <div class="cube-face  cube-face-bottom"></div>
  </div>
</div>

以及以下 CSS 来将立方体的旋转与单选按钮绑定。

#radio-back:checked ~ .scene .cube {
  transform: rotateY(180deg);
} 
#radio-left:checked ~ .scene .cube {
  transform: rotateY(90deg);
} 
#radio-right:checked ~ .scene .cube {
  transform: rotateY(-90deg);
}
#radio-top:checked ~ .scene .cube {
  transform: rotateX(-90deg);
}  
#radio-bottom:checked ~ .scene .cube {
  transform: rotateX(90deg);
}

在上面的 CSS 中,我们简单地说明了何时选中每个单选按钮,此时立方体的旋转应该是什么。

最终代码

为了使其更美观,我们为立方体添加了一些过渡效果和适当的对齐方式,以获得以下代码。

<!-- CONTROLS -->
<input type="radio" checked id="radio-front" name="select-face"/>    
<input type="radio" id="radio-left" name="select-face"/>
<input type="radio" id="radio-right" name="select-face"/>
<input type="radio" id="radio-top" name="select-face"/>
<input type="radio" id="radio-bottom" name="select-face"/>
<input type="radio" id="radio-back" name="select-face"/>

<div></div><!-- separator -->

<div class="scene">
  <div class="cube">
      <div class="cube-face  cube-face-front"></div>
      <div class="cube-face  cube-face-back"></div>
      <div class="cube-face  cube-face-left"></div>
      <div class="cube-face  cube-face-right"></div>
      <div class="cube-face  cube-face-top"></div>
      <div class="cube-face  cube-face-bottom"></div>
   </div>
</div>
$size: 150px; // cube length
body {
  text-align: center;
  padding: 50px;
} 
.scene {
  display: inline-block;
  margin-top: 50px;
  width: $size;
  height: $size;
  
  perspective: 600px;
}
.cube {
  position: relative;
  width: inherit;
  height: inherit;
  
  transform-style: preserve-3d;
  transition: transform 0.6s;
}
.cube-face {
  width: inherit;
  height: inherit;
  position: absolute;
  background: red;
  opacity: 0.8;
}
// faces
.cube-face-front {
  background: yellow;
  transform: translate3d(0, 0, $size/2);
}  
.cube-face-back {
  background: black;
  transform: rotateY(180deg) translate3d(0, 0, $size/2);
} 
.cube-face-left {
  background: green;
  transform: rotateY(-90deg) translate3d(0, 0, $size/2);
} 
.cube-face-right {
  background: magenta;
  transform: rotateY(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-top {
  background: blue;
  transform: rotateX(90deg) translate3d(0, 0, $size/2);
} 
.cube-face-bottom {
  background: red;
  transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}  
// controls 
#radio-back:checked ~ .scene .cube {
  transform: rotateY(180deg); 
} 
#radio-left:checked ~ .scene .cube {
  transform: rotateY(90deg); 
} 
#radio-right:checked ~ .scene .cube {
  transform: rotateY(-90deg); 
}
#radio-top:checked ~ .scene .cube {
  transform: rotateX(-90deg); 
}  
#radio-bottom:checked ~ .scene .cube {
  transform: rotateX(90deg); 
}

向所有面添加一些 background-image,我们得到了最终结果。

查看 Kushagra Gour (@chinchang) 在 CodePen 上的笔 3D 立方体图片画廊

希望你喜欢这次 3D 之旅,并利用它创造一些真正令人惊叹的东西!