No Time Dad

A blog about web development written by a busy dad

JavaScript Toggle Multiple SVG Elements

I was recently working on a mobile menu button for a project that needed to display an arrow pointing down when the menu was closed and arrow pointing up when it was open. Sounds simple enough. Until I started thinking about all of the possible ways to accomplish this. Of which there are many.

And in an effort to procrastinate even more, I decided I needed to try out each possible solution so I could definitively know which one was best. Spoiler alert: there isn’t a “best” solution.

As with most things in web development (or software in general), any solution is usually fine as long as you keep making progress. Below is one approach I like which puts both the up and down svg icon elements on top of each other but only shows one at a time.

<button id="header__menu_button" aria-expanded="false" class="header__menu_button" type="button">
  Menu
  <svg xmlns="http://www.w3.org/2000/svg" class="header__menu_icon" viewBox="0 0 20 20" fill="currentColor">
    <path fill-rule="evenodd"
      d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
      clip-rule="evenodd" />
  </svg>
  <svg xmlns="http://www.w3.org/2000/svg" class="header__menu_icon hide" viewBox="0 0 20 20" fill="currentColor">
    <path fill-rule="evenodd"
      d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z"
      clip-rule="evenodd" />
  </svg>
</button>
.header__menu_button {
  display: flex;
  align-items: center;
}

.header__menu_icon {
  width: 18px;
  height: 18px;
}

.hide {
  display: none;
}
document.getElementById("header__menu_button").onclick = () => {
  const menuIcons = document.getElementsByClassName("header__menu_icon");
  for (let i = 0; i < menuIcons.length; i++) {
    menuIcons[i].classList.toggle("hide");
  }
}

The css selector that makes this work is .hide. This selector is only applied to one of the arrow svg icons at a time, thus showing one icon and hiding the other. When the button is clicked, the hide selector is removed from the svg element class list if it exists or added if it doesn’t exist.

.hide {
  display: none;
}

Checking for the .hide selector, as well as adding or removing it requires some JavaScript. But before that can happen, a click event on the button must be captured to tell the browser when to execute the JavaScript.

document.getElementById("header__menu_button").onclick = () => {
  ...
}

There are a few different ways to add JavaScript event listeners, but for this example I chose to add it using document.getElementById("<id>").onclick. I sometimes also create a JavaScript function and add it to the element with the onclick="myFunction(event)" syntax.

Now that the click event on the button is being captured, I can focus on toggling the .hide selectors. Effectively what I’m doing is using JavaScript to toggle multiple elements. Which means I can use the JavaScript getElementsByClassName method, as opposed to the singular JavaScript getElementByClassName method.

const menuIcons = document.getElementsByClassName("header__menu_icon");

The result of calling getElementsByClassName with the header__menu_icon selector is an HTMLCollection containing two items, one for each of the svg icon elements. Once I have both of the svg icon elements, I can then iterate the HTMLCollection and toggle the .hide selector on each one.

An important thing to note about HTMLCollection elements is that they’re similar to arrays but they cannot be looped over using the newer JavaScript forEach method. Instead, the older style for (let i=0 ...) for loop must be used. Then the HTMLCollection containing each svg icon element can be accessed using the index value i.

Each svg icon element has an attribute called classList that is a list of all of the classes applied to the element. In this example, it would contain header__menu_icon and possibly hide. The next step is to figure out if the classList for each element contains the latter selector and add or remove it as needed.

Luckily, classList comes with a handle method called toggle that will do just that. Explicitly checking for the presence of the selector in the list is not required.

...
const menuIcons = document.getElementsByClassName("header__menu_icon");
for (let i = 0; i < menuIcons.length; i++) {
  menuIcons[i].classList.toggle("hide");
}
...