Monday, September 28, 2015

Create hexagons by CSS

Lets take a humble single element link<span>example</span>, which usually appears like so:
<span>example</span>
And let’s turn it into a hexagon:
example
Useful? Possibly. Fun? Let’s find out.
TL;DR? Final code at the bottom of the page.

This'll work by creating three rectangles – one from the main element, the other two from the ::before and ::after pseudo-elements. The two pseudo-element rectangles will be rotated, giving the appearance of a hexagon.
Diagram showing how we're going to construct the hexagon

For starters, we need to tell the span to act like a block element – otherwise none of the padding or margins will work.
a {
 display: block;
}

We now need to discover the dimensions of the rectangle.
Diagram showing what dimension we need to find out
We can work backwards from the final hexagon by first splitting it into six triangles. The six triangles are all equilateral – all angles and lengths are the same.
But this only tells us one of the lengths we need. We still need to find out the width from side to side, not point to point. We’re going to need to split these into two and end up with 12 right angle triangles.

Now we have two of the three measurements needed. These triangles can be used to find out the lengths that we need.

On this right angle triangle – since it's a right angle triangle that’s been created by splitting an equilateral triangle in two – the ratios are:
Diagram showing Pythagoras's theorem

Now we have the ratios of each side of the triangle and now can use this to make the rectangles that will make up the final hexagon. We don’t need to double the dimensions, as all we’re interested in at the moment are the ratios.

The final ratio of the rectangle: 1 to the square root of 3

In an ideal world, this would just work:
span {
 display: block;
 height: 1em;
 width: 3em;
}
But it doesn’t. Ah well. So instead we’re going to use this:
span {
 display: block;
 height: 1em;
 width: 1.73205081em; /* equivalent to √3em */
}
We now have the first building block needed to make the first part of our hexagon. Now, onto the pseudo-elements!
The ::before and ::after pseudo-elements need to be the same dimensions as the parent rectangle. To keep things simple, we’re telling the pseudo-classes to inherit the same characteristics from their parent element – if the dimensions of a change, the dimensions of it’s pseudo-element children will also change.
span::before,
span::after {
 display: inherit;
 height: inherit;
 width: inherit;
}
But ::before and ::after just need a bit more work to get them to actually show up. Content.
span::before,
span::after {
 content: ' ';
}
And now we have a lovely… slightly larger rectangle that hides the main a element. So z-index needs to be used – and for z-index to be actually work, every element needs to be defined as position: relative orposition: absolute.
span {
 position: relative;
 z-index: 100;
}
In addition to the position and z-index, the ::before and ::after pseudo elements will need their left and top characteristics defined. Because the parent element is position: relative, the pseudo-elements are contained within.
span::before,
span::after {
 left: 0;
 position: absolute;
 top: 0;
 z-index: -100;
}
And here’s where it gets strange. Even though the z-index of the pseudo elements is lower than the main element, it still obscures it. But if we add a text into the a element, the text will appear above the pseudo-elements – but the background does not.
Although this appears to disobey the z-index, the z-index of the a works in relation to other elements outside of it – but not the pseudo-elements and the content within it. The pseudo-elements and the content obey each other’s z-index, but ignore that of the parent element. If we twisted the layout so we could see the z-axis, we’d see this:

The z-index of the element, from the element (bottom) to pseudo-elements (middle) to the contents (top)

Now for the rotation. This uses CSS3 transforms, so will currently work in every modern browser apart from Internet Explorer 8. (Can I Use has the latest info on this). Personally, I think this is okay – the fallback for browsers that don’t support rotations is that it will just appear as a rectangle.
The rotation is dead simple: transform: rotate(45deg) will twist the element by 45 degrees clockwise; anticlockwise rotation is done with a negative/minus angle. So, with the hexagon the ::before and ::afterelements need to be rotated by -60 degrees and 60 degrees.
span::before {
 transform: rotate(60deg);
}
span::after {
 transform: rotate(-60deg);
}
The eagle-eyed will have no doubt noticed that the CSS so far will only work in Firefox 16+ and Internet Explorer 10+; unfortunately the remedy is the malaise of CSS – the vendor prefix. The W3C version – the non vendor prefixed version – should to go last so it isn’t overridden.
span::before {
  -moz-transform: rotate(60deg);
  -ms-transform: rotate(60deg);
  -o-transform: rotate(60deg);
  -webkit-transform: rotate(60deg);
 transform: rotate(60deg);
}
span::after {
  -moz-transform: rotate(-60deg);
  -ms-transform: rotate(-60deg);
  -o-transform: rotate(-60deg);
  -webkit-transform: rotate(-60deg);
 transform: rotate(-60deg);
}
And now you have a lovely hexagon, where there once was only a span.
span {
 background: #ffde17;
 color: rgba(255, 255, 255, 0.5);
 display: block;
 font: 100 65px/2em sans-serif;
 height: 2em;
 position: relative;
 width: 3.46410162em;
 z-index: 100;
}
span:before,
span:after {
 background: inherit;
 content: ' ';
 display: inherit;
 height: inherit;
 left: 0;
 line-height: inherit;
 position: absolute;
 top: 0;
 width: inherit;
 z-index: -100;
}
span:before {
 -moz-transform: rotate(60deg);
 -ms-transform: rotate(60deg);
 -o-transform: rotate(60deg);
 -webkit-transform: rotate(60deg);
 transform: rotate(60deg);
}
span:after {
 -moz-transform: rotate(-60deg);
 -ms-transform: rotate(-60deg);
 -o-transform: rotate(-60deg);
 -webkit-transform: rotate(-60deg);
 transform: rotate(-60deg);
}