Konva.js — HTML5 2d canvas framework

    image

    Приветствую. Представляю сообществу проект Konva.js.

    Konva.js — это фреймворк, который позволяет работать c canvas 2d в объектном стиле с поддержкой событий.

    Кратко список особенностей выглядит так:
    1. Объектное API
    2. Вложенные объекты и «всплытие» событий
    3. Поддержка нескольких слоёв (нескольких canvas элементов)
    4. Кэширование объектов
    5. Поддержка анимаций
    6. Настраиваемый drag&drop
    7. Фильтры
    8. Готовые к использованию объекты, включая прямоугольник, круг, изображение, текст, линия, SVG путь, ..
    9. Простое создание собственных фигур
    10. Событийная архитектура, которая позволяет разработчикам подписываться на события изменений аттрибутов, отрисовки, и так далее
    11. Сериализация и десериализация
    12. Продвинутый поиск с помощью селекторов stage.get('#foo') и layer.get('.bar')
    13. Десктоп и мобильные события
    14. Встроенная подержка HDPI устройств
    15. и еще много разного


    Далее подробней рассмотрим возможности фреймворка с примерами кода.

    Введение


    Всё начинается со Stage, который объеденяет в себе пользовательские слои (Layer).
    Каждый слой (Layer) представляет из себя один canvas элемент на странице и может содержать в себе фигуры, группы фигур или группы групп.

    Каждый элемент может быть стилизован и трансформирован.

    Как только вы настроили Stage и слои, добавили фигуры, вы можете подписываться на события, изменять свойства элементов, запускать анимацию, создавать фильтры.

    Минимальный пример кода [Результат]:

    // сначала создаём контейнер
    var stage = new Konva.Stage({
          container: 'container',  // индификатор div контейнера
          width: 500,
          height: 500
    });
    
    // далее создаём слой
    var layer = new Konva.Layer();
    
    // создаём фигуру
    var circle = new Konva.Circle({
          x: stage.width() / 2,
          y: stage.height() / 2,
          radius: 70,
          fill: 'red',
          stroke: 'black',
          strokeWidth: 4
    });
    
    // добавляем круг на слой
    layer.add(circle);
    
    // добавляем слой
    stage.add(layer);
    




    Базовые фигуры


    Konva.js поддерживает следующие фигуры: прямоугольник (Rect), круг (Circle), овал (Ellipse), линия (Line), изображение (Image), текст (Text), текстовый путь (TextPath), звезда (Star), ярлык (Label), svg путь (Path), правильный многоугольник (RegularPolygon). Так же вы можете создать собственную фигуру:

    var triangle = new Konva.Shape({
          drawFunc: function(context) {
            context.beginPath();
            context.moveTo(20, 50);
            context.lineTo(220, 80);
            context.quadraticCurveTo(150, 100, 260, 170);
            context.closePath();
    
            // специальный метод KonvaJS
            context.fillStrokeShape(this);
          },
          fill: '#00D2FF',
          stroke: 'black',
          strokeWidth: 4
    });
    




    Стили


    Каждая фигура поддерживает следующие свойства стилей:
    • Закрашивание (fill) — поддерживается сплошной цвет, градиенты и изображения
    • Контур (stroke, strokeWidth)
    • Тень (shadowColor, shadowOffset, shadowOpacity, shadowBlur)
    • Прозрачность (opacity)


    var pentagon = new Konva.RegularPolygon({
        x: stage.getWidth() / 2,
        y: stage.getHeight() / 2,
        sides: 5,
        radius: 70,
        fill: 'red',
        stroke: 'black',
        strokeWidth: 4,
        shadowOffsetX : 20,
        shadowOffsetY : 25,
        shadowBlur : 40,
        opacity : 0.5
    });
    


    Результат:



    События


    Используя Konva.js, вы легко можете подписываться на события ввода (click, dblclick, mouseover, tap, dbltap, touchstart и так далее), на события изменения аттрибутов (scaleXChange, fillChange), и на события drag&drop (dragstart, dragmove, dragend).

    circle.on('mouseout touchend', function() {
        console.log('user input');
    });
    
    circle.on('xChange', function() {
        console.log('position change');
    });
    
    circle.on('dragend', function() {
        console.log('drag stopped');
    });
    


    Рабочий код + демо

    DRAG AND DROP


    Фреймоворк имеет встоенную поддержку drag. На данный момент нет поддержки drop событий (drop, dragenter, dragleave, dragover), но они достаточно просто реализуются средствами фреймворка.

    Чтобы элемент можно было перетаскивать, достаточно поставить свойства draggable = true.

    shape.draggable('true');
    


    При этом вы сможете подписываться на drag&drop события и настраивать ограничения по перемещению. [Демо].

    Фильтры


    Konva.js включает в себя множество фильтров: размытие, инверсия, сепия, шум и так далее. Полный список доступных фильтров можно посмотреть в API документации по фильтрам.

    Пример использования фильтра:

    image.cache();
    image.filters([Konva.Filters.Invert]);
    



    Анимация


    Создавать анимацию можно двумя способами:

    1. Через объект «Animation»:

    var anim = new Konva.Animation(function(frame) {
        var time = frame.time,
            timeDiff = frame.timeDiff,
            frameRate = frame.frameRate;
        // update stuff
      }, layer);
      anim.start();
    

    [Демо]

    2. Через объект «Tween»:

    var tween = new Konva.Tween({
            node: rect,
            duration: 1,
            x: 140,
            rotation: Math.PI * 2,
            opacity: 1,
            strokeWidth: 6
    });
    tween.play();
    

    [Демо]

    Селекторы


    При построении крупного приложения, крайне удобно использовать поиск по созданным элементам. Konva.js позволяет искать объекты с помощью селекторов, используя методы find (возвращает коллекцию) и findOne (возвращает первый элемент коллекции):

    var circle = new Konva.Circle({
            radius: 10,
            fill: 'red',
            id : 'face',
            name : 'red circle'
    });
    layer.add(circle);
    // далее производим поиск
    
    // поиск по типу фигуры
    layer.find('Circle'); // все круги
    
    // поиск по id
    layer.findOne('#face');
    
    // поиск по имени (аналогия с css классами)
    layer.find('.red')
    


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


    Все созданные объекты вы можете сохранить в JSON формат, чтобы, например, сохранить на сервер или локальное хранилище:

    var json = stage.toJSON();
    

    А так же создать элементы из JSON:
    var json = '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{"x":100,"y":100,"sides":6,"radius":70,"fill":"red","stroke":"black","strokeWidth":4},"className":"RegularPolygon"}]}]}';
    
    var stage = Konva.Node.create(json, 'container');
    


    Производительность


    Konva.js имеет множество инструментов, для значительного повышения производительности.

    1. Кеширование позволяет отрисовать некоторый элемент в буфферный canvas и потом рисовать его оттуда. Это может значительно повысить производительность отрисовки сложных объектов как, например, текст или объекты с тенями и контурами.

    shape.cache();
    

    [Демо]

    2. Работа со слоями. Так как фреймворк поддерживает несколько canvas элементов, вы можете распределять объекты на ваше усмотрение. Допустим, приложение состоит из сложного фона с тектом и нескольких передвигаемый фигур. Логично будет фон и текст перенести на один слой, а фигуры на другой. При этом при обновлении положения фигур, фоновый слой можно не перерисовывать.

    [Демо]

    Более подробный список советов по повышению производительности доступен здесь: http://konvajs.github.io/docs/performance/All_Performance_Tips.html

    Заключение


    GitHub: https://github.com/konvajs/konva
    Домашняя страница: http://konvajs.github.io/
    Документация с примерами: http://konvajs.github.io/docs/
    Полное API: http://konvajs.github.io/api/Konva.html

    Данный проект является форком известной библиотеки KineticJS, которая автором уже не поддерживается.
    Список изменений от последней официальной версии KineticJS можно увидеть здесь.
    Поделиться публикацией

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

      –1
      Очень интересный проект, интересуют следующие вопросы:

      1) В чем принципиальное отличие от SVG? SVG по сути все то же самое, но без канвы.

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

      3) У вас есть поиск элементов «по имени» с точкой, как это сделано в CSS с классами. Но беда в том, что в HTML атрибут называется class, а не name, а name является эквивалентом id (содержит одно значение, не разделенное пробелами). Почему вы не назвали атрибут class?
        +2
        1. Принципиальное отличие в том, что это canvas. Что лучше canvas или svg это отдельная тема и зависит от сферы применения. Можно поискать «canvas vs svg».

        2. К сожалению не могу прокомментировать применение devicePixelRatio для десктоп устройств. Данная часть кода написанна не мной, а самостоятельно протестировать я не могу за не имением подходящего десктоп устройства. Я тестировал только на мобильном устройстве с retina. Спасибо за предложение по поводу ограничения максимального значения ratio. У вас на устройстве devicePixelRatio = 4, а чему равно «backingStoreRatio»? Так как увеличивается разрешение в (devicePixelRatio/backingStoreRatio) раз. Применение высокое разрешение опять же зависит от конкретного приложения.

        3. Так получилось исторически. Сначала у объекта мог быть уникальный id и не уникальное имя (name). Потом пришел pull request по поводу того, что имен у одного объекта могло бы быть несколько (т.е. name это одна строка где имена разделены пробелом). Потом я добавил методы «addName», «removeName», «hasName». В результате получился некоторый аналог classList DOM узла. В комментарии написал «аналогия с css классами», так как интерфейс действительно получился очень похожий, но name не имеет никакого отношения к стилям в отличии от css class.
          –1
          Принципиальное отличие в том, что это canvas.
          Но вы делаете из канвы полный аналог SVG, убирая это различие. В чем смысл?

          а чему равно backingStoreRatio?
          undefined. Насколько мне известно, этот параметр был выдуман Эпл в Сафари 7 и в 8 оно уже всегда 1 (по крайней мере в десктопе).

          Так как увеличивается разрешение в (devicePixelRatio/backingStoreRatio) раз.
          Ваше логическое разрешение, в котором вы считаете координаты, увеличивается в devicePixelRatio / backingStoreRatio, а физическое разрешение, память выделенная под канву и сложность отрисовки увеличивается в devicePixelRatio * backingStoreRatio.

          но name не имеет никакого отношения к стилям в отличии от css class.
          Аттрибут class тоже не имеет отношение к стилям, это распространенное заблуждение. Атрибут сlass это просто class данного элемента, а никакой не «css class». Просто в css есть средства удобной выборки классов и идентификаторов, но есть и другие способы выбрать элементы. Мне кажется вам нужно переименовать name в class, слишком уже очевидно сходство.
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            самое страшное, что здесь может произойти — это потеря чёткости изображения при увеличении зума
            Но фиксированный dPR равный 1 только значительно усугубляет эту проблему, а не решает ее. На мобильных точно так же можно зазумиться после загрузки страницы. Я не вижу каких-то причин фиксировать это значение только на десктопе. lavrton, давайте отменим это ограничение для десктопа?

            число 1.2999999523162841 (живой пример) ничем не хуже целого числа 2 — это же просто математика.
            На самом деле это не вся правда. Если вы нарисуете картинку в дробных координатах, она будет смазанной. А между двумя соседними картинками появится дыра. Смотреть пример.
            Если же округлять координаты перед нанесением, может появиться дыра уже в следствии округления.
            • НЛО прилетело и опубликовало эту надпись здесь
                –1
                dPR отличный от 1 уже предполагает подобное поведение by design
                Только дробный же. С целым dPR такого не происходит.

                А на мелкой сетке разрыв в 1 пиксель это уже какая-то теоретически-перфекционистская проблема
                Вообще, полоску контрастных пикселей отлично видно на любой сетке. Но я сейчас проверил на телефоне, мой пример рисуется с округленными координатами и в Хроме и в Фаерфоксе. Сюрприз.
                • НЛО прилетело и опубликовало эту надпись здесь
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • НЛО прилетело и опубликовало эту надпись здесь
              0
              Canvas, как правило, быстрее SVG, и ресурсов кушает меньше, но это от фреймворка всё зависит, а также от прямоты рук использующего.
              –2
              А физика? Физика?
                0
                Нет. И не планируется. Есть достаточно много отдельных физических движков на js.
                +1
                Может кто-нибудь сделает наконец порт “soldat 2d” в веб?
                  0
                  А я всё жду порт NFK (Quake3 in 2D) под веб :) хотя Soldat тоже был не плох в своё время.
                    0
                    эта игра была божественна! Но у меня вызывает сомнения реально ли сделать что-то такое быстрое на WebSocket?
                  0
                  Возможно ли при помощи этого фреймворка реализовать аналог preserveAspectRatio из SVG? Т.е. могу ли я сделать фуллскрин-приложение с сохранением пропорций?
                    +3
                    Как раз хотел какие преимущества в сравнении с KineticJS, и в конце увидел, что это его форк и дальнейшее развитие.
                      0
                      Да, к сожалению поддержка и развитие KineticJS его автором прекращено, так что стоит наверное в начале статьи сразу сообщить что это просто его форк, и дальше уже знакомым с кинетик, можно не читать.
                      0
                      Интересно — как у него с производительностью под мобильные устройствами? Сейчас есть задача — написать простенький по графике матч3 на html5, адекватно работающий под мобилками — никак не пожем найти специалиста, способного показать нормальную работу теста на чем то ниже айфона 5s.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          Да, особенно плавность движения фишек, особенно выпадании при совмещении 3+.
                          Есть ли вообще возможность сделать это плавнее на canvas на мобилках?
                          • НЛО прилетело и опубликовало эту надпись здесь
                              0
                              Есть очень много советов, как делать надо, и как — не надо.
                              Например, вы, возможно, рисуете по дробным координатам, или используете setInterval вместо requestAnimationFrame.
                              А ещё можно очищать не весь холст, а только часть, которая изменяется, а ещё — сохранять уже нарисованное в одну картинку, чтобы не рисовать заново.
                              Это если вкратце.

                              Лично я тестил Graphics2d.js на слабом Alcatel 993D (Android 4 с хвостиком на борту), вот эта демка: keyten.github.io/Graphics2D/examples/solarsystem/ шла вполне плавно и спокойно, правда, на нажатия реагировал не очень охотно (возможно, из-за отсутствия touch-событий в демке).
                          +1
                          Вот чёрт, да вы как-раз на том этапе, на котором был LibCanvas где-то в апреле-октябре 2010-ого)
                            +1
                            И, судя по всему, примерно там же и находится в настоящее время?
                              0
                              Кстати, о LibCanvas!
                              Нет ли где-нибудь склада со ссылками на разные примеры, документацию, tips and tricks? А то читая исходники сложно сходу понять как сделать удобнее/проще/производительнее, а пара статей на хабре покрывают далеко не все аспекты.
                            0
                            Посмотрел API, не нашел низкоуровневой математики(Point, Vector) Невнимательно смотрел? На чем к примеру реализовать collision detection?
                              0
                              Таких объектов в Konva.js нет. Коллизии можно считать самостоятельно или использовать другой фреймворк.
                              0
                              Полностью не работает в хроме, просто вижу серый канвас, и ничего не отображается, даже на официальном сайте.
                              У кого-нибудь есть та же проблема?
                                0
                                Хром Версия 42.0.2292.0 m (64-bit) полёт нормальный
                                0
                                Заменил KineticJS на Konva, и тут же выплыл баг, которого не было в KineticJS — невозможно перетаскивать объекты правой кнопкой мыши, при отпускании выскакивает браузерное меню.
                                  0
                                  Это не баг. Это было сделано специально. Сделал ограничение что только левой кнопкой мыши можно перетаскивать объекты. Думаю о том, чтобы сделать это поведение настраиваемым.
                                    0
                                    Дело в том, что у меня свой CAD-редактор. Как почти во всех таких редакторах, левой кнопкой перетаскивается объект, а правой — перемещение по рабочему полю. Теперь это поломалось, пришлось делать костыль — перемещение левой кнопкой с зажатым SHIFT.
                                      0
                                      Не думайте. Это нужно =)
                                    +1
                                    А в чём отличие от Fabric.js? (статьи о нём на хабре — один и два)

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое