Смещение контента сайта средствами JavaScript

в 7:18, , рубрики: html, javascript, парсинг, метки: , ,

Приветствую.

В работу поступила одна задача которая показалась мне интересной. Хотел бы поделиться решением с Хабросообществом.

Задача

Существует скрипт который автоматически подключается к каждой посещаемой пользователем странице, задача скрипта опустить весь контент страницы на 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js