108

Is it possible to get all elements with class a or b using getElementsByClassName() only once? I'd prefer vanilla JavaScript.

Pranav C Balan
  • 113,687
  • 23
  • 165
  • 188
Unknown developer
  • 5,414
  • 13
  • 52
  • 100
  • Did not found anything but chaining like parent.getElementsByClassName("Bottom")[0].getElementsByClassName("Left")[0] (using getElementsByClassName). – Jan Apr 06 '23 at 08:21

4 Answers4

170

You can't do it with getElementsByClassName() method instead use querySelectorAll() method with comma separated class selectors.

document.querySelectorAll('.a,.b')
Pranav C Balan
  • 113,687
  • 23
  • 165
  • 188
  • 2
    This is the right answer, but note that the support for IE8 is only for CSS2 selectors, and there is no support for IE <= 7, so in that case you will need to do two selections by class. – zoubida13 May 16 '16 at 13:46
  • 1
    I see your edit but getElementsByClassName won't work either in IE <= 7, I'm posting an alternative answer just to complete this but yours should be voted as best answer by OP. – zoubida13 May 16 '16 at 13:55
  • 1
    @zoubida13: How is `.a,.b` not just two CSS2 selectors? – BoltClock May 17 '16 at 05:13
  • 3
    @zoubida13 : I don't think there is people who still using `IE <= 7` :) ;) – Pranav C Balan May 17 '16 at 05:14
  • 7
    even microsoft doesn't support IE7 anymore. Why companies or devs still do, I can't understand! – sertsedat May 17 '16 at 13:42
  • Isn't `querySelectorAll()` much slower performance wise? – Dustexe Jan 26 '21 at 10:50
43

You can pass more than one class name to getElementsByClassName() by separating them with spaces:

var elems = document.getElementsByClassName("class1 class2 class3");

Now, this differs from the .querySelectorAll(".class1,.class2,.class3") approach in that it applies a conjunction, not a disjunction — "and" instead of "or". Thus

var elems = document.getElementsByClassName("class1 class2 class3");

is like

var elems = document.querySelectorAll(".class1.class2.class3");

Sometimes you want one, sometimes you want the other. It's definitely true that .querySelectorAll() gives you much more flexibility.

Pointy
  • 405,095
  • 59
  • 585
  • 614
9

No, you can't achieve that with only one document.getElementsByClassName() call. That function returns elements which have all of the classes specified in the first argument as a space-separated string.

There are two possible solution. First is to use document.querySelectorAll() instead, which uses CSS selectors.

document.querySelectorAll(".a, .b")

The second solution is to call document.getElementsByClassName() twice, turn the results into arrays using Array.from() and merge them using Array.prototype.concat(). To avoid duplicates (for example when element has both a and b class), you have to create a new Set from that array, and then turn it back to array using Array.from().

const classA = Array.from(document.getElementsByClassName("a"))
     ,classB = Array.from(document.getElementsByClassName("b"))
     ,result = Array.from(new Set(classA.concat(classB)))

See demo below:

console.log("first solution", document.querySelectorAll(".a, .b"))

const classA = Array.from(document.getElementsByClassName("a"))
     ,classB = Array.from(document.getElementsByClassName("b"))
     ,result = Array.from(new Set(classA.concat(classB)))

console.log("second solution", result)
<div class="a"></div>
<div class="b"></div>
<div class="a b"></div>
<div class="c"></div>

Note that the first solution gives an array-like NodeList object, whereas the second gives just an array.

Jed Fox
  • 2,979
  • 5
  • 28
  • 38
Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
  • 3
    It is pointless to use Set, because it is unsupported in those browsers, what do not support querySelectorAll. – Somnium May 16 '16 at 19:51
  • 3
    @Somnium It's not as pointless as it looks like. Babel supports Set, but doesn't support `document.querySelectorAll()` (because it's not part of ECMAScript, but of Web APIs). If you use Babel to compile your code, the second solution will work in all browser. And frankly, I wrote the second solution rather for fun, than as a real solution. – Michał Perłakowski May 16 '16 at 19:59
  • Array.from is generating issue in internet explorer 11. – Kamlesh Dec 27 '19 at 11:57
6

Just to add a bit more support, here is a version that is compatible with older versions of IE and uses pure vanilla js :

function getElementsByClassNameOr(root, classNameString) // classNameString like '.a, .b' don't forget the comma separator
 {
    var arr = [],
    rx = new RegExp('(^|[ \n\r\t\f])' + classNameString + '([ \n\r\t\f]|$)'),
    elements = root.getElementsByTagName("*");

    var elem;

    for (i=0 ; i < elements.length ; i++) {
        elem = elements[i];
        if (rx.test(elem.className)) {
            arr.push(elem);
        }

    }

    return arr; // will contain all the elements that have one of the classes in ClassNameString, root can be document or a div.
}
zoubida13
  • 1,738
  • 12
  • 20
  • 1
    This will be very inefficient. Note that by supporting older browsers, one is targeting `much slower` browsers. Performance is in this situation critical, I suggest using multiple calls to `Element.getElementsByClassName` and make use of a concatenated array of the results. – Tim May 16 '16 at 15:40
  • 1
    @Tim you are right about performance, although the fact that performance is critical in this situation is entirely down to your interpretation. And your suggestion is neither valid nor detailed. getElementsByClassName is not fully supported in IE < 9. – zoubida13 May 16 '16 at 15:44
  • 1
    @zoubida13 You are completely right. There seems to be no other way than this answer with better performance. – Tim May 16 '16 at 15:48
  • 1
    @Blender I replaced a comma with a semicolon it was indeed a typo yes. – zoubida13 May 16 '16 at 15:56
  • 1
    @zoubida13: Your code assumes that the class names are right next to one another, so it won't work for `class="a b c"` when you search with `'a c'`. – Blender May 16 '16 at 15:57
  • 1
    This code is quite insecure. Class names can take special chars, like `.[]{}()`. You need to escape those first. Also, the performance can be quite bad in no time. Instead of `(^|[ \n\r\t\f])`, use `(?:^|\s)` and instead of `([ \n\r\t\f]|$)`, use `(?:\s|$)`. Also, change `for (i=0 ; i < elements.length ; i++)` into `for (var i = 0, length = elements.length; i < length ; i++)`. – Ismael Miguel May 16 '16 at 17:22
  • 1
    Doesn't `classNameString` need to be something like `(?:a|b)` instead of `.a, .b`??? – Bergi May 16 '16 at 20:56