假设我们想要实现这样的效果

起初,这看起来并不复杂。 我们从 12
个编号的元素开始
- 12.times do |i|
.item #{i}
我们为这些元素设置尺寸,将它们绝对定位在容器的中间,为它们设置 background
、box-shadow
(或 border
),并稍微调整文本相关的属性,使一切看起来都很漂亮。
$d: 2em;
.item {
position: absolute;
margin: calc(50vh - #{.5*$d}) 0 0 calc(50vw - #{.5*$d});
width: $d; height: $d;
box-shadow: inset 0 0 0 4px;
background: gainsboro;
font: 900 2em/ #{$d} trebuchet ms, tahoma, verdana, sans-serif;
text-align: center;
}
到目前为止,一切顺利
查看 thebabydino 在 CodePen 上创建的 Pen(@thebabydino)。
现在剩下的就是将它们分布在圆周上,对吧? 我们获得一个用于分布的基础角度 $ba
,我们通过其索引乘以该 $ba
角度来旋转每个元素,然后沿其 x
轴平移它
$n: 12;
$ba: 360deg/$n;
.item {
transform: rotate(var(--a, 0deg)) translate(1.5*$d);
@for $i from 1 to $n { &:nth-child(#{$i + 1}) { --a: $i*$ba } }
}
结果起初看起来不错
查看 thebabydino 在 CodePen 上创建的 Pen(@thebabydino)。
但是,仔细观察后,我们发现存在一个问题:元素 11
同时位于元素 0
和元素 10
的上方,而元素 0
同时位于元素 1
和 11
的下方

有很多方法可以解决这个问题,但它们感觉有点笨拙且繁琐,因为它们要么涉及复制元素,要么使用 clip-path
偷工减料,要么添加伪元素来覆盖角或通过 overflow
切割它们。 如果我们还需要动画化元素的位置或希望元素半透明,其中一些方法效率特别低下。
那么,最好的解决方案是什么呢?
3D 来救援! 在这种情况下,我们可以做一件非常巧妙的事情,就是将这些元素在 3D 空间中旋转,使其顶部朝后(屏幕平面后面),底部朝前(屏幕平面前面)。 我们通过链接第三个 transform
函数——rotateX()
来实现这一点。
transform: rotate(var(--a, 0deg)) translate(1.5*$d) rotateX(40deg)
此时,似乎没有任何改善——我们仍然存在与之前相同的问题,此外,我们的元素沿其 y
轴似乎缩小了,这不是我们想要的。
查看 thebabydino 在 CodePen 上创建的 Pen(@thebabydino)。
让我们逐一解决这些问题。 首先,我们需要使所有元素都属于同一个 3D 渲染上下文,我们通过在其父元素(在本例中为 body
元素)上设置 transform-style: preserve-3d
来实现这一点。

使用当前 Firefox 的用户可能已经注意到,我们现在遇到了另一种问题。 元素 8
同时出现在前一个元素(7
)和下一个元素(9
)的上方,而元素 7
同时出现在前一个元素(6
)和下一个元素(8
)的下方。

这种情况在 Chrome 或 Edge 中不会发生,这是由于 Firefox 中的一个已知错误导致的,即 3D 变换元素并非始终以正确的 3D 顺序呈现。 幸运的是,此问题现已在 Nightly (55) 中修复。
现在让我们继续讨论高度缩小的问题。 如果我们在最后一次旋转后从侧面查看第一个元素,我们会看到以下内容
相对于垂直方向旋转 40°
的 AB 线是我们元素的实际 高度
(h
)。 CD 线是此 AB 线在屏幕平面上的投影。 这是我们旋转后感知元素高度的大小。 我们希望它等于 d
,也等于我们元素的另一个尺寸(其 宽度
)。
我们绘制一个矩形,其左边缘为此投影 (CD),其右上角为 A 点。 由于矩形中的对边相等,因此此矩形的右边缘 AF 等于投影 d
。 由于矩形中的对边也平行,因此我们也得到 ∠OAF(或 ∠BAF,相同)角等于 ∠AOC 角(它们是 内错角)。
现在让我们删除除直角三角形 AFB 之外的所有内容。 在此三角形中,AB 斜边的长度为 h
,∠BAF 角为 40°
,AF 边的长度为 d
。
由此,我们得到 ∠BAF 角的余弦为 d/h
cos(40°) = d/h → h = d/cos(40°)
因此,首先想到的是,如果我们希望元素的投影看起来与宽度一样高,我们需要将其高度设置为 $d/cos(40deg)
。 但是,这不会修复压缩的文本或任何压缩的背景,因此最好将其保留为初始 height: $d
,并链接另一个 transform
——scaleY()
,使用 1/cos(40deg)
的因子。 更好的是,我们可以将旋转角度存储到变量 $ax
中,然后我们有
$d: 2em;
$ax: 40deg;
.item {
transform: rotate(var(--a, 0deg)) translate(1.5*$d) rotateX($ax) scaleY(1/cos($ax));
}
上述更改使我们得到了期望的结果(好吧,在支持 CSS 变量且没有 3D 顺序问题的浏览器中)

此方法非常方便,因为它不需要我们对任何特定元素做任何不同的事情,并且在半透明元素的情况下效果很好,无需任何其他额外调整。 但是,上面的演示并不太令人兴奋,所以让我们看看一些稍微有趣一点的用例。
请注意,以下演示仅适用于 WebKit 浏览器,但这与本文中介绍的方法无关,这仅仅是当前 calc()
对除长度值之外的任何内容的支持都很差的结果。
第一个是滴答加载器,它是 Geometric Animations tumblr 中的 gif 的纯 CSS 重现。 在这种情况下,动画速度非常快,因此可能有点难以注意到此处的效果。 它仅适用于 WebKit 浏览器,因为 Firefox 和 Edge 不支持 calc()
作为 animation-delay
值,并且 Firefox 也不支持 rgb()
中的 calc()
。

第二个是海螺加载器,也是来自同一 Tumblr 的 gif 的纯 CSS 重现,并且由于与上一个相同的原因,也仅限 WebKit。

第三个演示是一个图表。 它仅适用于 WebKit 浏览器,因为 Firefox 和 Edge 不支持 rotate()
函数内的 calc()
值,并且 Firefox 也不支持 hsl()
内的 calc()
。

第四个是圆形图片库,由于与上述图表相同的原因,也仅限 WebKit。

第五个也是最后一个是另一个加载动画,这次的灵感来自 Dave Whyte 的 Disc Buddies .gif。

为什么不通过旋转非常小的角度(我尝试了 0.1deg,它在 Firefox 中有效)来解决高度缩小的问题,而不是旋转 40deg 呢?
因为如果遇到需要较大旋转角度的情况,并且因此导致高度差异更加明显,我想以逻辑的方式(而不是通过反复试验/近似值)展示如何解决此问题。
我在 Firefox 53.0.3 (64 位) Mac 版本中没有遇到顺序错误。 完美!
这真的很奇怪,在 Windows 上它可以正常工作。
但它在我的 Mac 上的所有浏览器上仍然无法正常工作。
一年前做过这个,但我不得不作弊 :)
http://codepen.io/wintercounter/pen/LZRBLj
逻辑很棒。
但我有一个困惑,
为什么旋转和平移关键字以不同的顺序排列,它们会产生不同的效果?
像这个笔
最终结果无法正常工作。
http://codepen.io/thebabydino/pen/3d16c309036a90ff2bbb96fbb2be1081/
我尝试了 Opera、Firefox、Safari 和 Chrome,第 0 个元素仍然在第 1 个和第 11 个元素下方
通常,变换函数不是可交换的,因为通常矩阵乘法不是可交换的。
如果您将统一(各向同性)缩放变换与旋转链接,那么是的,无论顺序如何,您都会得到相同的结果,但这只是一个特殊情况。
在旋转后跟随平移的情况下,您首先执行旋转。 如果旋转值为
90deg
,则元素的x
轴在旋转前指向右侧,旋转后指向下方。 如果您想在此旋转之后沿其x
轴正方向平移元素,则表示您正在将其向下平移。相反,如果您希望在任何旋转之前沿其
x
轴平移元素,则将其向右平移。最终结果在我的 Chrome/Opera、Edge 和 Firefox 55+ 中都能正常工作。OSX/iOS 上可能存在一些问题,但我不知道原因……
Firefox (50.0.2) 中的最终演示仍然显示方块 11 在方块 0 的上方……
http://codepen.io/thebabydino/pen/LyQBrR
对的。也许是我的 Mac 出现了问题。
它在 Windows 上可以工作。Mac 上的所有浏览器都无法工作。
我截了个屏
https://singlexyz.github.io/screenshot.png
在 Firefox 53.0.3 中看起来不错。
我已经升级到最新的 Firefox 53,但问题仍然存在,但这次是 6 在 7 和 8 的下方。
在 Firefox 53 中,我看到的是 7 在 6 和 8 的下方,就像文章中的屏幕截图所示。但是,此问题在 Firefox 55+ 中已修复。
很棒的文章,学到了很多!谢谢 Ana