如何循环遍历所有元素,包括伪元素?我知道可以使用getComputedStyle(element,pseudoEl)
来获取其内容,但是我一直无法找到一种方法来获取页面上的所有伪元素,以便我可以使用前面提到的函数来获取它们的内容/样式。这似乎是一个简单的问题,但我一直找不到解决方案。
如何循环遍历所有元素,包括伪元素?我知道可以使用getComputedStyle(element,pseudoEl)
来获取其内容,但是我一直无法找到一种方法来获取页面上的所有伪元素,以便我可以使用前面提到的函数来获取它们的内容/样式。这似乎是一个简单的问题,但我一直找不到解决方案。
getElementsByTagName("*")
或querySelectorAll("*")
循环遍历所有DOM元素相当容易。然后,我们需要查看每个元素是否有伪元素,正如@zzzzBov所提到的那样。虽然你没有明确提及,但我认为你最感兴趣的是:before
和:after
伪元素。因此,我们利用了一个事实,即必须使用content
属性才能实际使用伪元素:我们只需简单地检查它是否设置。希望这个小脚本能帮到你: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);
}
}
您已经知道如何使用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
伪元素,但很容易将其调整为包括其他伪元素,甚至是可配置列表。li[data-separator=","]:after
之类的东西)将被破坏,但我相信我可以通过一些工作来使脚本免受这些问题的影响。性能将根据样式表中的规则数量和与定义伪元素的选择器匹配的元素数量而变化。如果您有大型样式表、相对较小的文档或更高比例的带有伪元素的元素,Max K的解决方案可能更快。
我在几个网站上进行了一些测试,以便了解不同情况下性能差异的情况。下面是在控制台中循环运行每个函数1000次的结果(Chrome 31):
getPseudoElementsByCssSelectors
: 757毫秒getPseudoElements
: 1071毫秒getPseudoElementsByCssSelectors
: 59毫秒getPseudoElements
: 5492毫秒getPseudoElementsByCssSelectors
: 341毫秒getPseudoElements
: 12752毫秒getPseudoElementsByCssSelectors
: 22毫秒getPseudoElements
: 10908毫秒getPseudoElementsByCssSelectors
: 42910毫秒getPseudoElements
: 11684毫秒getPseudoElementsByCssSelectors
: 2761毫秒getPseudoElements
: 948毫秒请注意,Max K的解决方案在最后两个示例中表现优异。我本来预计在Nicholas Gallagher的CSS图标页面上会这样,但没有想到在Gmail上也会这样!原来Gmail有将近110个选择器,指定了5个样式表中的伪元素,总共超过9600个选择器,这使得实际使用的元素数量相形见绌(大约2800个)。
值得注意的是,即使在最慢的情况下,Max的解决方案运行一次仍然不需要超过10ms,考虑到它只有我的四分之一长度,并且没有任何警告,这并不算太糟糕。
Max K分享了一种解决方案,即检查所有元素的计算样式,这是我自己过去一天中一直在使用的临时解决方案。巨大的缺点是性能开销,因为所有元素都会检查不存在的伪元素的计算样式两次(如果有伪元素可用,则我的脚本需要花费两倍的时间来执行)。
无论如何,只是想分享我过去几天一直在使用的稍微更普遍的版本。
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);
});