Недавно на Хабре была статья про пятнашки на Canvas.Отличная статья, уверен, новички найдут в ней много полезного. К сожалению, в комментариях высказались о немного завышеном потреблении процессора.
Это не от недостатка технологии, а от недостаточного опыта и удобных инструментов.
В этом топике я расскажу, как, при помощи LibCanvas, сделать эту игру совершенно нетребовательной к процессору и отлично выглядящей.
В игре по ссылке достаточно было исправить всего один нюанс — убрать ненужную перерисовку каждого кадра и вызывать её только при изменении холста.
Но мы пойдём дальше — введем анимацию передвижения фишек. Во время анимации при перерисовке всего холста мы будем иметь ту же проблему — неоправданно загруженный процессор, потому подумаем, что мы можем сделать.


Dirty Rectangles
Несложно заметить, что изображение меняется нечасто. Необходимо воспользоваться этим и, вместо того, чтобы перерисовывать весь холст — стираем при помощи clearRect старое месторасположение пятнашки и рисуем новое.
Нам достаточно зарисовать старую клетку и очистить новую (по сути, можно было бы постоянно сохранять предыдущее место отрисовки фишки, но это не так критично).
var Tile = atom.Class({ [...] redraw: function () { this.libcanvas.ctx .clearRect( this.lastPositionRectangle ) .clearRect( this.field.emptyRectangle ); this.draw() }, [...] })
Итак, теперь у нас есть код перерисовки фишки. Допустим, раньше наше приложение перерисовывало каждый кадр. Код передвижения фишки выглядел приблизительно так:
var Tile = atom.Class({ [...] move: function (point) { // Блокируем поле, чтобы, пока не закончится передвижение, никто не двигал фишки this.field.blocked = true; this.animate({ time: 150, props: { x: point.x, y: point.y }, onFinish: function () { // Разблокируем поле this.field.blocked = false; }, fn: 'sine-out' }); }, [...] })
Мы отключаем автоматическую перерисовку каждого кадра и добавляем код, который заставляет каждую фишку перерисовывать себя при движении:
var Tile = atom.Class({ [...] move: function (point) { // Блокируем поле, чтобы, пока не закончится передвижение, никто не двигал фишки this.field.blocked = true; this.animate({ time: 150, props: { x: point.x, y: point.y }, onProccess: this.redraw.bind(this), onFinish: function () { // Разблокируем поле this.field.blocked = false; this.redraw(); }, fn: 'sine-out' }); }, [...] })
Теперь каждый шаг будет вызываться перерисовка холста. Впринципе, этого вполне достаточно для вполне плавной анимации и практически свободного проца, но пойдём дальше.
Буферизация
Если проинспектировать приложение в консоли Javascript можно заметить, что самая значительная часть — это перерисовка холста, в которой вызываются такие тяжелые функции, как отрисовка градиента.

Для своего приложения можно грубо считать, что (program) == 'Бездействие системы'
Исправим это досадное недоразумение предварительной отрисовкой фишки в буфер. Создадим новый скрытый холст, отрисуем фишку в него и далее будем отрисовывать сам холст вместо вызова кучи тяжелых функций. Я для этого использую плагин atom.Class.Mutators.Generators — простой способ единожды сгенерировать объект и далее брать из кеша.
Допустим, раньше у нас был следующий код, который отрисовывал фишку:
var Tile = atom.Class({ [...] draw: function () { this.callHardDrawFunctions( this.libcanvas.ctx ); } [...] })
Сменим его на следующий код:
var Tile = atom.Class({ [...] Generators: { buffer: function () { var buffer = LibCanvas.buffer( this.shape, true ); this.callHardDrawFunctions( buffer.ctx ); return buffer; } }, draw: function () { this.libcanvas.ctx.drawImage({ image: this.buffer, draw : this.shape }); } [...] })
Да, оно стало немного менее изящно, но, зато¸ мы достигли цели, отрисовка наших пятнашек очень быстра и совершенно нетребовательна к ресурсам:

До введения буфера

После введения буфера
Заключение
Программируйте и наслаждайтесь результатом)