Как бы я перебирал все элементы, включая элементы 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, соответствующих псевдоэлементам, определенным в документе, без прокрутки и проверки каждого элемента.
Предостережения
Было бы слишком надеяться, что этот подход будет объяснять все. Следует иметь в виду несколько вещей:
- Он возвращает только псевдонимы
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);
});