Das Problem mit dem letzten Element für eine Kreisverteilung mit teilweise überlappenden Elementen lösen

Avatar of Ana Tudor
Ana Tudor am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Nehmen wir an, wir wollten so etwas haben

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
Uhrzeigersinn kreisförmige (zyklische) Verteilung mit teilweise überlappenden Elementen.

Zuerst scheint das nicht allzu kompliziert. Wir beginnen mit `12` nummerierten Elementen.

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

Wir geben diesen Elementen Abmessungen, positionieren sie absolut in der Mitte ihres Containers, geben ihnen einen `background`, einen `box-shadow` (oder einen `border`) und passen die textbezogenen Eigenschaften ein wenig an, damit alles gut aussieht.

$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;
}

Bisher alles gut

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Nun müssen wir sie nur noch auf einem Kreis verteilen, oder? Wir erhalten einen Basiswinkel `$ba` für unsere Verteilung, drehen jedes Element um seinen Index mal diesen `$ba`-Winkel und verschieben es dann entlang seiner `x`-Achse.

$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 } }
}

Das Ergebnis scheint zunächst in Ordnung zu sein.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Bei näherer Betrachtung stellen wir jedoch fest, dass wir ein Problem haben: Element `11` liegt sowohl über Element `0` als auch über Element `10`, während Element `0` unter Element `1` und `11` liegt.

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).
Hervorhebung des Problems, auf das wir bei unserer Kreisverteilung stoßen.

Es gibt eine Reihe von Möglichkeiten, dies zu umgehen, aber sie fühlen sich eher wie Hacks an und sind mühsam, da sie entweder das Duplizieren von Elementen, das Umgehen von Einschränkungen mit `clip-path`, das Hinzufügen von Pseudo-Elementen zum Abdecken von Ecken oder deren Ausblenden über `overflow` beinhalten. Einige davon sind besonders ineffizient, wenn wir auch die Position der Elemente animieren oder die Elemente halbtransparent machen wollen.

Was ist also die beste Lösung?

3D zur Rettung! Eine wirklich nette Sache, die wir in diesem Fall tun können, ist, diese Elemente in 3D zu drehen, sodass ihr oberer Teil nach hinten (hinter die Bildschirmebene) und ihr unterer Teil nach vorne (vor die Bildschirmebene) geht. Dies tun wir, indem wir eine dritte `transform`-Funktion – ein `rotateX()` – verketteln.

transform: rotate(var(--a, 0deg)) translate(1.5*$d) rotateX(40deg)

Zu diesem Zeitpunkt scheint sich nichts zum Besseren verändert zu haben – wir haben immer noch dasselbe Problem wie zuvor und zusätzlich scheinen unsere Elemente entlang ihrer `y`-Achsen geschrumpft zu sein, was nicht das war, was wir wollten.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Lassen Sie uns diese Probleme nacheinander angehen. Zuerst müssen wir dafür sorgen, dass alle unsere Elemente zum selben 3D-Rendering-Kontext gehören, und das tun wir, indem wir `transform-style: preserve-3d` auf ihr Elternelement (in diesem Fall das `body`-Element) anwenden.

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.
Das Ergebnis, nachdem sichergestellt wurde, dass alle unsere Elemente innerhalb desselben 3D-Rendering-Kontexts liegen (Live-Demo).

Wer aktuelle Firefox-Versionen nutzt, hat vielleicht ein anderes Problem bemerkt. Element `8` erscheint sowohl über dem vorherigen (7) als auch über dem nächsten (9), während Element `7 sowohl unter dem vorherigen (6) als auch unter dem nächsten (8) erscheint.

Screenshot illustrating the Firefox issue described above.
Screenshot, der das Firefox-Problem illustriert.

Das passiert in Chrome oder Edge nicht und liegt an einem bekannten Firefox-Bug, bei dem 3D-transformierte Elemente nicht immer in der richtigen 3D-Reihenfolge gerendert werden. Glücklicherweise ist dies jetzt in der Nightly-Version (55) behoben.

Kommen wir nun zum Problem der schrumpfenden Höhe. Wenn wir das erste Element nach der letzten Drehung von der Seite betrachten, sehen wir Folgendes:

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.
Erstes Element und seine Projektion auf die Bildschirmebene, Seitenansicht.

Die Linie `AB`, die um `40°` von der Vertikalen abweicht, ist die tatsächliche `height` unseres Elements (h). Die Linie `CD` ist die Projektion dieser Linie `AB` auf die Bildschirmebene. Das ist die Größe, die wir als Höhe unseres Elements nach der Drehung *wahrnehmen*. Wir wollen, dass diese gleich `d` ist, was auch gleich der anderen Dimension unseres Elements (seiner `width`) ist.

Wir zeichnen ein Rechteck, dessen linke Kante diese Projektion (`CD`) ist und dessen rechte obere Ecke der Punkt `A` ist. Da gegenüberliegende Kanten in einem Rechteck gleich sind, ist die rechte Kante `AF` dieses Rechtecks gleich der Projektion `d`. Da die gegenüberliegenden Kanten eines Rechtecks auch parallel sind, erhalten wir auch, dass der Winkel `∠OAF` (oder `∠BAF`, dasselbe) dem Winkel `∠AOC` entspricht (es sind wechselseitige Innenwinkel).

Geometric illustration. We draw a rectangle whose left edge is the CD projection and whose top right corner is the A point.
Erstellung des Rechtecks `CDFA`.

Entfernen wir nun alles außer dem rechtwinkligen Dreieck `AFB`. In diesem Dreieck hat die Hypotenuse `AB` eine Länge von `h`, der Winkel `∠BAF` ist ein `40°`-Winkel und die Kathete `AF` ist `d`.

Geometric illustration focused on the right triangle AFB
Das rechtwinklige Dreieck `AFB`

Von hier aus haben wir, dass der Kosinus des Winkels `∠BAF` gleich `d/h` ist.

cos(40°) = d/h → h = d/cos(40°)

Das Erste, was uns in den Sinn kommt, ist, dass wir, wenn wir wollen, dass die Projektion unserer Elemente so hoch aussieht, wie sie breit ist, ihr eine Höhe von `$d/cos(40deg)` geben müssen. Dies behebt jedoch nicht den gequetschten Text oder gequetschte Hintergründe, daher ist es besser, sie bei ihrer ursprünglichen `height: $d` zu belassen und eine weitere `transform`-Funktion zu verketteln – ein `scaleY()` mit einem Faktor von `1/cos(40deg)`. Noch besser, wir können den Rotationswinkel in einer Variablen `$ax` speichern und dann haben wir:

$d: 2em;
$ax: 40deg;

.item {
  transform: rotate(var(--a, 0deg)) translate(1.5*$d) rotateX($ax) scaleY(1/cos($ax));
}

Die obigen Änderungen ergeben das gewünschte Ergebnis (nun ja, in Browsern, die CSS-Variablen unterstützen und keine Probleme mit der 3D-Reihenfolge haben).

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.
Das Endergebnis nach Behebung des Höhenproblems (Live-Demo).

Diese Methode ist sehr praktisch, da sie nicht erfordert, dass wir für ein einzelnes Element etwas anderes tun, und sie funktioniert gut, ohne weitere Anpassungen, bei halbtransparenten Elementen. Die obige Demo ist jedoch nicht besonders spannend, schauen wir uns also ein paar etwas interessantere Anwendungsfälle an.

Beachten Sie, dass die folgenden Demos nur in WebKit-Browsern funktionieren, dies hat jedoch nichts mit der im Artikel vorgestellten Methode zu tun, sondern ist lediglich das Ergebnis der derzeit schlechten Unterstützung von `calc()` für andere Werte als Längenangaben.

Das erste ist ein Tic-Toc-Loader, eine reine CSS-Nachbildung von einem GIF von Geometric Animations Tumblr. Die Animation ist in diesem Fall ziemlich schnell, daher ist es möglicherweise schwer, den Effekt hier zu bemerken. Sie funktioniert nur in WebKit-Browsern, da Firefox und Edge `calc()` nicht als `animation-delay`-Wert unterstützen und Firefox `calc()` auch nicht in `rgb()` unterstützt.

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.
Tic Toc Loader (siehe die Live-Demo, nur WebKit)

Der zweite ist ein Seemuschel-Loader, ebenfalls eine reine CSS-Nachbildung von einem GIF vom selben Tumblr und aus denselben Gründen wie der vorherige nur für 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.
Seemuschel-Loader (siehe die Live-Demo, nur WebKit)

Die dritte Demo ist ein Diagramm. Sie funktioniert nur in WebKit-Browsern, da Firefox und Edge `calc()`-Werte innerhalb von `rotate()`-Funktionen nicht unterstützen und Firefox `calc()` auch nicht innerhalb von `hsl()` unterstützt.

Diagram showing five discs distributed clockwise on a circle, partly overlapping, with each of the discs partly underneath the disc following it.
Diagramm (siehe die Live-Demo, nur WebKit)

Die vierte ist eine kreisförmige Bildergalerie, nur WebKit aus demselben Grund wie das obige Diagramm.

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.
Kreisförmige Bildergalerie (siehe die Live-Demo, nur WebKit)

Die fünfte und letzte ist eine weitere Ladeanimation, diesmal inspiriert von den Disc Buddies.gif von Dave Whyte.

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 Ladeanimation (siehe die Live-Demo, nur WebKit)