HTML5 Canvas game loop delta time calculations - javascript

I'm new to game development. Currently I'm doing a game for js13kgames contest, so the game should be small and that's why I don't use any of modern popular frameworks.
While developing my infinite game loop I found several articles and pieces of advice to implement it. Right now it looks like this:
self.gameLoop = function () {
self.dt = 0;
var now;
var lastTime = timestamp();
var fpsmeter = new FPSMeter({decimals: 0, graph: true, theme: 'dark', left: '5px'});
function frame () {
fpsmeter.tickStart();
now = window.performance.now();
// first variant - delta is increasing..
self.dt = self.dt + Math.min(1, (now-lastTime)/1000);
// second variant - delta is stable..
self.dt = (now - lastTime)/16;
self.dt = (self.dt > 10) ? 10 : self.dt;
self.clearRect();
self.createWeapons();
self.createTargets();
self.update('weapons');
self.render('weapons');
self.update('targets');
self.render('targets');
self.ticks++;
lastTime = now;
fpsmeter.tick();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
};
So the problem is in self.dt I've eventually found out that first variant is not suitable for my game because it increases forever and the speed of weapons is increasing with it as well (e.g. this.position.x += (Math.cos(this.angle) * this.speed) * self.dt;..
Second variant looks more suitable, but does it correspond to this kind of loop (http://codeincomplete.com/posts/2013/12/4/javascript_game_foundations_the_game_loop/)?

Here' an implementation of an HTML5 rendering system using a fixed time step with a variable rendering time:
http://jsbin.com/ditad/10/edit?js,output
It's based on this article:
http://gameprogrammingpatterns.com/game-loop.html
Here is the game loop:
//Set the frame rate
var fps = 60,
//Get the start time
start = Date.now(),
//Set the frame duration in milliseconds
frameDuration = 1000 / fps,
//Initialize the lag offset
lag = 0;
//Start the game loop
gameLoop();
function gameLoop() {
requestAnimationFrame(gameLoop, canvas);
//Calcuate the time that has elapsed since the last frame
var current = Date.now(),
elapsed = current - start;
start = current;
//Add the elapsed time to the lag counter
lag += elapsed;
//Update the frame if the lag counter is greater than or
//equal to the frame duration
while (lag >= frameDuration){
//Update the logic
update();
//Reduce the lag counter by the frame duration
lag -= frameDuration;
}
//Calculate the lag offset and use it to render the sprites
var lagOffset = lag / frameDuration;
render(lagOffset);
}
The render function calls a render method on each sprite, with a reference to the lagOffset
function render(lagOffset) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
sprites.forEach(function(sprite){
ctx.save();
//Call the sprite's `render` method and feed it the
//canvas context and lagOffset
sprite.render(ctx, lagOffset);
ctx.restore();
});
}
Here's the sprite's render method that uses the lag offset to interpolate the sprite's render position on the canvas.
o.render = function(ctx, lagOffset) {
//Use the `lagOffset` and previous x/y positions to
//calculate the render positions
o.renderX = (o.x - o.oldX) * lagOffset + o.oldX;
o.renderY = (o.y - o.oldY) * lagOffset + o.oldY;
//Render the sprite
ctx.strokeStyle = o.strokeStyle;
ctx.lineWidth = o.lineWidth;
ctx.fillStyle = o.fillStyle;
ctx.translate(
o.renderX + (o.width / 2),
o.renderY + (o.height / 2)
);
ctx.beginPath();
ctx.rect(-o.width / 2, -o.height / 2, o.width, o.height);
ctx.stroke();
ctx.fill();
//Capture the sprite's current positions to use as
//the previous position on the next frame
o.oldX = o.x;
o.oldY = o.y;
};
The important part is this bit of code that uses the lagOffset and the difference in the sprite's rendered position between frames to figure out its new current canvas position:
o.renderX = (o.x - o.oldX) * lagOffset + o.oldX;
o.renderY = (o.y - o.oldY) * lagOffset + o.oldY;
Notice that the oldX and oldY values are being re-calculated each frame at the end of the method, so that they can be used in the next frame to help figure out the difference.
o.oldX = o.x;
o.oldY = o.y;
I'm actually not sure if this interpolation is completely correct or if this is best way to do it. If anyone out there reading this knows that it's wrong, please let us know :)

The modern version of requestAnimationFrame now sends in a timestamp that you can use to calculate elapsed time. When your desired time interval has elapsed you can do your update, create and render tasks.
Here's example code:
var lastTime;
var requiredElapsed = 1000 / 10; // desired interval is 10fps
requestAnimationFrame(loop);
function loop(now) {
requestAnimationFrame(loop);
if (!lastTime) { lastTime = now; }
var elapsed = now - lastTime;
if (elapsed > requiredElapsed) {
// do stuff
lastTime = now;
}
}

This isn't really an answer to your question, and without knowing more about the particular game I can't say for sure if it will help you, but do you really need to know dt (and FPS)?
In my limited forays into JS game development I've found that often you don't really need to to calculate any kind of dt as you can usually come up with a sensible default value based on your expected frame rate, and make anything time-based (such as weapon reloading) simply work based on the number of ticks (i.e. a bow might take 60 ticks to reload (~1 second # ~60FPS)).
I usually use window.setTimeout() rather than window.requestAnimationFrame(), which I've found generally provides a more stable frame rate which will allow you to define a sensible default to use in place of dt. On the down-side the game will be more of a resource hog and less performant on slower machines (or if the user has a lot of other things running), but depending on your use case those may not be real concerns.
Now this is purely anecdotal advice so you should take it with a pinch of salt, but it has served me pretty well in the past. It all depends on whether you mind the game running more slowly on older/less powerful machines, and how efficient your game loop is. If it's something simple that doesn't need to display real times you might be able to do away with dt completely.

At some point you will want to think about decoupling your physics from your rendering. Otherwise your players could have inconsistent physics. For example, someone with a beefy machine getting 300fps will have very sped up physics compared to someone chugging along at 30fps. This could manifest in the first player cruising around in a mario-like scrolling game at super speed and the other player crawling at half speed (if you did all your testing at 60fps). A way to fix that is to introduce delta time steps. The idea is that you find the time between each frame and use that as part of your physics calculations. It keeps the gameplay consistent regardless of frame rate. Here is a good article to get you started: http://gafferongames.com/game-physics/fix-your-timestep/
requestAnimationFrame will not fix this inconsistency, but it is still a good thing to use sometimes as it has battery saving advantages. Here is a source for more info http://www.chandlerprall.com/2012/06/requestanimationframe-is-not-your-logics-friend/

I did not check the logic of the math in your code .. however here what works for me:
GameBox = function()
{
this.lastFrameTime = Date.now();
this.currentFrameTime = Date.now();
this.timeElapsed = 0;
this.updateInterval = 2000; //in ms
}
GameBox.prototype.gameLoop = function()
{
window.requestAnimationFrame(this.gameLoop.bind(this));
this.lastFrameTime = this.currentFrameTime;
this.currentFrameTime = Date.now();
this.timeElapsed += this.currentFrameTime - this.lastFrameTime ;
if(this.timeElapsed >= this.updateInterval)
{
this.timeElapsed = 0;
this.update(); //modify data which is used to render
}
this.render();
}
This implementation is idenpendant from the CPU-speed(ticks). Hope you can make use of it!

A great solution to your game engine would be to think in objects and entities. You can think of everything in your world as objects and entities. Then you want to make a game object manager that will have a list of all your game objects. Then you want to make a common communication method in the engine so game objects can make event triggers. The entities in your game for example a player would not need to inherent from anything to get the ability to render to the screen or have collision detection. You would simple make common methods in the entity that the game engine is looking for. Then let the game engine handle the entity as it would like. Entities in your game can be created or destroyed at anytime in the game so you should not hard-code any entities at all in the game loop.
You will want other objects in your game engine to respond to event triggers that the engine has received. This can be done using methods in the entity that the game engine will check to see if the method is available and if it is would pass the events to the entity. Do not hard code any of your game logic into the engine it messes up portability and limits your ability to expand on the game later on.
The problem with your code is first your calling different objects render and updates not in the correct order. You need to call ALL your updates then call ALL your renders in that order. Another is your method of hard coding objects into the loop is going to give you a lot of problems, when you want one of the objects to no longer be in the game or if you want to add more objects into the game later on.
Your game objects will have an update() and a render() your game engine will look for that function in the object/entity and call it every frame. You can get very fancy and make the engine work in a way to check if the game object/entity has the functions prior to calling them. for example maybe you want an object that has an update() but never renders anything to the screen. You could make the game object functions optional by making the engine check prior to calling them. Its also good practice to have an init() function for all game objects. When the game engine starts up the scene and creates the objects it will start by calling the game objects init() when first creating the object then every frame calling update() that way you can have a function that you only run one time on creation and another that runs every frame.
delta time is not really needed as window.requestAnimationFrame(frame); will give you ~60fps. So if you're keeping track of the frame count you can tell how much time has passed. Different objects in your game can then, (based off of a set point in the game and what the frame count was) determine how long its been doing something based off its new frame count.
window.requestAnimationFrame = window.requestAnimationFrame || function(callback){window.setTimeout(callback,16)};
gameEngine = function () {
this.frameCount=0;
self=this;
this.update = function(){
//loop over your objects and run each objects update function
}
this.render = function(){
//loop over your objects and run each objects render function
}
this.frame = function() {
self.update();
self.render();
self.frameCount++;
window.requestAnimationFrame(frame);
}
this.frame();
};
I have created a full game engine located at https://github.com/Patrick-W-McMahon/Jinx-Engine/ if you review the code at https://github.com/Patrick-W-McMahon/Jinx-Engine/blob/master/JinxEngine.js you will see a fully functional game engine built 100% in javascript. It includes event handlers and permits action calls between objects that are passed into the engine using the event call stack. check out some of the examples https://github.com/Patrick-W-McMahon/Jinx-Engine/tree/master/examples where you will see how it works. The engine can run around 100,000 objects all being rendered and executed per frame at a rate of 60fps. This was tested on a core i5. different hardware may vary. mouse and keyboard events are built into the engine. objects passed into the engine just need to listen for the event passed by the engine. Scene management and multi scene support is currently being built in for more complex games. The engine also supports high pixel density screens.
Reviewing my source code should get you on the track for building a more fully functional game engine.
I would also like to point out that you should have requestAnimationFrame() called when you're ready to repaint and not prior (aka at the end of the game loop). One good example why you should not call requestAnimationFrame() at the beginning of the loop is if you're using a canvas buffer. If you call requestAnimationFrame() at the beginning, then begin to draw to the canvas buffer you can end up having it draw half of the new frame with the other half being the old frame. This will happen on every frame depending on the time it takes to finish the buffer in relation to the repaint cycle (60fps). But at the same time you would end up overlapping each frame so the buffer will get more messed up as it loops over its self. This is why you should only call requestAnimationFrame() when the buffer is fully ready to draw to the canvas. by having the requestAnimationFrame() at the end you can have it skip a repaint if the buffer is not ready to draw and so every repaint is drawn as it is expected. The position of requestAnimationFrame() in the game loop has a big difference.

Related

Why does millisecond counter keep going in requestAnimationFrame()?

I'm making a game in JavaScript, using the great requestAnimationFrame(callback) function.
Today I learned that the callback passed to requestAnimationFrame() gets a high resolution time measured since we opened that page. Let's call it ms:
function paint(ms) {
// draw my game
requestAnimationFrame(paint);
}
requestAnimationFrame(paint);
It's all great, but there's one thing I don't quite get.
Function requestAnimationFrame() doesn't do anything when we go to another tab, so the rendering is paused. On the other hand, the time passed to the callback still goes on when we leave. Because of this, I'm not sure how would I make any use of that value. If it worked proportionally with rendering engine, I could use ms to calculate logical time of my game, because relying on requestAnimationFrame() as a rock stable 60 FPS doesn't sound like the greatest idea.
Am I missing something? What's the purpose of the ms parameter if it continues to count when we leave our tab?
That's just a timestamp, it doesn't really count anything. It's indeed generally the same as performance.now() which gives the amount of time since the page is active. As to why, it's just how the DOMHighResTimeStamp's origin has been defined.
We usually use it as a way to know how long time has elapsed since some prior event occurred, that is we store a start_time, then check the current timestamp and we can get the delta time of our animation, regardless of the actual frame-rate.
By the way, no, relying on requestAnimationFrame to be at any fixed frame-rate is really not a good idea, requestAnimationFrame is not tied to any frame-rate by specs, and actually it is recommended to align it with the screen refresh-rate (though only Blink does so).
So you actually need to rely on such a timestamp in order to have a consistent speed across different setups. Doing a simple pos++ at every frame will make your animation run twice faster on a 120Hz monitor than on a 60Hz one (at least in Chrome).
This only makes your idea more complex to perceive how it could be implemented: would your paint-timer go twice faster when the window is moved to a 120Hz monitor?
This timestamp may also help to catch up on long frames, it's not because the system had a hiccup that you necessarily want your animation to last longer.
Similarly, not all cases want the animation logic to stop when the window is blurred. Let's say I have a header with a background animation powered by rAF, I don't really want it to pause when going off-screen, even though it didn't have been painted.
If you want your game logic to pause when the window is blurred, listen for the onvisibilitychange, and save the current timestamp (using performance.now() since we're outside of rAF).
To add to Kaiido's answer. It's also common in apps to limit the time delta. For example a common way framerate independant calculations in apps is to compute the time between frames
let previousTime = 0;
function loop(currentTime) {
const deltaTime = currenTime - previousTime;
previousTime = currentTime;
// use deltaTime in various calculations
posX = posX + velX * deltaTime;
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
But, sometimes collision and or other math can break if deltaTime is too large so games will often add a limiter
// don't let deltaTime be more than a 1/10 of a second
const deltaTime = Math.min(currenTime - previousTime, 1000 / 10);
This mostly removes the need for checking for onvisiblitiychange. Especially if you keep your own animation/game clock.
let previousTime = 0;
let clock = 0;
let clockRate = 1;
function loop(currentTime) {
const deltaTime = Math.min(currenTime - previousTime, 1000 / 10) * clockRate;
previousTime = currentTime;
clock += deltaTime; // update our own clock
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
Now not only will or clock not jump if the player hides the tab but we can set clockRate to 0 if we want things to pause.
Why use your own clock? It lets you easily slow down or speed up time for all calculations that depend on that clock (see clockRate above). Also many app (games) have multiple clocks. They'd compute a different deltaTime for each clock. For example one clock for all objects that need to pause vs clocks need to keep going even even with the app/game is pause. Another might be a clock for the player vs one for the enemies so that when the player uses their "slow time super power" the enemies motions go slow but the player's motions remain at the same speed.

Make an image appear and then grow in size in a html canvas

I was wondering if its possible to create an image using various lines and bez-curves that appears and then moves down the canvas and gets larger as it grows. The animation in question requires the room to fill with gas and so the the gas gets greater and greater as it moves around the canvas.
I had thought about just drawing the image and then using a for loop to move the image down until a certain y coordinate, but this doesnt help with the increasing part of the created image
Any help appreciated
There are different ways to make animations happen. What I normally do for my canvas games is that I have a gameloop that loops at a certain FPS that I decide at the start of my project. Each animation is normally controlled by time an speed.
var fps = 60;
var lastUpdateTime = +new Date(); //when did I last update the game?
function gameloop() {
var updateStartTime = +new Date();
update(updateStartTime-lastUpdateTime); //update the game for the according to the elapsed time since last update
lastUpdateTime = updateStartTime;
//Normally I also handle spawning stuff here
//I also remove old object in my gameloop
setTimeout(gameloop,1000/fps)
}
function update(elapsedTime) {
//This function update the locations of game elements such as player position, bullets, enemies, bananas etc. (or even gas clouds!)
//When I change a value, I use the time-parameter passed along with the call so that I get a smooth game even though the browser might lag.
playerX += velocity*timeElapsed; //as an example
}
Here's an example on your problem where I made a function to create a gas bubble object (yes, I do know how wrong it is to use gas bubbles =P). These objects travel downwards (physics?!) and increase in size (OK, this is starting to sound more and more crazy), just have a look:
http://jsfiddle.net/Niddro/ppa4xuw8/

get a smooth animation for a canvas game

How to get a better animation, dinamically, even when browser is busy or idle, for different devices which have different hardware capacity.
I have tried many ways and still cannot find the right way to make the game to display a better animation.
This is what i tried:
var now;
var then = Date.now();
var delta;
window.gamedraw = function(){
now = Date.now();
delta = now - then;
if(delta > 18){
then = now - (delta % 18);
game_update();
}
}
window.gameloop = setInterval(window.gamedraw,1);
18 is the interval value to update the game, but when browser is busy this interval is not good, and it needs to lower. How to get a better animation dinamically, even when browser is idle or busy ?
I suppose that the interval value is the problem, because if interval is lower then game animation is very fast, if this value is 18 then game animation is good but not when browser is busy, and I do not have idea how to change it dinamically.
To get a smooth animation, you must :
• Synchronise on the screen.
• Compute the time elapsed within your game.
• Animate only using this game time.
Synchronizing with the screen is done by using requestAnimationFrame (rAF).
When you write :
requestAnimationFrame( myCalbBack ) ;
You are registering myCalbBack to be called once, the next time the screen is available to draw on.
( If you know about double buffering (canvas are always double-buffered), this time is the next time the GPU will swap the draw buffer with the display buffer. )
If, on the other hand, you don't use rAF but a interval/timeout to schedule the draws, what will happen is that the draws won't get actually displayed until next display refresh. Which might happen (on a 60Hz display) any time from right now to 16.6 ms later.
Below with a 20ms interval and a 16 ms screen, you can see that the images actually displayed will be alternatively 16ms away OR 2*16ms away - never 20, for sure-. You just can't know, from the interval callback, when the actual draw will show. Since both rAF and Intervals are not 100% accurate, you can even have a 3 frames delta.
So now that you are on sync with the screen, here's a bad news : the requestAnimationFrame does not tick exactly regularly. For various reasons the elapsed time in between two frames might change of 1ms or 2, or even more. So if you are using a fixed movement each frame, you'll move by the same distance during a different time : the speed is always changing.
(expl : +10 px on each rAF,
16.6 display -->> rAF time of 14, 15, 16 or 17 ms
--> the apparent speed varies from 0.58 to 0.71 px/ms. )
Answer is to measure time... And use it !
Hopefully requestAnimationFrame provides you the current time so you don't even have to use Date.now(). Secondary benefit is that this time will be very accurate on Browsers having an accurate timer (Chrome desktop).
The code below shows how you could know the time elapsed since last frame, and compute an application time :
function animate(time) {
// register to be called again on next frame.
requestAnimationFrame(animate);
// compute time elapsed since last frame.
var dt = time-lastTime;
if (dt<10) return;
if (dt >100) dt=16; // consider only 1 frame elapsed if unfocused.
lastTime=time;
applicationTime+=dt;
//
update(dt);
draw();
}
var lastTime = 0;
var applicationTime = 0;
requestAnimationFrame(animate);
Now last step is to always use time inside all you formulas. So instead of doing
x += xSpeed ;
you'll have to do :
x += dt * xSpeed ;
And now not only the small variations in between frames will be taken into account, but your game will run at the same apparent speed whatever the device's screen (20Hz, 50Hz, 60 Hz, ...).
I did a small demo where you can choose both the sync and if using fixed time, you'll be able to judge of the differences :
http://jsbin.com/wesaremune/1/
You should use requestAnimationFrame instead of setInterval.
Read this article or this one.
The latter one is in fact exactly discussing your approach (with the delta time), and then introduces the animation frame as a more reliable alternative.
The first article is really a great resource. For starters, with your interval of 18 ms you're apparently aiming for something close to 60 fps. This is in fact the default for requestAnimationFrame, so you don't need to write anything special:
function gamedraw() {
requestAnimationFrame(gamedraw); //self-reference
game_update(); //your update logic, propably needs to handle time intervals internally
}
gamedraw(); //this starts the animation
If you want to set the update interval explicitly, you can do so by wrapping the requestAnimationFrame inside a setInterval, like this:
var interval = 18;
function gamedraw() {
setTimeout(function() {
requestAnimationFrame(gamedraw);
game_update(); //must handle time difference internally
}, interval);
}
gamedraw();
Note that the game_update() function must keep track of when it was last called in order to e.g. move everything twice as far as normal in case a frame had to be skipped.
Actually, this means you could (and probably should) refactor your game_update() function to take the time that has actually passed as an argument instead of determining that internally. (There's no functional difference, it just is better, clearer code IMO because it doesn't hide the timing magic.)
var time;
function gamedraw() {
requestAnimationFrame(gamedraw);
var now = new Date().getTime(),
dt = now - (time || now);
time = now; //reset the timer
game_update(dt); //update with explicit and variable time step
}
gamedraw();
(Here I dropped the explicit frames again.)
Still, I urge you to read the first article because it also deals with cross-browser issues that I haven't gotten into here.
You should use requestAnimationFrame. It will queue up a callback to run on the next time the browser renders a frame. To achieve constant updating, call the update function recursively.
var update = function(){
//Do stuff
requestAnimationFrame(update)
}

How can I improve performance on my parallax scroll script?

I'm using Javascript & jQuery to build a parallax scroll script that manipulates an image in a figure element using transform:translate3d, and based on the reading I've done (Paul Irish's blog, etc), I've been informed the best solution for this task is to use requestAnimationFrame for performance reasons.
Although I understand how to write Javascript, I'm always finding myself uncertain of how to write good Javascript. In particular, while the code below seems to function correctly and smoothly, I'd like to get a few issues resolved that I'm seeing in Chrome Dev Tools.
$(document).ready(function() {
function parallaxWrapper() {
// Get the viewport dimensions
var viewportDims = determineViewport();
var parallaxImages = [];
var lastKnownScrollTop;
// Foreach figure containing a parallax
$('figure.parallax').each(function() {
// Save information about each parallax image
var parallaxImage = {};
parallaxImage.container = $(this);
parallaxImage.containerHeight = $(this).height();
// The image contained within the figure element
parallaxImage.image = $(this).children('img.lazy');
parallaxImage.offsetY = parallaxImage.container.offset().top;
parallaxImages.push(parallaxImage);
});
$(window).on('scroll', function() {
lastKnownScrollTop = $(window).scrollTop();
});
function animateParallaxImages() {
$.each(parallaxImages, function(index, parallaxImage) {
var speed = 3;
var delta = ((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight) / 2)) - parallaxImage.offsetY) / speed;
parallaxImage.image.css({
'transform': 'translate3d(0,'+ delta +'px,0)'
});
});
window.requestAnimationFrame(animateParallaxImages);
}
animateParallaxImages();
}
parallaxWrapper();
});
Firstly, when I head to the 'Timeline' tab in Chrome Dev Tools, and start recording, even with no actions on the page being performed, the "actions recorded" overlay count continues to climb, at a rate of about ~40 per second.
Secondly, why is an "animation frame fired" executing every ~16ms, even when I am not scrolling or interacting with the page, as shown by the image below?
Thirdly, why is the Used JS Heap increasing in size without me interacting with the page? As shown in the image below. I have eliminated all other scripts that could be causing this.
Can anyone help me with some pointers to fix the above issues, and give me suggestions on how I should improve my code?
(1 & 2 -- same answer) The pattern you are using creates a repeating animating loop which attempts to fire at the same rate as the browser refreshes. That's usually 60 time per second so the activity you're seeing is the loop executing approximately every 1000/60=16ms. If there's no work to do, it still fires every 16ms.
(3) The browser consumes memory as needed for your animations but the browser does not reclaim that memory immediately. Instead it occasionally reclaims any orphaned memory in a process called garbage collection. So your memory consumption should go up for a while and then drop in a big chunk. If it doesn't behave that way, then you have a memory leak.
Edit: I had not seen the answers from #user1455003 and #mpd at the time I wrote this. They answered while I was writing the book below.
requestAnimationFrame is analogous to setTimeout, except the browser wont fire your callback function until it's in a "render" cycle, which typically happens about 60 times per second. setTimeout on the other hand can fire as fast as your CPU can handle if you want it to.
Both requestAnimationFrame and setTimeout have to wait until the next available "tick" (for lack of a better term) until it will run. So, for example, if you use requestAnimationFrame it should run about 60 times per second, but if the browsers frame rate drops to 30fps (because you're trying to rotate a giant PNG with a large box-shadow) your callback function will only fire 30 times per second. Similarly, if you use setTimeout(..., 1000) it should run after 1000 milliseconds. However, if some heavy task causes the CPU to get caught up doing work, your callback won't fire until the CPU has cycles to give. John Resig has a great article on JavaScript timers.
So why not use setTimeout(..., 16) instead of request animation frame? Because your CPU might have plenty of head room while the browser's frame rate has dropped to 30fps. In such a case you would be running calculations 60 times per second and trying to render those changes, but the browser can only handle half that much. Your browser would be in a constant state of catch-up if you do it this way... hence the performance benefits of requestAnimationFrame.
For brevity, I am including all suggested changes in a single example below.
The reason you are seeing the animation frame fired so often is because you have a "recursive" animation function which is constantly firing. If you don't want it firing constantly, you can make sure it only fires while the user is scrolling.
The reason you are seeing the memory usage climb has to do with garbage collection, which is the browsers way of cleaning up stale memory. Every time you define a variable or function, the browser has to allocate a block of memory for that information. Browsers are smart enough to know when you are done using a certain variable or function and free up that memory for reuse - however, it will only collect the garbage when there is enough stale memory worth collecting. I can't see the scale of the memory graph in your screenshot, but if the memory is increasing in kilobyte size amounts, the browser may not clean it up for several minutes. You can minimize the allocation of new memory by reusing variable names and functions. In your example, every animation frame (60x second) defines a new function (used in $.each) and 2 variables (speed and delta). These are easily reusable (see code).
If your memory usage continues to increase ad infinitum, then there is a memory leak problem elsewhere in your code. Grab a beer and start doing research as the code you've posted here is leak-free. The biggest culprit is referencing an object (JS object or DOM node) which then gets deleted and the reference still hangs around. For example, if you bind a click event to a DOM node, delete the node, and never unbind the event handler... there ya go, a memory leak.
$(document).ready(function() {
function parallaxWrapper() {
// Get the viewport dimensions
var $window = $(window),
speed = 3,
viewportDims = determineViewport(),
parallaxImages = [],
isScrolling = false,
scrollingTimer = 0,
lastKnownScrollTop;
// Foreach figure containing a parallax
$('figure.parallax').each(function() {
// The browser should clean up this function and $this variable - no need for reuse
var $this = $(this);
// Save information about each parallax image
parallaxImages.push({
container = $this,
containerHeight: $this.height(),
// The image contained within the figure element
image: $this.children('img.lazy'),
offsetY: $this.offset().top
});
});
// This is a bit overkill and could probably be defined inline below
// I just wanted to illustrate reuse...
function onScrollEnd() {
isScrolling = false;
}
$window.on('scroll', function() {
lastKnownScrollTop = $window.scrollTop();
if( !isScrolling ) {
isScrolling = true;
animateParallaxImages();
}
clearTimeout(scrollingTimer);
scrollingTimer = setTimeout(onScrollEnd, 100);
});
function transformImage (index, parallaxImage) {
parallaxImage.image.css({
'transform': 'translate3d(0,' + (
(
lastKnownScrollTop +
(viewportDims.height - parallaxImage.containerHeight) / 2 -
parallaxImage.offsetY
) / speed
) + 'px,0)'
});
}
function animateParallaxImages() {
$.each(parallaxImages, transformImage);
if (isScrolling) {
window.requestAnimationFrame(animateParallaxImages);
}
}
}
parallaxWrapper();
});
#markE's answer is right on for 1 & 2
(3) Is due to the fact that your animation loop is infinitely recursive:
function animateParallaxImages() {
$.each(parallaxImages, function(index, parallaxImage) {
var speed = 3;
var delta = ((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight) / 2)) - parallaxImage.offsetY) / speed;
parallaxImage.image.css({
'transform': 'translate3d(0,'+ delta +'px,0)'
});
});
window.requestAnimationFrame(animateParallaxImages); //recursing here, but there is no base base
}
animateParallaxImages(); //Kick it off
If you look at the example on MDN:
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);
I would suggest either stopping recursion at some point, or refactor your code so functions/variables aren't being declared in the loop:
var SPEED = 3; //constant so only declare once
var delta; // declare outside of the function to reduce the number of allocations needed
function imageIterator(index, parallaxImage){
delta = ((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight) / 2)) - parallaxImage.offsetY) / SPEED;
parallaxImage.image.css({
'transform': 'translate3d(0,'+ delta +'px,0)'
});
}
function animateParallaxImages() {
$.each(parallaxImages, imageIterator); // you could also change this to a traditional loop for a small performance gain for(...)
window.requestAnimationFrame(animateParallaxImages); //recursing here, but there is no base base
}
animateParallaxImages(); //Kick it off
Try getting rid of the animation loop and putting the scroll changes in the 'scroll' function. This will prevent your script from doing transforms when lastKnownScrollTop is unchanged.
$(window).on('scroll', function() {
lastKnownScrollTop = $(window).scrollTop();
$.each(parallaxImages, function(index, parallaxImage) {
var speed = 3;
var delta = ((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight) / 2)) - parallaxImage.offsetY) / speed;
parallaxImage.image.css({
'transform': 'translate3d(0,'+ delta +'px,0)'
});
});
});

Choppy result from DeltaTime function in JS

I'm building an HTML5 canvas game that's using requestAnimationFrame to render each frame in the canvas. To move animations at a rate based on seconds, not framerate, I am building a deltaTime function similar to that used in Unity:
var startTime = new Date().getTime();
var lastTime = null;
function deltaTime()
{
if (lastTime == null)
{
lastTime = new Date().getTime();
}
var dt = new Date().getTime() - lastTime;
lastTime = new Date().getTime();
return dt;
}
However, when I multiply this value by the animation speed, it causes a choppy result instead of a smooth animation. It appears that millisecond accuracy is not enough. How can I improve this function to produce a smooth result?
Check out the newer version of requestAnimationFrame that sends in an elapsed time that is accurate to the sub-millisecond
http://updates.html5rocks.com/2012/05/requestAnimationFrame-API-now-with-sub-millisecond-precision
But that may not be the cause of your choppiness.
Using requestAnimationFrame (aka RAF) with a delta-time causes the object being animated to appear in an accurate location.
If you're animating the sun across the sky, then RAF+deltaTime will insure that whenever the sun is drawn, it will be drawn at the correct spot in the sky. It will insure accuracy at the cost of choppy sun movement.
RAF+deltaTime sacrifices smooth animation for positioning accuracy.
You don't give a code example, but the solution is to make sure your code is efficient enough to fully draw within the RAF timespan.
Alternatively, you can make the amount of movement smaller so a skipped frame will not be very noticeable to the user.
requestAnimationFrame is highly optimized because it updates the display when the system is most ready to do so. That means if you want to avoid janky animations, you need to make the animation happen inside an requestAnimationFrame loop. I'm not sure if your code is doing that, but here's how you can set the frame rate to 15 fps using requestAnimationFrame:
var fps = 15,
startTime = 0,
frameDuration = 1000 / fps;
//Start the game loop
update();
function update() {
requestAnimationFrame(update, canvas);
//Check whether the current time matches the start time
if (Date.now() >= startTime || startTime === 0){
//... run the game code ...
//Set the new start time for the next frame,
//which will be 66 milliseconds ahead of the current time
startTime = Date.now() + frameDuration;
}
}
But the biggest bottleneck to the display is usually the rendering: If you can use WebGL it's so much faster than canvas. A good rendering framework like PixiJs can help you optimize this:
https://github.com/GoodBoyDigital/pixi.js/

Categories