新蒲京200.c软件下载-app官网网址 > 文章 >

关于这个 API 的资料

浅析 requestAnimationFrame

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

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

图片 1

信赖以后大多数人在 JavaScript 中绘制动漫已经在动用 requestAnimationFrame 了,关于 requestAnimationFrame 的各类就十分少说了,关于这几个 API 的资料,详见 http://www.w3.org/TR/animation-timing/,https://developer.mozilla.org/en/docs/Web/API/window.requestAnimationFrame。

假诺大家把时钟往前拨到引进 requestAnimationFrame 以前,假设在 JavaScript 中要达成动漫效果,如何做呢?无外乎使用 setTimeout 或 setInterval。那么难点就来了:

  • 怎么着规定科学的年月间距(浏览器、机器硬件的习性各不相近)?
  • 飞秒的不正确性怎么消除?
  • 什么样防止超负荷渲染(渲染频率太高、tab 不可以见到等等)?

开荒者能够用不胜枚举艺术来缓解这么些难点的病症,可是彻底化解,那些、基本、很难。

算是,难点的来源于在于时机。对于前端开拓者来讲,setTimeout 和 setInterval 提供的是八个等长的反应计时器循环(timer loop),不过对于浏览器内核对渲染函数的响应以至什么时候能够发起下四个动漫帧的机遇,是完全不精通的。对于浏览器内核来说,它能够领悟发起下二个渲染帧的贴切机遇,不过对于任何 set提姆eout 和 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

下一篇:没有了