The ultimate getElementsByClassName, anno 2008

Published on Tuesday, May 27th, 2008

Two and a half years ago, I released the first version of getElementsByClassName. With how web browsers has evolved since, I thought I’d release a real ultimate version, dated 2008. :-)

Native web browser support

Safari 3.1 has native getElmentsByClassName support, and upcoming Firefox 3 and Opera 9.5 will have it too. It only leaves out, you’ve guessed it, Internet Explorer.

The necessity of this script

Living in a world where Microsoft is halting every possible way the web could evolve, this means that you will need a custom script for this for years to come. Also, the native version returns just a nodelist, as opposed to an array, which may somewhat limit your options to work with it.

New features

When I rewrote the script (download new version), there were a number of factors I wanted to include to make it the best script available for this task:

  • Utilize native getElementsByClassName support if it’s available.
  • Utilize native XPath support if it’s available.
  • Support multiple class names in the same call, in any order specified.
  • Return an actual array to work with, instead of just the native nodelists.

The way it works is that it detects the available native support, and then re-uses that for consecutive calls. This will lead top optimal performance for the end user experience. Please take a look at the demo page, which consists of a hefty 130 KB raw HTML, to compare execution times between call types, web browsers and platforms.

The script

The script should support basically any web browser being used today, and also has support back till IE 5.5. This is how the script now looks like:

/*
	Developed by Robert Nyman, http://www.robertnyman.com
	Code/licensing: http://code.google.com/p/getelementsbyclassname/
*/
var getElementsByClassName = function (className, tag, elm){
	if (document.getElementsByClassName) {
		getElementsByClassName = function (className, tag, elm) {
			elm = elm || document;
			var elements = elm.getElementsByClassName(className),
				nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,
				returnElements = [],
				current;
			for(var i=0, il=elements.length; i<il; i+=1){
				current = elements[i];
				if(!nodeName || nodeName.test(current.nodeName)) {
					returnElements.push(current);
				}
			}
			return returnElements;
		};
	}
	else if (document.evaluate) {
		getElementsByClassName = function (className, tag, elm) {
			tag = tag || "*";
			elm = elm || document;
			var classes = className.split(" "),
				classesToCheck = "",
				xhtmlNamespace = "http://www.w3.org/1999/xhtml",
				namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
				returnElements = [],
				elements,
				node;
			for(var j=0, jl=classes.length; j<jl; j+=1){
				classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";
			}
			try	{
				elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);
			}
			catch (e) {
				elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);
			}
			while ((node = elements.iterateNext())) {
				returnElements.push(node);
			}
			return returnElements;
		};
	}
	else {
		getElementsByClassName = function (className, tag, elm) {
			tag = tag || "*";
			elm = elm || document;
			var classes = className.split(" "),
				classesToCheck = [],
				elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
				current,
				returnElements = [],
				match;
			for(var k=0, kl=classes.length; k<kl; k+=1){
				classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));
			}
			for(var l=0, ll=elements.length; l<ll; l+=1){
				current = elements[l];
				match = false;
				for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
					match = classesToCheck[m].test(current.className);
					if (!match) {
						break;
					}
				}
				if (match) {
					returnElements.push(current);
				}
			}
			return returnElements;
		};
	}
	return getElementsByClassName(className, tag, elm);
};

How to call it and parameters

Parameters

className
One or several class names, separated by space. Multiple class names demands that each match have all of the classes specified. Mandatory.
tag
Specifies the tag name of the elements to match. Optional.
elm
Reference to a DOM element to look amongst its children for matches. Recommended for better performance in larger documents. Optional.

Call examples

To get all elements in the document with a “info-links” class.
getElementsByClassName("info-links");
To get all div elements within the element named “container”, with a “col” class.
getElementsByClassName("col", "div", document.getElementById("container"));
To get all elements within in the document with a “click-me” and a “sure-thang” class.
getElementsByClassName("click-me sure-thang");

Download the new getElementsByClassName

The new version is available for download right as we speak, so by all means, download it, take it for a spin, and experience selecting elements through their class names cross-browser, cross-platform without having to worry about any differences. :-)

54 comments

  • The Ultimate getElementsByClassName - Robert’s talk - Web development and Internet trends
    May 27th, 2008 at 15:59

    [...] completely rewritten version of getElementsByClassName has been released, taking into account all new available web bowser features and possibilities, to [...]

  • Bram.us » The ultimate getElementsByClassName, anno 2008
    May 27th, 2008 at 16:00

    [...] Update to a classic javascript function (anno 2005, if not earlier) in order to take advantage of the native getElementsByClassName function or XPATH (if available, with fallback mechanisms in place) Spread the word! [...]

  • kimblim
    May 27th, 2008 at 16:50

    Nice.

    In your first example, shouldn’t it just say “..get all elements..” instead of “..get all a elements..”, though?

  • Milo
    May 27th, 2008 at 17:42

    Why not use the Selectors API when it’s available (e.g in IE8)?

  • Robert Nyman - author
    May 27th, 2008 at 19:26

    kimblim,

    Absolutely right, thanks! Updated the post.

    Milo,

    I did consider that a while, but the day IE 8 finally gets out (and I’m fairly sure we’re not talking this year), I’m convinced that they will include native getElementsByClassName; if for no other reason, due to popular demand.

    With DOMAssistant, we rely on the Selectors API in web browsers that support it, and have XPath and other fallbacks when either the web browser fails, or if it’s a non-standard selector.

    With this function, though, it is targeted solely at getting elements by class, and using the Selector API seems a bit overkill.

    However, if IE 8 for no reason won’t support getElementsByClassName, then naturally, using Selectors will be the solution here.

  • Tino Zijdel
    May 27th, 2008 at 21:10

    This looks a lot like the revision I made almost a year ago (getElementsByClassName re-re-re-visited) except that I did not add namespace support (never had the need for it) and don’t support the usecase where you want to have elements that match more than 1 particular class (never had the need for that either and only once I had the need to do the opposite - selecting elements that match either of a list of classes - which the native gEBCN doesn’t support).
    I think partly as a result mine might be a bit faster…

  • Robert Nyman - author
    May 27th, 2008 at 21:49

    Tino,

    Ah, it looks very similar. :-)

    I thought that namespace support and especially multiple classes would make it a bit more complete. With just one class, the performance hit shouldn’t be too bad. As you can see in my simple test page, the overall times are definitely good, in my opinion.

    Thinking so alike, perhaps we should write something together one day. :-)

  • Tino Zijdel
    May 27th, 2008 at 22:34

    Robert: I’d might be interesting to exchange thoughts someday yeah, contact me anytime :)

    By the way, for my employer I use a version of gEBCN that supports regular expression syntax for className (it does fallback to the ‘generic’ method in that case, but that’s fast enough in most cases). Downside is that you can’t do branching for the definition of the function itself.

  • Robert Nyman - author
    May 27th, 2008 at 22:47

    Tino,

    Yeah, such specific tweaks are definitely options in certain environments. Personally, though, I think that when one starts to make it fairly complex, the CSS selector approach with different JavaScript libraries seems more suitable and more tested.

    Also, I do hope we get to talk and meet one day. We’ll just have to see what the future holds. :-)

  • Tino Zijdel
    May 28th, 2008 at 0:11

    fwiw: [a-z]+ doesn’t match tags like h[1-6]. Besides, the whole regexp matching in the loop seems useless when ‘tag’ isn’t given or equals ‘*’

    In your ultimate fallback it might be worth to branch when classesToCheck.length == 1 and first check for equality on .className which saves a (more expensive) regexp call in most cases ;)

  • Harmen Janssen
    May 28th, 2008 at 9:31

    Looks very complete, Robert!
    I’ve used a combination of a custom Array.contains() method and element.className.split (' ') to look for classes.
    Something like:
    element.className.split (' ').contains ('searchClass');

    I haven’t dabbled in Xpath and the Selectors API yet actually, but from all I hear about it, I think it’s about time I start learning :)

  • Eric Meyer
    May 28th, 2008 at 16:01

    Sweet. Thanks, Robert! One thing I’m wondering and wasn’t clear to me from reading the JS (’cause I’m a JS n00b): does this version support the several-class getElementsByClassName(document, "div", ["col", "left"]); pattern of the older script, or no? Though I guess if it does the new pattern would be getElementsByClassName(["col", "left"], “div”, document);.

  • Robert Nyman - author
    May 28th, 2008 at 19:57

    Tino,

    Ah, thanks for spotting the heading thing. And yes, the superfluous check when a tag isn’t sent in has been removed too.

    However, with the fallback for IE, a regular expression is still necessary, since even though just one class name is sent in, the element might contain multiple ones, so it’s necessary for checking and comparing.

    I’ve issued a new release, 1.0.1, with the above fixes, and have also updated the post above.

    Harmen,

    Thanks! Yes, the Selectors API and XPath is definitely something to dig into. :-)

    Eric,

    Thank you!
    It is almost as you mention it, but as opposed from the old version (and in line with the web browser native implementation) it doesn’t need any array anymore, just the desired class names separated by spaces. Like, with your code:

    getElementsByClassName("col left", "div", document);

    Also, they don’t have to be specified in that order on the actual HTML element either, so it will always work as long as they’re present at all.

  • Grant Palin
    May 28th, 2008 at 22:42

    Nicely done Robert. Way to push the envelope with current and upcoming browser support for the native version.

  • Robert Nyman - author
    May 28th, 2008 at 22:45

    Grant,

    Thank you! :-)

  • Eric Meyer
    May 29th, 2008 at 3:15

    Ah, okay. I guess I misunderstood how that syntax worked with the old plugin– I thought it was an OR, not an AND as in traditional CSS. So, cool. Thanks again! You rock, sir.

  • Robert Nyman - author
    May 29th, 2008 at 8:15

    Eric,

    Thanks a lot! :-)
    I’ve always been a great fan of your CSS work and it has made my coding skills grow considerably, so any way I can give something back, I’m just happy to help!

  • Aldrik Dunbar
    May 31st, 2008 at 19:09

    You could tidy up a bit by self invoking and return the supported function (rather when redeclaring and recalling the script). Alternatively you could of course just scrap the wrapping the if statement all together (as every function costs performance, stylistic preferences take a back seat on my javascript).

    For looping through NodeLists and arrays…
    var i = 0;
    while (current = elements[i++]) {
    }

    Is faster and IMO easy to read than…
    for(var l=0, ll=elements.length; l<ll; l+=1) {
    current = elements[l];
    }

    Also array[array.length]=”foo”; runs faster than array.push("bar"); and would have the small plus of adding IE5 support to you script.

    Since your going all out to make the “ultimate” version you may want to add the uses of XML.selectNodes for when IE is working with XML.

  • Robert Nyman - author
    May 31st, 2008 at 19:36

    Aldrik,

    Thanks for the input.

    The cost if if checking and applying is only run the first time the function is called, and it has then learned for consecutive calls which way to do it is the best way for that certain web browser/environment.

    With looping, I’m very reluctant to add variable assigning to expression checking, and it is also something that goes against the advice of JSLint.

    About scrapping push for adding it to the length of the array, it’s something I thought of, both for performance and IE 5.0 support, so thanks for pointing that out. It will be in the next release!

    Like the idea about selectNodes, and I never really even thought about support in XML documents.

    Also, I just have to say that the name ultimate, at least on my behalf, is mostly ironic, since I believe that no function or method can truly ever become ultimate for all needs and scenarios. :-)

  • Aldrik Dunbar
    May 31st, 2008 at 20:43

    I’m aware that the statement runs only once, it’s just that it could be written a bit more elegantly.
    var getElementsByClassName = function (className, tag, elm){
    if (document.getElementsByClassName) {
    getElementsByClassName = function (className, tag, elm) {
    //...
    }
    return getElementsByClassName(className, tag, elm);
    };

    vs
    var getElementsByClassName = function (){
    if (document.getElementsByClassName) {
    return function (className, tag, elm) {
    //...
    }
    }();

    vs
    var getElementsByClassName;
    if (document.getElementsByClassName) {
    getElementsByClassName = function (className, tag, elm) {
    //...
    }

    As for the ++ (and --) that is one of the few things I disagree with Crockford about (the other is wether one should extend JavaScripts base Object). But if you wish to fellow blindly, you could of course do the slightly less efficient and elegant…
    var i = -1;
    while (current = elements[(i+=1)]) {
    //…
    }

  • Weekly Links #3 | GrantPalin.com
    June 2nd, 2008 at 3:18

    [...] The ultimate getElementsByClassName, anno 2008 - Robert Nyman updates the useful script to include support for the native functions in Safari 3.1 and the upcoming Firefox 3 and Opera 9.5. Very nice. [...]

  • Robert Nyman - author
    June 2nd, 2008 at 19:46

    Aldrik,

    Well, what’s elegant is just in the eye of the beholder, right? :-)
    With Crockford and ++, I’m on the same page as you.

    And no, I’m not following blindly, I agree on some things while I disagree on others.

  • Anthony Ettinger
    June 3rd, 2008 at 10:56

    I’m not sure word-boundary is a good test for your regex, other than that it looks good.

    var elem = elem || document;

    So simple :)

  • Robert Nyman - author
    June 4th, 2008 at 19:36

    Anthony,

    Ah, but that’s only for checking actual node/tag names, which can only contain regular characters. In the script checking for class names, it instead checks for spaces and start/end of string, since word boundaries isn’t complete there.

    And thanks! :-)

  • Summer break - Robert’s talk - Web development and Internet trends
    June 13th, 2008 at 0:19

    [...] The ultimate getElementsByClassName, anno 2008 [...]

  • the emotional pumpkin » Ultimate getElementsByClassName update
    June 13th, 2008 at 17:42

    [...] Nyman has posted an updated version of his ultimate getElementsByClassName. It takes advantage of the native getElementsByClassName support in Safari 3, Firefox 3 and Opera [...]

  • micke
    June 18th, 2008 at 12:16

    I used this function in a project at work and it works great! Thanks Robert!

  • Robert Nyman - author
    June 18th, 2008 at 12:24

    micke,

    Great! :-)

  • Marcy Sutton
    June 24th, 2008 at 20:23

    Are there any examples of this in use? I am trying to use it in conjunction with PHP to get/set classes and I’m having some trouble.

  • Paul Hempsall
    July 2nd, 2008 at 11:18

    Thanks heaps Robert - this script was just what I needed for my Intranet project.

    I owe you a beer!

  • Nejoom
    July 4th, 2008 at 15:21

    Hi thanks for this.

    I found this one surfing the web, it is very efficient, you might be able to adapt it to your method signature.

    Regards:

    //@http://gathering.tweakers.net/forum/list_messages/1025018
    var getElementsByClassName = function (className)
    {
    var classes = className.split(” “);
    var r = [];
    for(var j=0; j -1)
    {
    s = [document.documentElement || document.body], i = 0;

    do
    {
    e = s[i];

    while (e)
    {
    if (e.nodeType == 1)
    {
    if (e.className && re.test(e.className)) r[l++] = e;

    s[i++] = e.firstChild;
    }

    e = e.nextSibling;
    }
    }
    while (i–);
    }
    else
    {
    s = document.getElementsByTagName(’*'), i = s.length;

    while (i–)
    {
    e = s[i];
    if (e.className && re.test(e.className)) r[l++] = e;
    }
    }

    }
    return r.length;
    }
    //@http://gathering.tweakers.net/forum/list_messages/1025018

  • sanish joseph
    July 9th, 2008 at 5:13

    actually am using ie7 this code is not at all useful for me. somebody please help me. i just solved my problem by using getelementsbytag() method.

  • Anth
    July 17th, 2008 at 10:51

    Thanks a lot !!!!!!!!!!!

  • John Goodwin
    July 18th, 2008 at 12:38

    Hi Robert, thanks for sharing.

    I realise the people above are only trying to assist with improvements, but I think it can be quite demoralising when all you seem to receive is suggestions for improvement or criticism, rather than recognition for what you have succeeded in doing.

    So, on behalf of all those who have found this script useful, helpful or just interesting;

    Cheers!! :-)

    John.

  • Hello World « 01 Web
    July 29th, 2008 at 2:24

    [...] The ultimate getElementsByClassName [...]

  • getElementByClass ? - Page 2 - DesignersTalk
    July 31st, 2008 at 23:34

    [...] if this is an old thread, could still be of use to someone The ultimate getElementsByClassName, anno 2008 - Robert’s talk - Web development and Internet … Your Favourite getElementsByClassName - Snook.ca __________________ http://www.benshu.ch // Evolving [...]

  • Vijay Krishna Ramesh
    August 9th, 2008 at 20:22

    This seems strange, and I’m assuming I’m overlooking something simple, but anyhow - I can’t get the call to work in an onclick in FF3:

    <a href="javascript:;" onclick="someMatches = getElementsByClassName('someClass'); alert(someMatches.length);">test</a>

    However, it does work if I do it in a window.onload (like in your demo page - http://www.robertnyman.com/test/getelementsbyclassname/test.htm). I’ve figured out a way around this, by simply adding a caller function to the end of the script:

    getElementsByClassNameCaller = function(className,tag,elm){
    return getElementsByClassName(className,tag,elm);
    }

    I’m hoping/assuming that I’m just doing something wrong, and that this shouldn’t be needed (in IE, the straight call works fine, it is only in FF3 that I’ve been running into it returning 0 items always) - but if not, is this missing just because you normally only use this function in a window.onload? Or am I overlooking something here?

  • red
    August 12th, 2008 at 3:39

    Robert,

    Just a note on your exasperation with some critical posters who seemed to be picking your work apart without thought of a little - nice job, kudos - praise in passing.

    You might think of it this way; if people didn’t find it valuable and worthy, they wouldn’t have come back with their ‘my trick is better’ stuff, it wouldn’t have been worth the time. I know, it’s a back-handed form of respect. Some add their two-cents because they do think they contribute an improvement. Some are just trying to say, “Oh yeah, I can do it better.” N’importe. Either way, they’ve acknowledged the value and quality of your work. Translate that subtext into the praise it really is and bask in it - much better than feeling you’re not being validated or appreciated.

    Ps. judging from your banner photo, you might like the work of Robert Newport at http://www.doctorobertsart.com/ Oh,& I hope you’re enjoying the time with the kids during your parental leave. Suck it up while you can, it passes so quickly…. best, red.

  • Robert Nyman - author
    August 18th, 2008 at 21:53

    Marcy,

    You need to provide more info for me to be able to help you.

    Paul,

    Great! :-)

    Nejoom,

    Absolutely, if you want to.

    sanish,

    You need to provide more info for me to be able to help you.

    Anth,

    You’re welcome!

    John Goodwin, red,

    Thank you!
    I appreciate the compliments, and I do like people giving feedback and tips; I mean, after all, that’s how it becomes even better and other people will surely be able to find things I’ve missed.

    The way I see it, if the input is something I really have to change I’ll update the code. Otherwise, the code is released under such a license that anyone can change it to their liking. :-)

    And red, thanks for the picture tips!

    Vijay Krishna Ramesh,

    Can’t tell what the problem is, but in general, you should never use inline event handlers, and especially not the javascript: protocol. Please read more in AJAX, JavaScript and Accessibility
    .

  • Patrick McGovern
    August 29th, 2008 at 16:53

    Works great in FF3 - having issues in IE6.

    Getting an ‘Object doesnt support this property or method’ error on this line:
    nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,

    Any ideas on how I can circumvent this?

    Thanks,
    -=Patrick=-

  • Robert Nyman - author
    August 29th, 2008 at 21:00

    Patrick,

    First, that line should never be executed, since it checks for native support for document.getElementsByClassName in the web browser, which certainly doesn’t exist in IE 6.

    I’m guessing that you have probably already extended the document in some way, and that that triggers the error. If I take the getElementsByClassName code here and try it out in a clean page in IE 6 and IE 7 I get no errors.

    Please try that as well and see if the problem persists.

  • Mazza
    September 5th, 2008 at 3:06

    Robert,

    First off, like the new site design! :) Secondly, so I am having an issue here with the getElementsByClassName with only IE7 (havn’t tested with IE6); IE7 reports (with debugging enabled) that “Microsoft JScript runtime error: Object doesn’t support this property or method” and highlights the var classes = className.split(" "), line that occurs in the last else statement. This baffles me cause I use getElementsByClassName in another function within the same script and it runs without any errors. Can this function only be used once per page/script?

  • Robert Nyman - author
    September 5th, 2008 at 8:49

    Mazza,

    Thanks!

    Hmm, sounds weird. Please post a link to an example, or e-mail me, and I’ll let you know if I see something weird.

  • Tom Oakes
    September 15th, 2008 at 21:35

    Thanks! This cut my script time from ~25 seconds to ~6.

  • Robert Nyman - author
    September 15th, 2008 at 21:47

    Tom,

    Great! :-)

  • Lori
    September 22nd, 2008 at 21:31

    I love this function! Am I correct to conclude that if elem is specified, and tag isn’t we should pass “” or null for tag instead of the old “*”? I noticed that I was running into a invalid regExp error in FF3 with tag = “*” (nodeName = (tag)? new RegExp(”\\b” + tag + “\\b”, “i”) : null,) since tag was evaluating as true — sorry it wasn’t too clear to me in the current documentation since I was use to using “*” with your previous version.

    Thanks again for the function!

  • Robert Nyman - author
    September 22nd, 2008 at 22:25

    Lori,

    I’m glad that you like it!
    Actually, it’s a minor flaw which was recently brought to my notice. "*" won’t work, like you say, just pass in null to be completely safe. Like this:

    getElementsByClassName("desired-class", null, document.body);

    I’ll fix this in the next release.

  • Função getElementsByClassName - Versão 2008 » Pinceladas da Web - Reflexões sobre XHTML, CSS, PHP e WebStandards
    September 28th, 2008 at 15:08

    [...] agora, Robert Nyman a melhorou adicionando novas [...]

  • Mazza
    October 1st, 2008 at 21:00

    Hey Robert,

    As a follow-up to my post and our email correspondence, I looked more into the issue and I found out the root of the problem is that I am using your code on MediaWiki 1.11… looks like the folks of MediaWiki integrated either an old version or a hacked version of your getElementsByClassName into their wikibits.js file

    Figured I would post this as a comment instead of email for anyone else that may think about adding this to their MediaWiki! :D

  • Robert Nyman - author
    October 1st, 2008 at 21:11

    Mazza,

    Interesting. Hopefully MediaWiki will update their code. If not, please alert them. :-)

  • The Turtle
    October 17th, 2008 at 15:42

    Robert: thanks for this. It saved us from a sticky problem brought on by confused user requirements…

    Turtle

  • Nightfalcon
    November 8th, 2008 at 11:55

    Hi Robert,

    i’m using your fabulous script but am getting the same error like Mazza on IE7 when calling it like this:


    function hideall()
    {
    var boxnr = 0;
    submenuboxes = getElementsByClassName(document, 'navigator');

    while (submenuboxes[boxnr])
    {
    submenuboxes[boxnr].style.display = ‘none’;
    boxnr++;
    }
    }

    I’m hiding all div-containers with the class ‘navigator’ via onfocus-eventhandling and then view the active submenu-divcontainer ([...] onfocus=”hideall(); displayactivebox(’id’, ‘block’)”[...])

    This works on all browsers except IE7, you got any idea to help me through this problem?
    The submenus are neccessary, you know? ;-)

  • Robert Nyman - author
    November 10th, 2008 at 20:02

    Nightfalcon,

    You’re specifying the parameters in the wrong order. The first one should be classname, the second tag name (optional) and the third parent element (optional). Like this:

    getElementsByClassName('navigator', null, document);

  • The WHATWG Blog » Blog Archive » The Road to HTML 5: getElementsByClassName()
    November 11th, 2008 at 20:01

    [...] it natively, you will need a wrapper script. There are many such scripts; I myself am partial to Robert Nyman’s Ultimate GetElementsByClassName. It uses the native getElementsByClassName() method in modern browsers that support it, then falls [...]

Share your thoughts:

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> . If you want to display code examples, please remember to write &lt; for < and &gt; for >.

Comment preview

Top results