Pull to refresh

Основы LibCanvas — практика

Canvas *


Это продолжение статьи про основы LibCanvas. Если первая часть затрагивала теоретические засады, то в этой части мы перейдём к практике и постараемся реализовать совсем базовые и простые вещи. Цель статьи — осилить самые основы LibCanvas, мы напишем очень простые скрипты, малопригодные для использования в реальной жизни, но вы их можете развить во что-то великое.



HTML


Структура html-файла очень простая:
<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8" />
      <script src="atom.js"></script>
      <script src="libcanvas.js"></script>
      <script src="application.js"></script>
   </head>
   <body>
      <canvas></canvas>
   </body>
</html>


Необходимо подключить последние AtomJS и LibCanvas. Новичкам лучше использовать *-full-compiled версии с репозитория.

Далее мы будем рассматривать содержимое application.js

Простая отрисовка в холст


Мы всё так же можем отрисовывать в холст при помощи контекста. Необходимо дождаться полной загрузки DOM, получить элемент canvas, его контекст и отрисовать две фигуры. Я сразу же глобализовал всё содержимое LibCanvas. В дальнейшем, когда я буду показывать примеры — будет подразумеваться только содержимое onready-функции.

Сейчас мы просто нарисуем зелёный прямоугольник и красный круг на светло-кофейный холст:
LibCanvas.extract();

atom.dom(function() {
   var canvas  = atom.dom('canvas').first;
   var context = canvas.getContext('2d-libcanvas');

   context
      .fillAll( '#efebe7' )
      .fill( new Rectangle( 75, 75, 30, 30 ), 'green' )
      .fill( new Circle   ( 50, 50, 20 )    , '#c00'  );
});



Анимация


Но такой подход позволяет отрисовывать только какие-нибудь статичные картинки. Для чего-нибудь интерактивного необходимо создать объект LibCanvas. Сделаем очень простое приложение — чёрный холст заполняется случайными прямоугольниками зелёного цвета.
Обратите внимание на два важных момента:
1. По-умолчанию холст очищается каждый кадр, потому необходимо выключить это поведение по-умолчанию при помощи { clear: null }
2. Функция, переданная в start относится к этапу рендера, потому она не будет вызываться без обновления холста, чего мы добились благодаря .addFunc( libcanvas.update ). На самом деле это плохое решение, но в данном случае оно — подходит.

var libcanvas = new LibCanvas('canvas', { clear: null });

libcanvas.ctx.fillAll('black');

libcanvas
   .start(function () {
      this.ctx.fill(
         new Rectangle({
            from: this.ctx.rectangle.getRandomPoint(),
            size: [ 2, 2 ]
         }), 'green'
      );
   })
   .addFunc( libcanvas.update );



Объекты


Теперь добавим объект. Пускай это будет отрезок чёрного цвета, который будет вращаться вокруг одной из своих точек. В этот раз нам необходимо очищать холст перед отрисовкой следующего кадра, потому мы не будет отменять clear. Смотрим код и читаем комментарии:

var Item = atom.Class({
   Implements: [ Drawable ],
   // В конструкторе мы принимаем два аргумента -
   // Point - центр вращения и скорость вращения в радианах за секунду
   initialize: function (center, speed) {
      this.line = new Line( center,
         // Мы клонируем и смещаем точку вправо так мы получаем
         // ещё одну точку в двадцати пикселях от текущей
         center.clone().move([ 20, 0 ])
      );
      // время передаётся в милисекундах, а скорость указана в миллисекундах
      // потому необходимо привести скорость к формату "радиан/миллисекунду"
      this.speed = speed / 1000;
   },
   update: function (time) {
      // мы вращаем вторую точку прямой вокруг первой
      this.line.to.rotate(
         // для того, чтобы скорость вращения не зависела от fps мы
         // умножаем скорость на прошедшее время. Если fps низкий, то
         // обновляться будет реже, но изменения будут на больший угол
         this.speed * time,
         this.line.from
      );
      // Необходимо сообщить libcanvas, что изменился
      // внешний вид холста и надо бы его перерисовать
      this.libcanvas.update();
   },
   draw: function () {
      // просто рисуем прямую
      this.libcanvas.ctx
         .stroke( this.line, 'black' );
   }
});

var libcanvas = new LibCanvas('canvas');

// Мы конструируем новый объект
// Обратите внимание на запись угла. Человеку обычно привычнее считать угол в градусах
// В программировании принято хранить угол в радианах.
// Такая запись позволяет легко получить радианы из градусов
var item = new Item( new Point(50, 50), (180).degree() );


libcanvas.addElement( item )
   // Очень важно не забыть "запустить" LibCanvas, он ждёт вашей команды "start"
   .start()
   // обратите внимание, что если мы просто передадим "item.update", то он будет вызван
   // с неверным контекстом, потому привязываем его при помощи bind к объекту:
   .addFunc( item.update.bind(item) );


У нас получилась крутящаяся стрелочка.

Реакция на мышь


Давайте возьмём нашу стрелочку в красный круг, который можно таскать. Это достаточно просто, я покажу, что изменилось, как видите, всего несколько строчек. Обратите внимание, что я создал свойство shape, а не circle. Это необходимо для Draggable.

var Item = atom.Class({
   Implements: [ Drawable, Draggable /* Таскаемый */ ],
   [...]
   initialize: function (center, speed) {
      [...]
      // создаём круг
      this.shape = new Circle( center, 25 );
   },
   [...]
   draw: function () {
      [...]
         .stroke( this.shape, '#c00' );
   }
});

[...]
libcanvas.listenMouse();
item.draggable()


Мы видим, что практически всё заработало, но есть ошибка, когда мы тягаем круг — стрелка меняет свою длину. Секрет в том, что, когда мы перемещаем круг, также перемещается точка center, которая является началом отрезка. Конец отрезка остаётся на месте и начинает вертеться по новой траектории. Его необходимо смещать вместе с началом. Это очень легко сделать, подписавшись на событие move у точки:

initialize: function (center, speed) {
   [...]
   // Необходимо, чтобы вторая точка двигалась вместе с первой
   center.addEvent('move', function (diff) {
      this.line.to.move(diff);
   }.bind(this))
},


Теперь корректно

Секундомер


Теперь добавим ещё одну стрелку и реализуем таким образом секундомер — времени, которое прошло с момента захода на страницу. Код немножко измениться, но, в основном, он будет очень похож на предыдущий, потому я прокомментирую только важные участки и приведу только код класса:

var StopWatch = atom.Class({
   Implements: [ Drawable, Draggable ],
   initialize: function (center) {
      this.center   = center;
      this.millisec = this.line();
      this.seconds  = this.line();
      this.minutes  = this.line();
      this.shape    = new Circle( center, 25 );

      center.addEvent('move', function (diff) {
         // мы вызываем метод "move" с параметром "diff" у всех точек
         [this.millisec.to, this.seconds.to, this.minutes.to].invoke('move', diff);
      }.bind(this));
   },
   // Метод для удобного создания линии
   line: function () {
      return new Line( this.center, this.center.clone().move([0, -20]) );
   },
   update: function (time) {
      var full = (360).degree();

      // Методы toSeconds, toMinutes и toHours взяты из LibCanvas.Utils.Math
      // Миллисекундная стрелка должна сделать полный оборот за секунду
      this.millisec.to.rotate( full * time.toSeconds(), this.center );
      // Секундная - за минуту
      this.seconds .to.rotate( full * time.toMinutes(), this.center );
      // Минутная - за час
      this.minutes .to.rotate( full * time.toHours()  , this.center );
      this.libcanvas.update();
   },
   draw: function () {
      this.libcanvas.ctx
         .stroke( this.shape   , '#c00' )
         .stroke( this.millisec, '#999' )
         .stroke( this.seconds , '#000' )
         .stroke( this.minutes , '#090' );
   }
});


Вот, получилось!

Заключение


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

Если у вас всё-ещё есть вопросы — можете смело писать в комментарии, на емейл shocksilien@gmail.com или в джаббер shock@jabber.com.ua
Tags:
Hubs:
Total votes 76: ↑73 and ↓3 +70
Views 10K
Comments 21
Comments Comments 21

Posts