For example I want to find all elements that have computed style position: fixed;
. How to do it without making much load on the CPU ?
Is iterating every getElementsByTagName('*')
and then doing for loop the only way ?
Instead of selecting all (*
) elements, and use getComputedStyle
+ getPropertyValue
, you can follow the following steps:
- Loop through all CSS rules (via
document.styleSheets
[1]) and take the selectors which containsposition: fixed
. - Select all elements whose
style
attributecontains
position: fixed`. Use
document.querySelectorAll
to select all elements which match the selector.- Test whether
window.getComputedStyle(elem, null).getPropertyValue('position')
equalsfixed
to filter elements which are not at a fixed position (possibly overridden through a more specific selector, or!important
). - If it matches, push the element in an array
- Test whether
- At this point, you have an array containing all
position: fixed
elements.
[1]
The external stylesheets have to be located at the same origin, because of the Same origin policy.
Code (small demo: 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
I had to do something similar where I needed to know all fixed position elements and then be able to toggle their visibility based on their position (top or bottom of page).
This object below is my resulting solution, which uses TreeWalker (or the older node traversal for IE) to find elements by their computed style. It is very fast for modern browsers. It could be improved upon, but the recursive traversal methods are well tested and have solid performance.
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');
Your best bet is using DOM traversal, because in most browsers that's done in parallel, so you'll be using the user's cores to their best.
来源:https://stackoverflow.com/questions/8691258/how-to-query-whole-dom-for-elements-matching-some-computed-style-in-pure-js