Получение доступа к узлам DOM для UX-аналитики

UX-проектировщики регулярно сталкивались и будут сталкиваться с ситуацией, когда нет возможности привлечь для проверки гипотезы менеджера, графического дизайнера, backend и frontend разработчика, а проверить гипотезу надо. В этом случае, необходимо редактировать готовый веб-сервис самостоятельно. Для получения доступа и управления содержанием веб-страницы используется JavaScript. С помощью JS можно выцепить из элементов HTML нужную информацию и модифицировать для проведения A/B теста или прототипирования. Не смотря на то, что статья про работу с узлами HTML, технически она про работу в целом с DOM. Многие возможности JS позволяют не только изменить поведение контролов на сайте, но и получить достаточно много интересных данных с форм ввода.

DOM это интерфейс (API) для HTML и XML-страниц, обеспечивающий структуру и необходимые методы для работы с элементами, написан на C++. Каждый элемент на странице это узел, содержание узла тоже является дочерним узлом. Можно выделить узлы-элементы, это тэги вроде body, head, html, и текстовые узлы с контентом внутри этих тэгов, у которых не может быть потомков. Отдельно в DOM находятся и комментарии к коду, и точка входа в DOM.

JS позволяет играться с самим DOM в целом. Для просмотра родителей элемента можно использовать следующий способ:

var allContent = document.querySelector('div');
allContent.parentElement.parentElement

Выполнение будет идти от элемента в переменной вверх по иерархии. Для просмотра дочерних элементов используется allContent.children.

Бывает полезно поиграться с allContent.childNodes. Она позволяет наглядно увидеть, что текст, все пробелы, даже переносы учитываются в DOM. Текстовая нода и элементная это разные типы узлов, как и указано на картинке выше. Также имеется возможность обращаться к конкретному дочернему элементу allContent.children[0]. Узнать количество дочерних элементов можно с помощью allContent.childElementCount.

Давайте рассмотрим, какие возможности браузера и JS нам могут быть полезны в первую очередь. Браузер состоит из DOM (либо Virtual DOM) и BOM. DOM это документ, со всеми body, div, span и прочими элементами. Структура документа, состоящая из объектов. Все свойства DOM описаны на www.w3.org. BOM — объекты для работы с чем угодно независимо от контента страницы. Для управления DOM и BOM используется JS.

С DOM все понятно, давайте посмотрим на пример работы с BOM. Если разобрать на составляющие адрес в браузерной строке https://your-scorpion.ru/portfolio#about, то

  • функция location.href вернет весь URL
  • location.hostname вернет лишь your-scorpion.ru
  • location.pathname вернет /portfolio/
  • location.hash вернет хэш #about.

 

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

  1. Атрибут HTML: onclick="...".
  2. Свойство: elem.onclick = function.
  3. Метод elem.addEventListener( событие, handler[, phase]).

 

Обработчики событий работают со следующими событиями:

  • click – клик по элементу левой кнопкой мыши
  • contextmenu – клик по элементу правой кнопкой мыши
  • mouseover – на элемент наведена мышь
  • mousedown и mouseup – нажали и отжали кнопку мыши
  • mousemove – любое движение мыши
  • submit – отправил форму, работает в тэге <form>
  • focus – посетитель фокусируется на элементе, например нажимает на <input>
  • keydown – когда посетитель нажимает клавишу
  • keyup – когда посетитель отпускает клавишу
  • DOMContentLoaded – когда HTML загружен и обработан, DOM документа полностью построен и доступен
  • transitionend – когда CSS-анимация завершена

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

 

Обработчики

Обработчик может быть назначен прямо в разметке, в атрибуте с названием on<событие>. Сам атрибут находится в двойных кавычках, поэтому для onclick используются одинарные кавычки. Писать напрямую в разметке это не лучшая практика. Обычно в разметке пишут простые обработчики для быстрых тестов. Правильнее написать свою функцию, и вызывать ее из обработчика.

<script>
function countRabbits() {
for(var i=1; i<=3; i++) {
alert(«Кролик номер « + i);
}
}
</script>
 
<input type="«button«" value="«Считать" />

В примере используется вызов функции countRabbits(), сама функция написана выше. Функция это то, что умеет создавать значение. В данном примере было бы уместнее использовать анонимную функцию. Если вас не смутит, что при удалении обработчика событии нужно передавать имя функции. Обработчик может быть назначен и на элемент DOM. Выглядит это как on<событие>. Вот пример:

<input id="elem" type="button" value="Нажми меня" />
<script>
elem.onclick = function() {
alert( 'Спасибо' );
};
</script>

Основной минус такого подхода: DOM-свойство onclick одно, и назначить более одного обработчика не получится. Мне, как Flash-разработчику в прошлом, куда ближе методы addEventListener и removeEventListener, которые являются лучшим способом назначить или удалить обработчик, и при этом позволяют использовать неограниченное количество любых обработчиков. Назначение обработчика осуществляется вызовом addEventListener, который имеет три аргумента: element.addEventListener(event, handler[, phase]);

event — имя события. handler — ссылка на функцию, которую надо поставить обработчиком. phase не обязателен к использованию, отвечает за «место», на которой обработчик должен сработать. Не только пользовательские события могут служить триггером для отработки функции. Рассмотрим пример.

 document.addEventListener(
'DOMContentLoaded',
console.log("Я отработаль"),
{once: true}
);
//или проще
document.addEventListener("DOMContentLoaded", ready);

Это очень простой пример, в котором текст «Я отработаль» будет выведен в консоль в тот момент, когда браузер полностью загрузил HTML и построил DOM-дерево. Важно понимать, что событие «Окно загружено» сработает позже всех, т.к. происходит когда окончательно загрузится весь контент и скрипты на странице. Во втором случае мы вызываем функцию ready.

Итак, у вас есть два способа:

document.getElementById('id0').onclick = function() {
    alert('Может быть только один обработчик');
}
 
document.getElementById('id1').addEventListener('click', function(event) {
    alert('Сколько угодно обработчиков');
})

Получение доступа по имени элемента

Начнем с объекта document, у которого есть свойства и методы для доступа к элементам. Самый простой и распространенный способ получения элемента из любой части документа это getElementById, который возвращает элемент по его id. Например, нам нужно найти элемент с уникальным id «userData» и сохранить HTML контент в переменную.

var dataCollection = document.getElementById( "masthead" ).innerHTML;

Отлично работает при условии, что все id в документе уникальны и не повторяются. Если есть много элементов с одинаковым id, то поведение непредсказуемо. Также DOM позволяет менять стили style с помощью getElementById. Разумеется, скрипт для управления элементами DOM должен размещаться после создания этих элементов. Это можно сделать за счет размещения скрипта <script> после <body>, использования onload или слушателя событий. но исполнение кода начнется только при полной загрузке страницы. При указании кода с помощью onload выполнение скрипта начнется, когда вся страница со всеми файлами загружена. В качестве альтернативы существует DOMContentLoaded, благодаря которому код JS сработает, когда загружен только HTML. Самый распространенный способ, это у тэга script задать один из двух атрибутов: async или defer. Первый атрибут означает, что когда парсер встретит script, то страница продолжит рендериться, а не остановится на паузу. Но если скриптов много, то их порядок загрузки может быть перемешан в зависимости от размера файла JS. defer это аналог DOMContentLoaded, браузер отложит выполнение скрипта до того момента, как страница будет полностью распарсена.

Из важных нюансов также можно отметить, что имена свойств, которые пишутся через дефис (border-right-padding), в JS и DOM пишутся слитно и с заглавной буквы каждое слово, borderRightPadding. Еще нюанс: у вас есть переменная myMug, и она хранит ссылку на объект с ID. Если уничтожить объект, то он останется висеть в DOM, но на странице его видно не будет. Чтобы его уничтожить полностью, нужно преобразовать его в null. Сборщик мусора не тронет объект, пока на него ссылается переменная.


getElementsByName возвращает коллекцию элементов с классом, причем не важно, один класс у элемента или много. Работает только с элементами, для которых явно предусмотрен атрибут name (это может быть form, input, a, select, textarea и т.п.). Не сработает с div, p. Можно использовать свойство length для получения длины списка объектов и метод item() для получения самого списка, как и с любой коллекцией. Используется редко, но если надо вбить данные в поля и нажать «submit», то допустим к использованию.

var elems = document.getElementsByName("description");
document.getElementsByName('login')[0].value = 'login';
document.getElementsByName('password')[0].value = 'password';
document.getElementsByName('goOff')[0].form.submit()

getElementsByClassName() делает тоже самое, что и getElementById, но по значению атрибута class, и возвращает коллекцию элементов HTMLCollection.

<div class="article">Статья</div>
<div class="long article">Длинная статья</div>

<script>
  var articles = document.getElementsByClassName('article');
  alert( articles.length ); // 2, найдёт оба элемента
</script>

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

Поэтому лучше использовать classList: elem.classList.add('true'); для добавления класса. elem.classList.remove('true'); удалит класс. elem.classList.contains('true'); проверит наличие класса и вернет true или false. Уверен, вы уже представили, как придется писать сложные конструкции if/else для добавления/удаления классов, но в JS еще есть elem.classList.toggle('true');, который добавляет/убирает классы в зависимости от их наличия/отсутствия у тэга. Удобно!

querySelectorAll() куда интереснее и выручает в сложных ситуациях. Позволяет получить доступ к узлам DOM по CSS-селекторам. Возвращает NodeList. Если вы захотите удивить коллег, то напишите querySelectorAll.forEach, будет работать в некоторых случаях. В примере ниже мы не только получили все input, но и преобразовали в нормальный массив. Работает для ≥ 5.1. Для быстрой работы в консоли я использую сокращенную запись $$("li.group"), но такая запись работает не во всех браузерах.

var listDomeArray = document.querySelectorAll('input');
listDomeArray.forEach(function() {
}); 
Array.prototype.forEach.call(domList, function() {
});

В CSS можно написать несколько селекторов, в JS доступен только один селектор. При вызове метода document.querySelector('body'); получим в распоряжение весь body. В дальнейшем его можно присвоить переменной var dropPage = document.querySelector('body'); dropPage.baseURI, у которой в JS есть много свойств и методов. Например, мы захотели добавить всплывающую подсказку. Это довольно просто, получаем атрибут var elem = document.querySelector('div'); elem.getAttribute('class'); и устанавливаем elem.setAttribute('title', 'всплывающая подсказка')

И еще один пример, который очень важен для A/B-тестов, изменение свойств CSS объекта на странице.

style.var elem = document.querySelector('h1');
elem.style.color = 'red';

Таким кодом мы поменяем цвет h1 заголовка на красный, позиция меняется с помощью всяких top, right, position. Если же задача состоит в определении размера элемента на странице, то поможет такой способ: var allContent = document.querySelector('body'); allContent.offsetWidth. В данном случае будет получена ширина контента, и полученное значение может отличаться от результата команды allContent.clientWidth т.к. это ширина без полосы прокрутки. Свойства, начинающиеся с offset, возвращают размеры видимого контента, отсутпы, borders и скроллбар. Client возвращает размеры без borders и ширины скроллбара. Scroll возвращает размер полного контента, но без скроллбара.Аналогично работает для высоты allContent.offsetHeight и allContent.clientHeight. На разных браузерах и операционных системах полученное значение может и будет отличаться. Для получения высоты всей страницы, даже с учетом контента, который за пределами области видимости, используется allContent.scrollHeight. Теперь вы знаете, как правильно мериться высотой лендинга.

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

var myTime = document.getElementsByTagName('*'); //выбираем все элементы на странице

Код выше извлечет ВСЕ элементы и заключит их в список узлов (коллекцию). Соответственно, для получения доступа к каждому узлу по очереди нужен цикл. В принципе, querySelectorAll() и getElementsByTagName взаимозаменяемы. Важно понимать, что получать все элементы на динамически обновляемых страницах не так страшно, как кажется, т.к. на выходе мы получаем не массив.

var dataMy = document.getElementsByTagName("div");
for( var i = 0; i &lt; dataMy.length; i++ ) {
// описание того, что надо сделать
}

Полученные похожие на массивы сущности это HTMLCollection или NodeList objects, про которые я говорил выше. Отличие от обычных массивов одно, наследуется Object.prototype вместо Array.prototype. Соответственно, можно забыть про forEach (), push (), map (), filter () и slice (). Преобразовать в нормальный массив можно с помощью:

var almostArray = {
0: 'myItem1',
1: 'myItem2',
2: 'myItem3',
length: 3
};
var fullyArray = Array.prototype.slice.call(almostArray);
fullyArray = [].slice.call(almostArray);
fullyArray.indexOf('myItem1');

Проверить, прошло ли преобразование и чем является наш объект, массивом или любым другим объектом, можно с помощью следующей конструкции: Array.isArray(dataS); //вернет true. Или более сложный вариант чуть ниже. Он не сработает, если массив был создан во фрейме. Проверка не пройдет корректно, т.к. будет унаследован другой прототип.

var whatItIs = [], itWillFlase = '';
alert(whatItIs instanceof Array);
alert(itWillFlase instanceof Array);

Если же задача в получении доступа сразу ко всем элементам с определенным классом, то можно использовать такое решение:

var elements = document.querySelectorAll('.user');
var handler = function(e) {
console.log("ваш код");
};
 
for (let btn of elements) {
btn.addEventListener('click', handler);
}

Доступ к значениям атрибутов

Это все было очень полезно, но чаще всего недостаточно получить доступ к элементам. Значения атрибутов тоже важны и их нужно менять. Для этого используется метод GetAttribute(). Он прост в использовании, т.к. ему требуется только один аргумент: имя атрибута. Обратите внимание, в примере мы использовали "src", но могли использовать "alt" или "id". Если в элементе не будет найден указанный атрибут, то будет возвращены null или пустая строка.

var homeCont = document.getElementById("home");
alert( homeCont.getAttribute("src") );

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

var item = document.getElementById("afisha");
item.setAttribute("class", "democlass");

Таким способом можно менять link у .css файлов, указав в качестве атрибута другой href. GetAttribute() и setAttribute() хороши тем, что поддерживают старые браузеры.

InnerHtml позволяет получить доступ к разметке и тексту внутри элемента, и менять их. Можно даже полностью удалить содержимое body, или можно получить код страницы, которая динамически меняется. Открывает хорошие возможности Для тестов. для примитивных задач, вроде вставки текста, я чаще использую node.textContent, меньше проблем безопасности.

var list = document.getElementById("header");
for(var i = 1; i < 5000; i++) {
list.innerHTML += `<ul><li>item</li></ul>`; // update 5000 times }

Про использование innerHTML будет еще много сказано в этой статьей, т.к. не входит в стандарт, но при этом работает почти везде и работает быстрее операций с DOM. Умеет забрать всю разметку и содержание внутри указанного элемента и очистить его.

swillovPage = document.getElementsByTagName("body")[0];
swillovPage.innerHTML = swillovPage.innerHTML;
swillovPage.innerHTML = ''

Если вставить внутрь innerHTML некорректный код, например elem.innerHTML += '</div>', то браузер не сможет это распарсить и результат будет непредсказуемым. И для простых задач, вроде замены значения title, я бы рекомендовал использовать document.title = 'New title';, а не писать огромные скрипты.


Добавление новых элементов

createElement() позволяет создать новый узел элемента, функция принимает только один аргумент: элемент, который требуется создать. Сразу после применения метода новый элемент не появится на странице, но JS уже будет о нем знать. Многие подумают, не дубль ли это innerHtml? Нет, это гораздо более подходящий инструмент для добавления элементов в HTML, и гораздо более шустрый. innerHTML удаляет все дочерние элементы, разбирает полученную строку и результат добавляет как дочерние элементы. createElement() работает без всех этих вычислений.

var newDiv = document.createElement("div");

Задать пустому элементу контент можно с помощью

littleOne.innerHTML = '
<h1>КОТЫ!</h1>';

Класс задается при помощи littleOne.classList.add('ac-gn-item'). И для добавления элемента с классом и контентом на страницу используется var content = document.querySelector('body'); content.appendChild(littleOne);. Элемент будет добавлен в конец страницы, что очень редко является ожидаемым поведением. Для добавления элемента в определенное место на странице используется метод insertBefore с двумя параметрами: что вставляем и куда. content.insertBefore(littleOne, content.children[1]).

Заменить элемент на другой довольно легко:

var newContent = document.createElement('span');
newContent.innerHTML = '<i>Возникла ошибка</i>';
newContent.style.borderBottom = '2px dotted white';
content.replaceChild(littleOne,newContent);

Если же надо удалить, то content.removeChild.(littleOne); поможет решить задачу. Про это подробнее поговорим в конце статьи.

registerElement. Синтаксис чуть ниже, в котором tag-name это имя тэга, например «mug-shops» (дефис обязателен). И options отвечает за новый элемент, который наследуется от HTMLElement. Иначе не будет стандартных методов и свойств. По факту, происходит регистрация нового кастомного элемента и возврат его конструктора.

var <em>constructor</em> = document.registerElement(<em>tag-name</em>, <em>options</em>);

Давайте рассмотрим createTextNode(), с помощью которого можно создать новый элемент, в частности текст. Его качественное отличие от innerHTML в том, что используя innerHTML нам необходимо проходить весь путь построения DOM. А вот между createElement() и createTextNode() большой разницы в скорости работы замечено не было.

var itsText = document.createTextNode ("нужный стринг");

Вот мы создали новую строку текста и новый элемент, но где же он теперь находится? Для добавления созданного элемента в документ используется метод appendChild. У него есть только один аргумент, имя узла, который мы хотим добавить в DOM. Важно указать, какой из уже существующих элементов будет родительским, т.к. будет добавлен узел в конец списка дочерних узлов. Помимо appendChild, можно использовать insertBefore. Начнем с appendChild().

var myNewText = document.createElement('div');
myNewText.textContent = "Это новый элемент.";
document.body.appendChild(myNewText);

или

document.domain = 'google.ru';
var output = document.createElement('p');
document.body.appendChild(output);

insertBefore пригодится, когда нужно вставить элемент в список дочерних элементов родителя перед определенным элементом. Оперируем двумя  аргументами: новый дочерний узел и смежный узел, который будет следовать после вставляемого.  Комбнириуя appendChild(), insertBefore() и replaceChild() можно вставить узлы в любое место страницы.

 

Клонирование и удаление элементов

Элементы можно клонировать с помощью elem.cloneNode(true) и удалять с помощью removeChild.

var post = document.getElementById("_Q5");
post.parentNode.removeChild(post);

Теперь вы сможете более гибко получать, изменять, и добавлять данные при подготовке тестирования и проверке гипотез. Всего немного JavaScript, и проверка гипотез становится куда быстрее и затрагивает все меньше участников команды. И готовьтесь к проблемам со старыми IE, куда уж без этого.


Безусловно, были представлены простые примеры. Мне доводилось писать более 1000 строк кода для получения достаточного количества данных при проверке гипотезы. Написав код, вы добавляете его в Google Tag Manager и проводите A/B тестирование, параллельно собирая данные.

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

  • URL-адрес страницы
  • Триггер — что инициирует получение данных
  • Category — к какому типу относится используемый компонент, Inputs, Buttons, etd
  • PageType — на какой странице или шаге мы находимся
  • Action — описание, что именно делает этот элемент (from/to, дата рождения, etd)
  • Label — значение полей или контролов

Например, Category: order_esignature, Action: esign_CTA, Label: buy_esign. Смысл в том, чтобы потом было удобно и быстро искать нужные события (и понимать к чему они). Разумеется, в рамках такого ТЗ обычно передается описание всех привязок к Google Analytics.

16 комментариев

  1. Артем Studio

    С помощью какого метода я могу отследить нажатие на элемент списка, и работать только с нажатым элементом? спасибо.

    • Цветков Максим (Author)

      JS:

      <div id='listM'>
              <label>Задача на день:</label>
              <input type="text" id="youtTask"/>
      </div>
       
      <span id = "btnStyleSend">Внести в список</span>
      <ul id = "list"></ul>

      Страница:

      var btnAdd = document.getElementById('btnStyleSend');
      var content = document.getElementById('tk');
      var ul = document.getElementById("list");//сам список
       
      btnAdd.addEventListener("click", function () {
       
      	if (!document.getElementById('youtTask').value != '') {
      		alert('Введите задание в текстовое поле');
      	}
      	else {
      		addNewLi();
      	}
      });
       
       
       
      function addNewLi() {
      	var dataTextField = document.getElementById('youtTask').value;//текстовое поле забираем значение
      	var li = document.createElement("li");//создаем для добавления
      	li.appendChild(document.createTextNode(dataTextField));
      	ul.appendChild(li);//вставлем значение как пунт
      	li.className = 'myClass_style2';//добавляем класс к пункту
      	document.getElementById('youtTask').value='';//убираем из поля текст
      	li.innetHTML = dataTextField;
      	ul.addEventListener('click', clickerFn);
      }
       
       
      clickerFn = function() {
      	for (var i = 0, len = ul.children.length; i < len; i++)//получаем индекс кликнутого элемента
      	{
       
      	    (function(index){
      	        ul.children[i].onclick = function(){
      	              console.log('индекс кликнутого элемента ' + index);
      	              var v2 = document.getElementsByTagName('li')[index];
       
      	              if (v2.classList.contains('myClass_style2') == true) {
      	              	v2.setAttribute('class', 'myClass_style');
      	              }
      	              else{
      	              	v2.setAttribute('class', 'myClass_style2');
      	              };
       
       
      	        }
      	    })(i);
      	}
      }
  2. Andrey Vassilyev

    Можно ли менять в GTM длинные URL на какие то понятные имена?

    • Цветков Максим (Author)

      Первое, что приходит на ум, это LookUp Table.
      Указываете в списке значение, которому при соответствии переменной будет присвоено выбранное вами значение. Значением может быть любая переменная GTM.

      Поля чувствительны к регистру и требуется точное совпадение.

      Если же вам требуется не точный результат, что бывает довольно часто, то обратите внимание на RegEx Table. Как работать с регулярными выражениями, я уже писал. Переменная возвращает первое совпадение. Есот оставить галочку Full Matches Only включенной, то пример dev\.infotecs\.ru будет возвращать dev.infotecs.ru. Если галочку отключить, то будет возвращено, например, anydev.infotecs.ru.com.

      Либо писать собственный JavaScript variables, как вариант для особо изощренных умов.

  3. Alexander Mirnyj

    Если в верстке не предусмотрены id, как и class или URL. Как тогда решать задачу отслеживания?

    • Цветков Максим (Author)

      Еще можно привязываться к селекторам CSS, это иногда позволяет не вмешиваться в код сайта. Для их использования требуется выбрать Click Element или Form Element.

      Допустим, можно привязаться к селектору для кнопки. В примере ниже уникальный #gform_btn дает безошибочную привязку к нужному элементу.

      #gform_btn [type="submit"]

      В Google Tag Manager создаем новую переменную c методом CSS Selector. Вбиваем имя селектора, например #form_element .title

      Работает так: элемент с id «form_element » обращается к дочернему элементу с классом «.title». Эту переменную можно передавать как атрибут события при выполнении клика.

  4. Denis Kharitonov

    Как строить воронку продаж?

    • Цветков Максим (Author)

      Если в Google Analytics, то «Администратор» -> «Цели» -> «Собственная» -> «Целевая страница». У третьего шага, «Подробные сведения о цели», надо указать URL-адрес последнего шага воронки. Остальные шаги воронки задаются при клике на кнопку «Последовательность», в виде URL или при помощи «виртуальных страниц», когда нет возможности указать точный URL.

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

      Поэтому дополнительно делаем нормальную воронку на основе последовательностей: «мои отчеты» -> «создание отчета». В созданном отчете нажимаем «Добавить сегмент» -> «+ сегмент» -> «Последовательности». Можно создать до 4-х сегментов.

      Вы получите линейную воронку, но на практике воронки имеют хаотичное распределение действий пользователей, как Цепи Маркова. Поэтому надо учитывать вклад отдельных сценариев, отслеживать последовательность действий/страниц по куке отдельного пользователя.

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

  5. Виктор Магнетиьев

    Вопрос про консоль и dalaLayer. Как работать с последовательностью страниц через консоль, данные ведь затираются после загрузки новой страницы.

    • Цветков Максим (Author)

      Если браузер Chrome, то решение это dataSlayer или WASP.inspector. Если ручками в консоли, то вот так:

      • Виктор Магнетиьев

        Спасибо, очень быстро ответили! Preserve log то что надо.
        Тогда скажите еще такой момент, как считаются сессии?

        • Цветков Максим (Author)

          C одного поисковика и меньше 30 минут между заходами — один визит. Даже если поисковые фразы разные, засчитана будет первая поисковая фраза. Закрытие вкладки и даже закрытие браузера не останавливает визит. Если пользователь ушел из вкладки на 30 минут и вернулся, то это новая сессия, у которой источник ваш сайт. Поэтому сравнивать визиты более корректно, чем посетителей, т.к. пользователь может создать много визитов в рамках разных сессий.

          При смене источника сессия должна порваться, в теории, но как минимум в Метрике это работает не всегда, в том же веб-визоре сессия не рвется на получасовые отрезки. Частая ситуация, что в метрике 300 визитов, а в GA 600. Метрика считает сессию по cookie, и если она удалилась, то сессия будет новой. В Метрике в конверсии считаются именно целевые визиты, в которых было минимум однократное достижение цели. Достижение цели в пределах одного визита может быть осуществлено несколько раз.

          Нестандартные истории: клик в приложении, ссылка открывается в браузере внутри приложения. Посетитель ничего не делает и уходит. Возвращается через обычный браузер на телефоне через другую ссылку. Соответственно, новый пользователь, источник direct / none. В таком случае обычно приходится подумать, в описанном выше случае надо из Facebook Pixel получать ID куки c_user и передавать в GAID.

          Переход из рекламных систем это отдельный визит, каждый новый клик по объявлению даже в течении 30 минут это отдельные визиты. Каждый хит по рекламе создает новый визит и завершает старый. Все рекламные переходы Метрики перечислены тут.

  6. Alexander Solyarskyi

    Подскажите, чем руководствоваться в следующей ситуации. Я менеджер, меня не устраивает качество работы нашего дизайнера. Мы провели A/B тест моего варианта карточки товара и варианта, который предложил дизайнер. Мой вариант выйграл. A/B тест может служить подтверждением низкой квалификации дизайнера и поводом для увольнения?

    • Цветков Максим (Author)

      Начнем с того, что A/B тесты бывают двух видов: на клиенте и на сервере. На клиенте тестируют фронтенд, сервер-сайд это тест ассортиментной матрицы. При планировании теста надо руководствоваться не мелочами в дизайне, а индексом PIE Score. Мелочи в дизайне, вроде цвета кнопок или размера заголовка, очень слабо влияют на ключевые метрики продукта. И их легко протестировать с помощью MVT-тестирования. Так что A/B-тест это не проверка дизайнера, а проверка гипотезы.

      И вы уверены, что убедились в статистической значимости результатов теста? Было ли проведено пристрастие для удаления выбросов? Всегда лидировал один вариант дизайна или они чередовались? Был ли поделен трафик на параметрические доли?

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

  7. Дмитрий Дивин

    Подскажите, как лучше вставлять код отслеживания на сайт, через GTM или напрямую?

    • Цветков Максим (Author)

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

      Если на сайте используется сквозная аналитика, то из кода счетчика надо получать ga_clientID и записывать его в custom dimension. Если код счетчика будет в GTM, то он будет срабатывать медленнее из-за лишнего звена и будет рассинхрон с записью ga_clientID. И clientID не будет записан. Конечно, можно выкрутиться и сделать проброс ga_clientID через CustomTasks сразу в Custom Dimension, но часть данных все равно потеряется.

«Взаимодействуя с данным сайтом, вы, как пользователь, автоматически даете согласие согласие на обработку персональных данных»

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.