why jquery can't animate number accurately? - javascript

i am trying to use the following code to increment number in a textbox
// Animate the element's value from 0 to 1100000:
$({someValue: 0}).animate({someValue: 1100000}, {
duration: 1000,
step: function() { // called on every step
// Update the element's text with value:
$('#counterx').text(Math.floor(this.someValue+1));
}
});
it is working with small numbers like from 0 to 100
but when it comes to large number like in the mentioned code,
it is not giving the target number,
it is animating to numbers like 1099933 or 1099610 or .....
and every time it changes.
so how can i make it to animate to the number i specify?

I have the same issue. The reasoning is because animate function uses a mathematical formula that is time based. You don't really notice this when animating something css based because close enough in pixels is good enough. It will get close to the final value but may not always be exactly the end value. Solution is to use the complete event to set that last value.
Here is what you need to do:
function animateNumber(ele,no,stepTime){
$({someValue: 0}).animate({someValue: no}, {
duration: stepTime,
step: function() { // called on every step. Update the element's text with value:
ele.text(Math.floor(this.someValue+1));
},
complete : function(){
ele.text(no);
}
});
}
animateNumber($('#counterx'),100,10000);
animateNumber($('#countery'),100,1000)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
counterx(slow): <span id=counterx>--</span>
<br/>
countery(fast): <span id=countery>--</span>

1) Javascript is a single threaded application. Timeouts and animations ONLY push the event to the end of the stack based on an ideal stacking order. A long running section of script can cause the actual firing time of that event well past the accuracy you are looking for.
2) Animation approximates how much to increment, and on larger numbers that resolution is very inaccurate.
3) jQuery only has one animation buffer. You might run into some serious rendering issues if you invoke more than one "counter" using animation. Make sure to stop the previous animation before making any adjustments that effect it.
4) Even with a timeout of 0, you can expect the real world delay of ~15. Even if that is the only "thread" you have running.
Solution:
take a snapshot of the DTG
set your interval to something within the human experience, say ~200
on each interval, check how much time has passed from the original DTG
set your text field to that delta number.
stop the interval with the original DTG + "your target number" > the new DTG

Animate is not designed to increment a counter as text (though it may work by accident, which could change with any new version of jQuery), it's designed to animate one or more CSS properties. You should be using setInterval instead.
http://jsfiddle.net/jbabey/mKa5r/
var num = 0;
var interval = setInterval(function () {
document.getElementById('result').innerHTML = num;
num++;
if (num === 100) {
clearInterval(interval);
}
}, 100);​

Here's a solution that doesn't use .animate().
DEMO: http://jsfiddle.net/czbAy/4/
It's just a linear modification; you don't get the easing options if that's what you were after.
var counterx = $('#counterx'), // cache the DOM selection! :)
i = 0,
n = 1100000,
dur = 1000, // 1 second
int = 13,
s = Math.round(n / (dur / int));
var id = setInterval(function() {
counterx.text(i += s);
if (i >= n) {
clearInterval(id);
counterx.text(n);
}
}, int);

Here is a jquery plugin to animate numbers reliably, ut uses the complete callback to set the correct final number once the animation has finished:
https://github.com/kajic/jquery-animateNumber

Related

Should I use requestAnimationFrame to fade in several elements? To achieve 60fps animation?

Trying to maintain 60 fps animation. Currently, I'm getting a lot of spikes of slow frames that are happening at like 30~ fps and the choppiness is noticeable to my eye.
Significant Edits: Throwing out old obsolete code, adding in new code with explanation
fadeIn: function(ele){
raf = window.requestAnimationFrame(function() {
console.log(ele);
var opacity = 0;
function increase () {
opacity += 0.05;
if (opacity >= 1){
// complete
ele.style.opacity = 1;
return true;
}
ele.style.opacity = opacity;
requestAnimationFrame(increase);
}
increase();
});
},
fadeInElements: function(elements, properties, speed, delay){
var raf;
var ele;
for (i = 0; i < properties.length; i++){
ele = elements[properties[i]];
console.log('outside loop ' + ele);
instance.fadeIn(ele);
}
},
My new code is above. It is successfully:
Iterating through several elements (each as ele) and then calling fadeIn(ele)
So, all elements fade in.
However, I want a 50ms delay between each "fade in" (each triggering of fadeIn() on a new element
The good news is that it's not actually recursion — it's more like a timeout. You provide a function that draws a frame, and the browser calls it for you.
Here's an answer showing excerpts from a complete JSFiddle. It doesn't try to replicate your exact code, but instead tries to explain what you need to know so you can adapt your code accordingly. The code was written to be easy to understand, so I'm sure there are other ways to do it faster!
This answer works from the top level down, which means I describe the end of the JSFiddle source and work my way backwards. I personally think this makes it easier to understand than does starting with the details.
You need to start the animation somewhere. So the very last thing in the JSFiddle is
window.requestAnimationFrame(eachFrame); //start the animation
This will call a function called eachFrame() when it's time for the next frame, e.g,. on the next multiple of 60 times per second. It will only do it once, though.
You need eachFrame() to keep track of where we are in the animation.
var startTime = -1.0; // -1 = a flag for the first frame.
function eachFrame()
{
// Render this frame ------------------------
if(startTime<0) {
// very first frame (because of the -1.0): save the start time.
startTime = (new Date()).getTime();
render(0.0);
// the parameter to render() is the time within the
// animation.
} else {
// every frame after the first: subtract the saved startTime
// to determine the current time.
render( (new Date()).getTime() - startTime );
}
// Now we're done rendering one frame. ------
//Start the timer to call this function again
//when it's time for the next frame.
window.requestAnimationFrame(eachFrame);
}; //eachFrame
eachFrame() determines what the current time is with respect to the beginning of the animation. getTime() gives you the time in milliseconds.
The other thing eachFrame() does is to call window.requestAnimationFrame(eachFrame); again. This isn't recursion. Instead, eachFrame() will finish running, and then after that, the next time a frame comes around, the browser will call eachFrame() again.
The last function you need is something to actually draw the frame! That is render(current time). Assume that, e.g., head1 and head2 refer to two heading elements you want to animate, e.g., <h1> elements declared in your HTML. The clamp(x) function returns x but clamped below at 0 and above at 1.
function render(currTime)
{ // *** Put your rendering code here ***
// How opaque should head1 be? Its fade started at currTime=0.
var opacity1 = clamp(currTime/FADE_DURATION);
// over FADE_DURATION ms, opacity goes from 0 to 1
// How opaque should head2 be?
var opacity2 = clamp( (currTime-FADE_SPACING)/FADE_DURATION );
// fades in, but doesn't start doing it until
// FADE_SPACING ms have passed.
// Apply the changes
head1.style.opacity = opacity1;
head2.style.opacity = opacity2;
} //render
In render(), you figure out opacity based on the current time. You don't have to worry about delaying between frames, because requestAnimationFrame handles that for us. You stagger the transitions by offsetting the time. In this example, opacity1 depends on currTime and opacity2 depends on currTime minus a constant FADE_SPACING, so the opacity change for element 2 will start later than the opacity change for element 1 by FADE_SPACING ms.
The JSFiddle has all the details filled in. It animates the opacity of two <h1> elements, with a spacing between the beginning of the animation for each element. I hope this helps!

How to animate a style property in plain vanilla javascript?

So for a while I've been moving away from jQuery, and in general just reducing my library use where-every possible to a) write leaner code, and b) really understand at a low level whats going on, particularly around the UI. While I've moved the majority of my UI animation to CSS3, theres often times when you need a little more control, but for a single tiny animation I'd prefer not to always have to pull in velocity.js or greensock etc.
Looking at you-might-not-need-jquery theres a fadeIn function they demostate that looks like this :
function fadeIn(el) {
el.style.opacity = 0;
var last = +new Date();
var tick = function() {
el.style.opacity = +el.style.opacity + (new Date() - last) / 400;
last = +new Date();
if (+el.style.opacity < 1) {
(window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16);
}
};
tick();
}
fadeIn(el);
I generally understand this function, but have a few questions on very specific items :
What does the + before new Date() and el.style.opacity on lines 3,5,6 & 8 indicate? is it something like += ?
On line 5, why the division by 400?
Is there anything inherently wrong with this recursive requestAnimationFrame technique for general quick animations?
If I understand the concept behind this pattern :
we set our starting position (force feeding) and time we're beginning,
we then update the style relevant to the amount of time thats passed,
until the final state is satisfied, call tick again, on the next animation frame.
Is this correct?
A unary + is a quick way to force a value to be interpreted as a number.
The division by 400 is the way that code sets the rate of fade-in. A bigger number would make the fade take longer, and a smaller number would make it faster. The number gives the number of milliseconds that will elapse (more or less) before the element is fully opaque.
It's not recursive. The reference to the function is passed to the timer mechanism (either setTimeout() or requestAnimationFrame()) but by the time the timer fires the original call will have exited.

Animate count down/up to specific number

I currently count down/up to a target number. The small snippet of code I currently use to do this seems to produce inconsistent results. There must be a bullet proof way of doing this without writing pages of code. Any advice would be greatly appreciated.
DEMO https://jsfiddle.net/d6anjubd/
var currentNum = $('p').text();
var targetNum = 0;
$({countNum: parseInt(currentNum)}).animate({countNum: parseInt(targetNum)}, {
duration: 500,
easing:'linear',
step: function() {
$('p').text(Math.floor(this.countNum));
},
complete: function() {
console.log('finished count');
}
});
You will notice that if you press 'run' within the jsfiddle, it will not always end in the target number, in this instance 0.
changing the line easing:'linear' to easing:'swing' makes this work for me.
Guessing there might be a problem with linear easing on such short intervalls as when I increase the duration of the animation (2000+) linear works to.
An added note: The amount of error between target and result also seems to vary with the size of the difference between the two numbers (the bigger X the more error: (ABS(startValue - target) = X). This makes sense as a greater distance means it needs to travel further to get to the target.
It would also be logical for the countdown to take more time to countdown when the distance between target and value is greater. ie: duration: 15 * currentNum
Increasing the allowed time to reach the target with the distance needed to travel makes the calculation work every time for me, no matter the target or number.

Javascript Loop: run function after precise time

I have searched high and low for a solution to this (that I can understand) and have yet to find one.
Fiddle here
https://jsfiddle.net/the_o/a7dwp41s/
The Goal:
To have the text change after a set period of time (for example 1 second).
The Problem:
The timing is not accurate at all. It's not apparent in the fiddle but on my page the text sometimes changes waaaay too fast. Also sometimes the loop will just stop. I know that setTimeout is not accurate from reading other Stack Overflow answers, but have not come across a good solution for running a function after a set period accurately. I'd appreciate some help.
The HTML:
<span class="text-center"><span class="top-line">A heading here</span>
<br><span class="bottom-line">There once was a...<br>
<span id="changeTextMobile"></span></span>
</span>
The Javascript:
var text = ["carrot", "potato", "tomato", "lettuce", "radish", "cabbage", "melon", "cucumber"];
var elem = document.getElementById("changeTextMobile");
var counter = 0;
function rotate_text() {
elem.innerHTML = text[counter];
if (counter < 8) {
counter++window.setTimeout(rotate_text, 1200);
}
if (counter == 8) {
counter = 0;
}
}
rotate_text();
Here's the fiddle again: https://jsfiddle.net/the_o/a7dwp41s/
The classic approach here is to set a fine-grained timer (either with setTimeout or more likely requestAnimationFrame, then within that examine the current time and compute the difference with the previous time, and execute your action if the desired amount of time has passed. RAF will send your callback a high-resolution timer which can be used if you are interested in sub-millisecond precision.
var text = ["carrot", "potato", ...];
var elem = document.getElementById("changeTextMobile");
var counter = 0;
var DELAY = 1000;
var old_timestamp = 0;
function rotate_text(timestamp) {
if (timestamp > old_timestamp + DELAY) { // HAVE WE WAITED LONG ENOUGH?
update_vegetable(); // CHANGE VEGETABLE NAME.
old_timestamp = timestamp; // REMEMBER WHEN WE DID THAT.
}
requestAnimationFrame(rotate_text); // RINSE AND REPEAT
}
rotate_text(0);
where update_vegetable would look something like
function update_vegetable() {
elem.textContent = text[counter++ % 8];
}
This should give you very accurate results. However, note that some browsers may slow down requestAnimationFrame when the tab is in the background. Also note that requestAnimationFrame may require vendor prefixing in certain browsers.
If you don't want to use requestAnimationFrame for some reason, you should be able to replace the call to it with setTimeout(tick, 16) with similar results.
Analysis of current code
In your current code, when counter reaches 8 and you reset it to 0, you are not calling setTimeout again to continue the sequence. That seems wrong.
if (counter == 8) {
counter = 0; //YOU ARE NOT RESETTING THE TIMEOUT.
}
In any case, you're better off using counter % 8 as another answer suggests and as shown above.
Also, the line below seems broken and is missing a semicolon. Is this your actual code? What it would do is add to counter the timer ID returned by setTimeout, which is completely meaningless.
counter++window.setTimeout(rotate_text, 1200);
should be
counter++;
window.setTimeout(rotate_text, 1200);
You should use setInterval for such cases. I have updated JSFiddle.
setTimeout
setTimeout is used for cases when you have to add a delay before a function is executed.
setInterval
setInterval is used for case where you have to run certain function after certain time delay.
Also, just a note, setTimeout(function(){},0) does not mean it will be executed immediately. When you use these function, an event is registered to be executed at certain tick, so setTimeout(function(){},0) will be triggered on next tick and not immediately.
var text = ["carrot", "potato", "tomato", "lettuce", "radish", "cabbage", "melon", "cucumber"];
var counter = 0;
function initInterval() {
interval = setInterval(function() {
counter++;
$("#changeTextMobile").text(text[counter % 8]);
}, 1200);
}
$(document).ready(function() {
initInterval();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<span class="text-center"><span class="top-line">A heading here</span>
<br><span class="bottom-line">There once was a...<br>
<span id="changeTextMobile"></span></span>
</span>
Hope it helps.
Another way to accomplish your goal would be to use the setInterval() method instead of the setTimeout() method.
Documentation for the method is found on W3Schools.com
Unless I am misunderstanding your intention, you don't just want to change your text once, but repeatedly. In this case, setInterval() is the choice as setTimeout() is intended to only run a function once. This could be the source of your consistency issues.
If and when you wish to stop the timer's repeat functionality, use clearInterval() in your trigger.

Issue with a javascript jQuery script in OpenX

I'm using OpenX at work, and one of my boss requirements is a expandable banner. For that (and made a horrible simplification of the whole story) I made this script.
function retro(){
var acs = jQuery('#trial_center').height() - 5;
jQuery('#trial_center').css('height', acs + 'px');
}
jQuery(document).ready(function(){
jQuery("#trial_center").mouseover(function(){
setTimeout("jQuery('#trial_center').css('height', '500px')", 1000);
})
jQuery("#trial_center").mouseleave(function(){
var c = 89;
while (c > 0) {
setTimeout("retro()", 1000);
c--;
}
})
});
The problem I have is in the mouseleave event: the original idea was to made this loop several times (89 times), and each time, decrease the height of the banner until it get his original size. Why this? Because my boss want an "effect", and this effect must be in sync with the customer's flash.
The problem is that instead of decrease his size progressively, apparently the script made all the operations an "after" the sum of setTimeout calls, updated the page. So, the result is exactly as the banner shrinks one time from the expanded size to the original size.
I don't know what is wrong with this, or if exists other more intelligent solution.
Any help will be very appreciate.
Thanks in advance
Your loop setting the timeout is just setting 89 timers for one second later than the loop runs, and the loop will run in milliseconds — so they'll all fire about a second later. That doesn't sound like what you want to do.
Two options for you:
1. Use animate
jQuery's animate function seems like it does what you want. You can tell jQuery to animate the size change, and you tell it how long to take to do so:
jQuery('#trial_center').animate({
height: "500px" // Or whatever the desired ending height is
}, 1000);
That will animate changing the height of the container from whatever it is at the point that code runs to 500px, across the course of 1,000 milliseconds (one second). Obviously you can change the duration to whatever you like.
2. Set up the timer loop manually
If for whatever reason you don't want to use animate, you can do this manually (of course you can; jQuery can't do anything you can't do yourself, it just makes things easier). Here's how to set up a timer loop:
jQuery("#trial_center").mouseleave(function(){
var c = 89;
// Do the first one right now, which will schedule the next
iteration();
// Our function here lives on until all the iterations are
// complete
function iteration() {
// Do one
retro();
// Schedule this next unless we're done
if (--c > 0 {
setTimeout(iteration, 100); // 100ms = 1/10th second
}
}
});
That works because iteration is a closure over c (amongst other things). Don't worry about the term "closure" if it's unfamiliar, closures are not complicated.
Separately: You're using mouseover to set the height of the trial_center element a second later; you probably wanted mouseneter rather than mouseover. mouseover repeats as the mouse moves across it.
Off-topic:
It's best not to use strings with setTimeout; just pass it a function reference instead. For example, instead of
setTimeout("retro()", 1000);
you'd use
setTimeout(retro, 1000); // No quotes, and no ()
And for the other place you're using, instead of
setTimeout("jQuery('#trial_center').css('height', '500px')", 1000);
you'd use
setTimeout(function() {
jQuery('#trial_center').css('height', '500px');
}, 1000);

Categories

Resources