How to highlighting active menu item on scroll and click? - javascript

I want to add a class active to the current menu item on scroll and click. It’s a single (long) page with different sections. When click on an item, the active state should switch to the current one.
I came up with the following code, but doesn’t now how I can integrate the above:
// Click event
$('#primary-navwrapper li a[href^="#"]').click(function(event) {
// Prevent from default action to intitiate
event.preventDefault();
// The id of the section we want to go to
var anchorId = $(this).attr('href');
// Our scroll target : the top position of the section that has the id referenced by our href
var target = $(anchorId).offset().top - offset;
console.log(target);
$('html, body').stop().animate({ scrollTop: target }, 500, function () {
window.location.hash = anchorId;
});
return false;
});

To add active class on click is simple using jQuery. You just need this code in the click handler
//remove active from all anchor and add it to the clicked anchor
$('#primary-navwrapper li a[href^="#"]').removeClass("active")
$(this).addClass('active');
And for the scroll part you need to monitor the position of the scroll bar and compare it with every page offset like so
//check the pages when scroll event occurs
$(window).scroll(function(){
// Get the current vertical position of the scroll bar
position = $(this).scrollTop();
$('#primary-navwrapper li a[href^="#"]').each(function(){
var anchorId = $(this).attr('href');
var target = $(anchorId).offset().top - offset;
// check if the document has crossed the page
console.log(position,target);
if(position>=target){
//remove active from all anchor and add it to the clicked anchor
$('#primary-navwrapper li a[href^="#"]').removeClass("active")
$(this).addClass('active');
}
})
Here is a demo http://jsfiddle.net/k5afwfva/

<nav>
item
item
item
item
</nav>
var el = $(".item"),
yPos = 0;
el.click(function(event){
event.preventDefault();
$(this).addClass("active").siblings().removeClass("active");
});
$(window).scroll(function(){
yPos = $(this).scrollTop();
//i'm almost sure that you will need to calculate offset of your section to know when to switch classes
if(yPos > 100){
el.removeClass("active").eq(1).addClass("active");
}
if(yPos > 200){
el.removeClass("active").eq(2).addClass("active");
}
//etc....
});

Related

How to add active class to selected list item

Hi I'm trying to add class to the selectedlList item & also add class if I scroll to the specific div. for example of scroll on div#six number six(6) in the menu should also have class active.
[see my code and demo here][1]
[1]: https://codepen.io/GoPerov/pen/aZmVgE
Try this code, Here's your updated pen
$(document).ready(function(){
$(".page-scroll").click(function(){
$(".page-scroll").removeClass("active"); //removes current active class
$(this).addClass("active"); // adds active class to specific click
})
});
Update your code as below.
You need to manually check for which div is currently active with scroll position.
Updated codepen demo
$(document).ready(function() {
$(".page-scroll").click(function() {
$(".page-scroll").removeClass("active"); //removes current active class
$(this).addClass("active"); // adds active class to specific click
});
});
$(window).scroll(function() {
var selectedDiv = $(".page-scroll:first");
var topOffset = 1;
var windowTop = $(window).scrollTop();
$(".page-scroll").each(function() {
var id = $(this).attr("href");
var offset = ($(id).offset() || {
top: 0
}).top - windowTop;
if (offset < 1 && (topOffset == 1 || offset > topOffset)) {
topOffset = offset;
selectedDiv = $(this);
}
});
if (!selectedDiv.hasClass("active")) {
$(".page-scroll").removeClass("active");
selectedDiv.addClass("active");
}
});

Custom slider issue

I have written a custom slider with object-oriented javascript as seen below. I have included the module in a fiddle here https://jsfiddle.net/5z29xhzg/7/. After sliding left or right, the slides are cloned and appended accordingly so that the user can loop through the slider as much as wanted. There is a separate function for controlling the active tab. When used separately, the tabs and slider work perfectly, but I am having an issue when the two are used in conjuction. For instance, clicking 'blue apron' and then clicking the left slide button (which should take you to the 'dave & busters' slide) takes you to the bliss slide. Or clicking the last slide using the tabs and then clicking the next button does not show anything. Can someone point out the flaw in the object that I have written. Any help is much appreciated!
GiftSlider = {
prev: '.slider-container .prev',
next: '.slider-container .next',
slide: '.slide',
slidesContainer: '#slides',
navLink: '.gift-nav li a',
init: function() {
// Initialize nextSlide function when clicking right arrow
$(this.next).click(this.nextSlide.bind(this));
$(this.next).click(this.activeTabs.bind(this));
// Initialize prevSlide function when clicking left arrow
$(this.prev).click(this.prevSlide.bind(this));
$(this.prev).click(this.activeTabs.bind(this));
// Initialize toggleSlides and activeTab functions when clicking nav links
$(this.navLink).click(this.toggleSlides.bind(this));
$(this.navLink).click(this.activeTabs.bind(this));
},
nextSlide: function(e) {
// Prevent default anchor click
e.preventDefault();
// Set the current slide
var currentSlide = $('.slide.current');
// Set the next slide
var nextSlide = $('.slide.current').next();
// remove the current class from the current slide
currentSlide.removeClass("current");
// Move the current slide to the end so we can cycle through
currentSlide.clone().appendTo(this.slidesContainer);
// remove the last slide so there is not two instances
currentSlide.remove();
// Add current class to next slide to display it
nextSlide.addClass("current");
},
prevSlide: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
// Set the current slide
var currentSlide = $('.slide.current');
// Set the last slide
var lastSlide = $('.slide').last();
// remove the current class from the current slide
currentSlide.removeClass("current");
// Move the last slide to the beginning so we can cycle through
lastSlide.clone().prependTo(this.slidesContainer);
// remove the last slide so there is not two instances
lastSlide.remove();
// Add current class to new first slide
$('.slide').first().addClass("current");
},
toggleSlides: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
var itemData = e.currentTarget.dataset.index;
var currentSlide = $('.slide.current');
currentSlide.removeClass("current");
newSlide = $('#slide-' + itemData);
// currentSlide.clone().appendTo(this.slidesContainer);
newSlide.addClass("current");
// console.log(newSlide);
},
activeTabs: function() {
// *** This could be much simpler if we didnt need to match the slider buttons
// *** up with nav tabs. Because I need to track the slider as well, I have
// *** made this its own function to be used in both instances
// get the active slide
var activeSlide = $('.slide').filter(':visible');
// get the active slide's id
var currentId = activeSlide.attr('id');
// grab just the number from the active slide's id
var currentNum = currentId.slice(-1);
// remove any active gift-nav links
$(this.navLink).removeClass("active");
// get the current tab by matching it to the current slide's id
var currentTab = $('.gift-nav li a[data-index="' + currentNum + '"]');
// make that active
currentTab.addClass("active");
}
}
$(document).ready(function(){
// Init our objects
GiftSlider.init();
});
The bug seems to be in toggleSlides.
EDIT: The following doesn't work
When the page loads, currentSlide is slide 1. Now say you click the 3rd tab. At this point, you need to move the slide before the 3rd tab i.e. the 2nd tab to the end. When you say currentSlide.clone().appendTo(this.slidesContainer);, you are instead moving the first slide to the end. Therefore no matter which tab you clicked, when you click the previous button it was going to the first slide.
If instead you do newSlide.prev().clone().appendTo(this.slidesContainer); the code seems to work fine.
toggleSlides: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
var itemData = e.currentTarget.dataset.index;
var currentSlide = $('.slide.current');
currentSlide.removeClass("current");
newSlide = $('#slide-' + itemData);
//currentSlide.clone().appendTo(this.slidesContainer);
newSlide.prev().clone().appendTo(this.slidesContainer);
newSlide.addClass("current");
//console.log("In toggle slide: "+newSlide.next().attr("id"));
//console.log("In toggle slide: "+newSlide.prev().attr("id"));
//console.log("In toggle slide: "+$('.slide.current').next().attr("id"));
},
This seems to work. Check it out at https://jsfiddle.net/rfgnm992/1/. Your nextSlide and previousSlide seems to keep the current slide at the beginning. toggleSlides wasn't doing that.
toggleSlides: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
var itemData = e.currentTarget.dataset.index;
var currentSlide = $('.slide.current');
currentSlide.removeClass("current");
newSlide = $('#slide-' + itemData);
//keep new slide at the beginning and move the preceding slides to the end
newSlide.nextAll().addBack().prependTo(this.slidesContainer);
//console.log("NewSlide.next: "+newSlide.next().attr('id') + "NewSlide.next.next: "+newSlide.next().next().attr('id')+"newSlide.next.next.next: "+newSlide.next().next().next().attr('id'));
//currentSlide.clone().appendTo(this.slidesContainer);
newSlide.addClass("current");
// console.log(newSlide);
},
sorry I got back a bit later than expected. Took a look there. Think you're over-complicating things with the whole append/prepend/cloning thing.
I got it working, but there's a minor bug in it still. It cycles grand, forward and back and the correct links highlight, however when you click on a random link it doesn't immediately highlight, but when you click the next/prev buttons, the relevant links from the chosen image highlight. It's certainly an advance!!! I'm sure I could roll it out with another look, but its 2am here, and I've already been looking at it for an hour and a half!
Here's a fiddle and a snippet (just js cos my msg was too long - I just took out the paragraph at the end of the content, no css changes)
GiftSlider = {
prev: '.slider-container .prev',
next: '.slider-container .next',
slide: '.slide',
slidesContainer: '#slides',
navLink: '.gift-nav li a',
init: function() {
// Initialize nextSlide function when clicking right arrow
$(this.next).click(this.nextSlide.bind(this));
$(this.next).click(this.activeTabs.bind(this));
// Initialize prevSlide function when clicking left arrow
$(this.prev).click(this.prevSlide.bind(this));
$(this.prev).click(this.activeTabs.bind(this));
// Initialize toggleSlides and activeTab functions when clicking nav links
$(this.navLink).click(this.activeTabs.bind(this));
$(this.navLink).click(this.toggleSlides.bind(this));
},
nextSlide: function(e) {
// Prevent default anchor click
e.preventDefault();
// Set the current slide
var currentSlide = $('.slide.current');
// Set the next slide
var currentId = currentSlide.attr('id');
var currNum = (currentId.slice(-1));
var nextNum;
var increment = 1;
if (currNum == 4){
nextNum = 1;
}
else
{
nextNum = parseInt(currNum) + parseInt(increment) ;
}
var nextSlide = $('#slide-' + nextNum);
// remove the current class from the current slide
currentSlide.removeClass("current");
// Add current class to next slide to display it
nextSlide.addClass("current");
// remove the last slide so there is not two instances
},
prevSlide: function(e) {
// Prevent default anchor click
e.preventDefault();
// Set the current slide
var currentSlide = $('.slide.current');
// Set the last slide
var currentId = currentSlide.attr('id');
var currNum = (currentId.slice(-1));
var prevNum;
var decrement =1;
if (currNum == 1){
prevNum = 4;
}
else
{
prevNum = parseInt(currNum) - parseInt(decrement) ;
}
var prevSlide = $('#slide-' + prevNum);
// Move the last slide to the beginning so we can cycle through
currentSlide.removeClass("current");
// Add current class to new first slide
prevSlide.addClass("current");
},
toggleSlides: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
var itemData = e.currentTarget.dataset.index;
var currentSlide = $('.slide.current');
currentSlide.removeClass("current");
newSlide = $('#slide-' + itemData);
newSlide.addClass("current");
},
activeTabs: function() {
var activeSlide = $('.slide').filter('.current');
// get the active slide's id
var currentId = activeSlide.attr('id');
// grab just the number from the active slide's id
var currentNum = currentId.slice(-1);
// remove any active gift-nav links
$(this.navLink).removeClass("active");
// get the current tab by matching it to the current slide's id
var currentTab = $('.gift-nav li a[data-index="'+ currentNum + '"]');
// make that active
currentTab.addClass("active");
}
}
$(document).ready(function(){
// Init our objects
GiftSlider.init();
});

How to make the scroll bar to move to a accurate place

I have a list with scroll bar. Also there is a button that when pressed, it moves to a certain #id and scroll bar also moves to make that element visible. But it is not accurate. It moves, but not always to the exact place. How can I make this scroll function to be accurate:
DEMO http://jsfiddle.net/danials/anpXP/1/
My jQuery code:
function next() {
$(".list li").css("background", "grey");
goToByScroll(myID);
$("#" + myID).css("background", "red");
myID = $("#" + myID).next("li").attr("id");
}
function goToByScroll(id) {
$('.list').animate({
scrollTop: $("#" + id).offset().top - $("#" + id).height()
},
'slow');
}
In the demo try pressing the next button, and you'll see in some items the scroll moves but not correctly.
Any idea?
The problem with your code is that you are getting the offset of each element as you scroll down the list.
Offset is:
The .offset() method allows us to retrieve the current position of an element
relative to the document.
So this makes the offset of the box smaller, the further down the list you go.
What you need to do is figure out what the height+margin of an element is and do some math:
var myID = $(".list").children().first().attr("id");
function next() {
var li = $("#"+myID);
$(".list li").css("background", "grey");
var offset = parseInt(li.height())+parseInt(li.css("margin-top"));
$('.list').animate({scrollTop: offset*(myID-1)},'slow');
$("#"+myID).css("background", "red");
myID++;
}
This fiddle shows it in action. What it does is get the height+margin of the current element, and then multiplies it by how many elements down the list you are.
This only works assuming that all elements are the same size and that they have incremental IDs though.
UPDATE
If you want to make it work with Dynamic IDs, all you do is set an incremental variable to keep track of how many you have iterated through, and then grab the next ID similarly to how you did before:
var myID = $(".list").children().first().attr("id");
var inc = 1;
function next() {
var li = $("#"+myID);
$(".list li").css("background", "grey");
var offset = parseInt(li.height())+parseInt(li.css("margin-top"));
$('.list').animate({scrollTop: offset*(inc-1)},'slow');
$("#"+myID).css("background", "red");
myID = $("#"+myID).next().attr("id");
inc++;
}
And here's a fiddle.

Can't figure out how to correctly call an element

I have some JQuery that does a ton of stuff with reordering and adding links to the nav, but the important part is that it's supposed to hide every nav link except for the first one (by looping through the nav) when the page loads. The way the loop works is that it hides every link that doesn't have the class attribute: class="top".
This is working fine, except for when var page = "". As you can see from the code, I'm trying to select the nav link that links to "index.php" and add the class="top" attribute to it when var page = "". I don't know if this is right, but something appears to be breaking my entire javascript document. I don't even know if it's selecting the right element or adding the class attribute, because when var page = "" none of the nav links are hidden.
Any ideas? Thanks for your help!
This is the HTML of my navigation bar:
<nav>
<ul id='nav'>
<li>Home</li>
<li>Skillsets</li>
<li>Gallery</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
This is the JQuery I'm using:
var is_mobile = false,
page = document.location.pathname.match(/[^\/]+$/)[0];
$(document).ready(function(){
var ul = $('nav > ul'),
li = ul.children('li'),
href = li.find('a[href*="'+page+'"]'),
is404 = true;
if($('#mobile').css('display')=='none') {
is_mobile = true;
}
if(is_mobile) {
orderList();
prepareList();
}
/************************************************************/
/* Reorders the list relative to the page the user is on */
/**********************************************************/
function orderList() {
//remove the right border from the contact link
$(li.find('a[href*="contact.php"]')).removeAttr('style');
//move element to top
ul.prepend(href.parent());
//set top elements class to "top"
$(href).attr( "class", "top" );
if(page != ""){
//loop through the nav elements
li.children('a').each(function(){
//if the name of the page the user is on matches one of the nav links execute the command
if (page == $(this).attr('href')) {
is404 = false;
}
});
if (is404) {
//if the user is on a page not in the nav, add a 404 link at the top of the nav
ul.prepend("<li><a href='404NotFound.php' class='top'>404</a></li>");
}else if(page == ""){
//set top links' class to "top"
$(li.find('a[href*="index.php"]')).attr( "class", "top" );
}else{
$(href).attr( "class", "top" );
}
}
};
/*****************************************************************/
/* Prepares the list to be dynamically expandable/collapsible */
/***************************************************************/
function prepareList() {
//loop through the nav elements and differentiate the first nav link and the remaining nav links
li.children('a').each(function(){
//check if the link has the class: "first"
if ($(this).attr('class') == "top") {// attribute value matches variable value
//make the first nav link function as the button to open and close the nav
} else {// attribute doesn't exist, or its value doesn't match variable value
//hide the remaining nav links with a slideUp animation
$(this).slideUp("slow");
}
});
}
});
Since I am not the best with regex I am getting string(filename) in my fiddle with the help of lastIndexOf() & substring():
Fiddle
/*Getting File name*/
var is_mobile = false,
path = document.location.pathname;
var qpre = path.indexOf('?');
if(qpre!=-1)
{
var page = path.substring(path.lastIndexOf('/')+1,path.lastIndexOf('?'));
}
else{
var page = path.substring(path.lastIndexOf('/')+1);
}
/*End*/
/*Hiding li s with a href's not matching var page(string)*/
$('#nav li').each(function(){
if($(this).children('a').attr('href')!=page)
{
$(this).hide();
}
if(page=="")
{
$('#nav li:nth-child(1)').show();
}
});
/*End*/
Update
I have written a script which does all the functions you require in less lines of code
var is_mobile = false,
page = document.location.pathname.match(/[^\/]+$/)||[""]/*[0]*/;
if(page=="")
{
page="index.php";
}
var i=0;
$('#nav li a:not([href^="'+page+'"])').each(
function(){
$(this).slideUp();
i++;
}).promise()
.done( function() {
if(i==$('#nav li a').length)
{
$('#nav').append('<li>404</li>');
}
});
Demo Fiddle
String.prototype.match returns null when there is no match (like an empty string).
I think you need to use:
page = (document.location.pathname.match(/[^\/]+$/)||[""])[0]; // no path/relative
or
page = (document.location.pathname.match(/[^\/]+$/)||["/"])[0]; // root path
The || makes a missed match function default to the single element array with a fallback string.

on click addClass selected overrule addClass on scroll

I'm finding myself in a problem which I think has a easy solution but I cannot see... I have two functions to add a class called selected which highlights the buttons.
One function removes the class selected if present and adds the class to the button clicked:
var sidebarLinks = $('.js-sidebar a');
// About section: Remove and add class on click
sidebarLinks.on('click', function() {
sidebarLinks.removeClass('selected');
$(this).addClass('selected');
});
The other function adds the class selected if the user decides to scroll instead of clicking on the buttons. These buttons would highlight in a determined offset of the page:
var buttonOne = $('.buttonOne');
var buttonTwo = $('.buttonTwo');
var buttonThree = $('.buttonThree');
// Select nav buttons if scrolling and not clicking the button
$(window).scroll(function () {
// About nav sidebar links
var buttonOne = $('.js-polspotten-sidebar');
var buttonTwo = $('.js-product-sidebar');
var buttonThree = $('.js-designers-sidebar');
// About sections top scroll position
var currentTopPosition = $(this).scrollTop();
var productsTopPosition = $('#products').offset().top;
var designersTopPosition = $('#designers').offset().top;
// polspotten selected depending on scroll position
if (currentTopPosition >= currentTopPosition && currentTopPosition < productsTopPosition) {
buttonOne.addClass('selected');
}
else{
buttonOne.removeClass('selected');
}
// products selected depending on scroll position
if (currentTopPosition >= productsTopPosition &&
currentTopPosition < designersTopPosition) {
buttonTwo.addClass('selected');
}
else{
buttonTwo.removeClass('selected');
}
// designers selected depending on scroll position
if (currentTopPosition >= designersTopPosition) {
buttonThree.addClass('selected');
}
else{
buttonThree.removeClass('selected');
}
});
The problem is that both interact at the same time as the click event creates scroll.
What I want is that if somebody clicks on the button the scroll function will not work. It will only work if the user scrolls himself.
Is this possible?

Categories