skip to Main Content
document.addEventListener("DOMContentLoaded", (event) => {
  const hamburgerButtons = document.querySelectorAll('.hm-button');
  const hamburgerLines = [
    {
      closed: {
        x1: 13,
        x2: 37,
        y1: 13,
        y2: 13,
      },
      open: {
        x1: 13,
        x2: 37,
        y1: 37,
        y2: 13,
      }
    }, {
      closed: {
        x1: 13,
        x2: 37,
        y1: 25,
        y2: 25,
      },
      open: null,
    }, {
      closed: {
        x1: 13,
        x2: 37,
        y1: 37,
        y2: 37,
      },
      open: {
        x1: 13,
        x2: 37,
        y1: 13,
        y2: 37,
      }
    }
  ]

  const handleHamburgerClick = (event) => {
    const button = event.target.closest('.hm-button');
    const lines = button.querySelectorAll('line');
    if (!button.classList.contains('open')) {
      lines.forEach((line, idx) => {
        if (hamburgerLines[idx].open) {
          line.setAttribute('x1', hamburgerLines[idx].open.x1)
          line.setAttribute('y1', hamburgerLines[idx].open.y1)
          line.setAttribute('x2', hamburgerLines[idx].open.x2)
          line.setAttribute('y2', hamburgerLines[idx].open.y2)
        }
      });
      button.classList.add('open');
    } else {
      if (button.classList.contains('open')) {
        lines.forEach((line, idx) => {
          if (hamburgerLines[idx].closed) {
            line.setAttribute('x1', hamburgerLines[idx].closed.x1)
            line.setAttribute('y1', hamburgerLines[idx].closed.y1)
            line.setAttribute('x2', hamburgerLines[idx].closed.x2)
            line.setAttribute('y2', hamburgerLines[idx].closed.y2)
          }
        });
        button.classList.remove('open');
      }
    }
  };

  hamburgerButtons.forEach((button) => {
    button.addEventListener("click", handleHamburgerClick);
  });
});
.hm-button {
  width: 50px;
  height: 50px;
  cursor: pointer;
}

.hm-button.open .hm-line2 {
  stroke: none;
}

.hm-outline {
  x: 2px;
  y: 2px;
  width: 46px;
  height: 46px;
  rx: 4px;
  fill: none;
}

.hm-outline,
[class^="hm-line"] {
stroke-width: 4px;
stroke: black;
}

[class^="hm-line"] {
stroke-linecap: round;
transition: all 0.25s ease;
}
<svg class="hm-button" role="button">
  <rect class="hm-outline" />
  <line class="hm-line1" x1="13" x2="37" y1="13" y2="13" />
  <line class="hm-line2" x1="13" x2="37" y1="25" y2="25" />
  <line class="hm-line3" x1="13" x2="37" y1="37" y2="37" />
</svg>

I’m attempting to create a hamburger menu icon using SVG and its child elements as a way to teach myself, but I’m having trouble transitioning the line positions. The change in position is instant, rather than gradual.

What I’m trying to achieve is, the middle line disappearing, and the left point of the first and third lines swap to form an X when the svg is clicked. When it is clicked again, the lines move back and the middle one reappears. Transition doesn’t seem to work when changing the position of the lines, however.

2

Answers


  1. This is easier to control if you use a path. The d attribute of the path can styled using CSS, so the transition can be transitioning from one path to another.

    The only caveat when using a path is that the two paths need to have the same number of commands. In this case that is not an issue. The line in the middle becomes a dot hidden by the X.

    document.addEventListener("DOMContentLoaded", event => {
      const hamburgerButtons = document.querySelectorAll('.hm-button');
    
      hamburgerButtons.forEach((button) => {
        button.addEventListener("click", handleHamburgerClick);
      });
    });
    
    const handleHamburgerClick = event => {
      let button = event.target.closest('.hm-button');
      button.classList.toggle('open');
    };
    .hm-button {
      width: 50px;
      height: 50px;
      cursor: pointer;
    }
    
    .hm-button.open .hm-line2 {
      stroke: none;
    }
    
    .hm-outline {
      x: 2px;
      y: 2px;
      width: 46px;
      height: 46px;
      rx: 4px;
      fill: none;
    }
    
    .hm-outline,
    [class^="hm-line"] {
      stroke-width: 4px;
      stroke: black;
    }
    
    [class^="hm-line"] {
      stroke-linecap: round;
      transition: all 0.25s ease;
      d: path('M 13 13 L 37 13 M 13 25 L 37 25 M 13 37 L 37 37');
    }
    
    .hm-button.open [class^="hm-line"] {
      d: path('M 13 13 L 37 37 M 25 25 L 25 25 M 13 37 L 37 13');
    }
    <svg class="hm-button" role="button">
      <rect class="hm-outline" />
      <path class="hm-line" d="M 13 13 L 37 13 M 13 25 L 37 25 M 13 37 L 37 37" />
    </svg>
    Login or Signup to reply.
  2. You can do SVG animation with SMIL. Basically you define in the SVG how an element should be animated. In the following example there are two <animate> elements, one for opening and one for closing.

    If you specify an empty begin attribute, the animation will not start unless you define something else or call beginElement() (SVGAnimationElement, section about methods).

    const handleHamburgerClick = event => {
      let button = event.target.closest('.hm-button');
      button.dataset.state = (button.dataset.state == 'close') ? 'open' : 'close';
      let anim = button.querySelector(`animate.${button.dataset.state}`);
      anim.beginElement();
    };
    
    document.addEventListener("DOMContentLoaded", event => {
      const hamburgerButtons = document.querySelectorAll('.hm-button');
    
      hamburgerButtons.forEach((button) => {
        button.addEventListener("click", handleHamburgerClick);
      });
    });
    .hm-button {
      width: 50px;
      height: 50px;
      cursor: pointer;
    }
    
    .hm-button.open .hm-line2 {
      stroke: none;
    }
    
    .hm-outline {
      x: 2px;
      y: 2px;
      width: 46px;
      height: 46px;
      rx: 4px;
      fill: none;
    }
    
    .hm-outline,
    [class^="hm-line"] {
      stroke-width: 4px;
      stroke: black;
    }
    <svg class="hm-button" role="button" data-state="close">
      <rect class="hm-outline" />
      <path class="hm-line" d="M 13 13 L 37 13 M 13 25 L 37 25 M 13 37 L 37 37">
        <animate
          class="open"
          attributeName="d"
          from="M 13 13 L 37 13 M 13 25 L 37 25 M 13 37 L 37 37"
          to="M 13 13 L 37 37 M 25 25 L 25 25 M 13 37 L 37 13"
          dur="250ms" begin="" fill="freeze" />
        <animate
          class="close"
          attributeName="d"
          from="M 13 13 L 37 37 M 25 25 L 25 25 M 13 37 L 37 13"
          to="M 13 13 L 37 13 M 13 25 L 37 25 M 13 37 L 37 37"
          dur="250ms" begin="" fill="freeze" />
      </path>
    </svg>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search