Маркетолог смотрит на отчёт: три четверти посетителей уходят с главной страницы, не дойдя до формы заказа. Причина неизвестна. Просмотр записи сессий ничего не проясняет, потому что смотреть сотни записей нет времени. Тогда накладывают на снимок страницы точки нажатий, и картина становится очевидной: люди упорно нажимают на заголовок, который выглядит как кнопка, но ею не является. Это не гипотеза, это наблюдаемый факт, и получают его дешёвым способом.
Карта плотности изображает значения цветом и известна более века
Способ представления данных, при котором величина показывается цветом, применяется давно. Самый ранний известный пример относится к 1873 году: исследователь вручную раскрасил таблицу социальной статистики по районам города.
Формально различают два основных вида. Первый накладывается на изображение и показывает непрерывное распределение: например, температуру на карте мира. Второй представляет собой сетку ячеек, где каждая ячейка соответствует комбинации двух признаков.
Для анализа интерфейсов применяется первый вид: цвет накладывается на снимок страницы, а плотность цвета показывает, куда чаще всего попадают нажатия или где дольше задерживается взгляд.
Отдельно стоит упомянуть смежную область. Изучение движения глаз началось ещё в девятнадцатом веке, когда исследователь заметил, что чтение идёт не плавно, а короткими остановками. Позже выяснилось, что направление взгляда зависит от задачи, стоящей перед человеком. Один и тот же рисунок рассматривают по-разному в зависимости от того, что ищут.
Это наблюдение важно и для веб-аналитики: карта внимания без знания задачи посетителя говорит меньше, чем кажется.
Три вида карт отвечают на разные вопросы
Первая разновидность это карта нажатий. На снимок страницы наносятся точки в местах кликов. Отвечает на вопрос, куда люди действительно нажимают.
Вторая разновидность это карта движения. Показывает положение курсора в моменты перемещения. Отвечает на вопрос, куда человек смотрит взглядом, наведя указатель.
Третья разновидность это карта прокрутки. Показывает, до какого места страницы доходят посетители. Отвечает на вопрос, видит ли человек важное содержимое.
// сбор данных для карты нажатий
function initClickMap(endpoint) {
const events = [];
document.addEventListener("click", (event) => {
const target = event.target.closest("[data-track]") || event.target;
events.push({
x: event.pageX,
y: event.pageY,
viewportWidth: window.innerWidth,
path: location.pathname,
selector: describe(target),
at: Date.now(),
});
if (events.length >= 20) {
flush();
}
});
function flush() {
if (events.length === 0) return;
const batch = events.splice(0, events.length);
navigator.sendBeacon(endpoint, JSON.stringify(batch));
}
window.addEventListener("pagehide", flush);
}
Ключевая деталь в этом примере это отправка данных при уходе со страницы. Событие скрытия страницы позволяет дослать накопленное без задержки, которую пользователь бы заметил.
Вторая деталь это пакетная отправка. Отправлять каждое нажатие отдельным запросом расточительно и по трафику, и по нагрузке на сервер.
Третья деталь это сохранение относительных координат. Размеры экранов различаются, поэтому точки нужно приводить к общему масштабу, иначе карта получится искажённой.
Ограничения метода заметны на мобильных устройствах
Первое ограничение связано с касаниями. На телефоне палец закрывает место нажатия, и человек ориентируется на приблизительное положение. Точки на карте размазываются.
Второе ограничение это отсутствие курсора. Карта движения на сенсорных устройствах попросту не собирается, потому что указателя нет.
Третье ограничение это разные размеры экранов. Собранные с разных устройств данные нельзя наносить на один снимок без нормализации.
Четвёртое ограничение это динамическое содержимое. Если разметка меняется между сессиями, точки попадают в элементы, которых уже нет.
Пятое ограничение это прокрутка. При нажатии ниже первого экрана координата по вертикали выходит за пределы снимка. Нужно либо наращивать снимок, либо разбивать карту по экранам.
function normalize(point, docHeight) {
return {
// доля от ширины и высоты документа
x: point.x / document.documentElement.scrollWidth,
y: (point.y + window.scrollY) / docHeight,
device: window.innerWidth < 768 ? "узкий" : "широкий",
};
}
Разделение по типу устройства обязательно. Смешивать данные телефонов и настольных машин в одной карте значит получить среднее, которое не описывает ни тех, ни других.
Данные нужно очищать перед построением карты
Сырые точки дают шумную картину. Прежде чем строить изображение, данные приводят в порядок.
Первое: удаляются технические нажатия. Клики по служебным элементам, двойные нажатия, случайные касания в первые доли секунды после загрузки.
Второе: отсекаются выбросы. Точки, попавшие за пределы страницы, исключаются. Причиной часто бывает масштабирование экрана.
Третье: данные разделяются по страницам. Карта по всему сайту сразу бесполезна, анализ ведётся по конкретному адресу.
Четвёртое: учитывается источник трафика. Поведение пришедших из поиска и из рассылки различается, и это влияет на картину.
Пятое: считается объём. Карта, построенная на сотне нажатий, показывает случайность. Для устойчивой картины нужны тысячи событий.
from collections import Counter
def aggregate(clicks, path, device):
points = []
for click in clicks:
if click["path"] != path:
continue
if click["viewport_width"] < 768 and device != "узкий":
continue
if click["viewport_width"] >= 768 and device != "широкий":
continue
points.append((round(click["x"] / 10), round(click["y"] / 10)))
counter = Counter(points)
total = sum(counter.values()) or 1
return [
{"x": x * 10, "y": y * 10, "share": count / total}
for (x, y), count in counter.most_common(200)
]
Округление координат до десятков пикселей здесь не огрубление ради экономии. Оно объединяет близкие нажатия в одну точку и делает картину читаемой.
Чтение карты начинается с поиска несоответствий
Построить карту мало, нужно понять, что она показывает. Полезно искать конкретные признаки.
Первый признак это нажатия на неактивные элементы. Текст, который выглядит как ссылка, но ею не является. Картинка, похожая на кнопку. Это самая частая и самая дешёвая в исправлении находка.
Второй признак это пустые зоны. Место, которое разработчик считал важным, а посетители не замечают. Причина обычно в расположении: правый край, низ страницы, область под баннером.
Третий признак это перекос в одну сторону. Все нажатия собираются на небольшом участке. Значит, остальное содержимое не воспринимается как интерактивное.
Четвёртый признак это отказы на мобильных. Пальцы промахиваются мимо целей, и точки ложатся рядом с кнопкой, а не на неё. Это прямое указание на малый размер цели.
Пятый признак это прокрутка. Если до важного блока доходит малая доля посетителей, содержимое стоит поднять выше.
def find_dead_clicks(clicks, interactive_selectors, min_share=0.01):
total = len(clicks) or 1
suspects = {}
for click in clicks:
if click["selector"] in interactive_selectors:
continue
key = click["selector"]
suspects[key] = suspects.get(key, 0) + 1
return {
selector: count / total
for selector, count in suspects.items()
if count / total >= min_share
}
Функция возвращает элементы, по которым нажимают, но которые не являются интерактивными, и делают это достаточно часто. Это готовый список задач для дизайнера.
Гипотезы проверяются экспериментом, а не глазом
Карта подсказывает, где искать проблему, но не доказывает, что исправление поможет.
Первое правило: формулируется гипотеза. Например, если сделать заголовок настоящей кнопкой, доля переходов вырастет, потому что сейчас люди нажимают на него безрезультатно.
Второе правило: изменение проверяется сравнением двух вариантов. Одна часть аудитории видит старую версию, другая новую.
Третье правило: заранее определяется показатель. Доля переходов, доля заказов, глубина прокрутки.
Четвёртое правило: проверяется значимость. Разница в несколько процентов на малой выборке ничего не значит.
Пятое правило: учитывается побочный эффект. Улучшение одного показателя может ухудшить другой. Например, перенос формы выше увеличивает число начатых заполнений, но снижает долю завершённых.
Приватность требует осторожности и ограничений
Отдельная тема, которую часто недооценивают. Сбор поведения посетителей касается персональных данных.
Первое требование это уведомление. Посетитель должен знать, что поведение записывается, и согласиться с этим.
Второе требование это маскирование. Поля ввода, содержащие персональные сведения, не должны попадать в записи и на снимки.
Третье требование это срок хранения. Данные о поведении нужны неделями, а не годами.
Четвёртое требование это ограничение доступа. Просмотр записей сессий открывает внутренние интерфейсы посторонним, и круг допущенных стоит ограничить.
Пятое требование это отказ. Возможность исключить себя из сбора должна существовать и работать.
Разбор конкретного случая показывает, как карта меняет решения
Полезно пройти путь от находки до результата.
Интернет-магазин техники заметил, что доля добавлений в корзину ниже ожидаемой. Карта нажатий показала неожиданное: немалая часть кликов приходится на фотографию товара, которая не увеличивается при нажатии.
Проверка гипотезы подтвердила догадку. Люди ожидали увидеть увеличенное изображение, нажимали и не получали реакции. Часть из них уходила на другой сайт за подробностями.
Изменение оказалось дешёвым. Галерея изображений подключена к существующей разметке, дополнительных серверных мощностей не потребовалось.
После выпуска доля добавлений в корзину выросла на несколько процентных пунктов. Приток покупателей был заметен уже на первой неделе.
Второе наблюдение из той же карты касалось прокрутки. До блока с характеристиками доходила лишь четверть посетителей, а характеристики важны для выбора. Блок перенесли выше, и глубина просмотра выросла.
Оба улучшения были найдены за час работы с картой. Стоимость внедрения оказалась несопоставимой с эффектом.
Числовая оценка помогает расставить приоритеты
Не каждая находка заслуживает внимания. Полезно считать возможный выигрыш.
Первый множитель это доля посетителей, сталкивающихся с проблемой. Если на неактивный заголовок нажимает один процент, выигрыш от исправления невелик.
Второй множитель это вероятность успеха исправления. Иногда неясно, поможет ли изменение, и тогда сначала стоит провести небольшой эксперимент.
Третий множитель это ценность действия. Переход в корзину важнее перехода в раздел новостей.
def expected_gain(share, success_probability, value):
return share * success_probability * value
candidates = [
{"name": "заголовок как кнопка", "share": 0.08, "success_probability": 0.7, "value": 100},
{"name": "перенос характеристик", "share": 0.4, "success_probability": 0.4, "value": 30},
]
for item in candidates:
item["expected"] = expected_gain(
item["share"], item["success_probability"], item["value"]
)
for item in sorted(candidates, key=lambda x: -x["expected"]):
print(item["name"], round(item["expected"], 2))
Расчёт грубый, но он переводит спор из плоскости мнений в плоскость чисел. Даже приблизительная оценка помогает выбрать, за что браться первым.
Короткая проверка перед началом сбора
Первое: определены ли цели анализа и конкретные страницы.
Второе: настроена ли пакетная отправка данных с досылкой при уходе со страницы.
Третье: маскируются ли поля ввода с персональными данными.
Четвёртое: разделяются ли данные по типу устройства и источнику трафика.
Пятое: определён ли срок хранения и круг доступа.
Шестое: есть ли способ отказаться от сбора.
Седьмое: понятно ли, кто будет разбирать карты и как часто.
Сбор без разбора бесполезен. Карту стоит смотреть сразу после крупных изменений на странице, а не раз в год.
Карты кликов получились инструментом, который окупается быстро и почти не требует сложной инфраструктуры. Несколько десятков строк сбора, простая обработка, наглядная картинка. Польза не в самой картинке, а в списке вопросов, которые она ставит. Ответы на эти вопросы приходят из экспериментов, и вместе эти два средства дают то, чего не даёт ни одно из них по отдельности: понимание, где именно теряются посетители.