Улучшаем опыт взаимодействия с формами

    Часто меня спрашивают студенты: «Какой элемент сайта самый важный?», на что я им отвечаю — формы. Ведь с помощью форм пользователи совершают почти все конверсионные действия. Именно с этим элементом связано больше всего проблем. В этой статье я постараюсь рассказать, что можно улучшить при взаимодействии с формами. А заодно описать новые возможности работы с ними в браузерах.

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

    Александр Першин подробно рассказывал об этом подходе в своей статье Progressive Enhancement. Если вы до сих пор не сталкивались с ним, то я вам крайне рекомендую прочитать.

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

    Браузерная статистика рунета


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

    Все приёмы в этой статье оценят 56,8% пользователей. В этой статистике присутствуют браузеры: IE 10, Firefox 11-17, Chrome 4-24, Safari 6, Opera 12.

    Часть приёмов оценят 80,1% пользователей. Сюда дополнительно включил поддержку: IE 8-9, Safari 4-5, Opera 10-11.

    Мобильные браузеры в статистику не включал, хотя они бы дали ещё больший процент поддержки. Информацию брал из LiveInternet со срезом по России. В расчёт попал средний трафик за 3 месяца (октябрь-декабрь) 2012 года.

    Новые атрибуты форм в HTML5


    Для начала, напомню о новых атрибутах у полей формы, которые буду использовать: required, autofocus, placeholder.

    • required — обязательное поле для заполнения;
    • autofocus — установка фокуса на поле при загрузке страницы;
    • placeholder — описание поля.

    Вместе с этим появилось много новых типов полей: date, email, number, range и другие. Однако, самое безобидное из них (email), до сих пор используется с опаской. А ведь для того, чтобы оно заработало специальных действий не нужно. Браузеры, которые не знают этот тип полей, будут считать его текстовым.

    Также появились дополнительные селекторы в CSS: E:valid, E:invalid, E:required — с помощью которых можно описывать стилевое оформление полей в разных ситуациях.

    Демонстрация работы новых атрибутов полей и их оформления.

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

    Запись данных формы по мере ввода


    Одна из частых проблем заполнения форм — введённые данные теряются. Это может произойти по разным причинам: ошибка сайта, переход по ссылке, в конце концов, может отключиться интернет. Решить эту проблему можно по-разному, например, записывать данные в localStorage по мере ввода.

    1. if (window.localStorage) {
    2.   var elements = document.querySelectorAll('[name]');
    3.  
    4.   for (var i = 0, length = elements.length; i < length; i++) {
    5.     (function(element) {
    6.       var name = element.getAttribute('name');
    7.  
    8.       element.value = localStorage.getItem(name) || '';
    9.  
    10.       element.onkeyup = function() {
    11.         localStorage.setItem(name, element.value);
    12.       };
    13.     })(elements[i]);
    14.   }
    15. }


    Валидация формы и отправка данных аяксом


    Раз уж мы используем атрибуты required, то можно и валидацию сделать по-новому. В спецификации HTML5 для элемента формы добавлен метод checkValidity(). Он возвращает true или false. Стратегия работы формы будет очень простой: если проверка валидации даёт отрицательный результат — мы блокируем отправку формы, в обратном случае — разрешаем отправку.

    1. submit.disabled = !form.checkValidity();


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

    1. form.onsubmit = function(event) {
    2.   if (window.FormData) {
    3.     event.preventDefault();
    4.  
    5.     var data = new FormData(form);
    6.     var xhr = new XMLHttpRequest();
    7.     var url = form.getAttribute('action') + '?time=' + (new Date()).getTime();
    8.  
    9.     xhr.open('post', url);
    10.     xhr.onreadystatechange = function() {
    11.       if (xhr.readyState == 4 && xhr.status == 200) {
    12.         // server response: xhr.responseText
    13.       }
    14.     };
    15.     xhr.send(data);
    16.   }
    17. };


    При работе с асинхронными запросами следует помнить, что некоторые браузеры кэшируют результат. Например, это делает Internet Explorer, Mobile Safari (iOS 6) и другие. Чтобы избежать эту проблему, можно добавлять к адресу запроса текущее время.

    Сейчас ответ от сервера мы получаем в текстовом виде (xhr.responseText), но со временем это изменится. Например, если мы точно знаем, что ответом будет JSON, мы можем получить JavaScript объект сразу.

    1. var xhr = new XMLHttpRequest();
    2.  
    3. xhr.open(method, url);
    4. xhr.responseType = 'json';
    5.  
    6. xhr.onreadystatechange = function() {
    7.   if (xhr.readyState == 4 && xhr.status == 200) {
    8.     // server response: xhr.response
    9.   }
    10. };
    11.  
    12. xhr.send();


    Обратите внимание, что ответ сервера будет в свойстве xhr.response. А свойство xhr.responseType может принимать и другие значения, например: arraybuffer, blob, document.

    Демонстрация сохранения данных формы и отправки их с помощью XMLHttpRequest.

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

    Предварительный просмотр закачиваемых фотографий


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

    • multiple — позволяет выбирать несколько файлов;
    • accept — даёт возможность указывать, какие файлы выбирать.

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

    1. <input type="file" name="image" accept="image/*" multiple>


    Хочу напомнить: поле с такими атрибутами будет работать в старых браузерах. Ограничением будет:

    1. Только один файл;
    2. Валидация файлов производится на серверной стороне.

    Попробуем улучшить опыт взаимодействия с файлами. Раз мы ожидаем от пользователей добавления фотографий, логично сделать возможным предварительный просмотр. Для этого мы будем использовать объект FileReader из спецификации File API.

    1. document.querySelector('[type=file]').addEventListener('change', function() {
    2.   [].forEach.call(this.files, function(file) {
    3.     if (file.type.match(/image.*/)) {
    4.       var reader = new FileReader();
    5.  
    6.       reader.onload = function(event) {
    7.         var img = document.createElement('img');
    8.         img.src = event.target.result;
    9.  
    10.         div.appendChild(img);
    11.  
    12.         queue.push({file: file, element: img});
    13.       };
    14.  
    15.       reader.readAsDataURL(file);
    16.     }
    17.   });
    18. }, false);


    Таким образом, все выбранные фотографии мы сразу же отображаем на сайте.



    А для того, чтобы отправить их с помощью аякса, мы собираем их в массиве queue. Ранее в статье мы использовали объект FormData, сейчас мы просто добавим к нему список файлов.

    1. var data = new FormData(form);
    2.  
    3. queue.forEach(function(element) {
    4.   data.append('image', element.file);
    5. });


    Только и всего, остальное остаётся таким же. Форма будет отправлена с файлами без перезагрузки.

    Демонстрация предварительного просмотра фотографий и отправки их с помощью аякс.

    Drag and drop файлов


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

    1. function previewImages(files) {
    2.   [].forEach.call(files, function(file) {
    3.     if (file.type.match(/image.*/)) {
    4.       var reader = new FileReader();
    5.  
    6.       reader.onload = function(event) {
    7.         var img = document.createElement('img');
    8.         img.src = event.target.result;
    9.  
    10.         div.appendChild(img);
    11.  
    12.         queue.push({file: file, element: img});
    13.       };
    14.  
    15.       reader.readAsDataURL(file);
    16.     }
    17.   });
    18. }


    Допустим, зоной для перемещения файлов будет блок с классом wrapper. Добавим события для него.

    1. var file = document.querySelector('[type=file]');
    2. var dropzone = document.querySelector('.wrapper');
    3.  
    4. file.addEventListener('change', function() {
    5.   previewImages(this.files);
    6.   this.value = '';
    7. }, false);
    8.  
    9. dropzone.addEventListener('dragover', function(event) {
    10.   event.preventDefault();
    11.   dropzone.classList.add('active');
    12.   event.dataTransfer.dropEffect = 'copy';
    13. }, false);
    14.  
    15. dropzone.addEventListener('drop', function(event) {
    16.   event.preventDefault();
    17.   dropzone.classList.remove('active');
    18.   previewImages(event.dataTransfer.files);
    19. }, false);


    Как видите, мы добавили события начала (dragover) и конца (drop) перемещения файлов. Все перемещённые файлы мы передаём функции previewImages. Таким образом, наша форма работает одинаково с файлами выбранными через сайт и перемещёнными с компьютера.

    Процесс загрузки файлов (progress bar)


    Фотографии бывают очень большими, поэтому попробуем отобразить процесс загрузки. Для визуализации этого процесса я возьму элемент progress, а вы можете взять div с двигающимся фоном. Сам процесс будет происходить в событии progress из спецификации XMLHttpRequest.

    1. var xhr = new XMLHttpRequest();
    2.  
    3. xhr.upload.addEventListener('progress', function(event) {
    4.   if (event.lengthComputable) {
    5.     progress.value = Math.round((event.loaded * 100) / event.total);
    6.   }
    7. }, false);


    Демонстрация drag & drop и прогресса загрузки файлов.

    В итоге


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

    1. Валидация происходит в момент ввода;
    2. Введённые данные запоминаются пока не будут отправлены;
    3. Контактные данные сохраняются для повторной работы;
    4. Предварительный просмотр фотографий;
    5. Процесс загрузки файлов.

    При этом, так как мы действовали в соответствии с прогрессивным улучшением, наша форма работает везде:

    • в IE 6-7 и других старых браузерах,
    • с отключённым JavaScript,
    • на мобильных устройствах.


    Работа FileReader на iOS 6.

    Уверен, занимаясь улучшением UX форм, можно найти более интересные решения. Пожалуйста, добавьте ваши решения, советы, критику в комментариях ниже. Спасибо!


    Оригинал статьи в блоге Web Energy.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 32
    • –44
      Думаю, что самый важный элемент сайта — это веб-сервер! Вполне себе можно представить сайт без форм. А вот без веб-сервера — это вряд ли.

      И к чему весь этот нативный JS? Вы хотите предложить свой фреймворк для обработки форм? Так он получается как минимум не кросс-браузерный.
      • +4
        Вы статью по диагонали читали?
        • –1
          Это уже не важно. Хотя статья действительно не несет чего-то особенного и имеет много ляпов.
          Но, интересно то, что не смотря на стадо минусующих, есть сознательные люди, которые со мной согласны.
          • 0
            Вот что делает один из примеров статьи с IE8
            dl.dropbox.com/u/5281873/ie8_bug.png
        • 0
          Не получилось у меня progress bar увидеть в последнем демо:
          Chrome 24, файл .jpeg ~2Мб

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

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


          UPD: Не сохраняется текст в поле «Сообщение» в примере отправки данных ajax-ом.
          • 0
            Спасибо за замечания. По поводу удаления файлов, это ведь голая демонстрация. Разумеется на проектах этот момент нужно улучшить. Информации в FileReader хватает, чтобы сделать всё красиво.
            • +1
              И, конечно, хорошо валидировать e-mail еще до отправки сообщения, выдавая, в случае необходимости, уникальное сообщение об ошибке: например «Поле заполнено некорректно», а не «Заполните электронную почту».

              Ну и хорошим тоном считается объяснять пользователю для чего какие введенные данные будут использованы: «Для уточнения заказа», «Мы вышлем Вам подтверждения заказа на почту» и т.д.

              И снова UPD: все это написано с позиции дизайнера и вряд ли нужно технарям и в этой статье)
            • 0
              В итоге у вас фраза «напишите свое имя» большим шрифтом написана, чем само имя. Ну круто, пусть пользователь заодно тренируется в меткости попадания пальцем в нужную область.
              • 0
                Это супер конечно, но валидацию все-равно нужно будет переносить на сервер, а веселая загрузка картинок вообще заработает у единиц.

                Жаль что столько интересного есть, но в практике этого не используешь :(
                • 0
                  На практике это давно используется, например, в узкозаточенных админках.
                  • +1
                    Не переносить, а дублировать! Клиентская валидация здорово разгружает сервер, а хитрожопых с файрбагом не так уж и много
                    • 0
                      Чтобы избавится от дублирования надо просто сделать гибкий механизм валидации. Не писать хардкод валидации, а просто хранить мета-информацию для полей о том как их валидировать. И написать генератор валидации для яваскрипта и сервер-сайд языка. Затраты на такую систему окупятся в будущем с лихвой.
                      Для всех новых форм вы просто будете указывать тип полей, маску ввода и тд.
                    • –1
                      Как для меня, человека который только начинает осваивать jQuery, статья просто бомба, автору огромнейшее спасибо.
                      • +2
                        А при чём тут jQuery вообще?
                      • –2
                        Насчет валидации браузером. В вашем примере прошло валидацию (Opera 12):
                        name	1651
                        email	1@2.3
                        message	" " (пробел)

                        Скажите, это соответствует стандарту? Если да, то это стандарт не для реальной жизни. А значит по-любому тащить «многокилобайтные библиотеки» во все браузеры и т.д.
                        • +1
                          А в чем проблема-то? Пробел — обычный символ, а атрибут «required» требует наличия символа. По остальному все тоже вполне логично. Если нужно что-то более изащренное, то есть атрибут «pattern», а далее уже в сторону JS смотреть нужно.
                          • 0
                            В том, что на реальном сайте вы так не оставите.
                          • 0
                            у меня в Firefox 16 проходит емеил 123@123
                            • +2
                              Самое интересное, что он соответствует стандарту.
                              Второе 123 считается айпишником (0.0.0.123), из «домашней» подсети для исходящих сокетов. Ну то есть конечно криво и всё такое, но формально — валидно.
                              • 0
                                В Chrome 23 проходит step@s
                                Это ведь не айпишник?)
                                • +2
                                  s — несуществующий, но допустимый домен первого уровня.
                          • 0
                            >Решить эту проблему можно по-разному, например, записывать данные в localStorage по мере ввода.

                            Спорное решение, на мой взгляд. Как оно будет взаимодействовать с аналогичной функциональностью самого браузера (настройка сохранять/не сохранять данные форм)? Какой UX а результате получит пользователь? :)

                            >После успешной отправки формы я советую оставить контактную информацию в localStorage, а остальное очистить.

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

                            В общем, тут еще надо хорошо все продумать.
                            • 0
                              Да, это проблема, особенно если сервис используется с «общего» компьютера.
                              Я бы делал такое только, если пользователь залогинен в системе, да и то, разделял бы локалсторедж по имени пользователя.
                            • 0
                              Мне кажется, что необходимо уменьшать количество взаимодействия с формами, а то что осталось, подавать под другим соусом. Почему яндекс.карты не прося вас вводить улицу и номер дома в разные окошки? Зачем пользователю нужна кнопка отправить? Он уже отправил картинку на ваш сайт кинув её в окошко браузера. Он уже оставил способ связи с собой когда дал вам свой профиль в фйсбуке.
                              Это одна крайность. Есть другая крайность.
                              Когда ваш интерфейс должен восприниматься не как игрушка, а как инструмент. И тогда вы берёте опыт использования Екселя, или ещё чего-то. Но ваша форма по прежнему не должна быть формой.
                              • –1
                                Изящно :) Особенно запись заполнения по мере ввода.

                                Однако автор не рассматривает аспект затрат разработчика на такое создание.
                                Если, например, разработчик с нуля нарисует форму ну допустим на Ext.Js, и потратит на это 15 минут, то данная форма будем иметь ряд недостатков: она будет тяжеловата, там не будет автосохранения и автоподгрузки.
                                Но это будет хорошее решение за 15 минут, работающее во всех браузерах.

                                Когда форм ввода не одна а десятка три, то вопрос стоимости разработки должен остро стоять у разработчика в голове.
                                • –2
                                  Как по мне драг-н-дроп файлов даже не преждевременно а вообще не нужно по большому счету. Нас молодых которые такое могут делать не так и много, а люди взрослые вообще в страницы глядят и у них оторопь что делать, и когда что-то по новому им прям страшно становится. Все таки правильно что все должно оставаться привычным. А что если случайно на страницу уронишь не тот файл, или файл который вообще не планировал грузить и он куда то там загрузится, что-то не очень хочется. Да и при развернутом на весь экран браузере лезть куда-то в менеджер файлов чтоб тянуть как то не практично возможно.
                                  • +1
                                    Да ну, а меня бесит искать все нужные файлы, указывая каждый раз, где находится эта папка, когда вот она, уже открытая, особенно если она где-то гораздо глубже стандартной «Изображения». Или если папка частоиспользуемая, то на неё есть ссылка, но вот для диалога открытия файлов её никак не задействуешь.
                                    • 0
                                      Ну просто опишите взаимодействие, вот у тебя открыт на весь экран браузер, ты получается нужно или программу немного свернуть, или поверх завешивать менеджер. Я не против, но удобно ли это. Так часто мы цепляем файлы в формы.

                                      Конечно как опция то все удобно, кто-то да освоит любые трюки :-)
                                      • 0
                                        Двигать необязательно: файл-менеджер на другом мониторе :-p. Хотя и сдвинуть не сложно по необходимости.
                                        • 0
                                          Да, когда то и я работал на двух мониторах — Золотые времена :-)
                                  • 0
                                    >>element.onkeyup
                                    и… при вставке мышкой текста из буффера обмена он не сохраняется. И даже onmousedown не приходит.
                                    Единственно надёжный вариант — по таймеру раз несколько в секунд делать сериализацию всей формы и сравнивать с последним сохранённым сериализованным значением.
                                    Ну и во имя скорости нужно сохранять именно сериализованную форму, а не поля по отдельности.
                                    • 0
                                      filereader конечно хорош и все о нем пишут, только реально его использовать можно будет когда в требованиях будет ie10+ т.е. в светлом будущем учитывая что сейчас еще пишут ie7+.
                                      предосмотр слишком большая фича чтобы деградировать всех отставших

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.