I have a simple function which is increasing the width of a div but its not doing it smoothly its kinda of "juddery".
I'm using request animation frame to do this on Chrome.. and I decided not to round the numbers so I could get decimal width increments.. but I can't get it to be smooth at all was wondering how I can improve on my method.
This is my function:
function test(data){
var start = parseInt(data.start);
var length = parseInt(data.length); //total length in minutes to complete
var dif = (new Date().getTime() / 1000) - start; //start holds seconds since epoch
var minutes = dif / 60; //convert seconds past into minutes past
var percentage = (minutes/length) * 100;
if(percentage > 100){ percentage = 100; }
if( percentage != 100 ){
document.getElementById('r').style.width = percentage+'%';
document.getElementById('rt').innerHTML = Math.round(percentage)+'%';
} else if (percentage == 100 ){
document.getElementById('r').style.width = percentage+'%';
document.getElementById('rt').innerHTML = 'COMPLETED';
}
}
My function is called like this:
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback, element){
window.setTimeout(callback, 200 / 100);
};
})();
function Update(){
requestAnimFrame( Update );
test();
}
JSFIddle: http://jsfiddle.net/RmXr9/7/
Any suggestions on ways to improve the smoothness of div width incrementing ?
try using css transitions. You get way smoother animations, but requires you to structure your code a bit differently. An example of a css transition property is this:
transition:300ms linear;
then whatever property you change (the width for example) will make a smooth linear 300 millisecond transition towards it.
so making a smooth width change is as simple as setting up the transition then doing something like this in javascript:
div.style.width="400px";
Here's a quick example i mocked up:
http://jsfiddle.net/nTMsC/1/
here's a nice tutorial to get you started:
http://www.css3.info/preview/css3-transitions/
One of the biggest causes of 'juddery' animations for me has always been frame rate. If your frame rate is too slow, obviously the animation 'judders'. But if it is too fast for the browser to handle, the browser gets confused, and you get a different kind of 'juddery'.
I'd recommend a frame rate of between 13 and 30 milliseconds. JQuery is supposed to use 13ms, but I've found that that is sometimes still too fast. I generally start with 16ms, and experiment from there.
The key is to ensure that you time it so that one frame starts as or after the previous frame is finished. This will depend on the code you process. I notice that you call the next frame before you begin processing the current frame, so it may be possible that you're still getting backed up. Perhaps try:
function Update(){
test();
requestAnimFrame( Update );
}
Your fallback function has a frame rate of 200 / 100, which is 2ms. It is extremely unlikely that your browser can complete the animation in 2ms, so it is likelyto get backed up. requestAnimationFrame uses a maximum frame rate of 16ms.
UPDATE:
The problem you're having, according to your jsfiddle, is that, while you're calculating your percentage often, the changes to the percentage are very small, and they don't translate into a change in the width of the div. This http://jsfiddle.net/RmXr9/13/ should demontrate the changes in the percentage, and show the corrsponding changes in actual width. So, although you do a calculation often (maybe 60 times a second), the actual visual change only happens once every 16 frames or so. So, your actual frame rate is only about 4 frames per second, which makes a 'juddery' animation. Your only options, I'm afraid, are to make the animation run faster (perhaps by decreasing your length variable), or to make the div much longer (or both).
As an aside, I notice you don't have a way to stop the animation at the end, and I've added that into the jsfiddle as well.
Related
I have a sprite sheet animation where I am using requestAnimationFrame method to animate a spritesheet with only 4 images on the sheet This is my code:
http://hyque.com/ryan/ani-ryan-2.html
The problem is that it is too fast at 60 fps, so I want to slow the fps. I have been reading several articles on different ways to do it using either setInterval or Date(). I can’t seem to get the code to work correctly. Can anyone help, please.
Here is one of the articles that I tried to merge into my code:
http://codetheory.in/controlling-the-frame-rate-with-requestanimationframe/
So what I like to use to control animation apart form the "game loop."
var lastRender = 0;
var counter = 0;
function render(time)
{
//checks to see if enough time has passed
if(time - lastRender<16){requestAnimationFrame(render);return;}
lastRender = time;
counter++;
if(counter %20 && counter != 0)
animation();
if(counter >= 60)
counter=0;
requestAnimationFrame(render);
}
requestAnimationFrame(render);
This gives you greater control over your sprites so you can now have them at different speeds and your logic stays at 60fps.
Generally, for Game Engines you will want your rendering speed to be different from your logic speed.
Logic speed should simply be as fast as possible
setInterval( logic, 0 ); // runs as fast as possible
OR
setTimer( logic, 0 ); // logic() runs this again ( i think this is generally better )
Whereas render should remain as you have it, as fast as rendering is done
requestAnimationFrame( render ) // per render frame
However, requestAnimationFrame is unstable between machines, and for the most part will run 60 frames per second ( depending on the users hardware ).
In this case, for consistency you should base your animation on consistent TIME or setTimeout.
In your animation you could use something like
var date = new Date(), // date object
milliseconds = date.getMilliseconds(), // 0 - 999 of the second
totalSpriteFrames = 4
frame = Math.floor( milliseconds / ( 1000 / totalSpriteFrames ) ); // break the second into four frames
Create the date object outside of your animation scope for optimization, and this math can easily be used to choose which " frame " your sprite is on. You can also use this for multiple sprites, just change their " totalSpriteFrames "
This is also scalable in case you end up with a frame heavy sprite with many frames.
I have a section of code which is supposed to move this image ( id = 'background' ), which I have downloaded locally, and is quite large. It is supposed to move when I hover over top of a certain div(s). This then changes the opacity CSS value, which in turn is detected by the js, which then makes the image move. The js code looks like this:
setInterval(function(){
var element = document.getElementById('background'),
style = window.getComputedStyle(element),
left = style.getPropertyValue('left');
left = left.replace(/px/g, "")
left = parseInt(left,10)
if(getOpacity('rightbar') == .5){
document.getElementById('background').style.left = left - 8 + 'px'
}
if(getOpacity('leftbar') == .5){
document.getElementById('background').style.left = left + 8 + 'px'
}
},10)
The getOpacity(idName) function looks like this:
function getOpacity(param){
var element = document.getElementById(param),
style = window.getComputedStyle(element),
opacity = style.getPropertyValue('opacity');
return opacity
}
So the problem is that, no matter what movement values or setInteveral time I use, the animation always makes out to be laggy. Is there a way to make this smooth with vanilla js, or better yet, to scrap the opacity detection and do it all with CSS?
It works fine when I put the above code in a fiddle, but when it actually runs full browser (on my personal chrome window), it looks like this.
Note: I am running this full browser window on a 4k monitor, is this just too much for chrome to handle?
1. Use requestAnimationFrame instead of setInterval
This signals the browser you want to do something before the next redraw. The callback you provide is executed exactly once per frame.
(In case this matters: requestAnimationFrame does not work in IE9 and below.)
2. Don't increase by a fixed value per frame, tween between values
Both when using requestAnimationFrame and using setInterval, the time difference between frames vary.
You can verify that yourself by using something like this in the developer toolbar:
var last = new Date();
function onFrame(){
var now = new Date();
console.log(new Date() - last);
last = now;
requestAnimationFrame(onFrame);
}
onFrame();
The developer console will output the frame times in ms, like this:
16
17
17
15
19
...
If you increase position (not so noticeable on e.g. opacity) by a fixed amount on vaying intervals, the animation will look jagged. So Instead of doing left = left + 8;, calculate at which position in the animation you are, based on the current time, something like this:
var myElement = ...;
var initialOpacity = 1.0;
var targetOpacity = 0.5;
var duration = 2000;
var startTime = new Date();
function animation() {
var delta = Math.min(1, (new Date() - startTime) / duration);
// delta is now a number in the range [0 ... 1]
myElement.style.opacity = initialOpacity + delta * (targetOpacity - initialOpacity);
if (delta < 1) requestAnimationFrame(animation);
}
requestAnimationFrame(animation);
Yes, this example tweens opacity and not position, but you get the idea - and your teacher can't claim you copy-pasted ;-)
3. Don't read and write back-and-forth between JS and CSS
Assuming the initial position of your image is not viewport-related (say, left: -10%), there is no need to read the position on every frame.
When your JavaScript is the only thing changing the left property, why read it from CSS? Save it in a variable and set it to CSS from your JavaScript.
var initialX = ComputeCssXPosition(myElement);
...
function animate() {
...
myElement.style.left = computedNewXPosition;
}
If you want to change the postition when the user hovers an element, use mouse events in your JS.
myElement.addEventListener('mouseover', function (ev) { ... });
myElement.addEventListener('mouseout', function (ev) { ... });
Alternative: Use CSS transitions
Already covered in the answer by Shomz.
The best approach would be to use requestAnimationFrame instead of setInterval and not to check for the style changes, but use mouseover listeners (too much communication: CSS->JS->CSS->JS...). See here: https://jsfiddle.net/yqpun3eb/4/
However, if you still want to use setInterval, you can simply put the transition CSS rule on your background element. For example:
#background {transition: left 0.2s linear}
That will smooth out all the value changes because CSS performs way better, so it should be fine even on the 4K screens. The problem was your changes can jump by 8 pixels.
Seems to work nice with 0.2s on my machine: https://jsfiddle.net/yqpun3eb/3/
Oh, and btw. you want good performance, but why are you raping the system with this:
function getOpacity(param){
var element = document.getElementById(param),
style = window.getComputedStyle(element),
opacity = style.getPropertyValue('opacity');
return opacity
}
This doesn't create extra variables (which you don't need anyway):
function getOpacity(param){
return window.getComputedStyle(document.getElementById(param))
.getPropertyValue('opacity');
}
Finally, here's a slightly optimized version using requestAnimationFrame (that's how I would do it + use listeners instead of reading the style values): https://jsfiddle.net/yqpun3eb/4/
I want to make a JavaScript animation take 5 seconds to complete using requestAnimationFrame().
I don't want a strict and precise timing, so anything close to 5 seconds is OK and I want my code to be simple and readable, so solutions like this won't work for me.
My question is, is it safe to assume most browsers render the page at 60 fps? i.e. if I want my animation to take 5 seconds to complete, I'll divide it to 60 * 5 = 300 steps and with each call of function draw() using requestAnimationFrame(), draw the next step of animation. (Given the fact the animation is pretty simple, just moving a colored div around.)
By the way, I can't use jQuery.
Edit: Let me rephrase the question this way: Do all browsers 'normally' try to render the page at 60 fps? I want to know if Chrome for example renders at 75 fps or Firefox renders at 70 fps.
(Normal condition: CPU isn't highly loaded, RAM is not full, there are no storage failures, room is properly ventilated and nobody tries to throw my laptop out the window.)
Relying on 60 frames per second is very unsafe, because the browser isn't always in the same conditions, and even if it tries to render the page at the maximum fps possible, there's always a chance of the processor/cpu/gpu being busy doing something else, causing the FPS to drop down.
If you want to rely on FPS (although I wouldn't suggest you so), you should first detect the current fps, and adjust the speed of your animation frame per frame. Here's an example:
var lastCall, fps;
function detectFps() {
var delta;
if (lastCall) {
delta = (Date.now() - lastCall)/1000;
lastCall = Date.now();
fps = 1/delta;
} else {
lastCall = Date.now();
fps = 0;
}
}
function myFunc() {
detectFps();
// Calculate the speed using the var fps
// Animate...
requestAnimationFrame(myFunc);
}
detectFps(); // Initialize fps
requestAnimationFrame(myFunc); // Start your animation
It depends on the GPU and monitor combination. I have a good GPU and a 120 hertz monitor, so it renders at 120 fps. During the render, If I move to 60 hertz monitor, it will max out at 60 fps.
Another factor, that happens in some browsers/OS, is the iGPU being used instead of the discrete gpu.
As already stated by others, it isn't.
But if you need to end your animation in approximately 5 seconds and it's not crucial not to miss any frames in the animation, you can use the old setTimeout() way. That way you can miss a target by a few milliseconds, and some of the frames in your animation will be skipped (not rendered) because of the fps mismatch, but this can be a "good enough" solution, especially if your animation is simple as you state it is, there's a chance that users won't even see the glitch.
It's not safe to assume everyone can handle animation.
People will have different needs.
A lot of common animations, and common web design practices, give me awful migraines, so I set my browser to 1 frame per second to kill the animation without causing too much fast flashing.
I use Box2D with WebGL.
Box2D demands a constant frame rate (time steps for it's "world" updates).
function update(time) {//update of box2d world
world.Step(
1/60 // 1 / frame-rate
, 3 //velocity iterations
, 8 //position iterations
);
But I've read that requestAnimFrame defined as below is the right way to go.
requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
window.setTimeout(callback, 1000/60);
};
})();
requestAnimFrame doesn't give me a constant frame rate and so my Box2D's variables are going unsynchronized.
Is there a fix to this?
[EDIT]
John's (Cutch) solution when implemented looks like this:
function interpolate(dt) {
var t = dt/time_step;
body_coordinates = (1-t) * body_coordinates + t * next_body_coordinates;
}
var physicsDt = 0;
function tick() {
var time_now = new Date().getTime();
var dt = time_now - last_time; //Note that last_time is initialized priorly
last_time = time_now;
physicsDt += dt;
clear_the_screen();
requestAnimFrame(tick);
drawEverything();
if(physicsDt >= time_step) {
update();
physicsDt -= time_step;
}
interpolate(dt);
}
Note that my physics update function takes care that the next_attribues are set.
And also, a physics update is called before this, to keep the physics world ahead by 1 frame.
The result
The animation is fairly smooth, except for those times when I can see some really bad jumps and random appearing micro-movements.
I thought the following issues were not addressed in the solution :
----> 1) dt may become larger than time_step : This would make dt/time_step greater than 1 which would ruin the interpolation equations.
When dt remains larger than time_step consistently, problems would increase.
Is it possible to overcome the problem of the time gap becoming larger than time_step ?
I mean, even if we keep the world one frame ahead of the rendering, if time gaps consistently stay greater than time_step, it wouldn't take long pass that "ahead" frame.
----> 2) Imagine dt being lesser than time_step by 1 ms. Then, the world is not updated that one time. Now interpolation is done and the approximate position is found(1 ms behind where it should have been).
Lets say the next time no difference is seen between dt and time_step.
Now, no interpolation is done considering that dt and time_step are equal. So, the next that is drawn is the "ahead" frame in the world, right?(using those equations, with t = 1)
But accurately, the rendered world should be that 1ms behind which it was before.
I mean, that 1ms by which it was behind the world frame should not vanish.
But with t = 1, draws the physics world frame and forgets that 1ms.
Am I wrong about the code or the above 2 points?
I request you to clarify these issues.
[EDIT]
I asked the author of this webpage, for a way to efficiently draw many shapes, in the comments there.
I learnt to do it this way :
I'm saving bufferData calls by keeping separate buffers for each shape and calling createBuffer, bindBuffer, bufferData only once during init.
Everytime I refresh the screen, I have to iterate over all the shapes and
I have to call enableVertexAttribArray and vertexAttribPointer after binding the required shape's buffer(using bindBuffer).
My shapes don't change with time.
There are just a variety of them (like polygons, circles, triangles) that stay from beginning to end.
You must decouple your physics simulation stepping timing from your render vsync timing. The easiest solution is to do this:
frameCallback(dt) {
physicsDt += dt;
if (physicsDt > 16) {
stepPhysics();
physicsDt -= 16;
}
renderWorld();
requestAnimFrame(frameCallback);
}
The biggest issue here is that sometimes you'll be rendering with an outdated physics world, for example, if physicsDt was 15 no simulation update will occur but your objects would have moved almost an entire frame by that point in time. You can work around this by keeping the physics 1 frame ahead of the rendering and linearly interpolating object positions in the renderer. Something like:
var t = dt/16.0;
framePosition = (1-t) * previousFramePositions + (t) * nextFramePositions;
That way your objects move smoothly even if you're rendering is out of sync with your physics simulation. Let me know if you have any questions.
John
requestAnimFrame isn't meant to guarantee a constant frame rate – it's designed so that the browser only does the calculations for frames it actually draws.
If you want/have to calculate frames that aren't drawn, then it isn't the way to go.
On my application, I have a canvas which CSS size (CSS, not html) updates depending on the window size.
I have a main gameplayLoop which looks like this :
run = function(){
console.log(timerDiff(frameTime));
game.inputManage();
game.logics();
game.graphics.animate();
game.graphics.render();
frameTime = timerInit();
requestAnimFrame(run);
}
My requestAnimFrame function is the one from Paul Irish :
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
So basically the problem is that the timer that I log which measures the time between the requestAnimFrame call and the effective function call totally changes.
If my browser is in fullscreen I get like 50/60 ms, and if I have a small window I can get to something like 10 ms.
As the requestAnimFrame call is supposed to call constantly the function under a 60fps rythm (which is something like 30 ms I think) I shouldn't have this kind of result, since there is basically nothing happening between the timer's creation and its check, except the requestAnimFrame
I also have some recurring micro-freezes (less than a second) which happens like every 2/3 seconds. But the timer doesn't detect any change in the time (like even the time counter of javascript is blocked)
My timers functions are like this but it doesn't really matter here
timerInit = function()
{
return new Date();
}
timerDiff = function(datePrev)
{
return new Date().getTime() - datePrev.getTime();
}
Well, the standard basically says that requestAnimationFrame will "do its best" to run at a "consistent" frame rate. That doesn't guarantee a 60fps; it just states that it will animate as fast as it can.
My experience with it has been as dim as yours so far unfortunately. I ended up going back to setTimeout. It goes at about the same rate, and graphical updates are accurate and don't skip a beat as they did with requestAnimationFrame. Sure it wasn't 60fps, but at least you could tell what was going on.
I'm sure the performance will only improve as browser developers optimize the new function, but for the time being, consider going back to timeouts.
Edit: I would like to remind people that this was answered a year ago, and the times have changed :)
"So basically the problem is that the timer that I log which measures the time between the requestAnimFrame call and the effective function call totally changes..."
Of course it does, that's why you need to measure it and BASED ON THIS TIME DIFFERENCE VALUE you calculate your next frame.
Let's assume you want to animate the 'left' css property of a div from 0px to 120px in 1 second with 60 fps.
using setTimeout, because YOU set the number of frames, you know how much you must increment the 'left' property: 120px/60frames = 2px/frame
using requestAnimationFrame, you have no ideea when the next frame will happen, until it happens; then you measure the time difference between this and the previous frame an you calculate the value you must increment the css 'left value' with; if the frame happens after 500ms, increment distance = (120px * 500ms)/1000ms = 60px; the ideea is that at the start of the animation you can't have a fixed, predetermined value to increment with at each frame, you need to calculate it dynamically based on the time difference
So even if the animation won't be at the advertised 60fps, if frames are skipped, every frame that is not skipped will draw the accurate updated situation.
You have to fiddle a bit to decide when the animation should end.