108

I would like to know how to select all elements with class names "widget" and "hover" and then remove class "hover" from these elements.

I have the following JavaScript code that selects all elements with class "widget" and "hover":

var elements = document.getElementsByClassName('widget hover');
console.log(elements);

This seems to work and outputs something like this (with no errors):

[div#.widget... 

The problem is that if I try to remove the class "hover", I get an error:

var elements = document.getElementsByClassName('widget hover');
console.log(elements);
elements.classList.remove("hover");

This outputs:

[item: function]
length: 0
Uncaught TypeError: Cannot call method 'remove' of undefined 

Can anyone tell me what I'm doing wrong?


Please note that I have it working in jQuery:

$('.widget.hover').removeClass('hover');

... but I'm looking for a solution in pure JavaScript.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Andrew
  • 2,691
  • 6
  • 31
  • 47

9 Answers9

145
var elems = document.querySelectorAll(".widget.hover");

[].forEach.call(elems, function(el) {
    el.classList.remove("hover");
});

You can patch .classList into IE9. Otherwise, you'll need to modify the .className.

var elems = document.querySelectorAll(".widget.hover");

[].forEach.call(elems, function(el) {
    el.className = el.className.replace(/\bhover\b/, "");
});

The .forEach() also needs a patch for IE8, but that's pretty common anyway.

cookie monster
  • 10,671
  • 4
  • 31
  • 45
  • 1
    @Andrew: The `.className.replace()` should be a little more sophisticated if the classes may have other than alpha-numeric characters. For example, if a class could be `hover-me`, the `hover` part could possibly be matched by the regex. Not sure how broad of a solution you need. – cookie monster Mar 08 '14 at 14:50
  • Because you were the first to answer, I'll give it you. – Andrew Mar 08 '14 at 15:00
  • 1
    @Andrew: I wasn't first, but the other answer does need to be fixed. – cookie monster Mar 08 '14 at 15:01
63

It's 2023... keep it simple and just use es6

Times have changed and now the cleanest and most readable way to do this is:

Array.from(document.querySelectorAll('.widget.hover')).forEach(
  (el) => el.classList.remove('hover')
);

If you can't support arrow functions then just convert it like this:

Array.from(document.querySelectorAll('.widget.hover')).forEach(function(el) { 
    el.classList.remove('hover');
});

Additionally if you need to support extremely old browsers then use a polyfil for the forEach and Array.from and move on with your life.

maxshuty
  • 9,708
  • 13
  • 64
  • 77
  • Wouldn't a `querySelectorAll` be a more modern way of doing this? – Qasim Dec 06 '20 at 11:16
  • @Qasim I should have emphasized the point of mine was not how you get the elements, rather how you are looping through and removing them using `Array.from` and `forEach` – maxshuty Dec 06 '20 at 12:35
  • 1
    you could get ride of Array.from: `(document.querySelectorAll("div")).forEach((el) => el.classList.remove("selected"));` – Savrige Jun 30 '21 at 13:27
  • I tried without the "Array.from" and it seems to be working too, please let me know if there are any special purpose for using "Array.from". – Danie Aug 27 '21 at 11:18
  • 2
    @Danie - that would still work, the difference between the two is that one references a `NodeList[]` and the `Array.from` references an `Array[]`, [see this writeup for more information](https://attacomsian.com/blog/javascript-nodelist-vs-array#:~:text=an%20array%20discussion%3A%20a%20NodeList,prototypes%2C%20methods%2C%20and%20properties.). – maxshuty Aug 27 '21 at 16:24
42

Find elements:

var elements = document.getElementsByClassName('widget hover');

Since elements is a live array and reflects all dom changes you can remove all hover classes with a simple while loop:

while(elements.length > 0){
    elements[0].classList.remove('hover');
}
Veikko Karsikko
  • 3,671
  • 1
  • 20
  • 15
11

Elements is an array of DOM objects. You should do something like this:

for (var i = 0; i < elements.length; i++) {
   elements[i].classList.remove('hover');
}

Enumerate the elements collection and for each element inside the collection call the remove method

maxshuty
  • 9,708
  • 13
  • 64
  • 77
nemo
  • 1,675
  • 10
  • 16
  • This works perfectly. I didn't realize that elements would be an array so this makes perfect sense. Is there anything faster that wouldn't have to re-iterate through the entire array? – Andrew Mar 08 '14 at 14:45
  • I believe there isn't anything faster. JQuery probably should do something similar. But don't worry about speed you must have thousands(if not million) of elements in order to see a performance problem – nemo Mar 08 '14 at 14:48
  • 15
    One thing to consider is that in the question, `.getElementsByClassName` was being used to fetch the elements. Because you're removing one of the classes used for the fetch, the element will be removed from the `elements` list, which means the list gets re-indexed, and you end up skipping over some elements. It's safer to iterate in reverse. – cookie monster Mar 08 '14 at 14:55
  • 2
    Based on @cookiemonster's point, here's the recursive version: `for (var i = elements.length-1; i > -1; i--) {}`. – ᴍᴀᴛᴛ ʙᴀᴋᴇʀ Oct 27 '15 at 13:59
  • @MattBaker your version works but not because it is recursive version, it only works because getElementsByClassName returns "live" array so when you go from 0-up as you remove class they disappear from "live" array. When you hide 0th item then 1st item move to 0th position but you continue for cycle from item 1, thus leaving 0th item with class. Going from end solves this issue. – asdjfiasd Oct 13 '18 at 13:38
8

For ES6, this can be done in a few ways with one liners, where you create an array of the elements with the spread operator ..., and remove the class with the map operator:

With querySelectorAll:

[...document.querySelectorAll('.widget')].map(x => x.classList.remove('hover'));

With getElementsByClassName:

[...document.getElementsByClassName('widget')].map(x => x.classList.remove('hover'));

For querySelectorAll, notice the use of .widget instead of widget. An alternative for the spread operator would be to use Array.from like:

Array.from(document.querySelectorAll('.widget')).map(x => x.classList.remove('hover'));
Kurt Van den Branden
  • 11,995
  • 10
  • 76
  • 85
5

This might help:

let allElements = Array.from(document.querySelectorAll('.widget.hover'));
for (let element of allElements) {
  element.classList.remove('hover');
}
maxshuty
  • 9,708
  • 13
  • 64
  • 77
Faiyaz Shaikh
  • 127
  • 3
  • 10
2

I use a simple method. If you always process [0] in the required number of loops, you can apply the process to all.

The HTMLCollection(elements) changes in real time, so put the length in a variable. (l = element.length)

for(var elements = document.getElementsByClassName('widget hover'), i = 0, l = elements.length; l > i; i++) {
  elements[0].classList.remove("hover");
}
maxshuty
  • 9,708
  • 13
  • 64
  • 77
arayutw
  • 41
  • 1
  • 2
1
var elems = document.querySelectorAll(".widget.hover");

 for(let elem of elems){
        elem.classList.remove('hover');
        }
Simon Angatia
  • 688
  • 1
  • 10
  • 16
0

Given worked for me.

document.querySelectorAll(".widget.hover").forEach(obj=>obj.classList.remove("hover"));
pl2ern4
  • 340
  • 2
  • 10
  • 1
    Welcome to Stack Overflow! In order to maintain a high quality of answers, avoid answers that are only code and add explanations. – Max Sep 05 '20 at 18:18