Scroll page to to change active/current menu item - javascript

I am using http://jsfiddle.net/mekwall/up4nu/ for my scrolling on a one pager. How would I deactivate the current marking when a menu item is clicked? I want to keep it on scroll. But when I am on the first item and click the fourth one, I just want the fourth one to mark current, with no marking "sliding" through second and third item.
I also tried the onePageNav, but it breaks if I have a subpage that doesn't have content (section with id) on the landing page.

This should work :) I added a noScrollAction variable. I set it before the scrolling animation starts and disable it when it's ended. But the window scroll still fires after the animation is over (haven't figured out why yet). So I put an extra delay on it. And set the active class for the right anchor.
// Cache selectors
var lastId,
topMenu = $("#top-menu"),
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; }
}),
noScrollAction = false;
// Bind click handler to menu items
// so we can get a fancy scroll animation
menuItems.click(function(e){
var href = $(this).attr("href"),
offsetTop = href === "#" ? 0 : $(href).offset().top-topMenuHeight+1;
noScrollAction = true;
$('html, body').stop().animate({
scrollTop: offsetTop
},{
duration: 300,
complete: function() {
menuItems
.parent().removeClass("active")
.end().filter("[href=" + href +"]").parent().addClass("active");
setTimeout(function(){ noScrollAction = false; }, 10);
}
});
e.preventDefault();
});
// Bind to scroll
$(window).scroll(function(){
if(!noScrollAction){
// 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");
}
}
});​

Related

JavasScript — Uncaught Error: Syntax error, unrecognised expression: [href=#contact] (WordPress)

I am trying to implement an interaction where a class is added to a specific menu item when a is in view. Unfortunately, I am getting the following error:
Uncaught Error: Syntax error, unrecognised expression: [href=#contact]
Thank you in advance.
Pseudo-code
Scroll the page
When the section is in view add a class to the respective nav item
JavaScript
// Cache selectors
var lastId,
topMenu = $('#top-menu'),
topMenuHeight = topMenu.outerHeight() + 1,
// 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 click handler to menu items
// so we can get a fancy scroll animation
menuItems.click(function(e) {
var href = $(this).attr('href'),
offsetTop = href === '#' ? 0 : $(href).offset().top - topMenuHeight + 1
$('html, body').stop().animate(
{
scrollTop: offsetTop,
},
850,
)
e.preventDefault()
})
// 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 : ''
console.log('Cur', cur, 'ID', id)
if (lastId !== id) {
lastId = id
// Set/remove active class
menuItems.parent().removeClass('active').end().filter('[href=#' + id + ']').parent().addClass('active')
}
})
jQuery is pretty picky when it comes to quotes in attribute selectors. You must us a quoted string for the value (either single or double).
Change:
filter('[href=#' + id + ']')
To:
filter('[href="#' + id + '"]')
If you wanted to be slightly less precise but have the benefit of dropping the # you could use ~= instead of just = too.

Sticky navigation not adding active links correctly

I'm trying to implement a navigation. I have used this code to do the same.
<nav>
<ul id="mainNav">
<li class="active">Home</li>
<li>Work</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
<section id="home"><h2>Home</h2></section>
<section id="work" data-sr><h2>Work</h2></section>
<section id="about"><h2>About</h2></section>
<section id="contact"><h2>Contact</h2></section>
// Cache selectors
var lastId,
topMenu = $("#mainNav"),
topMenuHeight = topMenu.outerHeight()+1,
// 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 click handler to menu items
// so we can get a fancy scroll animation
menuItems.click(function(e){
var href = $(this).attr("href"),
offsetTop = href === "#" ? 0 : $(href).offset().top-topMenuHeight+1;
$('html, body').stop().animate({
scrollTop: offsetTop
}, 850);
e.preventDefault();
});
$(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");
}
});
The requirement :- I want the navigation to have links which can be swapped as per the requirement.
The issue :- In the above codepen when I swap the links, it does not add the active classes properly. If I swap the navigation links AND the section link also then only it works well.
For example, if my navigation has HOME, WORK, ABOUT and CONTACT. Then I should be able to swap the position of the work link with the contact link and still my sticky navigation SHOULD add the active classes correctly WITHOUT shifting their respective sections.
Please help me achieve the above scenario.
The below code should solve your problem.
// Cache selectors
var lastId,
topMenu = $("#mainNav"),
topMenuHeight = topMenu.outerHeight()+1,
// All list items
menuItems = topMenu.find("a"),
// Anchors corresponding to menu items
scrollItems = $('section');
// Bind click handler to menu items
// so we can get a fancy scroll animation
menuItems.click(function(e){
var href = $(this).attr("href"),
offsetTop = href === "#" ? 0 : $(href).offset().top-topMenuHeight+1;
$('html, body').stop().animate({
scrollTop: offsetTop
}, 850);
e.preventDefault();
setActiveNav(href);
});
function setActiveNav(href) {
menuItems.each((index, menu) => {
if(menu.hash === href) {
$(menu).parent().addClass('active');
} else {
$(menu).parent().removeClass('active');
}
});
}
$(window).scroll(function(){
// Get container scroll position
var fromTop = $(this).scrollTop();
// Get id of current scroll item
var cur = scrollItems.toArray().findIndex(item => {
return (item.offsetTop >= fromTop);
});
// Get the id of the current element
var id = cur && cur > -1 ? scrollItems[cur].id : "";
if (lastId !== id) {
lastId = id;
// Set/remove active class
menuItems
.parent().removeClass("active")
.end().filter("[href=#"+id+"]").parent().addClass("active");
}
});
Structuring your functionalities in separate functions will solve future maintenance overheads.
Mostly the issues are with structuring, element index and position.

Minimalistic scrollspy changing active class just to a tag

Anyone could solve this? It's a code on minimalistic scrollspy. It adds active class when content is offset.top. I know this code works but could I know if there's a way to change active class to the "a" tag instead of its parent? I removed .parent() but it doesn't seem to work. I don't want the whole li to be active. Thanks in advance.
Codepen: https://jsfiddle.net/mekwall/up4nu/
HTML
<nav class="clearfix">
<ul class="clearfix" id="top-menu">
<li>Services</li>
<li>About Us</li>
<li>Testimonials</li>
<li>Contact Us</li>
</ul>
</nav>
jQuery
// Cache selectors
var lastId,
topMenu = $("#top-menu"),
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 click handler to menu items
// so we can get a fancy scroll animation
menuItems.click(function(e){
var href = $(this).attr("href"),
offsetTop = href === "#" ? 0 : $(href).offset().top-topMenuHeight+1;
$('html, body').stop().animate({
scrollTop: offsetTop
}, 300);
e.preventDefault();
});
// 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 -- This is the part.
menuItems
.parent().removeClass("active")
.end().filter("[href='#"+id+"']").parent().addClass("active");
}
});
Here an updated working jsfiddle
First I added a CSS for a.active, because it fits the [li] space so was impossible to discern what was active.
Then you must set starting as active the tag instead the [li]
Now you can change your last 2 javascript rows to
menuItems
.parent().find('a').removeClass("active")
.find('a').end().filter("[href='#"+id+"']").addClass("active");
using find('a') to get the right tag.

Why isn't my jQuery for binding to scroll event working?

I'm trying to create an active menu that inserts the CSS class .active into each list item as the user scrolls and the corresponding id is passed. I tested this in JSFiddle so I know my code is solid, but it's having no affect on the site I'm developing:
http://dev.celebrate-life.com/
Here's the jQuery:
(function($) {
// Selectors
var lastId,
topMenu = $("#menu-mainmenu"),
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 click handler to list items
menuItems.click(function(e){
var href = $(this).attr("href"),
offsetTop = href === "#" ? 0 : $(href).offset().top-topMenuHeight+1;
$('html, body').stop().animate({
scrollTop: offsetTop
}, 300);
e.preventDefault();
});
// 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");
}
});
})(jQuery);
And this is my menu structure:
<ul id="menu-mainmenu" class="x-nav">
<li>
List Item
</li>
</ul>
I have CSS for when the "active" class is applied to a list item, but it's not really relevant until I resolve the issue of applying the class to an <li> in the first place.
Why won't this work?

jQuery active class on navigation links not changing

I have searched through similar questions, and feel like I've tried just about everything to no avail.
I want a class ".active" to append to a link in my page's navigation when a user scrolls to the corresponding section of my one-page site. When the user continues to scroll, that active class will disappear from the link and become added to the next link.
The jQuery for the scrolling does work in the code, but nothing else seems to.
Here is my site: http://tendigi.com/staging/
And here are (shortened) the sections of code:
HTML:
<ul id="nav">
<li>ABOUT</li>
<li>TEAM</li>
<li>PORTFOLIO</li>
<li>PROCESS</li>
<li>BRANDS</li>
<li>PRESS</li>
<li>BLOG</li>
<li>MEETUP</li>
<li>CONTACT</li>
</ul>
<section>
<a id="about">Header</a>
Some Text
</section>
<section>
<a id="team">Header</a>
Some Text
</section>
<section>
<a id="portfolio">Header</a>
Some Text
</section>
jQuery:
// Cache selectors
var lastId,
topMenu = $("#nav"),
topMenuHeight = topMenu.outerHeight()+75,
// 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; }
}),
noScrollAction = false;
// Bind click handler to menu items
// so we can get a fancy scroll animation
menuItems.click(function(e){
var href = $(this).attr("href"),
offsetTop = href === "#" ? 0 : $(href).offset().top-topMenuHeight+1;
noScrollAction = true;
$('html, body').stop().animate({
scrollTop: offsetTop
},{
duration: 300,
complete: function() {
menuItems
.parent().removeClass("active")
.end().filter("[href=" + href +"]").parent().addClass("active");
setTimeout(function(){ noScrollAction = false; }, 10);
}
});
e.preventDefault();
});
// Bind to scroll
$(window).scroll(function(){
if(!noScrollAction){
// 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");
}
}
});​
This is my first question on stackoverflow, so I apologize if I'm not doing this correctly! And help would be SO APPRECIATED.
Thanks!
From what I can see this is working. The active link is being applied to the <li /> though. If you need it on the <a /> change this:
.end().filter("[href=" + href +"]").parent().addClass("active");
to:
.end().filter("[href=" + href +"]").addClass("active");
Hope that helps!
EDIT
whoops! you need to change the line before as well. So it's:
.parent().removeClass("active")
.end().filter("[href=" + href +"]").parent().addClass("active");
to:
.removeClass("active")
.filter("[href=" + href +"]").addClass("active");
You also look like you need to tweak your threshold by 5 or 6 pixels...

Categories

Resources