No matter what I do, I can't get a fluent canvas animation under Firefox. I even set up a simple test code which does absolutely nothing except calling empty draw function every 1/40 s and it's still flickering.
var t = 0;
function draw(time)
{
console.log(Math.round(time - t));
t = time;
}
setInterval(function(){ requestAnimationFrame(draw); }, 25);
Delay between frames under Firefox sometimes jumps to over 150 ms which is easily noticable by human eye. Same thing happens when using simple setInterval to call draw(), without the requestAnimationFrame. It runs perfectly under Chrome and Opera.
I've also tried getting rid of setInterval, results are the same:
var t = 0;
function draw(time)
{
requestAnimationFrame(draw);
console.log(Math.round(time - t));
t = time;
}
draw();
Is it a known issue? Is there any way to work around it?
Turns out the current implementation of requestAnimationFrame under Firefox is terrible and fails to provide smooth animation when called from timers or network events (even those which are repeated at constant interval).
This makes it hard to redraw canvas when state is updated over websocket connection. The only way I could get smooth animation was calling requestAnimationFrame immediately:
(function draw()
{
requestAnimationFrame(draw);
// do something
})();
When using this method, it's quite often a good idea to implement some kind of frame interpolation, as draw() calls won't be synchronized with network events.
Related
I want to create a custom progress bar. But I'm using a recursive function that updates data for it, I can't update canvas that is in my progress bar.
Here are parts of my code:
var length = 0;
recursiveFunction = function(){
length++;
updateLength(length);
//some work
recursiveFunction();
}
updateLength = function(length){
setLength(length);
}
setLength(length){
var c = document.getElementById(canvas);
var ctx = c.getContext("2d");
ctx.fillStyle = "#fc0";
ctx.fillRect(0, 0, length, 10);
}
All these functions are in different JS files and in different classes.
Problem is that canvas doesn't redraw in setLength function.
This has to do with JavaScript being single-threaded and can only do one thing at the time. As long as that code is running everything else such as updating to screen has to wait.
To get around it you can introduce some asynchronicity to your code pausing the code enough to allow the screen to be updated.
For example (note: this change alone will probably not work without performing other changes):
recursiveFunction = function(){
length++;
updateLength(length);
//some work
requestAnimationFrame(recursiveFunction); // makes call async
}
The function will now end but an event is added for future use (usually 16.7ms in this case). In the mean time the canvas can be updated to screen.
But not without problems of course. Context is changing and since it's a recursive function you may want to pass in arguments. Although not shown in the post which ones if any, you could instead of requestAnimationFrame() use setTimeout() which allow you to pass in arguments. You can also use bind() if you're depending on context (i.e. this).
// example of bind
requestAnimationFrame(recursiveFunction.bind(this));
The setTimeout() can take more arguments than delay:
setTimeout(recursiveFunction.bind(this), 17, arg1, arg2, arg3, ...);
An alternative to this is to use Web Workers. This will allow you to run your code as fast as possible at a separate thread, and once in a while send back a message to main host containing progress so far which will allow canvas to be updated independently. This is the recommended path if the function is long-running. Web workers has good support but won't work with older IE or Opera Mini.
Here's my tween function:
var secondsNotchTween = function() {
var tween = new Kinetic.Tween({
node: secondsNotchShape,
rotationDeg: currentRotationDegree + 6,
duration: 0.5,
easing: Kinetic.Easings.ElasticEaseOut
});
return {
tween: tween
};
};
I am calling it each second with setTimeout like that:
var secondsNotchAnimation = function() {
secondsNotchTween().tween.play();
};
var playSecondsNotchAnimation = function() {
minuteNotchTimeout = setTimeout(function() {
secondsNotchAnimation();
playSecondsNotchAnimation();
}, displayTime().intervalToSecond);
};
It's working just as I want it to, however when the animation is in the 'background' (it's not in the current browser tab) the tween is not really executing. I presume it's some sort of requestAnimationFrame issue however I can't seem to find a way around it? Has anyone else encountered such issue?
You presumption is correct.
KineticJS animations use requestAnimationFrame to drive its animations and RAF will automatically halt execution when its browser tab is not focused. Changing the internals of KineticJS animation processing is not recommended.
The workaround would require you to create your own requestAnimationFrame loop that advances your animation based on elapsed time rather than framecount.
Luckily, RAF has anticipated this need.
When the tab regains focus RAF will send a timestamp to your animation loop. You can use that timestamp to calculate elapsed time. Then you can properly advance your animation to reflect the elapsed time rather than the framecount of a halted animation loop.
You'll have to decide whether it's worth recreating the animation functions (and easings) outside of the KineticJS structure. It's certainly not difficult, but the quantity of functionality to recreate is large.
Either way...good luck with your project!
I need to catch the exact moment when HTML5 audio starts producing sound.
It turns out not so simple as it seems.
You might expect audio starts playing when onplay or onplaying event is fired? No way. At least in WebKit family, it seems to be no browser event that fires exactly at this point of time. In Chrome, Safari and Firefox onplay and onplaying events are just faking their behaviour by simply firing together with oncanplay!
I've prepared a simple test to prove that fact. It demonstrates that audio actually starts playing after some reasonable time (over 100ms - 400ms) when all the events had already been fired.
You can notice this by your ears and ears if you look at console log. In the log I output currentTime every 15ms. It seems to reflect the actual audio state correctly, and it starts changing 10-40 polls after any event has been fired. So the audio is still freezed after play is fired.
Test code looks like this:
var audioEl = new Audio('http://www.w3schools.com/tags/horse.ogg');
audioEl.oncanplay = function () {
console.log('oncanplay');
audioEl.currentTime = 1;
console.log('ready state is: ' + audioEl.readyState);
audioEl.play();
}
audioEl.oncanplay = function () {
console.log('oncanplay again');
}
audioEl.onplay = function() {
console.log('onplay -----------------');
}
audioEl.onplaying = function() {
console.log('onplaying ----------------');
}
setInterval(function () {
console.log(audioEl.currentTime);
}, 15);
JsFiddle
I critically need to know the exact moment when the audio starts playing for precise synchronisation with visual animations.
Of course, I can find this moment roughly using quick polling. This is very bad for performance in real-time app, especially on mobile.
I'm asking if anyone knows any better solution for this. HTML audio implementation looks to be still so poor in 2014 :(
As #justin says, you can listen for the playing event to get the (more or less) precise moment the media starts actually playing. But yeah I've been seeing some spotty support for media events and readyState in general, even in latest Chrome.
Whether those events work or not, I advise against using setInterval for animation (or just about anything else, for that matter). Instead, use requestAnimationFrame, which will fire more often and should synchronize with the browser and video card's repaint. And you can poll for the currentTime value on every frame to calculate where you should be in your animation. Performance shouldn't be a problem; your case is exactly what requestAnimationFrame was designed for.
function update() {
var currentTime = audioEl.currentTime;
// update your animation here
requestAnimationFrame(update);
}
update();
While you're at it, don't set currentTime to 5 until after readyState > 0 or the loadedmetadata event fires. If you try to set currentTime before the browser has loaded enough of the video file to know the duration, it will throw an error. But you can call play() whenever you want; it doesn't have to wait for canplay.
Try the canplaythrough instead. Might help and would be better to be sure your audio can be palyed all the way to the end anyway..
audioEl.oncanplay = function () {
console.log('ready state is: ' + audioEl.readyState);
audioEl.play();
}
Is there a way to know when the browser is actively running requestAnimationFrame?
For example when I switch tabs from one that was running requestAnimationFrame, the function stops getting executed, when I switch back it continues, what is the best way to deal with this?
To detect if requestAnimationFrame is running 100% you can check:
window.addEventListener('blur', function() {
//not running full
}, false);
and
window.addEventListener('focus', function() {
//running optimal (if used)
}, false);
this can be used as we know requestAnimationFrame reduces trigger rate (in most browsers) when window (tab) is not the active one (IF being used - it depends on the code actually using the requestAnimationFrame).
If you want it to run constantly you can insert a mechanism such as this:
var isActiveTab = true; //update with the events above
function myLoop() {
//my cool stuff here
if (isActiveTab) {
requestAnimationFrame(myLoop);
} else {
setTimeout(myLoop, 16); //force a rate (vblank sync not necessary
//when display isn't updated
}
}
Note that the reduction in rate for requestAnimationFrame is not part of the standard and is a browser specific implementation.
When you again come back to the tab with animation,It must be working fine(If thats the case--following is your answer!!!)
This is what RAF made for.To optimize performance.
SetInterval and Settimeout can be used instead for creating animations, But they cannot interact with the browser and eventually end up hogging up the cpu and the performance is also quite slow.
But your question is really not a question.This is actually a trick used by RAF to better your overall animation experience.
There are several articles which explains RAF.
http://creativejs.com/resources/requestanimationframe/
Just An Optimization TRICK--No need to worry about it
A solution I used in a project for canvas repainting. It's not 100% accurate but it works for out of focus users
// This will run when user is inactive
let = handleVisibilityChange = () => {
if (document.hidden) {
setTimeout(() => {
updateYourStuff();
handleVisibilityChange();
}, 1000);
}
};
// Listen if user is active or inactive
document.addEventListener("visibilitychange", handleVisibilityChange, false);
// Your loop when user is active
function myLoop() {
updateYourStuff();
requestAnimationFrame(myLoop);
}
If you need to know at what time a frame was painted, you can call requestPostAnimationFrame (google canary) or use a polyfill for it.
I want to to queue several transitions one after the other in html5 canvas.
Looping the transition function calls all the transitions at once. I dont know if callback will be do this if the iterations are more than 100.
I want to do something like this:--
for(i=0;i<10;i++)
{
move(circle,Math.floor(Math.random()*1000),400);
}
move is my defined function which makes some transitions.its working perfectly fine.
Here, i want the circle to change its postion with every iteration but its changing its position only once.
You could do this:
var i=10;
var interval = window.setInterval(function(){
move(circle,Math.floor(Math.random()*1000), 400);
console.log(i);
if(!--i) {
window.clearInterval(interval);
}
}, 400); // wait 400 msecs between calls
Or, if your move function was willing to invoke a callback function once the transition was complete :
var i=10;
var callback = function(){
if(i--){
move(circle,Math.floor(Math.random()*1000),400, callback);
}
}
callback();
Yeah ofcource. Its not exactly the solution to the problem but sort of a trick.i first stored the instructions in a separate array (transitionsequence) and used a recursive callback to Callback (the callback defined in kinetic). its not very efficient method but i dont care as long as it solves the problem. :)
`function move2( i , limit) {
var obj = transitionsequence[i].object;
obj.transitionTo({
y:100,
duration: 0.3,
callback : function()
{
obj.transitionTo({
x:transitionsequence[i].x,
duration:0.3,
callback: function()
{
obj.transitionTo({
y:transitionsequence[i].y,
duration:0.3,
callback: function()
{
if(i < limit)
move2(i+1 , limit);
}
});
}
});
}
});
};`
The reason why your approach doesn't work is because the browser doesn't get an opportunity to repaint the canvas between your painting steps. Traditionally this was solved by rendering a single step (frame) and then waiting a small amount of time, but there's a new feature available in recent browsers: requestAnimationFrame, which is solving that exact problem.
Check Animating with javascript: from setInterval to requestAnimationFrame and requestAnimationFrame for Smart Animating (they also show how to create a shim for animating in browsers that don't support requestAnimationFrame).
(I don't know kinetic.js, but there might even be direct support for such a shim in it).