First of all, I have to warn you, my English is not so good...
OK, here is my problem: I have a progress bar that gets wider every second based on percents.
Every second I want to add 1.67 / [max] percent.
[max] = 100% (how many minutes it'll take).
(if [max] = 10 - the progress bar will take 10 minutes)
My code WORKS, but only if the number (after dividing) is bigger than 0.3 (something like that).
So it means that if the progress bar will take 1 minute ([max] = 1) the code will work, because the number is 1.67 after dividing.
But if I make max to be 15 minutes, it wont work - WHY?!
This is my code: (I added some comments to make it easier)
<script>
function updateProgress() {
/*Get Progress Width (in percents)*/ var progress = ( 100 * parseFloat($('#arena_bar').css('width')) / parseFloat($('#arena_bar').parent().css('width')) );
var corrent = progress;
var max = 1; // one minute
var add = 1.67 / max;
if (progress < 100) {
corrent += add;
$("#arena_bar").css("width", corrent + "%");
setTimeout(updateProgress, 1000); // update every second
}
}
updateProgress();
</script>
Please Help !
The problem is that you're not making the width grow enough for the percentage to change in CSS, so it stays constant (at least that's what it looks like). The thing is, you don't really need all that.
Here's a js fiddle of your code, changed to work. I changed the time delay to make it run faster, you can change it back to 1000ms if you want.
And the code:
HTML:
<div style="width:400px; background-color:black">
<div id="arena_bar" style="background-color:navy; width:10px"> </div>
</div>
JS:
/*Get Progress Width (in percents)*/ var corrent = ( 100 * parseFloat($('#arena_bar').css('width')) / parseFloat($('#arena_bar').parent().css('width')) );
function updateProgress() {
var max = 15; // one minute
var add = 1.67 / max;
if (corrent < 100) {
corrent += add;
$("#arena_bar").css("width", corrent + "%");
setTimeout(updateProgress, 50); // update every second
}
}
updateProgress();
For the record, jQuery is not setting your computed widths as real percentage values, it sets them using pixel values.
In this example you can see your written widths and the read ones.
function updateProgress() {
var progress = (parseInt($('#arena_bar').css('width')) / parseInt($('#arena_bar').parent().css('width')))*100;
$('.progress').text('Read: ' + progress + ' %');
var max = 1;
var add = 1.67 / max;
if (progress < 100) {
progress += add;
$('.debug').html('Written: ' + progress + ' %');
$("#arena_bar").css("width", progress + "%");
setTimeout(updateProgress, 1000); // update every second
}
}
updateProgress();
http://jsfiddle.net/9PjqZ/1/
As you can see there is a difference to the values when you read them in the next function call. There are no problems when the difference between written and read values is above an unknown limit. When they come too close to each other your idea won't work anymore.
You need to save the current percentage outside the css and only write to css but not read from css.
Related
I wrote some JavaScript code to animate CSS properties of elements. I pass the following arguments to the function: amount, interval, and duration; amount being the change in the property (for example 200 could mean add 200 pixels to the element's width), interval being the time between two consecutive changes, and duration being the total duration of the animation.
The code works fine unless I pass the arguments in a way that the change in each interval becomes very small (like a tiny fraction of a pixel).
I know the code is working fine theoretically, as I get the change in console.
Any ideas about the problem?
Cheers.
UPDATE: the code:
function handleTimer (amount, interval, duration, execute, element) {
let i = 0;
let current = 0;
let stepsCount = countSteps(interval, duration);
let stepLength = calcStepLength(stepsCount, amount);
let count = setTimeout(function addOneMore () {
if ( i < stepsCount -1 ){
i++;
current += stepLength;
execute(stepLength, element);
if (current < amount) {
count = setTimeout(addOneMore, interval)
}
} else {
current = amount;
execute(amount - (stepsCount -1) * stepLength, element);
}
}, interval)
}
function countSteps (interval, duration) {
let remainder = duration % interval;
let stepsCount;
if (remainder) {
stepsCount = Math.floor(duration / interval) + 1;
} else {
stepsCount = duration / interval;
}
return stepsCount;
}
function calcStepLength(stepsCount, amount) {
return amount / stepsCount;
}
function resizeWidth (amount, element) {
let widthSTR = $(element).css('width');
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
$(element).css('width', `${width + amount}px`);
}
So this:
handleTimer(218, 5, 200, resizeWidth, '.box');
works fine, but this:
handleTimer(218, 5, 2000, resizeWidth, '.box');
doesn't.
UPDATE 2:
I know browsers are super accurate with pixels, like when you use percentages. Of course the value will be rounded before rendering since displays cant display half pixels, but the value is still calculated accurately.
I don't know at what decimal the rounding occurs.
This happens because parseInt is rounding your number up.
Pay attention to this line:
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
if width is a decimal number, like 22.5px, it will be rounded up to 22.
If amount is less than 1, it won't reach 23 and when you round up the number again, you'll get 22 again and it becomes a loop.
You have two solutions:
Use another variable to save the width value, avoiding to writing and reading it from CSS:
let initialWidth = $(element).css('width');
let savedWidth = widthSTR.substr(0, initialWidth, initialWidth.length - 2 ) );
function resizeWidth (amount, element) {
savedWidth += amount;
$(element).css('width', `${savedWidth}px`);
}
Just use parseFloat in place of parseInt to don't round your number up:
let width = parseFloat( widthSTR.substr( 0 , widthSTR.length - 2 ) );
I am working on simple script that should animate given value (for example 6345.23) to 0 by counting it down, it should also end up at 0 if specified amount of time have passed (for example 2 seconds.
I started by simple logic:
given config: initial value, time in sec, interval
time is given in seconds so convert it to milliseconds
calculate amount of ticks by dividing time in ms by interval
calculate amount of decreased value per tick by dividing initial value by amount of ticks
once above are known we can simply do: (simple model, not actual code)
intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
clearInterval(intId);
} else {
value -= amount_per_tick;
// update view
}
}, interval);
actual code:
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
Link do fiddle (in console you can observe how it works)
If you set time to 2 (2 seconds) its ok, but if you set time to for example 2.55 (2.55 seconds) it doesnt stop at all at 0, its passing by and going indefinitely in negative values.
How i can fix it so no matter what is set in seconds its always go precisly one by one until reaches perfectly 0?
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
You're relying on ticks_made === amount_of_ticks being an exact match. Chances are, due to rounding, you won't get an exact match, so you'd be better off doing:
if(ticks_made >= amount_of_ticks) {
kshetline's answer correctly addresses why you get into negative values. When dealing with fractional IEEE-754 double-precision binary numbers (in the normal range, or even whole numbers in very high ranges), == and === can be problematic (for instance, 0.1 + 0.2 == 0.3 is false). Dealing with values as small as the fractional values here are, accumulated imprecision is also a factor. It's inevitable to have to fudge the final step.
But there's a larger issue: You can't rely on timers firing on a precise schedule. Many, many things can prevent their doing so — other UI rendering work, other scripts, CPU load, the tab being inactive, etc.
Instead, the fundamental technique for animation on browsers is:
Update when you can
Update based on where you should be in the animation based on time, not based on how many times you've animated
Use requestAnimationFrame so your update synchronizes with the browser's refresh
Here's your code updated to do that, see comments:
// Tell in-snippet console to keep all lines (rather than limiting to 50)
console.config({maxEntries: Infinity});
var value = 212.45,
time = 2.55, // in seconds
time_in_ms = time * 1000,
amount_per_ms = value / time_in_ms,
interval = 100 / 6, // in milliseconds, ~16.66ms is a better fit for browser's natural refresh than 20ms
ticks_made = 0;
// A precise way to get relative milliseconds timings
var now = typeof performance !== "undefined" && performance.now
? performance.now.bind(performance)
: Date.now.bind(Date);
// Remember when we started
var started = now();
// Because of the delay between the interval timer and requestAnimationFrame,
// we need to flag when we're done
var done = false;
// Use the interval to request rendering on the next frame
var intId = setInterval(function() {
requestAnimationFrame(render);
}, interval);
// About half-way in, an artificial 200ms delay outside your control interrupts things
setTimeout(function() {
console.log("************DELAY************");
var stop = now() + 200;
while (now() < stop) {
// Busy-loop, preventing anything else from happening
}
}, time_in_ms / 2);
// Our "render" function (okay, so we just call console.log in this example, but
// in your real code you'd be doing a DOM update)
function render() {
if (done) {
return;
}
++ticks_made;
var elapsed = now() - started;
if (elapsed >= time_in_ms) {
console.log(ticks_made, "done");
done = true;
clearInterval(intId);
} else {
var current_value = value - (amount_per_ms * elapsed);
console.log(ticks_made, current_value);
}
}
/* Maximize in-snippet console */
.as-console-wrapper {
max-height: 100% !important;
}
If you run that, then scroll up to the "************DELAY************" line, you'll see that even though rendering was held up by "another process", we continue with the appropriate next value to render.
It would make sense to convert the result of .toFixed() to a number right away:
let amount_per_tick = +(value / amount_of_ticks).toFixed(5);
let value = +(value - amount_per_tick).toFixed(5);
(note the + signs)
Then you will never have to worry about type coercion or anything, and instead just focus on math.
I need help with something I'm working on in JavaScript/jQuery
I'd like to give a set 'destination' number, and give it a set duration, and have it so it adds up in intervals of 1 randomly throughout the duration (not equally spaced, but not all at the start, end or middle), but reaching the 'destination' number by the duration of time is up.
So, if I set a duration of 20 seconds, and a 'destination' number of 10. It will start the timer, and randomly add in intervals of 1 (following no pattern), and the duration finishes at the same time as the last number is added.
I'm really stuck with this, and not sure where to even begin.
Any help at all would be greatly appreciated, thanks a lot!
My approach is:
Divide the duration to equal pieces (one for each increment, starting with 0, ending with the full duration)
Randomize the delays, but keep the same sum. If you add some random value to one delays, then subtract the same amount from delays interval.
Call window.setTimeout with all the delays. Give it a function witch increments the current value by one.
The code:
var start = parseInt($("#inStart").val(), 10),
end = parseInt($("#inEnd").val(), 10),
duration = parseInt($("#inDuration").val(), 10),
difference = end - start,
current = start - 1,
step = duration * 1000 / difference,
delays = [],
index, amount,
increment = function () {
current += 1;
$("#outCurrent").text(current);
};
// calculate equal delays
for (index = 0; index <= difference; index++) {
delays.push(step * index);
}
// randomize delays, without changing the sum
for (index = 1; index < delays.length - 2; index++) {
amount = (Math.random() - 0.5) * step;
delays[index] -= amount;
delays[index+1] += amount;
}
// schedule the increment calls
for (index = 0; index < delays.length; index++) {
window.setTimeout(increment, delays[index]);
}
Here is a demo fiddle, you can try it out.
This is probably basic math that I don't seem to remember.
I'm trying to get from 0 to 5,000,000 in 10 seconds while having all the numbers ticking. I don't have to have the number reach exactly 5,000,000 because I can just do a conditional for it to stop when it's over.
Right now I have this:
count+= 123456
if (count > 5000000) {
count = 5000000;
}
It gives the sense of number moving you know? But It really starts off too high. I wanted to gradually climb up.
You could do something like this:
function timedCounter(finalValue, seconds, callback){
var startTime = (new Date).getTime();
var milliseconds = seconds*1000;
(function update(){
var currentTime = (new Date).getTime();
var value = finalValue*(currentTime - startTime)/milliseconds;
if(value >= finalValue)
value = finalValue;
else
setTimeout(update, 0);
callback && callback(value);
})();
}
timedCounter(5000000, 10, function(value){
// Do something with value
});
Demo
Note that with a number as big as 5000000 you won't see the last couple digits change. You would only see that with a small number like 5000. You could fix that; perhaps by adding in some randomness:
value += Math.floor(Math.random()*(finalValue/10000 + 1));
Demo with randomness
You can tween:
import fl.transitions.Tween;
import fl.transitions.easing.Regular;
var count = 0;
var tween:Tween = new Tween(this, "count", Regular.easeInOut,0,5000000,10, true);
This will tween you variable count from 0 to 5000000 in 10 seconds. Read about these classes if you want to expand on this code.
Tween
TweenEvent
Good luck!
I am using jquery.event.drag.js in a project I am creating, and I am trying to figure out a way to run a script for every X amount of pixels I have dragged. I am using only the X axis for this. Here is some code I have right now.
$('body').drag(function( ev, dd ){
var newcell = currentCell;
var dragOffset = Math.floor(dd.offsetX / 100);
if (dragOffset >= 1) {
alert("Dragged 100px");
}
newcell += dragOffset;
$('#info').html(dragOffset + " | " + dd.offsetX);
updateStack(newcell, magnifyMode);
});
This works, however, since this script runs for every pixel dragged, this runs the alert after 100px dragged, but from then on it runs for every pixel I drag it after that. I'm looking for a way to only run it for every 100px I drag it. Any ideas?
have an outside variable tracking when you last did the alert:
var chunkedOffset = 0;
$('body').drag(function( ev, dd ){
var newcell = currentCell;
var dragOffset = dd.offsetX / 100;
if (dragOffset > chunkedOffset) {
chunkedOffset = dragOffset;
alert("Dragged 100px");
}
newcell += dragOffset;
$('#info').html(dragOffset + " | " + dd.offsetX);
updateStack(newcell, magnifyMode);
});
I'm not entirely familiar with drag.js, but, you could just use modulus division to make sure its every 100px.
x % 100 - will be 0 if divisible by 100, so
if(dd.offsetX % 100 == 0)
{
alert("Dragged 100px");
}