Scrollspy effect stopped working - javascript

The scrollspy effect was working fine, but suddenly it stopped working. I mean the active element of the menu is identified while scrolling the onepage site, when the href links are given as like this href="#id". But It stops working when href is given like "/home#id". I want to use it like this. Since the site uses a common navigation.
This is my code:
/ Cache selectors
var lastId,
topMenu = $("#timenav"),
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
menuItems
.parent().removeClass("active")
.end().filter("[href=#"+id+"]").parent().addClass("active");
}
});
can someone help me with this
http://www.drkeenly.com/
Here is a Fiddle
http://jsfiddle.net/aGjTV/
Any help would be great. Thanks in advance !

var cur = scrollItems!=null?scrollItems.map(...):null;
After this line cur is either something or null.
If it's null, then you can't index it like and array, or for that matter get its length with cur.length.
You haven't specified the error, but I bet it's saying "TypeError: cur is null".
The solution depends on what you actually want your code to do, but I'd suggest wrapping the remainder of the function in
if (cur) {
...
}

I just wrote a custom scroll-spy script, if you wish.
Check the DEMO http://jsfiddle.net/yeyene/L4Gpq/
No need scrollspy plugin anymore and you only need to add in below;
1) target to your navi link.
2) Same class to your DIVs and id: same as your target of navi.
HTML
<nav>
<ul>
<li><a target="main">Home</a></li>
<li><a target="story">Our Story</a></li>
<li><a target="work">Our Work</a></li>
<li><a target="our-news">Our News</a></li>
<li><a target="contact">Contact Us</a></li>
</ul>
</nav>
<div id="main" class="content">main</div>
<div id="story" class="content">story</div>
<div id="work" class="content">work</div>
<div id="our-news" class="content">our-news</div>
<div id="contact" class="content">contact</div>
JQUERY
$('nav li a').click(function () {
var id = $(this).attr('target');
$('html, body').stop().animate({
scrollTop: $('#'+id).offset().top
}, 500);
});
$(window).scroll(function() {
var myArray = new Array();
$('div.content').each(function() {
myArray.push($(this).offset().top);
});
for( var i=0; i<= myArray.length; i++){
if($(this).scrollTop() >= myArray[i]){
$('nav a').removeClass('active');
$('nav a').eq(i).addClass('active');
}
}
});
CSS
li {
float:left;
margin-right:10px;
list-style:none;
text-decoration:none;
}
li a {
cursor:pointer;
text-decoration:none;
z-index:11;
padding:0 5px;
}
li a.active {
background:red;
color:#fff;
}
.content {
float:left;
width:90%;
padding:45% 5%;
background:#dfdfdf;
margin-bottom:10px;
}
nav {
position:fixed;
width:100%;
padding:0 0 10px 0;
top:0;
left:0;
z-index:10;
background:green;
}

var item = $($(this).attr("href")); in your code was creating problem because when you write href="#id" it is var item = $(#id); but when you write href="/home#id" it becomes var item = $(/home#id); which is incorrect.I fixed it:
scrollItems = menuItems.map(function(){
var indexItm = $(this).attr('href').indexOf('#');
var str = $(this).attr('href').substring(indexItm);
var item = $(str);
if (item.length) { return item; }
});
Another change filter("[href*=#"+id+"]") which will check for string #id within href even in href contains any text before #:
Edited line:
menuItems
.parent().removeClass("active")
.end().filter("[href*=#"+id+"]").parent().addClass("active");
Fiddle were only foo is given href="/home#foo"
Note : the above fiddle will require a text along with # for Top href also.Otherwise it will set .active to all menu items:
Top
.
.
.
<a id="top">top</a>

Related

Highlighting item in table of contents when section is active on page as scrolling

I'm trying to highlight the current section items in a sticky table of contents as you scroll down the page.
The structure is currently like:
<div>
<div>
<div>
<div>
<h2 id="twitter-chats-for-ecommerce">Header</h2>
<div>content...</div>
</div>
</div>
</div>
</div>
And the table of contents like:
<ul>
<li>Item 1</li>
<li>Item 1</li>
<li>Twitter Chats for Ecommerce</li>
</ul>
The anchor is being applied to the header automatically via a Gutenberg block in WordPress (in this case, the h2).
The existing JavaScript for this is the following:
(function($) {
/* Table of contents - highlight active section on scroll */
window.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const id = entry.target.getAttribute('id');
if (entry.intersectionRatio > 0) {
document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.add('active');
} else {
document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.remove('active');
}
});
});
// Track all sections that have an `id` applied
document.querySelectorAll('h2[id]').forEach((section) => {
observer.observe(section);
});
});
})( jQuery );
But as you can see from the below example screenshot, the item in the table of contents is highlighting when the matching header (in this case h2) is visible on the page.
So in the case where there are multiple headers visible on the page, more than one item is highlighted in the TOC.
Ideally, I guess that it should be matching the div of the section but the id anchor is being applied to the header and I don't have control over this.
Is there a way I can modify the JS to somehow only detect the entire div section instead of just the header so that it will only ever have one item highlighted in the TOC - or is there even a better way than this?
An example of one that works perfectly that I'd like to achieve can be seen here (see the 'On This Page' section in right hand sidebar).
I modified randomdude's answer to highlight the lowest scrolled-to header. This will persist highlighting of that link until the user scrolls down far enough to another one.
const anchors = $('body').find('h1');
$(window).scroll(function(){
var scrollTop = $(document).scrollTop();
// highlight the last scrolled-to: set everything inactive first
for (var i = 0; i < anchors.length; i++){
$('nav ul li a[href="#' + $(anchors[i]).attr('id') + '"]').removeClass('active');
}
// then iterate backwards, on the first match highlight it and break
for (var i = anchors.length-1; i >= 0; i--){
if (scrollTop > $(anchors[i]).offset().top - 75) {
$('nav ul li a[href="#' + $(anchors[i]).attr('id') + '"]').addClass('active');
break;
}
}
});
forked fiddle link: http://jsfiddle.net/tz6yxfk3/
I think you are looking for this:
$(window).scroll(function(){
var scrollTop = $(document).scrollTop();
var anchors = $('body').find('h1');
for (var i = 0; i < anchors.length; i++){
if (scrollTop > $(anchors[i]).offset().top - 50 && scrollTop < $(anchors[i]).offset().top + $(anchors[i]).height() - 50) {
$('nav ul li a[href="#' + $(anchors[i]).attr('id') + '"]').addClass('active');
} else {
$('nav ul li a[href="#' + $(anchors[i]).attr('id') + '"]').removeClass('active');
}
}
});
nav {
position: fixed;
right: 20px;
top: 20px;
}
div {
height: 2000px;
}
div > h1 {
height: 200px;
}
.active {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<nav>
<ul>
<li id="whatbutton">What We Are</li>
<li id="whybutton">Why Us</li>
<li id="offerbutton">What We Offer</li>
<li id="contactbutton">Contact Us</li>
</ul>
</nav>
<div>
<h1 id="whatissm" name="whatissm"><span>sometexthere</span></h1>
<h1 id="whyusesm" name="whyusesm"><span>somtexthere</span></h1>
<h1 id="whatdoessmoffer" name="whatdoessmoffer"><span>sometexthere</span></h1>
<h1 id="contactus" name="contactus"><span>Contact Us</span></h1>
</div>
credits: Making a menu class active when scrolled past
fiddle link:
http://jsfiddle.net/yrz54fqm/1/
This is same answer as above, but is written in pure JavaScript, no need for jQuery. Great work, Crane, works like a champ.
const anchors = document.querySelectorAll('h1');
const links = document.querySelectorAll('nav > ul > li > a');
window.addEventListener('scroll', (event) => {
if (typeof(anchors) != 'undefined' && anchors != null && typeof(links) != 'undefined' && links != null) {
let scrollTop = window.scrollY;
// highlight the last scrolled-to: set everything inactive first
links.forEach((link, index) => {
link.classList.remove("active");
});
// then iterate backwards, on the first match highlight it and break
for (var i = anchors.length-1; i >= 0; i--) {
if (scrollTop > anchors[i].offsetTop - 75) {
links[i].classList.add('active');
break;
}
}
}
});

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.

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?

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.

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