Приветствую.
В работу поступила одна задача которая показалась мне интересной. Хотел бы поделиться решением с Хабросообществом.
Задача
Существует скрипт который автоматически подключается к каждой посещаемой пользователем странице, задача скрипта опустить весь контент страницы на N пикселей для отрисовки баннера в верхней части страницы. Основные требования были что бы скрипт был един для всех браузеров, а так же что бы верстка не ломалась. Ниже приведу сам скрипт, и некоторые умозаключения. Если интересно — добро пожаловать под кат.
Решение
На каждую страницу, сразу после открывающегося тега подключается скрипт который и должен проводить смещение контента.
Первой мыслью было немного оптимизировать сервер, и кроме вставки js оборачивать весь контент в слой с относительным позиционированием, который и сдвигать. Но, увы, с этим вариантом возникло много проблем. Вот основные из них:
- CSS, на многих сайтах стили привязываются к тегу body, и приходилось перелопачивать и переназначать огромное количество css, что негативно сказывалось на производительности
- JavaScript, тот же jQuery при инициализации манипулирует конструкцией document.body, и получает элементы относительно его. Лечилось болезнь переназначением document.body = document.getElementById("_NEW_DIV"); Но тут возникала следующая проблема — эта строка должна была отработать первой. Перед остальными скриптами сайта.
- Снятие обработчиков с body. onLoad etc. Это приходилось делать на стороне сервера. Что было не гуд, так как сервер часть мы старались разгрузить по полной.
Кроме этого было еще множество проблем связаных с этим методом. Если будут желающие — распишу подробнее.
Поскольку прошлый метод был отброшен было принято решение зайти с другой стороны.
Скрипт проходился по всему DOM дереву, и смещал нужные элементы.
Логика алгоритма следующая:
- _M$.start() запускается и ждет пока будет загружен весь документ.
- _M$.shift(document.body, 0) начинает рекурсивно проходить по всему документу. первый раз родителем передается document.body, для него выбираются все child. Затем идет проверка на position. Нас интересует position 3-х типов. absolute, fixed, и relative
- Второй аргумент является флагом который указывает нужно ли смещать элементы с absolute позицией.
- В случае если position = relative; мы меняем флаг смещения на 1 и идем глубже в этот элемент. В дальнейшем в нем нас будут интеревовать только child с position fixed
- Если position = absolute и флаг 0 — то идет смещение элемента на нужную высоту. Флаг меняется на 1. Если флаг уже 1 то просто идем глубже
- Если position = fixed то мы всегда смещаем элемент
- На том же этапе когда мы проверяли позицию — мы так же проверяем display. В случае если он является grid (то есть moz-grid, ie-grid и т.д.) и флаг = 0 элементу присваивалось relative позиционирование и шло его смещение
- В конце идет отрисовка слоя в самом верху страницы. С position = static он смещал автоматически все обычные слои
А вот и сам скрипт:
(function(){
_D$ = {
e: {},
g:function(id){
if(typeof _D$.e[id] == "undefined") _D$.e[id] = document.getElementById(id);
return _D$.e[id];
},
c:function(e){
if(typeof e != "undefined" && e.hasChildNodes()) return e.childNodes;
},
s:function(e, s){
if(document.defaultView&&document.defaultView.getComputedStyle){
return document.defaultView.getComputedStyle(e,"").getPropertyValue(s)
}
else if(e.currentStyle){
return e.currentStyle[
s.replace(
/-(w)/g,function(strMatch,p1){
return p1.toLowerCase()
}
)
]
}
return"";
}
}
_B$ = {
w: 600,
h: 100,
insert:function(){
el = document.createElement("div");
el.setAttribute("id", "_HSS_TOP");
el.setAttribute("style", "width: 100% !important; height: "+_B$.h+"px !important; background-color: transparent !important;");
document.body.insertBefore(el, document.body.firstChild);
}
}
_M$ = {
start:function(){
if( document.readyState != "complete")
return setTimeout(arguments.callee, 100);
_M$.shift(document.body, 0);
_B$.insert();
},
shift:function(e, lable){
var e = _D$.c(arguments[0]);
if(typeof e == "undefined") return;
for(var key in e){
if(e[key].nodeType != 1 || e[key].nodeName == "SCRIPT" || e[key].nodeName == "STYLE"){
delete e[key];
continue;
}
var p = _D$.s(e[key], "position"),
l = lable;
if(l == 1 && p != "fixed") p = "";
else{
var d = _D$.s(e[key], "display");
if(d.indexOf("grid") != -1){
e[key].style.position = "relative";
p = "absolute";
}
}
if(p == "absolute" || p == "fixed"){
_M$.shift_e(e[key]);
l = 1;
}else if(p == "relative") l = 1;
_M$.shift(e[key], l);
}
},
shift_e:function(e){
var b = _D$.s(e, "bottom");
if(b != "auto") return;
var t = _D$.s(e, "top");
t += _B$.h;
if(t == "auto") t = 0;
if(t.replace) t = t.replace(/[^0-9-.]+/ig, "")*1;
e.style.top = t+"px";
e.setAttribute("edit", "true");
}
}
_M$.start();
}
)();
P.S. Заранее прошу прощения если что то не так. Уже довольно поздно, топик дописываю из последних сил)
P.P.S. Замечания по грамматическим ошибкам — прошу писать в личку
P.P.P.S. Если у кого то есть идеи по оптимизации алгоритма — с огромнейшим удовольствием выслушаю
Автор: Vendolin