Playing animation during x seconds using setInterval - javascript

I want to display an animated number from 0 to max value y during x seconds. I have tried this following code but it take too much to complete and clear the interval.
jQuery('.numbers').each(function(item, index) {
const $obj = jQuery(this);
let objValue = parseInt($obj.text()),
currentValue = 0,
speed = 1,
time = 4000,
step = Math.floor(objValue / time);
$obj.text(currentValue);
let interVal = setInterval(() => {
if (currentValue >= objValue) {
clearInterval(interVal);
$obj.text(objValue);
}
$obj.text(currentValue);
currentValue += step
}, speed);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class='numbers'>7586</span>
<span class='numbers'>147520</span>
How do I play this animation during exactly time seconds?

It is better not to depend on the timing of setInterval(), but the real problem in your script is that you use the floored value to decide the new value to print out.
It is better to use Window.requestAnimationFrame() and create a update() function that prints the current number based on the real time elapsed.
let start, previousTimeStamp;
let numbers = document.querySelectorAll('.numbers');
requestAnimationFrame(update);
function update(timestamp) {
if (start === undefined) {
start = timestamp;
}
const elapsed = timestamp - start;
[...numbers].forEach(elm => {
if(!elm.dataset.start){
elm.dataset.start = elm.textContent;
}
let start = parseInt(elm.dataset.start);
elm.textContent = Math.floor(start / 4000 * elapsed);
});
if (elapsed < 4000) {
previousTimeStamp = timestamp;
requestAnimationFrame(update);
}else {
start = undefined;
[...numbers].forEach(elm => {
elm.textContent = elm.dataset.start;
});
}
}
<span class='numbers'>7586</span>
<span class='numbers'>147520</span>

Related

Random speed in javascript count?

So I have this javascript which basically goes from 0 to 1000 at the same exact speed:
HTML:
<div id="value">0</div>
Javascript:
function animateValue(id, start, end, duration) {
if (start === end) return;
var range = end - start;
var current = start;
var increment = end > start? 1000 : +1;
var stepTime = Math.abs(Math.floor(duration / range));
var obj = document.getElementById(id);
var timer = setInterval(function() {
current += increment;
obj.innerHTML = current;
if (current == end) {
clearInterval(timer);
}
}, stepTime);
}
animateValue("value", 100, 25, 5000);
Basically I want the counter to go up with random speed intervals.
So as it goes up, it will slow down and speed up completely random until it reaches 1000.
How do I achieve that?
Thanks for your help.
I don`t understand purpose of duration parameter in function. That is why i used it as a max duration of one iteration.
/**
* Increment value with random intervals.
* #param {string} id - Id of DOM Element.
* #param {number} start - Start counter value. Applied immediately.
* #param {number} end - End counter value.
* #duration {number} duration - Max duration of one iteration in ms.
*/
function animateValue(id, start, end, duration) {
let current = start;
const obj = document.getElementById(id);
obj.innerHTML = current; // immediately apply start value
const setIncrOut = () => {
let time = Math.random() * duration;
setTimeout(function () {
if (current < end) {
current += 1;
obj.innerHTML = current;
setIncrOut(time);
}
}, time);
}
setIncrOut();
}
animateValue("value", 100, 1001, 1000);
<div id="value"></div>
A simplified version:
animate(document.querySelector(`#value`), 5, 50, 1000);
function animate(elem, start, end, maxDuration) {
elem = elem || document.querySelector(`#value`);
if (start < end) {
elem.textContent = !elem.textContent.trim() ? start : start + 1;
return setTimeout( () =>
animate( elem, start + 1, end, maxDuration ),
Math.floor( (Math.random() * maxDuration) ) );
}
}
#value {
font-size: 2rem;
font-weight: bold;
}
<div id="value"></div>
Instead of using setInterval, which has a static pause between iterations you could create your own asynchronous loop that pauses with variable time.
Incomplete, but something like...
async function setVaryingInterval(callback, start, end, increment) {
var step = start;
while (step != end) {
step += increment;
var random = Math.random() * 3000;
await new Promise(resolve => setTimeout(resolve, random));
callback(step);
}
}
setVaryingInterval((value) => {
// this will be executed with random delays, and the delay can be changed by adding logic to the "random" variable
console.log(value);
}, 0, 1000, 1);
You will need to add logic for speeding up / slowing down, this will simply have a random delay between every interval, but should be easy enough to add if you have a vision on how it should behave.
You can paste this code in your dev tools (F12) and see the random behaviour, and tailor it to your needs.

How to optimize a script in terms of performance

I have a function called ajaxCall() which simple does the call on a server and returns a value from a feed.
What I wanted to do is to save the value from feed to firstInterval variable, wait 10 seconds, make the call again and save the value to secondInterval variable. And then animate the increasing of these numbers on the webpage. This is what we have so far:
setInterval(function(){
getAmounts()
}, 11000);
function getAmounts(){
firstInterval = ajaxCall();
setTimeout(() => {
secondInterval = ajaxCall();
animateValue('#bronze-price p', firstInterval, secondInterval, 10000)
}, 10000);
};
function animateValue(id, start, end, duration) {
start = parseInt(start);
end = parseInt(end);
var range = end - start;
var current = start;
var increment = end > start? 1 : -1;
var stepTime = Math.abs(Math.floor(duration / range));
var obj = document.querySelector(id);
var timer = setInterval(function() {
current += increment;
obj.innerHTML = current;
if (current == end) {
clearInterval(timer);
}
}, stepTime);
}
So the idea is to have a function which gets first interval, after 10 seconds it grabs the second interval and calls the animation. This all is wrapped into setInterval function so I could change the number smoothly repeatedly.
However I am pretty sure that thats not a very clean solution since its setInterval in setTimeout and this all is wrapped in another setInterval function. I am also getting this kind of warnings in the console with both functions:
[Violation] 'setInterval' handler took ms
What would be the best approach to follow up on this idea but optimize the code?
I think Promises and requestAnimationFrame makes this a lot easier to handle while also getting rid of setTimeout/setInterval. An example would be:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#Getting_a_random_integer_between_two_values_inclusive
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// fake api endpoint
const ajaxCall = () => new Promise(resolve => {
setTimeout(resolve, 400, 1000 + getRandomIntInclusive(0, 1000));
});
// simple animation helper using requestAnimationFrame
const animate = (from, to, duration, cb) => {
const start = performance.now();
return new Promise(resolve => {
requestAnimationFrame(function loop() {
const f = (performance.now() - start) / duration;
cb(from + Math.round(f * (to - from)));
f < 1.0
? requestAnimationFrame(loop)
: resolve();
});
});
};
// main
(async (interval) => {
// where to output
const dst = document.querySelector('#out');
// initial value
let prev = 0;
// wait for next value of ajaxCall
while (true) {
const next = await ajaxCall();
console.log(`animating: ${prev} -> ${next}`);
// animate from `prev` to `next`
await animate(prev, next, interval, current => {
dst.innerHTML = current;
});
prev = next;
}
})(10000);
<div id="out"></div>

Track the total time the HTMLAudio was playing

How could I track for how long was the HTMLAudio playing in total?
What I tried to do is to run the following function each time an audio started to play or paused:
let totalTimeInSeconds = 0;
let intervalId = null;
const startedToPlay = function() {
intervalId = setInterval(function() { ++totalTimeInSeconds; }, 1000);
};
const pausedPlaying = function() {
clearInterval(intervalId);
};
But I am getting a little bit of a difference. The difference is +-20 seconds.
So, what is a better way to do this?
In order to avoid the x-y problem: I need this to trigger my own events once the audio played for more than a specified amount of time in total (that means that we can rewind the audio back and forth, but still we are only interested in total time listened and not the length of the audio listened).
Thank you.
The way I would approach this is to simply set a timeout when they start playing, and clear the timeout when they pause, and update the total time left only on pause.
Something like:
const SECOND = 1000;
let timeLeft = 5 * SECOND;
let startTime = null;
let timeout = null;
let div = document.getElementById('timer')
let toggle = document.getElementById('toggle');
let playing = false;
toggle.addEventListener('click', () => {
if (playing) {
pausedPlaying();
} else {
startedToPlay();
}
playing = !playing;
});
const startedToPlay = function() {
startTime = (new Date()).getTime();
timeout = setTimeout(() => alert(), timeLeft);
};
const pausedPlaying = function() {
clearTimeout(timeout);
timeLeft -= (new Date()).getTime() - startTime;
startTime = null;
};
setInterval(() => div.textContent = startTime ? (timeLeft - ((new Date()).getTime() - startTime)) : timeLeft);
<div id="timer">5</div>
<button id="toggle">Start/Stop</button>
Note the interval at the bottom is only for updating the displayed time left.

How to wrap consecutive setInterval() in a forever loop?

I was trying to implement a function, which is supposed to post measurement A every 5 sec for 10 times, and then post measurement B every 5 sec for a random amounts of time. And I want repeat this function forever as I was trying to implement a fake agent.
So I had the code:
let intervalId = null, repeat = 0;
while (true) {
intervalId = setInterval(() => {
if (repeat < 5) {
// post measurement A
repeat += 1;
}
else {
clearInterval(intervalId)
}
}, 1000);
repeat = 0;
intervalId = setInterval(() => {
if (repeat < Math.floor(Math.random() * 11)) {
// post measurement B
repeat += 1;
}
else {
clearInterval(intervalId)
}
}, 1000);
}
The two setInterval() function didn't happen consecutively as I expected, instead they happened at the same time. And the while (true) loop seems not behave as expected either. I'm just wondering is there any way to get around with this problem? Thanks.
You can create two function, one is doA() and one is doB().
Start with doA(), count the number of time //do A is called, when it reached 10, clearInterval and call doB().
In doB(), set the min and max time it should be called, then when it reached randTime clearInterval and doA()
function doA() {
let count = 0;
const a = setInterval(() => {
//do A
console.log('do A');
count += 1;
if (count === 10) {
clearInterval(a);
doB();
}
}, 5000/10);
}
function doB() {
// set your min and max for B
const minTime = 1;
const maxTime = 10;
const randTime = Math.floor(Math.random() * (maxTime - minTime + 1)) + minTime;
let count = 0;
const b = setInterval(() => {
// do B
console.log(randTime);
console.log('do B');
count += 1;
if (count === randTime) {
clearInterval(b);
doA();
}
}, 5000 / randTime);
}
doA();
Working on top of your code, first thing first, remove infinite while loop. It will run endlessly in synchronous fashion while setInterval is asynchronous. repeat value will be far ahead before you do repeat += 1.
Second, break them down in function so they have their own closure for intervalId and repeat value.
function intervalA () {
let intervalId = null
let repeat = 0
intervalId = setInterval(() => {
if (repeat < 5) {
console.log(new Date(), 'A')
// post measurement A
repeat += 1; // increment repeat in callback.
}
else {
clearInterval(intervalId); // done with interval, clear the interval
intervalB(); // and start interval B
}
}, 1000)
}
function intervalB () {
let repeat = 0
let randomEnd = Math.floor(Math.random() * 11) // calculate when it should end.
let intervalId = setInterval(() => {
if (repeat < randomEnd) {
console.log(new Date(), 'B will finish in', randomEnd, 'times')
repeat += 1
}
else {
clearInterval(intervalId) // clear the interval once done
}
}, 1000)
}
intervalA(); //start with interval A
Currently, the intervals are being set at once, synchronously, at the start of your script and during every while thereafter. It would probably be clearer if you only a single interval, with a variable that indicated which measurement to run, and change that variable every random-nth iteration:
const getRandomRepeats = () => Math.ceil(Math.random() * 11)
let repeatsRemaining = getRandomRepeats();;
let measureA = true;
setInterval(() => {
repeatsRemaining--;
if (repeatsRemaining === 0) {
repeatsRemaining = getRandomRepeats();
measureA = !measureA;
}
console.log('repeats remaining: ' + repeatsRemaining);
if (measureA) {
console.log('posting a');
} else {
console.log('posting b');
}
}, 1000);

Effect where numbers count as the element appears [duplicate]

I am updating a numeric value inside an element by doing intervalled ajax requests.
To make the whole thing a bit more alive, I want to count from the current value to the new one, by partially in- or decreasing the value over a time of n sec.
So basically something like this:
<div id="value">100</div>
<script type="text/javascript">
/** Decrease $value (over a time of 2 seconds) till it reaches 25 */
$value.increaseAnimation(-75, {duration:2});
</script>
Is there a javascript library for doing so?
You can just code it yourself pretty simply:
function animateValue(id, start, end, duration) {
if (start === end) return;
var range = end - start;
var current = start;
var increment = end > start? 1 : -1;
var stepTime = Math.abs(Math.floor(duration / range));
var obj = document.getElementById(id);
var timer = setInterval(function() {
current += increment;
obj.innerHTML = current;
if (current == end) {
clearInterval(timer);
}
}, stepTime);
}
animateValue("value", 100, 25, 5000);
#value {
font-size: 50px;
}
<div id="value">100</div>
Here's is a more accurate version that self adjusts in case the timer intervals aren't perfectly accurate (which they sometimes aren't):
function animateValue(id, start, end, duration) {
// assumes integer values for start and end
var obj = document.getElementById(id);
var range = end - start;
// no timer shorter than 50ms (not really visible any way)
var minTimer = 50;
// calc step time to show all interediate values
var stepTime = Math.abs(Math.floor(duration / range));
// never go below minTimer
stepTime = Math.max(stepTime, minTimer);
// get current time and calculate desired end time
var startTime = new Date().getTime();
var endTime = startTime + duration;
var timer;
function run() {
var now = new Date().getTime();
var remaining = Math.max((endTime - now) / duration, 0);
var value = Math.round(end - (remaining * range));
obj.innerHTML = value;
if (value == end) {
clearInterval(timer);
}
}
timer = setInterval(run, stepTime);
run();
}
animateValue("value", 100, 25, 5000);
#value {
font-size: 50px;
}
<div id="value">100</div>
Current solutions do update more often than needed. Here a frame based approach, which is accurate:
function animateValue(obj, start, end, duration) {
let startTimestamp = null;
const step = (timestamp) => {
if (!startTimestamp) startTimestamp = timestamp;
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
obj.innerHTML = Math.floor(progress * (end - start) + start);
if (progress < 1) {
window.requestAnimationFrame(step);
}
};
window.requestAnimationFrame(step);
}
const obj = document.getElementById('value');
animateValue(obj, 100, -25, 2000);
div {font-size: 50px;}
<div id="value">100</div>
I had a slightly different approach to this kind of animation. Based on these assumptions:
there is a starting text (as fallback for non-JS browser or google
indexing)
this text can contains non-numeric chars
the function take an element directly as first param (as opposed to an
element Id)
So, if you want to animate a simple text like "+300% gross margin", only the numeric part will be animated.
Moreover, all params have now a default value for start, end and duration.
https://codepen.io/lucamurante/pen/gZVymW
function animateValue(obj, start = 0, end = null, duration = 3000) {
if (obj) {
// save starting text for later (and as a fallback text if JS not running and/or google)
var textStarting = obj.innerHTML;
// remove non-numeric from starting text if not specified
end = end || parseInt(textStarting.replace(/\D/g, ""));
var range = end - start;
// no timer shorter than 50ms (not really visible any way)
var minTimer = 50;
// calc step time to show all interediate values
var stepTime = Math.abs(Math.floor(duration / range));
// never go below minTimer
stepTime = Math.max(stepTime, minTimer);
// get current time and calculate desired end time
var startTime = new Date().getTime();
var endTime = startTime + duration;
var timer;
function run() {
var now = new Date().getTime();
var remaining = Math.max((endTime - now) / duration, 0);
var value = Math.round(end - (remaining * range));
// replace numeric digits only in the original string
obj.innerHTML = textStarting.replace(/([0-9]+)/g, value);
if (value == end) {
clearInterval(timer);
}
}
timer = setInterval(run, stepTime);
run();
}
}
animateValue(document.getElementById('value'));
#value {
font-size: 50px;
}
<div id="value">+300% gross margin</div>
Now we can animate counters (and a lot of things that cannot be animated before) with CSS variables and new #property. No javascript needed. Currently supports only Chrome and Edge.
#property --n {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
body {
display: flex;
}
.number {
animation: animate var(--duration) forwards var(--timing, linear);
counter-reset: num var(--n);
font-weight: bold;
font-size: 3rem;
font-family: sans-serif;
padding: 2rem;
}
.number::before {
content: counter(num);
}
#keyframes animate {
from {
--n: var(--from);
}
to {
--n: var(--to);
}
}
<div class="number" style="--from: 0; --to: 100; --duration: 2s;"></div>
<div class="number" style="--from: 10; --to: 75; --duration: 5s; --timing: ease-in-out"></div>
<div class="number" style="--from: 100; --to: 0; --duration: 5s; --timing: ease"></div>
const counters = document.querySelectorAll('.counters');
counters.forEach(counter => {
let count = 0;
const updateCounter = () => {
const countTarget = parseInt(counter.getAttribute('data-counttarget'));
count++;
if (count < countTarget) {
counter.innerHTML = count;
setTimeout(updateCounter, 1);
} else {
counter.innerHTML = countTarget;
}
};
updateCounter();
});
<p class="counters" data-counttarget="50"></p>
<p class="counters" data-counttarget="100"></p>
<p class="counters" data-counttarget="500"></p>
<p class="counters" data-counttarget="1000"></p>
This works well. However, I needed to use a comma within the number. Below is the updated code which checks for commas. Hope someone finds this useful if they stumble across this post.
function animateValue(id, start, end, duration) {
// check for commas
var isComma = /[0-9]+,[0-9]+/.test(end);
end = end.replace(/,/g, '');
// assumes integer values for start and end
var obj = document.getElementById(id);
var range = end - start;
// no timer shorter than 50ms (not really visible any way)
var minTimer = 50;
// calc step time to show all interediate values
var stepTime = Math.abs(Math.floor(duration / range));
// never go below minTimer
stepTime = Math.max(stepTime, minTimer);
// get current time and calculate desired end time
var startTime = new Date().getTime();
var endTime = startTime + duration;
var timer;
function run() {
var now = new Date().getTime();
var remaining = Math.max((endTime - now) / duration, 0);
var value = Math.round(end - (remaining * range));
obj.innerHTML = value;
if (value == end) {
clearInterval(timer);
}
// Preserve commas if input had commas
if (isComma) {
while (/(\d+)(\d{3})/.test(value.toString())) {
value = value.toString().replace(/(\d+)(\d{3})/, '$1'+','+'$2');
}
}
}
var timer = setInterval(run, stepTime);
run();
}
animateValue("value", 100, 25, 2000);
HTML
<!DOCTYPE html>
<html>
<head>
<title>Count</title>
</head>
<body>
<div id="value">1000</div>
</body>
</html>
JAVASCRIPT snippet
Here is a simple js function decrementing values from a given start number to an end number (object prototype)..
function getCounter(startCount,endcount,time,html){
objects = {
//you can alternateif you want yo add till you reach the endcount
startCount:startCount,
endCount:endcount,
timer:time
}
this.function = function(){
let startTm = objects.startCount,
timer = objects.timer,
endCount = objects.endCount;
//if you want it to add a number just replace the -1 with +1
/*and dont forget to change the values in the object prototype given a variable of counter*/
let increament = startTm < endCount ? 1:-1;
timmer = setInterval(function(){
startTm += increament;
html.innerHTML = startTm ;
if(startTm == endCount){
clearInterval(timmer);
}
},timer);
}
}
// input your startCount,endCount the timer.....
let doc = document.getElementById('value');
let counter = new getCounter(1000,1,10,doc);
//calling the function in the object
counter.function();
Check out this demo https://jsfiddle.net/NevilPaul2/LLk0bzvm/
This is what i came up with:
function animateVal(obj, start=0, end=100, steps=100, duration=500) {
start = parseFloat(start)
end = parseFloat(end)
let stepsize = (end - start) / steps
let current = start
var stepTime = Math.abs(Math.floor(duration / (end - start)));
let stepspassed = 0
let stepsneeded = (end - start) / stepsize
let x = setInterval( () => {
current += stepsize
stepspassed++
obj.innerHTML = Math.round(current * 1000) / 1000
if (stepspassed >= stepsneeded) {
clearInterval(x)
}
}, stepTime)
}
animateVal(document.getElementById("counter"), 0, 200, 300, 200)
Here is one version where increments grow by some defined multiplier (mul).
frameDelay is the time delay for each increment. This looks a bit better if you have values that camn
function cAnimate(id, start, end, frameDelay = 100, mul = 1.2) {
var obj = document.getElementById(id);
var increment = 2;
var current = start;
var timer = setInterval(function() {
current += increment;
increment *= mul;
if (current >= end) {
current = end;
clearInterval(timer);
}
obj.innerHTML = Math.floor(current).toLocaleString();
}, frameDelay);
}
cAnimate("counter", 1, 260000, 50);
I used a mixture of all of these to create a function that updates a BehaviorSubject.
function animateValue(subject, timerRef, startValue, endValue, duration){
if (timerRef) { clearInterval(timerRef); }
const minInterval = 100;
const valueRange = endValue - startValue;
const startTime = new Date().getTime();
const endTime = startTime + (duration * 1000);
const interval = Math.max((endTime-startTime)/valueRange, minInterval);
function run() {
const now = new Date().getTime();
const rangePercent = Math.min(1-((endTime-now)/(endTime-startTime)),1);
const value = Math.round(rangePercent * valueRange+startValue);
subject.next(value);
if (rangePercent >= 1) {
clearInterval(timerRef);
}
}
timerRef = setInterval(run, interval);
run();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
</style>
</head>
<body style="width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center;">
<h1 style="font-size: 50px;" data-value="3212">0</h1>
<script>
let e = document.querySelector('h1');
let v = Number(e.dataset.value);
let i = Math.floor(v/10);
let r = v%10;
function increment() {
let c = Number(e.innerText);
if (c<v) {
if (v-c===i+r) {
e.innerText = c+i+r;
}
else{
e.innerText = c+i;
};
setTimeout(increment,200);
};
};
setTimeout(increment, 200);
</script>
</body>
</html>

Categories