Оптимизация сайта под AdBlock

Сегодня AdBlock является главной проблемой индустрии интернет-рекламы. Число пользователей, блокирующих рекламный контент, постепенно растёт, а прибыль владельцев сайтов снижается. Многочисленные подписки позволяют обладателям расширений значительно увеличить список нежелательных элементов, добавив к нему не только рекламу, но и, например, установленные на странице социальные виджеты или скрипты сбора статистики, что серьезно повышает приватность просмотра и осложняет вовлечение посетителей в процесс интеграции сайта с социальными сетями. Рано или поздно разработчики сталкиваются с вопросом: как проверить наличие AdBlock и минимизировать наносимый им «ущерб»?

Принцип работы блокировщиков рекламы

С технической точки зрения AdBlock, uBlock Origin, AdGuard и другие подобные браузерные расширения (далее по тексту — AdBlock) блокируют не саму рекламу, а соответствующие сетевые запросы: при загрузке страницы каждое соединение сверяется со списком запрещённых и при необходимости отклоняется. В самих блокировщиках это называется сетевым фильтром. Он может содержать как конкретный перечень доменов, например, ad.mail.ru, так и части URL — /ucoz/img/uads/, что позволяет блокировать запросы даже при совпадении по маске.

Случается, что в результате фильтрации сайт теряет нормальную работоспособность или возможностей одного лишь сетевого фильтра не хватает, чтобы заблокировать нежелательный контент. Для этого в блокировщиках предусмотрено скрытие элементов — косметический фильтр, применяющийся путём встраивания в документ правил CSS повышенного приоритета, которые скрывают области с рекламой, как правило, при помощи свойства display или в отдельных ситуациях добавлением атрибута hidden.

Как блокируется реклама через CSS: действие косметического фильтра AdBlock Как блокируется реклама через CSS: действие косметического фильтра AdBlock
Работа косметического фильтра uBlock Origin на примере рекламы ВКонтакте: элемент #ads_left принудительно скрывается встроенным CSS с внушительного размера селектором.

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

Но реклама — далеко не единственный объект фильтрации. Блокирование внешних ресурсов или скрытие элементов зависит от подписок, которые включены в пользовательский AdBlock. Каждая подписка специализируется на определенном типе нежелательного контента и периодически обновляется.

Популярные подписки

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

  1. Реклама — любые элементы станицы, в том числе подключаемые извне ресурсы, в идентификаторах, классах и атрибутах которых встречаются запрещенные фильтром выражения. Обычно, подписка составляется с региональным учетом, например, EasyList+RuAdList.
  2. Элементы социальных сервисов — кнопки «share», виджеты социальных сетей и иные встраиваемые посредством API элементы. Основным списком является Fanboy’s Social Blocking List.
  3. «Раздражающие» элементы — вспомогательный контент страницы: кнопки для подписок, RSS, скроллинга вверх; формы для новостных рассылок; блоки с предупреждениями, информацией о политике приватности и использовании cookies и др. Всё это содержит список Fanboy’s Annoyance List.
  4. Сервисы статистики — счётчики и скрипты анализа посещаемости, отслеживающие поведение пользователя, фактически не являются рекламой, однако рекомендуются к блокировке в целях повышения приватности. Среди таких списков стоит выделить EasyPrivacy и региональный RuAdList Counters.
  5. Элементы «Anti-AdBlock» — баннеры, модальные окна и всплывающие уведомления, которые отображаются для пользователей с включенными расширениями и призывают отключить блокировщик и/или ограничивают функционал сайта. Ответом на них являются списки по типу Anti-AdBlock Filter.
  6. Вредоносный и мошеннический контент — сайты, наносящие вред компьютеру и пользователю, блокируются всеми средствами: поисковыми системами, антивирусами, браузерами. Расширения, направленные на борьбу с рекламой, также предлагают подобные фильтры — например, Malware Domains.
  7. Пользовательские правила блокирования — пользователь может самостоятельно заблокировать определенный сетевой запрос или скрыть элемент на странице, что запишется в отдельный фильтр.

Таким образом, список «кандидатов» на потенциальное скрытие или блокировку существенно увеличивается, и возникает вопрос, как разработчик может учесть эту проблему при вёрстке сайта.

Вёрстка с учётом AdBlock

Периодически обновляемые подписки с огромным числом правил блокирования невозможно досконально проанализировать, и на их основе нельзя составить универсальные рецепты для правильной вёрстки контента, который может попасть под действие фильтров. Самым верным решением будет установка всевозможных подписок и проверка конкретного сайта через логи блокировщика. Ниже приведены общие рекомендации по верстке двух типов наиболее часто встречающегося контента — рекламы и социальных виджетов.

Рекламные блоки

Ввиду того, что косметический фильтр аналогично сетевому также работает по маске и сверяет названия идентификаторов, классов и атрибутов элементов с указанными в списке, независимо от домена могут фильтроваться такие элементы как, например, #adv, .ad-google, .banner125x125, .sponsor-logo, a[href*="banner"] и многие другие, поэтому во избежание искажений вёрстки подобные названия не рекомендуется использовать для обозначения элементов в HTML-разметке и для указания содержимого атрибутов href, src в ссылках, картинках и фреймах: они будут по умолчанию скрыты, даже если разработчик не подразумевал под ними контент рекламного характера.

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

<div class="container" style="width: 460px; height: 120px;">
  <!-- AdBlock скроет дочерний блок через "display: none !important;" -->
  <div id="adv" class="adspace" style="width: 460px; height: 120px;">Ads...</div>
</div>

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

Важно, что AdBlock анализирует и атрибуты элементов, поэтому ссылки, href которых содержит явное указание на рекламу, рекомендуется заменять через сервисы сокращения URL, а содержимое атрибута src у изображений по возможности преобразовывать в base64:

<!-- Плохо -->
<a href="https://adsservice.com/ads/banner/click/">
  <img src="https://adsservice.com/ads/banner.gif" />
</a>

<!-- Хорошо -->
<a href="https://vk.cc/[...]">
  <img src="data:image/gif;base64,[...]" />
</a>

Социальные виджеты

Если продвижение в социальных сервисах является важным направлением развития сайта, нельзя не учитывать фактор доступности элементов страницы, обеспечивающих интеграцию с подобными ресурсами. Речь идёт о привычных кнопках «сохранить», «поставить лайк», «поделиться», виджетах для подписки или вступления в группы, блоках с комментариями и иных элементах подобного рода, встраиваемых на сайт благодаря API.

Практически все популярные «социальные скрипты» блокируются как косметическим, так и сетевым фильтром: упомянутая подписка Fanboy’s Social Blocking List содержит значительное количество названий классов и идентификаторов, которые часто употребляются при вёрстке блоков, включающих социальные виджеты: .b-share, .article__footer-share-title, .connect-icons и многие другие, а также маски для внешних скриптов, например, /fbshare.js, /share42.js, /twitter.js.

AdBlock скрывает блоки с социальными кнопками share AdBlock скрывает блоки с социальными кнопками share
AdBlock является причиной скрытия не только рекламы, но и блоков с социальными кнопками. Косметический фильтр по умолчанию добавляет display: none элементу с классом .article__share.

Чтобы ограничить действия фильтров на подобные элементы, разработчикам следует по возможности отказываться от популярных решений в пользу собственных скриптов (пример создания кнопок share своими руками) и избирательнее использовать названия для обозначения контента в разметке. То же касается и изображений, используемых, как правило, в качестве социальных иконок:

<!-- Плохо -->
<div id="footer-social-block"></div>
<img src="icons/social/facebook.png" />
<script src="js/share.js"></script>

<!-- Хорошо -->
<div id="footer-scl-block"></div>
<img src="data:image/png;base64,[...]" />
<script src="js/shr.js"></script>

В дополнение рекомендуется ознакомиться с подпиской Fanboy’s Annoyance List, так же содержащей перечень часто употребляемых в вёрстке обозначений для элементов.

Просьба добавить сайт в исключение

Очевидно, что «спасти» контент от блокирования не всегда возможно, и лучшее, что следует предпринять в такой ситуации, — обратиться к пользователю с просьбой исключить сайт из фильтров AdBlock. Для вывода обращения не обязательно использовать JS: достаточно применить простые CSS-техники, о которых пойдет речь ниже. Но сначала стоит рассмотреть вопрос о содержимом и месте появления обращения на странице, так как его главной целью является не только привлечь внимание посетителя, но и получить согласие.

Как правильно попросить пользователя отключить AdBlock?

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

Когда отключение AdBlock является категорически поставленным обязательным условием, это демотивирует и заставляет посетителя искать иные сайты или, если так случается, что других вариантов у него нет, — тратить время на обход такого рода блокировок, вручную скрывая навязчивые элементы со страницы.

Пример правильного вывода просьбы об отключении AdBlock Пример правильного вывода просьбы об отключении AdBlock
Пример правильного отображения просьбы об отключении AdBlock на Хабре. Ссылка на обращение ненавязчива и размещена строго на месте рекламного баннера.

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

Это можно проиллюстрировать на примере просьбы упомянутого IT-сообщества, даже небольшой фрагмент которой отражает уважительное отношение к участникам:

«Надеемся, что ты не воспримешь наше обращение как попытку „поплакаться в жилетку“. Мы хотим растить свою команду, делать новые продукты и проекты, постоянно, каждый день, работать над улучшением существующих. Так сложилось, что только реклама помогает нам в достижении этих целей, поэтому мы стараемся делать её полезной и тематичной. Надеемся, что ты ответишь нам взаимностью и прислушаешься к нашей просьбе».

Обращение на Хабре

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

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

Вывод просьбы отключить AdBlock на CSS

Существует как минимум два простых варианта реализации вывода сообщения с просьбой отключить AdBlock на чистом CSS без использования JavaScript: посредством псевдокласса :empty и наложением двух элементов друг на друга через свойства position и z-index. Первый вариант расчитан скорее на сетевой фильтр, второй — более универсален, так как потенциально учитывает действие и косметического фильтра.

Добавление псевдоэлемента в родительский контейнер

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

<!-- Предполагается без AdBlock -->
<div class="container">
  <div id="adv" class="adspace">Ads...</div> <!-- Элемент сгенерирован рекламным скриптом -->
</div>

<!-- AdBlock работает, скрипт не загрузился и дочерние элементы не добавились -->
<div class="container"></div>
.container:empty::before {
  content: "Пожалуйста, отключите AdBlock!";
}

В примере внешний JS-файл вставляет в блок с атрибутом data-insert новый элемент. Если скрипт не загрузится из-за сетевого фильтра, к элементу .container будут применены стили псевдокласса :empty:

Однако этот способ не учитывает ситуацию, когда рекламный скрипт загрузился, сгенерировал и вставил необходимые элементы в искомый контейнер, но впоследствии они были скрыты косметическим фильтром.

Наложение одного блока на другой

Если рекламный блок не отображается из-за косметического фильтра, т. е. элемент с рекламой внутри принудительно скрывается посредством display: none !important, то псевдокласс :empty станет бесполезен:

<!-- Предполагается без AdBlock -->
<div class="container">
  <div id="adv" class="adspace">Ads...</div> <!-- Элемент сгенерирован рекламным скриптом -->
</div>

<!-- AdBlock работает, реклама загрузилась, но была скрыта -->
<div class="container">
  <div id="adv" class="adspace" style="display: none !important;">Ads...</div> <!-- Элемент каким-либо образом скрывается через CSS -->
</div>

Для разрешения проблемы предлагается создать дополнительный элемент, непосредственно содержащий сообщение с просьбой отключить блокировщик, но поместить его на слой ниже, т. е. позиционировать под рекламным блоком так, чтобы он был виден лишь тогда, когда к визуально перекрывающему его элементу было применено правило display: none. Для этого потребуется провести незамысловатные манипуляции со свойствами position и z-index:

<div class="container">
  <div id="adv" class="adspace">Ads...</div>
  <div class="request">Пожалуйста, отключите AdBlock.</div>
</div>
.container {
  position: relative;
}

.adspace,
.request {
  position: absolute;
  width: 100%;
  height:100%;
}

.adspace {
  z-index: 2; /* рекламный блок располагается на слой «выше»  */
}

.request {
  z-index: 1;
}

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

Единственный недостаток данной техники — абсолютно позиционированный элемент .request будет ограничен областью своего родителя, поэтому указанный код непригоден в случае, если возникает потребность отображать модальное окно по центру экрана и затенение, занимающее весь viewport. Для решения такого рода задач нельзя обойтись без JavaScript.

Как проверить наличие AdBlock через JS?

Механизмы работы сетевого и косметического фильтров открывают разработчикам возможность определить наличие расширений AdBlock посредством JavaScript: состояние загрузки подключенного к странице скрипта отслеживается событиями onload и onerror или возможностью корректного вызова содержащихся в нём методов и функций, а видимость элемента — через анализ примененного к нему CSS-свойства display. На основе этого существует несколько путей обнаружения AdBlock.

Использование внешнего скрипта

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

// ads.js
var adb = "";

Соответственно, при включённом блокировщике рекламы файл ads.js не загрузится, и задекларированная внутри него переменная adb не будет существовать. Проверка выполнения скрипта представляет собой тривиальное условие, которое необходимо поместить ниже подключенного файла:

if(typeof adb === "undefined") {
  // AdBlock включен
} else {
  // AdBlock выключен
}

Второй вариант схож с первым: внешний скрипт ads.js может быть пуст (но не возвращать ошибку 404), а проверка на блокировку заключается в добавлении к нему событий onerror или onload, которые будут обрабатываться отдельной функцией с одним параметром, например, adsLoaded(status):

var adsLoaded = function(status) {
  if(status === false) {
    // AdBlock включен
  } else {
    // AdBlock выключен
  }
}

В HTML-документе подключаемый скрипт должен иметь соответствующие обработчики:

<script src="js/ads.js" onerror="adsLoaded(false)" onload="adsLoaded(true)"></script>

Недостатком указанных способов является создание отдельного файла и дополнительный запрос к серверу.

Проверка видимости элемента

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

window.addEventListener("load", function() {
  if(window.getComputedStyle(document.getElementById("my-adv")).getPropertyValue("display") === "none") {
    // AdBlock включен
  } else {
    // AdBlock выключен
  }
);

Это наиболее оптимальный способ для проверки блокировки не только рекламы, но и любого другого контента.

Функции рекламных скриптов

Рекламные скрипты имеют собственные методы и функции, которые не могут быть вызваны, когда внешний файл оказывается недоступен. Например, Google Adsense создаёт объект google_jobrunner и встраивает рекламу через элементы <ins> с классом .adsbygoogle:

document.addEventListener("load", function() {
  if(typeof window.google_jobrunner === "undefined" || document.querySelector("ins.adsbygoogle").innerHTML.replace(/s/g, "").length === 0) {
    // AdBlock включен, скрипт Adsense заблокирован
  }
});

Проверка на AdBlock с определением подписок

Проанализировав, какие именно элементы отфильтрованы блокировщиком, можно сделать предположение о наличии соответствующих видов подписок у пользователя. На уменьшение точности определения влияют два фактора — пользовательский список фильтров и большое число «официальных» подписок, правила блокирования которых нередко пересекаются между собой.

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

<!-- Реклама: класс взят из «EasyList» -->
<div class="ad_box"></div>

<!-- Блок «поделиться»: класс взят из «Fanboy’s Social Blocking List» -->
<div class="share_box"></div>

<!-- Форма для оформления подписки: класс взят из «Fanboy's Annoyance List» -->
<div class="subscribe-form"></div>

<!-- Счётчик посещаемости: атрибут взят из «RuAdList Counters» -->
<img src="top.mail.ru/counter?" />

<!-- Модальное окно «Anti-AdBlock»: класс взят из «Adblock Warning Removal List» -->
<div class="adb-enabled"></div>

<!-- Клик с функцией fileice(): класс взят из «Spam 404» -->
<div onclick="fileice();"></div>

Чтобы не засорять DOM лишним мусором, элементы следует удалять сразу после проверки.

Как видно из примера, функция adbCheck() возвращает объект, поэтому при необходимости можно обратиться только к интересующему в нём свойству:

var result = adbCheck();
result.ads_block; // реклама
result.cnt_block; // счетчики
result.soc_block; // социальные виджеты
result.ang_block; // раздражающие элементы
result.ant_block; // элементы anti-adblock
result.scm_block; // мошеннические сайты

В заключение

Учет действий фильтров AdBlock — это показатель качественной вёрстки сайта и принятие во внимание сегмента посетителей, которые не только не видят рекламы, но и, вполне возможно, не могут использовать социальные виджеты share и не учитываются счетчиками посещений и прочими сервисами статистики. Именно такой широкий спектр объектов фильтрации — от рекламных скриптов до социальных кнопок и отслеживающих элементов — предоставляют сегодня популярные подписки. Проверке на AdBlock стоит уделять особенно пристальное внимание на сайтах, зависящих от доходов с рекламы или активно использующих социальные виджеты. В свою очередь, правильное, тактичное и ненавязчивое обращение к посетителю может серьезно поспособствовать пересмотру его отношения к заблокированному контенту.