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.
We can use the document. getElementsByTagName or document. querySelectorAll method to get all elements on a page. Then we can loop through them all with the for-of loop.
You can't add two ::after pseudo-elements to one DOM element. You can however add an ::before additionally.
To get all DOM elements by an attribute, use the querySelectorAll method, e.g. document. querySelectorAll('[class="box"]') . The querySelectorAll method returns a NodeList containing the elements that match the specified selector.
1, an element can only have at most one of any kind of pseudo-element at any time. (This means an element can have both a :before and an :after pseudo-element — it just cannot have more than one of each kind.)
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);
}
}
After some performance testing, my recommendation is:
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.
jsFiddle demo
It would be too much to hope that this approach would account for everything. There are a few things to bear in mind:
before
and after
pseudo-elements, though it would be easy to adapt it to include others, or even a configurable list.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 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):
getPseudoElementsByCssSelectors
: 757msgetPseudoElements
: 1071msgetPseudoElementsByCssSelectors
: 59msgetPseudoElements
: 5492msgetPseudoElementsByCssSelectors
: 341msgetPseudoElements
: 12752msgetPseudoElementsByCssSelectors
: 22msgetPseudoElements
: 10908msgetPseudoElementsByCssSelectors
: 42910msgetPseudoElements
: 11684msgetPseudoElementsByCssSelectors
: 2761msgetPseudoElements
: 948msCode used to test performance
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.
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);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With