Fire a function when passing a section - jquery - javascript

I am struggling to get a last bit of my code to work. Problem is I need to fire a function when a user scrolls past a div
So the structure of my html looks roughly like this
<div class="panel-grid-cell">
<div id="pgc-1">...</div>
<div id="pgc-2">...</div>
...
<div id="pgc-n">...</div>
</div>
So the idea is when users passes a div with id="pgc-1" fire a function and so on for the rest of them.
Each of these divs has a li a item with the href attached, so I my function would look something like this
ga('send','event','scroll',city,$(this).text())
where this would be corresponding li a with the href
At the moments what I have is this.
A function to loop through div's and store their id's in array. And a build a li a menu.
var li="";
var linames = ["Intro","1925","1935","1945","1955","1965","1975","1985","1995","2005","Now"];
var i = 0;
function getSectionIDs()
{
$("div.panel-grid-cell").children().each(function() {
if(linames[i] !== undefined)
{
li += "<li><a href='#"+$(this).attr('id')+"'>"+linames[i]+"</a></li>";
}
i++;
});
$("ul.timeline-items").append(li);
}
Now I also have a function that checks how far did I scroll and change a class for each li item.
function onScroll(event){
var scrollPos = $(document).scrollTop();
$('ul.timeline-items li a').each(function () {
var currLink = $(this);
var refElement = $(currLink.attr("href"));
if (refElement.position().top -75 <= scrollPos && refElement.position().top+75 + refElement.height()-75 > scrollPos) {
$('ul.timeline-items li a').removeClass("active");
currLink.addClass("active");
}
else{
currLink.removeClass("active");
}
});
}
$(document).on("scroll", onScroll);
This Code does not what I want, because it constantly check If I have passed the div or not, but I need to fire my function only once.
Any help?

Found a solution myself the other day,
Inside a scrolling function just put a condition when $(document).scrollTop() equals to refElement.offset(), fire a function, and it works perfectly.
Meaning that check how far user has scrolled and if this value is equals to the offset of the div, fire a function.

Related

How can I select every single element with JS or jQuery?

I want to select every single element in a document and make them color red when I scroll to them.
$(document).ready(function() {
$(document).on("scroll", animationDivs);
function animationDivs(event) {
var scrollPos = $(document).scrollTop();
var divs = $("*");
$(divs).each(function() {
var currLink = $(this);
if (currLink.position().top <= scrollPos && currLink.position().top + currLink.height() > scrollPos) {
currLink.style.color = "red";
}
});
};
});
I used this codes but didn't work.
using JS:
document.querySelectorAll('*')
.forEach(el => el.style.color = 'red')
Try it in the console of your browser to see how it works and here's a brief overview of DOM selection with JS vs jQuery.
This is a similar question with a variety of solutions.
First add some common css class on each divs and add following jquery.
$('.class-name').each(function() {
var currLink = $(this);
if (currLink.position().top <= scrollPos && currLink.position().top + currLink.height() > scrollPos) {
currLink.style.color = "red";
}
});
using jq, you could simple get all element withing the html by "*"
var items = $("*").css({background : "red"})
console.log(items)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div></div>
<p></p>
currLink is a jQuery object in your code. So use a jQuery method on it.
That would be the .css() method in your case.
And I suggest you use an else part to your condition so the elements does not turn red after the first single wheel spin... Since <body> is also collected in $("*").
$(document).ready(function() {
$(document).on("scroll", animationDivs);
function animationDivs(event) {
var scrollPos = $(document).scrollTop();
var divs = $("*");
$(divs).each(function() {
var currLink = $(this);
if (currLink.position().top <= scrollPos && currLink.position().top + currLink.height() > scrollPos) {
currLink.css({"color":"red"});
}else{
currLink.css({"color":"initial"});
}
});
};
});
.spacer{
height:500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<br>
<span>Scroll me.</span>
<div class="spacer"></div>
<div>div
<p>paragraph</p>
<a>anchor</a>
<span>span</span>
</div>
<div class="spacer"></div>
By the way... Using an .each() loop on the $("*") collection on every scroll event is the worst jQuery usage I suppose I will ever see. I can assure you that you'll scratch your head pretty soon with a real web page with real content.
Every elements, including <br> and <script> and <link> etc. are collected using $("*") that way... And are compared in the loop. You should only use it when absolutely necessary and within at least a container to lower the amount of collected elements.... Like $(".some-class *").

jQuery highlight menu item when scrolled over section

I am trying to highlight the menu item when you scroll down to the section.
The highlighting works but for some reason I can't remove the highlighting when scrolled to an other section
This is what my menu looks like:
<div id="navbar">
<ul class="nav navbar-nav">
<li class="active"><a data-id="home" href="#home">Home</a></li>
<li class="navbar-right"><a data-id="cont" href="#contact">Contact</a></li>
<li class="navbar-right"><a data-id="exp" href="#exp">Expertise</a></li>
<li class="navbar-right"><a data-id="wie2" href="#wie2">Wie</a></li>
</ul>
</div>
In the html for every section where I use the id anchor I added class="section"
This is my jQuery:
jQuery(window).scroll(function () {
var position = jQuery(this).scrollTop();
jQuery('.section').each(function() {
var target = jQuery(this).offset().top;
var id = jQuery(this).attr('id');
if (position >= target) {
jQuery('#navbar>ul>li>a').removeClass('clicked');
jQuery('#navbar ul li a[data-id=' + id + ']').addClass('clicked');
}
});
});
Anyone has any idea why the class get deleted everytime? becuase when I comment out jQuery('#navbar>ul>li>a').removeClass('clicked'); it works great. The classes are being added correctly. But removing them doesn't work :(
Havent tested this, but i think this should work
jQuery(window).scroll(function () {
var position = jQuery(this).scrollTop();
jQuery('.section').each(function() {
var target = jQuery(this).offset().top;
var id = jQuery(this).attr('id');
jQuery('#navbar ul li a[data-id=' + id + ']').removeClass('clicked');
if (position >= target) {
jQuery('#navbar ul li a[data-id=' + id + ']').addClass('clicked');
}
});
});
Hard for me to tell exactly what the issue is without being able to dig through the actual code, but you could try updating this line:
jQuery('#navbar>ul>li>a').removeClass('clicked');
to:
jQuery('#navbar').find('clicked').removeClass('clicked');
That way you are for sure going to be removing the class "clicked" from only link that already has the class "clicked" before reassignment.
I would also recommend checking out bootstrap's scrollspy feature. It sounds like it does what you are trying to achieve. You could either try implementing it instead, or digging into their code and see how they are approaching it and learn something new.
http://getbootstrap.com/javascript/#scrollspy
Hope this helps!
I think because: #navbar>ul>li>a is not the same as #navbar ul li a
The first is trying to find direct <ul> child to #navbar and the second is asking to find a <ul> child (at any level) under #navbar and the same goes for the rest of the selector.
Have a look here
The child combinator (E > F) can be thought of as a more specific form
of the descendant combinator (E F) in that it selects only first-level
descendants.
I think the problem is that position >= target only adds the active class if the user has scrolled below the top of the section, so this will add the class even if the user has scrolled beyond the entire section.
Change
if (position >= target)
to
if (position >= target && position < target + $(this).height())
appears to solve the problem.
hi with small change it worked without any issues
jQuery(window).scroll(function () {
var position = jQuery(this).scrollTop();
var classSet = 0;
jQuery('.section').each(function() {
var target = jQuery(this).offset().top;
var id = jQuery(this).attr('id');
if (classSet == 0)
jQuery('#navbar ul li a[data-id=' + id + ']').removeClass('clicked');
if (position >= (target - 200) && position < target + $(this).height()) {
jQuery('#navbar ul li a[data-id=' + id + ']').addClass('clicked');
classSet = 1;
}
});
});

Reveal hidden items when using up/down arrow on Ul reaches top/bottom of an overflow auto item

I have a ul that I am scrolling up and down on. The ul has overflow:auto on it, so after a certain point the selected item is no longer visible. You can see an example of the problem at this link: http://jsfiddle.net/NjC58/34/
Currently I have the following code, borrowed largely from this post:
var displayBoxIndex = -1;
var Navigate = function (diff) {
displayBoxIndex += diff;
var oBoxCollection = $("#leftDrop li");
if (displayBoxIndex >= oBoxCollection.length) {
displayBoxIndex = 0;
}
if (displayBoxIndex < 0) {
displayBoxIndex = oBoxCollection.length - 1;
}
var cssClass = "selectedInitialNav";
oBoxCollection.removeClass(cssClass).eq(displayBoxIndex).addClass(cssClass);
}
$(document).keydown(function(e){ // 38-up, 40-down
if (e.keyCode == 40) {
Navigate(1);
return false;
}
if (e.keyCode == 38) {
Navigate(-1);
return false;
}
});
Thus, I am looking for a solution that will scroll the overflow to the top if you are pressing down arrow and get to the bottom of the visible ul, and if you are pressing up arrow and get to the top of the visible ul will scroll overflow down so that the selected li is at the bottom of the visible ul.
I have been looking for solutions using jQuery's scroll function, using this post, and this post (which mentions scrollTop), but haven't actually found anything close to a solution yet.
One method you could try is by creating a function which handles the scrolling. It sets the scrollTop position of each item.
ScrollUl function
function scrollUl(){
if(window.event.keyCode == 40){
document.getElementById("parent").scrollTop = scrolLength;
scrolLength = scrolLength + 19;
}
else if(window.event.keyCode == 38){
scrolLength = scrolLength - 19;
document.getElementById("parent").scrollTop = scrolLength;
}
}
Then you should call the function within your Navigate function and you should ad an id to your ul like: <ul id="parent">
JSFiddle Demo
This code still needs some improvements but it might help you getting your way.

Highlight Menu Item when Scrolling Down to Section

I know this question have been asked a million times on this forum, but none of the articles helped me reach a solution.
I made a little piece of jquery code that highlights the hash-link when you scroll down to the section with the same id as in the hash-link.
$(window).scroll(function() {
var position = $(this).scrollTop();
$('.section').each(function() {
var target = $(this).offset().top;
var id = $(this).attr('id');
if (position >= target) {
$('#navigation > ul > li > a').attr('href', id).addClass('active');
}
});
});
The problem now is that it highlights all of the hash-links instead of just the one that the section has a relation to. Can anyone point out the mistake, or is it something that I forgot?
EDIT:
I have modified my answer to talk a little about performance and some particular cases.
If you are here just looking for code, there is a commented snippet at the bottom.
Original answer
Instead of adding the .active class to all the links, you should identify the one which attribute href is the same as the section's id.
Then you can add the .active class to that link and remove it from the rest.
if (position >= target) {
$('#navigation > ul > li > a').removeClass('active');
$('#navigation > ul > li > a[href=#' + id + ']').addClass('active');
}
With the above modification your code will correctly highlight the corresponding link. Hope it helps!
Improving performance
Even when this code will do its job, is far from being optimal. Anyway, remember:
We should forget about small efficiencies, say about 97% of the time:
premature optimization is the root of all evil. Yet we should not pass
up our opportunities in that critical 3%. (Donald Knuth)
So if, event testing in a slow device, you experience no performance issues, the best you can do is to stop reading and to think about the next amazing feature for your project!
There are, basically, three steps to improve the performance:
Make as much previous work as possible:
In order to avoid searching the DOM once and again (each time the event is triggered), you can cache your jQuery objects beforehand (e.g. on document.ready):
var $navigationLinks = $('#navigation > ul > li > a');
var $sections = $(".section");
Then, you can map each section to the corresponding navigation link:
var sectionIdTonavigationLink = {};
$sections.each( function(){
sectionIdTonavigationLink[ $(this).attr('id') ] = $('#navigation > ul > li > a[href=\\#' + $(this).attr('id') + ']');
});
Note the two backslashes in the anchor selector: the hash '#' has a special meaning in CSS so it must be escaped (thanks #Johnnie).
Also, you could cache the position of each section (Bootstrap's Scrollspy does it). But, if you do it, you need to remember to update them every time they change (the user resizes the window, new content is added via ajax, a subsection is expanded, etc).
Optimize the event handler:
Imagine that the user is scrolling inside one section: the active navigation link doesn't need to change. But if you look at the code above you will see that actually it changes several times. Before the correct link get highlighted, all the previous links will do it as well (because their corresponding sections also validate the condition position >= target).
One solution is to iterate the sections for the bottom to the top, the first one whose .offset().top is equal or smaller than $(window).scrollTop is the correct one. And yes, you can rely on jQuery returning the objects in the order of the DOM (since version 1.3.2). To iterate from bottom to top just select them in inverse order:
var $sections = $( $(".section").get().reverse() );
$sections.each( ... );
The double $() is necessary because get() returns DOM elements, not jQuery objects.
Once you have found the correct section, you should return false to exit the loop and avoid to check further sections.
Finally, you shouldn't do anything if the correct navigation link is already highlighted, so check it out:
if ( !$navigationLink.hasClass( 'active' ) ) {
$navigationLinks.removeClass('active');
$navigationLink.addClass('active');
}
Trigger the event as less as possible:
The most definitive way to prevent high-rated events (scroll, resize...) from making your site slow or unresponsive is to control how often the event handler is called: sure you don't need to check which link needs to be highlighted 100 times per second! If, besides the link highlighting, you add some fancy parallax effect you can ran fast intro troubles.
At this point, sure you want to read about throttle, debounce and requestAnimationFrame. This article is a nice lecture and give you a very good overview about three of them. For our case, throttling fits best our needs.
Basically, throttling enforces a minimum time interval between two function executions.
I have implemented a throttle function in the snippet. From there you can get more sophisticated, or even better, use a library like underscore.js or lodash (if you don't need the whole library you can always extract from there the throttle function).
Note: if you look around, you will find more simple throttle functions. Beware of them because they can miss the last event trigger (and that is the most important one!).
Particular cases:
I will not include these cases in the snippet, to not complicate it any further.
In the snippet below, the links will get highlighted when the section reaches the very top of the page. If you want them highlighted before, you can add a small offset in this way:
if (position + offset >= target) {
This is particullary useful when you have a top navigation bar.
And if your last section is too small to reach the top of the page, you can hightlight its corresponding link when the scrollbar is in its bottom-most position:
if ( $(window).scrollTop() >= $(document).height() - $(window).height() ) {
// highlight the last link
There are some browser support issues thought. You can read more about it here and here.
Snippet and test
Finally, here you have a commented snippet. Please note that I have changed the name of some variables to make them more descriptive.
// cache the navigation links
var $navigationLinks = $('#navigation > ul > li > a');
// cache (in reversed order) the sections
var $sections = $($(".section").get().reverse());
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
$sections.each(function() {
var id = $(this).attr('id');
sectionIdTonavigationLink[id] = $('#navigation > ul > li > a[href=\\#' + id + ']');
});
// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
var lastCall, timeoutId;
return function () {
var now = new Date().getTime();
if (lastCall && now < (lastCall + interval) ) {
// if we are inside the interval we wait
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
lastCall = now;
fn.call();
}, interval - (now - lastCall) );
} else {
// otherwise, we directly call the function
lastCall = now;
fn.call();
}
};
}
function highlightNavigation() {
// get the current vertical position of the scroll bar
var scrollPosition = $(window).scrollTop();
// iterate the sections
$sections.each(function() {
var currentSection = $(this);
// get the position of the section
var sectionTop = currentSection.offset().top;
// if the user has scrolled over the top of the section
if (scrollPosition >= sectionTop) {
// get the section id
var id = currentSection.attr('id');
// get the corresponding navigation link
var $navigationLink = sectionIdTonavigationLink[id];
// if the link is not active
if (!$navigationLink.hasClass('active')) {
// remove .active class from all the links
$navigationLinks.removeClass('active');
// add .active class to the current link
$navigationLink.addClass('active');
}
// we have found our section, so we return false to exit the each loop
return false;
}
});
}
$(window).scroll( throttle(highlightNavigation,100) );
// if you don't want to throttle the function use this instead:
// $(window).scroll( highlightNavigation );
#navigation {
position: fixed;
}
#sections {
position: absolute;
left: 150px;
}
.section {
height: 200px;
margin: 10px;
padding: 10px;
border: 1px dashed black;
}
#section5 {
height: 1000px;
}
.active {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navigation">
<ul>
<li>Section 1</li>
<li>Section 2</li>
<li>Section 3</li>
<li>Section 4</li>
<li>Section 5</li>
</ul>
</div>
<div id="sections">
<div id="section1" class="section">
I'm section 1
</div>
<div id="section2" class="section">
I'm section 2
</div>
<div id="section3" class="section">
I'm section 3
</div>
<div id="section4" class="section">
I'm section 4
</div>
<div id="section5" class="section">
I'm section 5
</div>
</div>
And in case you are interested, this fiddle tests the different improvements we have talked about.
Happy coding!
I've taken David's excellent code and removed all jQuery dependencies from it, in case anyone's interested:
// cache the navigation links
var $navigationLinks = document.querySelectorAll('nav > ul > li > a');
// cache (in reversed order) the sections
var $sections = document.getElementsByTagName('section');
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
for (var i = $sections.length-1; i >= 0; i--) {
var id = $sections[i].id;
sectionIdTonavigationLink[id] = document.querySelectorAll('nav > ul > li > a[href=\\#' + id + ']') || null;
}
// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
var lastCall, timeoutId;
return function () {
var now = new Date().getTime();
if (lastCall && now < (lastCall + interval) ) {
// if we are inside the interval we wait
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
lastCall = now;
fn.call();
}, interval - (now - lastCall) );
} else {
// otherwise, we directly call the function
lastCall = now;
fn.call();
}
};
}
function getOffset( el ) {
var _x = 0;
var _y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: _y, left: _x };
}
function highlightNavigation() {
// get the current vertical position of the scroll bar
var scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
// iterate the sections
for (var i = $sections.length-1; i >= 0; i--) {
var currentSection = $sections[i];
// get the position of the section
var sectionTop = getOffset(currentSection).top;
// if the user has scrolled over the top of the section
if (scrollPosition >= sectionTop - 250) {
// get the section id
var id = currentSection.id;
// get the corresponding navigation link
var $navigationLink = sectionIdTonavigationLink[id];
// if the link is not active
if (typeof $navigationLink[0] !== 'undefined') {
if (!$navigationLink[0].classList.contains('active')) {
// remove .active class from all the links
for (i = 0; i < $navigationLinks.length; i++) {
$navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
}
// add .active class to the current link
$navigationLink[0].className += (' active');
}
} else {
// remove .active class from all the links
for (i = 0; i < $navigationLinks.length; i++) {
$navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
}
}
// we have found our section, so we return false to exit the each loop
return false;
}
}
}
window.addEventListener('scroll',throttle(highlightNavigation,150));
For anyone trying to use this solution more recently, I hit a snag trying to get it to work. You may need to escape the href like so:
$('#navigation > ul > li > a[href=\\#' + id + ']');
And now my browser doesn't throw an error on that piece.
function navHighlight() {
var scrollTop = $(document).scrollTop();
$("section").each(function () {
var xPos = $(this).position();
var sectionPos = xPos.top;
var sectionHeight = $(this).height();
var overall = scrollTop + sectionHeight;
if ((scrollTop + 20) >= sectionPos && scrollTop < overall) {
$(this).addClass("SectionActive");
$(this).prevAll().removeClass("SectionActive");
}
else if (scrollTop <= overall) {
$(this).removeClass("SectionActive");
}
var xIndex = $(".SectionActive").index();
var accIndex = xIndex + 1;
$("nav li:nth-child(" + accIndex + ")").addClass("navActivePage").siblings().removeClass("navActivePage");
});
}
.navActivePage {
color: #fdc166;
}
$(document).scroll(function () {
navHighlight();
});
In this line:
$('#navigation > ul > li > a').attr('href', id).addClass('active');
You are actually setting the href attribute of every $('#navigation > ul > li > a') element, and then adding the active class also to all of them. May be what you need to do is something like:
$('#navigation > ul > li > a[href=#' + id + ']')
And select only the a which href match the id. Make sense?

first time my function runs fine, second time its wrong jquery

I'm trying to make a lightbox. But when i open the lightbox for the second time. It goes trough my code twice. When i open my lightbox the third time, it goes trough my code three times. Don't get it at all.
$(document).ready(function(){
$('.bg-overlay, .overlay-content, .overlay-content img').hide();
$('.thump-bnr > li').click(function(){
// show the overlay and bg-overlay
$('.bg-overlay, .overlay-content').fadeIn(500);
// gets the index of the thunp thats been clicked in the banner
var x = $(this).index();
// console.log(x);
$('.overlay-content > img').eq(x).fadeIn(500);
// thumpPop checks if there aren't to mutch list items
var thumpPop = $('.overlay-content .thump-pop li').length;
// console.log(thumpPop);
// appends all li for the thump navigation in the overlay
if (thumpPop < 1) {
$('.overlay-content').append('<ul class="thump-pop"></ul>');
for (var i = 0; i < 4; i++) {
$('.thump-pop').append('<li></li>');
}
}
// sets all thump li to the border white
$('.thump-pop > li').css("border-color", "#fff");
// sets the active thump li to a dark border
$('.thump-pop > li').eq(x).css("border-color", "#e2e2e2");
// console.log(x);
// calls the thumpNav function for the thump navigation
thumpNav();
// calles the arrowNav function for the arrow navigation beside the big images
arrowNav();
});
In this function i have managed to execute the function only once by using an if statement.
// this is the function for the thump navigation
function thumpNav() {
$('.thump-pop > li').click(function(){
// get the index number of the thump li
var y = $(this).index();
// console.log(y);
// checks if the big image thats equal to the clicked thump is hidden
if($('.overlay-content > img').eq(y).is(':hidden')) {
// fadeIn and fadeOut the big images
$('.overlay-content img').fadeOut();
$('.overlay-content > img').eq(y).fadeIn();
// this wil change the border color of the active li
$('.thump-pop > li').css("border-color", "#fff");
$(this).css("border-color", "#e2e2e2");
}
});
}
I think i have made a mistake in the function arrowNav(), because he executes this twice when i open my lightbox for the second time.
function arrowNav() {
$('.arrow-nav-left').click(function(){
// this wil get the index number of the visible image in the overlay. This number can be used to display the number -1 our +1
var x = $('.overlay-content').find('img:visible').index();
// console.log(x);
var x = x - 2;
console.log(x);
$('.overlay-content > img').hide();
$('.overlay-content > img').eq(x).show();
});
}
// hides the pop-up
$('.bg-overlay').click(function(){
$('.bg-overlay, .overlay-content, .overlay-content img').fadeOut(500);
});
});
Please help me, and some feedback on the code is alway helpfull. Thanks
The problem is here:
function thumpNav() {
$('.thump-pop > li').click(function(){
You're attaching a new click handler everytime you call thumpNav, and they will all execute and do the same thing everytime you click.
Replace with:
function thumpNav() {
$('.thump-pop > li').unbind("click").click(function(){
Just like you did with arrowNav().
Note that your code is very unefficient and not structured quite right. Even if this works it's not good when you're juggling click handlers like this. At least define the callback as a seperate function and pass that as an argument to click().
If you want to get help with improving your code, you can always post it on Codereview.
Every time you're calling:
thumpNav();
you're attaching a new click handler.
same with arrowNav()
but atleast here you unbind first.

Categories

Resources