Graphics2D.js — объекты, интерактивность, анимация на canvas… И ничего лишнего

    Доброго new Date().getTimeOfDay();




    HTML5 Canvas незаменим, когда нужно что-то динамически нарисовать. Но если мы захотим что-то динамически изменять — нам придётся хранить состояние элементов и перерисовывать при необходимости.
    Если мы захотим реагировать на события — нам придётся ловить координаты мыши и определять, находятся ли они внутри нужной фигуры.
    И т.д.

    Частые повторяющиеся задачи. Так и появляются фреймворки и библиотеки.

    Впрочем, случай с Graphics2D.js немного другой: мне просто захотелось порисовать. С объектной моделью, анимацией и событиями. И — ничего лишнего.
    Но максимально расширяемо: идей много, и всё можно вынести в плагины.

    keyten.github.io/Graphics2D


    (русская версия сайта будет сегодня-завтра)
    Итак…

    Начинаем


    Контекст:
    var ctx = Graphics2D.id('mycanvas');
    
    // ну или так:
    var ctx = Graphics2D.query('canvas', 1); // второй <canvas>
    
    // или
    var ctx = Graphics2D.query( document.getElementById('mycanvas') );
    


    Нарисуем… ну, например, небольшой круг, который будет анимироваться при наведении мыши:
    ctx.circle({
    	cx : 300,
    	cy : 300,
    	radius : 50,
    	fill : '#f0a'
    }).mouseover(function(){
    	this.animate({
    		scale : 2,
    		opacity : 0.5,
    	}, 300);
    }).mouseout(function(){
    	this.animate({
    		scale : 0.5,
    		opacity : 1
    	}, 300);
    });
    

    jsfiddle.net/wzemyho6

    А теперь… пусть их будет 100:
    for(var i = 0; i < 100; i++){
    	ctx.circle({
    		cx : Math.floor(Math.random() * 700),
    		cy : Math.floor(Math.random() * 400),
    		radius : 10,
    		fill : 'rgb(' + [Math.floor(Math.random() * 255), Math.floor(Math.random() * 255), Math.floor(Math.random() * 255)].join(',') + ')'
    	}).mouseover(function(){
    		this.animate({
    			radius : 20,
    			opacity : 0.5,
    		}, 300);
    	}).mouseout(function(){
    		this.animate({
    			radius : 10,
    			opacity : 1
    		}, 300);
    	});
    }
    

    jsfiddle.net/9v63govv/4
    Что удивляет — неплохая производительность: анимация начинает ощутимо тормозить начинает при 2-3 тысячах.
    UPD. С введением requestAnimationFrame (большое спасибо Gerh) ситуация очень сильно улучшилась

    Объекты


    Встроенных рисуемых объектов 6: rect, circle, path, image, text и textblock.
    Все фигуры наследуются от внутреннего класса Shape, который содержит большинство методов, изменяющих объект (трансформации, анимация, события, заливка, обводка, прозрачность...).
    Разница между text и textblock: второй умеет переносить строки (автоматически и вручную через \n), в качестве координат указываются координаты блока, а не надписи.
    В объекте path — кривые — квадратичные и кубические Безье, эллиптические… всё, что позволяет canvas. И вдобавок, всё расширяемо: например, один из плагинов добавляет рисование Catmull-Rom.

    Любой объект мы можем создавать, указывая параметры по порядку, либо в объекте (последний позволяет дополнительные параметры:

    ctx.circle(150, 150, 70, 'red', '2px black'); // fill, stroke
    ctx.circle({
    	cx : 150,
    	cy : 150,
    	radius : 70,
    	fill : 'red',
    	stroke : '5px dot red 0.5 round',
    
    	opacity : 0.5 // а вот дополнительный параметр
    });
    

    В любом объекте мы можем указывать заливку и обводку одновременно, причём последняя позволяет сразу несколько параметров.

    А вот градиент:

    var rect = ctx.rect(100, 100, 200, 200, {
    	colors : ['red', 'green', 'blue'],
    	from : 'top',
    	to : 'bottom'
    });
    

    Также можно создать градиент отдельным объектом ctx.gradient и залить им сразу несколько фигур. А потом любое изменение градиента будет отражаться на всех фигурах.
    Да и инлайновый градиент тоже экземпляр класса градиентов, например, изменим один из цветов:

    rect.fill().color(0, 'yellow');
    


    Пути рисуются одним из трёх вариантов:
    ctx.path("M10,10 L200,200 Z", null, "2px blue");
    ctx.path([ [10,10], [200,200], [400,100,450,150] ]);
    ctx.path([
    	{ name : 'moveTo', arguments:[10,10] },
    	{ name : 'lineTo', arguments:[200,200] },
    	{ name : 'closePath' }
    ]);
    


    Объекты без заливки и обводки не рисуются, но могут реагировать на события (это нужно включить функцией path.events(true)… так будет через пару дней).

    Строковый формат — не SVG, хотя поддерживает полный его синтаксис (пропуск пробелов перед минусов, пропуск повторяющихся функций и т.п.). Поддерживает только функции M, L, C, Q и Z (только абсолютные координаты) — moveTo, lineTo, bezier, quadratic и closePath.

    На днях будет плагин, добавляющий полную поддержку SVG-путей :)

    Можно обрабатывать отдельные точки путей:
    path.point(0).name; // -> moveTo
    path.point(0).set('x', 20);
    
    path.before(1,  'L20,20 L30,50');
    


    Нативный контекст


    Мы можем создать функцию, рисующую на нативном контексте (например, чтобы оптимизировать какую-то медленную функциональность) и добавить её в перерисовку:

    ctx.push({
    	draw : function(ctx){
    		ctx.fillRect(200, 200, 10, 10);
    	}
    });
    


    При большом желании можно добавить также обработку событий (просто добавить функцию isPointIn)… Или даже унаследовать от Graphics2D.Shape (получив кучу функций для изменения стилей и трансформации)…
    Впрочем, это отдельная тема, о которой я, при желании хабрачитателей, расскажу.

    Кроме того, большинство фигур и их методов умеют принимать CSS-значения, например:
    ctx.rect("10pt", "10pt", "2em", "2em", "blue")

    Это функциональность, в смысле которой я до сих пор не уверен (будет интересно увидеть ваши комментарии на эту тему).

    Плагины


    Как я уже упоминал, Graphics2D достаточно расширяем, вот некоторые уже существующие плагины:
    — Sprite — просто-спрайты и спрайтовая анимация.
    — ImageAnim — анимация, когда разные кадры в разных файлах.
    — CatmullRom — рисование кривых Catmull-Rom (в рамках объекта Path).
    Планируются и другие (расширенная обработка событий, поддержка SVG-путей...) — как я говорил, идей много, и всё пойдёт в плагины.

    keyten.github.io/Graphics2D


    (русская версия сайта будет сегодня-завтра)
    Github: github.com/keyten/Graphics2D.
    Лицензия: MIT / LGPL.

    Некоторые демо: Bezier, Gradients, Transformations, Textblock.

    На этом всё, интересно услышать ваши отзывы.

    И ещё: большое спасибо TheShock-у за немалую помощь.

    Средняя зарплата в IT

    110 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 8 385 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

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

      +3
      Неплохо бы демо.

      > Что удивляет даже меня самого — неплохая производительность: анимация начинает ощутимо тормозить начинает при 2-3 тысячах.

      На каком железе? В каком браузере? А вообще, попахивает LibCanvas'ом.
        +1
        Firefox (в Chrome ещё быстрее).
        Демо уже добавил (под кодом ссылки на jsfiddle), можете там крутить количество итераций и смотреть.

        > А вообще, попахивает LibCanvas'ом
        В каком плане? Функции похожи?
          +3
          > Пути рисуются одним из трёх вариантов:

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

          Кстати, если во время анимации быстро вывести мышь из круга и снова навести, все размеры анимации ломаются :)
            0
            Вообще говоря, я изначально хотел реализовать полный SVG, т.к. легко переносить SVG-фигуры, ну и на ходу сочинять несложно. Вполне человекопонятный формат… Но потом понял, что реализация займёт больше, чем задумывалось, так что вынес в плагин.
            Второй вариант тоже задумывался как такой человекопонятный, но немного другой… Вроде как что удобно — то и использовать.
            Третий же изначально вообще подразумевался как удобный для генерации (там были имена свойств f, arg), но потом он тоже превратился в человекопонятный…
            _parsePath вызывается только при создании самого пути, проверки на тип простые, так что я не думаю, что сожрёт весь процессор :)

            Анимация да, там даже в коде комментарий про это есть ). Ломается из-за отсутствия очереди событий… Сделаю, как только решу, как именно.
            +3
            Почему Вы сделали апдейт через setTimeout? Для этого есть window.requestAnimationFrame же. Установка и исполнение таймеров весьма затратно.
        +6
        Ээх, велосипеды.
        pixi.js возьмите, там и 20 тысяч спрайтов спокойно будут тянуться :)
        www.goodboydigital.com/pixijs/bunnymark/ — у меня фпс начинает всаживаться (с 60 до 57 фпс, но все же) на где-то 30к, насколько помню, меньше 25 кадров в секунду — это 50к зайцев.
          0
          Честно говоря, разработка начиналась аж 2 года назад, когда велосипедом это ещё не было :)
          Потом на некоторое время пропало свободное время, и вот оно наконец появилось…

          PixiJS — вполне естественно, т.к. там WebGL (а это, если не ошибаюсь, практически прямой доступ к видеокарте).

          Я подумаю над WebGL-рендером. Тем более, непосредственно за рисование в Graphics2D отвечают всего 5-6 функций.
            +1
            да, именно, там фолбэк до канваса, если нет webgl. потому он и лучше для быстрой графики, к тому же — большое сообщество.

            проблема таких велосипедов не в том что они хуже, а в том, что не поддерживаются. Я вот с vue.js мучаюсь. Он проще, быстрее и эффективнее ангуляра, но реально мешающие баги автор очень медленно исправляет.
              0
              О, интересная библиотека (про vue.js). На беглый взгляд напоминает Ractive.js

              Собственно, сам Graphics2D начался с проблем с одним из таких фремйворков ( вот этого: jcscript.com/ ), в определённый момент мне пришла мысль — а почему бы не написать свой?
              Сам Graphics2D собираюсь поддерживать, как минимум, пока не пофикшу максимум (прошу прощения за тавтологию) багов, всех, что найду, и не реализую несколько интересных мне идей в плагинах.
          +1
            +2
            new Date().getTimeOfDay();

            Uncaught TypeError: undefined is not a function
              0
              Разумеется, это псевдокод :)

              Date.prototype.getTimeOfDay = function(){
                var h = this.getHours();
                if(h <= 3 || h > 23)
                   return "ночь";
                if(h > 3 && h < 11)
                   return "утро";
                if(h >= 12 && h < 17)
                   return "день";
                return "вечер";
              }
              
                +1
                Сейчас вам конечно же скажут про то, что модифицировать стандартные прототипы — плохо :)
                  0
                  Обязательно скажут :)
                  Но здесь это необходимо для совместимости и работающего кода.
              +2
              В примерах если быстро водить мышкой, то круги или сжимаются в точку или разрастаются на весь экран.
              Баг или фича?

              Win x64, Opera последняя.
                –1
                Баг, во всех браузерах, из-за отсутствия очереди анимаций (пока над ней думаю). То есть, анимация уменьшения запускается до окончания анимации увеличения, в итоге получается вот так.
                Уже сообразил, как пофиксить, сделал :)

                jsfiddle.net/9v63govv/4/

                Теоретически ещё может, но практически — ничего не заметил.
                0
                какие преимущества перед cocos2d-html5?
                  0
                  Cocos2D — игровой движок, а Graphics2D — фреймворк для рисования. И больше ни для чего.
                  То есть, звуков, физики… в самой библиотеке точно не будет, а вот в плагинах могут появиться ).

                  В Cocos2D же...
                  Scene management (workflow)
                  Transitions between scenes
                  Sprites and Sprite Sheets
                  Effects: Lens, Ripple, Waves, Liquid, etc.
                  Actions (behaviours):
                  Trasformation Actions: Move, Rotate, Scale, Fade, Tint, etc.
                  Composable actions: Sequence, Spawn, Repeat, Reverse
                  Ease Actions: Exp, Sin, Cubic, Elastic, etc.
                  Misc actions: CallFunc, OrbitCamera, Follow, Tween
                  Assets manager (hot update)
                  Basic menus and buttons
                  Integrated with physics engines: Chipmunk and Box2d
                  Particle system
                  Skeleton Animations: Spine and Armature support
                  Fonts:
                  Fast font rendering using Fixed and Variable width fonts
                  Support for .ttf fonts
                  Tile Map support: Orthogonal, Isometric and Hexagonal
                  Parallax scrolling
                  Motion Streak
                  Render To Texture
                  Touch/Accelerometer on mobile devices
                  Touch/Mouse/Keyboard on desktop
                  Sound Engine support (CocosDenshion library) based on OpenAL or WebAudio on web
                  Integrated Slow motion/Fast forward
                  Fast and compressed textures: PVR compressed and uncompressed textures, ETC1 compressed textures, and more
                  Resolution Independence
                  Modularized engine for customization
                  Open Source Commercial Friendly: Compatible with open and closed source projects
                  OpenGL ES 2.0 (mobile) / OpenGL 2.1 (desktop) based
                  Full WebGL support and auto canvas fallback

                  www.cocos2d-x.org/wiki/Cocos2d-JS


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

                  А вот Cocos2D — это большой внушительный инструмент, с физикой, эффектами, WebGL, скелетной анимацией, частицами, тайлами, параллаксом, звуком…

                  Всё зависит от того, что хотите делать вы :).
                  +1
                  Интересно) а есть ли какие преимущества/недостатки перед paperjs, например? Или просто захотелось создать своё?
                    0
                    Захотелось своё, да). Но не только.

                    Во-первых, скорость: самостоятельный парсинг пейпером JavaScript-а явно очень не ускоряет рисование. Да и обновление там по таймауту, а не по необходимости, насколько я помню.

                    Во-вторых: на мой взгляд, в пейпере много лишнего. Например, импорт SVG или выделение путей — они нужны не всем и каждому.
                    Имхо, вынести дополнительную функциональность в плагины — более удачная идея; чем я сейчас и занимаюсь для graphics2d.

                    Недостатки — у graphics2d сейчас функциональность немного пониже. Думаю, это будет недолго.

                    Это из того, что на виду и легко назвать :)
                    +1
                    Ну как я и говорил, это имеет мало смысла.
                    Базовые векторные операции в красивой обёртке хороши для демок на сайте, а юзать их на практике — я почти не видел такого и таких фреймворков тьма)
                    Чисто так поучиться писать на JS — похвально)
                      0
                      Я попробую добавить что-нибудь интересное)
                      А опыт в самом деле очень неплохой, да :)

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

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