Lazy load баннеров

Сегодня из-за очередных проблем с sol.adbureau.net было решено реализовать ленивую загрузку баннеров.
Начальное решение было использовать iframe, но идея была не очень удачной, т. к. могут быть проблемы с подсчётом кликов, да и модные картинки, увеличивающиеся при наведении, будут вести себя странно. А как этот iframe смотрится в разных браузерах — это вообще сказка)
В итоге родилась идея: при генерации страницы размещать в нужных местах, к примеру, пустые дивы с id с нужным префиксом (<div id=»js-advert-place-{num}»></div>), а сами баннеры загружать в футер вот в такую, к примеру, разметку:

<div id="js-all-advert-block" class="all-advert-block">
	<div class="js-advert-block-content" data-number="1">
		{баннер}
	</div>
	<div class="js-advert-block-content" data-number="5">
		{баннер}
	</div>
	<div class="js-advert-block-content" data-number="15">
		{баннер}
	</div>
</div>
{здесь грузится счётчик показов}

После загрузки страницы размещаем все баннеры на нужных местах:

	(function( $ ) {
		function moveAdvert() {
			$( '#js-all-advert-block' ).find( '.js-advert-block-content' ).each( function() {
				var $oldContent = $( this );

			// в ие перемещаем
				if ( $.browser.msie ) {
					$( "#js-advert-place-" + $oldContent.data( "number" ) ).append( $oldContent );
				}
			// в других браузерах убираем лишнее и вставляем заново
				else
				{
					var cleanedContent = $oldContent.html().replace( /document\.write/gi, 'function a__(){}' );

					$( "#js-advert-place-" + $oldContent.data( "number" ) ).html( cleanedContent );
					$oldContent.remove();
				}


			// этот вариант менее хороший - глюки в ие и проблемы с яндекс.директ

			//	var $newContent = $.browser.msie ? $( $oldContent.html() ) : $oldContent.clone( true );

			//// не надо заново создавать скрипты - они заново выполнятся, а там document.write
			//	$newContent.find( "script, link" ).each( function() {
			//		$( this ).remove();
			//	} );

			//// вставляем
			//	$( "#js-advert-place-" + $oldContent.data( "number" ) ).html( $newContent );

			//// на старом месте нужно убить всё, кроме скриптов
			//// (в скриптах могут быть необходимые для правильного подсчёта кликов переменные)
			//	$oldContent.find( '*' ).each( function() {
			//		var $el = $( this );
			//		if ( ! $( 'script, link', $el ).length && this.tagName.toLowerCase() != 'script' && this.tagName.toLowerCase() != 'link' ) {
			//			$el.remove();
			//		}
			//	} );
			} );
		}

		$( document ).ready( function() {
			moveAdvert();
		} );
	}( jQuery ));

В итоге отвалившаяся внешняя баннерная площадка позволяет корректно отображать сайт без лишних задержек.
За рамками осталась работа сервер-сайда, где нужно собрать данные по всем отображаемым на странице баннерам, а потом вывести их все сразу внизу.

UP:
Всё-таки проблем оказалась куча. К примеру, opera 10.10 (хотя у меня 11.52) некорректно понимает тег script при .html( cleanedContent ), поэтому приходится чистить все теги script, но тогда умирает yandex.direct. К сожалению, на проекте нет нормальной возможности отделить директ от остальной рекламы (нет возможности объяснить тем людям, как что нужно настраивать), поэтому этот способ подходит с большими оговорками.
UP2: Наткнулся на гениальное решение — переопределить document.write, потом немного доработал и вот что получилось:

(function ( $ )
{
	// для собирания тега script
	var previousValue = '',
	// для проверки, собрали ли уже полный тег script
		reTestIfScript = new RegExp( '<\/scr' + 'ipt>$', 'i' ),
	// чтобы найти id дива, куда вставлять код соли
		reGetSolId = new RegExp( "sol\\.adbureau\\.net[^>]+aamsz=(\\d+x\\d+)", 'i' ),
	// для проверки, что это относится к яндекс-директу
		reYandexDirect = new RegExp( "an\\.yandex\\.ru", 'i' );

// заменяем стандартные методы
	document.writeln = document.write = function(value)
	{
	// если последовательно пишут в документ для получения тега script (document.write( '<SCR' ); document.write( '<IPT src="...' );)
		try {
			$( value );
		}
		catch ( e )
		{
			var joinedValue = previousValue + value;
		// сли собрали полный тег скрипт, то вставляем его, куда надо
			if ( reTestIfScript.test( joinedValue ) )
			{
				value = joinedValue;
				previousValue = '';
			}
		// если начали писать тег скрипт, продолжаем до сбора всего тега
			else if ( joinedValue.toLowerCase().indexOf( '<sc' ) == 0 )
			{
				previousValue = joinedValue;
				return;
			}
		// непонятно что это - ничего не делаем
			else {
				return;
			}
		}

		var parentNodeId,
			matches;
	// если в строке есть упоминание яндекса - вставляем в яндекс
		if ( reYandexDirect.test( value ) ) {
			parentNodeId = 'adv_media';
		}
	// если в строке есть упоминание соли - ищем id и вставляем в нужный div 
		else if ( matches = value.match( reGetSolId ) ) {
			parentNodeId = 'sol' + matches[1];
		}

	// скрипт вставляем только в нужный элемент
		if ( $( value )[0].tagName == "SCRIPT" && parentNodeId )
		{
			var js = document.createElement( 'SCRIPT' );
			var obj = document.getElementById( parentNodeId ).appendChild( js );

		// подгрузка внешнего скрипта
			if ( $( value ).attr( 'src' ) !== "undefined" ) {
				$( obj ).attr( {type: 'text/javascript', 'src': $( value ).attr( "src" )} );
			}
		// выполнение скрипта
			else
			{
				var reReplaceWrite = new RegExp( '(document\\.write[a-zA-Z]{0,2}\\([^)]+)(\\))', 'ig' );
				eval( $( value ).text().replace( reReplaceWrite, "$`$1, '" + parentNodeId + "'$2$'" ) );
			}
		}
	// стили ставим в head
		else if ( $( value )[0].tagName == "LINK" )
		{
			var js = document.createElement( 'LINK' );
			var obj = document.getElementsByTagName( 'head' )[0].appendChild( js );
			$( obj ).attr( {rel: 'stylesheet', 'href': $( value ).attr( "href" ) , type: 'text/css'} );
		}
		else if ( $( value )[0].tagName == "STYLE" )
		{
			if ( $.browser.msie ) {
				var css = document.createElement( 'STYLE' );
				document.documentElement.firstChild.appendChild( css );
				$( obj ).attr( {type: "text/css"} );
				css.styleSheet.cssText = $( value ).html();
			}
			else {
				$( value ).appendTo( $( "head" ) );
			}
		}
	// любой другой элемент вставляем в DOM, только если поняли, в какой элемент вставлять
		else if (  parentNodeId ) {
			$( value ).appendTo( $( "#" + parentNodeId ) );
		}
	};
}( jQuery ));

Всё бы ничего с этим решением, и даже знает, как собирать тег script из кусочков, но всё-таки мы не можем знать, в какое место DOM должна была производится запись, так что там есть костыли с определением места по контенту, и это решение не универсально.
UP3:
В итоге сделал более универсальное решение для «ленивой» загрузки любого контента. Выкладывать не буду — идеи все те же самые, отличается только серверная часть.

LEAVE A COMMENT