Как запросить весь DOM для элементов, соответствующих некоторому вычисленному стилю? (в чистом виде) - PullRequest
6 голосов
/ 01 января 2012

Например, я хочу найти все элементы, у которых вычислено стиль position: fixed;. Как это сделать без особой нагрузки на процессор?

Итерация каждого getElementsByTagName('*'), а затем выполнение цикла for единственным способом?

Ответы [ 3 ]

8 голосов
/ 07 января 2012

Вместо выбора всех (*) элементов и использования getComputedStyle + getPropertyValue вы можете выполнить следующие шаги:

  • Перебрать все правила CSS (через document.styleSheets [1] ) и взять селекторы, которые содержат position: fixed.
  • Выбрать все элементы, чей атрибут style contains position: fixed`.
  • Используйте document.querySelectorAll для выбора всех элементов, соответствующих селектору.

    • Проверьте, равняется ли window.getComputedStyle(elem, null).getPropertyValue('position') 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
2 голосов
/ 27 ноября 2014

Мне нужно было сделать что-то похожее, когда мне нужно было знать все элементы с фиксированной позицией, а затем иметь возможность переключать их видимость в зависимости от их положения (вверху или внизу страницы).

Этот объект ниже является моим результирующим решением, которое использует 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');
0 голосов
/ 07 января 2012

Лучше всего использовать обход DOM, потому что в большинстве браузеров это делается параллельно, поэтому вы будете использовать ядра пользователей наилучшим образом.

...