Пользователь
0,0
рейтинг
23 февраля 2011 в 15:32

Разработка → Продвинутые анимации с requestAnimationFrame перевод

Если вы никогда не писали код для выполнения анимаций, то вы можете дальше не читать :)

Что такое requestAnimationFrame?


Во всех ваших функциях анимаций вы используете повторяющийся таймер для применения изменений каждый несколько миллисекунд. Хорошие новости: производители браузеров решили «почему бы нам не дать вам API для этого потому, что мы, возможно, сможем оптимизировать некоторые моменты для вас». Итак, это основное API для создания анимаций на основе изменения DOM стилей, перерисовки canvas или WebGL

Зачем я должен это использовать?


Браузеры могут оптимизировать анимации идущие одновременно, уменьшив число reflow и repaint до одного, что в свою очередь приведет к повышению точности анимации. Например анимации на JavaScript синхронизированные с CSS transitions или SVG SMIL. Плюс ко всему если выполняется анимация в табе, который невидим, браузеры не будут продолжать перерисовку, что приведет к меньшему использованию CPU, GPU, памяти и как следствие снизит расход батареи в мобильных устройствах.

Пример использования


    // Если ничего нет - возвращаем обычный таймер
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     || 
              function(/* function */ callback, /* DOMElement */ element){
                window.setTimeout(callback, 1000 / 60);
              };
    })();
 
 
    // Использование: 
 
    (function animloop(){
      render();
      requestAnimFrame(animloop, element);
    })();

Замечание: я использую requestAnimFrame потому что спецификация все ещё в процессе разработки и поэтому я не хочу заранее объявлять глобальный requestAnimationFrame (применять polyfill)

Посмотреть в действии можно тут: jsfiddle.net/paul/XQpzU

requestAnimationFrame API


window.requestAnimationFrame(function(/* time */ time){
	// time ~= +new Date
}, /* связанный элемент */ elem);

В callback передается текущее время, оно в любом случае будет вам необходимо. Вторым параметром передается связанный с текущей анимацией элемент (для оптимизации). Для canvas и WebGL это будет элемент <canvas>. Для прочих анимаций вы можете ничего не передавать либо определить для повышения производительности.

Уже можно использовать?


Сейчас Webkit (Nightly Safari и Chrome Dev Channel) и Mozilla (FF4) немного отличаются. Mozilla имеет баг, который лимитирует количество кадров анимации (порядка 30) На самом деле, «оно лимитируется 1000/(16 + N) fps, где N количество миллисекунд необходимое для выполнения функции анимации. Если функция занимает 1 секунду, то количество кадров будет менее 1 в секунду. Если функция анимации занимает 1 мсек, то количество кадров будет около 60» Это будет исправлено на следующем релизе FF, но после FF4. Также Chrome 10 не имеет time параметра (добавлено в m11), FF сейчас игнорирует аргумент elem.

Пишите отзывы!


Если вы работаете с анимациями, то разработчики WebKit и Gecko будут рады услышать ваши отзывы и пожелания. Посмотрите черновик спецификации requestAnimationFrame Сейчас он действует как setTimeout; стоит ли заменить на setInterval? Имеет ли это API недостатки при анимации нескольких объектов адновременно? Работает ли оптимизация elem? Покрывает ли это API все потребности анимации?

Другие источники

  1. Draft spec (authored by heycam and jamesr)
  2. Chromium design doc
  3. A basic example
  4. A more comprehensive example with some available config
  5. MDC docs on mozRequestAnimationFrame
  6. Библиотеки использующие таймеры анимаций yui anim loop, three.js, limejs, ticket for jQuery's implementation
  7. Demo by Louis-Rémi Babé при переключении табов посмотрите на загрузку процессора
  8. Nokarma's coverage of requestAnimationFrame
  9. Mozilla's Robert O'Callhan early post on rAF
Перевод: Paul Irish
Mikhail Davydov @azproduction
карма
449,5
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (36)

  • –21
    Ну-ну, а потом удивляетесь, почему «простая» анимация жрет 30-50% ресурсов CPU, а браузеры занимают по 300-400 мегабайт в памяти.
    Уже 100 лет назад придумали GIF (причем по реальной необходимости) и APNG (тут можно о необходимости поспорить) — нет, мало! Придумаем, чего-бы еще такого засунуть в наш новый стандарт, чтобы все поняли, что мы не присиживаем штаны в своем консорциуме, а напичкиваем его новыми веб-два-нольными функциями, чтобы хомячью было удобнее фейсбуки и вконтакты просматривать. Как дети, ей богу.
    • +11
      А вы, наверное, в интернете через lynx сидите?
      • 0
        К сожалению, то же Demo by Louis-Rémi Babé, с монохромными капельками забирает одно ядро процессора. Конечно умом понимаешь, что веб, что новые технологии, что все хорошо, и что так надо. А сердце помнит демки, которые на намного меньших ресурсах показывали всякое, и огорчается.
    • +1
      Толсто. Ну, аватару зато соответствуете
    • 0
      gif был придуман, чтобы сиськи анимировать
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Canvas 2D всегда сглаживает и будет сглаживать(алиазные линии сейчас больше не нужны чем нужны). Стоит обратить внимание на алгоритм Брезенхэма либо нарисовать ступенчатые линии WebGL'ем
  • 0
    Плюс ко всему если выполняется анимация в табе, который невидим, браузеры не будут продолжать перерисовку

    Что-то подобного не наблюдается в Хроме (последняя бета).
    • 0
      Все работает. Хром 10.0.648.82 beta. В примере Demo by Louis-Rémi Babé нужно нажать Interval Bookmarklet затем включить [v] requestAnimationFrame. При смене таба нагрузка ЦП снижается с 28% до 7%
    • 0
      Вообще говоря, safari mobile давно уже таймеры не обрабатывает неактивной вкладке. Про другие ничего не знаю, но у меня есть подозрения, что реализовать это совсем не сложно и все это итак уже умеют.
      А для десктопа это не так критично.
  • 0
    function(/* function */ callback, /* DOMElement */ element){
                    window.setTimeout(callback, 1000 / 60);
                  };


    Это ничего, что тут element теряется? Я бы сделал так:

    function(/* function */ callback, /* DOMElement */ element){
                    window.setTimeout(function() { callback.call(callback,element); }, 1000 / 60);
                  };

    • 0
      В оригинале элемент передается в функцию анимации для оптимизации рефлоу и репаинтов связанных с этим элементом. От того, что мы передадим его в нашу функцию-заменитель ничего не изменится.
      • 0
        А, ну да, ускользнуло как-то
  • 0
    Ну что сказать, правильно всё.
    • 0
      Не всё. fallback должна возвращать id таймера. Ну то есть, там должен быть return, согласно спецификации. Тогда можно будет организовать и останов анимации. Я написал об этом в комментах к оригинальной заметке, но Пол текст не исправил.
  • 0
    Как-то совсем совсем никакого эффекта кроме нормирования FPS не наблюдается.
    Где обещанные оптимизация reflow — не знаю :(
  • 0
    function(/* function */ callback, /* DOMElement */ element){
      window.setTimeout(callback, 1000 / 60);
    };
    

    Кажется, тут ошибка и необходимо так:
    function(/* function */ callback, /* DOMElement */ element){
      window.setInterval(callback, 1000 / 60);
    };
    


    requestAnimationFrame вызывается приблизительно раз в 17 мс.
    а если у меня вычисления длятся больше — он будет захлёбываться, или пропускать кадры?
    • 0
      Понял, глупость сморозил.
      • 0
        Эм, а можете пояснить? А то я тоже об этом задумался, но не понял, почему это глупость.
        • 0
          17ms = 60 fps
          • 0
            А, ну да, растянуть вычисления на 17ms — надо постараться.
        • 0
          Я сказал глупость про setInterval вместо setTimeout. RAF работает как setTimeout
          • 0
            А, вот оно что. А по поводу второго вопроса никаких ответов пока нет?
            • 0
              Она как setTimeout только с синхронизацией с «внутренним таймером перерисовки страницы» и не дает завышать rps
              • 0
                Да нет, мне понятно, как работает requestAnimationFrame, вопрос про другое: если вычисления длятся дольше, чем 17ms, то он просто пропустит кадр, или будет ждать, пока они завершатся?
                • 0
                  Как и setTimeout будет ждать, но тут скорее особенность Event Loop.
                  • 0
                    Ага, понятно, спасибо)
  • 0
    Простите, что поднимаю не очень новый пост, зашел через поиск.
    Я новичек в js+canvas, наткнулся на проблему, на которую по моим ощущениям, как-то «забивают»:

    даже самая простая анимация на канвасе в js, сделанная через setInterval с каким бы то ни было интервалом периодически устраивает небольшой еле-еле заметный подлаг (у меня раз в секунду в среднем).
    Проверено не только на моем компьютере, но и еще на пяти. У кого-то раз в 5 сек, у кого-то раз в секунду.

    Зашел на пример из этого топика
    jsfiddle.net/paul/XQpzU/
    и тоже самое — наблюдаю небольшие скачки раз в секунду, если внимательно смотреть.
    Если анимация посложнее, то подлаги есть, просто их труднее разглядеть, если много чего «мельтешит».

    При этом компьютер довольно мощный 4 ядра и все такое.

    Похожий эффект наблюдается при анимации во flash.

    Получается в браузерх пока что никак не реализовать более-менее гарантированное плавное движение?
    • 0
      Это сборщик мусора запускается.
      • 0
        хм… И получается, что не реализовать плавное перемещение хотябы просто квадратика, а-ля директ Х, ОпенГЛ и т.п. из-за этого «сборщика мусора»?
        • +1
          Сразу скажу, что я не особо эксперт в этих деталях, потому расскажу, как я понимаю ситуацию.
          Я проверял эту ситуацию через хромопрофайлер — как раз во время запуска ГК слегка подлагивает изображение.
          Да. Сборщик блокирует поток из-за которого один-другой кадр рендерится, скажем, в два раза дольше. И лучше было бы стабильных 30 фпс, чем 60 фпс, а один раз кадр одрендерённый в два раза медленнее.
          В Си такой проблемы нету из-за ручной сборки мусора (вроде, в Джава тоже есть эти протормаживания?) и из-за того, что рендер лежит на видеокарте.
          Может, поможет рендер через кадр, тогда лаги будут менее заметны
          Хотя я всё же думаю, что это не так важно =)
          • 0
            Спасибо за пояснение!
        • +1
          В Java тоже GC. GC есть разных типов: рефкаунт, который не блокирует поток, и блокирующий. Повсеместно используется блокирующий тк он может работать с циклическими ссылками.
          Весь код основного потока JavaScript работает с одной кучей, каждый раз когда браузер понимает, что нужно почистить мусор он блокирует поток — чистит и дефрагементирует память. Время простоя зависит от количества созданных и убитых объектов и контекстов в один момент времени.
          Пользователь видит GC как зависание окна. Я вот, например, часто вижу как висит WebStorm (Java)…
          Управлять GC в JavaScript, в отличии от того же Питона, нельзя (отанавливать и запускать руками). Можно только в Опере принудительно вызывать GC.
          Уменьшить время GC можно разбив кучу(Heap) на несколько мелких куч — те использовать Воркеры.

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