Home > Mobile >  Is there a way to generate Hexagons in a triangular/pyramid fashion in CSS/HTML/JS?
Is there a way to generate Hexagons in a triangular/pyramid fashion in CSS/HTML/JS?

Time:10-05

I was trying to get started on one of my personal projects and I wanted to generate hexagons in a particular Pattern and each new row is generated depending on user input. I went through all the stackoverflow answers and everyone seems to make an entire grid or just svg pattern on the website directly. The problem for me is I want to animate each hexagon and need to access to each hexagon element. Is there a way to do this in CSS/HTML/JS or in general using some other language.

CodePudding user response:

You can use a combination of CSS clip paths and a basic understanding of geometry to get the position right. Assuming that you want to define the width of an individual hexagon, then the ratio of height to width is 1.157... (2 / Math.sqrt(3) to be precise), but we round it to 1.2 for ease of calculation.

The CSS clip path for a hexagon (with the long diagonal being vertical) is:

clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);

Then it's a matter of applying these calculations to get the right layout. I have used CSS custom properties in the example below, so you can adjust the placement when necessary.

.stage {
  --cell-width: 100px;
  --cell-height: calc(var(--cell-width) * 1.2);
  --cell-spacing: 4px;
  
  padding-top: calc(var(--cell-width) * 0.25);
  padding-bottom: calc(var(--cell-width) * 0.25);
  background-color: #ddd;
}

.row {
  display: flex;
  justify-content: center;
  margin-top: calc(var(--cell-width) * -0.27   var(--cell-spacing));
  margin-bottom: calc(var(--cell-width) * -0.27   var(--cell-spacing));
}

.cell {
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  background-color: steelblue;
  width: var(--cell-width);
  height: var(--cell-height);
  margin-left: var(--cell-spacing);
  margin-right: var(--cell-spacing);
}
<div class="stage">
  <div class="row">
    <div class="cell"></div>
  </div>
  <div class="row">
    <div class="cell"></div>
    <div class="cell"></div>
  </div>
  <div class="row">
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
  </div>
  <div class="row">
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
  </div>
</div>

CodePudding user response:

You can make an element into a hexagon shape by using clip-path polygon.

Then on user input you can create however many hexagons you want and lay them side by side in a new row.

This snippet is a simplified version, it creates a hexagon on a click and puts it in a row.

It uses CSS variable to define the length of the side of the hexagon so it is easy for you to change.

const row = document.querySelector('.row');

function createHexagon() {
  const hexagon = document.createElement('div');
  hexagon.classList.add('hexagon');
  return hexagon;
}
.hexagon {
  --sin30: 0.5;
  --side: 10vmin;
  --x: calc(var(--side) * var(--sin30));
  background-color: gray;
  clip-path: polygon(var(--x) 0, calc(100% - var(--x)) 0, 100% 50%, calc(100% - var(--x)) 100%, var(--x) 100%, 0% 50%);
  width: calc(2 * var(--side));
  height: calc(2 * var(--side));
  display: inline-block;
  margin: 1vmin;
}
<div class="row"></div>
<button onclick="row.appendChild(createHexagon());">Click to add a hexagon</button>

CodePudding user response:

You can store the pattern into an array and then create the elements dynamically looping through that array.

Just create a container div and than create rows dynamically and put hexagons into rows and than append the row into container.

const container = document.querySelector('.container');
const hexagonPattern = [1, 2, 3, 4];

for (let i = 0; i < hexagonPattern.length; i  ) {
    const row = document.createElement('div');
    row.classList.add('row');
     for (let j = 0; j < hexagonPattern[i]; j  ) {
          const hexagon = document.createElement('div');
          hexagon.classList.add('hexagon');
          row.appendChild(hexagon);
      }
      container.appendChild(row);
}
.container {
    display: flex;
     flex-direction: column;
     align-items: center;
   }
     
   .row {
     margin-bottom: -30px;
   }
   
   .hexagon {
     display: inline-block;
     box-shadow: 10px 10px 5px #000;
     width: 100px; 
     height: 100px;
     background: grey;
     -webkit-clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
     transition: .7s;
     margin: 2px;
   }
   .hexagon:hover {
     background: red;
     transform: rotateY(-180deg);
     transition: .7s;
   }
  <div class="container">
  </div>

CodePudding user response:

Here is a demo from the article I wrote: https://css-tricks.com/hexagons-and-beyond-flexible-responsive-grid-patterns-sans-media-queries/

It's responsive Octagon grid. Run it on full screen to see the pyramidal grid that will fallback to a normal one on small screen. I invite you to read the above article to understand the technical detail behind this technique.

All you have to do is to adjust the variables to get your hexagon grid:

let inputs = document.querySelectorAll('input[type=range]')
let elem = document.querySelector('.main')


inputs.forEach(input => {
   input.addEventListener('change', function(e) {
      var p = e.target.getAttribute('name');
      if(p=="s" || p=="mv") {
        elem.style.setProperty("--" p, this.value "px");
            e.target.previousElementSibling.innerHTML = this.value "px";
      } else { 
        elem.style.setProperty("--" p, this.value);
          e.target.previousElementSibling.innerHTML = this.value;
      }
    });
});
.main {
  display:flex;
  --s: 100px; /* size */
  --r: 1; /* ratio */
  /* clip-path */
  --h: 0.25;  
  --v: 0.35; 
  --hc:calc(clamp(0,var(--h),0.5) * var(--s)) ;
  --vc:calc(clamp(0,var(--v),0.5) * var(--s) * var(--r)); 
  
  /*margin */
  --mv: 4px; /* vertical */
  --mh: calc(var(--mv)   (var(--s) - 2*var(--hc))/2); /* horizontal */
  /* for the float*/
  --f: calc(2*var(--s)*var(--r)   4*var(--mv)  - 2*var(--vc) - 2px);
  --nr:6;
  --lw:calc(var(--nr)*(var(--s)   2*var(--mh)));
}

.container {
  font-size: 0; /*disable white space between inline block element */
  max-width:var(--lw);
  margin:0 auto;
}

.container div {
  width: var(--s);
  margin: var(--mv) var(--mh);
  height: calc(var(--s)*var(--r)); 
  display: inline-block;
  font-size:initial;
  clip-path: polygon(var(--hc) 0, calc(100% - var(--hc)) 0,100% var(--vc),100% calc(100% - var(--vc)), calc(100% - var(--hc)) 100%,var(--hc) 100%,0 calc(100% - var(--vc)),0 var(--vc));
  background: red;
  margin-bottom: calc(var(--mv) - var(--vc)); 
}
.container div:nth-child(odd) {
  background:green;
}


.container::before {
  content: "";
  width: clamp(0px, (var(--lw) - 100%)*1000,calc(var(--s)/2   var(--mh)));
  float: left;
  height: 120%; 
  shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px),#000 0 var(--f));
}

.container i::before ,
.container i::after{
  content: "";
  width: clamp(0px, (100% - var(--lw)   1px)*1000,calc(50% - var(--mh) - var(--s)/2));
  float: left;
  height: calc(var(--f)*(var(--nr) - 1)/2);
  shape-outside: linear-gradient(to bottom right,#000 50.5%,#0000 0);
}
.container i::after {
  float:right;
  shape-outside: linear-gradient(to bottom left,#000 49%,#0000 0);
}

.panel {position: fixed;top: 20px;right: 20px;padding: 10px;border: 1px solid;border-radius: 10px;background: #fff;font-family: sans-serif;opacity:.5}
.panel:hover {opacity:1}
.panel > div:not(:last-child) {border-bottom: 1px solid;padding-bottom: 10px;margin-bottom: 10px;}
*,*::before {transition:0.5s linear}
<div class="main">
  <div class="container">
    <i></i>
    <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 class="panel">
<div>Size: [<span>100px</span>] <input type="range" min="20" max="200" step="10" value="100" name="s"></div>
<div>Ratio: [<span>1</span>] <input type="range" min="0" max="2" step="0.05" value="1" name="r"></div>
<div>Spacing: [<span>4px</span>]<input type="range" min="0" max="10" step="1" value="4" name="mv"></div>
<div>Clip-path<br>
hc: [<span>0.25</span>]<input type="range" min="0" max=".5" step=".05" value=".25" name="h"><br>
vc: [<span>0.35</span>]<input type="range" min="0" max=".5" step=".05" value=".35" name="v"></div></div>

  • Related