javascriptcsssvg

Transitioning SVG Line Position


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.


Solution

  • 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.

    const handleHamburgerClick = event => {
      let button = event.target.closest('.hm-button');
      button.classList.toggle('open');
    };
    
    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;
    }
    
    [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>