>웹 프론트엔드 >JS 튜토리얼 >requestAnimationFrame을 사용하여 좋은 js 애니메이션 성능을 얻으세요.javascript 기술

requestAnimationFrame을 사용하여 좋은 js 애니메이션 성능을 얻으세요.javascript 기술

WBOY
WBOY원래의
2016-05-16 15:46:541965검색

좋은 성능의 js 애니메이션을 얻으려면 requestAnimationFrame을 사용하세요. 먼저, setTimeout 및 setInterval과 비교하여 requestAnimationFrame의 장점이 무엇인지 간략하게 소개하겠습니다.

예 1:

requestAnimationFrame은 setTimeout 및 setInterval에 비해 두 가지 주요 장점이 있습니다.
1. requestAnimationFrame은 각 프레임의 모든 DOM 작업을 집중하여 한 번의 다시 그리기 또는 리플로우로 완료하며, 다시 그리기 또는 리플로우의 시간 간격은 일반적으로 브라우저의 새로 고침 빈도를 밀접하게 따릅니다. .
2. 숨겨진 요소나 보이지 않는 요소에서는 requestAnimationFrame이 다시 그려지거나 리플로우되지 않습니다. 이는 물론 CPU, GPU 및 메모리 사용량이 적음을 의미합니다.

setTimeout 및 setInterval과 마찬가지로 requestAnimationFrame은 전역 함수입니다. requestAnimationFrame을 호출한 후 브라우저에 자체 빈도에 따라 다시 그리도록 요청합니다. 브라우저가 다시 그리려고 하면 이 함수가 호출되고 이 함수에 콜백이 전달됩니다. 함수는 시간을 매개변수로 취합니다. requestAnimationFrame의 함수는 일회성이므로 애니메이션 효과를 얻으려면 애니메이션을 구현하기 위해 setTimeout을 사용하는 것처럼 requestAnimationFrame을 지속적으로 호출해야 합니다. requestAnimationFrame 함수는 requestAnimationFrame 콜백을 취소하기 위해 cancelAnimationFrame 함수에 매개변수로 전달될 수 있는 리소스 식별자를 반환합니다. setTimeout의clearTimeout과 매우 유사합니까?
따라서 requestAnimationFrame은 애니메이션에 특별히 맞춰진 setTimeout의 성능 최적화 버전이라고 할 수 있습니다. 차이점은 requestAnimationFrame은 콜백 함수 자체의 실행 시간을 지정하지 않고 브라우저에 내장된 새로 고침에 따라 콜백을 실행한다는 것입니다. 물론 브라우저가 얻을 수 있는 최고의 애니메이션 효과를 얻을 수 있습니다.
현재 requestAnimationFrame을 지원하는 일부 브라우저에는 여전히 자체 비공개 구현이 있으므로 접두사를 추가해야 합니다. requestAnimationFrame을 지원하지 않는 브라우저의 경우 두 가지의 사용법이 거의 동일하므로 setTimeout만 사용할 수 있습니다. 두 가지가 호환됩니다. 어렵지 않습니다. requestAnimationFrame을 지원하는 브라우저의 경우 requestAnimationFrame을 사용하고, 이를 지원하지 않는 브라우저의 경우 기존 setTimeout으로 단계적으로 다운그레이드합니다. 이를 캡슐화하면 모든 주요 브라우저와 균일하게 호환되는 API를 얻을 수 있습니다.
코드는 여기에서 볼 수 있습니다: https://gist.github.com/chaping/88813f56e75b0fd43f8c

var lastTime = 0;
var prefixes = 'webkit moz ms o'.split(' '); //各浏览器前缀
var requestAnimationFrame = window.requestAnimationFrame;
var cancelAnimationFrame = window.cancelAnimationFrame;
var prefix;
//通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
for( var i = 0; i < prefixes.length; i++ ) {
 if ( requestAnimationFrame && cancelAnimationFrame ) {
 break;
 }
 prefix = prefixes[i];
 requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ];
 cancelAnimationFrame = cancelAnimationFrame || window[ prefix + 'CancelAnimationFrame' ] || window[ prefix + 'CancelRequestAnimationFrame' ];
}
//如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame,则会退到setTimeout
if ( !requestAnimationFrame || !cancelAnimationFrame ) {
 requestAnimationFrame = function( callback, element ) {
 var currTime = new Date().getTime();
 //为了使setTimteout的尽可能的接近每秒60帧的效果
 var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ); 
 var id = window.setTimeout( function() {
 callback( currTime + timeToCall );
 }, timeToCall );
 lastTime = currTime + timeToCall;
 return id;
 };
 cancelAnimationFrame = function( id ) {
 window.clearTimeout( id );
 };
}
//得到兼容各浏览器的API
window.requestAnimationFrame = requestAnimationFrame; 
window.cancelAnimationFrame = cancelAnimationFrame;

이렇게 하면 모든 브라우저에서 requestAnimationFrame 및 cancelAnimationFrame을 사용할 수 있습니다.
다음은 애니메이션에 requestAnimationFrame을 사용하는 방법을 설명하는 간단한 예입니다. 다음 코드는 애니메이션 형식으로 ID 데모가 있는 div를 300px

오른쪽으로 이동합니다.
<div id="demo" style="position:absolute; width:100px; height:100px; background:#ccc; left:0; top:0;"></div>
<script>
var demo = document.getElementById('demo');
function rander(){
 demo.style.left = parseInt(demo.style.left) + 1 + 'px'; //每一帧向右移动1px
}
requestAnimationFrame(function(){
 rander();
 //当超过300px后才停止
 if(parseInt(demo.style.left)<=300) requestAnimationFrame(arguments.callee);
});
</script>


예 2:

JavaScript 애니메이션은 항상 타이머와 간격을 통해 구현되었습니다. CSS 전환 및 애니메이션을 사용하면 웹 개발에서 애니메이션이 더욱 편리해졌지만 JavaScript를 기반으로 한 애니메이션 구현은 수년 동안 거의 변하지 않았습니다. Firefox 4가 출시되고 나서야 JavaScript 애니메이션의 첫 번째 개선 사항이 도입되었습니다. 그러나 개선 사항을 완전히 이해하려면 웹 애니메이션이 어떻게 발전하고 개선되는지 이해하는 데 도움이 될 것입니다.
타이머
애니메이션을 만드는 첫 번째 패턴은 연결된 setTimeout() 호출을 사용하는 것입니다. Netscape 3의 전성기 시절 오랫동안 개발자들은 인터넷 어디에서나 볼 수 있었던 일종의 고정된 최신 견적 상태 표시줄을 기억했습니다.

(function(){ 
 var msg = "新的广告", 
 len = 25, 
 pos = 0, 
 padding = msg.replace(/./g, " ").substr(0,len), 
 finalMsg = padding + msg; 
 function updateText(){ 
 var curMsg = finalMsg.substr(pos++, len); 
 window.status = curMsg; 
 if (pos == finalMsg.length){ pos = 0; } 
 setTimeout(updateText, 100); 
 } 
 setTimeout(updateText, 100); 
})(); 

브라우저에서 이 코드를 테스트하려면 새 코드를 생성하세요.
태그는 window.status를 시뮬레이션하는 데 사용됩니다. 예: newsticker example
이 성가신 웹 패턴은 나중에 window.status 비활성화에 대한 저항에 직면했지만 Explorer 4 및 Netscape 4가 출시되면서 브라우저는 처음으로 개발자에게 페이지 요소에 대한 더 많은 제어권을 부여했습니다. 이런 식으로 JavaScript를 사용하여 요소의 크기, 위치, 색상 등을 동적으로 변경하는 새로운 애니메이션 모드가 나타납니다. 예를 들어 다음은 div의 너비를 100%로 변경하는 애니메이션입니다(진행률 표시줄과 유사).

(function(){ 
 function updateProgress(){ 
 var div = document.getElementByIdx_x("status"); 
 div.style.width = (parseInt(div.style.width, 10) + 5) + "%"; 
 if (div.style.width != "100%"){ setTimeout(updateProgress, 100); } 
 } 
 setTimeout(updateProgress, 100); 
})(); 

尽管动画在页面上的地方不同,但基本原理却是一样的:做出改变,用setTimeout()间隔使页面更新,然后setTimeout又执行下一次变化,这个过程反复执行,直到动画完成(见进度条动画),早期的状态栏动画是相同的技术,只是动画不一样而已。
间隔动画Intervals
随着成功将动画引入web,新的探索开始了。一个动画已经无法满足了,现在需要多个动画。首次尝试为每个动画创建多个动画循环,在早期的浏览器中使用setTimeout()来创建多个动画是有点复杂的,所以开发商开始使用setInterval()一创建单一的动画循环,来管理页面上所有的动画,一个使用wetInterval()的基本动画像这样:

(function(){ 
 function updateAnimations(){ 
 updateText(); 
 updateProgress(); 
 } 
 setInterval(updateAnimations, 100); 
})();

创建一个小动画库,updateAnimations()方法将每一个动画(同时看到一个新闻股票和进度条在一起运行)循环执行并进行适当的改变。如果没有动画需要更新,该方法可以退出而不做任何事情,甚至停止动画循环,直到有更多的动画更新做好准备。
动画问题比较棘手的问题是延迟应该为多少。间隔一方面必须足够短,从而使不同的动画都能流畅的进行,别一方面还要足够长,使得浏览器可以完成渲染。大多数浏览器的刷新频率为60HZ,即每秒60次刷新,大多数浏览器的刷新频率都不会比这个更频繁,因为他们知道,最终用户是得不到更好的体验的。
鉴于此,为流畅动画的最佳时间间隔为1000毫秒/ 60,约17ms。在这个频率你会看到流畅的动画,那是因为你最大的接近了浏览器能达到的频率。跟以前的动画相比,你会发现17ms间隔的动画更加平滑,也更快(因为动画更新更频繁,没有做其他任何修改的情况下),多个动画可能需要节流,以免17ms的动画完成得太快。
问题
即使使用setInterval()为基础的动画循环比多套使用setTimeout()的动画循环高效,这里还是存在问题。无论是setInterval()还是setTimeout()都无法达到精确,这个延迟即你指定的第二个参数仅仅表示何时代码会添加到浏览器的可能被执行的UI线程队列中。如果队列中有其他工作在此之前,那代码将会等到他完成才会执行。简而言之,毫秒级的延迟不是表示何时代码会执行,而是表示何时代码会添加进队列。如果UI线程处于繁忙状态或在处理用户动作,那么代码将不会被马上执行。
平滑动画的关键是理解下一帧何时被执行,直到现在都没有一个方法来保证下一帧将会在浏览器中被绘制。随着的日益流行和新的基于浏览器的游戏的出现,开发商对setInterval()和setTimeout()的不精准越来越感到失望。
浏览器的计时器分辨率加剧了这个问题,计时器对毫秒不精准,这里有一些常见的计时器分辨率:
Internet Explorer 8 and earlier 15.625ms
Internet Explorer 9 and later 4ms.
Firefox and Safari ~10ms.
Chrome has a timer 4ms.

IE在版本9之前的的分辨率为15.625,所以0~15之间的任意值可能是0或15,但没有分别。IE9的计时器分辨率改进为4ms,但涉及到动画时也是不具体的,chrome的计时器分辨率为4ms,firefox 和 safari的为10ms。因此即使你把间隔设定为最佳的显示效果,你也仅仅是得到这个近似值。
mozRequestAnimationFrame
Mozilla 的 Robert O'Callahan 在思考这个问题,并想出了一个独特的方案。他指出CSS transitions 和 animations的优势在于浏览器知道哪些动画将会发生,所以得到正确的间隔来刷新UI。而javascript动画,浏览器不知道动画正在发生。他的解决方案是创建一个mozRequestAnimationFrame()方法来告诉浏览器哪些javascript代码正在执行,这使得浏览在执行一些代码后得到优化。
mozRequestAnimationFrame()方法接受一个参数,是一个屏幕重绘前被调用的函数。这个函数用来对生成下合适的dom样式的改变,这些改变用在下一次重绘中。你可以像调用setTimeout()一样的方式链式调用mozRequestAnimationFrame(),例如:

function updateProgress(){ 
 var div = document.getElementByIdx_x("status"); 
 div.style.width = (parseInt(div.style.width, 10) + 5) + "%"; 
 if (div.style.left != "100%"){ 
 mozRequestAnimationFrame(updateProgress); 
 } 
} 


mozRequestAnimationFrame(updateProgress); 
由于mozRequestAnimationFrame()只运行给定的函数一次,你需要在下一次UI动画的时候再次调用它。你也需要相同的方法来管理何时停止调用。很酷,是非常流畅的动画增强的实例。
因此,mozRequestAnimationFrame()解决了浏览器不知道Javascript动画正在执行和不知道多少才是合适的间隔的问题,但对于不知道何时你的代码才被真正执行,也是由这个方案来解决的。
传递给mozRequestAnimationFrame()的函数实际是一个下一次重绘何时发生的的时间码(以毫秒为单位自1970年1月1日计算)。这是很重要的一点:mozRequestAnimationFrame()实际上列表出将要重绘的点并可以告诉你他们所处的时间。这样你就能够决定怎样更好的来调整你的动画。
为了得到上次重绘过去的时间,你可以查询mozAnimationStartTime,其中包含了过去重绘的时间代码。减去传递回调时的这个值可以计算出下一次重绘到屏幕时所用的时间。使用这些值的典型模式如下:

function draw(timestamp){ 
 //calculate difference since last repaint 
 var diff = timestamp - startTime; 
 //use diff to determine correct next step 
 //reset startTime to this repaint 
 startTime = timestamp; 
 //draw again 
 mozRequestAnimationFrame(draw); 
} 
var startTime = mozAnimationStartTime; 
mozRequestAnimationFrame(draw); 

关键是第一次不是通过callback调用时,mozAnimationStartTime是到mozRequestAnimationFrame()经过的时间。如果是在回调函数中,mozAnimationStartTime是通过参数传递进来的时间代码平均值。
webkitRequestAnimationFrame
在很多人热忠于chrome时,随即创建了webkitRequestAnimationFrame()方法。这个版本与firefox的版本在两方面有着细微的差别。一方面,它不通过回调函数传递时间代码,你将无法知道下次重绘何时发生,另一方面,它添加了第二个可选参数来确定哪一个DOM元素发生改变。因此,如果你知道重绘发生在页面哪个部分的元素内,你可以限制重绘发生的区域。
应该不会感到惊讶,有没有相应的mozAnimationStartTime,因为如果没有下一个重绘的时间信息不是很有益。有,只是webkitCancelAnimationFrame()取消了之前计划的重绘。
如果你不需要精确的时间差异,你可以用下面的方式来创建一个用于Firefox4和chrome10+的动画:

function draw(timestamp){ 
 //calculate difference since last repaint 
 var drawStart = (timestamp || Date.now()), 
 diff = drawStart - startTime; 
 //use diff to determine correct next step 
 //reset startTime to this repaint 
 startTime = drawStart; 
 //draw again 
 requestAnimationFrame(draw); 
} 
var requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame, 
startTime = window.mozAnimationStartTime || Date.now(); 
requestAnimationFrame(draw); 

这种模式使用可用的方法来创建以花费多少时间为理念的循环动画。Firefox使用时间代码信息是有用的,而Chrome默认为欠精准的时间对象。当用这种模式的时候,时间的差异给你一种多少时间过去了的想法,但不会告诉你Chrome的下一次重绘出现在何时。不过这比只有多少时间过去了的模糊概念要好些。
总结
mozRequestAnimationFrame()方法的介绍为推动Javascript 动画及web的历史发展有着非常重要的作用。如前所述,JavaScript动画的态几乎和JavaScript的初期一样。随着浏览器逐渐推出CSS transitions 和 animations,很高兴看到基于JavaScript的动画的关注,因为这些在基于的游戏领域将变得更重要和更与CUP联系紧密。知道Javascript何时尝试动画,允许浏览器做更多的优化处理,包括在tab处于后台或移动设备电量过低时停止进程。
该requestAnimationFrame()API现在正由W3C起草一个新议案,并正由Mozilla和Google努力使之成为Web大舞台的一部分。很高兴能看到这两大集团这么迅速的兼容(可能不完全)实现。
RequestAnimFrame使用
对于一个侦中对DOM的所有操作,只进行一次Layout和Paint。
如果发生动画的元素被隐藏了,那么就不再去Paint。

window.requestAnimFrame = (function(){ 
 return window.requestAnimationFrame || 
 window.webkitRequestAnimationFrame || 
 window.mozRequestAnimationFrame || 
 window.oRequestAnimationFrame || 
 window.msRequestAnimationFrame || 
 function( callback ){ 
  window.setTimeout(callback, 1000/60); 
 }; 
})(); 
//调用 
function animationLoop(elem){ 
 requestAnimFrame(animationLoop); 
//logic 
} 
Or
window.requestAnimFrame = (function(w, r) { 
 w['r'+r] = w['r'+r] || w['webkitR'+r] || w['mozR'+r] || w['msR'+r] || w['oR'+r] || function(c){ w.setTimeout(c, 1000 / 60); }; 
 return w['r'+r]; 
})(window, 'equestAnimationFrame'); 

以上通过两段代码示例详解说明了使用requestAnimationFrame实现js动画性能好,希望大家喜欢。

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.