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

|Let’s say we wanted to have something like this:

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`

:

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).

Those on 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`

).

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.

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:

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).

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`

.

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):

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.

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.

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:

The fourth is a circular image gallery, WebKit only for the same reason as the diagram above.

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