解决圆形分布中部分重叠元素的最后一个元素问题

Avatar of Ana Tudor
Ana Tudor

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

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

Clockwise circular (cyclic) distribution with twelve partially overlapping square items. Every item's top left corner is underneath the previous item's bottom left corner
顺时针圆形(循环)分布,元素部分重叠。

起初,这看起来并不复杂。 我们从 12 个编号的元素开始

- 12.times do |i|
  .item #{i}

我们为这些元素设置尺寸,将它们绝对定位在容器的中间,为它们设置 backgroundbox-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 同时位于元素 111 的下方

Highlighting the issue we encounter with our circular distribution using the above code. The last item (11), ends up both over one before it (10) and over the one after (0), while the first item (0) is both under the one before it (11) and under the one after it (1).
突出显示我们在圆形分布中遇到的问题。

有很多方法可以解决这个问题,但它们感觉有点笨拙且繁琐,因为它们要么涉及复制元素,要么使用 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 来实现这一点。

The result after ensuring all our items are within the same 3D rendering context: they are all in the correct order, with every item's top left corner underneath the bottom left corner of the previous item.
确保所有元素都在同一个 3D 渲染上下文中的结果(实时演示)。

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

Screenshot illustrating the Firefox issue described above.
屏幕截图说明了 Firefox 问题。

这种情况在 Chrome 或 Edge 中不会发生,这是由于 Firefox 中的一个已知错误导致的,即 3D 变换元素并非始终以正确的 3D 顺序呈现。 幸运的是,此问题现已在 Nightly (55) 中修复。

现在让我们继续讨论高度缩小的问题。 如果我们在最后一次旋转后从侧面查看第一个元素,我们会看到以下内容

Geometric illustration. First item and its projection onto the plane of the screen, side view from the + of the x axis. From this point, we see these as two lines, AB and CD, which intersect in the middle, this intersection being the point O. The angle between them is the angle of rotation of each item around its own x axis, 40° in this case.
第一个元素及其在屏幕平面上的投影,侧视图。

相对于垂直方向旋转 40°AB 线是我们元素的实际 高度h)。 CD 线是此 AB 线在屏幕平面上的投影。 这是我们旋转后感知元素高度的大小。 我们希望它等于 d,也等于我们元素的另一个尺寸(其 宽度)。

我们绘制一个矩形,其左边缘为此投影 (CD),其右上角为 A 点。 由于矩形中的对边相等,因此此矩形的右边缘 AF 等于投影 d。 由于矩形中的对边也平行,因此我们也得到 ∠OAF(或 ∠BAF,相同)角等于 ∠AOC 角(它们是 内错角)。

Geometric illustration. We draw a rectangle whose left edge is the CD projection and whose top right corner is the A point.
创建 CDFA 矩形。

现在让我们删除除直角三角形 AFB 之外的所有内容。 在此三角形中,AB 斜边的长度为 h∠BAF 角为 40°AF 边的长度为 d

Geometric illustration focused on the right triangle AFB
直角三角形 AFB

由此,我们得到 ∠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 顺序问题的浏览器中)

The final result after fixing the height issue: all items are square again and they are all in the correct order, with every item's top left corner underneath the bottom left corner of the previous item.
修复高度问题后的最终结果(实时演示)。

此方法非常方便,因为它不需要我们对任何特定元素做任何不同的事情,并且在半透明元素的情况下效果很好,无需任何其他额外调整。 但是,上面的演示并不太令人兴奋,所以让我们看看一些稍微有趣一点的用例。

请注意,以下演示仅适用于 WebKit 浏览器,但这与本文中介绍的方法无关,这仅仅是当前 calc() 对除长度值之外的任何内容的支持都很差的结果。

第一个是滴答加载器,它是 Geometric Animations tumblr 中的 gif 的纯 CSS 重现。 在这种情况下,动画速度非常快,因此可能有点难以注意到此处的效果。 它仅适用于 WebKit 浏览器,因为 Firefox 和 Edge 不支持 calc() 作为 animation-delay 值,并且 Firefox 也不支持 rgb() 中的 calc()

Animated gif showing a tic toc loader. Eighteen bars are distributed on a circle, all pointing towards the origin. Every two opposing bars animate at the same time, rotating by half a turn around their own central points. Once they are done, the next pair of opposing bars starts animating.
滴答加载器(查看 实时演示,仅限 WebKit)

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

Animated gif showing a sea shell loader. There are two layers, with eighteen bars distributed on identical circles on each layer. All the bars rotate around their own central points at the same time, with those on the layer behind being 90 degrees away from those on the layer in front at all times.
海螺加载器(查看 实时演示,仅限 WebKit)

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

Diagram showing five discs distributed clockwise on a circle, partly overlapping, with each of the discs partly underneath the disc following it.
图表(查看 实时演示,仅限 WebKit)

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

Circular image gallery. Image thumbnails are distributed in a similar fashion to the discs in the previous demo, on a circle around the current image. Clicking on a thumbnail selects that image and moves it in the middle where it grows to its natural size, while the previously selected image shrinks and moves back in place on the circle. All images show pictures of Amur leopards.
圆形图片库(查看 实时演示,仅限 WebKit)

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

Animated gif. 12 discs are distributed on a circle in a similar fashion to the previous demos. The ones on odd positions shift out on another outer layer. The two layers rotate in opposite directions, then the items on the outer layer shift back on the inner layer and then the animation repeats itself.
Disc Buddies 加载动画(查看 实时演示,仅限 WebKit)