
Это продолжение статьи про основы 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
