Как стать автором
Обновить

Комментарии 9

Для маленького приложения в целом можно, но мне не очень нравится идея использовать dom как хранилище состояния. Когда приложение разрастается, становится сложно отлаживать подобное.
Не знаю как в случае .transform.baseVal.getItemно обычно обращения к dom дереву ухудшают производительность (тут придется делать обращение на каждом кадре).
К тому же element.transform есть только в svg элементах, а значит для обычных html элементов придется реализовывать отдельную функцию. Ну а если решим использовать canvas то вообще не сработает.

Потому и стёр коммент, что поздно сообразил, что речь не о том :)

Обращения к dom ничего не ухудшают, он развёрнут в памяти целиком и приложение является его частью, объекты html ничем не отличаются от объектов создаваемых js с точки зрения доступа к ним.

Написал коммент до того как увидел удаление :) Меня .getItem(0) смутило, не нашел сходу как это работает с точки зрения производительности. Скорее всего ничего страшного.
Ну и обращения к dom это постоянный источник узких мест. Не правильные селекторы, а тем более модификация дума могут сильно навредить производительности.

Самая тупая реализация, которую можно представить

function getItem(idx) {
  let i = 0;
  for (let obj in transform) {
    if (i === idx)
      return obj;
    i++;
  }
  return null;
}

Обычно в трансформе rotate, translate и scale, да, теряем немного на лишних вызовах, но не настолько, чтобы заострять внимание. Из опыта создания движка редактора SCADA могу сказать, нет здесь потери производительности, у меня очень большие схемы шустро работают при такой организации данных. Пример объектов:

<g xmlns="http://www.w3.org/2000/svg" id="item-48141" transform="rotate(0 330 80) translate(317.5 67.5)" data-connpoints="EachSideAmidst" data-width="25" data-height="25" data-state="1" data-lines="48176;48358" data-parent="48591" data-fillcolor="white" data-shape="VacuumSwitch" data-type="switch3" data-owner="" data-connlevel="0" data-geolocation="" data-childs="" data-affecting="48123">
  <rect x="0" y="0" width="25" height="25" fill="none" stroke="none" style="stroke-width:2.1168;" id="stroke-shape-48141"></rect>
  <rect x="0" y="0" width="25" height="25" fill="white" stroke="darkblue" style="stroke-width:0.2646;" id="fill-shape-48141"></rect>
  <rect x="3.5" y="3.5" width="18" height="18" class="VacuumSwitch-switch1" id="switch-shape-48141"></rect>
</g>

<g xmlns="http://www.w3.org/2000/svg" id="line-48184" data-thickness="5" data-state="1" data-itembegin="48154" data-itemend="48193" data-itempointbegin="2" data-itempointend="0" data-parent="48591" data-fillcolor="blue" data-shape="LineAir" data-type="line" data-owner="Abonent" data-connlevel="2" data-geolocation="">
  <polyline points="340,122.5 340,135 350,135 350,1087.5" stroke="none" stroke-width="7" fill="none" id="stroke-shape-48184"></polyline>
  <polyline points="340,122.5 340,135 350,135 350,1087.5" stroke="blue" stroke-width="5" fill="none" id="fill-shape-48184"></polyline>
  <polyline points="340,122.5 340,135 350,135 350,1087.5" stroke="white" stroke-width="4.5" stroke-dasharray="13 20" fill="none" id="dotted-shape-48184"></polyline>
  <polyline points="340,122.5 340,135 350,135 350,1087.5" stroke="#00C853" stroke-width="5" fill="none" stroke-dasharray="2.5 2.5" class="LineAir-switch1" id="switch-shape-48184"></polyline>
</g>

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

Покажу Вам по настоящему сложные вещи в таких приложениях, прошу понять, один пишу проект, знаний и вопросов накопилось много, а обсудить не с кем :) На видео пример, как элементы схемы могут автоматически соединяться коннекторами, если один поднести к другому и отпустить кнопку мыши. Пока держим нажатой кнопку мыши, на обоих элементах подсвечиваются зелёным цветом все свободные точки, синим цветом точки которые будут соединены, красным занятые точки. Для всех точек элемента, который перемещаем, производится расчет со всеми точками ближайших к нему элементов и ищется ближайшая. Когда такой расчёт происходит для огромного элемента усеянного точками подключения с кучей других таких элементов, это вызывает существенную задержку движения мышки - она не двигается, пока не будет закончен расчёт. Пошёл на хитрость, которая по логике сама и напрашивается - пользователь должен подержать секунду элемент на одном месте и тогда начнётся расчёт точек подключения. То, что после этой секунды мышка какое-то время не способна двигаться пользователем не замечается, мы ждём появления точек и то, что они появляются немного дольше не так критично.

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

Пробовал другой вариант решения и пытался обсудить его с ChatGPT, но он мне только голову морочил чепухой какой-то. Обработчики событий у меня на объекте svg, с него запускается расчёт точек при движениях мышкой и создаётся обработчик события движения мышки на элементе под мышкой - в цепочке выполнения события элемент svg получает вызов раньше, чем сам svg и он срабатывает на новое движение, в то время как обработчик svg занят расчётом. Можно с обработчика события движения мышки на элементе прибить выполняющийся в это время обработчик на svg и движение мышки будет идти без лагов, но столкнулся с тем, что устанавливаемые таким образом обработчики событий на элементы не получается очистить после выполнения их роли, и они плодятся, что приводит к лагам с другой стороны. Вот над такими вещами голову ломать приходится, а доступ к свойствам объектов - сущая мелочь.

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

Использование воркеров в таком проекте - правильный путь, помимо расчёта точек существует масса задач, которые следует перенести в параллельные потоки. Но здесь нельзя опираться на общение через API между потоками, большинство операций требует данных всего контекста основного потока, необходимо обеспечивать актуальность данных в SharedArrayBuffer данным основной части приложения. Дойду до этого. Эксперименты с улучшением поиска точек соединения так же лучше переносить в воркер - слишком много опорных данных надо строить, которыми не стоит нагружать основной поток.

Вызовы функций съедают производительность, да, прошёл через это. Собственно, даже на чистом машинном коде при вызове функции происходит прыжок в другую область памяти, сохранение значений используемых регистров перед выполнением и их восстановление по завершении работы функции. Всё это ведёт к издержкам на больших данных. А в таких платформах как JS вызов функции ещё более дорогостоящ. Поэтому я молчу, когда ругают многостраничные функции. И даже иногда поддакиваю. Но продолжаю писать функции-программы)

Спасибо. Буду читать продолжение. Такие ясные рассказы - редкость.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории