Работа с JS для проверки UX-гипотез

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

Давайте рассмотрим, какие возможности браузера и 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.

 

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

  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-проектировщиками и дизайнерами при проработке разных состояний контролов.

 

Примеры

<input type="button" value="Кнопка" />

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

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">

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

Здесь используется вызов функции 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('Сколько угодно обработчиков');
})

Практические примеры:

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

var button = document.querySelector('button');  //при этом у кнопки есть соответствующий тэг button
button.addEventListener('click', function() {console.log('Button clicked.');

Все работает при условии, что текстовому полю назначен id=’cecer’. Осталось вместо button clicked слать в консоль содержание поля.

button.addEventListener('click', function() {console.log(document.getElementById('cecer').value);});


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

$('.ui-input-image__input').mouseover(function() {console.log(this.id);});

Можно получать много параметров за раз. Например, ниже представлена реализация на основе связного списка, возвращающая ширину и высоту экрана устройства, дату клика в текстовое поле (id 1) и текущий URL.

var today = new Date();
document.getElementById('1').addEventListener('click', function(event) {
    var list = {
  value: today,
  next: {
    value: screen.height,
    next: {
      value: screen.width,
      next: {
        value: window.location.href,
        next: null
      }
    }
  }
};
 
function printList(list) {
  var tmp = list;
 
  while (tmp) {
    console.log( tmp.value );
    tmp = tmp.next;
  }
}
printList(list); 
})

Безусловно, это очень простые примеры. Мне доводилось писать более 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.

 

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

  1. Алексей Блохин

    Добрый день.
    Подскажите, все сервисы работают на апи, и иногда требуется делать дизайн апи. Что такое API и как это относится к проектированию интерфейсов?

    • your-scorpion (Author)

      API это своего рода ответ на вопрос, который задает программный продукт. Чаще всего существует в формате JSON (Javascript Object Notation). Вот пример API:

      Входные данные:
      {
      "consumer_id": "e654",
      "phone": "99876543210"
      }

      Выходные данные:
      {
      "id": "eo909oprv55"
      "last_name": "Иванов",
      "first_name": "Иван",
      "middle_name": "Иванович",
      "gender": "M",
      "birthed": "1982-01-01",
      "phone": "99876543210",
      "inn": "123654987456",
      }

      Как видно, это набор параметров относительно пользователя, спрятанного за неким id. В примере есть входные и выходные данные, следовательно, пользователь тоже может создавать или обновлять параметры API. Основной плюс API: нельзя намертво фиксировать в коде данные, которые могут измениться, и API помогает легко обновлять данные без обновления всего программного продукта.

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

  2. Dendy Herlambang

    Добрый день.
    Подход, описанный в статье, работает только с ванильным JS, или прогрессивные фреймворки тоже необходимо учить для работы?

    • your-scorpion (Author)

      Можно и нужно использовать фреймворки, такие как Vue, React, Angular.
      Например, вот пример вывода данных в консоль на Vue.js, аналогичный тому, что я приводил в статье. При нажатии на Enter в консоль выводится содержимое текстового поля.

      <div id="app">
      		<input type="text" v-model="message" v-on:keyup.13="onclick">
      </div>
      <script src="https://unpkg.com/vue@2.1.4/dist/vue.js"></script>
      <script>
      	new Vue ({
      		el: "#app",
      		data: {
      				message:''
      		},
      		methods: {
      			onclick: function() {
      				console.log(this.message)
      			}
      	}
      })
      </script>

      Директива v-model позволяет связывать элементы формы input и textarea. Директива v-on нужна, когда мы хотим отследить, нажал ли пользователь клавишу на клавиатуре, в нашем случае это Enter. Так, с помощью keyup.13 мы отслеживаем нужную нам клавишу. И в кавычках пишем название метода, который должен отработать в случае нажатия на Enter, у нас это onClick.

      new Vue создает экземпляр класса Vue. Внутрь добавляем объект methods. И уже внутри него указываем название метода onClick, в качестве значения передавая сам метод.

      this используется для передачи одноименного свойства объекта data.
      data это функция. message это свойство объекта data.

      У клавиш есть клавиатурные сокращения, которые я использовал в примере, можете ознакомиться.

      • Pavel Ushakov

        Интересно, а почему сначала указан div id=»app», а потом script?

        • your-scorpion (Author)

          Это шаблон Vue.js. В данном примере текст передается через переменную, что позволяет его динамически менять.

          <div id="app">
            {{ message }}
           
          <script src="http://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
              <script>
                  var app = new Vue({
            el: '#app',
            data: {
              message: 'Test text'
            }
          })
              </script>
          </div>

          В двойных скобочках {{}} мы создаем экземпляр Vue, внутри которого объявляем переменную message. В нее мы и записываем наше сообщение.

  3. Alexander Mirnyj

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

    • your-scorpion (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. Artem Originative

    Добрый день!
    Подскажите, пожалуйста, у меня такая задача.
    Есть контактная форма, она выводит введенные пользователем данные в текстовое окно. Как я могу забирать данные из этого окна?

    • your-scorpion (Author)

      Допустим, контактная форма такая

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
        "http://www.w3.org/TR/html4/strict.dtd">
      <html>
       <head>
         <title>!DOCTYPE</title>
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
       </head>
       <body>
       
      <div id="blocks">
      <label><input checked="" type="checkbox"> text1</label><input value="20"><input value="13"><br>
      <label><input type="checkbox"> text3</label><br>
      <label><input type="checkbox"> text15</label><input value="10"><br>
      <label><input checked="" type="checkbox"> text1</label><input value="20"><input value="13"><br>
      <label><input type="checkbox"> text3</label><br>
      </div>
      <textarea id="text_for_show" cols="90" rows="10" onkeypress='keypress(event)'></textarea>

      Опишем задачу. Нужно перебрать массив вложенных элементов, для этого надо создать переменную с полем, куда выводится результат, как вы описали. Собираем элементы в псевдомассив. Проверяем, если первый параметр соответствует ‘label’, то записываем в поле значение 1/0 или текст из текстовых полей.

      <script>
      	var area = document.querySelector('textarea');
      		[].forEach.call(document.querySelectorAll('#blocks>*'), 
      			function ss (text_get, perm){   
      				if(text_get.matches('label')) { 
      					area.value +=perm?' r'+(+text_get.children[0].checked):+text_get.children[0].checked; 
      					area.value += text_get.textContent; 
      				}
       
      				if(text_get.matches('input')) area.value += ' '+text_get.value; //
      		});
       
      		var inpt = document.getElementById('text_for_show');
                      var msg = inpt.value;
      		content=document.getElementById('text_for_show').innerHTML
      		console.log(msg);
      </script>

      querySelector возвращает первый элемент, соответствующий CSS-селектору textarea;
      forEach перебираем массив, далее в функции передаем два параметра, метод call собирает найденные элементы в «псевдомассив»;
      matches() проверит является ли элемент вернет ‘label’ и вернет true или false, в зависимости от того, соответствует ли элемент указаному css-селектору;
      += это присвоение со сложением;
      textContent содержит только текст внутри элемента, за вычетом всех < тегов>.

  5. Alexander Solyarskyi

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

    • your-scorpion (Author)

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

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

«Нажимая на кнопку Submit Comment, я даю согласие на обработку персональных данных»