Я работал над небольшой анимацией холста для ffconf 2015 и реализовал две важные оптимизации, которые я упустил в прошлом.
Конечный результат: больше нет гудящего вентилятора на моем ноутбуке.
В результате получается простая ретро-анимация, которая продлится всего несколько дней, поэтому я включил здесь улучшенную версию:
Распознать анимацию? Попробуйте включить целевой компьютер ?
Ради краткости (и, фактически, написания этого поста в обычные несколько часов ), я просто собираюсь поговорить о том, что я изменил.
Пиннинг ФПС
Я знал, что «правильный» подход requestAnimationFrame
заключается в использовании (rAF) для анимации, но исторически я сталкивался с проблемами в том, что частота вызовов моей функции обновления была слишком, слишком часто. Это может привести к слишком быстрой анимации или к перегреву процессора.
Одним приятным преимуществом rAF для анимации является то, что она перестает срабатывать, когда вкладка не в фокусе (т.е. если вы переключаетесь на другую вкладку). Принимая во внимание, что он setInterval
не только не выберет нужное время, но и будет продолжать работать, сжигая батарею.
TIL requestAnimationFrame
передает метку времени с высоким разрешением для обратного вызова.
Используя метку времени, мы можем получить дельту последнего запуска, и если, и только если, последний кадр был нарисован X FPS назад, тогда мы нарисуем новый кадр. Например:
var lastFrameTime = 0;
function draw(elapsedTime) {
// calculate the delta since the last frame
var delta = elapsedTime - (lastFrameTime || 0);
// queue up an rAF draw call
window.requestAnimationFrame(draw);
// if we *don't* already have a first frame, and the
// delta is less than 33ms (30fps in this case) then
// don't do anything and return
if (lastFrameTime && delta < 33) {
return;
}
// else we have a frame we want to draw at 30fps...
// capture the last frame draw time so we can work out
// a delta next time.
lastFrameTime = elapsedTime;
// now do the frame update and render work
// ...
}
Минимизируйте свои краски
Первоначально в моей демонстрации было нарисовано несколько квадратов, которые будут масштабироваться по направлению к зрителю, создавая впечатление движения. Изначально безобидно:
function draw() {
// ... calculate x, y, scale, etc
// makes the shape: |_|
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y + y2);
ctx.lineTo(x + x2, y + y2);
ctx.lineTo(x + x2, y);
ctx.stroke();
ctx.closePath();
}
// update is called on a new frame
function update() {
// ... update state then draw:
for (i = 0; i < boxes.length; i++) {
boxes[i].draw();
}
}
Это будет повторяться для каждой «коробки», анимируемой к зрителю. Так как я просто рисую линии, я могу объединить все это за один раз и сгруппировать коллективные фигуры под одним путем, а затем выполнить один штрих:
function draw() {
// ... calculate x, y, scale, etc
// makes the shape: |_|
ctx.moveTo(x, y);
ctx.lineTo(x, y + y2);
ctx.lineTo(x + x2, y + y2);
ctx.lineTo(x + x2, y);
}
// update is called on a new frame
function update() {
// ... update state then draw:
ctx.beginPath();
for (i = 0; i < boxes.length; i++) {
boxes[i].draw();
}
ctx.stroke();
ctx.closePath();
}
Это довольно крошечная оптимизация, но результат тот же, но с меньшим взаимодействием с холстом, и, учитывая, что мы стремимся к тому, чтобы входить и выходить быстро, это не плохо.
Одиночный обработчик rAF
Если у вас запущено более одной анимации, вам нужно настроить несколько обратных вызовов requestAnimationFrame
, но наличие нескольких обратных вызовов rAF не является идеальным и начинает становиться громоздким, когда вы работаете с другими, которые также хотят поставить в очередь до их анимации.
Вы действительно хотите, чтобы все было обработано в одном обработчике rAF.
Я написал небольшую суть raf.js, которая позволяет мне выполнять все мои вызовы rAF через один обработчик (и добавил некоторые тонкости, такие как пиннинг FPS и running
логический флаг).