skip to Main Content

Whenever the top and bottom bars rotate, it isn’t centered on the middle. How can I fix this?
I want the 3 bars to form an X shape from the hamburger shape, but when the animation starts, the bottom and top bars get offset from the center when they rotate.

What I have currently:

function toggleState(target) {
  target.classList.toggle("animate");
}
:root {
  --btn-size: min(8vh, 8vw);
  --bar-height: calc(var(--btn-size) / 5);
  --margin-top: calc(var(--bar-height)*2);
  --margin-bottom: calc(var(--bar-height)*-2);
  --animation: 1s;
  --anim-half: calc(var(--animation)/2);
  --anim-func: ease-in-out;
  --btn-color: #929292;
}

body {
  height: max-content;
  width: max-content;
  margin: 5%;
}

#menu-btn {
  height: var(--btn-size);
  width: var(--btn-size);
  display: flex;
  align-items: center;
  cursor: pointer;
}

#menu-icon,
#menu-icon::before,
#menu-icon::after {
  height: var(--bar-height);
  width: var(--btn-size);
  background-color: var(--btn-color);
  position: absolute;
  transform-origin: center;
}

#menu-icon {
  transition: rotate var(--animation) var(--anim-func) 0.00s;
}

#menu-icon::before {
  transition: translate var(--anim-half) var(--anim-func) var(--anim-half), rotate var(--anim-half) var(--anim-func) 0.00s;
  content: "";
  transform: translateY(var(--margin-top));
}

#menu-icon::after {
  transition: translate var(--anim-half) var(--anim-func) var(--anim-half), rotate var(--anim-half) var(--anim-func) 0.00s;
  content: "";
  transform: translateY(var(--margin-bottom));
}

.animate #menu-icon {
  transition: rotate var(--animation) var(--anim-func) 0.00s;
  rotate: 360deg;
}

.animate #menu-icon::before {
  transition: translate var(--anim-half) var(--anim-func) 0.00s, rotate var(--anim-half) var(--anim-func) var(--anim-half);
  translate: 0px var(--margin-bottom);
  rotate: 45deg;
}

.animate #menu-icon::after {
  transition: translate var(--anim-half) var(--anim-func) 0.00s, rotate var(--anim-half) var(--anim-func) var(--anim-half);
  translate: 0px var(--margin-top);
  rotate: -45deg;
}
<body>
  <div id="menu-btn" onclick="toggleState(this)">
    <div id="menu-icon" class=""></div>
  </div>
</body>
<html>

I tried transform-origin: center on every line but it didn’t work. I expected it to rotate centered about the middle of the original.

2

Answers


  1. I would consider a slightly different idea and simplify your code like below:

    function toggleState(target) {
      target.classList.toggle("animate");
    }
    :root {
      --btn-size: 20vmin; /* no need to use min between vh and vw, vmin will do it for you */
      --animation: 1s;
      --anim-half: calc(var(--animation)/2);
      --anim-func: ease-in-out;
      --btn-color: #929292;
    }
    
    #menu-btn {
      height: var(--btn-size);
      aspect-ratio: 1;
      border: none;
      cursor: pointer;
      background: /* the background will create the middle bar */
       linear-gradient(var(--btn-color) 0 0) 
       50%/100% 20% no-repeat;
      position: relative;
      transition: 
        rotate var(--animation) var(--anim-func),
        background-size 0s var(--anim-half);
    }
    #menu-btn::before,
    #menu-btn::after {
      content:"";
      position: absolute;
      inset-inline: 0;
      height: 20%;
      background-color: var(--btn-color);
      transition: 
        translate var(--anim-half) var(--anim-func) var(--anim-half), 
        rotate    var(--anim-half) var(--anim-func);
    }
    #menu-btn::before { top: 0}
    #menu-btn::after { bottom: 0}
    
    #menu-btn.animate{
      rotate: 360deg;
      background-size: 0% 20%;
    }
    #menu-btn.animate::before,
    #menu-btn.animate::after {
      transition: 
        translate var(--anim-half) var(--anim-func), 
        rotate    var(--anim-half) var(--anim-func) var(--anim-half);
    }
    #menu-btn.animate::before {
      rotate: 45deg;
      translate: 0 calc(2*var(--btn-size)/5);
    }
    #menu-btn.animate::after {
      rotate: -45deg;
      translate: 0 calc(-2*var(--btn-size)/5);
    }
    
    body {
      margin: 50px;
    }
    <button id="menu-btn" onclick="toggleState(this)"></button>
    Login or Signup to reply.
  2. You’re trying to rotate the #menu-icon block inside of which there are &:before and &:after pseudo-elements. I’d suggest you use the following structure:

    <div id="menu-icon" class="">
    <span></span>
    <span></span>
    <span></span>
    
     function toggleState(target) {
                    target.classList.toggle("animate");
                }
    :root {
        --btn-size: min(8vh, 8vw);
        --bar-height: calc(var(--btn-size) / 5);
        --margin-top: calc(var(--bar-height)*2);
        --margin-bottom: calc(var(--bar-height)*-2);
        --animation: 1s;
        --anim-half: calc(var(--animation)/2);
        --anim-func: ease-in-out;
        --btn-color: #929292;
    }
    body {
        height: max-content;
        width: max-content;
        margin: 5%;
    }
    
    #menu-btn {
        height: var(--btn-size);
        width: var(--btn-size);
        display: flex;
        align-items: center;
        cursor: pointer;
    }
    
    #menu-icon {
      height:100%;
      width:100%;
      position:relative;
      display:flex;
      justify-content:center;
    }
    
    #menu-icon span {
        height: var(--bar-height);
        width: var(--btn-size);
        background-color: var(--btn-color);
        position: absolute;
        display:block;
        top: calc( 50% - var(--bar-height) / 2);
        transform-origin: center center;
        transition:all var(--anim-half) var(--anim-func) var(--anim-half);
    }
    
    #menu-icon span:nth-child(1) {
      
        margin-top: var(--margin-bottom);
    }
    
    #menu-icon span:nth-child(2) {
     
    }
    
    #menu-icon span:nth-child(3) {
      
        margin-top: var(--margin-top);
    }
    
    .animate #menu-icon span:nth-child(1) {
        
        margin-top: 0;
        rotate: -45deg;
    }
    
    .animate #menu-icon span:nth-child(2) {
        width:0;
      
        rotate: 360deg;
    }
    
    .animate #menu-icon span:nth-child(3) {
        
        margin-top:0;
        rotate: 45deg;
    }
    <!DOCTYPE html>
    <html>
      <head>
        <title>Hello, World!</title>
        <link rel="stylesheet" href="styles.css" />
        <script>
                function toggleState(target) {
                    target.classList.toggle("animate");
                }
            </script>
      </head>
       <body>
            <div id = "menu-btn" onclick = "toggleState(this)">
                <div id = "menu-icon" class = "">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
            </div>
        </body>
    </html>

    Then in the CSS, target the individual spans using span:nth-child(1), span:nth-child(2), and so on. This way, the #menu-icon itself isn’t part of the animation and there won’t be any unexpected rotations. To fix the offset during rotation, you can utilize the transform: translate property.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search