Мне нужно было сделать что-то похожее, когда мне нужно было знать все элементы с фиксированной позицией, а затем иметь возможность переключать их видимость в зависимости от их положения (вверху или внизу страницы).
Этот объект ниже является моим результирующим решением, которое использует 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');