Как перебрать все элементы на странице, включая псевдоэлементы?

Как бы я перебирал все элементы, включая элементы psuedo? Я знаю, что могу использовать getComputedStyle(element,pseudoEl) для получения его содержимого, однако мне не удалось найти способ получить все псевдоэлементы на странице, чтобы я мог использовать вышеупомянутую функцию для получения их контента/стиля. Кажется, это простая проблема, но не удалось найти какое-либо решение.

Ответ 1

Вы на правильном пути. Зацикливание по всем элементам DOM довольно легко, используя либо getElementsByTagName("*"), либо querySelectorAll("*"). И тогда мы должны смотреть на каждый из этих элементов, есть ли у них псевдоэлемент. Что все делают как @zzzzBov.

Хотя вы не указали это явно, но я предполагаю, что псевдо-элементы :before и :after - это те, кого вы в основном интересуете. Поэтому мы воспользуемся тем, что вам нужно использовать content свойство фактически использовать псевдоэлементы: мы просто просто проверяем, установлен ли он или нет. Надеюсь, этот маленький script поможет вам:

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);
    }
}

Ответ 2

После некоторого тестирования производительности моя рекомендация:

  • В большинстве случаев используйте решение Max K. Производительность достаточно хороша в большинстве случаев, она надежна и работает на уровне менее 15 ЛОС (моя составляет около 70).
  • Используйте нижеприведенное решение, если вам действительно нужно выжать каждую миллисекунду, и вы знаете (потому что вы ее протестировали), что она быстрее.

Быстрое решение (обычно)

Вы уже знаете, как получить список каждого элемента в документе, используя document.querySelectorAll('*'). Это работает в большинстве случаев, но для больших документов, в которых только несколько элементов имеют псевдоэлементы, они могут быть медленными.

В этой ситуации мы можем подойти к проблеме под другим углом. Сначала мы просматриваем таблицы стилей документов и строим словарь селекторов, связанных с псевдо-элементами before или after:

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);
}

Мы можем использовать этот словарь для получения массива псевдоэлементов, определенных на элементах, соответствующих этим селекторам:

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;
            }, [])
        );
    }, []);
}

Наконец, небольшая служебная функция, которую я использовал для преобразования объектов типа массива, возвращаемых большинством методов DOM, в фактические массивы:

Array.from = Array.from || function(arrayish) {
    return [].slice.call(arrayish);
};

Et voilà! Вызов getPseudoElements() возвращает массив объявлений стиля CSS, соответствующих псевдоэлементам, определенным в документе, без прокрутки и проверки каждого элемента.

демон jsFiddle

Предостережения

Было бы слишком надеяться, что этот подход будет объяснять все. Следует иметь в виду несколько вещей:

  • Он возвращает только псевдонимы before и after, хотя было бы легко адаптировать их для включения других или даже настраиваемого списка.
  • Таблицы таблиц междоменного пространства без соответствующих заголовков CORS будут вызывать исключение (исключение) безопасности и не будут включены.
  • Будут подняты только псевдоэлементы, созданные в вашем CSS; те, которые настроены непосредственно в JavaScript, не будут.
  • Некоторые нечетные селектора (например, что-то вроде li[data-separator=","]:after) будут искалечены, хотя я уверен, что смогу пропустить script для большинства из них с небольшой работой.

Производительность

Производительность будет зависеть от количества правил в ваших таблицах стилей и количества элементов, соответствующих селекторам, которые определяют псевдоэлемент. Если у вас большие таблицы стилей, относительно небольшие документы или более высокая доля элементов с псевдоэлементами, решение может быть быстрее.

Я проверил это немного на нескольких сайтах, чтобы дать представление о разнице в производительности при разных обстоятельствах. Ниже приведены результаты запуска каждой функции в цикле 1000 раз в консоли (Chrome 31):

  • Google (Великобритания)
    • getPseudoElementsByCssSelectors: 757мс
    • getPseudoElements: 1071ms
  • Yahoo! Великобритания
    • getPseudoElementsByCssSelectors: 59ms
    • getPseudoElements: 5492мс
  • MSN UK
    • getPseudoElementsByCssSelectors: 341ms
    • getPseudoElements: 12752мс
  • Переполнение стека
    • getPseudoElementsByCssSelectors: 22 мс
    • getPseudoElements: 10908ms
  • Gmail
    • getPseudoElementsByCssSelectors: 42910ms
    • getPseudoElements: 11684ms
  • Николас Галлахер, чистый графический интерфейс CSS GUI
    • getPseudoElementsByCssSelectors: 2761ms
    • getPseudoElements: 948ms

Код, используемый для тестирования производительности

Обратите внимание на то, что решение Max K превосходит мои майки в последних двух примерах. Я ждал его с помощью страницы значков CSS Николаса Галлахера, но не Gmail! Оказывается, Gmail имеет в общей сложности почти 110 селекторов, которые определяют псевдоэлементы более чем в 5 таблицах стилей, в общей сложности объединены более 9 600 селекторов, что затмевает количество используемых фактических элементов (примерно 2800).

Стоит отметить, что даже в самом медленном случае решение Max по-прежнему занимает не более 10 мс для запуска один раз, что неплохо, учитывая, что это четверть длины моей и не имеет никаких оговорок.

Ответ 3

Max K разделяет решение, в котором все элементы проверяются на их вычисленный стиль, который является концепцией, которую я использую как временное решение самостоятельно в течение последнего дня. Недостатком HUGE является служебная работа с производительностью, так как все элементы проверяются на вычисленный стиль несуществующих псевдоэлементов два раза (мой script занимает в два раза больше времени, чтобы выполнить случайность наличия доступных псевдоэлементов).

В любом случае, просто подумал, что я поделюсь чуть более обобщенной версией, которую я использовал последние пару дней.

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,computedStyle){
    console.log(arguments);
});