Применение «ненавязчивого» JavaScript

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

Принципы «ненавязчивой» рекламы

Итак, как лучше организовывать размещение рекламы на веб-страницах для того, чтобы доставить посетителям сайтов минимум неудобств? Поскольку большинство выводов последуют из анализа техник «ненавязчивого» JavaScript, то раздел озаглавлен именно таким образом. Речь пойдет о клиентской оптимизации использования рекламы на сайтах.

Как было продемонстрировано в исследованиях 2007-2008 годов, большая часть задержек при загрузке страницы у обычного пользователя приходится на долю рекламы, подключаемой, в основном, через JavaScript. Далее будут рассмотрены основные типы использования рекламы на сайтах и предложены способы (в большинстве своем опробованные на практике) для разгона ее загрузки.

Можно спросить: зачем нам это? Разве разработчики баннерообменных систем, контекстной рекламы и других сложных клиент-серверных приложений не подумали уже за нас о возможных последствиях? Подумали, но можно подумать и дальше. Конвертация показов рекламы в клики/покупки напрямую зависит от общего удобства использования сайтом. А оно, в свою очередь, значительно ухудшается при обширном применении различных рекламных сетей.

Именно поэтому клиентская оптимизация вызовов рекламы конвертируется во вполне реальное повышение прибыли компании от своего веб-сайта (или же целой их сети). Если вы уже задумывались над тем, почему ваши сайты так медленно грузятся из-за обилия рекламы, или проектировали сеть учета показов-переходов, тогда следующие мысли будут действительно полезны. Ниже изложены все ключевые моменты, которыми стоит руководствоваться в этих случаях.

document.write против innerHTML

Контекстная реклама, пожалуй, является одним из главных «тормозов» при загрузке страницы (при прочих равных условиях), ибо активно применяет document.write, который «морозит» загрузку до получения всех необходимых JavaScript-файлов с внешних серверов. Естественным предположением было бы использовать вместо него document.write innerHTML Принцип первый: при проектировании рекламных вызовов используйте innerHTML или script.src (последний подразумевает подключение внешнего JavaScript-файла путем создания соответствующего дополнительного узла в head после загрузки страницы, техника более подробно описана в начале главы). Идеальным является подход, когда для оценки эффективности рекламы не применяется клиентская логика (все показы и переходы отслеживаются по серверным логам).

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

Принцип второй: вставляйте рекламный код максимально близко к концу страницы.

Контекстная реклама

Основными игроками на рынке контекстной рекламы на данный момент являются Яндекс.Директ, Google AdSense и Бегун. Google поступает наиболее практично: в результате вызова скрипта вставляется iframe, который дальше уже загружает все рекламное объявление. Поскольку исходные файлы рекламных скриптов расположены на одной из самых доступных и быстрых CDN в мире, то скорость отображения таких объявлений на клиенте впечатляет.

С Яндексом ситуация похуже. Мало того, что выполняется document.write содержимого рекламных объявлений в основное DOM-дерево, к тому же, загружается порядка 5 дополнительных файлов в виде узлов текущего документа (в частности, это счетчики и файл стилей). Все это не самым лучшим образом сказывается на быстродействии. Преобразовать в данном случае вызов Яндекс.Директа к виду innerHTML не удается (однако вполне вероятно, что разработчики со стороны Яндекса в ближайшее время изменят JavaScript-код, и такая возможность появится).

Бегун в этом плане приятно удивил: он предоставляет (слабо документированное, но все же) API для множественных вставок рекламных объявлений на страницу при помощи innerHTML. Для этого всего лишь нужно выставить в качестве JavaScript-переменных:

begun_multispan=1,begun_spans=[{‘span_id’:’ad’,’limit’:7,’width’:230}]

В данном случае подключается множественный показ рекламных объявлений (begun_multispan=1), далее задается, в каком месте и в каком количестве их показывать. Это происходит через массив begun_spans, где для span_id назначается идентификатор блока, в который будет вставлены объявления после загрузки, limit указывает на их количество в данном блоке, а width просто описывает ширину рекламного блока (в пикселах). Таким образом, можно вставить код Бегуна в самый низ страницы и максимально ускорить ее загрузку.

В случае Бегуна в клиентский браузер в минимальном варианте загружается всего 2 файла (отвечающий за логику и содержащий сами объявления), что также добавляет чести данной системе контекстных объявлений.

В качестве альтернативы можно рассмотреть размещение практически всего кода на собственном сайте, а подгружать только необходимую контекстную составляющую (хотя в правилах рекламных сетей это и запрещается, но других кардинальных способов ускорить загрузку и сжать скрипты, которые (у Бегуна, например) отдаются в обычном виде, наверное, не существует).

TopLine, Pop-Up, Pop-Under и RichMedia

В стандартных рекламных сетях сейчас превалируют три формата показа объявлений на странице: TopLine, Pop-Under и RichMedia. Последние два весьма дружественны к техникам «ненавязчивого», ибо подключаются только после полной загрузки страницы (хотя такая реклама, возможно, будет слишком раздражающей, чтобы ее использовать на нормальных сайтах). TopLine отличается тем, что должен быть вставлен в самом начале HTML-документа и, таким образом, максимально замедлит его загрузку.

Поскольку TopLine мало чем отличается от стандартных баннеров, то посетители будут довольно лояльны к его использованию. Однако, как же нам исправить ситуацию с замедлением загрузки? Так же, как и для контекстной рекламы: переместить вызов document.write в innerHTML (или в appendChild). Что и было успешно проделано. Исходный код модифицированного варианта слишком простой, чтобы приводить его здесь. Однако стандартный код вызова может быть замечательно заменен DOM- эквивалентом, который срабатывал по комбинированному событию window.onload и вставлял в заранее подготовленное место все необходимые элементы.

Принцип третий: используются заранее подготовленные места для рекламных объявлений. Если заранее (через стили) назначить размеры тем областям, где будет показана реклама (это несложно сделать, ибо почти всегда известны точные размеры баннеров и текстовых блоков), то посетители будут испытывать гораздо меньше дискомфорта при заходе на странице сайта. Экран не будет «дергаться» при загрузке, а реклама будет появляться постепенно, заполняя строго отведенное ей место.

Внутренние рекламные сети

На некоторых веб-страницах, использующих внутренние системы показа рекламы, вставка объявлений выполняется через iframe (в общем случае – наиболее быстрый способ), иногда через document.write (иногда даже каскадный, когда с помощью одного document.write вставляется скрипт, в котором содержится другой и т.д.). Последний способ может достаточно замедлить загрузку страницы, если звеньев в цепочки вставок много или же они расположены на медленных серверах.

И только на небольшом проценте сайтов действует наиболее быстрый подход: логика рекламных показов рассчитывалась на сервере, а на клиент отдавался уже полностью статичный код. При разработке внутренней рекламной системы самым оптимальным будет именно такой метод.

Принцип четвертый: создавайте рекламные объявления на сервере. Большая (если не вся) логика может быть вынесена на сервере безо всяких потерь функциональности. Определение страны, языка, браузера, источника перехода – все это можно установить еще до того, как страница будет выдана пользователю. Все, что ему нужно, – это лишь итоговые картинки или мультимедийные файлы. Зачем устраивать сеть распределенных вычислений из пользовательских машин, если все можно сделать на мощном сервере, специально для этого предназначенном?

Идеальная архитектура рекламной сети

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

1. Создание внутреннего хранилища объявлений. Этот этап присутствует во всех современных баннерообменных сетях, ведь нужно как-то упорядочить весь рекламный материал по категориям перед назначением его на места показа.

2. Создание каталога рекламных мест. Этот этап тоже обычно проходится, но не всегда явно. Каждый рекламный блок может быть откручен только в нескольких соответствующих местах (например, на странице есть 3 возможных варианта для вывода баннера: 240×240, 240×720 и 120×800). Каждое рекламное место должно быть прикреплено к ряду страниц, на которых оно присутствует.

3. Логика показа рекламных объявлений. После первых двух шагов необходимо задать правила, согласно которым те или иные объявления будут выводиться на соответствующих страницах. Это также обычно осуществляется, однако, далее этого шага дело, как правило, не двигается – ведь можно с JavaScript-вызова просто передать все необходимые параметры выделенному скрипту, который сам рассчитает, что отдать клиенту.

4. Настройка серверной стороны. На самом деле, вместо клиентской логики должен быть разработан серверный модуль, который обеспечит тот же самый функционал, но без дополнительных запросов к серверу. Ведь все (или почти все) данные, как было упомянуто выше, у нас уже есть – так зачем нам дополнительная нагрузка на пользователя?

5. Настройка статистики. Этот момент является, пожалуй, наиболее ключевым во всей схеме. Ведь при вызове внешнего скрипта мы, фактически, не будем думать о статистике – она собирается автоматически сторонним приложением. Если мы разрабатываем внутреннее решение, то все данные о произведенных показах должны собираться либо самим модулем, который эти показы осуществляет, либо (что более предпочтительно) собираться на основе логов запросов к рекламным файлам (например, именно таким образом организованы счетчики посещаемости).

6. Решение коллизий с кэшированием. Для высоконагруженных проектов, где в полной мере применяется кэширование, немаловажным будет задуматься над тем, как разрешить конфликты рекламных показов с занесением страницы в серверный (или даже клиентский) кэш. Для этого нужно будет либо кэшировать страницу отдельно по модулям (т.е. рекламный блок вообще не кэшировать), либо класть в кэш все возможные варианты страницы, а потом показывать только тот, который удовлетворяет поставленным условиям. Разумеется, это проблема возникает только для проектов с большой посещаемостью (ее решение, в любом случае, должно прозрачно следовать из архитектуры такого проекта, а не являться дополнительной головной болью для серверных разработчиков).

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

Разгоняем счетчики: от мифов к реальности

Давайте рассмотрим теперь, что собой представляет код JavaScript-счетчика. Обычно (в 99% случаев) он «вытаскивает» из клиентского окружения набор параметров: URL текущей страницы; URL страницы, с который перешли на текущую; браузер; ОС и т.д. Затем они все передаются на сервер статистики. Все дополнительные возможности счетчиков связаны с обеспечением максимальной точности передаваемой информации (кроссбраузерность, фактически). Наиболее мощные (Omniture, Google Analytics) используют еще и собственные переменные и события, чтобы усилить маркетинговую составляющую.

Но сейчас речь не об этом. Как собранные на клиенте данные попадают на сервер статистики? Все очень просто: в документе создается уникальный элемент, в URL которого «зашиваются» все необходимые значения (обычно в качестве GET-параметров). URL этот ведет, как можно догадаться, на сервер статистики, где данные кладутся в базу и каким-то образом показываются в администраторском интерфейсе.

Как же создается этот самый «уникальный» элемент? Так сложилось, что наиболее простым транспортным средством для данных стала картинка. Обычный однопиксельный GIF-файл (сейчас, в эпоху CSS-верстки, это, пожалуй, единственное его применение) отдается сервером в ответ на URL с параметрами от клиента.

Разбираем по косточкам

Нам нужно гарантировать загрузку внешнего JavaScript-файла «ненавязчивым» образом, при этом обеспечить запрос на сервер статистики (создание картинки со специальными параметрами). В случае Google Analytics все будет очень тривиально, ибо картинка уже создается через new Image(1,1). Однако большинство счетчиков (Рунета и не только) оперируют document.write, и если такая конструкция отработает после создания основного документа, то браузер просто создаст новый, в который запишет требуемый результат. Для пользователя это выльется в совершенно пустую страницу в браузере.

Основная сложность в переносе скриптов статистики в стадию пост-загрузки (по комбинированному событию window.onload, которое описано в начале главы) заключается как раз в изменении вызова картинки, обеспечивающей сбор статистики, на DOM-методы (это может быть не только new Image, но и appendChild). В качестве примера рассмотрим преобразование скрипта статистики для LiveInternet:

document.write(“<img src=’http://counter.yadro.ru/hit;tutu_elec?r”+ escape(document.referrer) +((typeof(screen)==”undefined”)?””:”;s”+screen.width+”*”+screen.height+”*” +(screen.colorDepth?screen.colorDepth:screen.pixelDepth)) +”;u”+escape(document.URL)+”;”+Math.random()+”‘ width=1 height=1 alt=”>”)

Как мы видим, его нельзя просто так перенести в область динамической загрузки. Для этого данный код нужно преобразовать примерно следующим образом:

new Image(1,1).src=’http://counter.yadro.ru/hit;tutu_elec?r” +escape(document.referrer)+((typeof(screen)==”undefined”)?””:”;s” +screen.width+”*”+screen.height+”*” +(screen.colorDepth?screen.colorDepth:screen.pixelDepth)) +”;u”+escape(document.URL)+”;”+Math.random()

Таким образом (все приведенные участки кода – это одна строка, разбитая для удобства чтения), мы просто заменили вызов document.write на new Image(). Это поможет в большинстве случаев. Если у вас ситуация не сложнее уже описанной, то следующие абзацы можно смело пропустить.

А если сложнее?

Не все счетчики одинаково просты. Например, для сбора статистики с помощью того же Google Analytics нам нужно загрузить целую библиотеку – файл urchin.js или ga.js. На наше счастье конкретно в этом скрипте данные уже собираются с помощью создания динамической картинки.

Поэтому все, что нам требуется в том случае, если во внешней библиотеке находится мешающий нам вызов document.write, – это заменить его соответствующим образом. Обычно для этого необходимо изменить сам JavaScript-файл. Не будем далеко ходить за материалом и рассмотрим преобразования на примере Omniture – довольно популярной на Западе библиотеки для сбора статистики.

Сначала нам нужно найти соответствующий участок кода внутри JavaScript-файла. В нашем случае это будет возвращаемая строка, которая затем вписывается в документ:

var s_code=s.t();if(s_code)document.write(s_code)

В коде Omniture достаточно найти соответствующий return

return ‘<im’+’g sr’+’c=” +”\”‘+rs+’\” width=1 height=1 border=0 alt=\”\”>’

И заменить его на следующий код (заметим, что для src картинки берется переменная rs)

return ‘new Image(1,1).src=\”‘+rs+’\”‘

Затем мы уже можем заменить вызов и в самом HTML-файле на

var s_code=s.t();if(s_code)eval(s_code)

Для того, чтобы все окончательно заработало, необходимо заменить в файле s_code.js и остальные вызовы document.write (всего их там два). Выглядит это примерно так:

var c=s.t();if(c)s.d.write(c);

s.d.write(‘<im’+’g name=\”‘
+imn+” +”‘\” height=1 width=1 border=0 alt=\”\”>’);

меняем на

var c=s.t();if(c)eval(c);

new Image(1,1).name=imn;

Внимательные читатели уже заметили, что альтернативой document.write в нашем случае стал eval, что, по большому счету, не очень хорошо. Однако здесь не ставится задачи перебирать конкретный скрипт «по косточкам», чтобы избавиться от такого костыля. В некоторых случаях стоит ограничиться просто уверенностью, что вся остальная логика останется нетронутой после вмешательств, ибо все изменения касались только отправки собираемых данных на сервер.

Делаем статистику динамической

Итак, мы узнали, как подготовить внешний JavaScript-файл к динамической загрузке. Осталось понять, как теперь это использовать.

Основное преимущество (или недостаток?) Omniture заключается в том, что JavaScript- файл (обычно s_code.js) располагается на нашем сервере. Поэтому ничего не мешает нам его там и заменить. После этого обеспечить динамическую загрузку и вызов счетчика уже не составит труда.

В той ситуации, когда скрипт совсем внешний (Google Analytics), у нас, по большому счету, только 2 выхода:

 

  • Перенести сам скрипт на наш сервер, добавить в него необходимые инициализационные переменные и вызов (помимо самого объявления) функции статистики (для Google Analytics это urchinTracker()). В качестве плюсов можно отметить то, что, в общем случае, скрипт будет загружаться с нашего сервера побыстрее, чем будет устанавливаться новое соединение с www.google- analytics.com и проверяться, что файл не изменился. В качестве минусов – необходимость отслеживать (возможные) изменения скрипта и необходимость отдавать JavaScript-файл с собственного сервера со всеми вытекающими последствиями.
  • Проверять через определенные промежутки времени, загрузилась ли библиотека. Пишется очень простой код, который через каждый 10 мс проверяет, доступна ли необходимая из библиотеки функция. Если да, то она вызывается. В противном случае проверка запускается снова через 10 мс. Плюсы: можно использовать тот же самый скрипт, что и раньше. Минусы: дополнительная (небольшая) нагрузка на клиентский браузер при загрузке. В качестве примера можно рассмотреть следующий код для Goole Analytics.

var _counter_timer = setInterval(function() {
if (urchinTracker) {
urchinTracker();
clearInterval(_counter_timer); 

}, 10);

Видим, что в первом случае у нас загрузка сильно ускоряется (особенно для постоянных посетителей), во втором случае – получается дешево и сердито, зато более надежно (в смысле отсутствия дополнительного вмешательства в исходный код).

Материалы близкой тематики:

Posted in Разгони свой сайт.