9

例如,我想查找所有具有计算样式的元素position: fixed;。如何在不对 CPU 造成太大负载的情况下做到这一点?

每次迭代getElementsByTagName('*')然后做for循环是唯一的方法吗?

4

3 回答 3

10

您可以按照以下步骤操作,而不是选择所有 ( *) 元素并使用getComputedStyle+ getPropertyValue

  • 遍历所有 CSS 规则(通过[1])并获取包含.document.styleSheets position: fixed

  • style选择其属性包含的所有元素position: fixed

  • 用于document.querySelectorAll选择与选择器匹配的所有元素。

    • 测试window.getComputedStyle(elem, null).getPropertyValue('position')equals是否fixed与不在固定位置的过滤器元素(可能通过更具体的选择器或 覆盖!important)。
    • 如果匹配,则将元素推送到数组中
  • 此时,您有一个包含所有position: fixed元素的数组。

[1]由于同源策略,外部样式表必须位于同一源。

代码(小演示: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
于 2012-01-07T11:24:08.183 回答
3

我必须做类似的事情,我需要知道所有固定位置的元素,然后能够根据它们的位置(页面顶部或底部)切换它们的可见性。

下面的这个对象是我得到的解决方案,它使用 TreeWalker(或 IE 的旧节点遍历)通过计算样式来查找元素。对于现代浏览器来说,速度非常快。它可以改进,但是递归遍历方法经过了很好的测试并且具有可靠的性能。

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');
于 2014-11-27T01:10:54.440 回答
0

最好的办法是使用 DOM 遍历,因为在大多数浏览器中这是并行完成的,因此您将充分利用用户的核心。

于 2012-01-07T10:47:40.077 回答