admin管理员组文章数量:1277297
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.
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.
- 2 My understanding is that all elements have pseudo elements implicitly, and that their default styles make them invisible. – zzzzBov Commented Nov 27, 2013 at 18:15
- 1 Take a look at this answer: stackoverflow./a/5041526/722135 – Babblo Commented Nov 27, 2013 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 Commented Nov 27, 2013 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 Commented Nov 27, 2013 at 18:47
3 Answers
Reset to default 5 +100You 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 remendation 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.
jsFiddle demo
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
Code 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 bined total of nearly 110 selectors that specify pseudo-elements over 5 stylesheets with a total of over 9,600 selectors bined, 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 puted 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 puted 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,putedStyle){
console.log(arguments);
});
本文标签: javascriptHow to loop over all elements on a page including pseudo elementsStack Overflow
版权声明:本文标题:javascript - How to loop over all elements on a page including pseudo elements? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741285607a2370249.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论