how to query whole DOM for elements matching some computed style ? (in pure js)

老子叫甜甜 提交于 2019-11-29 21:12:35

问题


For example I want to find all elements that have computed style position: fixed;. How to do it without making much load on the CPU ?

Is iterating every getElementsByTagName('*') and then doing for loop the only way ?


回答1:


Instead of selecting all (*) elements, and use getComputedStyle + getPropertyValue, you can follow the following steps:

  • Loop through all CSS rules (via document.styleSheets [1]) and take the selectors which contains position: fixed.
  • Select all elements whose style attributecontainsposition: fixed`.
  • Use document.querySelectorAll to select all elements which match the selector.

    • Test whether window.getComputedStyle(elem, null).getPropertyValue('position') equals fixed to filter elements which are not at a fixed position (possibly overridden through a more specific selector, or !important).
    • If it matches, push the element in an array
  • At this point, you have an array containing all position: fixed elements.

[1] The external stylesheets have to be located at the same origin, because of the Same origin policy.

Code (small demo: http://jsfiddle.net/GtXpw/):

//[style*=..] = attribute selector
var possibilities = ['[style*="position:fixed"],[style*="position: fixed"]'],
    searchFor = /\bposition:\s*fixed;/,
    cssProp = 'position',
    cssValue = 'fixed',
    styles = document.styleSheets,
    i, j, l, rules, rule, elem, res = [];

for (i=0; i<styles.length; i++) {
    rules = styles[i].cssRules;
    l = rules.length;
    for (j=0; j<l; j++) {
        rule = rules[j];
        if (searchFor.test(rule.cssText)) {
            possibilities.push(rule.selectorText);
        }
    }
}
possibilities = possibilities.join(',');
possibilities = document.querySelectorAll(possibilities);
l = possibilities.length;
for (i=0; i<l; i++) {
   elem = possibilities[i];
   // Test whether the element is really position:fixed
   if (window.getComputedStyle(elem, null).getPropertyValue(cssProp) === cssValue) {
       res.push(elem);
   }
}
res; //<-- = Array containing all position:fixed elements



回答2:


I had to do something similar where I needed to know all fixed position elements and then be able to toggle their visibility based on their position (top or bottom of page).

This object below is my resulting solution, which uses TreeWalker (or the older node traversal for IE) to find elements by their computed style. It is very fast for modern browsers. It could be improved upon, but the recursive traversal methods are well tested and have solid performance.

window.fixedElementsTool = {
    data: { 
        error: false,
        method: typeof document.createTreeWalker == 'function' && typeof NodeFilter !== 'undefined' ? 'TreeWalker' : 'NodeTraversal',
        viewport: { width: -1, height: -1 },
        fixedElements: []
    },
    walker: null, 
    reject_tags: ['script','param'],
    skip_tags: ['noscript', 'option'],
    acceptNode: function(node){ 

        var self = window.fixedElementsTool;

        if (self.reject_tags.indexOf(node.tagName.toLowerCase()) > -1){ 
            return self.data.method == 'TreeWalker' ? NodeFilter.FILTER_REJECT : false;
        }
        else if (self.skip_tags.indexOf(node.tagName.toLowerCase()) > -1){ 
            return self.data.method == 'TreeWalker' ? NodeFilter.FILTER_SKIP : false;
        }
        else { 
            return self.data.method == 'TreeWalker' ? NodeFilter.FILTER_ACCEPT : true;
        }
    },
    getStyle: function (el,styleProp){ 
        try{
            //access a computed style property - different from a css property
            if (window.getComputedStyle){ 
                return document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp); 
            }  
            else if (el.currentStyle){
                return el.currentStyle[styleProp];
            }
            else {
                return '';
            }
        }
        catch(e){
            return 'failed';
        }
    },
    init: function(){

        //initially add polyfills for JS functionality not in some browsers
        this.addPolyfills();

        //clear any elements from before
        this.data.fixedElements = [];

        try{

            if (this.data.method == 'TreeWalker'){    
                this.walker = document.createTreeWalker( document.body, NodeFilter.SHOW_ELEMENT, this.acceptNode, false);
                this.traverse();
            }
            else {
                this.traverseIE(document.body);
            }

            this.data.viewport = this.getViewport();

        }
        catch(e){
            //this will show us the browser's error message as an object
            this.data.error = e;
        }
        finally{

            if (this.data.error){

            }
        }

    },
    toggle: function(toggle, type){

        if (this.data.fixedElements.length == 0){ this.init(); }

        switch(type){
            case 'all':
                for (var i=0; i < this.data.fixedElements.length; i++){
                    var item = this.data.fixedElements[i];
                    item.node.style.display = toggle == 'hide' ? 'none' : item.originalDisplay;
                };
                break;
            case 'top':
                for (var i=0; i < this.data.fixedElements.length; i++){
                    var item = this.data.fixedElements[i];

                    //give it 5 pixels or so
                    if (item.rect.top <= 5){
                        item.node.style.display = toggle == 'hide' ? 'none' : item.originalDisplay;
                    }
                };
                break;
            case 'bottom':
                for (var i=0; i < this.data.fixedElements.length; i++){

                    //to get the actual offset FROM the bottom of the viewport
                    //subtract the rect.bottom value from the viewport height
                    var item = this.data.fixedElements[i],
                        offsetBottom =  this.data.viewport.height - item.rect.bottom;

                    //give it 5 pixels or so
                    if (offsetBottom <= 5){
                        item.node.style.display = toggle == 'hide' ? 'none' : item.originalDisplay;
                    }
                };
                break;
        }
    },
    traverse: function(){

        //Main method for traversing and recording properties we want about each dom element
        var position = this.getStyle(this.walker.currentNode,'position');

        if (position == 'fixed'){
            this.data.fixedElements.push({
                node: this.walker.currentNode, 
                rect: this.walker.currentNode.getBoundingClientRect(),
                originalDisplay: this.getStyle(this.walker.currentNode,'display') || ''
            });
        }

        //if true then currentNode points to first child
        if (this.walker.firstChild()){
            this.traverse();
        }

        //if true then currentNode points next sibiling
        if (this.walker.nextSibling()){
            this.traverse();
        }
        else{
            //set back to parent... this is our base case for recursive return
            this.walker.parentNode();
            return;
        }
    },
    traverseIE: function(node){

        //this is our base case
        if (node == null){ return; }

        //only store info for node of type ELEMENT which isn't in the rejected or skipped array of tags
        if (node.nodeType === 1 && this.acceptNode(node)){

            var position = this.getStyle(node,'position');

            if (position == 'fixed'){
                this.data.fixedElements.push({
                    node: node, 
                    rect: node.getBoundingClientRect(),
                    originalDisplay: this.getStyle(node,'display') || ''
                });
            }
        }

        //if true then currentNode points to first child
        if (node.firstChild && this.acceptNode(node)){
            this.traverseIE(node.firstChild);
        }

        //if true then currentNode points next sibiling
        if (node.nextSibling){
            this.traverseIE(node.nextSibling);
        }
    },
    getViewport: function(){
        var viewport = { width: -1, height: -1 };
        if (window.document.documentElement != undefined && window.document.documentElement != null){
            viewport.width = window.document.documentElement.clientWidth;
            viewport.height = window.document.documentElement.clientHeight;
        }
        else if (window.document.body != undefined && window.document.body != null){
            viewport.width = window.document.body.clientWidth;
            viewport.height = window.document.body.clientHeight;
        }
        return viewport;
    },
    addPolyfills: function(){

        //ensure indexOf on arrays
        if (!Array.prototype.indexOf) {
            Array.prototype.indexOf = function (searchElement, fromIndex) {
                if ( this === undefined || this === null ) {
                    throw new TypeError( '"this" is null or not defined' );
                }

                // Hack to convert object.length to a UInt32
                var length = this.length >>> 0; 

                fromIndex = +fromIndex || 0;

                if (Math.abs(fromIndex) === Infinity) {
                    fromIndex = 0;
                }

                if (fromIndex < 0) {
                    fromIndex += length;
                    if (fromIndex < 0) {
                        fromIndex = 0;
                    }
                }

                for (;fromIndex < length; fromIndex++) {
                    if (this[fromIndex] === searchElement) {
                        return fromIndex;
                    }
                }

                return -1;
            };
        }
    }
};

fixedElementsTool.init();
//fixedElementsTool.toggle('hide','all');
//fixedElementsTool.toggle('show','bottom');
//fixedElementsTool.toggle('show','top');



回答3:


Your best bet is using DOM traversal, because in most browsers that's done in parallel, so you'll be using the user's cores to their best.



来源:https://stackoverflow.com/questions/8691258/how-to-query-whole-dom-for-elements-matching-some-computed-style-in-pure-js

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!