Да, рынок есть рынок. Но поскольку мы в основном работаем с людьми, чьи сайты сделаны на конструкторах, у них нет 25 тысяч на один виджет. Вот и возникло желание написать калькулятор, которым они смогли бы пользоваться самостоятельно — и без изучения HTML, JS, JQuery и CSS.
В процессе работы над проектом нам удалось реализовать несколько находок в логике работы и дизайне калькулятора. Ими, а также полезными инструментами, и хотим поделиться с сообществом.
По сути, у нас получился довольно универсальный онлайн-конструктор калькуляторов, результат работы в котором можно встроить на любую платформу, поддерживающую вставку HTML.
Как устроен конструктор калькуляторов
Пишем свою адаптивность
Лайфхак: как упростить формулы до азбуки
Чистим код с GULP (а не тем, о чем вы могли подумать)
Есть ли жизнь после жизни?
Как устроен конструктор калькуляторов
Начиная проект, мы обсуждали довольно хардкорные идеи, но в итоге пришли к drag-n-drop интерфейсу сборки, плюс админке, в которой человек может хранить и настраивать свои калькуляторы.
В начале было пустое поле. Регистрируясь на сервисе впервые, человек действительно видит пустую страницу с единственной кнопкой добавления нового проекта-калькулятора.
В будущем на этой странице будут появляться снимки-ссылки на калькуляторы пользователя, вот как тут:
Для создания скриншота-превью калькулятора в кабинете мы использовали PhantomJS. Штука очень удобная, когда ты уже создал несколько калькуляторов, — при входе в кабинет сразу понятно, где какой из проектов.
Люди любят ползунок. Это стало понятно, когда мы запустили первых людей на сервис, и они стали выбирать, из каких элементов создать виджет.
Сам интерфейс создания калькулятора устроен похоже с ЛК — есть большое пустое поле, на которое можно добавлять элементы из боковой панели. Для старта мы выбрали 8 элементов. Пять отвечают непосредственно за калькулятор — это ползунок, выпадающий список, галочка, текстовое поле (для сбора почт, адресов и т.д.) и переключатель. Еще три — за привлекательность (картинка) и опцию заказа — текстовый блок и кнопка. Самым востребованным элементом из всех оказался ползунок.
Сначала для создания ползунка мы выбрали расширения jQuery Scrollbar, но штука странно себя вела на мобильных. Поэтому мы взяли и модифицировали расширение JQuery-Range-Slider. Остальные элементы написали и стилизовали сами
Манипуляции с элементами и данными калькуляторов производятся на клиентской части проекта — поэтому в процессе важно было придумать, как максимально экономить ресурсы браузера.
Этот момент стал одним из самых хлопотных при отладке. Но зато сейчас запись процессов, происходящих на странице, когда человек перетаскивает элемент в калькулятор (это самый ресурсозатратный момент), выглядит так:
Мы максимально порезали обработчики, оставив только необходимый минимум. С оптимизацией на клиентской части нам здорово помог инструмент Timeline из Google Chrome Developer Tools.
Исходно все элементы хранятся в объекте FIELDS — у каждого есть типовой HTML-шаблон и список опций. После перетаскивания элемента в рабочую область, нужные опции прилетают с сервера и подставляются в шаблон — например, на кнопку навешены отправка информации о заказе владельцу и клиенту: по почте через наш сервер, либо по смс — пока через API SmsSimple, но мы ищем другой сервис (и будем рады рекомендациям).
Чтобы подставлять опции, к прототипу строки мы написали свой метод Signe. Работает он так:
При этом мы стараемся защититься от вставки исполняемых кодов в шаблон: прежде всего на стороне сервера удаляются теги <скрипт>, а основная защита построена на экранировании спецсимволов + эскейп тегов.
Drag’n’drop по-своему. Идея «бери больше — кидай дальше», на наш взгляд, это самый удобный способ сборки чего бы то ни было для обычного пользователя. Ну хотя бы потому, что красиво.
Когда мы рассматривали существующие решения для создания калькуляторов, в них смущала некая «прибитость элементов гвоздями» — факт, что элементы можно расположить довольно строго определенным образом: например, только друг под другом, а не рядом. Хотелось уйти от этого, для чего мы придумали систему точек.
Cетка невидимых пользователю точек
Перед перетаскиванием нового элемента мы формируем карту точек, в которые можно добавить новое поле — для этого скрипт обращается ко всем элементам рабочей области и оценивает их границы.
Что это дает? Пользователь сразу может выбрать между таким:
И вот таким вариантом расположения элемента:
При движении мы постоянно проверяем, находится ли мышь над калькулятором, а под капотом запоминаем тип перетаскиваемого поля и позицию, в которую нужно его бросить.
Перетаскивание элемента само по себе не затрачивает много ресурсов браузера, однако проверка того, куда прицелился пользователь, прямо зависит от количества полей, которые уже добавлены в калькулятор. Чтобы сэкономить ресурсы браузера, мы стали определять только координаты полей, которые находятся рядом с мышью, и выявлять ближайшее к ней поле:
Для создания самих визуальных эффектов при сборке калькулятора мы использовали jQuery UI и Animate.css
Абстрагируемся от системы мер и весов. Поскольку решение хотелось сделать универсальным и простым, мы отказались от дополнительных полей, в которых при создании калькулятора человек бы выбирал метры, граммы или рубли. Условные обозначения можно вписать — но чисто для удобства и ориентира. Для всех текстовых элементов мы использовали движок Medium Editor – очень удобный и простой текстовый редактор.
Чтобы доказать, что конструктор подходит для чего угодно, мы наделали разных примеров. А один из примеров наделал шума среди первых тестеров:
«Шаблон «расчет количества мяса» — просто убил: по картинке понятно, что шашлык, а по градациям — такое впечатление, будто из всех этих людей собрались шашлык делать) Ржали всем отделом».
Пощупать калькулятор-шаблон, который развеселил целый отдел, можно здесь
Картинки — это важно. Для лучшего знакомства с товаром или услугой логично добавить изображения над теми же галочками или иным полем, отвечающем за выбор. Благодаря сетке точек, получилось реализовать вставку картинки в любую область рабочего поля. Иногда это полезно:
За тему с ёлочками спасибо Владимиру Гынгазову, автору канала “Adobe Muse по-русски”
Сама реализация загрузки картинок сделана через FileSystem API&File API — весь процесс отлично описан в этой статье.
«А поиграться с...?» Логично дать пользователю возможность подстроить цвета текстов, кнопок, фона и т.д. под цвета сайта. Для вызова и создания цветовой палитры мы использовали виджет Spectrum.
Хранение данных и автосохранение. Данные о клиентской части калькулятора хранятся в формате JSON. Вы можете увидеть их структуру, просто написав в консоли SAVER.json на сервисе.
Автосохранение происходит при каждом действии, если в калькуляторе есть активность. Изменения параллельно сохраняются и в DOM, причем каждый раз мы проверяем:
- Есть ли в JSON все данные из DOM.
- Есть ли в DOM все данные из JSON.
Если же в пределах 4 секунд ничего не происходит, калькулятор останавливает автосохранение до новых правок — так мы избавляемся от бессмысленных запросов к серверу.
Превью. Чтобы не затрачивать ресурсы браузера клиента, мы решили не анимировать интерфейс предпросмотра с помощью jQuery — поскольку с анимацией отлично справляется и CSS3: достаточно поменять класс в корне интерфейса, и у области просмотра изменится ширина и наружное оформление, стилизованное под смартфон и планшет.
Внимание на консоль
Само создание адаптивной версии калькулятора стало отдельной песней.
Div-ная верстка: пишем свою адаптивность
В конструкторе сайтов uKit, для которого исходно создавался наш проект, используется сетка Twitter Bootstrap — популярное и заслуженное решение, чтобы адаптировать веб-элементы под экран посетителя. Но бутстреп предполагает два варианта дизайна: таблицу или колонку. Поэтому мы разработали собственный вариант адаптации калькулятора.
Т.к. структура калькулятора хранится в JSON, у нас есть родительский массив со строками, а в каждой строке — массив ячеек. Помимо этого, в ячейке есть массив суб-строк (и суб-ячеек), чтобы внутри было не одно поле, а несколько. Структура ячеек показана ниже:
У калькулятора есть родительский блок со стилем display: table, внутри у него есть table-row и table-cell, соответственно. Сам калькулятор отрисовывается на сайте во фрейме. Внутри фрейма размещены стили для адаптации — и когда фрейм становится достаточно узким, калькулятор без изменения HTML-сетки перебрасывает поля на новые строки. Сделано это с помощью изменения стиля display: если на широком калькуляторе это table-cell, то на узком становится block, и наше поле оказывается на новой строке.
Исходно-десктопный вид калькулятора зависит от ширины контейнера, в котором он находится, а калькулятор стремится показать в одной строке как можно больше полей. При сужении экрана функция, которая перестраивает сетку, проходит по всем строкам калькулятора, и если в строке есть «лишние» ячейки, ниже создается новая строка.
- Если сам сайт с калькулятором адаптирован под мобильные устройства, контейнер с калькулятором будет занимать всю ширину устройства без масштабирования.
- Если же сайт не адаптирован под мобильные устройства, калькулятор будет отображаться в меньшем масштабе — а его вид повторять вид для ПК.
Аналогично и в редакторе: когда пользователь меняет ширину своего проекта, перед ним отображается фрейм с копией его калькулятора. Как только ресайз завершен, под фреймом рисуется новая сетка, фрейм незаметно для пользователя скрывается — и можно продолжать работу с полями и формулами.
Упрощаем работу с формулами
Так как у нас есть полноценный кабинет управления калькулятором, мы решили отказаться от использования чего-то внешнего и экселеподобного, а в идеале — и сложных формул при создании и редактировании форм.
Вместо этого всего в отдельной вкладке есть элементы калькулятора в виде схемы. Схема содержит названия переменных и диапазоны значений для каждого из элементов калькулятора.
Чтобы задействовать какое-то поле в расчете, достаточно указать его переменную в окошке слева. Формул может быть несколько: в этом случае в калькуляторе отображается несколько результатов, например “Обычная цена” и “Цена со скидкой”.
Переменные начинаются с буквы “A”. Если полей больше, чем букв в латинском алфавите, к имени переменной добавится еще одна буква: “AA” и так далее. Каждая буква связана с числовым id конкретного поля в калькуляторе. Найти готовое решение для преобразования числа в латинские буквы и комбинации букв нам не удалось. Поэтому мы написали следующий метод:
DAT.varName(9) // I
DAT.varName(39) // AM
DAT.varName(9650215) // UCALC
Будем рады, если он вам пригодится (с вопросами можно стучаться к condor-bird).
Оптимизируем скорость загрузки
Чем дальше, тем больше мы занимались интерфейсом сервиса. Но конечная цель — чтобы человек не только собрал у нас свой калькулятор, но и поставил его к себе на сайт в виде виджета (хотя можно и опубликовать калькулятор по ссылке и использовать в каком-нибудь соцсети).
То есть, пора было отрезать
Быстрым. В том же превью грузится виджет калькулятора — можно скрыть все элементы интерфейса конструктора, оставив поля, сетку и калькулирование — и вот он, по сути, виджет для стороннего сайта.
Но быстрый путь был отвергнут — потому что он замедлял загрузку: мы получили бы 1959 килобайт, 269 из которых заняли бы все CSS-ки, используемые в сервисе. А ведь одно из главных требований к виджету на сайте — чтобы он грузился быстро.
И правильным. Тут мы пошли к GULP — чтобы обрезать все лишнее, вроде переноса строк, и собрать один минифицированный файл с максимально чистым кодом. Почему GULP? На то есть важная причина — у нас был 41 файл (и, соответственно, 41 запрос к серверу), а мы хотели уместить все в один запрос. И мы получили то, что хотели.
Это наш дефолтный шаблон. Была скорость загрузки курильщика
Стала скорость загрузки здорового человека
Теперь мы оставляем от 140 до 180 килобайт — в зависимости от числа полей. Для каждого типа поля есть две версии: короткая и вдвое короче — для стороннего сайта.
А что насчет скорости исполнения скрипта, спросите вы?
Это огромный калькулятор расходов на свадьбу, созданный реальным пользователем. Было так.
Тот же проект. Стало так
Как видно на картинке ниже, самым тяжеловесным остается текстовое поле — его мы будем оптимизировать дальше, отдавая на сайт только опцию, которую выбрал пользователь (в настройках поля есть выбор между телефоном, почтой, текстом, числовым значением и т.д.). В остальном для каждого калькулятора мы подключаем только используемые модули.
После загрузки на стороннем сайте калькулятор больше не обращается к нашему серверу: все формулы и прочее необходимое зашиты в загруженный на сайте код.
Упрощаем автообновление калькулятора, встроенного на сайт
В идеальном случае пользователь собрал калькулятор, получил код для встраивания на сайт — и наступило счастье.
Но установка на сайт не всегда значит, что человек больше не будет трогать готовый калькулятор. Самый очевидный случай, когда требуется внести изменения, — это рост или снижение цены на услугу.
Поэтому для каждого встроенного калькулятора мы делаем две версии:
- Опубликованную — ту, что непосредственно встроена на сайт.
- Редактируемую — ту, которую можно открыть и начать править в личном кабинете.
Именно для этого в системе присутствует большая зеленая кнопка «Сохранить» — пока вы её не тронули, мы не переносим на сайт изменения, сделанные в версии для редактирования, а просто запоминаем их через автосохранение.
Первые выводы
Состоят в том, что при создании онлайн-калькулятора и его админки, — задаче, имеющей массу готовых решений, — много места для новинок. Кому-то в новинку все, как brizing — конструктор калькуляторов стал первым боевым проектом, в котором ему доверили работу джуниора. Но и остальные открыли для себя много нового.
Открытий явно станет больше — и вы можете подкинуть нам еще идей и задачек: uCalc находится на стадии открытого тестирования, и мы будем благодарны всем, кто найдет время пощупать решение и отписать мысли и ощущения в комментариях, либо в личку мне, brizing и condor-bird.
UPD. Спасибо всем, кто принял участие в тестировании сервиса. Список ближайших обновлений вы можете найти здесь.
Комментарии (18)
wispoz
23.03.2017 15:21+3А попробовать создать калькулятор без регистрации нельзя?
spasibo_kep
23.03.2017 15:29Без регистрации можно пощупать примеры на сайте, а залогиниться, например, через соцсеть — особенно если там аккаунт, привязаный на старую почту какую-нибудь.
Внутрь вас пустит, ну и спамить мы не будем: максимум — шлем апдейты о новых фичах.
sheriffdm
23.03.2017 18:06+1Специально для хабровчан сделали исключение до конца выходных — см. апдейт поста.
Спасибо, что помогаете!
Keyten
23.03.2017 16:09Подсос изменений?..
spasibo_kep
23.03.2017 16:20Система описана в этой главке — https://habrahabr.ru/company/uteam/blog/324620/#42
QDeathNick
23.03.2017 17:01«Шаблон «расчет количества мяса» — просто убил
Как раз в тему пришёлся, пора сезон начинать, но он у вас не подходит для женщин от 70 до 80 кг и для мужчин от 90 до 100, а у нас как назло все такие :)
brizing
23.03.2017 17:07Алексей, вы, в принципе, можете быстро переделать его под себя, опубликовать по ссылке вида ucalc.pro/api/[id калькулятора] — и удачного вам сезона, конечно)
QDeathNick
23.03.2017 17:14+2Если я его начну переделывать, то мне придется взять на себя ответственность по ранжированию женщин по весу, а это опасно.
spasibo_kep
23.03.2017 17:52Алексей, ловите https://ucalc.pro/api/18264
«У меня на аватарке енот и никто меня никогда не найдет» (с)
fijj
23.03.2017 17:18Честно говоря ожидал какой нибудь react или vue, а под капотом оказался jquery (Не в упрек, а хотелось посмотреть как все это будет работать на подобном проекте). Понравился дизайн, интерфейс довольно сильно тормозит, особенно в firefox. Drag&drop не очень предсказуемо себя ведет, да и перетягивать хочется за любую область элемента, а не только за специальный корешок. В настройках инпута не появлялся выбор типа, потом скролл не работал и вообще это окно не закрывалось.
sheriffdm
23.03.2017 17:21Александр, а вот вы же про это окно, мы так поняли?
Хм, не закрываться оно не может: постучимся в личку за деталями. Фидбек учтем, спасибо.fijj
23.03.2017 17:48Скринspasibo_kep
23.03.2017 20:15Ага, поняли, в чем дело — зальем исправление, а коллега позже расскажет занимательную историю, как, почему и откуда так происходило.
Спасибо за помощь!
spasibo_kep
Собственно, тот самый пример, обнаруженный на просторах сети