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. vs配置opencv

    配置OpenCv: 一.新建工程 无论是mfc还是控制台程序都可以. 二.下载opencb,安装在非空格路径下面 解压出来有两个文件夹:build和source,主要用到build,build-x86 ...

  2. 获取或者设置时,无后缀和A后缀和W后缀的区别

    SetWindowTextW表示设置的字符串是WCHAR (双字节字符 )SetWindowTextA表示设置的字符串是CHAR (单字节字符 )SetWindowText表示设置的字符串是自动匹配当 ...

  3. Recast & Detour & TerrainExport Study Feeling

    Recast Navigation和Terrain Export终于算是有点成果了.今天作一个简单的总结,顺便写一些话激励自己. 这个项目的成功对于我这个算法又烂,而又不懂3d图形学的人来说,感觉真是 ...

  4. 【转载】为什么CPU有多层缓存

    原文:为什么CPU有多层缓存 http://mp.weixin.qq.com/s?__biz=MzI1NDM2Nzg5Mw==&mid=2247483712&idx=1&sn= ...

  5. FreeSWITCH的传真发送

    详细的学习请参考:https://wiki.freeswitch.org/wiki/Mod_spandsp 我只说说WIKI上没有直接写出来却又很实用的东西. 一.传真收发 环境大致如下: FreeS ...

  6. 502 Proxy Error The proxy server received an invalid response from an upstream server

    Proxy Error The proxy server received an invalid response from an upstream server. The proxy server ...

  7. ASP.NET四则运算--策略模式

    在ASP.NET中实现四则运算,同样使用了类的封装,以及策略模式.只不过是把封装的类.前台代码以及后台的代码分离开来,但同样是要达到功能的实现. Calculator.cs using System; ...

  8. C# 导出数据至 CSV

    有时候将Excel的数据另存到csv文件会出现csv格式错误,以下示例实现将DataTable里面的数据直接保存到csv文件. System.Web.HttpRuntime.Cache["v ...

  9. C#打印条码的几种方式

    标题虽然是说C#,但是以下介绍的几种方法不是只能在C#中使用,在其它的语言里面也行. 总结一下常见的条码打印方法,其实打条码的方式很多,大概有以下几种: 1.斑马打印软件制作好模板,保存为.prn格式 ...

  10. CUBRID学习笔记 13 日志文件

    欢迎转载 ,转载时请保留作者信息.本文版权归本人所有,如有任何问题,请与我联系wang2650@sohu.com . 过错 CUBRID Broker Log Files  可以理解为数据库中间件日志 ...