468 lines
17 KiB
HTML
Executable File
468 lines
17 KiB
HTML
Executable File
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>GreenSock Animation Speed Test</title>
|
|
<script type="text/javascript" src="../src/uncompressed/plugins/CSSPlugin.js"></script>
|
|
<script type="text/javascript" src="../src/uncompressed/TweenLite.js"></script>
|
|
<script type="text/javascript" src="js/mootools-core-1.4.5-full-compat-yc.js"></script>
|
|
<script src="http://yui.yahooapis.com/3.4.1/build/yui/yui-min.js"></script>
|
|
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.2/dojo/dojo.js"></script>
|
|
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.2/dojo/fx/easing.js"></script>
|
|
<script src="http://code.createjs.com/easeljs-0.4.2.min.js"></script>
|
|
<script src="http://code.createjs.com/tweenjs-0.2.0.min.js"></script>
|
|
<script src="js/tweenjs/CSSPlugin.js"></script>
|
|
<script src="js/tweenjs/Ease.js"></script>
|
|
<script src="js/zepto.min.js"></script>
|
|
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
|
|
<style type="text/css">
|
|
|
|
html, body {
|
|
overflow:hidden;
|
|
}
|
|
body {
|
|
background-color:#000000;
|
|
margin:0px;
|
|
padding:0px;
|
|
color:#CCCCCC;
|
|
font-family:Verdana, Geneva, sans-serif;
|
|
}
|
|
#footer {
|
|
position:fixed;
|
|
bottom:0px;
|
|
background-color:#555;
|
|
left:0px;
|
|
width:100%;
|
|
padding:10px 10px 10px 5px;
|
|
z-index:1000;
|
|
}
|
|
#fps {
|
|
float:right;
|
|
background-color:#CCC;
|
|
padding:6px;
|
|
margin-right:14px;
|
|
color:#CC0000;
|
|
border-radius: 5px;
|
|
border-color:#000000;
|
|
border-style:solid;
|
|
border-width:1px;
|
|
font-size:24px;
|
|
}
|
|
#start {
|
|
width:100px;
|
|
}
|
|
#footer form li {
|
|
display:block;
|
|
float:left;
|
|
margin:10px 5px 5px 5px;
|
|
}
|
|
#instructions {
|
|
width:70%;
|
|
margin-left:15%;
|
|
padding-top:50px;
|
|
opacity:0;
|
|
}
|
|
#container {
|
|
position:absolute;
|
|
top:0;
|
|
left:0;
|
|
width:100%;
|
|
height:100%;
|
|
overflow:hidden;
|
|
z-index:-100;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="footer">
|
|
<div id="fps">0 fps</div>
|
|
<form id="form">
|
|
<li>
|
|
Dots:
|
|
<select id="dotQuantity" size="1">
|
|
<option value="25">25</option>
|
|
<option value="50">50</option>
|
|
<option value="100">100</option>
|
|
<option value="200">200</option>
|
|
<option value="300" selected="selected">300</option>
|
|
<option value="400">400</option>
|
|
<option value="500">500</option>
|
|
<option value="750">750</option>
|
|
<option value="1000">1000</option>
|
|
<option value="1250">1250</option>
|
|
<option value="1500">1500</option>
|
|
<option value="2000">2000</option>
|
|
<option value="2500">2500</option>
|
|
<option value="3000">3000</option>
|
|
</select>
|
|
</li>
|
|
<li style="display:none">
|
|
Duration:
|
|
<select id="duration" size="1">
|
|
<option value="0.5">0.5 seconds</option>
|
|
<option value="0.75" selected="selected">0.75 seconds</option>
|
|
<option value="1">1 second</option>
|
|
<option value="5">5 seconds</option>
|
|
</select>
|
|
</li>
|
|
<li>
|
|
Engine:
|
|
<select id="engine" size="1">
|
|
<option value="jquery">jQuery</option>
|
|
<option value="tweenlite">TweenLite</option>
|
|
<option value="yui">YUI 3 (Yahoo)</option>
|
|
<option value="mootools">MooTools</option>
|
|
<option value="dojo">Dojo</option>
|
|
<option value="tweenjs">TweenJS</option>
|
|
<option value="zepto">Zepto</option>
|
|
</select>
|
|
</li>
|
|
<li>
|
|
<button id="start" type="button" value="test"> START </button>
|
|
</li>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="instructions">
|
|
<p>Stress test the animation performance of various common Javascript tools and
|
|
compare them with TweenLite. This test does <strong>not</strong> use a canvas element
|
|
(although it certainly could) - it simply animates the left, top,
|
|
width, and height css properties of standard image elements because those are supported in virtually all browsers.
|
|
The goal was to be extremely fair and use the same code for everything except the actual animation. No tricks.
|
|
Look at the source for yourself or run your own tests to confirm.</p>
|
|
<p>Choose the number of dots you'd like to animate and then choose the
|
|
engine and click the "START" button below. Watch the <strong>fps</strong> in the lower right
|
|
(you want that number high - it is the total average frames per second rendered).
|
|
As more dots are animated, you'll see the performance gap widen. Try to push things until the fps drops below 30fps. When the
|
|
CPU isn't breaking a sweat, fps should hover around 100fps in most modern browsers.</p>
|
|
</div>
|
|
|
|
<div id="container"></div>
|
|
|
|
|
|
<script language="JavaScript" type="text/javascript">
|
|
jQuery(function() {
|
|
|
|
var fps = document.getElementById("fps"),
|
|
button = document.getElementById("start"),
|
|
dotQtyInput = document.getElementById("dotQuantity"),
|
|
durInput = document.getElementById("duration"),
|
|
engineInput = document.getElementById("engine"),
|
|
instructions = document.getElementById("instructions"),
|
|
container = document.getElementById("container"),
|
|
ticker = com.greensock.core.Animation.ticker,
|
|
inProgress = false,
|
|
tests = {},
|
|
centerX, centerY, dots, rawDots, currentTest, startTime, startFrame, prevUpdate, duration, startingCSS;
|
|
|
|
/**
|
|
* The goal of this test is to compare how various animation engines perform under pressure, taking relatively common
|
|
* animation tasks and running a lot of them at once to see raw performance. The goal is NOT to figure out the most
|
|
* efficient way to move dots in a starfield pattern.
|
|
*
|
|
* The same code runs everything except the actual tweens themselves. Every test in the "test"
|
|
* object has 4 properties:
|
|
*
|
|
* - milliseconds [boolean] - true if the duration should be defined in milliseconds
|
|
*
|
|
* - wrapDot [function] - when each dot <img> is created, it is passed to the wrapDot() method
|
|
* and whatever is returned gets stored in the array of dots to tween. This
|
|
* is useful to improve performance of things like jQuery because
|
|
* instead of passing the dom element to the tween() method (which would require
|
|
* jQuery to then query the dom and wrap the element in an engine-specific object
|
|
* before calling animate() on it), a native object can be used. Basically it lets you
|
|
* cache the dot's wrapper for better performance.
|
|
*
|
|
* - tween [function] - This is the core of the whole test. tween() is called for each dot, and the dot is
|
|
* passed as a parameter. The tween() function should set the dot's cssText to the
|
|
* startingCSS value (which just places the dot in the middle of the screen and sets its
|
|
* width/height to 1px) and then after a random delay between 0 and the duration of the tween,
|
|
* it should tween the dot at a random angle, altering the left/top values accordingly as
|
|
* well as the width/height to 32px. Then, after the tween is done, it should call the tween()
|
|
* method again for that dot. So the same dot will just continuously tween outward from the
|
|
* center at random angles and at random delay values.
|
|
*
|
|
* - stop [function] - This function is called when the user stops the test. The dot is passed as a parameter.
|
|
* The function should immediately stop/kill the tween(s) of that dot (or all dots - that's fine too).
|
|
*
|
|
* I don't claim to be an expert at the various other tweening engines out there, so if there are optimizations
|
|
* that could be made to make them run better, please let me know. I tried to keep things as fair as possible.
|
|
**/
|
|
|
|
|
|
//jQuery
|
|
tests.jquery = {
|
|
milliseconds:true,
|
|
wrapDot:function(dot) {
|
|
return jQuery(dot); //wrap the dot in a jQuery object in order to perform better (that way, we don't need to query the dom each time we tween - we can just call animate() directly on the jQuery object)
|
|
},
|
|
tween:function(dot) {
|
|
dot[0].style.cssText = startingCSS;
|
|
var angle = Math.random() * Math.PI * 2;
|
|
dot.delay(Math.random() * duration).animate({left:Math.cos(angle) * radius + centerX,
|
|
top:Math.sin(angle) * radius + centerY,
|
|
width:32,
|
|
height:32}, duration, "cubicIn", function() { tests.jquery.tween(dot) });
|
|
},
|
|
stop:function(dot) {
|
|
dot.stop(true);
|
|
}
|
|
};
|
|
|
|
|
|
//TweenLite
|
|
tests.tweenlite = {
|
|
milliseconds:false,
|
|
wrapDot:function(dot) {
|
|
return dot; //no wrapping necessary
|
|
},
|
|
tween:function(dot) {
|
|
dot.style.cssText = startingCSS;
|
|
var angle = Math.random() * Math.PI * 2;
|
|
TweenLite.to(dot, duration, {css:{left:Math.cos(angle) * radius + centerX,
|
|
top:Math.sin(angle) * radius + centerY,
|
|
width:32,
|
|
height:32},
|
|
delay:Math.random() * duration,
|
|
ease:Cubic.easeIn,
|
|
overwrite:"none",
|
|
onComplete:tests.tweenlite.tween,
|
|
onCompleteParams:[dot]});
|
|
},
|
|
stop:function(dot) {
|
|
TweenLite.killTweensOf(dot);
|
|
}
|
|
}
|
|
|
|
|
|
//YUI 3 (Yahoo)
|
|
YUI().use("anim", function (Y) {
|
|
window.YAnim = Y.Anim; //faster lookup
|
|
window.YEase = Y.Easing.easeInStrong; //closest to Cubic.easeIn
|
|
});
|
|
tests.yui = {
|
|
milliseconds:false,
|
|
wrapDot:function(dot) {
|
|
return dot; //no wrapping necessary
|
|
},
|
|
tween:function(dot) {
|
|
dot.style.cssText = startingCSS;
|
|
var angle = Math.random() * Math.PI * 2,
|
|
anim = new YAnim({node:dot, to:{
|
|
left:Math.cos(angle) * radius + centerX,
|
|
top:Math.sin(angle) * radius + centerY,
|
|
width:32,
|
|
height:32
|
|
}, duration:duration, easing:YEase
|
|
});
|
|
anim.on("end", function(){ tests.yui.tween(dot); });
|
|
setTimeout(function() { if (inProgress) anim.run();}, Math.random() * duration * 1000);
|
|
},
|
|
stop:function(dot) {
|
|
YAnim.stop();
|
|
}
|
|
};
|
|
|
|
|
|
//MooTools
|
|
tests.mootools = {
|
|
milliseconds:true,
|
|
wrapDot:function(dot) {
|
|
var m = new Fx.Morph(dot, {duration:duration, transition:Fx.Transitions.Cubic.easeIn, onComplete:function() {tests.mootools.tween(m);}});
|
|
return m;
|
|
},
|
|
tween:function(dot) {
|
|
dot.element.style.cssText = startingCSS;
|
|
var angle = Math.random() * Math.PI * 2;
|
|
setTimeout(function() {if (inProgress) dot.start({ //I couldn't find a way to delay a Morph, so setTimeout was my only option. If anyone knows a better way, please let me know.
|
|
left:Math.cos(angle) * radius + centerX + "px",
|
|
top:Math.sin(angle) * radius + centerY + "px",
|
|
width:"32px",
|
|
height:"32px"});}, Math.random() * duration);
|
|
},
|
|
stop:function(dot) {
|
|
dot.cancel();
|
|
}
|
|
};
|
|
|
|
|
|
//Dojo
|
|
tests.dojo = {
|
|
milliseconds:true,
|
|
wrapDot:function(dot) {
|
|
return {dot:dot, anim:null}; //must record the animation instance so that we can stop() it later
|
|
},
|
|
tween:function(dot) {
|
|
dot.dot.style.cssText = startingCSS;
|
|
var angle = Math.random() * Math.PI * 2;
|
|
dot.anim = dojo.animateProperty({node:dot.dot,
|
|
properties:{
|
|
left:Math.cos(angle) * radius + centerX,
|
|
top:Math.sin(angle) * radius + centerY,
|
|
width:32,
|
|
height:32},
|
|
easing:dojo.fx.easing.cubicIn,
|
|
delay:Math.random() * duration,
|
|
duration:duration}).play();
|
|
dot.anim.onEnd = function() { tests.dojo.tween(dot); }
|
|
},
|
|
stop:function(dot) {
|
|
dot.anim.stop();
|
|
}
|
|
};
|
|
|
|
//TweenJS
|
|
CSSPlugin.install();
|
|
Ticker.setFPS(100); //ensures that TweenJS refreshes at the same rate as TweenLite for maximum smoothness
|
|
tests.tweenjs = {
|
|
milliseconds:true,
|
|
wrapDot:function(dot) {
|
|
return dot; //no wrapping necessary
|
|
},
|
|
tween:function(dot) {
|
|
dot.style.cssText = startingCSS;
|
|
var angle = Math.random() * Math.PI * 2;
|
|
Tween.get(dot).wait( Math.random() * duration ).to({
|
|
left:Math.cos(angle) * radius + centerX,
|
|
top:Math.sin(angle) * radius + centerY,
|
|
width:32,
|
|
height:32
|
|
}, duration, Ease.cubicIn).call(tests.tweenjs.tween, [dot]);
|
|
},
|
|
stop:function(dot) {
|
|
Tween.removeTweens(dot);
|
|
}
|
|
};
|
|
|
|
//Zepto (uses CSS3 transitions)
|
|
tests.zepto = {
|
|
milliseconds:true,
|
|
wrapDot:function(dot) {
|
|
return Zepto(dot); //wrap the dot in a jQuery object in order to perform better (that way, we don't need to query the dom each time we tween - we can just call animate() directly on the jQuery object)
|
|
},
|
|
tween:function(dot) {
|
|
dot[0].style.cssText = startingCSS;
|
|
//Zepto doesn't have a delay() feature, so we need to use a setTimeout() instead.
|
|
setTimeout(function() {
|
|
if (!dot.isKilled) { //Zepto doesn't have a feature that allows us to kill tweens, so we simply set our own "isKilled" property to true when the tween is supposed to be killed and then stop the recursion thereafter which gives us a somewhat similar effect.
|
|
var angle = Math.random() * Math.PI * 2;
|
|
dot.animate({left:Math.cos(angle) * radius + centerX,
|
|
top:Math.sin(angle) * radius + centerY,
|
|
width:32,
|
|
height:32}, duration, "ease-in", function() { tests.zepto.tween(dot) });
|
|
}
|
|
}, duration * Math.random());
|
|
},
|
|
stop:function(dot) {
|
|
dot.isKilled = true;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
function toggleTest() {
|
|
inProgress = !inProgress;
|
|
var i;
|
|
if (inProgress) {
|
|
|
|
currentTest = tests[engineInput.value];
|
|
centerX = jQuery(window).width() / 2;
|
|
centerY = (jQuery(window).height() / 2) - 30;
|
|
startingCSS = "position:absolute; left:" + centerX + "px; top:" + centerY + "px; width:1px; height:1px;"; //for opacity, add: zoom:1; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=30)'; filter: alpha(opacity=30); opacity:0.3
|
|
radius = Math.sqrt(centerX * centerX + centerY * centerY);
|
|
duration = Number(durInput.value);
|
|
if (currentTest.milliseconds) {
|
|
duration *= 1000;
|
|
}
|
|
|
|
createDots();
|
|
i = dots.length;
|
|
while (--i > -1) {
|
|
currentTest.tween(dots[i]);
|
|
}
|
|
|
|
startTime = prevUpdate = ticker.time;
|
|
startFrame = ticker.frame;
|
|
ticker.addEventListener("tick", updateFPS, this);
|
|
|
|
dotQtyInput.disabled = engineInput.disabled = true;
|
|
|
|
//adjust the fps style and fade out the instructions
|
|
button.innerHTML = " STOP ";
|
|
fps.innerHTML = "0 fps";
|
|
fps.style.backgroundColor = "#FFFFFF";
|
|
fps.style.borderColor = fps.style.color = "#FF0000";
|
|
fps.style.borderWidth = "3px";
|
|
fps.style.paddingTop = fps.style.paddingBottom = "4px";
|
|
TweenLite.to(instructions, 0.7, {css:{autoAlpha:0}, overwrite:"all"});
|
|
|
|
} else {
|
|
//adjust the fps style and fade in the instructions
|
|
ticker.removeEventListener("tick", updateFPS);
|
|
fps.style.backgroundColor = "#CCCCCC";
|
|
fps.style.color = "#CC0000";
|
|
fps.style.borderColor = "#000000";
|
|
fps.style.borderWidth = "1px";
|
|
fps.style.paddingTop = fps.style.paddingBottom = "6px";
|
|
button.innerHTML = " START ";
|
|
TweenLite.to(instructions, 0.7, {css:{autoAlpha:1}, delay:0.2});
|
|
|
|
dotQtyInput.disabled = engineInput.disabled = false;
|
|
|
|
//stop the tweens and remove the dots.
|
|
i = dots.length;
|
|
while (--i > -1) {
|
|
currentTest.stop(dots[i]);
|
|
container.removeChild(rawDots[i]); //removes dot(s)
|
|
}
|
|
dots = null;
|
|
rawDots = null;
|
|
|
|
}
|
|
}
|
|
|
|
function createDots() {
|
|
var i = Number(dotQtyInput.value), dot;
|
|
dots = [];
|
|
rawDots = [];
|
|
while (--i > -1) {
|
|
dot = document.createElement("img");
|
|
dot.src = "img/dot.png";
|
|
dot.id = "dot" + i;
|
|
container.appendChild(dot);
|
|
rawDots.push(dot);
|
|
dots.push(currentTest.wrapDot(dot));
|
|
}
|
|
}
|
|
|
|
jQuery("#start").click(toggleTest);
|
|
jQuery("#dotQuantity,#duration,#engine").change(function(e) {
|
|
if (inProgress) {
|
|
toggleTest();
|
|
toggleTest();
|
|
}
|
|
});
|
|
jQuery.easing.cubicIn = $.easing.cubicIn = function( p, n, firstNum, diff ) { //we need to add the standard CubicIn ease to jQuery
|
|
return firstNum + p * p * p * diff;
|
|
}
|
|
jQuery.fx.interval = 10; //ensures that jQuery refreshes at roughly 100fps like TweenLite, TweenJS, and most of the others to be more even/fair.
|
|
TweenLite.to(instructions, 0, {css:{opacity:0}, immediateRender:true});
|
|
TweenLite.to(instructions, 0.7, {css:{opacity:1}, delay:0.25});
|
|
|
|
ticker.fps(100);
|
|
ticker.useRAF(false); //I noticed that requestAnimationFrame didn't provide as much accuracy in terms of counting true frame renders, particularly in Chrome. For example, set it to true and then choose a VERY high number of dots for an engine like jQuery and even though it is so bogged down that it doesn't even get to render a single dot mid-point in its tween, the FPS reports as around 10-16fps in Chrome. Apparently the browser is calling the requestAnimationFrame without even rendering the screen! Maybe there's a minimum threshold. In any case, switching requestAnimationFrame off appears to give the most accurate results. However, the default timing mode in TweenLite does use requestAnimationFrame because it has many other benefits, like throttling down when the browser tab is switched.
|
|
|
|
function updateFPS() {
|
|
if (ticker.time - prevUpdate > 1) { //only update every 1 seoond
|
|
prevUpdate = ticker.time;
|
|
fps.innerHTML = Number((ticker.frame - startFrame) / (prevUpdate - startTime)).toFixed(1) + " fps";
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|