Make first menu item red on load with scrollspy an jQuery - javascript

Can someone help me with my code? This code below makes my menu items red on scroll within the div where a "scrollspy" is added. But there is something missing so my first menu item (Home) does not get red when page is loaded, only when I scroll a bit below. I need to have this 1st item red on load. How can I fix this?
An example code to make first menu item active on load?
window.onload = function() {
//code?
};
This makes menu item red when added "scrollspy" class on a row in admin
var elems = $('.scrollspy');
$(window).bind('scroll', function() {
var currentActive = null;
var currentActiveDistance = -1;
var currentTop = $(window).scrollTop();
elems.each(function(index) {
var elemTop = $(this).offset().top - 102
var id = $(this).attr('id');
var navElem = $('.menu a[href="#' + id + '"]');
navElem.removeClass('active');
if (currentTop >= elemTop) {
var distance = currentTop - elemTop;
if (currentActiveDistance > distance || currentActiveDistance == -1) {
currentActive = navElem;
}
}
});
if (currentActive) {
currentActive.addClass('active');
}
});

Why not use just CSS?
.nav li a.active {
background-color: red;
}

jQuery can be used as below to make first menu item link RED i.e by applying class active.
$(document).ready(function(){
$(".menu:first a").addClass('active');
});
Please post your HTML markup if it does not work with your HTML.

Related

How is the active class added to the correct nav item inside scroll my event?

So I have a single page where the active menu item gets highlighted when you scroll to its corresponding section.
My code works, but I don't understand why, specifically this part:
document.querySelector('nav a[href="' + anchorID + '"]').classList.add("active");
On window.onscroll the for loop collects all the anchors (nav a) from the menu
Then I get access to individual anchorIDs (hrefs) with:
var anchorID = anchorsArray[i].getAttribute("href");
What I don't understand, is how the .activeclass gets added to the correct anchorID based on the current section inside the viewport — when no comparison is made between the section id and the corresponding anchor href. E.g. the href & section id:
Section 2
<section id="section-2"></section>
..are never compared on scroll.
DEMO: https://jsfiddle.net/zgpzrvns/
All the JS
(function() {
var anchorsArray = document.querySelectorAll("nav a");
var sections = document.querySelectorAll("section");
var sectionsArray = [];
// Collect all sections and push to sectionsArray
for (var i = 0; i < sections.length; i++) {
var section = sections[i];
sectionsArray.push(section);
}
window.onscroll = function() {
var scrollPosition = window.pageYOffset;
for (var i = 0; i < anchorsArray.length; i++) {
// Get hrefs from each anchor
var anchorID = anchorsArray[i].getAttribute("href");
var sectionHeight = sectionsArray[i].offsetHeight;
var sectionTop = sectionsArray[i].offsetTop;
if (
scrollPosition >= sectionTop &&
scrollPosition < sectionTop + sectionHeight
) {
/**
* I don't understand how querySelector finds & adds the active
* class to the correct anchor, when no comparison is made between the
* section ID (that is inside current section in viewport) and the
* anchors ID from anchorsArray
*/
document
.querySelector('nav a[href="' + anchorID + '"]')
.classList.add("active");
} else {
document
.querySelector('nav a[href="' + anchorID + '"]')
.classList.remove("active");
}
}
};
})();
In summary: how is the active class added to correct anchor ID, when the corresponding section ID inside the viewport on scroll (when section ID is never detected inside the scroll event?)
I'm so confused about this, and I bet it's something silly I'm overlooking!
Would greatly appreciate some input! :-)
In short:
It doesn't need to compare any ids, because on scroll you loop over all anchors in your navigation. For each you check, if the section at the same index is in the viewport. If so, you add the active class.
If you switch the positions of your navigation items, you'll see that it will add the active class to the wrong item, because it just checks the index.
If you need some more explanation, tell me. Going to edit the answer then.
Edit index explanation:
You have your navigation anchors in an array, and also your sections.
var anchorsArray = document.querySelectorAll("nav a");
var sections = document.querySelectorAll("section");
You are looping your anchors array
for (var i = 0; i < anchorsArray.length; i++) {
and then you get the height and top position of your section at the same index as your anchor (variable i)
var sectionHeight = sectionsArray[i].offsetHeight;
var sectionTop = sectionsArray[i].offsetTop;
if (
scrollPosition >= sectionTop &&
scrollPosition < sectionTop + sectionHeight
) {
and then you set the active class if its true, or remove it, if its false.
So on each scroll your code does the following:
Get anchor one -> check if section one is in range -> If yes -> add active -> else remove active
Get anchor two -> check if section two is in range -> If yes -> add active -> else remove active
Get anchor three -> ... and so one

Adding a css class on scroll via javascript

I have a navigation with an empty div in each anchor tag that I am styling on hover.
html
<li>SPECIAL EVENTS <div></div></li>
css
a:hover div, a:active div{
color: $black;
height: 1px;
background-color: $black;
width: 100%;
margin-top: 5px;
}
I also have a active class that I am attaching on click with some js. This is currently working correctly.
var currentDiv;
function addSelected(){
if(currentDiv !== undefined){
$(currentDiv).removeClass("active");
}
currentDiv = $(this).find('div');
$(currentDiv).addClass("active");
}
$(".menu a").click(addSelected);
What I am trying to do is attached that same active class based on the user scroll. I get most of it working, but I can't seem to figure how how to attached the class to the div, and not the anchor itself. Here is the js I'm working with.
js
// Cache selectors
var lastId,
topMenu = $("#desktop-nav"),
topMenuHeight = topMenu.outerHeight()+15,
// All list items
menuItems = topMenu.find("a"),
// Anchors corresponding to menu items
scrollItems = menuItems.map(function(){
var item = $($(this).attr("href"));
if (item.length) { return item; }
});
// Bind to scroll
$(window).scroll(function(){
// Get container scroll position
var fromTop = $(this).scrollTop()+topMenuHeight;
// Get id of current scroll item
var cur = scrollItems.map(function(){
if ($(this).offset().top < fromTop){
return this;
}
});
// Get the id of the current element
cur = cur[cur.length-1];
var id = cur && cur.length ? cur[0].id : "";
if (lastId !== id) {
lastId = id;
// Set/remove active class
menuItems
//.parent().removeClass("active")
//.end().filter("[href='#"+id+"']").parent().addClass("active");
.removeClass("active");
end().filter($("[href='#"+id+"']")).find('div').addClass("active");
console.log(id);
}
});
I think the part that I am trying to change is this
"[href='#"+id+"']").parent()
but everything I try is either not working or giving me errors.
EDIT
Here is the fiddle that I am trying to modify.
fiddle link
Use children() instead of parent() or find()
Example:
if (lastId !== id) {
lastId = id;
// add/remove active class on div
menuItems
.children().removeClass("active")
.end().filter("[href='#"+id+"']").children().addClass("active");
}
Try changing it to use find() like in your click handler:
"[href='#"+id+"']").find('div')
I assume you've tried...
"[href='#"+id+"']").parent().find('div')
and/or...
"[href='#"+id+"'].div"
...if so, are you able to add a class or id to the div and target it that way?

Add class to nav when scrolling to the anchor

This is dynamic. The nav changes based upon the page. The code here adds a class to the nav as you scroll to the section and then removes it as you scroll past. The problem is it only removes as you scroll down not up past the section. How do I do my condition to remove classes as you scroll up while achieving adding the class and removing it as you scroll down??
jQuery( document ).ready(function() {
var sectionelements = jQuery('.nav li');
(function(jQuery) {
var scrolling = function(){
jQuery(sectionelements).each(function(){
var object=jQuery('#'+this);
var wh = jQuery(window).height();
var st = jQuery(document).scrollTop();
var ot = jQuery(object).offset().top;
var eh = jQuery(object).height();
var href="a[href*=#"+object.attr('id')+"]";
if(st>ot){
jQuery(href).addClass('posreached');
}
if (st>ot+eh) {
jQuery(href).removeClass('posreached');
};
})
};
jQuery(window).scroll(scrolling);
jQuery(window).bind('resize orientationchange',scrolling);
//fire initial scroll
jQuery(window).scroll();
})(jQuery);
});
this:
if(st>ot){
jQuery(href).addClass('posreached');
}
if (st>ot+eh) {
jQuery(href).removeClass('posreached');
};
even in the second state the first will be true, so u need to add:
if(st>ot || st<ot+eh){
jQuery(href).addClass('posreached');
}
give it a try
I used these for my conditions and it worked
if(st>ot){
jQuery(href).addClass('posreached');
}
if (st<ot) {
jQuery(href).removeClass('posreached');
}
if (st>ot+eh) {
jQuery(href).removeClass('posreached');
}

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?

Animating the active item background in a nav fails

I have the following markup:
<ul>
<li>first</li>
<li>largerWord</li>
<li class="active">third</li>
</ul>
The items have transparent background except the active class, that has a blue color.
The idea is that when user clicks (not the active) instead of switching the backgrounds i want to move the background, you can see the result almost done here, but it fails the first click (see end of question for error log):
http://jsfiddle.net/FeV55/26/
What i do is to dynamically create a lower z-index <li> and animate the left according the (clicked) item offset and the width according the (clicked) item width
The jquery code:
$(document).ready(function(){
$('ul li a').not('.active').click(function(){
/*caching*/
var activa = $('li.active');
var bg = $('li.back');;
var list = $(this).closest('ul');
/*when it's first click the background item doesnt exist*/
if(bg.length>0 == false){
list.append('<li class="back"></li>');
}
var width = $(this).outerWidth(true);
var leftUL = list.offset().left;
var leftThis = $(this).offset().left;
var left = leftThis - leftUL;
/*Remove class to previous active*/
activa.removeClass('active');
/*Cancel background even if parent is active*/
$(this).addClass('noBg');
/*Update active class*/
$(this).parent().addClass('active');
/*Move the background to its offset*/
bg.animate({'left':left,'width':width});
/*logs*/
$('#oUl').text(leftUL);
$('#ocl').text($(this).offset().left);
$('#odf').text(left);
$('#obg').text(bg.offset().left);
});
});
But it fails on first click, firebug logs:
bg.offset() is null
[Parar en este error]
$('#obg').text(bg.offset().left);
Question is why? the item should exist by then in any case..
Be sure to select the element you've assigned to bg after you create it:
var bg = $('li.back');
if (bg.length == 0) {
list.append('<li class="back"></li>');
bg = $('li.back');
}
Even better, create the element into bg:
if (bg.length == 0) {
bg = $('<li class="back"></li>');
list.append(bg);
}

Categories

Resources