JavaScript Toggle Multiple SVG Elements
January 01, 2022
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");
}
...