I have this simple jquery logic, How would I convert that into pure javascript?
Besides I have to use this code in React with Typescript.
I have no clue where to start unfortunately. Any help would be extremely appreciated.
$('.counting').each(function() {
var $this = $(this),
countTo = $this.attr('data-count');
$({ countNum: $this.text()}).animate({
countNum: countTo
},
{
duration: 3000,
easing:'linear',
step: function() {
$this.text(Math.floor(this.countNum));
},
complete: function() {
$this.text(this.countNum);
//alert('finished');
}
});
});
I've converted that until start animate function..
let counting = document.querySelectorAll(".counting");
let countingArray = Array.prototype.slice.call(counting);
countingArray.forEach((el) => {
let countTo = el.getAttribute("data-count");
//start animate...
I referred this code in https://codepen.io/shvvffle/pen/JRALqG
An animation function for you:
function animate(render, from, to, duration, timeFx) {
let startTime = performance.now();
requestAnimationFrame(function step(time) {
let pTime = (time - startTime) / duration;
if (pTime > 1) pTime = 1;
render(from + (to - from) * timeFx(pTime));
if (pTime < 1) {
requestAnimationFrame(step);
}
});
}
render is the callback function with which you expect to update new values;
from and to are the initial values and the target values of your animation;
duration is the continuance of the animation in time in miliseconds;
timeFx is the timing function from [0, 1] to [0, 1].
You may use it as:
countingArray.forEach((el) => {
let countTo = el.getAttribute("data-count");
animate(function(newValue) {
el.innerText = Math.floor(newValue);
}, 0, countTo, 3000, x => x);
});
Related
This code make the counter/animation start when in view, but I would like for it to restart when scrolling out of view and then in view again. Can't seem to solve it.
If you would like to see it live link here - scroll down to the bottom just before the footer.
https://easyrecycle.dk/Serviceomraader.html
var a = 0;
$(window).scroll(function() {
var oTop = $('#counter').offset().top - window.innerHeight;
if (a == 0 && $(window).scrollTop() > oTop) {
$('.counter-value').each(function() {
var $this = $(this),
countTo = $this.attr('data-count');
$({
countNum: $this.text()
}).animate({
countNum: countTo
},
{
duration: 3000,
easing: 'swing',
step: function() {
$this.text(Math.floor(this.countNum));
},
complete: function() {
$this.text(this.countNum);
//alert('finished');
}
});
});
a = 1;
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="counter">
<div class="counter-container">
<div class="counter-box">
<div class="counter-value" data-count="30">0</div>
<span></span>
<p>Antal medarbejdere</p>
</div>
<div class="counter-box">
<div class="counter-value" data-count="51000">0</div>
<span></span>
<p>Processeret udstyr i KG pr. md.</p>
</div>
<div class="counter-box">
<div class="counter-value" data-count="51">0</div>
<span></span>
<p>Antal afhentninger pr. md.</p>
</div>
</div>
</div>
Don't use the .scroll() Event listener. It's a performance killer. Same goes for JavaScript's "scroll" EventListener - if used to perform heavy tasks. Actually you don't need jQuery at all.
Your task basically involves the use of:
the Intersection Observer API to watch for elements entering / exiting the viewport — instead of the pretty expensive scroll listener you used
a counterStart() function (triggered on viewport enter) that uses requestAnimationFrame — the most performant way to handle animations on the web.
a counterStop() function (triggered on viewport exit) that cancels the AnimationFrame and resets the element counter back to its start value.
an easing function in which you'll pass the linearly mapped time fraction (float 0.0 ... 1.0) in order to retrieve the easing value to be used to derive your integers/counters.
//gist.github.com/gre/1650294
const easeInOutQuad = (t) => t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
const inViewportCounter = (el) => {
const duration = +el.dataset.duration || 3000;
const start = +el.textContent || 0;
const end = +el.dataset.count || 100;
let raf;
//stackoverflow.com/a/70746179/383904
const counterStart = () => {
if (start === end) return; // If equal values, stop here.
const range = end - start;
let curr = start; // Set current to start
const timeStart = Date.now();
const loop = () => {
let elaps = Date.now() - timeStart;
if (elaps > duration) elaps = duration;
const frac = easeInOutQuad(elaps / duration); // Get the time fraction with easing
const step = frac * range; // Calculate the value step
curr = start + step; // Increment or Decrement current value
el.textContent = Math.trunc(curr); // Apply to UI as integer
if (elaps < duration) raf = requestAnimationFrame(loop); // Loop
};
raf = requestAnimationFrame(loop); // Start the loop!
};
const counterStop = (el) => {
cancelAnimationFrame(raf);
el.textContent = start;
};
//stackoverflow.com/a/70746179/383904
const inViewport = (entries, observer) => {
entries.forEach(entry => {
// Enters viewport:
if (entry.isIntersecting) counterStart(entry.target);
// Exits viewport:
else counterStop(entry.target);
});
};
const Obs = new IntersectionObserver(inViewport);
const obsOptions = {}; //developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options
// Attach observer to element:
Obs.observe(el, obsOptions);
};
document.querySelectorAll('[data-count]').forEach(inViewportCounter);
[data-count] { margin: 55vh 0; font: 3rem/1.4 sans-serif; }
Scroll down...
<div data-count="300">0</div>
<div data-count="51000">0</div>
<div data-count="1000">0</div>
For a better understanding of the above main two chunks of code head to their original base answers which I basically reused and adapted:
Animate counter from start to end value
CSS3-Animate elements if visible in viewport (Page Scroll)
with the basic differences being:
in the counter function I passed the linear fraction of time into the easing function
and for the Intersection Observer one, I used an if .. else block; the else used to reset the element's textContent back to "0" (or any start value).
Sorry for not using jQuery as I probably would've years ago, but hopefully you learned something exciting and new. If some parts are not completely clear, feel always free to ask.
As you can see from my example below, i add a simple if (if is not in view) and reset the text/variable.
var a = 0;
$(window).scroll(function() {
var oTop = $('#counter').offset().top - window.innerHeight;
if (a == 0 && $(window).scrollTop() > oTop) {
$('.counter-value').each(function() {
var $this = $(this),
countTo = $this.attr('data-count');
$({
countNum: $this.text()
}).animate({
countNum: countTo
},
{
duration: 3000,
easing: 'swing',
step: function() {
$this.text(Math.floor(this.countNum));
},
complete: function() {
$this.text(this.countNum);
//alert('finished');
}
});
});
a = 1;
} else {
$('.counter-value').text('0');
a = 0;
}
});
.fakespace{
height:500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='fakespace'></div>
<div id="counter">
<div class="counter-container">
<div class="counter-box">
<div class="counter-value" data-count="30">0</div>
<span></span>
<p>Antal medarbejdere</p>
</div>
<div class="counter-box">
<div class="counter-value" data-count="51000">0</div>
<span></span>
<p>Processeret udstyr i KG pr. md.</p>
</div>
<div class="counter-box">
<div class="counter-value" data-count="51">0</div>
<span></span>
<p>Antal afhentninger pr. md.</p>
</div>
</div>
</div>
I am a beginner with JS and have no idea how to pull it off here. Idea is to have commas in the numbers to make it easier to read while counting up and also the final static result should have commas.
Eg: "10,000"
var a = 0;
$(window).scroll(function() {
var oTop = $('#counter').offset().top - window.innerHeight;
if (a == 0 && $(window).scrollTop() > oTop) {
$('.counter-value').each(function() {
var $this = $(this),
countTo = $this.attr('data-count');
$({
countNum: $this.text()
}).animate({
countNum: countTo
},
{
duration: 2000,
easing: 'swing',
step: function() {
$this.text(Math.floor(this.countNum));
},
complete: function() {
$this.text(this.countNum);
//alert('finished');
}
});
});
a = 1;
}
});
Best way is to use the built-in Intl object. More info here.
const number = 123456;
const formattedNumber = new Intl.NumberFormat('en-us').format(number);
console.log({ formattedNumber }); // 123,456
How can I change the following function so it will display random numbers instead of counting up to the final number?
$('.counter').each(function() {
var $this = $(this),
countTo = $this.attr('data-count');
$({
countNum: $this.text()
}).animate({
countNum: countTo
}, {
duration: 1000,
easing: 'linear',
step: function() {
$this.text(Math.floor(this.countNum));
},
complete: function() {
$this.text(this.countNum);
var element = document.getElementById('result');
element.style.opacity = "1";
element.style.filter = 'alpha(opacity=1)'; // IE fallback
var button = document.getElementById('return');
button.style.opacity = "1";
button.style.filter = 'alpha(opacity=1)'; // IE fallback
}
});
});
Thanks in advance.
This:
step: function() {
$this.text(Math.floor(this.countNum));
},
Should be:
step: function() {
var min = 5; // change min if you want to
var max = 200; // change max if you want to
$this.text(Math.floor(Math.random() * (max - min) + min)); // use Math.random to generate the random numbers
},
Sample:
http://codepen.io/anon/pen/xgZMVd/
html file:
<div class="game-page">
</div>
css file:
.game-page {
width: 1024px;
height: 768px;
position: absolute;
background-color: grey;
}
.stick {
position: absolute;
left: 50%;
margin-left: -94px;
top: -60px;
width: 188px;
height: 50px;
background-color: black;
}
js file:
$(document).ready(function() {
init();
});
var CSS_CLASS = { STICK: "stick" },
changeSpeedTime = 1000, //in milliseconds
gameTime = 60, //in seconds
timer = 1, //in seconds
windowWidth = 1024,
windowHeight = 768,
stickTop = -60,
restStickTime = 0, //in milliseconds
initialStickInterval = 1000, //in milliseconds
stickInterval = null, //in milliseconds
initialStickDuration = 7, //in seconds
stickDuration = null, //in seconds
stickTweensArray = [],
changeSpeedInterval = null,
countdownTimerInterval = null,
generateSticksInterval = null,
generateSticksTimeout = null,
$gamePage = null;
function init() {
initVariables();
initGamePage();
}
function changingSpeedFunction(x){
var y = Math.pow(2, (x / 20));
return y;
}
function initVariables() {
$gamePage = $(".game-page");
stickDuration = initialStickDuration;
stickInterval = initialStickInterval;
}
function initGamePage() {
TweenMax.ticker.useRAF(false);
TweenMax.lagSmoothing(0);
initGamePageAnimation();
}
function initGamePageAnimation () {
generateSticks();
changeSpeedInterval = setInterval(function () {
changeSpeed();
}, changeSpeedTime);
countdownTimerInterval = setInterval(function () {
updateCountdown();
}, 1000);
}
function changeSpeed () {
var x = timer;
var y = changingSpeedFunction(x); //change speed function
stickDuration = initialStickDuration / y;
stickInterval = initialStickInterval / y;
changeCurrentSticksSpeed();
generateSticks();
}
function changeCurrentSticksSpeed () {
stickTweensArray.forEach(function(item, i, arr) {
var tween = item.tween;
var $stick = item.$stick;
var oldTime = tween._time;
var oldDuration = tween._duration;
var newDuration = stickDuration;
var oldPosition = stickTop;
var newPosition = $stick.position().top;
var oldStartTime = tween._startTime;
var distance = newPosition - oldPosition;
var oldSpeed = distance / oldTime;
var newSpeed = oldSpeed * oldDuration / newDuration;
var newTime = distance / newSpeed;
var currentTime = oldStartTime + oldTime;
var newStartTime = currentTime - newTime;
item.tween._duration = newDuration;
item.tween._startTime = newStartTime;
});
}
function generateSticks () {
if (restStickTime >= changeSpeedTime) {
restStickTime -= changeSpeedTime;
restStickTime = Math.abs(restStickTime);
} else {
generateSticksTimeout = setTimeout(function () {
generateSticksInterval = setInterval(function () {
generateStick();
restStickTime -= stickInterval;
if (restStickTime <= 0) {
clearInterval(generateSticksInterval);
restStickTime = Math.abs(restStickTime);
}
}, stickInterval);
generateStick();
restStickTime = changeSpeedTime - Math.abs(restStickTime) - stickInterval;
if (restStickTime <= 0) {
clearInterval(generateSticksInterval);
restStickTime = Math.abs(restStickTime);
}
}, restStickTime);
}
}
function generateStick () {
var $stick = $("<div class='" + CSS_CLASS.STICK + "'></div>").appendTo($gamePage);
animateStick($stick);
}
function animateStick ($stick) {
var translateYValue = windowHeight + -stickTop;
var tween = new TweenMax($stick, stickDuration, {
y: translateYValue, ease: Power0.easeNone, onComplete: function () {
$stick.remove();
stickTweensArray.shift();
}
});
stickTweensArray.push({tween:tween, $stick:$stick});
}
function updateCountdown () {
timer++;
if (timer >= gameTime) {
onGameEnd();
clearInterval(changeSpeedInterval);
clearInterval(countdownTimerInterval);
clearInterval(generateSticksInterval);
clearTimeout(generateSticksTimeout);
}
}
function onGameEnd () {
var $sticks = $gamePage.find(".stick");
TweenMax.killTweensOf($sticks);
}
So, as I researched, I have next situation:
TweenMax (as it uses requestAnimationFrame) freezes when tab is inactive.
setInterval keep going when tab is inactive (also it's delay may change when tab is inactive, depends on browser)
Is there any other javascript functionality that changes when tab is inactive?
Then I have 2 solutions:
Freeze whole game, when tab is inactive.
Keep going, when tab is inactive.
With first solution I have next problem: as TweenMax uses requestAnimationFrame, it works correct according to this solution (freezes animation), but how can I freeze intervals and timeouts when tab is inactive and then resume intervals and timeouts?
With second solution I can use TweenMax.lagSmoothing(0) and TweenMax.ticker.useRAF(false) for animation and it works, but anyway something goes wrong with intervals and/or timeouts. I expected that animation goes wrong because of change of interval delay to 1000+ ms when tab is inactive (according to http://stackoverflow...w-is-not-active), but I disabled acceleration and set delays to 2000ms and it didn't help.
Please help me with at least one solution. Better with both to have some variety.
Resolved problem with first solution: freeze whole game. Just used TweenMax.delayedCall() function instead of setTimeout and setInterval and whole animation synchronized pretty well.
I'm developing an HTML5 application and I'm drawing a sequence of images on the canvas with a certain speed and a certain timeout between every animation.
Able to use it multiple times I've made a function for it.
var imgNumber = 0;
var lastImgNumber = 0;
var animationDone = true;
function startUpAnimation(context, imageArray, x, y, timeOut, refreshFreq) {
if (lastImgNumber == 0) lastImgNumber = imageArray.length-1;
if (animationDone) {
animationDone = false;
setTimeout(playAnimationSequence, timeOut, refreshFreq);
}
context.drawImage(imageArray[imgNumber], x, y);
}
function playAnimationSequence(interval) {
var timer = setInterval(function () {
if (imgNumber >= lastImgNumber) {
imgNumber = 0;
clearInterval(timer);
animationDone = true;
} else imgNumber++;
}, interval);
}
Now in my main code, every time startUpAnimation with the right parameters it works just fine. But when I want to draw multiple animations on the screen at the same time with each of them at a different interval and speed it doesnt work!
startUpAnimation(context, world.mushrooms, canvas.width / 3, canvas.height - (canvas.height / 5.3), 5000, 300);
startUpAnimation(context, world.clouds, 100, 200, 3000, 100);
It now displays both animations at the right location but they animate both at the interval and timeout of the first one called, so in my case 5000 timeout and 300 interval.
How do I fix this so that they all play independently? I think I need to make it a class or something but I have no idea how to fix this. In some cases I even need to display maybe 5 animations at the same time with this function.
Any help would be appreciated!
try something like this
var Class = function () {
this.imgNumber = 0;
this.lastImgNumber = 0;
this.animationDone = true;
}
Class.prototype.startUpAnimation = function(context, imageArray, x, y, timeOut, refreshFreq) {
// if (lastImgNumber == 0) lastImgNumber = imageArray.length-1;
if (this.animationDone) {
this.animationDone = false;
setTimeout(this.playAnimationSequence, timeOut, refreshFreq, this);
}
// context.drawImage(imageArray[imgNumber], x, y);
};
Class.prototype.playAnimationSequence = function (interval, object) {
var that = object; // to avoid flobal this in below function
var timer = setInterval(function () {
if (that.imgNumber >= that.lastImgNumber) {
that.imgNumber = 0;
clearInterval(timer);
that.animationDone = true;
console.log("xxx"+interval);
} else that.imgNumber++;
}, interval);
};
var d = new Class();
var d1 = new Class();
d.startUpAnimation(null, [], 300, 300, 5000, 50);
d1.startUpAnimation(null,[], 100, 200, 3000, 60);
It should work,
As far as I can see, each call to startUpAnimation will draw immediately to the canvas because this execution (your drawImage(..) call) hasn't been included in the callback of the setTimeout or setInterval.