Generate repeating hexagonal pattern with CSS3
Asked Answered
A

7

79

So, I need to make a repeating hexagonal pattern, using CSS. If images are needed, I can go there, but I'd prefer to just use CSS if possible.

Here's an idea of what I'm trying to create:

enter image description here

Basically, I just need a way to create the hexagonal shapes, and then overlay text/images on top of them. I don't have much code yet, because I'm not really sure where to start. The problem is, I could just use <div>s in the shape of a hexagon like shown in (http://css-tricks.com/examples/ShapesOfCSS/), but then they wouldn't be connecting. I could use a repeating hexagon pattern, but then I wouldn't be able to specify the exact location of the text or images I need in specific shapes. Thanks for any help in advance.

Analysis answered 8/4, 2012 at 12:45 Comment(3)
There're CSS masks, but the support is terrible : webkit.org/blog/181/css-masksXylon
For a the same hexagon pattern using the <img> tag instead of a background image, you can check Grid of hexagons with <img> tag.Penalty
I made a sass version of ScottS's solution to have different sizes fast. Check it here codepen.io/mort3za/pen/wBabaBLam
A
130

(Though Ana's answer came in months after mine, probably using mine as a base to "think from", the fact that she was able to come up with a method using a single div is worth promoting, so check out her answer too--but note that content in the hex is more limited.)

This was a truly amazing question. Thank you for asking it. The great thing is the fact that:

This Fiddle Proves You Can Do It!

Original Fiddle Used (modified in later edit to fiddle link above)--it utilized imgur.com images, which were not seeming to be very reliable in loading, so the new fiddle is using photobucket.com (let me know if there are persistent image loading issues). I've kept the original link because the explanation code below goes with that (there are a few differences in background-size or position to the new fiddle).

The idea came to me almost instantly after reading your question, but took some time to implement. I originally tried getting a single "hex" with a single div and just pseudo elements, but as best I could tell, there was no way to just rotate the background-image (which I needed), so I had to add some extra div elements to get the right/left sides of the hex, so that I could then use the pseudo elements as a means of background-image rotation.

I tested in IE9, FF, and Chrome. Theoretically any browser supporting CSS3 transform it should work in.

First Main Update (added explanation)

I have some time now to post some code explanation, so here goes:

First, hexagons are defined by 30/60 degree relationships and trigonometry, so those will be the key angles involved. Second, we start with a "row" for the hex grid to reside in. The HTML is defined as (the extra div elements help build the hex):

<div class="hexrow">
    <div>
        <span>First Hex Text</span>
        <div></div>
        <div></div>
    </div>
    <div>
        <span>Second Hex Text</span>
        <div></div>
        <div></div>
    </div>
    <div>
        <span>Third Hex Text</span>
        <div></div>
        <div></div>
    </div>
</div>

We are going to use inline-block for the hexagon display, but we don't want them to accidentally wrap to the next line and ruin the grid, so white-space: nowrap solves that issue. The margin on this row is going to depend on how much space you want between hex's, and some experimentation may be needed to get what you want.

.hexrow {
    white-space: nowrap;
    /*right/left margin set at (( width of child div x sin(30) ) / 2) 
    makes a fairly tight fit; 
    a 3px bottom seems to match*/
    margin: 0 25px 3px;
}

Using the immediate children of the .hexrow which are just div elements, we form the basis for the hex shape. The width will drive the horizontal of the top of the hex, the height is derived from that number since all the sides are equal length on a regular hexagon. Again, margin is going to depend on spacing, but this is where the "overlap" of the individual hexagons is going to occur to make the grid look occur. The background-image is defined once, right here. The shift left on it is to accommodate at least the added width for the left side of the hex. Assuming you want centered text, the text-align handles the horizontal (of course) but the line-height that matches the height is going to allow for a vertical centering.

.hexrow > div {
    width: 100px;
    height: 173.2px; /* ( width x cos(30) ) x 2 */
    /* For margin:
    right/left = ( width x sin(30) ) makes no overlap
    right/left = (( width x sin(30) ) / 2) leaves a narrow separation
    */
    margin: 0 25px;
    position: relative;
    background-image: url(http://i.imgur.com/w5tV4.jpg);
    background-position: -50px 0; /* -left position -1 x width x sin(30) */
    background-repeat: no-repeat;
    color: #ffffff;
    text-align: center;
    line-height: 173.2px; /*equals height*/
    display: inline-block;
}

Each odd number hex we are going to shift down in relation to the "row" and each even shift up. The shift calculation ( width x cos(30) / 2 ) is also the same as (height / 4).

.hexrow > div:nth-child(odd) {
    top: 43.3px; /* ( width x cos(30) / 2 ) */
}

.hexrow > div:nth-child(even) {
    top: -44.8px; /* -1 x( ( width x cos(30) / 2) + (hexrow bottom margin / 2)) */
}

We are using 2 child div elements to create the "wings" of the hex. They are sized the same as the main hex rectangle, and then rotated, and pushed "below" the main hex. Background-image is inherited so that the image is the same (of course), because the image in the "wings" is going to be "lined up" to that in the main rectangle. The pseudo elements are used to generate the images, because they need to be "rerotated" back to horizontal (since we rotated the parent div of them to create the "wings").

The :before of the first will translate its background the width of the negative amount equal to the main portion of the hex plus the original background shift of the main hex. The :before of the second will change the origin point of the translation and will shift the main width on the x-axis, and half the height on the y-axis.

.hexrow > div > div:first-of-type {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: -1;
    overflow: hidden;
    background-image: inherit;

    -ms-transform:rotate(60deg); /* IE 9 */
    -moz-transform:rotate(60deg); /* Firefox */
    -webkit-transform:rotate(60deg); /* Safari and Chrome */
    -o-transform:rotate(60deg); /* Opera */
    transform:rotate(60deg);
}

.hexrow > div > div:first-of-type:before {
    content: '';
    position: absolute;
    width: 200px; /* width of main + margin sizing */
    height: 100%;
    background-image: inherit;
    background-position: top left;
    background-repeat: no-repeat;
    bottom: 0;
    left: 0;
    z-index: 1;

    -ms-transform:rotate(-60deg) translate(-150px, 0); /* IE 9 */
    -moz-transform:rotate(-60deg) translate(-150px, 0); /* Firefox */
    -webkit-transform:rotate(-60deg) translate(-150px, 0); /* Safari and Chrome */
    -o-transform:rotate(-60deg) translate(-150px, 0); /* Opera */
    transform:rotate(-60deg) translate(-150px, 0);

    -ms-transform-origin: 0 0; /* IE 9 */
    -webkit-transform-origin: 0 0; /* Safari and Chrome */
    -moz-transform-origin: 0 0; /* Firefox */
    -o-transform-origin: 0 0; /* Opera */
    transform-origin: 0 0;
}

.hexrow > div > div:last-of-type {
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: -2;
    overflow: hidden;
    background-image: inherit;

    -ms-transform:rotate(-60deg); /* IE 9 */
    -moz-transform:rotate(-60deg); /* Firefox */
    -webkit-transform:rotate(-60deg); /* Safari and Chrome */
    -o-transform:rotate(-60deg); /* Opera */
    transform:rotate(-60deg);
}

.hexrow > div > div:last-of-type:before {
    content: '';
    position: absolute;
    width: 200px; /* starting width + margin sizing */
    height: 100%;
    background-image: inherit;
    background-position: top left;
    background-repeat: no-repeat;
    bottom: 0;
    left: 0;
    z-index: 1;

    /*translate properties are initial width (100px) and half height (173.2 / 2 = 86.6) */
    -ms-transform:rotate(60deg) translate(100px, 86.6px); /* IE 9 */
    -moz-transform:rotate(60deg) translate(100px, 86.6px); /* Firefox */
    -webkit-transform:rotate(60deg) translate(100px, 86.6px); /* Safari and Chrome */
    -o-transform:rotate(60deg) translate(100px, 86.6px); /* Opera */
    transform:rotate(60deg) translate(100px, 86.6px);

    -ms-transform-origin: 100% 0; /* IE 9 */
    -webkit-transform-origin: 100% 0; /* Safari and Chrome */
    -moz-transform-origin: 100% 0; /* Firefox */
    -o-transform-origin: 100% 0; /* Opera */
    transform-origin: 100% 0;
}

This span houses your text. The line-height is reset to make the lines of text normal, but the vertical-align: middle works since the line-height was larger on the parent. The white-space is reset so it allows wrapping again. The left/right margin can be set to negative to allow the text to go into the "wings" of the hex.

.hexrow > div > span {
    display: inline-block;
    margin: 0 -30px;
    line-height: 1.1;
    vertical-align: middle;
    white-space: normal;
}

You can individual target rows and cells in those rows to change images, or span text settings, or opacity, or accommodate a larger image (to shift it to the place you want), etc. That is what the following do for the second row.

.hexrow:nth-child(2) > div:nth-child(1) {
    background-image: url(http://i.imgur.com/7Un8Y.jpg);
}

.hexrow:nth-child(2) > div:nth-child(1) > span {
    /*change some other settings*/
    margin: 0 -20px;
    color: black;
    font-size: .8em;
    font-weight: bold;
}

.hexrow:nth-child(2) > div:nth-child(2) {
    background-image: url(http://i.imgur.com/jeSPg.jpg);
}

.hexrow:nth-child(2) > div:nth-child(3) {
    background-image: url(http://i.imgur.com/Jwmxm.jpg);
    /*you can shift a large background image, but it can get complicated
    best to keep the image as the total width (200px) and height (174px)
    that the hex would be.
    */
    background-position: -150px -120px;
    opacity: .3;
    color: black;
}

.hexrow:nth-child(2) > div:nth-child(3) > div:before {
    /*you can shift a large background image, but it can get complicated
    best to keep the image as the total width (200px) and height (174px)
    that the hex would be.
    */
    background-position: -100px -120px; /* the left shift is always less in the pseudo elements by the amount of the base shift */
}

.hexrow:nth-child(2) > div:nth-child(4) {
    background-image: url(http://i.imgur.com/90EkV.jpg);
    background-position: -350px -120px;
}

.hexrow:nth-child(2) > div:nth-child(4) > div:before {
    background-position: -300px -120px;
}
Allfired answered 9/4, 2012 at 2:41 Comment(7)
Thank you for giving such a well explained, thorough, and genius answer, this is awesome!Analysis
@Allfired I've been having the weirdest issue implementing it though. I'm using Twitter's Bootstrap toolkit, and somehow it always appears like this. I've whittled the issue down to div.container: the layout becomes messed up when the hexagon is within this container div. The weird thing though, is look at the comparison between each scenario: link and link. I ran some tests but I can't figure out what's messing up the hexagons!Analysis
@Archio--First, you show the code for the first level child of the hex row, but the issue is likely with the second level children that are generating the "wings" of the hex since that is the part of the image missing (so I cannot debug). The issue could have been there (an overflow: hidden would do what you show), but that does not appear to be so. Look at the second level nesting div and its :before code, because likely either the div is not rotating in the transform, or the background-image is not inheriting to the both that div and its :before pseudoelement.Allfired
I just checked- they both seem to be inheriting the background color (link). They also are rotating, I can see that they are rotated in Chrome Web inspector. The problem is that they just aren't displaying, even though they have display:block.Analysis
Ah, I think I found it! Looks like the issue was z-index. I'm tweaking it now to make sure everything works correctly.Analysis
@ScottS, I'm trying to tweak your solution such that the hexagon is rotated 90deg. A couple of issues I'm having are that the vertical rectangle is still appearing and figuring out the correct background-positions. My fiddle is at jsfiddle.net/NoelYap/VZD9q.Funereal
@ScottS, I changed the image to better reflect the image sizes I'm using: jsfiddle.net/NoelYap/PxZkc.Funereal
P
53

It actually can be done with just one element per hexagon and pseudo-elements for the background image and text.

demo

Basic HTML structure:

<div class='row'>
    <div class='hexagon'></div>
</div>
<div class='row'>
    <div class='hexagon content ribbon' data-content='This is a test!!! 
    9/10'></div><!--
    --><div class='hexagon content longtext' data-content='Some longer text here.
       Bla bla bla bla bla bla bla bla bla bla blaaaah...'></div>
</div>

You can have may more rows, you just need to have n hexagons on odd rows and n+/-1 hexagons on even rows.

Relevant CSS:

* { box-sizing: border-box; margin: 0; padding: 0; }
.row { margin: -18.5% 0; text-align: center; }
.row:first-child { margin-top: 7%; }
.hexagon {
    position: relative;
    display: inline-block;
    overflow: hidden;
    margin: 0 8.5%;
    padding: 16%;
    transform: rotate(30deg) skewY(30deg) scaleX(.866); /* .866 = sqrt(3)/2 */
}
.hexagon:before, .content:after {
    display: block;
    position: absolute;
    /* 86.6% = (sqrt(3)/2)*100% = .866*100% */
    top: 6.7%; right: 0; bottom: 6.7%; left: 0; /* 6.7% = (100% -86.6%)/2 */
    transform: scaleX(1.155) /* 1.155 = 2/sqrt(3) */ 
               skewY(-30deg) rotate(-30deg);
    background-color: rgba(30,144,255,.56);
    background-size: cover;
    content: '';
}
.content:after { content: attr(data-content); }
/* add background images to :before pseudo-elements */
.row:nth-child(n) .hexagon:nth-child(m):before {
    background-image: 
        url(background-image-mxn.jpg); 
}
Paradiddle answered 25/10, 2012 at 13:5 Comment(5)
This really deserves more upvotes! So terse and simple, and the demo really shows off the versatility. Only drawback might be that the content is stored in an attribute rather than the element itself.Gavingavini
You can put the content in a child element :) I only wanted to minimize the number of HTML elements needed here. But a child element with actual content can be used easily instead of the pseudo.Paradiddle
You're right, basically only requires replacing .content:after with .hexagon > *. Ingenious.Gavingavini
any thoughts on how the margins between the hexagons could be elegantly altered?Ghana
oddly, if expanded to a whole bunch of hexagons, this becomes glitchy on safari, as per this questionGhana
L
2

I will provide a simple demo of how to create a hexagonal shape.

.hex {
  width: 40px;
  height: 70px;
  margin: 20px;
  overflow: hidden;
}

.hex:before {
  content: "";
  transform: rotate(45deg);
  background: #f00;
  width: 50px;
  height: 50px;
  display: inline-block;
  margin: 10px -5px 10px -5px;
}
<div class="hex">
</div>
Lynnettelynnworth answered 9/4, 2012 at 3:24 Comment(0)
U
1

You can create a fully responsive hexagonal grid using only CSS. The idea is to create a parent shape as a mask using CSS2.1 overflow:hidden which is compatible with almost all browsers, even internet explorer 6.

It is a surprisingly simple technique that can be used for creating a responsive grid of all kinds of shapes, it just requires thinking outside of the box to solve the problem.

I have an extensive step by step guide on how to do this technique here: https://www.codesmite.com/article/how-to-create-pure-css-hexagonal-grids

This is the best way I have found so far, requires no javascript and is both fluid and responsive.

I also used the technique in a free HTML template that includes images inside the hexagons which you can demo and download here: https://www.codesmite.com/freebie/hexa-free-responsive-portfolio-template

Unreasonable answered 21/1, 2017 at 6:54 Comment(1)
The link gives an internal server error. Is there an updated link?Reactant
H
1

I have wrote a detailed article on how to generate a responsive grid of hexagon shapes with a little code: https://css-tricks.com/hexagons-and-beyond-flexible-responsive-grid-patterns-sans-media-queries/

All you have to do is to update a few CSS variables to control the grid:

.main {
  display:flex;
  --s: 100px;  /* size  */
  --m: 4px;    /* margin */
  --f: calc(1.732 * var(--s) + 4 * var(--m)  - 1px);
}

.container {
  font-size: 0; /*disable white space between inline block element */
}

.container div {
  width: var(--s);
  margin: var(--m);
  height: calc(var(--s)*1.1547); 
  display: inline-block;
  font-size:initial;
  clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);
  background: red;
  margin-bottom: calc(var(--m) - var(--s)*0.2885); 
}
.container div:nth-child(odd) {
  background:green;
}
.container::before {
  content: "";
  width: calc(var(--s)/2 + var(--m));
  float: left;
  height: 120%;
  shape-outside: repeating-linear-gradient(     
                   #0000 0 calc(var(--f) - 3px),      
                   #000  0 var(--f));
}
<div class="main">
  <div class="container">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</div>

Responsive grid of hexagon using CSS

Find the project on Github as well: https://github.com/Afif13/responsive-grid-shapes

Hippodrome answered 14/1, 2023 at 11:22 Comment(0)
C
0

Here is another approach using COMPASS/SCSS, that allows easily set the hexagons size and layout:

http://codepen.io/interdruper/pen/GrBEk

Clotilde answered 3/5, 2013 at 10:59 Comment(0)
T
-1

if you are able to implement the div shapes trick, then simply give each div a position:relative (you would have to initially position them all one by one at first, by also setting top and left)

Twotime answered 8/4, 2012 at 13:0 Comment(2)
Can you give an example in a JSFiddle? The div shapes trick doesn't really work, if I need to fill the hexagon with images and text.Analysis
Yeah, you will only be able to create single-color shapes: jsfiddle.net/Exceeder/cVqtW It will not be possible to use this div's as clipping regions for background images or to shape text.Admissive

© 2022 - 2024 — McMap. All rights reserved.