Chrome请求动画帧问题 [英] Chrome requestAnimationFrame issues

查看:116
本文介绍了Chrome请求动画帧问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

相关主题: requestAnimationFrame垃圾回收



I'我一直致力于为构建触控设备的小部件提供流畅的动画,并且我发现可以帮助我的工具之一就是Chrome内存时间线屏幕。

这有助于我评估我在rAF循环中的内存消耗情况,但我对这个行为的几个方面感到困扰,点。



当我最初进入我的页面时,其中有rAF循环运行,我看到了这一点。



看起来没问题。如果我已经完成了我的工作并在我的内部循环中消除了对象分配,那么不应该有锯齿。这是与链接主题一致的行为,也就是说,无论何时使用rAF,Chrome都有内置泄漏。 (yikes!)



当我开始在页面中做各种事情时,它变得更有趣。





我没有做任何不同的事情,只是暂时添加了两个元素,这些元素将CSS3 3D变换样式应用于几帧,然后停止与它们进行交互。

我们在这里看到的是,Chrome报告突然间每一次射击(16ms)都会导致 Animation Frame Fired x 3

这个重复和它的速率单调递增,直到页面刷新。

您可以在第二个screencap中看到在从> Animation Fired Animation Fired x 3

不久之后,它跳到 x 21





它会出现我的代码正在运行一大堆额外的时间,但所有额外的多次运行只是浪费热量,丢弃计算。

当我拿着第三个屏幕盖帽时,我的Macbook变得非常糟糕。不久之后,在我能够清理时间线到结束位(大约8分钟)以查看 x 数字增加到了什么程度之前,检查器窗口变得完全没有响应,并且我被提示说我的页面没有响应并且不得不被终止。



以下是页面中运行的所有代码:

  // ================================================ ============================ 
//版权所有(c)2013 Steven Lu

/ /特此免费授予任何获得本软件和相关文档文件(软件),
// //无限制地处理本软件的
副本的人,包括但不限于
//使用,复制,修改,合并,发布,分发,再许可
//和/或销售本软件副本的权利,并允许获得
//根据以下条件提供软件:

//上述版权声明和本许可声明应包含在
中//所有副本或大部分的软件。

//本软件按原样提供,不附有任何形式的明示或暗示的保证,包括但不限于对适销性的保证,
/ /针对特定用途的适用性和不侵权。在任何情况下,
//作者或版权所有者均不对任何索赔,损害赔偿或其他
//责任承担责任,无论是在合同,民事侵权行为还是其他方面的行为中引起的
//从本软件或本软件的使用或其他交易
//以外或与本软件相关或与之相关。
// ============================================ ================================

//这是一个真实的速度verlet积分器,这意味着发送
// in来获得力和扭矩函数(不是数值)。如果提供
//的力量在当前时间步骤被评估,那么我认为我们剩下的是简单的
//旧欧拉整合。这是一个3自由度积分器,用于使用
//具有2D刚体,但它对建模3d点
//动态应该同样有用。

//这样做是为了尽量减少内存状态下的内存浪费。

函数vel_verlet_3(state,acc,dt){
var x = state [0],
y = state [1],
z = state [2],
vx = state [3],
vy = state [4],
vz = state [5],
ax = state [6],
ay = state [7],
az = state [8],
x1 = x + vx * dt + 0.5 * ax * dt * dt,
y1 = y + vy * dt + 0.5 * ay * dt * dt,
z1 = z + vz * dt + 0.5 * az * dt * dt,//等式1
a1 = acc(x1,y1,z1),
ax1 = a1 [0],
ay1 = a1 [1],
az1 = a1 [2];
state [0] = x1;
state [1] = y1;
state [2] = z1;
state [3] = vx + 0.5 *(ax + ax1)* dt,
state [4] = vy + 0.5 *(ay + ay1)* dt,
state [5] = vz + 0.5 *(az + az1)* dt; // eqn 2
state [6] = ax1;
state [7] = ay1;
state [8] = az1;
}

//速度独立acc ---这是需要尽快更改
var acc = function(x,y,z){
return [0,0,0];
};
$(#lock)。click(function(){
var values = [Number($('#ax')。val()),Number($('#ay')) .val()),Number($('#az').val())];
acc = function(){
返回值;
};
} );

//从一个角度获得罪与cos。
//分配任何东西。
函数getRotation(angle,cs){
cs [0] = Math.cos(angle);
cs [1] = Math.sin(angle);
}

//将本地点提供为[x,y]。
//分配任何东西。
function global(bodystate,localpoint,returnpoint){
getRotation(bodystate [2],returnpoint);
//现在返回点包含角度的余弦+正弦。
var px = bodystate [0],py = bodystate [1];
var x = localpoint [0],y = localpoint [1];
// console.log('global():',cs,[px,py],localpoint,'with',[x,y]);
// [c -s px] [x]
// [s c py] * [y]
// [1]
var c = returnpoint [0];
var s = returnpoint [1];
returnpoint [0] = c * x - s * y + px;
returnpoint [1] = s * x + c * y + py;
}

函数local(bodystate,globalpoint,returnpoint){
getRotation(bodystate [2],returnpoint);
//现在返回点包含角度的余弦+正弦
var px = bodystate [0],py = bodystate [1];
var x = globalpoint [0],y = globalpoint [1];
// console.log('local():',cs,[px,py],globalpoint,'with',[x,y]);
// [c s] [x - px]
// [-s c] * [y - py]
var xx = x - px,yy = y - py;
var c = returnpoint [0],s = returnpoint [1];
returnpoint [0] = c * xx + s * yy;
returnpoint [1] = -s * xx + c * yy;
}

var cumulativeOffset = function(element){
var top = 0,left = 0;
do {
top + = element.offsetTop || 0;
left + = element.offsetLeft || 0;
element = element.offsetParent;
} while(element);
return {
top:top,
left:left
};
};

//帮助创建/分配位置调试器(处理单个点)
//这里的偏移量是一个boundingclientrect偏移量,需要window.scrollXY更正
var hasDPOffsetRun = false;
var dpoff = false;
函数debugPoint(position,id,color,offset){
if(offset){
position [0] + = offset.left;
position [1] + = offset.top;
}
// if(position [0]> = 0){console.log('debugPoint:',id,color,position); }
var element = $('#point'+ id);
if(!element.length){
element = $('< div>< / div>)
.attr('id','point'+ id)
.css({
pointerEvents:'none',
position:'absolute',
backgroundColor:color,
border:'#fff 1px solid',
top:-2,
left:-2,
width:2,
height:2,
borderRadius:300,
boxShadow:'0 0 6px 0'+ color
});
$('body')。append(
$('< div>< / div>')
.addClass('debugpointcontainer')
.css({
position:'absolute',
top:0,
left:0
})
.append(element)
);
if(!hasDPOffsetRun){
//确定附加主体的绝对元素的偏移量。身体的边际利润
//是主要的犯罪分子,他们往往会对我们的狗屎施加一个扳手。
var dpoffset = $('。debugpointcontainer')[0] .getBoundingClientRect();
dpoff = [dpoffset.left + window.scrollX,dpoffset.top + window.scrollY];
hasDPOffsetRun = true;
}
}
if(dpoff){
position [0] - = dpoff [0];
position [1] - = dpoff [1];
}
//设置位置
元素[0] .style.webkitTransform ='translate3d('+ position [0] +'px,'+ position [1] +'px,0 )';
}

var elements_tracked = [];

/ *
var globaleventhandler = function(event){
var t = event.target;
if(false){// t是被跟踪元素的子元素...

}
};

//在加载库时,GRAB的全局事件处理程序不是
//安装的。当第一次调用GRAB_global时,它是懒惰地安装的,所以
//如果你只是调用GRAB,那么文档不会得到任何处理程序
//附加到它。这仍然未被实现,因为不清楚定义行为的
//语义是什么。使用
//直接API
函数会更直截了当GRAB_global(element,custom_behavior){
//这是初始化一个可抓取元素的入口点,所有状态
//元素可以通过它的__GRAB__元素通过
// DOM来访问,并且DOM从不被代码访问(除了通过初始
//赋值)。

//事件处理程序附加到文档,因此如果您的
//网页依赖防止事件冒泡,请使用GRAB_direct。
if(elements_tracked.indexOf(element)!== -1){
console.log('您试图在一个元素上多次调用GRAB()',
元素, 'existing elements:',elements_tracked);
}
elements_tracked.push(element);
if(elements_tracked.length === 1){//这是初始调用
document.addEventListener('touchstart',globaleventhandler,true);
document.addEventListener('mousedown',globaleventhandler,true);
}
}

//清理函数清理所有东西,返回正常的行为。
//可能会提供一个布尔真实参数来表示您希望清除CSS 3D
//变换值
函数GRAB_global_remove(cleartransform){
document.removeEventListener(' touchstart',globaleventhandler,true);
document.removeEventListener('mousedown',globaleventhandler,true);
}

* /

var mousedownelement = false;
var stop = false;
//只有一个鼠标,当我们需要处理释放指针
//时,只有一个鼠标放在远处的某个地方。
函数GRAB(element,onfinish,center_of_mass){
//此版本直接将事件处理程序分配给元素
//效率较低但更便携且独立,还有
//通过使用常规事件处理程序而非
//捕获事件处理程序可能更友好,以便您可以自定义抓取行为
//更好,也更容易定义它每元素
var offset = center_of_mass;
var pageOffset = cumulativeOffset(element);
var bcrOffset = element.getBoundingClientRect();
bcrOffset = {
left:bcrOffset.left + window.scrollX,
right:bcrOffset.right + window.scrollX,
top:bcrOffset.top + window.scrollY,
bottom:bcrOffset.bottom + window.scrollY
};
if(!offset){
offset = [element.offsetWidth / 2,element.offsetHeight / 2];
}
var model = {
state:[0,0,0,0,0,0,0,0],
offset:offset,
pageoffset:bcrOffset //记住,这些值是pre-window.scroll [XY] -corrected
};
元素.__ GRAB__ = model;
var eventhandlertouchstart = function(event){
// set
var et0 = event.touches [0];
model.anchor = [0,0];
local(model.state,[et0.pageX - bcrOffset.left - offset [0],et0.pageY - bcrOffset.top - offset [1]],model.anchor);
debugPoint([et0.pageX,et0.pageY],1,'red');
event.preventDefault();
requestAnimationFrame(step);
};
var eventhandlermousedown = function(event){
console.log('todo:reject right clicks');
// console.log('a',document.body.scrollLeft);
// set
// model.anchor = [event.offsetX - offset [0],event.offsetY - offset [1]];
model.anchor = [0,0];
var globalwithoffset = [event.pageX - bcrOffset.left - offset [0],event.pageY - bcrOffset.top - offset [1]];
local(model.state,globalwithoffset,model.anchor);
debugPoint([event.pageX,event.pageY],1,'red');
mousedownelement = element;
requestAnimationFrame(step);
};
var eventhandlertouchend = function(event){
// clear
model.anchor = false;
requestAnimationFrame(step);
};
element.addEventListener('touchstart',eventhandlertouchstart,false);
element.addEventListener('mousedown',eventhandlermousedown,false);
element.addEventListener('touchend',eventhandlertouchend,false);
elements_tracked.push(element);
//为可抓取元素分配一些有利的属性。
element.style.webkitTouchCallout ='none';
element.style.webkitUserSelect ='none';
// TODO:找出这些
的合适值element.style.MozUserSelect ='none';
element.style.msUserSelect ='none';
element.style.MsUserSelect ='none';

document.addEventListener('mouseup',function(){
if(mousedownelement){
mousedownelement .__ GRAB __。anchor = false;
mousedownelement = false;
requestAnimationFrame(step);
}
},false);

函数GRAB_remove(element,cleartransform){}
// unimpld
函数GRAB_remove_all(cleartransform){}

GRAB($('#content2 )[0]);

(function(){
var requestAnimationFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.requestAnimationFrame ;
window.requestAnimationFrame = requestAnimationFrame;
})();

var now = function(){return window.performance? performance.now():Date.now(); };
var lasttime = 0;
var abs = Math.abs;
var dt = 0;
var scratch0 = [0,0];
var scratch1 = [0,0]; //内存池
var step = function(time){
dt =(time - lasttime)* 0.001;
if(time< 1e12){
// highres timer
} else {
// ms自unix epoch
if(dt> 1e9){
dt = 0;
}
}
// console.log('dt:'+ dt);
lasttime = time;
var foundnotstopped = false;
for(var i = 0; i< elements_tracked.length; ++ i){
var e = elements_tracked [i];
var data = e .__ GRAB__;
if(data.anchor){
global(data.state,data.anchor,scratch0);
scratch1 [0] = scratch0 [0] + data.offset [0];
scratch1 [1] = scratch0 [1] + data.offset [1];
//console.log(输出全球,点);
debugPoint(scratch1,
0,'blue',data.pageoffset);
} else {
scratch1 [0] = -1000;
scratch1 [1] = -1000;
debugPoint(scratch1,0,'blue');
}
// timestep是动态的,并基于报告的时间。夹到100ms。
if(dt> 0.3){
//console.log(''+ dt +'@'+ now());
dt = 0.3;
}
vel_verlet_3(data.state,acc,dt);
e.style.webkitTransform ='translate3d('+ data.state [0] +'px,'+ data.state [1] +'px,0)'+
'rotateZ('+ data.state [2] +'rad)';
}
requestAnimationFrame(step);
};

requestAnimationFrame(step);

为了完整起见,这里是测试页面HTML:

 <!DOCTYPE html> 
< html lang =en>
< head>
< meta charset =utf-8/>
< meta http-equiv =cache-controlcontent =max-age = 0/>
< meta http-equiv =cache-controlcontent =no-cache/>
< meta http-equiv =expirescontent =0/>
< meta http-equiv =expirescontent =Tue,01 Jan 01 1:00:00 GMT/>
< meta http-equiv =pragmacontent =no-cache/>
< title> symplectic integrator测试页< / title>
< script src =zepto.js>< / script>
< script src =d3.v3.js>< / script>
< style type ='text / css'>
body {
position:relative;
margin:80px;
}
#content {
width:800px;
height:40px;
display:inline-block;
背景:lightgreen;
padding:20px;
margin:30px;
border:绿色虚线1px;
}
#content2 {
top:200px;
width:600px;
height:200px;
display:inline-block;
background:lightblue;
padding:20px;
margin:30px;
border:蓝色虚线1px;
}
< / style>
< / head>
< body>
< div id ='scrolling-placeholder'style ='background-color:#eee; height:1000px;'>< / div>
< label> dt:< input id ='dt'type ='number'step ='0.001'value ='0.016666666'/>< / label>
< label> ax:< input id ='ax'type ='number'step ='0.25'value ='0'/>< / label>
< label> ay:< input id ='ay'type ='number'step ='0.25'value ='0'/>< / label>
< label> t:< input id ='az'type ='number'step ='0.01'value ='0'/>< / label>
< button id ='lock'>设置< / button>
< button id ='zerof'onclick ='$(#ax,#ay,#az)。val(0);'>零势< / button>
< button id ='zerov'>零速度< /按钮>
< div>
< span id ='content'>内容< / span>
< span id ='content2'> content2< / span>
< / div>
< div id ='debuglog'>< / div>
< script src =rb2.js>< / script>
< / body>
< / html>

这应该满足任何向我们显示代码请求。

现在我不打赌我的生活,但我确信我至少做了一个正确使用rAF的好工作。我没有滥用任何东西,并且我已经通过这一点改进了代码,使其对Javascript内存分配非常轻松。



所以,确实绝对没有理由让Chrome浏览器尝试将我的笔记本电脑像火箭一样置入轨道。没有理由。

Safari总体上似乎处理得更好(它不会最终死掉),而且我会注意到iOS通常能够保留200x600px的div翻译并在60fps旋转。

然而,我承认,我没有看到Chrome真的死这样的,除非我有它记录内存时间表。

在这一点上,我只是在挠头。据我所知,这可能只是一些意想不到的,不可预知的与这个特定的开发工具功能(这是我的知识中的唯一一种)的交互。

然后我尝试了一些新的东西最少帮助调查这个问题的内存时间表额外回调发射:



添加这些行。

  window.rafbuf = []; 
var step = function(time){
window.rafbuf.push(time);

这基本上记录了我的rAF例程( step( )



当它正常运行时,它大概每16.7毫秒记下一次。



我得到了这个:


清楚地表明它正在用同样的时间输入参数重新运行 step()至少22倍,就像时间线试图告诉我的一样。

因此,我敢说,互联网告诉我这是预期的行为。 :)

解决方案

我觉得你有一个问题,因为你调用 requestAnimationFrame(step); 在每个 mousedown mouseup 事件上。由于你的 step()函数也是(它应该)调用 requestAnimationFrame(step); 你基本上开始新的动画循环为每个 mousedown mouseup 事件,因为你永远不会阻止它们,所以它们会积累。



我可以看到你也在代码的末尾开始了动画循环。如果您想在鼠标事件中立即重绘,您应该移出 step()函数并直接从鼠标事件处理程序调用。


$

 函数redraw(){
//绘图逻辑

函数onmousedown(){
// ...
redraw()
}
函数onmouseup(){
// ...
redraw()
}

函数step(){
redraw();
requestAnimationFrame(step);
}
requestAnimationFrame(step);


Related topic: requestAnimationFrame garbage collection

I've been working toward smooth animations in a widget that I'm building for touch devices, and one of the tools I found to help me with this has been the Chrome Memory Timeline screen.

It's helped me a bit to evaluate my memory consumption in the rAF loop, but I am bothered by a few aspects of the behavior that I am observing in Chrome 30 at this point.

When initially entering my page, which has the rAF loop running, I see this.

Looks okay. There shouldn't be a sawtooth if I have done my job and eliminated object allocations in my inner loop. This is behavior consistent with the linked topic, which is to say that Chrome has a built-in leak whenever you use rAF. (yikes!)

It gets more interesting when I start doing various things in the page.

I'm not really doing anything different, just temporarily adding two more elements which get CSS3 3D transform styles applied for a few frames, and then I stop interacting with them.

What we see here is Chrome reporting that all of a sudden every rAF firing (16ms) results in Animation Frame Fired x 3.

This repeating, and the rate at which it does so, monotonically increases until page refresh.

You can already see in the second screencap the sawtooth slope having dramatically increased after that initial jump from Animation Frame Fired to Animation Frame Fired x 3.

A short while later it has jumped to x 21:

It would appear that my code is being run a whole bunch of extra times, but all of the extra multiple runs is just wasted heat, discarded computation.

While I was taking the third screencap, my Macbook was heating up pretty badly. Shortly after, before I was able to scrub the timeline to the end bit (around 8 minutes) to see what the x number had increased to, the inspector window became completely unresponsive, and I was prompted that my page had become unresponsive and had to be terminated.

Here's the entirety of the code running in the page:

// ============================================================================
// Copyright (c) 2013 Steven Lu

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ============================================================================

// This is meant to be a true velocity verlet integrator, which means sending
// in for the force and torque a function (not a value). If the forces provided
// are evaluated at the current time step then I think we are left with plain
// old Euler integration.  This is a 3 DOF integrator that is meant for use
// with 2D rigid bodies, but it should be equally useful for modeling 3d point
// dynamics.

// this attempts to minimize memory waste by operating on state in-place.

function vel_verlet_3(state, acc, dt) {
  var x = state[0],
      y = state[1],
      z = state[2],
      vx = state[3],
      vy = state[4],
      vz = state[5],
      ax = state[6],
      ay = state[7],
      az = state[8],
      x1 = x + vx * dt + 0.5 * ax * dt * dt,
      y1 = y + vy * dt + 0.5 * ay * dt * dt,
      z1 = z + vz * dt + 0.5 * az * dt * dt,  // eqn 1
      a1 = acc(x1, y1, z1),
      ax1 = a1[0],
      ay1 = a1[1],
      az1 = a1[2];
  state[0] = x1;
  state[1] = y1;
  state[2] = z1;
  state[3] = vx + 0.5 * (ax + ax1) * dt,
  state[4] = vy + 0.5 * (ay + ay1) * dt,
  state[5] = vz + 0.5 * (az + az1) * dt; // eqn 2
  state[6] = ax1;
  state[7] = ay1;
  state[8] = az1;
}

// velocity indepedent acc --- shit this is gonna need to change soon
var acc = function(x, y, z) {
  return [0,0,0];
};
$("#lock").click(function() {
  var values = [Number($('#ax').val()), Number($('#ay').val()), Number($('#az').val())];
  acc = function() {
    return values;
  };
});

// Obtain the sin and cos from an angle.
// Allocate nothing.
function getRotation(angle, cs) {
  cs[0] = Math.cos(angle);
  cs[1] = Math.sin(angle);
}

// Provide the localpoint as [x,y].
// Allocate nothing.
function global(bodystate, localpoint, returnpoint) {
  getRotation(bodystate[2], returnpoint);
  // now returnpoint contains cosine+sine of angle.
  var px = bodystate[0], py = bodystate[1];
  var x = localpoint[0], y = localpoint[1];
  // console.log('global():', cs, [px, py], localpoint, 'with', [x,y]);
  // [ c -s px ]   [x]
  // [ s  c py ] * [y]
  //               [1]
  var c = returnpoint[0];
  var s = returnpoint[1];
  returnpoint[0] = c * x - s * y + px;
  returnpoint[1] = s * x + c * y + py;
}

function local(bodystate, globalpoint, returnpoint) {
  getRotation(bodystate[2], returnpoint);
  // now returnpoint contains cosine+sine of angle
  var px = bodystate[0], py = bodystate[1];
  var x = globalpoint[0], y = globalpoint[1];
  // console.log('local():', cs, [px, py], globalpoint, 'with', [x,y]);
  // [  c s ]   [x - px]
  // [ -s c ] * [y - py]
  var xx = x - px, yy = y - py;
  var c = returnpoint[0], s = returnpoint[1];
  returnpoint[0] = c * xx + s * yy;
  returnpoint[1] = -s * xx + c * yy;
}

var cumulativeOffset = function(element) {
  var top = 0, left = 0;
  do {
    top += element.offsetTop || 0;
    left += element.offsetLeft || 0;
    element = element.offsetParent;
  } while (element);
  return {
    top: top,
    left: left
  };
};

// helper to create/assign position debugger (handles a single point)
// offset here is a boundingclientrect offset and needs window.scrollXY correction
var hasDPOffsetRun = false;
var dpoff = false;
function debugPoint(position, id, color, offset) {
  if (offset) {
    position[0] += offset.left;
    position[1] += offset.top;
  }
  // if (position[0] >= 0) { console.log('debugPoint:', id, color, position); }
  var element = $('#point' + id);
  if (!element.length) {
    element = $('<div></div>')
    .attr('id', 'point' + id)
    .css({
          pointerEvents: 'none',
          position: 'absolute',
          backgroundColor: color,
          border: '#fff 1px solid',
          top: -2,
          left: -2,
          width: 2,
          height: 2,
          borderRadius: 300,
          boxShadow: '0 0 6px 0 ' + color
        });
    $('body').append(
        $('<div></div>')
        .addClass('debugpointcontainer')
        .css({
          position: 'absolute',
          top: 0,
          left: 0
        })
      .append(element)
    );
    if (!hasDPOffsetRun) {
      // determine the offset of the body-appended absolute element. body's margin
      // is the primary offender that tends to throw a wrench into our shit.
      var dpoffset = $('.debugpointcontainer')[0].getBoundingClientRect();
      dpoff = [dpoffset.left + window.scrollX, dpoffset.top + window.scrollY];
      hasDPOffsetRun = true;
    }
  }
  if (dpoff) {
    position[0] -= dpoff[0];
    position[1] -= dpoff[1];
  }
  // set position
  element[0].style.webkitTransform = 'translate3d(' + position[0] + 'px,' + position[1] + 'px,0)';
}

var elements_tracked = [];

/*
var globaleventhandler = function(event) {
  var t = event.target;
  if (false) { // t is a child of a tracked element...

  }
};

// when the library is loaded the global event handler for GRAB is not
// installed. It is lazily installed when GRAB_global is first called, and so
// if you only ever call GRAB then the document does not get any handlers
// attached to it.  This will remain unimplemented as it's not clear what the
// semantics for defining behavior are. It's much more straightforward to use
// the direct API
function GRAB_global(element, custom_behavior) {
  // this is the entry point that will initialize a grabbable element all state
  // for the element will be accessible through its __GRAB__ element through
  // the DOM, and the DOM is never accessed (other than through initial
  // assignment) by the code.

  // event handlers are attached to the document, so use GRAB_direct if your
  // webpage relies on preventing event bubbling.
  if (elements_tracked.indexOf(element) !== -1) {
    console.log('You tried to call GRAB() on an element more than once.',
                element, 'existing elements:', elements_tracked);
  }
  elements_tracked.push(element);
  if (elements_tracked.length === 1) { // this is the initial call
    document.addEventListener('touchstart', globaleventhandler, true);
    document.addEventListener('mousedown', globaleventhandler, true);
  }
}

// cleanup function cleans everything up, returning behavior to normal.
// may provide a boolean true argument to indicate that you want the CSS 3D
// transform value to be cleared
function GRAB_global_remove(cleartransform) {
  document.removeEventListener('touchstart', globaleventhandler, true);
  document.removeEventListener('mousedown', globaleventhandler, true);
}

*/

var mousedownelement = false;
var stop = false;
// there is only one mouse, and the only time when we need to handle release
// of pointer is when the one mouse is let go somewhere far away.
function GRAB(element, onfinish, center_of_mass) {
  // This version directly assigns the event handlers to the element
  // it is less efficient but more "portable" and self-contained, and also
  // potentially more friendly by using a regular event handler rather than
  // a capture event handler, so that you can customize the grabbing behavior
  // better and also more easily define it per element
  var offset = center_of_mass;
  var pageOffset = cumulativeOffset(element);
  var bcrOffset = element.getBoundingClientRect();
  bcrOffset = {
    left: bcrOffset.left + window.scrollX,
    right: bcrOffset.right + window.scrollX,
    top: bcrOffset.top + window.scrollY,
    bottom: bcrOffset.bottom + window.scrollY
  };
  if (!offset) {
    offset = [element.offsetWidth / 2, element.offsetHeight / 2];
  }
  var model = {
    state: [0, 0, 0, 0, 0, 0, 0, 0, 0],
    offset: offset,
    pageoffset: bcrOffset // remember, these values are pre-window.scroll[XY]-corrected
  };
  element.__GRAB__ = model;
  var eventhandlertouchstart = function(event) {
    // set
    var et0 = event.touches[0];
    model.anchor = [0,0];
    local(model.state, [et0.pageX - bcrOffset.left - offset[0], et0.pageY - bcrOffset.top - offset[1]], model.anchor);
    debugPoint([et0.pageX, et0.pageY], 1, 'red');
    event.preventDefault();
    requestAnimationFrame(step);
  };
  var eventhandlermousedown = function(event) {
    console.log('todo: reject right clicks');
    // console.log('a', document.body.scrollLeft);
    // set
    // model.anchor = [event.offsetX - offset[0], event.offsetY - offset[1]];
    model.anchor = [0,0];
    var globalwithoffset = [event.pageX - bcrOffset.left - offset[0], event.pageY - bcrOffset.top - offset[1]];
    local(model.state, globalwithoffset, model.anchor);
    debugPoint([event.pageX, event.pageY], 1, 'red');
    mousedownelement = element;
    requestAnimationFrame(step);
  };
  var eventhandlertouchend = function(event) {
    // clear
    model.anchor = false;
    requestAnimationFrame(step);
  };
  element.addEventListener('touchstart', eventhandlertouchstart, false);
  element.addEventListener('mousedown', eventhandlermousedown, false);
  element.addEventListener('touchend', eventhandlertouchend, false);
  elements_tracked.push(element);
  // assign some favorable properties to grabbable element.
  element.style.webkitTouchCallout = 'none';
  element.style.webkitUserSelect = 'none';
  // TODO: figure out the proper values for these
  element.style.MozUserSelect = 'none';
  element.style.msUserSelect = 'none';
  element.style.MsUserSelect = 'none';
}
document.addEventListener('mouseup', function() {
  if (mousedownelement) {
    mousedownelement.__GRAB__.anchor = false;
    mousedownelement = false;
    requestAnimationFrame(step);
  }
}, false);

function GRAB_remove(element, cleartransform) {}
// unimpld
function GRAB_remove_all(cleartransform) {}

GRAB($('#content2')[0]);

(function() {
  var requestAnimationFrame = window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.msRequestAnimationFrame ||
      window.requestAnimationFrame;
  window.requestAnimationFrame = requestAnimationFrame;
})();

var now = function() { return window.performance ? performance.now() : Date.now(); };
var lasttime = 0;
var abs = Math.abs;
var dt = 0;
var scratch0 = [0,0];
var scratch1 = [0,0]; // memory pool
var step = function(time) {
  dt = (time - lasttime) * 0.001;
  if (time < 1e12) {
    // highres timer
  } else {
    // ms since unix epoch
    if (dt > 1e9) {
      dt = 0;
    }
  }
  // console.log('dt: ' + dt);
  lasttime = time;
  var foundnotstopped = false;
  for (var i = 0; i < elements_tracked.length; ++i) {
    var e = elements_tracked[i];
    var data = e.__GRAB__;
    if (data.anchor) {
      global(data.state, data.anchor, scratch0);
      scratch1[0] = scratch0[0] + data.offset[0];
      scratch1[1] = scratch0[1] + data.offset[1];
      //console.log("output of global", point);
      debugPoint(scratch1,
                 0, 'blue', data.pageoffset);
    } else {
      scratch1[0] = -1000;
      scratch1[1] = -1000;
      debugPoint(scratch1, 0, 'blue');
    }
    // timestep is dynamic and based on reported time. clamped to 100ms.
    if (dt > 0.3) {
      //console.log('clamped from ' + dt + ' @' + now());
      dt = 0.3;
    }
    vel_verlet_3(data.state, acc, dt);
    e.style.webkitTransform = 'translate3d(' + data.state[0] + 'px,' + data.state[1] + 'px,0)' +
        'rotateZ(' + data.state[2] + 'rad)';
  }
  requestAnimationFrame(step);
};

requestAnimationFrame(step);

For completeness here is the test page HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="cache-control" content="max-age=0" />
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="expires" content="0" />
    <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
    <meta http-equiv="pragma" content="no-cache" />
    <title>symplectic integrator test page</title>
    <script src="zepto.js"></script>
    <script src="d3.v3.js"></script>
    <style type='text/css'>
        body {
            position: relative;
            margin: 80px;
        }
        #content {
            width: 800px;
            height: 40px;
            display: inline-block;
            background: lightgreen;
            padding: 20px;
            margin: 30px;
            border: green dashed 1px;
        }
        #content2 {
            top: 200px;
            width: 600px;
            height: 200px;
            display: inline-block;
            background: lightblue;
            padding: 20px;
            margin: 30px;
            border: blue dashed 1px;
        }
    </style>
</head>
<body>
    <div id='scrolling-placeholder' style='background-color: #eee; height: 1000px;'></div>
    <label>dt:<input id='dt' type='number' step='0.001' value='0.016666666' /></label>
    <label>ax:<input id='ax' type='number' step='0.25' value='0' /></label>
    <label>ay:<input id='ay' type='number' step='0.25' value='0' /></label>
    <label>t:<input id='az' type='number' step='0.01' value='0' /></label>
    <button id='lock'>Set</button>
    <button id='zerof' onclick='$("#ax,#ay,#az").val(0);'>Zero forces</button>
    <button id='zerov'>Zero velocities</button>
    <div>
        <span id='content'>content</span>
        <span id='content2'>content2</span>
    </div>
    <div id='debuglog'></div>
    <script src="rb2.js"></script>
</body>
</html>

That ought to satisfy any "show us the code" requests.

Now I would not bet my life on it, but I am pretty certain that I did at least an okay job of using rAF in a proper way. I am not abusing anything, and I have by this point refined the code to be very light on Javascript memory allocation.

So, really, there is absolutely no reason for Chrome to take this and attempt to ride my laptop into orbit like a rocket. No reason.

Safari in general seems to handle it better (it does not eventually die), and also I will note that iOS is generally able to maintain a 200x600px div translating and rotating at 60fps.

However, I admit that I haven't seen Chrome really die like this unless I've got it recording the memory timeline.

I'm sorta just scratching my head at this point. It's probably just some unintended, unforeseen interaction with this particular dev tool feature (the only one of its kind, to my knowledge).

So then I tried something new to at least help investigate this issue with the memory timeline extra-callback-firing:

Added these lines.

window.rafbuf = [];
var step = function(time) {
  window.rafbuf.push(time);

This basically logs out all the times that my rAF routine (the step() function) gets called.

When it's running normally it writes down a time roughly every 16.7 ms.

I got this:

That clearly indicates it's re-running step() with the same time input parameter at least 22 times, just like the timeline's trying to tell me.

So I dare you, internet, to tell me that this is intended behavior. :)

解决方案

I think you have a problem because you call requestAnimationFrame(step); on every mousedown and mouseup event. Since your step() function also (as it should) calls requestAnimationFrame(step); you essentially start new "animation loop" for each mousedown and mouseup event and since you never stop them they do accumulate.

I can see that you also start "animation loop" at the end of your code. If you want to redraw immediately on mouse event you should move drawing out of step() function and call that directly from mouse event handlers.

Samething like this:

function redraw() { 
  // drawing logic
}
function onmousedown() {
  // ...
  redraw()
}
function onmouseup() {
  // ...
  redraw()
}

function step() {
  redraw();
  requestAnimationFrame(step);
}
requestAnimationFrame(step);

这篇关于Chrome请求动画帧问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆