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. ADO.NET增删改-------跟查不一样

    建立数据库 create database ren go use database go create table user ( code nvarchar(20) primary key,--编号 ...

  2. c#存储过程

    1. 只返回单一记录集的存储过程 SqlConnection sqlconn = new SqlConnection(conn);         SqlCommand cmd = new SqlCo ...

  3. C# DataTable的详细用法

    在项目中经常用到DataTable,如果DataTable使用得当,不仅能使程序简洁实用,而且能够提高性能,达到事半功倍的效果,现对DataTable的使用技巧进行一下总结. 一.DataTable简 ...

  4. C#日志写入

    /// <summary> /// 写日志,指定日志文件 /// </summary> /// <param name="File"></ ...

  5. Servlet技术

    Java Applet和Java Servlet都有一个共同特点: 它们都不是独立的应用程序,都没有main( )方法: 它们都不是由用户或者程序员直接调用,而是生存在容器中,由容器管理,Applet ...

  6. [Effective Java]第三章 对所有对象都通用的方法

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

  7. CUBRID学习笔记 22 插入数据

    CREATE TABLE auto_tbl(id INT AUTO_INCREMENT, name VARCHAR); 自增长的列可以插入null, 同时一次可以插入多条记录.别的和其他的sql数据库 ...

  8. log4j的使用(1) —— 简单入门篇

    这里会介绍三种打印日志的方法:控制台Console,文件File,数据库DataBase 1.下载lo4j的jar包并导入project 2.因为要在数据库添加日志,所以先新建一个库,并新建打印日志的 ...

  9. mysql密码忘记或者不知道,怎么办?

    运行cmd: 输入mysql回车,如果成功,将出现MySQL提示符 > 连接权限数据库>use mysql; (>是本来就有的提示符,别忘了最后的分号) 修改改密码:> upd ...

  10. nyoj 1058部分和问题(DFS)

    部分和问题 时间限制:1000 ms  |  内存限制:65535 KB 难度:2   描述 给定整数a1.a2........an,判断是否可以从中选出若干数,使它们的和恰好为K.   输入 首先, ...