Здравствуйте! Мы — команда ниндзя-разработчиков проекта ninjamock.com. Ninjamock.com — это еще один онлайн-дизайнер скетчей и прототипов. Проект полностью написан на javascript и HTML5, серверная часть — на ASP.NET MVC.
За год работы над проектом мы наступили на огромное количество граблей и накопили бесценный опыт разработки больших приложений на JavaScript, которым и хотим поделиться. В этой статье мы расскажем, как прототип из одного файла index.html перерос в полноценный проект с более чем 250 классами и 60000 строк кода (не считая сторонних библиотек). Также, в общих чертах опишем нашу архитектуру и детально опишем реализацию отрисовки на клиенте.
Начало
Мы все работаем (всё еще работаем) в одной крупной компании, и как-то так совпало, что нам это надоело в один момент и мы решили захватить мир начать проект, который бы позволил нам работать на себя. Так, одним осенним днем мы собрались в баре выпить по кружке «чая» и обсудить, как это сделать. Где-то после N-й кружки наш гениальный план был готов:
- Пишем лучший скетч-дизайнер
- ???
- Получаем прибыль
Позже выяснилось, что мы не оригинальны и этот план придумали до нас — бизнес-план гномов.
Под скетч-дизайнером мы подразумеваем приложение, в котором можно рисовать скетчи, вайрфреймы или мокапы приложений, а также, делать простые кликабельные прототипы. Главная наша целевая аудитория — это программисты, дизайнеры, бизнес-аналитики и небольшие компании, занимающиеся разработкой мобильных приложений, которым нужно быстро нарисовать дизайн приложения, или же протестировать прототип.
Главным конкурентом мы считали хорошо известный balsamiq.com, чьи финансовые отчеты и вдохновили нас начать работать в этой области. Но, похоже, что мы были не одни, кто думал так же, и в течение года появилось несколько похожих проектов.
В отличие от balsamiq, который написан на flash, мы решили все делать «правильно» и писать на HTML5, чтобы можно было работать на всех платформах (и потому, что это был модный тренд на тот момент).
Еще одно наше отличие от balsamiq состоит в том, что мы ориентируемся на специфические платформы (iPhone, iPad, Windows Phone, и т.д.). То есть, бальзамик — это универсальная платформа для рисования чего угодно, поэтому у них нет семантики специфичной конкретным платформам. Зачем такая семантика нужна? Например, для генерации кода по нарисованному дизайну. У нас все контролы ведут себя более-менее как настоящие.
Архитектура проекта на текущий момент
Серверная часть довольно проста и реализована на ASP.NET MVC; там, наверное, не о чем особо рассказывать. Большая часть кода у нас находится на клиенте и написана на JavaScript. Когда мы начинали проект, мы смотрели в сторону CoffeeScript, но посчитали, что инфраструктура для него на тот момент была достаточно сырой, так что мы решили писать все на чистом JavaScript.
Как и все уважающие себя JavaScript разработчики, мы изобрели не один велосипед и написали несколько своих фреймворков :) Например, у нас свой способ создания классов — там есть несколько интересных моментов, о которых мы расскажем в одной из следующих статей. Сейчас же, с появлением TypeScript, мы, скорее всего, перейдем на него, как только появится поддержка в WebStorm.
На клиенте у нас есть одна основная страница, на которой используется весь JavaScript. Из сторонних фреймворков, мы используем jQuery и knockout.js. Однако, архитектурно мы решили жестко не завязываться на сторонние фреймворки, поэтому у нас есть всего несколько платформо-зависимых классов, в которых разрешено их использование.
Код клиента включает в себя:
- Объектную модель дизайн-элементов, т.е., базовые классы для наших контролов.
- Ядро отрисовки. Сюда входят классы обработки событий мыши и клавиатуры. Эти классы отвечают за нахождение и перемещение элементов по координатам, составление порядка отрисовки элементов, а также, вызов самой отрисовки для каждого элемента.
- Дизайн-элементы (контролы) для конкретных платформ (пока что, только для iPhone). Каждый контрол сам решает, как себя отрисовывать в заданных координатах. У текущих контролов отрисовка «захардкоджена» в код самого контрола — например, в коде «кнопки» мы рисуем прямоугольник, закрашиваем его цветом, потом сверху рисуем текст, выравниваем по середине и т.д. Однако, для отрисовки новых контролов (которые еще не выпущены) мы решили поступить иначе — использовать собственный дизайнер для рисования полноценных контролов. Получилось довольно интересно и мы об этом расскажем более детально в одной из следующих статей.Частично этот функционал уже доступен, можно выбрать один или несколько элементов, затем кликнуть правой кнопкой и выбрать Assets -> Convert to asset. После этого новый элемент появляется в панели слева. И по двойному клику на элемент можно редактировать его шаблон.
- Модели представления (ViewModel) для страницы и диалогов. Это платформо-независимые классы, на которые завязаны шаблоны knockout.js.
- Классы привязки ядра к платформе, на которой запущено наше приложение. Здесь у нас находится код подписки на события мыши и клавиатуры; этот код перенаправляет события в ядро для последующей обработки. Например, на компьютере, на iPad или на iPhone подписка на события делается по-разному; маки и PC отличаются сочетаниями клавиш, и т.д.
- Расширения. Проектируя наш код, мы полагались на принцип открытости/закрытости, поэтому в расширениях реализован тот функционал, без которого в принципе можно было бы обойтись: например, снаппинг элементов, подсветка контейнера при дропе элемента, автосохранение проекта в localStorage. Всего у нас 17 расширений.
Но все начиналось гораздо проще…
Первый прототип
Первый прототип был написан за несколько дней. В нем, как и должно быть в прототипе, все было очень просто. Наши контролы были HTML-элементами. Например, прямоугольник — это просто div, клавиатура телефона — это img, а элементы посложнее, там где нужны градиенты (например таб-бар или тулбар), — это canvas. Все элементы имели абсолютные координаты, положение элементов относительно друг друга задавалось через css-свойство z-index. Перемещение элементов было реализовано через jquery.draggable.
Все контролы размещались в div-элементе, который представлял собой нашу виртуальную страницу, т.е., он был шире и выше, чем рабочая область (для обеспечения скроллинга), а при масштабировании его размер соответствующим образом изменялся.
Главными плюсами такого подхода являются простота реализации и отличный скроллинг элементов, который браузер делает за нас, ну, и много других плюшек.
Такая архитектура просуществовала у нас несколько месяцев. Но наш проект рос и развивался, и, по мере добавления новых возможностей, мы поняли, что она нам не подходит.
Первым камнем преткновения стал банальный зум, который мы так и не смогли нормально реализовать. Собственно, у нас было два варианта:
- CSS-свойство zoom — работает отлично, если вам нужно уменьшить элемент. С увеличением же есть проблема: все наши элементы — векторные, большинство из них было нарисовано на канвасе. При увеличении канваса через css, его контент масштабируется растрово, в то время как нам был нужен векторный зум. Пример того, как это не работает, можно увидеть тут: jsfiddle.net/MzGpe/3/
- «Ручной» зум, когда нужно пройтись по всем элементам и поменять их положение и размер, а также перерисовать всё содержимое всех канвасов с учётом нового масштаба. Но мы так и не смогли добиться плавной отрисовки — все элементы ужасно (последовательно) прыгали в момент масштабирования.
Удивительно, но ни у одного из конкурирующих приложений, реализованных на HTML (fluidui.com, gomockingbird.com, moqups.com), нет масштабирования.
Второй прототип
Нашим решением стало выбросить все HTML элементы и заменить их одним большим канвасом, на котором все и рисовалось. Размер канваса был равен размеру нашей виртуальной страницы. Поэтому скроллинг работал как и раньше но теперь появилась возможность все плавно масштабировать.
Дополнительным преимуществом такого подхода является легкий способ генерации картинки из канваса — это позволяет нам делать превьюшки страниц, используя метод canvas.toDataUrl. Причём, все это выполняется на клиенте и не нагружает сервер. Кроме этого, у нас появилась возможность обработки отдельных пикселов изображения (что в будущем позволит добавить интересные графические инструменты).
Рабочая область нашего приложения с 15-кратным масштабированием:
Однако и тут все оказалось не так просто. При любом перемещении элементов мышью (а это наиболее часто используемое действие), канвас со всеми контролами перерисовывался целиком, что значительно сказывалось на производительности приложения. Решилось это довольно просто — мы добавили еще один канвас поверх первого. Так у нас получилось 2 слоя (изначально их было 3 но это другая история :) ). Первый слой содержал более-менее статичную картинку, которая относительно редко перерисовывалась, в то время как на втором канвасе рисовались временные объекты и различные эффекты, такие как линии привязки и подсветка элементов при наведении. Результат удовлетворил нас почти полностью.
Однако, опять нашлись проблемы, которые показали несовершенство данного подхода. При масштабировании, размер изображения, а, соответственно, и размер канваса, увеличивался. По умолчанию, размер нашей дизайн-страницы равен 1024х768. При 4-кратном увеличении, размер канваса равен 4096х3072, и уже не каждая машина справляется с отрисовкой (с приемлемой скоростью). Более того, на нескольких машинах при таком увеличении стали появляться странные артефакты в виде полос и разноцветных точек. А ведь 4-кратное увеличение — это далеко не предел.
В результате, мы пришли к решению, которое и используем сейчас. Это все те же два канваса, но их размер теперь равен не размеру дизайн страницы, а размеру видимой части страницы на экране. Из-за этого нам пришлось отказаться от стандартного скроллинга и учитывать позицию скроллинга при отрисовке.
При таком подходе все работает отлично, за исключением одного небольшого минуса — скорость скроллинга незначительно упала по сравнению с предыдущими вариантами.
На скриншоте в начале статьи можно увидеть, как наш проект сейчас выглядит. Вживую его можно посмотреть здесь: ninjamock.com.
Эпилог
Сейчас мы активно работаем над увеличением количества контролов, поддерживаемых платформ (скоро появится пооддержка ipad, windows phone, surface, а так же десктоп приложений) и одновременно думаем над вторым пунктом нашего бизнес плана. Будем рады услышать любой фидбек и ответить на любые вопросы по нашему проекту.
Команда проекта ninjamock — pastorgluk, Денис и Headwithoutbrains.
Автор: HeadWithoutBrains