I created from various sources the simplest js counter up I could find. How can I make different setTimeout for each counter so both of them end at the same time?
UPDATE: I have minimized the right answer below and this is the final code:
const counters = document.querySelectorAll('.counter');
const duration = 2000; // Finish in 2 seconds
const speed = 2000;
const state = {};
const max = Math.max(...[...counters]
.map(counter => +counter.dataset.target));
const tick = duration / max;
const updateCount = (counter) => {
const target = +counter.dataset.target;
const value = +counter.dataset.value;
const ratio = target / max;
const ms = Math.ceil(ratio * tick);
const incr = target / speed;
const newVal = value + incr;
if (value < target) {
counter.innerText = Math.floor(newVal);
counter.dataset.value = newVal;
setTimeout(updateCount, ms, counter);
} else {
counter.innerText = target;
counter.dataset.value = target;
}
};
counters.forEach(updateCount);
<div class="counter" data-key="1" data-value="0" data-target="1000">0</div>
<div class="counter" data-key="2" data-value="0" data-target="2000">0</div>
Each timer has to run at an increases rate relative to the max time.
This is a rudimentary example, and it still may need some work. I also recmomment using the dataset property to store the value and only re-render the value (in case the result is a floating-point number).
const results = document.querySelector('.results');
const counters = document.querySelectorAll('.counter');
const duration = 2000; // Finish in 2 seconds
const speed = 1000;
const state = {};
const max = Math.max(...[...counters]
.map(counter => +counter.dataset.target));
const tick = duration / max;
const updateCount = (counter) => {
const target = +counter.dataset.target;
const value = +counter.dataset.value;
const ratio = target / max;
const ms = Math.ceil(ratio * tick);
const incr = target / speed;
const newVal = value + incr;
const { dataset: { key }} = counter;
state[key] = {
...state[key],
ratio, ms, incr, value
};
results.textContent = JSON.stringify(state, null, 2);
if (value < target) {
counter.innerText = Math.floor(newVal);
counter.dataset.value = newVal;
setTimeout(updateCount, ms, counter);
} else {
counter.innerText = target;
counter.dataset.value = target;
}
};
counters.forEach(updateCount);
.results {
border: thin solid grey;
padding: 0.25em;
white-space: pre;
font-family: monospace;
}
<div class="counter" data-key="1" data-value="0" data-target="1000">0</div>
<div class="counter" data-key="2" data-value="0" data-target="2000">0</div>
<div class="counter" data-key="3" data-value="0" data-target="4000">0</div>
<div class="results"></div>
It works like this because your setTimeout(updateCount, 100); runs after 100 miliseconds. So when you are counting from 0 to 150 and you are adding a number once per 100ms it will be twice faster as counting to 300.
You can make it end at the same time, when you change setTimeout() to run after 50 miliseconds for counting to 300
Something like this
setTimeout(updateCount150, 100);
setTimeout(updateCount300, 50);
Of course you need to adjust those both functions accordingly.
Try getting the avg, like this:
const counters = document.querySelectorAll('.counter');
const speed = 1;
let avg=0;
counters.forEach(counter => {
avg+=parseInt(counter.getAttribute('data-target'))
});
avg=avg/counters.length;
counters.forEach(counter => {
const updateCount = () => {
const count = +counter.innerText;
const target = +counter.getAttribute('data-target')
const inc = counter.getAttribute('data-target') / (speed * avg);
if (count < target) {
counter.innerText = count + inc;
setTimeout(updateCount, 100);
} else {
counter.innerText = target;
}
};
updateCount();
});
<div class="counter" data-target="150">0</div>
<div class="counter" data-target="300">0</div>
Related
I have an animated counter number that works fine on mouseover but I have 2 problems with it:
I can't find a way to add mouse out to see the number decrease when the mouse is out and increase when the mouse is over
How to have 3 numbers visible, I mean starting with 000 then 001, 002... until 99 then 100, 101, 102... and not 000 then 1, 2, 3...
Here is my code
function animationEffect(){
const counters = document.querySelectorAll('.counter');
for(let n of counters) {
const updateCount = () => {
const target = + n.getAttribute('data-target');
const count = + n.innerText;
const speed = 3000; // change animation speed here
const inc = target / speed;
if(count < target) {
n.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 1);
} else {
n.innerText = target;
}
}
updateCount();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<span>Kal /</span>
<span id="animate" style="font-size: 200px;" class="counter" data-target="652" onmouseover="animationEffect();">000</span>
I'm trying to calculate the normalized cpu percentage for my node process. My goal is to get it to match the output that I have in my htop for the pid but I'm unable to do so. My code is below along with my htop output.
import { cpuUsage } from "node:process";
import { cpus } from "os";
function basicCpuUsage() {
const startTime = process.hrtime();
const startUsage = cpuUsage();
const numCpus = cpus().length;
const add = 1 + 1; // make cpu do work?
const usageDiff = cpuUsage(startUsage); // get diff time from start
const endTime = process.hrtime(startTime); // total amount of time that has elapsed
const usageMS = (usageDiff.user + usageDiff.system) / 1e3;
const totalMS = endTime[0] * 1e3 + endTime[1] / 1e6;
const cpuPercent = (usageMS / totalMS) * 100;
const normTotal = usageMS / numCpus; // average usage time per cpu
const normPercent = (normTotal / totalMS) * 100;
console.log({
cpuPercent: cpuPercent.toFixed(2),
normPercent: normPercent.toFixed(2),
});
}
process.title = "CPU Test";
const { pid } = process;
console.log({ pid });
const title = setInterval(() => {
basicCpuUsage();
}, 1000);
Here is my output, as you can see my code cpu output does no match my htop cpu output. Which part of my calculation is incorrect? I was thinking this might have to do with my setInterval function call, but not sure. I am attempting to create a long running process where I can view the cpu usage.
Turns out I was making an incorrect calculation. I was dividing my totalTimeMS twice when I should have done it once. Additionally I moved the currentUsage and currentTime to the outside of the function.
Below is the correct calculation:
import { cpus } from "os";
let currentUsage = process.cpuUsage();
let currentTime = process.hrtime();
function basicCpuUsage() {
const numCpus = cpus().length;
const usageDiff = process.cpuUsage(currentUsage); // get diff time from start
const endTime = process.hrtime(currentTime); // total amount of time that has elapsed
currentUsage = process.cpuUsage()
currentTime = process.hrtime();
const usageMS = (usageDiff.user + usageDiff.system) / 1e3;
const totalMS = endTime[0] * 1e3 + endTime[1] / 1e6;
const cpuPercent = (usageMS / totalMS) * 100;
const normPercent = (usageMS / totalMS / numCpus) * 100; // average usage time per cpu
console.log({
cpuPercent: cpuPercent.toFixed(2),
normPercent: normPercent.toFixed(2),
});
}
process.title = "CPU Test";
setInterval(() => {
for (let i = 0; i < 25; i++) {
const add = 1 + 1;
}
basicCpuUsage();
}, 5000);
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>
I made counting animations! But, the Designer asked them to take commas every three digits, so I wrote a code to take commas, but I think it should be uploaded in real-time, not just at the end. I'm not used to JavaScript yet. ㅜㅜ How should I fix it?
function counterAnimationHandler() {
const counters = document.querySelectorAll('.counter ')
counters.forEach(counter => {
counter.innerText = '0' //set default counter value
const updateCounter = () => {
const target = +counter.getAttribute('data-target') //define increase couter to it's data-target
const count = +counter.innerText //define increase couter on innerText
const increment = target / 200 // define increment as counter increase value / speed
if (count < target) {
counter.innerText = `${Math.ceil(count + increment)}`;
setTimeout(updateCounter, 1);
} else {
counter.innerText = numberWithCommas(target); //if default value is bigger that date-target, show data-target
}
}
updateCounter() //call the function event
})
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
}
counterAnimationHandler();
<div class="counter" data-target="1000000"></div>
I would suggest you keepp a different variable for count with the raw (unformatted) number and then make sure you wrap every update to the UI with numberWithCommas.
function counterAnimationHandler() {
const counters = document.querySelectorAll('.counter ')
counters.forEach(counter => {
counter.innerText = '0' //set default counter value
counter.dataset.count = 0;
const updateCounter = () => {
const target = +counter.getAttribute('data-target') //define increase couter to it's data-target
const count = +counter.dataset.count //define increase couter on innerText
const increment = target / 200 // define increment as counter increase value / speed
if (count < target) {
const newCount = Math.ceil(count + increment);
counter.dataset.count = newCount;
counter.innerText = numberWithCommas(newCount);
setTimeout(updateCounter, 1);
} else {
counter.innerText = numberWithCommas(target); //if default value is bigger that date-target, show data-target
}
}
updateCounter() //call the function event
})
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
}
counterAnimationHandler();
<div class="counter" data-target="1000000"></div>
I am trying to use a for loop in order to run a function 10 times, which relies on the previous state to update. I know I am not supposed to setState in a loop since state changes are batched, so what are my options if I want to run the function 10 times with a single click handler? I am using hooks (useState), if that matters.
Below is my relevant code :
export default function Rolls(props) {
const [roll, setRoll] = useState(null)
const [tenPityCount, setTenPityCount] = useState(0)
const [ninetyPityCount, setNinetyPityCount] = useState(0)
// handler for single roll
const handleSingleRoll = () => {
// sets the main rolling RNG
const rng = Math.floor(Math.random() * 1000) +1
// pulls a random item from the data set for each star rating
const randomFiveStar = Math.floor(Math.random() * fiveStarData.length)
const randomFourStar = Math.floor(Math.random() * fourStarData.length)
const randomThreeStar = Math.floor(Math.random() * threeStarData.length)
// check if ten pity count has hit
if (tenPityCount === 9) {
setRoll(fourStarData[randomFourStar].name)
setTenPityCount(0)
return;
}
// check if 90 pity count has hit
if (ninetyPityCount === 89) {
setRoll(fiveStarData[randomFiveStar].name)
setNinetyPityCount(0)
return;
}
// check if rng hit 5 star which is 0.6%
if (rng <= 6) {
setRoll(fiveStarData[randomFiveStar].name)
setNinetyPityCount(0)
// check if rng hit 4 star which is 5.1%
} else if (rng <= 51) {
setRoll(fourStarData[randomFourStar].name)
setTenPityCount(0)
// only increment for 90 pity counter b/c 10 pity resets upon hitting 4 star
setNinetyPityCount(prevState => prevState +1)
// anything else is a 3 star
} else {
setRoll(threeStarData[randomThreeStar].name)
// pity counter increment for both
setTenPityCount(prevState => prevState + 1)
setNinetyPityCount(prevState => prevState +1)
}
}
const handleTenRoll = () => {
for (let i = 0; i < 10; i++) {
handleSingleRoll()
}
}
return (
<>
<div>
<span>
<button onClick={handleSingleRoll} className='btn btn-primary mr-2'>Wish x1</button>
<button onClick={handleTenRoll} className='btn btn-primary mr-2'>Wish x10</button>
<button className='btn btn-danger'>Reset</button>
</span>
</div>
</>
)
}
My suggestion would be to use a proxy functions:
const [roll, setRollState] = useState(null)
const [tenPityCount, setTenPityCountState] = useState(0)
const [ninetyPityCount, setNinetyPityCountState] = useState(0)
const newState = useRef({});
const consolidatedUpdates = useRef({setRollState, setTenPityCountState, setNinetyPityCountState})
function presetStateUpdate(name, state){
newState.current[name] = state
}
function pushPresetState(){
Object.entries(newState.current).forEach(([fnName, value]) => {
const fn = consolidatedUpdates[fnName];
if(typeof fn == 'function') fn(value);
});
newState.current = {};
}
const setRoll = v => presetStateUpdate('setRoll', v);
const setTenPityCount = v => presetStateUpdate('setTenPityCount', v);
const setNinetyPityCount = v => presetStateUpdate('setNinetyPityCount', v);
respectively:
const handleTenRoll = () => {
for (let i = 0; i < 10; i++) {
handleSingleRoll()
}
pushPresetState()
}