Trigger countup on div scroll - javascript

I've been trying to code a count-up that would be triggered when a specific div is visible on the screen.
I've been going from forum to forum, but none of the examples I used seems to work for me.
It only seems to trigger it correctly if the page is refreshed while the div is visible, otherwise the count-up doesn't seem to activate.
Could anyone help please? Thank you all in advance.
HTML:
<section id="facts_section" style="background:#fff;">
<h1 class="main-title">fun facts about us</h1>
<div class="row">
<div class="columns large-3 medium-3">
<h2 class="count">8</h2>
</div>
<div class="columns large-3 medium-3">
<h2 class="count">15</h2>
</div>
<div class="columns large-3 medium-3">
<h2 class="count">80</h2><span class="percent">%</span>
</div>
<div class="columns large-3 medium-3">
<h2 class="count">5</h2>
</div>
</div>
</section>
jQuery:
//COUNTUP & MAP/HERO TOGGLE
$times = 0;
$(window).scroll(function() {
var hT = $('#facts_section').offset().top,
hH = $('#facts_section').outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop();
console.log((hT - wH), wS);
if (wS > (hT + hH - wH) && $times == 0) {
$('.count').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1000,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now));
}
});
});
$times++;
}
});

Related

jQuery animate number function. Want to turn it into Vanilla JavaScript

this function animates number inside an element to a defined number inside data-count value
How could I please do it in vanilla JavaScript
<div class="counter">
<div class="row no-gutters">
<div class="col-4">
<div
class="single-counter counter-color-1 d-flex align-items-center justify-content-center"
>
<div class="counter-items text-center">
<span id="count" data-count="175">0</span
><span>K</span>
<p>Downloads</p>
</div>
</div>
</div>
<div class="col-4">
<div
class="single-counter counter-color-2 d-flex align-items-center justify-content-center"
>
<div class="counter-items text-center">
<span id="count" data-count="73">0</span
><span>K</span>
<p>Active users</p>
</div>
</div>
</div>
<div class="col-4">
<div
class="single-counter counter-color-3 d-flex align-items-center justify-content-center"
>
<div class="counter-items text-center">
<span id="count" data-count="4.8">0</span>
<p>user rating</p>
</div>
</div>
</div>
</div>
$('#count').each(function() {
var counter = $(this),
countTo = counter.attr('data-count');
const countObj = { countNum: counter.text()}
$(countObj).animate({
countNum: countTo
},{
duration: 2000,
easing:'linear',
step: function() {
counter.text(Math.floor(this.countNum));
},
complete: function() {
counter.text(this.countNum);
}
});
});
I tried this
countUp(elem) {
var current = elem.innerHTML;
var interval = setInterval(increase, 70);
function increase() {
elem.innerHTML = current++;
if (current > elem.getAttribute("data-count")) {
clearInterval(interval);
}
}
}
var span = document.querySelectorAll("#count");
var i = 0;
for (i; i < span.length; i++) {
countUp(span[i]);
}
but it doesn't finish all elements animation at the same time; the elements which has the lower data-count value finishes earlier than the others that have higher data-count value
element is not a selector in your case. Another issue is all <span> have duplicate id i.e. count
I have modified these duplicate ids to count1, count2, count3. And selector $('span[id^=count') in script below means all <span> elements which have id starting with word count
$('span[id^=count').each(function() {
var counter = $(this),
countTo = counter.attr('data-count');
const countObj = { countNum: counter.text()}
$(countObj).animate({
countNum: countTo
},
{
duration: 2000,
easing:'linear',
step: function() {
counter.text(Math.floor(this.countNum));
},
complete: function() {
counter.text(this.countNum);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<div class="counter">
<div class="row no-gutters">
<div class="col-4">
<div class="single-counter counter-color-1 d-flex align-items-center justify-content-center">
<div class="counter-items text-center">
<span id="count1" data-count="175">0</span><span>K</span>
<p>Downloads</p>
</div>
</div>
</div>
<div class="col-4">
<div class="single-counter counter-color-2 d-flex align-items-center justify-content-center">
<div class="counter-items text-center">
<span id="count2" data-count="73">0</span><span>K</span>
<p>Active users</p>
</div>
</div>
</div>
<div class="col-4">
<div class="single-counter counter-color-3 d-flex align-items-center justify-content-center">
<div class="counter-items text-center">
<span id="count3" data-count="4.8">0</span>
<p>user rating</p>
</div>
</div>
</div>
</div>
EDIT : Below is pure vanilla js function for you
You just need some basic maths to decide time interval for all elements
function countUp(elem) {
var current = elem.innerHTML;
// assume 2000(milliseconds) is time delay to complete all animations
// determine time interval based on value of data-count
var timeIntervalBeforeIncrement = 2000/elem.getAttribute("data-count")
var interval = setInterval(increase, timeIntervalBeforeIncrement);
function increase() {
elem.innerHTML = current++;
if (current > elem.getAttribute("data-count")) {
clearInterval(interval);
}
}
}
var span = document.querySelectorAll("[id^='count']");
for (i = 0; i < span.length; i++) {
countUp(span[i]);
}

Animated counters on scroll not all loading

I have wrote the code below to get animated counters starting when visible on the window. It works well when the counters are all visible on the same row, but if only the first one is visible, this one will start the animation, but the others won't even if we scroll down. The first one is complete, but the others remain to zero.
/* SCROLL FUNCTIONS */
// Every time the window is scrolled...
$(window).scroll(function() {
// Check the location of each desired element
$('.counter').each(function(i) {
var bottom_of_object = $(this).offset().top + $(this).outerHeight();
var bottom_of_window = $(window).scrollTop() + $(window).height();
// If the object is completely visible in the window, fade it it
if (bottom_of_window > bottom_of_object) {
var $this = $(this);
$({
Counter: 0
}).animate({
Counter: $this.attr('data-to')
}, {
duration: 2000,
easing: 'swing',
step: function() {
$this.text(Math.ceil(this.Counter));
},
complete() {
$this.text(Math.ceil(this.Counter));
}
});
$(window).off("scroll");
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="row">
<div class="col">
<div class="row counters text-dark">
<div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
<div class="counter" data-to="30000">0</div>
<label>Happy Clients</label>
</div>
<div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
<div class="counter" data-to="15">0</div>
<label>Years in Business</label>
</div>
<div class="col-sm-6 col-lg-3 mb-4 mb-sm-0">
<div class="counter" data-to="352">0</div>
<label>Cups of Coffee</label>
</div>
<div class="col-sm-6 col-lg-3">
<div class="counter" data-to="178">0</div>
<label>High Score</label>
</div>
</div>
</div>
</div>
The problem is this line of code:
$(window).off("scroll");
Your off call unbinds all events, not just one. That means all scroll event bindings are lost after the first number animation executes.
To solve this, you need to bind and unbind each number's animation separately. A simple way to do this would be to have a different function for each number animation and bind/unbind them separately. A generic example:
var myScroll1 = function () {
$(window).off("scroll", myScroll1)
}
$(window).on("scroll", myScroll1)
Notice we are turning on and off just this specific function reference. You can have 4 of them and switch them on and off separately.
EDIT: Here's your script modified to work as explained:
var anim1 = function () { animateAndKill(1, $("#n1"), 3000, anim1); }
var anim2 = function () { animateAndKill(2, $("#n2"), 15, anim2); }
var anim3 = function () { animateAndKill(3, $("#n3"), 352, anim3); }
var anim4 = function () { animateAndKill(4, $("#n4"), 178, anim4); }
// Every time the window is scrolled...
function animateAndKill(id, $number, max, myFunction) {
var bottom_of_object = $number.offset().top + $number.outerHeight();
var bottom_of_window = $(window).scrollTop() + window.innerHeight;
// If the object is completely visible in the window, fade it it
if (bottom_of_window > bottom_of_object) {
$({ Counter: 0 }).animate({ Counter: max }, {
duration: 2000,
easing: 'swing',
step: function () {
var n = Math.ceil(this.Counter);
$number.html(n);
}
});
$(window).off("scroll", myFunction);
}
}
$(window).on("scroll", anim1);
$(window).on("scroll", anim2);
$(window).on("scroll", anim3);
$(window).on("scroll", anim4);
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="code.js"></script>
</head>
<body>
<div style="height: 1000px; background: #33FF44"></div>
<div class="row" style="z-index: 100; font-size: 100px;">
<div class="col">
<div class="row counters text-dark">
<div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
<div id="n1" class="counter" data-to="30000">0</div>
<label>Happy Clients</label>
</div>
<div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
<div id="n2" class="counter" data-to="15">0</div>
<label>Years in Business</label>
</div>
<div class="col-sm-6 col-lg-3 mb-4 mb-sm-0">
<div id="n3" class="counter" data-to="352">0</div>
<label>Cups of Coffee</label>
</div>
<div class="col-sm-6 col-lg-3">
<div id="n4" class="counter" data-to="178">0</div>
<label>High Score</label>
</div>
</div>
</div>
</div>
<div style="height: 3000px; background: #33FF44"></div>
</body>
</html>
https://jsfiddle.net/tyddlywink/pdvh4b3n/
Get rid of the $(window).off("scroll");bit. And keep track of who's already been counted or not.
<div class="row">
<div class="col">
<div class="row counters text-dark">
<div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
<div class="counter" data-to="30000" data-counted='false'>0</div>
<label>Happy Clients</label>
</div>
<div style="height: 750px">
</div>
<div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
<div class="counter" data-to="15" data-counted='false'>0</div>
<label>Years in Business</label>
</div>
<div style="height: 750px">
</div>
<div class="col-sm-6 col-lg-3 mb-4 mb-sm-0">
<div class="counter" data-to="352" data-counted='false'>0</div>
<label>Cups of Coffee</label>
</div>
<div style="height: 750px">
</div>
<div class="col-sm-6 col-lg-3">
<div class="counter" data-to="178" data-counted='false'>0</div>
<label>High Score</label>
</div>
</div>
</div>
</div>
Javascript:
// Every time the window is scrolled...
$(window).scroll(function() {
// Check the location of each desired element
$('.counter').each(function(i) {
var bottom_of_object = $(this).offset().top + $(this).outerHeight();
var bottom_of_window = $(window).scrollTop() + $(window).height();
var counted = $(this).data("counted");
// If the object is completely visible in the window, fade it it
if (!counted && bottom_of_window > bottom_of_object) {
$(this).data("counted", true);
var $this = $(this);
$({
Counter: 0
}).animate({
Counter: $this.attr('data-to')
}, {
duration: 2000,
easing: 'swing',
step: function() {
$this.text(Math.ceil(this.Counter));
},
complete() {
$this.text(Math.ceil(this.Counter));
}
});
}
});
});
/*
SCROLL FUNCTIONS
********************************/
// Every time the window is scrolled...
$(window).scroll(function () {
// Check the location of each desired element
$('.count').each(function (i) {
var bottom_of_object = $(this).offset().top + $(this).outerHeight();
var bottom_of_window = $(window).scrollTop() + $(window).height();
// If the object is completely visible in the window, fade it it
if (bottom_of_window > bottom_of_object) {
var $this = $(this);
$({
Counter: 0
}).animate({
Counter: $this.attr('data-to')
}, {
duration: 2000,
easing: 'swing',
step: function () {
$this.text(Math.ceil(this.Counter));
},
complete(){
$this.text(Math.ceil(this.Counter));
}
});
$(this).removeClass('count').addClass('counted');
}
});
});
<div class="row">
<div class="col">
<div class="row counters text-dark">
<div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
<div class="count" data-to="30000">0</div>
<label>Happy Clients</label>
</div>
<div class="col-sm-6 col-lg-3 mb-4 mb-lg-0">
<div class="count" data-to="15">0</div>
<label>Years in Business</label>
</div>
<div class="col-sm-6 col-lg-3 mb-4 mb-sm-0">
<div class="count" data-to="352">0</div>
<label>Cups of Coffee</label>
</div>
<div class="col-sm-6 col-lg-3">
<div class="count" data-to="178">0</div>
<label>High Score</label>
</div>
</div>
</div>
</div>
Listening to scroll event is not performance friendly, you should really consider using Intersection Observer for stuff like this.
First you have to create a new observer:
var options = {
rootMargin: '0px',
threshold: 1.0
}
var observer = new IntersectionObserver(callback, options);
Here we define that once your target Element is 100% visible in the viewport (threshold of 1) your callback Function is getting executed. Here you can define another percentage, 0.5 would mean that the function would be executed once your element is 50% visible.
Then you have to define which elements to watch, in your case this would be the counter elements:
var target = document.querySelector('.counter');
observer.observe(target);
Last you need to specify what should happen once the element is visible in your viewport by defining the callback function:
var callback = function(entries, observer) {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// here you animate the counter
});
};
In your specific case you probably won't run into performance problems but if you have more and more elements you will start to notice something. So it's better to know of this and to "do it right" if you come across this problem again.
If you need to support older browsers, use the official polyfill from w3c.
You can also remove the observer from any element if you don't need element where

Animate Counter on Scroll Past Part of Div?

I have created a counter section on my site which animates on page load, however I am trying to trigger the animation when the user gets to that section.
Currently I have this, however the animation only triggers when the div is beyond the nav, ie: the top of the screen including the nav. How would I change this so that the animation triggers as the div becomes visible?
I'm also having an issue that it shows to start as the full number without the commas, and then goes to 0 and animates, how can I make it show a 0 to begin with?
I'm pretty new to JS so would appreciate any explanation into this.
Heres what I have:
const convert = str => {
// Find the number
let regx = /(\d{1,3})(\d{3}(?:,|$))/;
// Set a variable
let currStr;
// Start loop
do {
// Replace current string, split it
currStr = (currStr || str.split(`.`)[0])
.replace(regx, `$1,$2`)
} while (currStr.match(regx)); // Loop
// Return our result from function
return (str.split(`.`)[1]) ?
currStr.concat(`.`, str.split(`.`)[1]) :
currStr;
};
$(window).scroll(startCounter);
function startCounter() {
if ($(window).scrollTop() > $('#counter').offset().top) {
$(window).off("scroll", startCounter);
$('.count').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 2000,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now));
$(this).text(convert($(this).text()))
}
});
});
}
}
.section-counter {
margin-top: 150vh;
margin-bottom: 150vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="section-counter" id="counter">
<div class="row">
<h2>Headline Data Figures</h2>
</div>
<div class="row">
<div class="col span-1-of-2">
<div class="row">
<div id="shiva"><span class="count">1688019</span>
<h3>Contributions</h3>
</div>
</div>
<div id="shiva"><span class="count">82150</span>
<h3>Items of Business</h3>
</div>
</div>
<div class="col span-1-of-2">
<div class="row">
<div id="shiva"><span class="count">10505</span>
<h3>Meetings</h3>
</div>
</div>
<div id="shiva"><span class="count">168260</span>
<h3>Written/Oral Questions</h3>
</div>
</div>
</div>
<div class="row arrow-dark arrow__7 animated pulse infinite">
<i class="ion-md-arrow-dropdown"></i>
</div>
</section>
If I understand correctly, the problem is that you're comparing the offset of the div to the top of the screen, when actually you'd want to find out where the bottom of the screen is, and compare that to the position of the div.
Regarding starting from 0, you can have the elements use a data attribute to determine the max instead of the text, that way you can have the elements read 0 until they start counting:
const convert = str => {
// Find the number
let regx = /(\d{1,3})(\d{3}(?:,|$))/;
// Set a variable
let currStr;
// Start loop
do {
// Replace current string, split it
currStr = (currStr || str.split(`.`)[0])
.replace(regx, `$1,$2`)
} while (currStr.match(regx)); // Loop
// Return our result from function
return (str.split(`.`)[1]) ?
currStr.concat(`.`, str.split(`.`)[1]) :
currStr;
};
$(window).scroll(startCounter);
function startCounter() {
var bottomOfScreen = $(window).scrollTop() + $(window).innerHeight();
if (bottomOfScreen > $('#counter').offset().top) {
$(window).off("scroll", startCounter);
$('.count').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).attr('data-max')
}, {
duration: 2000,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now));
$(this).text(convert($(this).text()))
}
});
});
}
}
.section-counter {
margin-top: 150vh;
margin-bottom: 150vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="section-counter" id="counter">
<div class="row">
<h2>Headline Data Figures</h2>
</div>
<div class="row">
<div class="col span-1-of-2">
<div class="row">
<div id="shiva"><span class="count" data-max="1688019">0</span>
<h3>Contributions</h3>
</div>
</div>
<div id="shiva"><span class="count" data-max="812150">0</span>
<h3>Items of Business</h3>
</div>
</div>
<div class="col span-1-of-2">
<div class="row">
<div id="shiva"><span class="count" data-max="10505">0</span>
<h3>Meetings</h3>
</div>
</div>
<div id="shiva"><span class="count" data-max="168260">0</span>
<h3>Written/Oral Questions</h3>
</div>
</div>
</div>
<div class="row arrow-dark arrow__7 animated pulse infinite">
<i class="ion-md-arrow-dropdown"></i>
</div>
</section>
I will give you the logic to kickstart an animation once the user gets to an element.
The relevant event trigger is the ONMOUSEOVER, so to incorporate it...
<div id="abc" onmouseover="AnimateIt();"></div>
Needless to mention that our element must have its animation PAUSED in its styling to begin with, like this...
animation-play-state:paused;
So the JavaScript logic to animate...
function AnimateIt(){ abc.style.animationPlayState='running'; }
That's it.

ScrollTop Javascript is not working in Firefox

I am having an issue with my nav bar not working correctly with the ScrollTop Javascript. It works in Chrome and Safari but not Firefox.
This is all the code I currently have on the site. I want the nav bar to follow the scroll once the nav bar is at the top of the page. Please view in Firefox as that is where I am having the issue!
Html
<body>
<div class="container">
<nav class="bottom" id="nav">
<div class="buttonWrapper">
<a href="#about">
<div class="navButton">About</div>
</a>
<a href="#designs">
<div class="navButton">Designs</div>
</a>
<a href="#contact">
<div class="navButton">Contact</div>
</a>
</div>
</nav>
<div class="largeLogo"></div>
</div>
<div class="container about" id="about">
<div class="sideBar about">
<div class="sidebarText"></div>
<p></p>
</div>
</div>
<div class="container designs" id="designs">
<div class="view view-ninth">
<img src="images/11.jpg" />
<div class="mask mask-1"></div>
<div class="mask mask-2"></div>
<div class="content">
<h2>Hover Style #9</h2>
<p>Some Text</p>
Read More
</div>
</div>
<div class="sideBar designs">
<div class="sidebarText"></div>
</div>
</div>
<div class="container contact" id="contact">
<div class="sideBar contact">
<div class="sidebarText"></div>
</div>
</div>
</body>
JavaScript
$(function() {
$('a[href*="#"]:not([href="#"])').click(function() {
if (location.pathname.replace(/^\//, '') === this.pathname.replace(/^\//, '') && location.hostname === this.hostname) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
if (target.length) {
$('html, body').animate({
scrollTop: target.offset().top
}, 1000);
return false;
}
}
});
});
window.addEventListener("scroll", navTop, false);
function navTop() {
var nav = document.getElementById("nav");
var about = document.getElementById("about").offsetTop - 1;
if (document.body.scrollTop > about) {
nav.className = "minimize";
} else {
nav.className = "bottom";
}
}
Fiddle
The issue is with document.body.scrollTop. Try using below code :
function navTop() {
var nav = document.getElementById("nav");
var about = document.getElementById("about").offsetTop - 1;
var scrollTop = $(document).scrollTop();
if (scrollTop > about) {
nav.className = "minimize";
} else {
nav.className = "bottom";
}
}
$(document).scrollTop(); / $(window).scrollTop(); works for both Firefox and Chrome

drag and drop working funny when using variable draggables and droppables

i have some containers that contain some divs like:
<div id="container1">
<div id="task1" onMouseOver="DragDrop("+1+");"> </div>
<div id="task2" onMouseOver="DragDrop("+2+");"> </div>
<div id="task3" onMouseOver="DragDrop("+3+");"> </div>
<div id="task4" onMouseOver="DragDrop("+4+");"> </div>
</div>
<div id="container2">
<div id="task5" onMouseOver="DragDrop("+5+");"> </div>
<div id="task6" onMouseOver="DragDrop("+6+");"> </div>
</div>
<div id="container3">
<div id="task7" onMouseOver="DragDrop("+7+");"> </div>
<div id="task8" onMouseOver="DragDrop("+8+");"> </div>
<div id="task9" onMouseOver="DragDrop("+9+");"> </div>
<div id="task10" onMouseOver="DragDrop("+10+");"> </div>
</div>
i'm trying to drag tasks and drop them in one of the container divs, then reposition the dropped task so that it doesn't affect the other divs nor fall outside one of them
and to do that i'm using the event onMouseOver to call the following function:
function DragDrop(id) {
$("#task" + id).draggable({ revert: 'invalid' });
for (var i = 0; i < nameList.length; i++) {
$("#" + nameList[i]).droppable({
drop: function (ev, ui) {
var pos = $("#task" + id).position();
if (pos.left <= 0) {
$("#task" + id).css("left", "5px");
}
else {
var day = parseInt(parseInt(pos.left) / 42);
var leftPos = (day * 42) + 5;
$("#task" + id).css("left", "" + leftPos + "px");
}
}
});
}
}
where:
nameList = [container1, container2, container3];
the drag is working fine, but the drop is not really, it's just a mess!
any help please??
when i hardcode the id and the container, then it works beautifully, but as soon as i use id in drop then it begins to work funny!
any suggestions???
thanks a million in advance
Lina
Consider coding it like this:
<div id="container1" class="container">
<div id="task1" class="task">1 </div>
<div id="task2" class="task">2 </div>
<div id="task3" class="task">3 </div>
<div id="task4" class="task">4 </div>
</div>
<div id="container2" class="container">
<div id="task5" class="task">5 </div>
<div id="task6" class="task">6 </div>
</div>
<div id="container3" class="container">
<div id="task7" class="task">7 </div>
<div id="task8" class="task">8 </div>
<div id="task9" class="task">9 </div>
<div id="task10" class="task">10 </div>
</div>
$(function(){
$(".task").draggable({ revert: 'invalid' });
$(".container").droppable({
drop: function (ev, ui) {
//process dropped item
}
});
})

Categories