Skip to content

Instantly share code, notes, and snippets.

@pgarciacamou
Created January 4, 2017 19:15
Show Gist options
  • Save pgarciacamou/9a297d136695eac9fa43f3bdf4644a20 to your computer and use it in GitHub Desktop.
Save pgarciacamou/9a297d136695eac9fa43f3bdf4644a20 to your computer and use it in GitHub Desktop.
Request Animation Frame HOAX! requestAnimationFrame with setTimeout to push the fps-limited browsers like iOS8.
/*
* Tasks that are supposed to be executed every screen paint.
*/
var tasks = [
() => {
var now = Date.now();
// heavy algorithm example
for(var i = 0; i < 200/*000*/; i+=1){
var dummy = i * 0.1;
}
var milliseconds = Date.now() - now;
// console.log(`algorithm milliseconds: ${milliseconds}`);
}
];
/*
* HELPER: applies a linear transformation to a range to convert it
* to a different range.
*
* E.g. from range [-20, 20] TO [-90, 90]
*/
function linearTransformation(value, sourceMin, sourceMax, destMin, destMax) {
return ((value - sourceMin) / (sourceMax - sourceMin)) * (destMax - destMin) + destMin;
}
/*
* requestAnimationFrame loop
*
* Computes the top speed at which the screen can render
* by keeping track of the highest fps ever (fpsHighestRecord) gotten.
*
* The renderer's speed becomes a function of this fpsHighestRecord.
*
* E.g. iOS8 and above, where fpsHighestRecord is limited to 30fps;
* speed would transform from 0->30fps to 0->16ms (roughly 60fps).
* Thus hacking around the limits.
*
* BUT devices that are not limited;
* speed would transform from 0->~60fps to 0->16ms.
* Thus no change.
*/
var previousTimestamp;
var renderSpeedUpperBound = Math.floor(1000/60);
var renderCurrentSpeed = renderSpeedUpperBound;
var fpsHighestRecord = 0;
requestAnimationFrame(function speedLoop(currentTimestamp){
previousTimestamp = isNaN(previousTimestamp) ? currentTimestamp : previousTimestamp;
if(currentTimestamp > previousTimestamp) {
var tick = currentTimestamp - previousTimestamp;
var fps = Math.floor(1000 / tick);
fpsHighestRecord = Math.max(fpsHighestRecord, fps);
renderCurrentSpeed = linearTransformation(fps, 0, fpsHighestRecord, 0, renderSpeedUpperBound);
previousTimestamp = currentTimestamp;
// console.log(fpsHighestRecord);
// console.log(fps);
}
requestAnimationFrame(speedLoop);
});
/*
* renderLoop
*
* It is a requestAnimationFrame HOAX!.
*
* By using a timeout loop, we can use the renderSpeed computed
* to push the limits of which the device can paint the screen.
*
* An arbitraryInitialWaitingTime is used to allow the requestAnimationFrame
* to compute the fastest the device can go before starting to execute the tasks.
*
* NOTE: this is NOT the same as an interval (but very simillar).
*/
var arbitraryInitialWaitingTime = 500;
setTimeout(function renderLoop(){
setTimeout(renderLoop, renderCurrentSpeed);
tasks.forEach(fn => fn());
}, arbitraryInitialWaitingTime);
@pgarciacamou
Copy link
Author

pgarciacamou commented Jan 4, 2017

iOS8 and above limit the requestAnimationFrame upper bound fps (to what I've seen 30fps).

  1. Even if we use a requestAnimationFrame, something like an animation can look choppy because of the limited rAF.
  2. This limit is not caused by the hardware, but by the software (iOS). Meaning that the browser can —most likely— paint the screen faster than that limit!
  3. By using this timeout hoax, we can hack around the limited browsers to push the devices further. AND if the device turns out to be slow by hardware and can't handle the extra speed, the frames will drop and so will the timeout loop, so we have a win-win situation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment