Solving the Last Item Problem for a Circular Distribution with Partially Overlapping Items

Let's say we wanted to have something like this:
Clockwise circular (cyclic) distribution with partially overlapping items.
At first, this doesn't seem too complicated. We start with 12 numbered items:

- 12.times do |i|
.item #{i}
We give these items dimensions, position them absolutely in the middle of their container, give them a background, a box-shadow (or a border) and tweak the text-related properties a bit so that everything looks nice.
$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;
}
So far, so good:
See the Pen by thebabydino (@thebabydino) on CodePen.
Now all that's left is to distribute them on a circle, right? We get a base angle $ba for our distribution, we rotate each item by its index times this $ba angle and then translate it along its x axis:
$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 } }
}
The result seems fine at first:
See the Pen by thebabydino (@thebabydino) on CodePen.
However, on closer inspection, we notice that we have a problem: item 11 is above both item 0 and item 10, while item 0 is below both item 1 and 11:
Highlighting the issue we encounter with our circular distribution.
There are a number of ways to get around this, but they feel kind of hacky and tedious because they involve either duplicating elements, cutting corners with clip-path, adding pseudo-elements to cover the corners or cut them out via overflow. Some of these are particularly inefficient if we also need to animate the position of the items or if we want the items to be semi transparent.
So, what's the best solution then?
3D to the rescue! A really neat thing we can do in this case is to rotate these items in 3D such that their top part goes towards the back (behind the plane of the screen) and their bottom part comes forward (in front of the plane of the screen). We do this by chaining a third transform function - a rotateX():
transform: rotate(var(--a, 0deg)) translate(1.5*$d) rotateX(40deg)
At this point, nothing seems to have changed for the better - we still have the same problem as before and, in addition to that, our items appear to have shrunk along their y axes, which isn't something we wanted.
See the Pen by thebabydino (@thebabydino) on CodePen.
Let's tackle these issues one by one. First off, we need to make all our items belong to the same 3D rendering context and we do this by setting transform-style: preserve-3d on their parent (which in this case happens to be the body element).
The result after ensuring all our items are within the same 3D rendering context (live demo).
Those on current Firefox may have noticed we have a different kind of issue now. Item 8 appears both above the previous one (7) and above the next one (9), while item 7 appears both below the previous one (6) and below the next one (8).
Screenshot illustrating the Firefox issue.
This doesn't happen in Chrome or in Edge and it's due to a known Firefox bug where 3D transformed elements are not always rendered in the correct 3D order. Fortunately, this is now fixed in Nightly (55).
Now let's move on to the issue of the shrinking height. If we look at the first item from the side after the last rotation, this is what we see:
First item and its projection onto the plane of the screen, side view.
The AB line, rotated at 40° away from the vertical is the actual height of our item (h). The CD line is the projection of this AB line onto the plane of the screen. This is the size we perceive our item's height to be after the rotation. We want this to be equal to d, which is also equal to the other dimension of our item (its width).
We draw a rectangle whose left edge is this projection (CD) and whose top right corner is the A point. Since the opposing edges in a rectangle are equal, the right edge AF of this rectangle equals the projection d. Since the opposing edges of a rectangle are also parallel, we also get that the ∠OAF (or ∠BAF, same thing) angle equals the ∠AOC angle (they're alternate angles).
Creating the CDFA rectangle.
Now let's remove everything but the right triangle AFB. In this triangle, the AB hypotenuse has a length of h, the ∠BAF angle is a 40° one and the AF cathetus is d.
The right triangle AFB
From here, we have that the cosine of the ∠BAF angle is d/h:
cos(40°) = d/h → h = d/cos(40°)
So the first thing that comes to mind is that, if we want the projection of our items to look as tall as it is wide, we need to give it a height of $d/cos(40deg). However, this doesn't fix the squished text or any squished backgrounds, so it's a better idea to leave it with its initial height: $d and to chain another transform - a scaleY() using a factor of 1/cos(40deg). Even better, we can store the rotation angle into a variable $ax and then we have:
$d: 2em;
$ax: 40deg;

.item {
transform: rotate(var(--a, 0deg)) translate(1.5*$d) rotateX($ax) scaleY(1/cos($ax));
}
The above changes give us the desired result (well, in browsers that support CSS variables and don't have 3D order issues):
The final result after fixing the height issue (live demo).
This method is really convenient because it doesn't require us to do anything different for any one item in particular and it works nicely, without any other extra tweaks needed, in the case of semitransparent items. However, the above demo isn't too exciting, so let's take a look at a few slightly more interesting use cases.
Note that the following demos only work in WebKit browsers, but this is not something related to the method presented in the article, it's just a result of the currently poor support of calc() for anything other than length values.
The first is a tic toc loader, which is a pure CSS recreation of a gif from the Geometric Animations tumblr. The animation is pretty fast in this case, so it may be a bit hard hard to notice the effect here. It only works in WebKit browsers as Firefox and Edge don't support calc() as an animation-delay value and Firefox doesn't support calc() in rgb() either.
Tic toc loader (see the live demo, WebKit only)
The second is a sea shell loader, also a pure CSS recreation of a gif from the same Tumblr and also WebKit only for the same reasons as the previous one.
Sea shell loader (see the live demo, WebKit only)
The third demo is a diagram. It only works in WebKit browsers because Firefox and Edge don't support calc() values inside rotate() functions and Firefox doesn't support calc() inside hsl() either:
Diagram (see the live demo, WebKit only)
The fourth is a circular image gallery, WebKit only for the same reason as the diagram above.
Circular image gallery (see the live demo, WebKit only)
The fifth and last is another loading animation, this time inspired by the Disc Buddies .gif by Dave Whyte.
Disc Buddies loading animation (see the live demo, WebKit only)

Solving the Last Item Problem for a Circular Distribution with Partially Overlapping Items is a post from CSS-Tricks
Source: CssTricks


We Don't Serve Your Type Here: A History of Fonts on the Web - Part 1

Back in 1994, websites looked a lot different. Those of you who spent time on the web during that year probably have a good idea of what I'm talking about. If you didn't spend time on the web that year, or if you weren’t even born yet, allow me to paint you a brief picture:Read more