jQuery animtion stops when user scrolls - javascript

I'm using multiple number elements on my site which are counting up after hitting the visible area of the viewport.
That part works until the user manually scrolls the page. If the user scrolls up or down, the animation stops for a second and repeats when the user don't scroll anymore. It looks very buggy.
If I try to replicate the problem in a fiddle, the same code always works without the "stuttering"?
jQuery(function($) {
$(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i, el) {
function visPx() {
var H = $(this).height(),
r = el.getBoundingClientRect(),
t = r.top,
b = r.bottom;
return cb.call(el, Math.max(0, t > 0 ? H - t : (b < H ? b : H)));
}
visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
$(".fig-number").inViewport(function(px) {
// if px>0 (entered V.port) and
// if prop initNumAnim flag is not yet set = Animate numbers
if (px > 0 && !this.initNumAnim) {
this.initNumAnim = true; // Set flag to true to prevent re-running the same animation
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 10000,
step: function(now) {
$(this).text(Math.ceil(now));
}
});
}
});
});
html,
body {
height: 100%;
}
.spacer {
height: 100%;
width: 100%;
display: block;
background: red;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="spacer">
scroll down
</div>
<div class="number-box">
<h1 class="fig-number">1000</h1>
<h1 class="fig-number">1500</h1>
</div>
<div class="spacer">
scroll down
</div>
<div class="number-box">
<h1 class="fig-number">2000</h1>
<h1 class="fig-number">2500</h1>
</div>
<div class="spacer">
scroll down
</div>
<div class="number-box">
<h1 class="fig-number">3000</h1>
<h1 class="fig-number">3500</h1>
</div>
The working fiddle (same code): https://jsfiddle.net/JSCray/r7g0vn93/3/

Related

How to Delay a Javascript Function Until it is in the middle of web page?

Hello I have a number animation on my web page and I dont want the animation to start until it is in the middle of the web page. I tried to google onscroll and other options but I could not get this to work properly.
I prefer for the animation not to start until the visitor has scrolled down to 472px. As of right now as soon as the web page loads the number animation starts automatically. Any help I would really appreciate it.
// 472 px - Starts Yellow Counter Section
const counters = document.querySelectorAll('.counter');
const speed = 200; // The lower the slower
counters.forEach(counter => {
const updateCount = () => {
const target = +counter.getAttribute('data-target');
const count = +counter.innerText;
// Lower inc to slow and higher to slow
const inc = target / speed;
// console.log(inc);
// console.log(count);
// Check if target is reached
if (count < target) {
// Add inc to count and output in counter
counter.innerText = count + inc;
// Call function every ms
setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
};
updateCount();
});
.bg-yellow-white {
background: #f7c51e;
color: white;
}
.container {
max-width: 1404px;
margin: auto;
padding: 0 2rem;
overflow: hidden;
}
.l-heading {
font-weight: bold;
font-size: 4rem;
margin-bottom: 0.75rem;
line-height: 1.1;
}
/* Padding */
.py-1 {
padding: 1.5rem 0;
}
.py-2 {
padding: 2rem 0;
}
.py-3 {
padding: 3rem 0;
}
/* All Around Padding */
.p-1 {
padding: 1.5rem;
}
.p-2 {
padding: 2rem;
}
.p-3 {
padding: 3rem;
}
/* ======================== Red Block ======================== */
.red-block {
height: 472px;
width: 100%;
background-color: red;
}
/* ======================== PROJECS COMPLETED ======================== */
#projects-completed .container .items {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
#projects-completed .container .items .item .circle {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
<div class="red-block">
<p>red block</p>
</div>
<section id="projects-completed" class="counters bg-yellow-white">
<div class="container">
<div class="items">
<div class="item text-center p-3">
<div class="circle">
<div class="counter l-heading" data-target="1750">500</div>
</div>
<h2 class="py-2">Projects Completed</h2>
</div>
<div class="item text-center p-3">
<div class="circle py-2">
<div class="l-heading counter" data-target="5">500</div>
</div>
<h2 class="py-2">Staff Members</h2>
</div>
<!-- <div class="item text-center p-3">
<div class="circle">
<h3 class="l-heading ">1750</h3>
</div>
<h2 class="py-2">Projects Completed</h2>
</div>
<div class="item text-center p-3">
<div class="circle py-2">
<h3 class="l-heading">5</h3>
</div>
<h2 class="py-2">Staff Members</h2>
</div> -->
</div>
</div>
</section>
wesbos has great video on this https://www.youtube.com/watch?v=uzRsENVD3W8&list=PLu8EoSxDXHP6CGK4YVJhL_VWetA865GOH&index=14&t=0s
Basically what you need to do is listen for scroll and check where user currently is compared to desired place in px
you can check code here and adjust it to your needs https://github.com/wesbos/JavaScript30/blob/master/13%20-%20Slide%20in%20on%20Scroll/index-FINISHED.html
Try getBoundingClientRect(). document.querySelector( 'some element' ).getBoundingClientRect() will give you the properties of the specific element
for Example if you want to start an animation when an element is visible to user on his screen ( in the visible viewport ), you can use this to call the function and start the animation
let calledStatus = 0; // some flag variable to remember if function is called
window.onscroll = function(){
element = document.querySelector( '.some element' );
clientRect = element.getBoundingClientRect();
if( clientRect.top < window.innerHeight && clientRect.top > ( clientRect.height * -1) && calledStatus == 0){
//call your function or do other stuff
console.log('called' )
calledStatus = 1;
}
}
By using jquery , first add this reference script above your js code or referenece script
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></scrip>
....
</head>
if you want the code to launch specifically after 472 px:
js
$(document).ready(function () {
Let initialScroll = true;
//you can decrease or increase 472 depending on where exactly
//you want your function to be called
$(document).scroll(function () {
if (($(document).scrollTop() > 472)&& initialScroll) {
//call your function here
console.log( "reached 472")
InitialScroll=false;
}
});
});
if you want your function to start after reaching the middle
of the document
you place a div where the middle of the html code is :
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
...
<div id="middle"></div>
...
</body>
</html>
js
$(document).ready(function () {
Let initialScroll=true
$(document).scroll(function () {
if (($(document).scrollTop() >=$('#middle').position().top)&&initialScroll) {
//call your function here
console.log( "reached middle")
InitialScroll=false;
}
});
});
There is a native javascript API for "listetning" where the user currently is on the page called Intersection Observer. Basically you set a callback which should execute once the desired content scrolls into view.
It's used for all those fancy page animations where cards appear once you start scrolling to the bottom of the page since it's far more efficient than listening on the scroll event.
Kevin Powell did a great video about this topic.
Hope it helps!
Here's a code copy pasted, but it should give you a clue on how it should work:
document.addEventListener("DOMContentLoaded", function() {
let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
let active = false;
const lazyLoad = function() {
if (active === false) {
active = true;
setTimeout(function() {
lazyImages.forEach(function(lazyImage) {
if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== "none") {
lazyImage.src = lazyImage.dataset.src;
lazyImage.srcset = lazyImage.dataset.srcset;
lazyImage.classList.remove("lazy");
lazyImages = lazyImages.filter(function(image) {
return image !== lazyImage;
});
if (lazyImages.length === 0) {
document.removeEventListener("scroll", lazyLoad);
window.removeEventListener("resize", lazyLoad);
window.removeEventListener("orientationchange", lazyLoad);
}
}
});
active = false;
}, 200);
}
};
document.addEventListener("scroll", lazyLoad);
window.addEventListener("resize", lazyLoad);
window.addEventListener("orientationchange", lazyLoad);
});

jQuery bind / unbind not working

I'm trying to create a simple slider. Here is a example but slider next and prev button not working properly.
// next
var next = $('.next').click(function() {
var storepos = $(".storepos").val();
$('.prev').bind('click');
$('.storepos').val($('.storepos').val() / 1 + 110);
$('.container').animate({
scrollLeft: $('.storepos').val()
}, 200);
});
//prev
$('.prev').click(function() {
var storepos = $(".storepos").val();
$('.next').bind('click');
$('.storepos').val($('.storepos').val() / 1 - 110);
$('.container').animate({
scrollLeft: $('.storepos').val()
}, 200);
});
//after scrollend right event
$('.container').bind('scroll', function() {
if ($('.container').scrollLeft() + $(this).innerWidth() >= $(this)[0].scrollWidth) {
$('.next').unbind('click');
}
});
//after scrollend left event
$('.container').bind('scroll', function() {
if ($('.container').scrollLeft() < 1) {
$('.prev').unbind('click');
}
});
.container {
overflow: hidden !important
}
.container::-webkit-scrollbar {
width: 0;
height: 0
}
.content {
width: 1600px
}
.items {
background: black;
color: white;
margin-left: 10px;
width: 100px;
height: 100px;
float: left;
text-align: center
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="content">
<div class="items">1</div>
<div class="items">2</div>
<div class="items">3</div>
<div class="items">4</div>
<div class="items">5</div>
<div class="items">6</div>
<div class="items">7</div>
<div class="items">8</div>
<div class="items">9</div>
<div class="items">10</div>
</div>
</div>
Prev / Next
<input class="storeposx" value="" />
<input class="storepos" value="" />
fiddle
I see two errors. First, the previous button is active from the begging, enabling scrolling to negative values. Second, you do unbind the events when reaching the end both sides, but you're not bind them back after that.
I used two variables where I keep the buttons status. When I reach the start or end position I don't unbind them, instead I just return false on click.
// next
var next = $('.next').click(function() {
if (!nextIsActive || $('.container').is(':animated')) return false;
var storepos = $(".storepos").val();
$('.prev').bind('click');
$('.storepos').val($('.storepos').val() / 1 + 110);
$('.container').animate({
scrollLeft: $('.storepos').val()
}, 200);
});
//prev
$('.prev').click(function() {
if (!prevIsActive || $('.container').is(':animated')) return false;
var storepos = $(".storepos").val();
$('.next').bind('click');
$('.storepos').val($('.storepos').val() / 1 - 110);
$('.container').animate({
scrollLeft: $('.storepos').val()
}, 200);
});
var nextIsActive=true;
var prevIsActive=false;
//after scrollend right event
$('.container').bind('scroll', function() {
if ($('.container').scrollLeft() + $(this).innerWidth() >= $(this)[0].scrollWidth) {
nextIsActive=false;
}else{
nextIsActive=true;
}
});
//after scrollend left event
$('.container').bind('scroll', function() {
if ($('.container').scrollLeft() < 1) {
prevIsActive=false;
}else{
prevIsActive=true;
}
});
.container{overflow:hidden !important}
.container::-webkit-scrollbar {
width:0;
height:0
}
.content {width:1600px}
.items { background:black;
color:white;
margin-left:10px;
width:100px;
height:100px;
float:left;
text-align:center
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="content">
<div class="items">1</div>
<div class="items">2</div>
<div class="items">3</div>
<div class="items">4</div>
<div class="items">5</div>
<div class="items">6</div>
<div class="items">7</div>
<div class="items">8</div>
<div class="items">9</div>
<div class="items">10</div>
</div>
</div>
Prev / Next
<input class="storeposx" value="" />
<input class="storepos" value="" />

Crossbrowser solution for disable/enable scroll

Long time ago I found somewhere (on stackoverflow I think) code to scroll one window height per scroll. I tried many libraries but that script works just like I want. But it's jumpy when scrolling during animation. So I disable scroll during animation but it works only in Firefox.
I made a fiddle: https://jsfiddle.net/msoys5gc/1/
if( $(window).scrollTop() < $(window).height() * 6 ) {
window.onwheel = function(){ return false; };
}
$('html,body').stop().animate({
scrollTop: divs.eq(div).offset().top
}, 600, function() {
window.onwheel = function(){ return true; };
});
I don`t understand this:
if( $(window).scrollTop() < $(window).height() * 6 )
this will be true until you pass the six slide...
UPDATED: If you want to stop triggering the animation while it is running you can just delete the stop(). Then if you don't want to enqueue animations(and then get strange behaviors), you can just call clearQueue() when the animation has finished.
var divs = $('.section');
var dir = 'up'; // wheel scroll direction
var div = 0; // current div
$(document.body).on('DOMMouseScroll mousewheel', function (e) {
if (e.originalEvent.detail > 0 || e.originalEvent.wheelDelta < 0) {
dir = 'down';
}
else {
dir = 'up';
}
// find currently visible div :
div = -1;
divs.each(function(i){
if (div<0 && ($(this).offset().top >= $(window).scrollTop())) {
div = i;
}
});
if (dir == 'up' && div > 0) {
div--;
}
if (dir == 'down' && div < divs.length) {
div++;
}
$('html,body').animate({
scrollTop: divs.eq(div).offset().top
}, 1600, function() {
$('html,body').clearQueue();
});
return false;
});
$(window).resize(function () {
$('html,body').scrollTop(divs.eq(div).offset().top);
});
.section {
height: 100vh;
}
body{
margin: 0px !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="section" style="background: yellow;"></div>
<div class="section" style="background: green;"></div>
<div class="section" style="background: blue;"></div>
<div class="section" style="background: red;"></div>
<div class="section" style="background: violet;"></div>
<div class="section" style="background: orange;"></div>
<div class="section" style="background: black;"></div>

How to change the background color of a screen area on hover

I am making a website for a school project, wherein I have left and right drawers. The drawers are hidden and show only when onclick pageX < 100 (left drawer) and pageX > 1200 (right drawer). As the drawers show only onclick(), I want that area to get highlighted in some way (preferably color-change) so that the user knows there is something there. How do I do this?
HTML:
<div id="pgcontainer">
<header>
<div id="navbar">
<div id="rightdrawer">
<ul>
<li>Register</li>
<li>Archives</li>
<li>Contact Us</li>
<li>Our sponsors</li>
</ul>
</div>
</div>
</header>
</div>
JavaScript:
$(function() {
var menuwidth = 240; // pixel value for sliding menu width
var menuspeed = 400; // milliseconds for sliding menu animation time
var $bdy = $('body');
var $container = $('#pgcontainer');
var $hamburger = $('#hamburgermenu');
var $rightmenu = $('#rightdrawer');
var negwidth = "-"+menuwidth+"px";
var poswidth = menuwidth+"px";
$('#pgcontainer').on('click',function(e) {
if(e.pageX < 130) {
if($bdy.hasClass('openmenu')) {
jsAnimateMenuLeft('close');
} else {
jsAnimateMenuLeft('open');
}
}
});
$('.overlay').on('click', function(e) {
if($bdy.hasClass('openmenu')) {
jsAnimateMenuLeft('close');
}
else if($bdy.hasClass('openmenur')) {
jsAnimateMenuRight('close');
}
});
$('a[href$="#"]').on('click', function(e) {
e.preventDefault();
});
function jsAnimateMenuLeft(tog) {
if(tog == 'open') {
$bdy.addClass('openmenu');
$container.animate({marginRight: negwidth, marginLeft: poswidth}, menuspeed);
$hamburger.animate({width: poswidth}, menuspeed);
$('.overlay').animate({left: poswidth}, menuspeed);
}
if(tog == 'close') {
$bdy.removeClass('openmenu');
$container.animate({marginRight: "0", marginLeft: "0"}, menuspeed);
$hamburger.animate({width: "0"}, menuspeed);
$('.overlay').animate({left: "0"}, menuspeed);
}
}
});
I think that the optimal solution here is to add two more elements, position them fixed and add some nice hover styles.
Note that since .leftdrawer-hover and .rightdrawer-hover are children on #pgcontainer clicking on them would act exactly as you need, because click events will bubble to #pgcontainer where you will detect them and show/hide corresponding drawer.
#pgcontainer .leftdrawer-hover,
#pgcontainer .rightdrawer-hover {
content: '';
position: fixed;
top: 0;
bottom: 0;
width: 130px;
display: block;
background: rgba(200, 200, 200, .4);
}
#pgcontainer .rightdrawer-hover {
right: 0;
}
#pgcontainer .leftdrawer-hover:hover,
#pgcontainer .rightdrawer-hover:hover {
background: rgba(0, 0, 0, .7);
cursor: pointer;
}
<div id="pgcontainer">
<div class="leftdrawer-hover"></div>
<div class="rightdrawer-hover"></div>
<!-- other tags -->
</div>

Function acting funny on resize

I have this banner ticker which works as you first load the page, but it gets messed up as you resize the second time and my assumption is that I am not calling the function in the right way on the resize event, any advice so I can understand what is going on?
jsfiddle
html
<div class="">
<div class="topBanners" style="float: left; width: 200px; ">
<p>First one</p>
</div>
<div class="topBanners" style="float: left; width: 200px; ">
<p>Second</p>
</div>
<div class="topBanners" style="float: left; width: 200px; ">
<p>Third</p>
</div>
</div>
js
$(document).ready(function() {
currentTopBanner = 0;
var topBanners = $('.topBanners');
console.log(topBanners.length);
function rotateTopBanners() {
if ($(window).width() < 768) {
topBanners.hide();
$(topBanners[currentTopBanner]).fadeIn('slow').delay(100).fadeOut('slow');
$(topBanners[currentTopBanner]).queue(function () {
currentTopBanner = currentTopBanner < topBanners.length - 1 ? currentTopBanner + 1 : 0;
rotateTopBanners();
$(this).dequeue();
});
} else {
topBanners.show();
}
}
rotateTopBanners();
$(window).resize(function () {
rotateTopBanners();
});
});

Categories