requestAnimationFrame是HTML5游戏和动画必不可少的函数,相对于setTimeout或setInterval它有两个优势,一是它注册的回调函数与浏览器的渲染同步,不用担心Timer的时间间隔太长或太短。二是时间间隔相对与Timer要稳定,requestAnimationFrame注册的回调函数最高执行频率是60FPS,虽然在HTML5游戏里通常是达不到的,但是它两次调用之间时间间隔要比Timer稳定一些。前段时间我在CanTK Runtime里自己模拟过requestAnimationFrame,为了深入的理解Chrome里实现requestAnimationFrame方法,花了一点时间去读Blink的代码。

requestAnimationFrame的基本用法如下:

var start = null;
var element = document.getElementById("SomeElementYouWantToAnimate"); function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress/10, 200) + "px";
if (progress < 2000) {
window.requestAnimationFrame(step);
}
} window.requestAnimationFrame(step);

我比较关注的是回调函数的注册和调用过程:

  • 1.注册回调函数的实现。

(WebKit/Source/core/dom/ScriptedAnimationController.cpp)

ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(FrameRequestCallback* callback)
{
CallbackId id = m_callbackCollection.registerCallback(callback);
scheduleAnimationIfNeeded();
return id;
}

注册之后还需要请求重绘,scheduleAnimationIfNeeded最终会调到ThreadProxy::SendCommitRequestToImplThreadIfNeeded:

(cc/trees/thread_proxy.cc)

bool ThreadProxy::SendCommitRequestToImplThreadIfNeeded(
CommitPipelineStage required_stage) {
DCHECK(IsMainThread());
DCHECK_NE(NO_PIPELINE_STAGE, required_stage);
bool already_posted =
main().max_requested_pipeline_stage != NO_PIPELINE_STAGE;
main().max_requested_pipeline_stage =
std::max(main().max_requested_pipeline_stage, required_stage);
if (already_posted)
return false;
Proxy::ImplThreadTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread,
impl_thread_weak_ptr_));
return true;
} void ThreadProxy::SetNeedsCommitOnImplThread() {
TRACE_EVENT0("cc", "ThreadProxy::SetNeedsCommitOnImplThread");
DCHECK(IsImplThread());
impl().scheduler->SetNeedsBeginMainFrame();
} void Scheduler::SetNeedsBeginMainFrame() {
state_machine_.SetNeedsBeginMainFrame();
ProcessScheduledActions();
}
  • 2.执行回调函数的实现。

WebKit/Source/core/dom/ScriptedAnimationController.cpp

void ScriptedAnimationController::executeCallbacks(double monotonicTimeNow)
{
// dispatchEvents() runs script which can cause the document to be destroyed.
if (!m_document)
return; double highResNowMs = 1000.0 * m_document->loader()->timing().monotonicTimeToZeroBasedDocumentTime(monotonicTimeNow);
double legacyHighResNowMs = 1000.0 * m_document->loader()->timing().monotonicTimeToPseudoWallTime(monotonicTimeNow);
m_callbackCollection.executeCallbacks(highResNowMs, legacyHighResNowMs);
}
void FrameRequestCallbackCollection::executeCallbacks(double highResNowMs, double highResNowMsLegacy)
{
// First, generate a list of callbacks to consider. Callbacks registered from this point
// on are considered only for the "next" frame, not this one.
ASSERT(m_callbacksToInvoke.isEmpty());
m_callbacksToInvoke.swap(m_callbacks); for (size_t i = 0; i < m_callbacksToInvoke.size(); ++i) {
FrameRequestCallback* callback = m_callbacksToInvoke[i].get();
if (!callback->m_cancelled) {
TRACE_EVENT1("devtools.timeline", "FireAnimationFrame", "data", InspectorAnimationFrameEvent::data(m_context, callback->m_id));
InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireAnimationFrame(m_context, callback->m_id);
if (callback->m_useLegacyTimeBase)
callback->handleEvent(highResNowMsLegacy);
else
callback->handleEvent(highResNowMs);
InspectorInstrumentation::didFireAnimationFrame(cookie);
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", TRACE_EVENT_SCOPE_THREAD, "data", InspectorUpdateCountersEvent::data());
}
} m_callbacksToInvoke.clear();
}

这行代码m_callbacksToInvoke.swap(m_callbacks);比较有意思,在循环执行的callbacks会有新的callback注册进来,我在实现这个功能时,没看过这段代码,当时费了点功夫才想明白怎么搞。

上面的代码是由RenderWidgetCompositor::BeginMainFrame调过来的:

(src/content/renderer/gpu/render_widget_compositor.cc)

void RenderWidgetCompositor::BeginMainFrame(const cc::BeginFrameArgs& args) {
double frame_time_sec = (args.frame_time - base::TimeTicks()).InSecondsF();
double deadline_sec = (args.deadline - base::TimeTicks()).InSecondsF();
double interval_sec = args.interval.InSecondsF();
WebBeginFrameArgs web_begin_frame_args =
WebBeginFrameArgs(frame_time_sec, deadline_sec, interval_sec);
compositor_deps_->GetRendererScheduler()->WillBeginFrame(args);
widget_->webwidget()->beginFrame(web_begin_frame_args);
}

requestAnimationFrame在Chrome里的实现的更多相关文章

  1. LocalStorage在Chrome里的实现

    前段时间我们在实现CanTK-Runtime时,也曾在V8基础上模拟过浏览器的LocaleStorage功能,其实现非常简单:每个domain的数据使用的单独文件存储,因为同一时间只有一个游戏运行,所 ...

  2. 【27前端】base标签带有href属性会让chrome里的svg元素url失效

    一个chrome的问题,但具体原因不明. 触发条件:chrome浏览器base标签里href属性有值的时候 触发问题:svg里面的元素如果有用url的滤镜和模糊,则会失效,在firefox里和IE10 ...

  3. 玩爽了!直接在Chrome里抓取数据

    一个小测试发现可以自动做题,于是想通过脚本的方式看能不能获取相应的题库,刚好可以学习一下JS异步操作.花了一天时间,总算跑顺利了,遇到了不少坑.记录下来分享. 1.JS如何顺序执行 JS有强大的异步操 ...

  4. 直接在Chrome里抓取数据

    一个小测试发现可以自动做题,于是想通过脚本的方式看能不能获取相应的题库,刚好可以学习一下JS异步操作.花了一天时间,总算跑顺利了,遇到了不少坑.记录下来分享. 1.JS如何顺序执行 JS有强大的异步操 ...

  5. favicon.ico在chrome里显示正常,在ie,edge浏览器中不显示

    代码: <head> <meta charset="UTF-8"> <link href="imgs/favicon.ico" r ...

  6. 关闭在chrome里使用双指前进后退页面的功能

    defaults write com.google.Chrome AppleEnableSwipeNavigateWithScrolls -bool FALSE

  7. 在CHROME里安装 VIMIUM 插件, 方便操作

    VIMIUM 插件使用方法 VIMIUM 命令列表 网页导航 j, :向下滚动网页 k, :向上滚动网页 h : 向左滚动 l : 向右滚动 gg : 滚动到网页头部 G : 滚动到网页底部 :向上翻 ...

  8. 如何在CHROME里调试前端代码?

    以前看前端们调得很神的, 刚看书到这里,作一个记录,演练了一下,确实有点神!!! :) <!DOCTYPE html> <html lang="en"> & ...

  9. Chrome 里的请求报错 "CAUTION: Provisional headers are shown" 是什么意思?

    在调试器中看到文件显示提示为 CAUTION: Provisional headers are shown, 可是直接复制链接访问资源却可以正常访问, 最后发现是https 问题,资源采用ssl协议, ...

随机推荐

  1. java实现UDP协议传输DatagramSocket

    摘自:http://blog.csdn.net/wintys/article/details/3525643/ Server端. package com.topca.server; import ja ...

  2. Python3基础 for循环 遍历并输出一个字符串的列表

    镇场诗:---大梦谁觉,水月中建博客.百千磨难,才知世事无常.---今持佛语,技术无量愿学.愿尽所学,铸一良心博客.------------------------------------------ ...

  3. PS(photoshop)里A4纸张的像素是多大?

    A4纸的尺寸是210mm×297mm, 当你设定的分辨率是72像素/英寸时,A4纸的尺寸的图像的像素是595×842, 当你设定的分辨率是150像素/英寸时,A4纸的尺寸的图像的像素是1240×175 ...

  4. MusigCV安装

    首先,将下载的安装文件zip包,http://www.mathworks.com/products/compiler/mcr/ MCR2013a 然后依次执行下面的命令: 进入目录:cd /tmp 解 ...

  5. [SAP ABAP开发技术总结]几个小问题

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  6. 部署PDA程序的时候存储不足的解决办法

    通常Windows Mobile的存储空间分为程序内存和存储内存,默认都比较小,当程序比较大的时候可能无法正常部署到设备上面,针对此问题可采用如下办法解决:1.通过VS修改,工具--选项--设备--选 ...

  7. CUBRID学习笔记 31 通过select创建表

    语法 CREATE {TABLE | CLASS} <table_name> [( <column_definition> [,<table_constraint> ...

  8. CA*Layer(CAReplicatorLayer--)

    CAReplicatorLayer (反射应用) 指定一个继承于UIView的ReflectionView,它会自动产生内容的反射效果: + (Class)layerClass//我们也可以通过重写V ...

  9. HDU 5826 physics(物理)

     physics(物理) Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)   D ...

  10. weblogic启动报错

    重启了一次linux服务器后,weblogic启动莫名报错,查看日志发现说部署的项目有个bean类无法加载, 1.然后手动删除 已经部署的项目,先在 domais/servers/AdminServe ...