Продолжаю тему разработки динамической векторной (по координатам) карты для браузера dbcartajs. В новой версии (v2) я перевел отрисовку объектов с канваса на SVG. И переделал несколько примеров, в частности карту метро Москвы. В своем посте про канвасную версию карты я сравнивал ее с svg-версией из Википедии, сделав акцент на скорости загрузки, которая у канваса оказалась выше. Но воспроизведя карту через svg-обработчик в новой версии проекта, я понял, что скорость загрузки, пожалуй, единственное преимущество канваса перед SVG.

image

Во-первых, потребовалось меньшее количество кода для создания на svg.

Во-вторых, простота и удобство создания кода. У svg логика строится на отдельных объектах, их свойствах и методах. Например, чтобы определить объект под курсором, нужно создать обработчик события для данного объекта (circle для станций метро), в котором можно поменять его свойства — цвет, масштаб (на картинке для значка станции «Ходынское поле» применен метод scale) и др. У канваса логика отрисовки построена на перерисовке всей карты. И для выделения отдельных объектов нужно перерисовывать и масштабировать всю карту. Кроме этого, канвас не хранит отрисованные объекты в памяти, их нужно отдельно сохранять в переменных (в dbcartajs в объекте mflood) и самому следить за ними (добавлять, удалять). SVG-изображение хранит отрисованные объекты в DOM-модели и к ним можно обращаться напрямую.

В-третьих, возможностей в svg намного больше, чем в канвасе, например, анимация, фильтры для изображений.

В-четвертых, совместимость с браузерами. У SVG она выше. C SVG я проблем не заметил. В канвасе некоторые свойства в Firefox 3.x и новых версиях работают по разному (например setDashLine, isPointInPath). Internet Explorer до 9 версии и Safari до 4 версии канвас вообще не поддерживали.

И svg, и канвас могут манипулировать готовыми изображениями (png, jpeg). Правда, только канвас может обращаться к нему попиксельно. И это, как и скорость отрисовки, пожалуй, его главная особенность.

В общем, кому интересно, смотрите примеры на egaxegax.github.io.

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


  1. stalkerg
    04.10.2015 23:32
    +5

    У канваса логика отрисовки построена на перерисовке всей карты.

    Это проблема конкретной вашей реализации, а не самого канваса.


    1. egaxegax
      04.10.2015 23:45

      В канвасе можно перерисовать отдельный фрагмент. Но перерисованный фрагмент наложится на изображение. Можно стереть часть изображения перед наложением фрагмента, но тогда появится пустые области. Кроме того, довольно сложно каждый раз вычислить область наложения особенно при выводе текста несколько раз в одной области. Поэтому я имел в виду перерисовку всей картинки как базовый вариант работы с канвасом в динамике.


      1. janitor
        05.10.2015 10:46
        -3

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

        Ну, если правильная архитектура приложения, то ничего сложного нет.


      1. stalkerg
        05.10.2015 13:36

        При создании игр все эти проблемы давно давным решили…
        Дам совет — лучше карту рисовать в буфер и скролиннг реализовывать уже по этому буферу, а не рисовать каждый раз, с отключением текста. :)


        1. TimsTims
          05.10.2015 17:22

          Ждём статью)


          1. stalkerg
            05.10.2015 17:52

            Мне бы собраться с мыслями и про Вангеры написать (там к слову этот подход во всю, каждый объект сам за собой чистил).


  1. Keyten
    05.10.2015 00:33
    -1

    Первые 3 «во-первых» легко решаются библиотеками для canvas.


    1. egaxegax
      05.10.2015 01:02

      Какими, например? И как? У каждой библиотеки своя область применения.


      1. Keyten
        06.10.2015 00:49

        Ну это естественно.
        LibCanvas, Fabric, Graphics2D, PaperJS хотя бы. Первое, что вспомнилось.
        Они общего назначения, есть и специализированные.

        Во-первых, потребовалось меньшее количество кода для создания на svg.

        Сравниваем:
        <rect x="10" y="10" width="200" height="200" fill="red"/>
        <path d="M10,10 L200,200 L400,10" fill="blue"/>
        

        Пример для canvas будет на Graphics2d, т.к. я её лучше всего знаю, да и там короче всех, пожалуй:
        var rect = ctx.rect(10, 10, 200, 200, 'red');
        // либо:
        var rect = ctx.rect({ x: 10, y: 10, width: 200, height: 200, fill: 'red' });
        
        var path = ctx.path([[10, 10], [200, 200], [400, 10]], 'blue');
        

        А многие библиотеки (Fabric, например) и SVG-пути поддерживают.

        Во-вторых, простота и удобство создания кода. У svg логика строится на отдельных объектах, их свойствах и методах. Например, чтобы определить объект под курсором, нужно создать обработчик события для данного объекта (circle для станций метро), в котором можно поменять его свойства — цвет, масштаб (на картинке для значка станции «Ходынское поле» применен метод scale) и др. У канваса логика отрисовки построена на перерисовке всей карты. И для выделения отдельных объектов нужно перерисовывать и масштабировать всю карту. Кроме этого, канвас не хранит отрисованные объекты в памяти, их нужно отдельно сохранять в переменных (в dbcartajs в объекте mflood) и самому следить за ними (добавлять, удалять). SVG-изображение хранит отрисованные объекты в DOM-модели и к ним можно обращаться напрямую.

        Та-даам.
        var station = ctx.circle({ cx: 50, cy: 50, radius: 10, fill: 'red', stroke: 'red 2px' });
        station.on('mouseover', 'animate', 'stroke', 'blue', 1000);
        station.on('mouseout', 'animate', 'stroke', 'red', 1000);
        

        Вот так ещё можно:
        station.on('mouseover', function(e){
          this.animate('stroke', 'blue', 1000);
          this.scale(1.3);
        });
        
        station.on('mouseout', function(e){
          this.animate({
            stroke: 'blue',
            scale: 1 / 1.3
          }, 1000);
        });
        


        В общем-то, на всех 4-х из мною перечисленных это спокойно реализуется.


        1. Keyten
          06.10.2015 01:00

          Ах да, и забыл:

          В-третьих, возможностей в svg намного больше, чем в канвасе, например, анимация, фильтры для изображений.

          Анимацию в G2D я уже показал, в LibCanvas тоже есть, и в Fabric.
          Фильтры в Fabric:
          fabric.Image.fromURL('image.jpg', function(img) {
            img.filters.push(new fabric.Image.filters.Grayscale());
            img.applyFilters(canvas.renderAll.bind(canvas));
            canvas.add(img);
          });
          

          Фильтры в g2d:
          var image = ctx.image('image.jpg', 10, 10);
          image.filter('pixel', function(r, g, b){
           var mid = (r + g + b) / 3;
           return [mid, mid, mid, 1];
          });
          

          Тут пример с Grayscale, встроенных в G2D пока нет.

          Да и вообще реализовать, в общем-то, несложно, в отличие от SVG есть прямой доступ к пикселям.


          1. egaxegax
            06.10.2015 23:57

            Приятно получить комментарий тоже от разработчика библиотеки для канваса. Только почему ни один пример не работает на http://graphics2d.js.org? Хотелось бы увидеть что-нибудь из вышеописанного в действии. В проекте graphics2d примеров тоже нет.


            1. Keyten
              09.10.2015 23:03

              Всё пофиксил, благодарю. Добавил ещё один пример, теперь можно оценить соотношение прыгающих зайчиков и FPS :).

              Сейчас очень многое изменяю, хочется сделать более общей и более функциональной (а ещё добавить WebGL-рендер), поэтому последние версии немного неюзабельны, а на сайте многое немного не работает. Думаю, к ноябрю закончу.


  1. MTonly
    05.10.2015 00:49
    +1

    А ещё SVG автоматически адаптирует своё качество в зависимости от плотности точек дисплея (например, на 4K-мониторе с системным масштабом 200%). Векторная графика есть векторная графика.


    1. Devgru
      05.10.2015 13:00

      Для канвы это тоже делается на раз-два, см. функцию getRetinaRatio здесь.


      1. MTonly
        05.10.2015 19:32
        +1

        Ну да, можно выводить растровое изображение пропорционально бОльших размеров, вписывая его в пропорционально меньшую область (и надеясь на точное попадание каждого логического пиксела в пиксел физический — для нештриховой графики сейчас так и приходится делать), но зачем, если можно просто использовать векторную графику, именно для штриховых изображений подходящую идеально и адаптирующуюся под любую плотность точек автоматически и с гарантированным качеством? (Вопрос риторический.)


  1. Lirein
    05.10.2015 07:42
    +2

    На маломощном нетбуке наблюдаю обратную ситуацию — канвас версия очень сильно тормозит при масштабировании, очень инертная по задержкам (при наведении курсора на станцию) и т.п. SVG версия по сравнению с ней летает, скорость загрузки тоже немного выше чем Canvas версии.
    Linux Mint 17.2, amd64, Firefox, Atom D525


    1. egaxegax
      05.10.2015 23:30

      Да действительно, я уже и забыл, что в последних версиях канвасной карты метро я заменил отрисовку линий по координатам на фоновую картинку-скриншот с линиями. Отрисовываются только точки (прямоугольники) станций. Загружаться карта стала немного медленнее чем раньше (до v1.8). Зато намного быстрее стала работать на смартфоне особенно при перетаскивании и масштабировании. А SVG и правда быстрее.


  1. alexpp
    05.10.2015 09:38

    А можете дополнить карту дополнительными элементами — ССВ и депо? Например отсюда — trackmap.ru


    1. egaxegax
      05.10.2015 23:43
      +1

      Все можно. А почему бы самому не попробовать? На заказ я уже делал одну карту в своем блоге, но товарищ после этого почему-то прекратил переписку.


  1. kirichenko
    05.10.2015 10:05

    Монорельс не подписан.