Редактор диаграммы рабочего процесса
Редактор диаграммы рабочего процесса

Demo | GitHub

Эксперименты с созданием редактора диаграмм на Blazor Webassembly (Blazor WebAssembly: Drag and Drop в SVG, Blazor WebAssembly: соединительные линии в SVG) показали что технология не годится для интенсивных манипуляций с DOM.

То что будут проседания было известно заранее: WebAssembly не имеет доступа к DOM, любые изменения только через вызовы JavaScript. Задержки оказались такими большими, что перетаскивание на мобильном тормозило уже после добавления третьей фигуры.

Отказ от фреймворков (для данной задачи)

Подозрение что Blazor-овский виртуальный DOM не корректно отслеживает изменения (может Blazor пытается обновить больше DOM-объектов чем требуется) не оправдались. Throttling событий и прочие рекомендации Microsoft (ASP.NET Core Blazor performance best practices) - не помогли.

Чтобы просто обновлять один атрибут, нужно учесть так много нюансов:

  • виртуальный DOM, деревья, поддеревья,

  • “петли” изменений (изменения в одном месте приводят к изменениям в другом, изменения там приводят к изменению в первом),

  • особенности передачи параметров в компоненты и отслеживания их изменений, подписки/отписки.

Тривиальная для JavaScript задача, в Blazor сделалась слишком сложной. Фреймворк здесь только мешает. Излишняя сложность проявляется не только в Blazor, но и в других фреймворках. Если еще не видели, посмотрите выступление автора SvelteRich Harris - Rethinking reactivity”. В видео есть пример с тормозами React приложения: там DOM перестраивается на лету при вводе в текстовое поле. Здесь DOM перестраивается на лету при движении мышки (перетаскивании фигуры).

Сделанный за 20 минут на vanilla-JavaScript прототип не показывал признаков замедления при 1000 фигур.

После нескольких лет использования Angular-а, делать что-то на vanilla-JavaScript казалось регрессом. Ладно еще вручную читать HTML атрибуты и вешать обработчики. А как без компонентов, без IoC, без шаблонов? Самое главное - без “реактивности”? Однако ломка прошла достаточно быстро. Оказалось за границами фреймворков есть жизнь, и в чем то более полноценная.

Отказ от TypeScript (от компилятора TypeScript)

Проверка типов, intellisense и прочий tooling - вот за что любят TypeScript. В TypeScript есть интерфейсы, литеральные типы и даже генерики. TypeScript так затягивает, что легко забыть что TypeScript - это же только механизм описания типов для JavaScript. Да это написано на главной странице typescriptlang.org: “TypeScript is JavaScript with syntax for types.”

Все те же возможности (проверки типов, intellisense и проч.) дает JSDoc.
Пример “типизации” с помощью JSDoc:

/**
 * @param {SVGGraphicsElement} svgEl
 * @param {number} transform
 * @param {SVGSVGElement=} svg pass if svgEl not yet in DOM
 * @returns {SVGTransform}
 */
function ensureTransform(svgEl, transform, svg) {
    ...
    return ...;
}

Можно даже описывать типы на TypeScript и использовать их в js-файлах:

// ts-файл
interface IDiagram {
    on(evtType: DiagramEventType, listener: EventListenerOrEventListenerObject): this;
    shapeAdd(param: PresenterShapeAppendParam): IDiagramShape;
    shapeDel(shape: IDiagramShape): void;
    shapeConnect(param: DiagramShapeConnectParam): void;
}

// js-файл - Diagram реализует IDiagram
/** @implements {IDiagram} */
export class Diagram {
    …
}

При этом будут работать и “find all references” и переименование и проверка что объект реализует интерфейс (по крайней мере в Visual Studio Code все заработало из коробки).

Плюсы отказа от компилятора TypeScript:

  • код JS именно такой, какой вы написали,

  • ускоряет разработку - не нужно ждать компиляции,

  • не нужны map-файлы, легче отлаживать.

JSDoc не так лаконичен как TypeScript, синтаксис на любителя, хуже поддержка IDE.
Удобным оказался смешанный подход:

  • описания типов в ts-файлах на TypeScript

  • реальный код на JavaScript с JSDoc.

DgrmJS

В итоге получилась vanilla-JavaScript библиотека DgrmJS.
DgrmJS - это библиотека для создания редакторов диаграмм рабочих процессов.

Особенности библиотеки:

  • поддержка пк и мобильных,

  • нет зависимостей,

  • малый вес,

  • фигуры создаются декларативно.

Основная идея:

  • Использовать стандартные объекты и возможности SVG для декларативного создания фигур, которые будут использоваться на диаграмме.
    Чтобы создать фигуру - в стандартную разметку SVG нужно добавить специальные data-атрибуты. Таким образом, любые svg изображения можно использовать в качестве фигур диаграммы.

  • DgrmJS отправляет события, такие как «фигура выбрана» или «фигура соединяется с другой фигурой» и другие.
    Можно использовать эти события для реализации собственной логики, например: сделать JSON-описание рабочего процесса, запретить соединение фигур и т.п.

Пример декларативного описания шаблона фигуры “circle”:

<g data-templ="circle">
    <circle ... />
    <text data-key="text"></text>
 
    <!--
        out connector
        data-connect-point - point into shape where connector line starts
        data-connect-dir - direction of connector line
    -->
    <circle
        data-connect="out"
        data-connect-point="60,0"
        data-connect-dir="right" ...>
    </circle>
 
    <!--
        in connector
    -->
    <circle
        data-connect="in"
        data-connect-point="-60,0"
        data-connect-dir="left" ...>
    </circle>
</g>

На рисунке ниже показаны две фигуры (два круга), созданные по шаблону “circle”. По клику на фигуре - отображаются выходные коннекторы, откуда можно вытащить соединительную линию. При наведении конца соединительной линии на фигуру - отображаются входные коннекторы.

Процесс соединения фигур
Процесс соединения фигур

Код добавления фигуры в диаграмму:

import { svgDiagramCreate } from './diagram/svg-presenter/svg-diagram-fuctory.js';

const diagram = svgDiagramCreate(document.getElementById('diagram'));

diagram.shapeAdd({
    templateKey: 'circle',
    position: { x: 120, y: 120 }
});

Больше примеров на GitHub.

Заключение

Статья не призывает отказываться от фреймворков или TypeScript. Долгое следование одним и тем же парадигмам, подходам, фреймворкам в итоге может “зашорить”, сузить охват зрения. Часто мы даже не делаем выбор - попробуйте найти вакансию Blazor WebAssembly или Svelte, выбирать можно только между React и Angular (еще Vue).

Хорошо что есть возможность экспериментировать. Вынырнуть из пузыря “реактивного подхода” было интересно.

DgrmJS весит в 6.5 раз меньше Bootstrap
DgrmJS весит в 6.5 раз меньше Bootstrap

Комментарии (16)


  1. stgunholy
    31.01.2022 13:08
    +2

    Как же я давно ищу что-то подобное и легковесное. Спасибо!


    1. Alex_BBB Автор
      31.01.2022 15:41
      +1

      Спасибо!


  1. VanishMax
    31.01.2022 13:21
    +11

    А зачем нужно сравнение с Bootstrap? Тем более .js файла с .css?


    1. Alex_BBB Автор
      31.01.2022 17:38
      +3

      Говорят же например: "площадь озера равна пяти футбольным полям" - зачем сравнивать озеро и футбольные поля? Что бы представить размер озера.


  1. Nengchak
    31.01.2022 13:21
    +1

    Не хватает возможности запихать какой-нибудь контент внутрь объекта (картинки, текст итд)


    1. Alex_BBB Автор
      31.01.2022 13:42
      +1

      Если правильно понял, то можно запихнуть. Можно взять любую SVG картинку (хоть в Adobe нарисовать) и обвесить атрибутами.


      1. Nengchak
        31.01.2022 13:45
        +1

        Ну да, можно и так, я про сам функционал. Чтобы были готовые методы для этого.


  1. ramil_trinion
    31.01.2022 14:52

    Возможно ли сделать так чтобы линии были прямыми, и если и изгибались, то под углом 90°?


    1. Alex_BBB Автор
      31.01.2022 15:39
      +2

      Сейчас нет. При достаточном интересе к библиотеке можно будет добавить.
      Думаю сейчас не хватает:
      - менять масштаб
      - многострочный текс
      - отмена действий (Crtl + Z)
      - одноврменное перетаскивание нескольких фигур
      - выравнивание фигур по сетке


      1. ramil_trinion
        31.01.2022 16:57

        - менять масштаб
        - многострочный текс
        - отмена действий (Crtl + Z)
        - одноврменное перетаскивание нескольких фигур
        - выравнивание фигур по сетке

        Как активный пользователь систем моделирования бизнес процессов, могу сказать что в списке только один пункт критичен - многострочный текст. Ну и конечно изгиб под 90% и прямые линии тоже нужны.


        1. Alex_BBB Автор
          31.01.2022 16:59

          Спасибо за обратную связь!


      1. mia123
        01.02.2022 18:26

        Осмелюсь предложить фичу возможность сворачивания структуры


  1. amakhrov
    01.02.2022 03:30

    Все те же возможности (проверки типов, intellisense и проч.) дает JSDoc.

    А вот неожиданно да. У вас в репозитории этого не видно - но можно в зависимости добавить typescript и запускать отдельную проверку типов

    ./node_modules/.bin/tsc --noEmit -p ./jsconfig.json


  1. Bhudh
    01.02.2022 06:58
    +1

    <offtopic>
    Не то удивительно, что в браузере пятилетней давности белое поле и ни одна кнопка не работает.
    А то удивительно, что ни строчки ошибок в консоль не пишет.
    Как Вы там, в мире без Graceful Degradation с новейшими хромыми браузерами, не икается ли?
    </offtopic>


  1. CoolCmd
    01.02.2022 12:13
    +1

        /** connector id */
        connector: string;

    почему бы сразу не дать название connectorID?


  1. ustmaestro
    01.02.2022 18:26
    +1

    Также можно добавить Параллелограмм для рисования блок схем программ
    очень бы пригодилось

    <rect width="120" height="70" fill="#1aaee5" stroke="#fff" stroke-width="1" transform="skewX(-20)"></rect>