How would I loop over all elements including psuedo elements? I am aware I can use getComputedStyle(element,pseudoEl)
to get its content, however I have been unable to find a way to get all pseudo elements on the page so that I can use the afore mentioned function to get their content/styling. Seems to be a simple problem, but have been unable to find any solution.

- 112,730
- 33
- 157
- 176

- 26,123
- 9
- 51
- 114
-
2My understanding is that **all** elements have pseudo elements implicitly, and that their default styles make them invisible. – zzzzBov Nov 27 '13 at 18:15
-
1Take a look at this answer: http://stackoverflow.com/a/5041526/722135 – Babblo Nov 27 '13 at 18:16
-
Well, you can at least select the styling through the aforementioned API which 'proves' that question wrong already~ + that question is 2 years old, so I was secretly hoping some new API's might have been released. – David Mulder Nov 27 '13 at 18:35
-
Good question. I really hate the nature of pseudo element; Why shouldn't they be visible to the dom? If I was to write the spec, I would have them actually insert content into the dom. – bjb568 Nov 27 '13 at 18:47
3 Answers
You are on the right track. Looping over all DOM elements is fairly easy using either getElementsByTagName("*")
or querySelectorAll("*")
. And then we have to look at each of those elements whether they have a pseudo-element. Which all do as @zzzzBov mentioned.
Although you didn't mention it explicitly, but I assume the :before
and :after
pseudo elements are those you are mostly interested in. So we take the advantage of the fact that you have to use the content
property to actually use pseudo elements: We just simply check whether it's set or not. Hopefully this little script helps you:
var allElements = document.getElementsByTagName("*");
for (var i=0, max=allElements.length; i < max; i++) {
var before = window.getComputedStyle(allElements[i], ':before');
var after = window.getComputedStyle(allElements[i], ':after');
if(before.content){
// found :before
console.log(before.content);
}
if(after.content){
// found :after
console.log(after.content);
}
}

- 1,505
- 14
- 19
-
Haha, I had literally the same code already lying around, wait, let me publish that one as well as it's slightly differently structured so it might be useful to some people. Still am hoping to see some less expensive solution. – David Mulder Nov 28 '13 at 23:13
-
Btw, I will be awarding a bounty on this question as well in the search for better answers, but otherwise you can look forward to some 50 bonus rep :P – David Mulder Nov 28 '13 at 23:22
-
not all tags can have before/after content, input for example. you could reduce the overhead of hitting all tags by not hitting all tags, but only a list of "contentable" tags. even better would be a short custom whitelist like querySelectorAll("div,a,i"). you can also start in body/#main/form instead of root to skip meta, title, link, etc. just sayin... – dandavis Dec 03 '13 at 19:08
-
I noticed this question the other day and remembered that I never felt quite right about getting the bounty when, if I ever had to do this and the performance was acceptable, I'd probably just use *your* answer. Let's fix that. :) – Jordan Gray Jan 09 '15 at 02:13
After some performance testing, my recommendation is:
- In most circumstances, use Max K's solution. The performance is good enough in most circumstances, it's reliable and it clocks in at under 15 LOC (mine is about 70).
- Use the solution below if you really need to squeeze out every little millisecond and you know (because you've actually tested it) that it's faster.
The (usually) faster solution
You already know how to get a list of every element in the document using document.querySelectorAll('*')
. This works in most circumstances, but for larger documents in which only a few elements have pseudo-elements it can be slow.
In this situation, we can approach the problem from a different angle. First, we loop through the document stylesheets and construct a dictionary of selectors associated with before
or after
pseudo-elements:
function getPseudoElementSelectors() {
var matchPseudoSelector = /:{1,2}(after|before)/,
found = { before: [], after: [] };
if (!(document.styleSheets && document.styleSheets.length)) return found;
return Array.from(document.styleSheets)
.reduce(function(pseudoSelectors, sheet) {
try {
if (!sheet.cssRules) return pseudoSelectors;
// Get an array of all individual selectors.
var ruleSelectors = Array.from(sheet.cssRules)
.reduce(function(selectors, rule) {
return (rule && rule.selectorText)
? selectors.concat(rule.selectorText.split(','))
: selectors;
}, []);
// Construct a dictionary of rules with pseudo-elements.
var rulePseudoSelectors = ruleSelectors.reduce(function(selectors, selector) {
// Check if this selector has a pseudo-element.
if (matchPseudoSelector.test(selector)) {
var pseudoElement = matchPseudoSelector.exec(selector)[1],
cleanSelector = selector.replace(matchPseudoSelector, '').trim();
selectors[pseudoElement].push(cleanSelector);
}
return selectors;
}, { before: [], after: [] });
pseudoSelectors.before = pseudoSelectors.before.concat(rulePseudoSelectors.before);
pseudoSelectors.after = pseudoSelectors.after.concat(rulePseudoSelectors.after);
// Quietly handle errors from accessing cross-origin stylesheets.
} catch (e) { if (console && console.warn) console.warn(e); }
return pseudoSelectors;
}, found);
}
We can use this dictionary to get an array of pseudo-elements defined on elements matching those selectors:
function getPseudoElements() {
var selectors = getPseudoElementSelectors(),
names = ['before', 'after']
return names.reduce(function(pseudoElements, name) {
if (!selectors[name].length) return pseudoElements;
var selector = selectors[name].join(','),
elements = Array.from(document.querySelectorAll(selector));
return pseudoElements.concat(
elements.reduce(function(withContent, el) {
var pseudo = getComputedStyle(el, name);
// Add to array if element has content defined.
return (pseudo.content.length)
? withContent.concat(pseudo)
: withContent;
}, [])
);
}, []);
}
Finally, a little utility function I used to convert the array-like objects returned by most DOM methods into actual arrays:
Array.from = Array.from || function(arrayish) {
return [].slice.call(arrayish);
};
Et voilà! Calling getPseudoElements()
returns an array of CSS style declarations corresponding to pseudo-elements defined in the document without looping through and checking every element.
Caveats
It would be too much to hope that this approach would account for everything. There are a few things to bear in mind:
- It only returns the
before
andafter
pseudo-elements, though it would be easy to adapt it to include others, or even a configurable list. - Cross-domain stylesheets without the appropriate CORS headers will raise a (suppressed) security exception and won't be included.
- Only pseudo-elements set up in your CSS will be picked up; those set up directly in JavaScript won't be.
- Some odd selectors (for example, something like
li[data-separator=","]:after
) will be mangled, though I'm pretty sure I could bulletproof the script against most of these with a little work.
Performance
Performance will vary depending on the number of rules in your stylesheets and the number of elements matching selectors that define a pseudo-element. If you have big stylesheets, relatively small documents or a higher proportion of elements with pseudo-elements, Max K's solution might be faster.
I tested this a little on a few sites to give an idea of the difference in performance under different circumstances. Below are the results of running each function in a loop 1000 times in the console (Chrome 31):
- Google (UK)
getPseudoElementsByCssSelectors
: 757msgetPseudoElements
: 1071ms
- Yahoo! UK
getPseudoElementsByCssSelectors
: 59msgetPseudoElements
: 5492ms
- MSN UK
getPseudoElementsByCssSelectors
: 341msgetPseudoElements
: 12752ms
- Stack Overflow
getPseudoElementsByCssSelectors
: 22msgetPseudoElements
: 10908ms
- Gmail
getPseudoElementsByCssSelectors
: 42910msgetPseudoElements
: 11684ms
- Nicholas Gallagher's pure CSS GUI icons demo
getPseudoElementsByCssSelectors
: 2761msgetPseudoElements
: 948ms
Notice that Max K's solution beats the pants off of mine in the last two examples. I was expecting it with Nicholas Gallagher's CSS icons page, but not Gmail! It turns out that Gmail has a combined total of nearly 110 selectors that specify pseudo-elements over 5 stylesheets with a total of over 9,600 selectors combined, which dwarfs the number of actual elements used (approximately 2,800).
It's worth noting that even in the slowest case, Max's solution still doesn't take much more than 10ms to run once, which isn't bad considering that it's a quarter of the length of mine and has none of the caveats.

- 1
- 1

- 16,306
- 3
- 53
- 69
-
1@DavidMulder I'm pleased that you liked the answer, but I admit that I'm also conflicted about whether I would recommend it over Max's. I looked into the performance in more detail (see update), and there are even particular scenarios in which his would perform better. Given its simplicity and reliability—especially in situations where you're loading CSS from a CDN, as you mentioned—I'd be tempted to use his in any scenario where performance wasn't crucial. – Jordan Gray Dec 04 '13 at 20:53
Max K shared a solution where all elements are checked for their computed style which is a concept I have been using as a temporary solution myself for the last day already. The HUGE disadvantage is the performance overhead as all elements are checked for the computed style of the non exisiting pseudo elements two times(my script is taking twice as long to execute for the off chance that there are pseudo elements available).
Either way, just thought I would share the slightly more generalized version I have been using for the last couple of days
var loopOverAllStyles = function(container,cb){
var hasPseudo = function(el){
var cs;
return {
after: (cs = getComputedStyle(el,"after"))["content"].length ? csa : false,
before: (cs = getComputedStyle(el,"before"))["content"].length ? csb : false
};
}
var allElements = container.querySelectorAll("*");
for(var i=0;i<allElements.length;i++){
cb(allElements[i],"element",getComputedStyle(allElements[i]));
var pcs = hasPseudo(allElements[i]);
if(pcs.after) cb(allElements[i],"after",pcs.after);
if(pcs.before) cb(allElements[i],"before",pcs.before);
}
}
loopOverAllStyles(document,function(el,type,computedStyle){
console.log(arguments);
});

- 26,123
- 9
- 51
- 114