Как написахме SVG джаджи за JavaScript

Опровержение.

Честно признавам, че първоначално бях изправен пред задачата да пиша на Habré за нашия продукт. Първоначално планирах да се огранича до редовно PR есе, описващо основните характеристики и функции на нашия компонент, без да се фокусирам върху детайлите. Разбира се, такава история би позволила да разкрием напълно всички чипове на нашия продукт. От друга страна, това със сигурност би ви накарало да се прозяете около третия параграф.

На пръв поглед тази задача е доста проста за решаване. Например има безплатен компонент Google Gauge и много различни неща, които попадат при поискване в същия Google. От друга страна, повечето от тези библиотеки са склонни да имат ограничен набор от опции. Веднага щом трябва да направите нещо свое, принципът „по-лесно е да пишете сам“ започва да работи.

Затова нашата задача беше да създадем система, която да позволява не само да персонализираме съществуващите „обрати“, но и създайте ги сами от нулата. Системата трябваше да направи възможно събирането на джаджа от графични примитиви (геометрични фигури, стрелки, специални елементи като „епруветки“, „пружини“) и т.н. Освен това искахме да разработим скриптов език, с който програмистът да може да обвърже стойностите на отделни елементи на приспособлението (например числото в текстовото поле и позицията на стрелката в скалата).

И така, екип от двама програмисти, QA (което се свърза малко по-късно) и продуктов мениджър (което постоянно пречеше на работата на всички, принуждавайки всичко да бъде преработено, „защото потребителят е неудобен или труден“), започна разработка веднага след новогодишните празници.

Изправихме се пред няколко технически проблема, които трябваше да бъдат решени:

В нашия случай въпросът се влоши от факта, че имахме професионален екип на Silverlight, който трябваше да бъде преквалифициран (уви, стратезите на Microsoft понякога могат да дразнят собствените си разработчици). Съответно, за да се достигне същото ниво при работа с „чист“ JS, ще са необходими няколко години и няколко „слети“ продукта.

Архитектура

SVG срещу Платно

Ако погледнете Canvas, тогава хардуерната поддръжка в много браузъри е несъмнено предимство, а очевидният недостатък е необходимостта от сложно преначертаване на обекти по време на анимация (например, когато стрелката се движи, ще трябва да прерисувате някои от обектите под него).

След малко размисъл разбрахме, че цялата система за рендиране трябва да бъде написана в абстракции, които не са специфични за SVG или Canvas (здравей, капачка). След това се реализира отделен набор от класове за рисуване и пребоядисване на обекти по специфична технология. В първата версия решихме да се ограничим до SVG, тъй като бяхме силно привлечени от възможността да завъртаме стрелките, използвайки атрибута ъгъл, и да не пречертаваме изображението при всеки завой. Освен това искахме да използваме стандартна SVG анимация (уви, това беше голям проблем) и CSS, за да контролираме стиловете на джаджата.

По този начин, докато всички джаджи са изчертани в чист SVG.

Обектен модел

Вземайки предвид факта, че разработката е извършена в Script #, пренасянето на модела е намалено до банално "copy-paste". Всички опити за оставяне на една кодова база, за съжаление, не са увенчани с успех. Имаше твърде много разлики в поведението, изобразяването и други подробности. Поради това се появиха куп условни директиви и клонове, които само затрудниха разбирането на кода и донесоха повече вреда, отколкото полза. В резултат беше решено, че принципът на СУХО няма да бъде нарушен. Както показа времето, направихме всичко както трябва - в кода на уеб частта практически няма парчета, които да са идентични с частта на Windows.

За да бъдем обективни обаче има и успешни примери за споделяне на код между продуктите C # и Script #.

Сериализация

Един от основните недостатъци, борбата срещу който вече се планира в близко бъдеще, е изобилието от ненужна ненужна информация в полученото описание на JSON. Това се случва поради факта, че използваме стандартни механизми за сериализация и получаваме копие на обекта "както е", включително куп ненужни свойства (уви, трябва да сериализираме всички публични полета). Това прави описанието на JSON чудовищно и заема доста място. Дори специално направихме JSON инспектор, за да могат програмистите да видят как работи тяхната джаджа, като заредим JSON с описание в специална форма.

Между другото, добавихме възможността за промяна на всички параметри на JSON по време на създаването на приспособлението, така че е много лесно, с общо описание, да променяте детайлите: например, променете мащаба на скалата, направете стрелката невидим и т.н.

Изпълнение

Основните опасения, че всичко ще се забави, се оказаха напразни и щом спряхме да прекрояваме цялата джаджа за малка промяна в позицията на стрелката, всичко просто започна да лети. Заключение - нормално оптимизираното прерисуване „ръчно“ от SetInterval практически не отстъпва по производителност на CSS анимацията, но работи еднакво във всички браузъри.

Друг много неприятен момент беше липсата на коничен градиент запълване в SVG. Това е много удобен метод за запълване на инструменти, които използват везни с гладки цветове, като червено до зелено. Съответно, той е бил използван в много джаджи WinForms. Трябваше да апроксимираме коничния градиент с линеен, което не винаги е успешно.

Интерактивност

Mousedown и манипулирането с мишката бяха направени за div, където беше вмъкната джаджа. Когато бутонът на мишката беше натиснат, ние определихме върху кой елемент е щракнал потребителят и съответно изградихме допълнителна логика в зависимост от това.

Тъй като границата на областта обикновено се задава не от полигон, а от някаква крива на Безие, не беше възможно да се приложат прости алгоритми като сумиращи ъгли. Bounding Box е доста подходящ за тестване на щракания върху повечето обекти, но има изключения под формата на големи и тънки елементи с форма на полумесец като полумесец.

В крайна сметка беше решено да се напише HitText за всеки компонент поотделно, като се запомни аналитичната геометрия и се апроксимира обектът с по-прости примитиви.

Друг малък проблем възникна, когато започнахме да прерисуваме приспособлението в отговор на въведеното от потребителя. Например, потребител „хваща“ стрелка и започва да я върти с голяма скорост. Естествено, това изисква много прекрояване. Положителното при използването на вектор SVG беше възможността да се преначертае (и в повечето случаи просто да се завърти) само преместените обекти и да не се докосват останалите, докато с помощта на Canvas ще трябва да преначертаем обекта под стрелката. От друга страна, понякога трябваше да преначертаете доста.

Разбира се, първоначално всичко се забави невероятно, но след няколко часа оптимизация то просто започна да лети. И летеше, докато не разрешихме на потребителите да добавят сложни скриптове към приспособлението, което принуди да прерисува не само директно движещата се стрелка, но и куп други компоненти, свързани със скриптове.

За да се приложи поддръжка за "жестове" за сензорни устройства, трябва да се обработват напълно различни събития, например touchstart, touchchend, touchmove и т.н. Следователно тази подкрепа трябваше да бъде написана отделно.

Скриптове и вътрешна логика

В допълнение, скриптовете направиха възможно свързването на размерите на елементите, форматирането на показания текст и т.н. Наличието на скриптове направи възможно създаването на приспособление с почти всякаква сложност, описващо взаимодействията между отделни обекти и примитиви в него, без да прехвърля тази задача на раменете на програмиста, който вгражда приспособлението в своя продукт (капсулиране, да).

Въпреки безусловната сила на вътрешния сценарий, има и откровени недостатъци.

На второ място, първоначално имаше рязко влошаване на работата на приспособлението при наличието на сложни скрипт връзки. Въпреки това не загубихме сърце и се заехме с оптимизация. След около седмица сложните скриптове практически нямаха ефект върху анимацията и забавянията бяха забележими само при много бързо плъзгане на обекти в IE. Всичко работеше перфектно в хром. Решихме да спрем на това.

Избрани незначителни проблеми

Невъзможност за изчисляване на размера на текста. В SVG, както и в много други графични системи, няма начин да се определи предварително какъв размер ще вземе текстът след рендирането. Тъй като версията на WinForms разчиташе малко повече от напълно на тази функция на GDI, трябваше да измислим хитри схеми, за да я приложим. Сега целият текст се изобразява два пъти. Веднъж - за да разберете размерите, а вторият път с нормално позициониране.

Проблеми с RGBA на устройства с iOS. Към средата на разработката, докато тествахме джаджи на различни модели устройства, забелязахме, че на iPhone 4 и iPad запълването ни беше напълно черно. Разбира се, джаджите изглеждаха много готически и забавни поради това, но все пак беше напълно погрешно. След малко проучване разбрахме, че форматът RGBA, използван за определяне на цвета, не се поддържа в Safari под iOS4 + (изненадващо, всичко работеше на iPhone 3G). В резултат на това разделихме RGBA стойността на две отделни (цвят и прозрачност), където и да я използвахме. Помогна.