>

深刻精通requestAnimationFrame

- 编辑:至尊游戏网站 -

深刻精通requestAnimationFrame

浅析 requestAnimationFrame

2017/03/02 · JavaScript · 1 评论 · requestAnimationFrame

初稿出处: Taobao前端团队(FED)- 腾渊   

图片 1

相信今后超越75%人在 JavaScript 中绘制动画已经在利用 requestAnimationFrame 了,关于 requestAnimationFrame 的种种就非常少说了,关于这几个 API 的资料,详见 http://www.w3.org/TR/animation-timing/,https://developer.mozilla.org/en/docs/Web/API/window.requestAnimationFrame。

若是大家把石英钟往前拨到引进 requestAnimationFrame 在此以前,假诺在 JavaScript 中要落到实处动画效果,如何是好呢?无外乎使用 set提姆eout 或 setInterval。那么难点就来了:

  • 怎么鲜明科学的时刻间距(浏览器、机器硬件的性质各个区域别样)?
  • 飞秒的不正确性怎么消除?
  • 什么样防止过度渲染(渲染频率太高、tab 不可以见到等等)?

开采者能够用非常多方法来缓慢消除那个主题素材的症状,然而通透到底消除,这几个、基本、很难。

算是,难点的来源于在于时机。对于前端开辟者来讲,setTimeout 和 setInterval 提供的是三个等长的反应计时器循环(timer loop),可是对于浏览器内核对渲染函数的响应以致曾几何时能够发起下二个动画帧的契机,是完全不打听的。对于浏览器内核来说,它可以领悟发起下四个渲染帧的适宜机会,不过对于别的setTimeout 和 setInterval 传入的回调函数奉行,都是相提并论的,它很难知晓哪位回调函数是用以动画渲染的,因而,优化的时机特别不便通晓。谬论就在于,写 JavaScript 的人询问如日方升帧卡通在哪行代码初阶,哪行代码截至,却不领会应该何时最初,应该哪天甘休,而在根本引擎来讲,事情却恰恰相反,所以两岸很难完美合作,直到 requestAnimationFrame 出现。

自身很赏识 requestAnimationFrame 这些名字,因为起得万分直白 – request animation frame,对于这一个 API 最棒的演说正是名字自个儿了。那样叁个API,你传入的 API 不是用来渲染一日千里帧动画,你上街都不好意思跟人打招呼。

由于自家是个喜欢读书代码的人,为了显示本人好学的千姿百态,特意读了下 Chrome 的代码去理解它是怎么落实 requestAnimationFrame 的(代码基于 Android 4.4):

JavaScript

int Document::requestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> callback) { if (!m_scriptedAnimationController) { m_scriptedAnimationController = ScriptedAnimationController::create(this); // We need to make sure that we don't start up the animation controller on a background tab, for example. if (!page()) m_scriptedAnimationController->suspend(); } return m_scriptedAnimationController->registerCallback(callback); }

1
2
3
4
5
6
7
8
9
10
11
int Document::requestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> callback)
{
  if (!m_scriptedAnimationController) {
    m_scriptedAnimationController = ScriptedAnimationController::create(this);
    // We need to make sure that we don't start up the animation controller on a background tab, for example.
      if (!page())
        m_scriptedAnimationController->suspend();
  }
 
  return m_scriptedAnimationController->registerCallback(callback);
}

细心看看就以为底层实现意内地大致,生成二个 ScriptedAnimationController 的实例,然后注册那个 callback。这我们就看看 ScriptAnimationController 里面做了些什么:

JavaScript

void ScriptedAnimationController::serviceScriptedAnimations(double monotonicTimeNow) { if (!m_callbacks.size() || m_suspendCount) return; double highResNowMs = 1000.0 * m_document->loader()->timing()->monotonicTimeToZeroBasedDocumentTime(monotonicTimeNow); double legacyHighResNowMs = 1000.0 * m_document->loader()->timing()->monotonicTimeToPseudoWallTime(monotonicTimeNow); // First, generate a list of callbacks to consider. Callbacks registered from this point // on are considered only for the "next" frame, not this one. CallbackList callbacks(m_callbacks); // Invoking callbacks may detach elements from our document, which clears the document's // reference to us, so take a defensive reference. RefPtr<ScriptedAnimationController> protector(this); for (size_t i = 0; i < callbacks.size(); ++i) { RequestAnimationFrameCallback* callback = callbacks[i].get(); if (!callback->m_firedOrCancelled) { callback->m_firedOrCancelled = true; InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireAnimationFrame(m_document, callback->m_id); if (callback->m_useLegacyTimeBase) callback->handleEvent(legacyHighResNowMs); else callback->handleEvent(highResNowMs); InspectorInstrumentation::didFireAnimationFrame(cookie); } } // Remove any callbacks we fired from the list of pending callbacks. for (size_t i = 0; i < m_callbacks.size();) { if (m_callbacks[i]->m_firedOrCancelled) m_callbacks.remove(i); else ++i; } if (m_callbacks.size()) scheduleAnimation(); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void ScriptedAnimationController::serviceScriptedAnimations(double monotonicTimeNow)
{
  if (!m_callbacks.size() || m_suspendCount)
    return;
 
    double highResNowMs = 1000.0 * m_document->loader()->timing()->monotonicTimeToZeroBasedDocumentTime(monotonicTimeNow);
    double legacyHighResNowMs = 1000.0 * m_document->loader()->timing()->monotonicTimeToPseudoWallTime(monotonicTimeNow);
 
    // First, generate a list of callbacks to consider.  Callbacks registered from this point
    // on are considered only for the "next" frame, not this one.
    CallbackList callbacks(m_callbacks);
 
    // Invoking callbacks may detach elements from our document, which clears the document's
    // reference to us, so take a defensive reference.
    RefPtr<ScriptedAnimationController> protector(this);
 
    for (size_t i = 0; i < callbacks.size(); ++i) {
        RequestAnimationFrameCallback* callback = callbacks[i].get();
      if (!callback->m_firedOrCancelled) {
        callback->m_firedOrCancelled = true;
        InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireAnimationFrame(m_document, callback->m_id);
        if (callback->m_useLegacyTimeBase)
          callback->handleEvent(legacyHighResNowMs);
        else
          callback->handleEvent(highResNowMs);
        InspectorInstrumentation::didFireAnimationFrame(cookie);
      }
    }
 
    // Remove any callbacks we fired from the list of pending callbacks.
    for (size_t i = 0; i < m_callbacks.size();) {
      if (m_callbacks[i]->m_firedOrCancelled)
        m_callbacks.remove(i);
      else
        ++i;
    }
 
    if (m_callbacks.size())
      scheduleAnimation();
}

这么些函数自然正是实行回调函数的地点了。那么动画是哪些被触发的吧?我们供给火速地看生机勃勃串函数(一个从下往上的 call stack):

JavaScript

void PageWidgetDelegate::animate(Page* page, double monotonicFrameBeginTime) { FrameView* view = mainFrameView(page); if (!view) return; view->serviceScriptedAnimations(monotonicFrameBeginTime); }

1
2
3
4
5
6
7
void PageWidgetDelegate::animate(Page* page, double monotonicFrameBeginTime)
{
  FrameView* view = mainFrameView(page);
  if (!view)
    return;
  view->serviceScriptedAnimations(monotonicFrameBeginTime);
}

JavaScript

void WebViewImpl::animate(double monotonicFrameBeginTime) { TRACE_EVENT0("webkit", "WebViewImpl::animate"); if (!monotonicFrameBeginTime) monotonicFrameBeginTime = monotonicallyIncreasingTime(); // Create synthetic wheel events as necessary for fling. if (m_gestureAnimation) { if (m_gestureAnimation->animate(monotonicFrameBeginTime)) scheduleAnimation(); else { m_gestureAnimation.clear(); if (m_layerTreeView) m_layerTreeView->didStopFlinging(); PlatformGestureEvent endScrollEvent(PlatformEvent::GestureScrollEnd, m_positionOnFlingStart, m_globalPositionOnFlingStart, 0, 0, 0, false, false, false, false); mainFrameImpl()->frame()->eventHandler()->handleGestureScrollEnd(endScrollEvent); } } if (!m_page) return; PageWidgetDelegate::animate(m_page.get(), monotonicFrameBeginTime); if (m_continuousPaintingEnabled) { ContinuousPainter::setNeedsDisplayRecursive(m_rootGraphicsLayer, m_pageOverlays.get()); m_client->scheduleAnimation(); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void WebViewImpl::animate(double monotonicFrameBeginTime)
{
  TRACE_EVENT0("webkit", "WebViewImpl::animate");
 
  if (!monotonicFrameBeginTime)
      monotonicFrameBeginTime = monotonicallyIncreasingTime();
 
  // Create synthetic wheel events as necessary for fling.
  if (m_gestureAnimation) {
    if (m_gestureAnimation->animate(monotonicFrameBeginTime))
      scheduleAnimation();
    else {
      m_gestureAnimation.clear();
      if (m_layerTreeView)
        m_layerTreeView->didStopFlinging();
 
      PlatformGestureEvent endScrollEvent(PlatformEvent::GestureScrollEnd,
          m_positionOnFlingStart, m_globalPositionOnFlingStart, 0, 0, 0,
          false, false, false, false);
 
      mainFrameImpl()->frame()->eventHandler()->handleGestureScrollEnd(endScrollEvent);
    }
  }
 
  if (!m_page)
    return;
 
  PageWidgetDelegate::animate(m_page.get(), monotonicFrameBeginTime);
 
  if (m_continuousPaintingEnabled) {
    ContinuousPainter::setNeedsDisplayRecursive(m_rootGraphicsLayer, m_pageOverlays.get());
    m_client->scheduleAnimation();
  }
}

JavaScript

void RenderWidget::AnimateIfNeeded() { if (!animation_update_pending_) return; // Target 60FPS if vsync is on. Go as fast as we can if vsync is off. base::TimeDelta animationInterval = IsRenderingVSynced() ? base::TimeDelta::FromMilliseconds(16) : base::TimeDelta(); base::Time now = base::Time::Now(); // animation_floor_time_ is the earliest time that we should animate when // using the dead reckoning software scheduler. If we're using swapbuffers // complete callbacks to rate limit, we can ignore this floor. if (now >= animation_floor_time_ || num_swapbuffers_complete_pending_ > 0) { TRACE_EVENT0("renderer", "RenderWidget::AnimateIfNeeded") animation_floor_time_ = now + animationInterval; // Set a timer to call us back after animationInterval before // running animation callbacks so that if a callback requests another // we'll be sure to run it at the proper time. animation_timer_.Stop(); animation_timer_.Start(FROM_HERE, animationInterval, this, &RenderWidget::AnimationCallback); animation_update_pending_ = false; if (is_accelerated_compositing_active_ && compositor_) { compositor_->Animate(base::TimeTicks::Now()); } else { double frame_begin_time = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF(); webwidget_->animate(frame_begin_time); } return; } TRACE_EVENT0("renderer", "EarlyOut_AnimatedTooRecently"); if (!animation_timer_.IsRunning()) { // This code uses base::Time::Now() to calculate the floor and next fire // time because javascript's Date object uses base::Time::Now(). The // message loop uses base::TimeTicks, which on windows can have a // different granularity than base::Time. // The upshot of all this is that this function might be called before // base::Time::Now() has advanced past the animation_floor_time_. To // avoid exposing this delay to javascript, we keep posting delayed // tasks until base::Time::Now() has advanced far enough. base::TimeDelta delay = animation_floor_time_ - now; animation_timer_.Start(FROM_HERE, delay, this, &RenderWidget::AnimationCallback); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void RenderWidget::AnimateIfNeeded() {
  if (!animation_update_pending_)
    return;
 
  // Target 60FPS if vsync is on. Go as fast as we can if vsync is off.
  base::TimeDelta animationInterval = IsRenderingVSynced() ? base::TimeDelta::FromMilliseconds(16) : base::TimeDelta();
 
  base::Time now = base::Time::Now();
 
  // animation_floor_time_ is the earliest time that we should animate when
  // using the dead reckoning software scheduler. If we're using swapbuffers
  // complete callbacks to rate limit, we can ignore this floor.
  if (now >= animation_floor_time_ || num_swapbuffers_complete_pending_ > 0) {
    TRACE_EVENT0("renderer", "RenderWidget::AnimateIfNeeded")
    animation_floor_time_ = now + animationInterval;
    // Set a timer to call us back after animationInterval before
    // running animation callbacks so that if a callback requests another
    // we'll be sure to run it at the proper time.
    animation_timer_.Stop();
    animation_timer_.Start(FROM_HERE, animationInterval, this, &RenderWidget::AnimationCallback);
    animation_update_pending_ = false;
    if (is_accelerated_compositing_active_ && compositor_) {
      compositor_->Animate(base::TimeTicks::Now());
    } else {
      double frame_begin_time = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
      webwidget_->animate(frame_begin_time);
    }
    return;
  }
  TRACE_EVENT0("renderer", "EarlyOut_AnimatedTooRecently");
  if (!animation_timer_.IsRunning()) {
    // This code uses base::Time::Now() to calculate the floor and next fire
    // time because javascript's Date object uses base::Time::Now().  The
    // message loop uses base::TimeTicks, which on windows can have a
    // different granularity than base::Time.
    // The upshot of all this is that this function might be called before
    // base::Time::Now() has advanced past the animation_floor_time_.  To
    // avoid exposing this delay to javascript, we keep posting delayed
    // tasks until base::Time::Now() has advanced far enough.
    base::TimeDelta delay = animation_floor_time_ - now;
    animation_timer_.Start(FROM_HERE, delay, this, &RenderWidget::AnimationCallback);
  }
}

专程表达:RenderWidget 是在 ./content/renderer/render_widget.cc 中(content::RenderWidget)而非在 ./core/rendering/RenderWidget.cpp 中。我最初读 RenderWidget.cpp 还因为内部并未有此外有关 animation 的代码而纠葛了比较久。

看来这里实在 requestAnimationFrame 的落到实处原理就很刚强了:

  • 挂号回调函数
  • 浏览器更新时触发 animate
  • animate 会触发全部注册过的 callback

这里的做事机制可以预知为全体权的转变,把触发帧更新的小时全部权交给浏览器内核,与浏览器的换代保持同步。那样做不仅能够免止浏览器更新与动画帧更新的不联合,又有啥不可授予浏览器丰富大的优化空间。
在往上的调用入口就那多少个了,非常多函数(RenderWidget::didInvalidateRect,RenderWidget::CompleteInit等)会触发动画检查,进而必要一遍动画帧的翻新。

这里一张图说明 requestAnimationFrame 的兑现机制(来自官方):
图片 2

题图: By Kai Oberhäuser

1 赞 1 收藏 1 评论

图片 3

前言

正文首要参谋w3c资料,从后面部分完结原理的角度介绍了requestAnimationFrame、cancelAnimationFrame,给出了有关的演示代码以致自己对促成原理的知晓和评论。


本文介绍

浏览器中卡通有三种达成格局:通过声明成分完结(如SVG中的

要素)松阳邵阳花鼓戏本实现。

能够通过setTimeout和setInterval方法来在剧本中贯彻动画,可是这么效果兴许远远不足流畅,且会占用额外的能源。可参谋《Html5 Canvas宗旨技巧》中的论述:

它们有如下的特征:

1、尽管向其传递微秒为单位的参数,它们也不可能达到规定的标准ms的准头。这是因为javascript是单线程的,恐怕会发出阻塞。

2、未有对调用动画的巡回机制举行优化。

3、未有思量到绘制动画的最棒机缘,只是后生可畏味地以有些差非常少的事件间隔来调用循环。

骨子里,使用setInterval或setTimeout来兑现主循环,根本错误就在于它们抽象等级不切合供给。大家想让浏览器实施的是意气风发套能够调控各样细节的api,完结如“最优帧速率”、“接纳绘制下意气风发帧的最好机遇”等功能。然而假如应用它们来讲,那几个现实的内情就务须由开辟者自身来形成。

requestAnimationFrame无需使用者内定循环间距时间,浏览器会基于当前页面是不是可以知道、CPU的载荷情形等源于行决定最棒的帧速率,进而更合理地运用CPU。


名词表明

动画帧乞请回调函数列表

每个Document都有叁个动画帧诉求回调函数列表,该列表能够用作是由< handle, callback>元组组成的集合。个中handle是多个整数,唯黄金时代地方统一标准识了元组在列表中的地方;callback是一个无重回值的、形参为二个时间值的函数(该时间值为由浏览器传入的从1966年2月1日到当下所经过的飞秒数)。 刚开首该列表为空。

Document

Dom模型中定义的Document节点。

Active document

浏览器上下文browsingContext中的Document被钦定为active document。

browsingContext

浏览器上下文。

浏览器上下文是显现document对象给客户的条件。 浏览器中的1个tab或叁个窗口包蕴三个甲级浏览器上下文,借使该页面有iframe,则iframe中也有和好的浏览器上下文,称为嵌套的浏览器上下文。

DOM模型

详细我的知道DOM。

document对象

当html文书档案加载成功后,浏览器会创设二个document对象。它对应于Document节点,实现了HTML的Document接口。 通过该目标可获得全套html文书档案的新闻,进而对HTML页面中的全体因素实行拜望和操作。

HTML的Document接口

该接口对DOM定义的Document接口举行了扩张,定义了 HTML 专项使用的属性和艺术。

详见The Document object

页面可知

当页面被最小化或许被切换来后台标签页时,页面为不可知,浏览器会触发二个visibilitychange事件,并设置document.hidden属性为true;切换来彰显状态时,页面为可以知道,也同样触发一个visibilitychange事件,设置document.hidden属性为false。

详见Page Visibility、Page Visibility(页面可以预知性) API介绍、微扩充

队列

浏览器让三个单线程共用来推行javascrip和立异客商分界面。那么些线程平日被称为“浏览器UI线程”。 浏览器UI线程的工作依据二个简练的行列系统,职责会被封存到行列中央政府机关到进度空闲。生意盎然旦空闲,队列中的下一个任务就被再一次提取出来并运维。那个职分仍然为运转javascript代码,要么实行UI更新,包含重绘和重排。

API接口

Window对象定义了以下五个接口:

partial interface Window {

long requestAnimationFrame(FrameRequestCallback callback);

void cancelAnimationFrame(long handle);

};


requestAnimationFrame

requestAnimationFrame方法用于公告浏览重视采集样本动画。

当requestAnimationFrame(callback)被调用时不会执行callback,而是会将元组< handle,callback>插入到动画帧央浼回调函数列表末尾(其相月组的callback正是传播requestAnimationFrame的回调函数),并且重回handle值,该值为浏览器定义的、大于0的卡尺头,唯意气风发标记了该回调函数在列表中地方。

各类回调函数都有三个布尔标记cancelled,该标记初步值为false,并且对外不可以预知。

在末端的“管理模型” 中大家会见到,浏览器在试行“采集样本全部动画”的职分时会遍历动画帧央浼回调函数列表,决断每一个元组的callback的cancelled,假如为false,则实践callback。

cancelAnimationFrame

cancelAnimationFrame 方法用于撤销早先安插的壹个动画帧更新的乞请。

当调用cancelAnimationFrame(handle)时,浏览器会设置该handle指向的回调函数的cancelled为true。

随意该回调函数是不是在动画帧央求回调函数列表中,它的cancelled都会被设置为true。

要是该handle未有针对性任何回调函数,则调用cancelAnimationFrame 不会时有发生任何专门的职业。

拍卖模型

当页面可以知道并且动画帧央求回调函数列表不为空时,浏览器会定期地走入一个“采集样本全部动画”的天职到UI线程的种类中。

这里使用伪代码来验证“采样全数动画”职责的推行步骤:

var list = {};

var browsingContexts = 浏览器一流上下文及其下属的浏览器上下文;

for (var browsingContext in browsingContexts) {

var time = 从1968年一月1日到眼下所经过的飞秒数;

var d = browsingContext的active document;  //即当前浏览器上下文中的Document节点

//如果该active document可见

if (d.hidden !== true) {

//拷贝active document的动画帧乞求回调函数列表到list中,并清空该列表

var doclist = d的动画帧伏乞回调函数列表

doclist.appendTo(list);

clear(doclist);

}

//遍历动画帧乞求回调函数列表的元组中的回调函数

for (var callback in list) {

if (callback.cancelled !== true) {

try {

//每一种browsingContext都有三个相应的WindowProxy对象,WindowProxy对象会将callback指向active document关联的window对象。

//传入时间值time

callback.call(window, time);

}

//忽视相当

catch (e) {

}

}

}

}

已消除的标题

何以在callback内部进行cancelAnimationFrame不能裁撤动画?

难题叙述

如上边包车型地铁代码会平素实行a:

var id = null;

function a(time) {

console.log("animation");

window.cancelAnimationFrame(id); //不起作用

id = window.requestAnimationFrame(a);

}

a();

由来剖判

大家来剖析下这段代码是什么样举行的:

1、执行a

(1)实践“a();”,实践函数a;

(2)执行“console.log("animation");”,打印“animation”;

(3)推行“window.cancelAnimationFrame(id);”,因为id为null,浏览器在动画帧乞求回调函数列表中找不到相应的callback,所以不发出任何事情;

(4)施行“id = window.requestAnimationFrame(a);”,浏览器会将贰个元组< handle, a>插入到Document的动画帧央浼回调函数列表末尾,将id赋值为该元组的handle值;

2、a实行完成后,实践第二个“采样全体动画”的天职

倘若当前页面向来可以见到,因为动画帧伏乞回调函数列表不为空,所以浏览器会定期地参与贰个“采集样本全数动画”的天职到线程队列中。

a实行实现后的第一个“采样全体动画”的职务试行时会进行以下步骤:

(1)拷贝Document的动画帧央浼回调函数列表到list变量中,清空Document的动画帧央求回调函数列表;

(2)遍历list的列表,列表有1个元组,该元组的callback为a;

(3)判别a的cancelled,为默许值false,所以试行a;

(4)执行“console.log("animation");”,打印“animation”;

(5)推行“window.cancelAnimationFrame(id);”,此时id指向当前元组的a(即近来正在进行的a),浏览器将

方今元组

的a的cancelled设为true。

(6)实践“id = window.requestAnimationFrame(a);”,浏览器会将

新的元组< handle, a>

插入到Document的动画帧央求回调函数列表末尾(新元组的a的cancelled为默许值false),将id赋值为该元组的handle值。

3、推行下三个“采集样板全部动画”的天职

及时贰个“采样全部动画”的任务实行时,会判别动画帧央求回调函数列表的元组的a的cancelled,因为该元组为新插入的元组,所以值为暗中同意值false,由此会延续试行a。

如此类推,浏览器会一贯循环实践a。

削株掘根方案

有上边七个方案:

1、实施requestAnimationFrame之后再施行cancelAnimationFrame。

下边代码只会实行一遍a:

var id = null;

function a(time) {

console.log("animation");

id = window.requestAnimationFrame(a);

window.cancelAnimationFrame(id);

}

a();

2、在callback外部实践cancelAnimationFrame。 下边代码只会试行一回a:

function a(time) {

console.log("animation");

id = window.requestAnimationFrame(a);

}

a();

window.cancelAnimationFrame(id);

因为实施“window.cancelAnimationFrame(id);”时,id指向了新插入到动画帧央求回调函数列表中的元组的a,所以 “采样全数动画”职分判定元组的a的cancelled时,该值为true,进而不再试行a。

注意事项

1、在处理模型 中大家已经观看,在遍历推行拷贝的动画帧央浼回调函数列表中的回调函数在此之前,Document的动画帧要求回调函数列表已经被清空了。因而只要要每每实践回调函数,必要在回调函数中再次调用requestAnimationFrame将包括回调函数的元组出席到Document的动画帧乞请回调函数列表中,进而浏览器才会另行按期加入“采集样板全部动画”的职分(当页面可以知道并且动画帧诉求回调函数列表不为空时,浏览器才会参预该任务),试行回调函数。

例如下边代码只进行1次animate函数:

var id = null;

function animate(time) {

console.log("animation");

}

window.requestAnimationFrame(animate);

下边代码会向来施行animate函数:

var id = null;

function animate(time) {

console.log("animation");

window.requestAnimationFrame(animate);

}

animate();

2、借使在实践回调函数大概Document的动画帧诉求回调函数列表被清空在此之前一再调用requestAnimationFrame插入同一个回调函数,那么列表中会有两个元组指向该回调函数(它们的handle分歧,但callback都为该回调函数),“采撷全体动画”职务会试行数次该回调函数。

举例说下边包车型地铁代码在进行“id1 = window.requestAnimationFrame(animate);”和“id2

window.requestAnimationFrame(animate);”时会将七个元组(handle分别为id1、id2,回调函数callback都为animate)插入到Document的动画帧诉求回调函数列表末尾。 因为“采集样板全部动画”职责会遍历实施动画帧央浼回调函数列表的每一种回调函数,所以在“采样全体动画”职务中会实践三遍animate。

//上面代码会打字与印刷四遍"animation"

var id1 = null,

id2 = null;

function animate(time) {

console.log("animation");

}

id1 = window.requestAnimationFrame(animate);

id2 = window.requestAnimationFrame(animate);  //id1和id2值不一样,指向列表中差别的元组,那五个元组中的callback都为同三个animate

包容性方法

上面为《HTML5 Canvas 宗旨手艺》给出的合作主流浏览器的requestNextAnimationFrame 和cancelNextRequestAnimationFrame方法,大家可一贯拿去用:

window.requestNextAnimationFrame = (function () {

var originalWebkitRequestAnimationFrame = undefined,

wrapper = undefined,

callback = undefined,

geckoVersion = 0,

userAgent = navigator.userAgent,

index = 0,

self = this;

// Workaround for Chrome 10 bug where Chrome

// does not pass the time to the animation function

if (window.webkitRequestAnimationFrame) {

// Define the wrapper

wrapper = function (time) {

if (time === undefined) {

time = +new Date();

}

self.callback(time);

};

// Make the switch

originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;

window.webkitRequestAnimationFrame = function (callback, element) {

self.callback = callback;

// Browser calls the wrapper and wrapper calls the callback

originalWebkitRequestAnimationFrame(wrapper, element);

}

}

// Workaround for Gecko 2.0, which has a bug in

// mozRequestAnimationFrame() that restricts animations

// to 30-40 fps.

if (window.mozRequestAnimationFrame) {

// Check the Gecko version. Gecko is used by browsers

// other than Firefox. Gecko 2.0 corresponds to

// Firefox 4.0.

index = userAgent.indexOf('rv:');

if (userAgent.indexOf('Gecko') != -1) {

geckoVersion = userAgent.substr(index + 3, 3);

if (geckoVersion === '2.0') {

// Forces the return statement to fall through

// to the setTimeout() function.

window.mozRequestAnimationFrame = undefined;

}

}

}

return  window.requestAnimationFrame ||

window.webkitRequestAnimationFrame ||

window.mozRequestAnimationFrame ||

window.oRequestAnimationFrame ||

window.msRequestAnimationFrame ||

function (callback, element) {

var start,

finish;

window.setTimeout(function () {

start = +new Date();

callback(start);

finish = +new Date();

self.timeout = 1000 / 60 - (finish - start);

}, self.timeout);

};

}());

window.cancelNextRequestAnimationFrame = window.cancelRequestAnimationFrame

|| window.webkitCancelAnimationFrame

|| window.webkitCancelRequestAnimationFrame

|| window.mozCancelRequestAnimationFrame

|| window.oCancelRequestAnimationFrame

|| window.msCancelRequestAnimationFrame

|| clearTimeout;


参照他事他说加以考察资料

Timing control for script-based animations

Browsing contexts

The Document object

《HTML5 Canvas大旨工夫》

理解DOM

Page Visibility

Page Visibility(页面可以看到性) API介绍、微扩充

HOW BROWSERS WORK: BEHIND THE SCENES OF MODERN WEB BROWSERS

本文由IT-综合发布,转载请注明来源:深刻精通requestAnimationFrame