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.

Share Improve this question edited Nov 27, 2013 at 18:11 Diodeus - James MacFarlane 114k33 gold badges163 silver badges180 bronze badges asked Nov 27, 2013 at 17:32 David MulderDavid Mulder 27k11 gold badges56 silver badges118 bronze badges 4
  • 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
Add a ment  | 

3 Answers 3

Reset to default 5 +100

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 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 and after 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: 757ms
    • getPseudoElements: 1071ms
  • Yahoo! UK
    • getPseudoElementsByCssSelectors: 59ms
    • getPseudoElements: 5492ms
  • MSN UK
    • getPseudoElementsByCssSelectors: 341ms
    • getPseudoElements: 12752ms
  • Stack Overflow
    • getPseudoElementsByCssSelectors: 22ms
    • getPseudoElements: 10908ms
  • Gmail
    • getPseudoElementsByCssSelectors: 42910ms
    • getPseudoElements: 11684ms
  • Nicholas Gallagher's pure CSS GUI icons demo
    • getPseudoElementsByCssSelectors: 2761ms
    • getPseudoElements: 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