Быстрая интерактивная схема зала на canvas +27



Вы можете помочь и перевести немного средств на развитие сайта

Теги:



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

  1. vmm86
    /#10693946

    Схема зала — это не обязательно сетка, похожая на таблицу "X на Y". Нужна возможность рисовать как схемы с полукруглыми/вытянутыми секторами (театры оперы и балета с бенуарами, бельетажами, балконами), так и полностью круглые схемы (цирки с ареной посередине).


    К тому же полезна возможность отрисовать слишком большой зал как посекторную схему, чтобы при клике на сектор открывалась схема только этого сектора.


    Мы в ходе разработки сайтов по продаже билетов bezantrakta.ru поначалу экспериментировали с SVG, сейчас остановились на HTML-схемах.

    • yjurfdw
      /#10693964

      Совершенно верно. Чтобы работать с объектами любой формы, нужно только доработать определение вхождения точки в объект (при клике и ховере). Передо мной такой задачи не стояло, но доработать недолго.
      По поводу HTML схем: думаю, вы не будете спорить, что браузеру намного сложнее отрендерить dom элемент с кучей параметров и событий, чем кучку пикселей на канвасе? :)
      Как раз решение перейти на канвас пришло после использования svg и html схем, результат порадовал.

    • yjurfdw
      /#10693974

      Тут даже вопрос не в том, на чем лучше делать схемы залов, а сколько объектов на этих схемах.
      Конечно, если элементов не более 500, проще сделать на дивах или свг. Все будет работать из коробки: клики, ховеры, повороты и прочее.
      Но когда схема будет иметь 6к мест и у Вас популярный сервис (т.е. число пользователей условного ie большое), то встанет вопрос: «почему схема открывается 10 секунд?». Вот у нас примерно такая ситуация :)
      А решение, описанное в статье, достойно тянет и 20к объектов даже в ie, пример

      • Tyusha
        /#10695000

        Как количество пользователей и то, какой у них браузер, влияет на скорость работы?

        • NikitchenkoSergey
          /#10695004

          Напрямую: в разных браузерах и устройствах скорость отрисовки канваса разная. Ну и не только канваса, даже свг. Обычно хуже всего работает в ie.

  2. kurtsergey
    /#10694118

    Быстрая она в реализации или в работе? На машинке с i5-7440HQ(2.8Ghz), Win10, Firefox демка безбожно тормозит :-( Выделение места на карте отстает от курсора нa ~1-1.5 секунды.

    • yjurfdw
      /#10694130

      Должна быть в работе, конечно :)
      FF почему то на крупном масштабе работает примерно как ie, при приближении становится нормально. Еще не придумал, как это дело ускорить, но я в процессе.

    • Bonart
      /#10694390

      На телефоне в хроме работает как надо.

  3. kalyabus
    /#10694120

    Хорошая работа. Хотелось бы в следующей статье увидеть сравнительные нагрузочные тесты: canvas vs svg vs html для разных популярных браузеров. Например, для маленьких залов, согласен с предыдущим оратором, наверное проще отрендерить html на сервере?

    • yjurfdw
      /#10694134

      Для маленьких да, проще html: не нужно возиться с обработкой событий и будет легко менять внешний вид.

  4. luckybet100
    /#10694166

    В целом не плохо, но сразу видно, что можно улучшить. Смотрите, в чем состоит задача по нахождению квадрата. Я бы предложил Вам избавиться от Вашего дерева и просто делать бинпоиск, сейчас расскажу как. Это будет быстрее и, кстати, меньше кода. Условимся, что все места (квадраты) имеют одинаковый размер. Запихнем все координаты верхних левых углов в массив пар (x, y). Отсортируем вначале по координате x, потом по y. Понятно, что тогда массив разобьется на куски с одинаковой координатой x. Бинарным поиском найдем первую такую группу, что ее x координата больше либо равна координате курсора. Далее вторым бинарным найдем первую точку поиском найдем конец этой группы. И нам остается сделать бинпоиск чтобы найти первую координату y большую либо равную y курсора. Теперь Вы знаете предполагаемый квадрат, остается проверить принадлежность курсора, предполагаемому квадарту. Итого log2(n) ассимптотика и примерно 20 строчек кода. Намного проще и быстрее

    • yjurfdw
      /#10694182

      Деление схемы на квадраты помогает не только искать объект по курсору, но и позволяет вообще не думать об объектах при рендере видимой области: место перебора объектов мы ищем узлы дерева, входящие в зону видимости и показываем все их объекты.
      Спасибо за замечание, но если честно, мне кажется удобнее использовать дерево.
      Кроме того, в нашей войне за производительность канваса время поиска занимает одно из последних мест, больше всего проблем именно со скоростью отрисовки.

    • luckybet100
      /#10694186

      На TypeScript не пишу, но на c++ это выгладяло бы примерно так:
      arr — массив c верхними левыми координатами
      sz — сторона квадрата
      auto start = lower_bound(arr.begin(), arr.end(), {x_cur, y_cur});
      auto end = upper_bound(arr.begin(), arr.end(), {start->first, INF}) — 1;
      auto res = lower_bound(start, end, {start->first, — INF});
      if (res->first + sz >= x_cur && res->second + sz >= y_cur) {
      // курсор над квадратом res
      } else {
      // курсор не над квадратом
      }
      upper_bound — первый больший, lower_bound — первый больше либо равный

  5. Workanator
    /#10694354

    Почему бы вместо дерева не использовать обычный двумерный массив? А нужная ячейка в таком массиве ищется обычным дилением позиции чего бы то ни было, будь то позиция курсора или другой координаты, на заранее известную константу. При это вы экономите кучу памяти только на том, что вам не нужны разные дополнительные поля для организации дерева. И вы экономите время на поиск нужного квадрата, потому вам не надо перебирать элементы структуры данных; вы находите нужный элемент простым математическим вычислением его позиции.


    const BOX_HEIGHT = 100, BOX_WIDTH = 160;
    
    // Распределяем места по массиву. Предполагается, что если место пересекает границу
    // одной или более области, то добавляем место в смежные области.
    let boxes = [];
    for (let seat in seatList) {
      let topX = int(seat.X/BOX_WIDTH),
          topY = int(seat.Y/BOX_HEIGHT),
          bottomX = int((seat.X+seat.Width)/BOX_WIDTH),
          bottomY = int((seat.Y+seat.Height)/BOX_HEIGHT);
    
      boxes[topY][topX].push(seat);
      if (bottomX != topX) boxes[topY][bottomX].push(seat);
      if (bottomY != topY) boxes[bottomY][topX].push(seat);
      if (bottomX != topX && bottomY != topY) boxes[bottomY][bottomX].push(seat);
    }
    
    // Ищем ячейку массива, в которой есть нужное место.
    let boxX = int(point.X/BOX_WIDTH), boxY = int(point.Y/BOX_HEIGHT);
    if (boxes[boxY][boxX]) {
      for (let seat in boxes[boxY][boxX]) {
        if (seat.Contains(point)) {
          alert("Вы выбрали ряд " + seat.Row + " место " + seat.Number); 
        }
      } 
    }
    
    // Прошу прощения за возможные огрехи в синтаксисе, JS не мой основной инструмент.

    Если у вас не вся область вмещается на "экран", то добавьте к point.X и point.Y смещение, если масштабирование — то умножьте/разделите на коэффициент масштабирования.

    • yjurfdw
      /#10694574

      Если я правильно понял Ваш код, работать он будет только для точек в углах квадрата :)
      Наведение внутрь объекта не даст результата, т.к. понятно, что не будет существовать таких индексов у Вашего массива. А чтобы они были — нужно разметить так каждую точку на схеме либо также перебирать уже 'boxes', что явно не оптимальнее поиску по дереву. Да и вы потратите кучу памяти, т.к. дублируете объект в массив 4 раза. В общем посыл я не понял, извините.

    • yjurfdw
      /#10694602

      Теперь немного понял. Будет неправильно определяться, если размеры объекта будут больше разбиения в 2+ раза. В нашем случае это важно, т.к. есть крохотные места и большие сцены или сектора со свободной рассадкой.