Search code examples
javascriptcsssvgcss-shapes

CSS animated hexagon menu


I'm trying to figure out the best way to build an animated hexagon menu. Please, see the following image for better understanding:

Animated hexagonal menu

The hexagon burger button is in the center. Once clicked, it reveals the triangular shapes surrounding the burger button. The latter transformed into a cross to reverse the process and hide everything back to the how it started.

My picture is actually missing one step in the very beginning. The first picture should only show the burger button just as the codepen demo.

So my question is the following :

Could you please me advise me on how you would build this hexagon menu animation from HTML to CSS through jQuery and others. What techniques would you use to make it happen. Keep in mind that whereas the codepen example is featuring simple icons, mine features triangular pictures which once hovered reveal a title with subtitle.


Solution

  • Here is an approach and demo for a

    Hexagon menu demo

    And here is an animated gif of the hexagon menu in action :

    Hexagon menu with CSS HTML SVG ad JS

    Hexagon menu main features :

    • responsive according to the viewport size (vmin). This can be modified by changing the width/height values to percentages (aspect ratio needs to be maintained see here)
    • Images (with the <img/> tag), titles and subtitles
    • 6 menu items
    • animated burger icon in a hexagon outline
    • menu item boundaries are clipped to triangles so they don't overlap each other. This way, the click event and hover state are triggered only when the actual menu item is hovered/clicked (except for IE, see browser support at the en of this post)
    • Can be featured over images, gradients or any non plain backgrounds

    The hexagonal layout :

    • CSS3 2d transforms to make the triangles skewY() and rotate()
    • the menu items are clipped to triangles with .tr and .clip, unskewed with .clip and unrotated with .content the hexagon shape around the burger is made with an SVG polygon (easier to make and better result than with CSS (see 1st version)
    • The burger icon is made with a span and 2 pseudo elements

    Hexagon menu featuring Animation :

    • the triangles are transitioned one by one (translate() and opacity) with the transition-delay property
    • the "bounce effect" for the triangle featuring animation is made with the transition-timming-function and a cubic bezier curve
    • the burger to cross animation is made by transitioning the 2 pseudo elments (translate() and rotate()) and fading the center elements background to a transparent rgba color

    Hover animations :

    • the burger hexagon hover effect is made by animating the stroke-dashoffset property of the SVG <polygon> element.
    • the menu item titles and subtitles are featured on hover using 3D transforms (translateZ()) and opacity. The images are faded out at the same time

    Used technology :

    • the CSS is coded with the help of SCSS and Autoprefixer to make it easier to write, read and shorter. You can see the compiled CSS by clicking on the "view compiled" button in the demo (the compiled CSS is also available at the end of this post)
    • SVG for the hexagon around the burger
    • HTML for the markup
    • plain JS to trigger the featuring animation with a class change on the container

    Browser support :

    I tested this menu on IE 11, chrome, FF and opera on a windows system. and the menu works on all these browsers. Chrome and FF render the font with blur (as seen in the animated gif made with chrome) and FF tends to make jagged sides for the triangles.

    IE 11 has the best quality output for the fonts and triangles but :

    • it doesn't support SMIL animations and therefore doesn't render the hover effect on the burger hexagon
    • the overflow property for the links is ignored and the boundaries of the triangles aren't maintained

    I used crossbrowser testing to test safari support and the hexagon menu works on that system too.


    For people who got this far, here is another link to the demo : hexagon menu
    And the full code with the compiled CSS :

    var hexNav = document.getElementById('hexNav');
    
    document.getElementById('menuBtn').onclick = function() {
        var className = ' ' + hexNav.className + ' ';
        if ( ~className.indexOf(' active ') ) {
            hexNav.className = className.replace(' active ', ' ');
        } else {
            hexNav.className += ' active';
        }              
    }
    * {
      margin: 0;
      padding: 0;
    }
    
    html, body {
      height: 100%;
    }
    
    body {
      font-family: 'Open Sans', sans-serif;
      background: #E3DFD2;
    }
    
    ul {
      list-style-type: none;
    }
    
    a, a:hover, a:focus, a:visited {
      text-decoration: none;
    }
    
    nav {
      position: relative;
      width: 70vmin;
      height: 70vmin;
      min-width: 500px;
      min-height: 500px;
      margin: 0 auto;
      overflow: hidden;
    }
    
    /** MENU BUTTON ******************************************/
    #menuBtn {
      position: absolute;
      top: 45%;
      left: 45%;
      width: 10%;
      height: 10%;
      cursor: pointer;
      z-index: 2;
      will-change: transform;
    }
    #menuBtn svg {
      display: block;
    }
    #menuBtn:hover svg polygon {
      -webkit-animation: hexHover 0.7s;
              animation: hexHover 0.7s;
    }
    #menuBtn span {
      position: absolute;
      top: 50%;
      left: 50%;
      width: 20px;
      height: 2px;
      padding: 8px 0;
      background-clip: content-box;
      background-color: #585247;
      -webkit-transform: translate(-50%, -50%);
          -ms-transform: translate(-50%, -50%);
              transform: translate(-50%, -50%);
      -webkit-transition: background-color 0.5s;
              transition: background-color 0.5s;
    }
    #menuBtn span:before, #menuBtn span:after {
      position: absolute;
      background-color: #585247;
      content: '';
      width: 20px;
      height: 2px;
      -webkit-transition: -webkit-transform 0.5s;
              transition: transform 0.5s;
    }
    #menuBtn span:before {
      top: 0;
    }
    #menuBtn span:after {
      bottom: 0px;
    }
    
    @-webkit-keyframes hexHover {
      0% {
        stroke-dasharray: 0,0,300;
      }
      10% {
        stroke-dasharray: 0,20,300;
      }
      100% {
        stroke-dasharray: 300,20,300;
      }
    }
    
    @keyframes hexHover {
      0% {
        stroke-dasharray: 0,0,300;
      }
      10% {
        stroke-dasharray: 0,20,300;
      }
      100% {
        stroke-dasharray: 300,20,300;
      }
    }
    /** MENU ITEMS *******************************************/
    #hex {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      -webkit-transform: scale(0.1) translatez(0);
          -ms-transform: scale(0.1) translatez(0);
              transform: scale(0.1) translatez(0);
      -webkit-transition: -webkit-transform 0.05s 0.5s;
              transition: transform 0.05s 0.5s;
    }
    
    .tr {
      position: absolute;
      left: 50%;
      bottom: 50%;
      width: 34.6%;
      height: 40%;
      -webkit-transform-origin: 0 100%;
          -ms-transform-origin: 0 100%;
              transform-origin: 0 100%;
      overflow: hidden;
      -webkit-transform: skewY(-30deg);
          -ms-transform: skewY(-30deg);
              transform: skewY(-30deg);
      opacity: 0;
    }
    
    .tr:nth-child(1) {
      -webkit-transform: rotate(0deg) skewY(-30deg);
          -ms-transform: rotate(0deg) skewY(-30deg);
              transform: rotate(0deg) skewY(-30deg);
      -webkit-transition: opacity 0.5s, -webkit-transform 0.5s;
              transition: opacity 0.5s, transform 0.5s;
    }
    .tr:nth-child(1) .clip {
      -webkit-transform: skewY(30deg) rotate(30deg);
          -ms-transform: skewY(30deg) rotate(30deg);
              transform: skewY(30deg) rotate(30deg);
    }
    .tr:nth-child(1) .content {
      -webkit-transform: rotate(-30deg);
          -ms-transform: rotate(-30deg);
              transform: rotate(-30deg);
      -webkit-transform-origin: 0 0;
          -ms-transform-origin: 0 0;
              transform-origin: 0 0;
      padding-left: 15%;
      -webkit-perspective-origin: 30% 70%;
              perspective-origin: 30% 70%;
    }
    
    .active .tr:nth-child(1) {
      -webkit-transform: rotate(0deg) skewY(-30deg) translate(10%, -10%);
          -ms-transform: rotate(0deg) skewY(-30deg) translate(10%, -10%);
              transform: rotate(0deg) skewY(-30deg) translate(10%, -10%);
      -webkit-transition: opacity 0.5s 0.05s, -webkit-transform 0.5s 0.05s cubic-bezier(0, 2.3, 0.8, 1);
              transition: opacity 0.5s 0.05s, transform 0.5s 0.05s cubic-bezier(0, 2.3, 0.8, 1);
    }
    
    .tr:nth-child(2) {
      -webkit-transform: rotate(60deg) skewY(-30deg);
          -ms-transform: rotate(60deg) skewY(-30deg);
              transform: rotate(60deg) skewY(-30deg);
      -webkit-transition: opacity 0.5s, -webkit-transform 0.5s;
              transition: opacity 0.5s, transform 0.5s;
    }
    .tr:nth-child(2) .clip {
      -webkit-transform: skewY(30deg) rotate(30deg);
          -ms-transform: skewY(30deg) rotate(30deg);
              transform: skewY(30deg) rotate(30deg);
    }
    .tr:nth-child(2) .content {
      -webkit-transform: rotate(-90deg);
          -ms-transform: rotate(-90deg);
              transform: rotate(-90deg);
      top: -8%;
      left: 6.67%;
      padding-left: 30%;
      -webkit-perspective-origin: 30% 50%;
              perspective-origin: 30% 50%;
    }
    
    .active .tr:nth-child(2) {
      -webkit-transform: rotate(60deg) skewY(-30deg) translate(10%, -10%);
          -ms-transform: rotate(60deg) skewY(-30deg) translate(10%, -10%);
              transform: rotate(60deg) skewY(-30deg) translate(10%, -10%);
      -webkit-transition: opacity 0.5s 0.1s, -webkit-transform 0.5s 0.1s cubic-bezier(0, 2.3, 0.8, 1);
              transition: opacity 0.5s 0.1s, transform 0.5s 0.1s cubic-bezier(0, 2.3, 0.8, 1);
    }
    
    .tr:nth-child(3) {
      -webkit-transform: rotate(120deg) skewY(-30deg);
          -ms-transform: rotate(120deg) skewY(-30deg);
              transform: rotate(120deg) skewY(-30deg);
      -webkit-transition: opacity 0.5s, -webkit-transform 0.5s;
              transition: opacity 0.5s, transform 0.5s;
    }
    .tr:nth-child(3) .clip {
      -webkit-transform: skewY(30deg) rotate(30deg);
          -ms-transform: skewY(30deg) rotate(30deg);
              transform: skewY(30deg) rotate(30deg);
    }
    .tr:nth-child(3) .content {
      -webkit-transform: rotate(-150deg);
          -ms-transform: rotate(-150deg);
              transform: rotate(-150deg);
      -webkit-transform-origin: 42.3% 36.5%;
          -ms-transform-origin: 42.3% 36.5%;
              transform-origin: 42.3% 36.5%;
      padding-left: 10%;
      -webkit-perspective-origin: 30% 30%;
              perspective-origin: 30% 30%;
    }
    
    .active .tr:nth-child(3) {
      -webkit-transform: rotate(120deg) skewY(-30deg) translate(10%, -10%);
          -ms-transform: rotate(120deg) skewY(-30deg) translate(10%, -10%);
              transform: rotate(120deg) skewY(-30deg) translate(10%, -10%);
      -webkit-transition: opacity 0.5s 0.15s, -webkit-transform 0.5s 0.15s cubic-bezier(0, 2.3, 0.8, 1);
              transition: opacity 0.5s 0.15s, transform 0.5s 0.15s cubic-bezier(0, 2.3, 0.8, 1);
    }
    
    .tr:nth-child(4) {
      -webkit-transform: rotate(180deg) skewY(-30deg);
          -ms-transform: rotate(180deg) skewY(-30deg);
              transform: rotate(180deg) skewY(-30deg);
      -webkit-transition: opacity 0.5s, -webkit-transform 0.5s;
              transition: opacity 0.5s, transform 0.5s;
    }
    .tr:nth-child(4) .clip {
      -webkit-transform: skewY(30deg) rotate(30deg);
          -ms-transform: skewY(30deg) rotate(30deg);
              transform: skewY(30deg) rotate(30deg);
    }
    .tr:nth-child(4) .content {
      -webkit-transform: rotate(-210deg);
          -ms-transform: rotate(-210deg);
              transform: rotate(-210deg);
      -webkit-transform-origin: 65.4% 38.4%;
          -ms-transform-origin: 65.4% 38.4%;
              transform-origin: 65.4% 38.4%;
      padding-left: 30%;
      -webkit-perspective-origin: 70% 30%;
              perspective-origin: 70% 30%;
    }
    
    .active .tr:nth-child(4) {
      -webkit-transform: rotate(180deg) skewY(-30deg) translate(10%, -10%);
          -ms-transform: rotate(180deg) skewY(-30deg) translate(10%, -10%);
              transform: rotate(180deg) skewY(-30deg) translate(10%, -10%);
      -webkit-transition: opacity 0.5s 0.2s, -webkit-transform 0.5s 0.2s cubic-bezier(0, 2.3, 0.8, 1);
              transition: opacity 0.5s 0.2s, transform 0.5s 0.2s cubic-bezier(0, 2.3, 0.8, 1);
    }
    
    .tr:nth-child(5) {
      -webkit-transform: rotate(240deg) skewY(-30deg);
          -ms-transform: rotate(240deg) skewY(-30deg);
              transform: rotate(240deg) skewY(-30deg);
      -webkit-transition: opacity 0.5s, -webkit-transform 0.5s;
              transition: opacity 0.5s, transform 0.5s;
    }
    .tr:nth-child(5) .clip {
      -webkit-transform: skewY(30deg) rotate(30deg);
          -ms-transform: skewY(30deg) rotate(30deg);
              transform: skewY(30deg) rotate(30deg);
    }
    .tr:nth-child(5) .content {
      -webkit-transform: rotate(-270deg);
          -ms-transform: rotate(-270deg);
              transform: rotate(-270deg);
      top: -8%;
      left: 6.67%;
      padding-left: 15%;
      -webkit-perspective-origin: 70% 50%;
              perspective-origin: 70% 50%;
    }
    
    .active .tr:nth-child(5) {
      -webkit-transform: rotate(240deg) skewY(-30deg) translate(10%, -10%);
          -ms-transform: rotate(240deg) skewY(-30deg) translate(10%, -10%);
              transform: rotate(240deg) skewY(-30deg) translate(10%, -10%);
      -webkit-transition: opacity 0.5s 0.25s, -webkit-transform 0.5s 0.25s cubic-bezier(0, 2.3, 0.8, 1);
              transition: opacity 0.5s 0.25s, transform 0.5s 0.25s cubic-bezier(0, 2.3, 0.8, 1);
    }
    
    .tr:nth-child(6) {
      -webkit-transform: rotate(300deg) skewY(-30deg);
          -ms-transform: rotate(300deg) skewY(-30deg);
              transform: rotate(300deg) skewY(-30deg);
      -webkit-transition: opacity 0.5s, -webkit-transform 0.5s;
              transition: opacity 0.5s, transform 0.5s;
    }
    .tr:nth-child(6) .clip {
      -webkit-transform: skewY(30deg) rotate(30deg);
          -ms-transform: skewY(30deg) rotate(30deg);
              transform: skewY(30deg) rotate(30deg);
    }
    .tr:nth-child(6) .content {
      -webkit-transform: rotate(-330deg);
          -ms-transform: rotate(-330deg);
              transform: rotate(-330deg);
      -webkit-transform-origin: 106.7% 25.2%;
          -ms-transform-origin: 106.7% 25.2%;
              transform-origin: 106.7% 25.2%;
      padding-left: 30%;
      -webkit-perspective-origin: 70% 70%;
              perspective-origin: 70% 70%;
    }
    
    .active .tr:nth-child(6) {
      -webkit-transform: rotate(300deg) skewY(-30deg) translate(10%, -10%);
          -ms-transform: rotate(300deg) skewY(-30deg) translate(10%, -10%);
              transform: rotate(300deg) skewY(-30deg) translate(10%, -10%);
      -webkit-transition: opacity 0.5s 0.3s, -webkit-transform 0.5s 0.3s cubic-bezier(0, 2.3, 0.8, 1);
              transition: opacity 0.5s 0.3s, transform 0.5s 0.3s cubic-bezier(0, 2.3, 0.8, 1);
    }
    
    .tr:nth-child(7) {
      -webkit-transform: rotate(360deg) skewY(-30deg);
          -ms-transform: rotate(360deg) skewY(-30deg);
              transform: rotate(360deg) skewY(-30deg);
      -webkit-transition: opacity 0.5s, -webkit-transform 0.5s;
              transition: opacity 0.5s, transform 0.5s;
    }
    .tr:nth-child(7) .clip {
      -webkit-transform: skewY(30deg) rotate(30deg);
          -ms-transform: skewY(30deg) rotate(30deg);
              transform: skewY(30deg) rotate(30deg);
    }
    .tr:nth-child(7) .content {
      -webkit-transform: rotate(-390deg);
          -ms-transform: rotate(-390deg);
              transform: rotate(-390deg);
    }
    
    .active .tr:nth-child(7) {
      -webkit-transform: rotate(360deg) skewY(-30deg) translate(10%, -10%);
          -ms-transform: rotate(360deg) skewY(-30deg) translate(10%, -10%);
              transform: rotate(360deg) skewY(-30deg) translate(10%, -10%);
      -webkit-transition: opacity 0.5s 0.35s, -webkit-transform 0.5s 0.35s cubic-bezier(0, 2.3, 0.8, 1);
              transition: opacity 0.5s 0.35s, transform 0.5s 0.35s cubic-bezier(0, 2.3, 0.8, 1);
    }
    
    .clip {
      position: absolute;
      top: 0;
      left: 0;
      width: 116%;
      height: 86.66%;
      overflow: hidden;
      -webkit-transform-origin: 0 0;
          -ms-transform-origin: 0 0;
              transform-origin: 0 0;
    }
    
    .content {
      position: absolute;
      width: 86.6%;
      height: 116%;
      top: 0;
      left: 0;
      box-sizing: border-box;
      font-size: 2vmin;
      -webkit-perspective: 500px;
              perspective: 500px;
      background: #000;
    }
    .content img {
      position: absolute;
      top: 0;
      left: -50%;
      right: -50%;
      margin: auto;
      height: 100%;
      z-index: -1;
      -webkit-transition: opacity 0.3s;
              transition: opacity 0.3s;
      pointer-events: none;
    }
    .content h2, .content p {
      position: absolute;
      width: 60%;
      line-height: 1em;
      color: #fff;
      opacity: 0;
      -webkit-transform: translateZ(-50px);
              transform: translateZ(-50px);
    }
    .content h2 {
      bottom: 50%;
      text-transform: uppercase;
      font-weight: 900;
      font-size: 2em;
      -webkit-transition: -webkit-transform 0.3s cubic-bezier(0, 2.3, 0.8, 1), opacity 0.3s;
              transition: transform 0.3s cubic-bezier(0, 2.3, 0.8, 1), opacity 0.3s;
    }
    .content p {
      position: absolute;
      top: 50%;
      font-size: 1em;
      -webkit-transition: -webkit-transform 0.3s 0.075s cubic-bezier(0, 2.3, 0.8, 1), opacity 0.3s 0.075s;
              transition: transform 0.3s 0.075s cubic-bezier(0, 2.3, 0.8, 1), opacity 0.3s 0.075s;
    }
    .content:hover h2, .content:hover p {
      opacity: 1;
      -webkit-transform: translatez(0);
          -ms-transform: translatez(0);
              transform: translatez(0);
    }
    .content:hover img {
      opacity: 0.4;
    }
    
    .active #menuBtn:hover svg polygon {
      -webkit-animation: none;
              animation: none;
    }
    .active #menuBtn span {
      background-color: transparent;
    }
    .active #menuBtn span:before {
      -webkit-transform: translatey(8px) rotate(45deg);
          -ms-transform: translatey(8px) rotate(45deg);
              transform: translatey(8px) rotate(45deg);
    }
    .active #menuBtn span:after {
      -webkit-transform: translatey(-8px) rotate(-45deg);
          -ms-transform: translatey(-8px) rotate(-45deg);
              transform: translatey(-8px) rotate(-45deg);
    }
    .active #hex {
      -webkit-transform: scale(0.9) translatez(0);
          -ms-transform: scale(0.9) translatez(0);
              transform: scale(0.9) translatez(0);
      -webkit-transition: none;
              transition: none;
      will-change: transform;
    }
    .active .tr {
      opacity: 1;
      will-change: transform;
    }
    <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,800' rel='stylesheet' type='text/css'>
    <nav id="hexNav">
      <div id="menuBtn">
        <svg viewbox="0 0 100 100">
          <polygon points="50 2 7 26 7 74 50 98 93 74 93 26" fill="none" stroke-width="4" stroke="#585247" stroke-dasharray="0,0,300"/>
        </svg>
        <span></span>
      </div>
      <ul id="hex">
        <li class="tr"><div class="clip"><a href="#" class="content">
          <img src="https://farm8.staticflickr.com/7435/13629508935_62a5ddf8ec_c.jpg" alt="" />
          <h2 class="title">Title</h2><p>Catch phrase</p>
        </a></div></li>
        <li class="tr"><div class="clip"><a href="#" class="content">
          <img src="https://farm3.staticflickr.com/2827/10384422264_d9c7299146.jpg" alt="" />
          <h2 class="title">Title</h2><p>Catch phrase</p>
        </a></div></li>
        <li class="tr"><div class="clip"><a href="#" class="content">
          <img src="https://farm7.staticflickr.com/6083/6055581292_d94c2d90e3.jpg" alt="" />
          <h2 class="title">Title</h2><p>Catch phrase</p>
        </a></div></li>
        <li class="tr"><div class="clip"><a href="#" class="content">
          <img src="https://farm7.staticflickr.com/6092/6227418584_d5883b0948.jpg" alt="" />
          <h2 class="title">Title</h2><p>Catch phrase</p>
        </a></div></li>
        <li class="tr"><div class="clip"><a href="#" class="content">
          <img src="https://farm8.staticflickr.com/7187/6895047173_d4b1a0d798.jpg" alt="" />
          <h2 class="title">Title</h2><p>Catch phrase</p>
        </a></div></li>
        <li class="tr"><div class="clip"><a href="#" class="content">
          <img src="https://farm4.staticflickr.com/3766/12953056854_b8cdf14f21.jpg" alt="" />
          <h2 class="title">Title</h2><p>Catch phrase</p>
        </a></div></li>
      </ul>
    </nav>


    Here is a link to the : first version