Highlighting links based on scroll position - javascript

I'm trying to get the my links to highlight if the user is scrolling over the page that is for that link. But for some reason it isn't working properly. I Have commented out my first try in jquery and tried again but link two is highlighting when link one should.
<nav>
<ul>
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
</ul>
<p></p>
</nav>
<div id="sec_one" class="sections">
</div>
<div id="sec_two" class="sections">
</div>
<div id="sec_three" class="sections">
</div>
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
*{
margin: 0;
padding: 0;
}
nav{
width: 100%;
background-color: black;
position: fixed;
top: 0;
}
nav ul{
width: 50%;
margin: 0 auto;
list-style-type: none;
text-align: center;
}
nav ul li{
display: inline;
width: 100%;
}
nav ul li a{
font-size: 40px;
color: white;
text-decoration: none;
}
nav ul li a{
}
.sections{
width: 100%;
height: 2000px;
}
#sec_one{
background-color: blue;
}
#sec_two{
background-color: red;
}
#sec_three{
background-color: yellow;
}
.active{
background-color: #666666;
}
p{
color: white;
}
$(window).scroll(function(){
var scrollPos = $(window).scrollTop();
var page1Top = $("#sec_one").scrollTop();
var page1Bot = $("#sec_one").outerHeight();
var page2Top = $("#sec_two").scrollTop();
var page2Bot = $("#sec_two").outerHeight();
var page3Top = $("#sec_three").scrollTop();
var page3Bot = $("#sec_three").outerHeight();
/*if(scrollPos >= page1Top && scrollPos < page1Bot){
$("#link_1").addClass("active");
$("#link_2").removeClass("active");
$("#link_3").removeClass("active");
}else if(scrollPos >= page2Top && scrollPos < page2Bot){
$("#link_1").removeClass("active");
$("#link_3").removeClass("active");
$("#link_2").addClass("active");
}else if(scrollPos >= page3Top && scrollPos < page3Bot){
$("#link_3").addClass("active");
$("#link_1").removeClass("active");
$("#link_2").removeClass("active");
}*/
if(scrollPos >= page1Top && scrollPos < page1Bot){
$("#link_1").addClass("active");
$("#link_2").removeClass("active");
$("#link_3").removeClass("active");
}else {
$("#link_1").removeClass("active");
}
if(scrollPos >= page2Top && scrollPos < page2Bot){
$("#link_2").addClass("active");
$("#link_1").removeClass("active");
$("#link_3").removeClass("active");
}else {
$("#link_2").removeClass("active");
}
});

Your main problem is you're not using .offset() - With your code you're just getting the position relative to itself so the top always becomes 0 and bottom becomes 2000 - using offset would mean you're getting the position relative to the document so that it takes into account other elements as well.
Also you don't need to check the bottom location. You can just use the top location of the next section.
$(document).ready(function() {
$(window).scroll(function() {
var scrollPos = $(window).scrollTop();
var page1Top = $("#sec_one").offset().top;
var page2Top = $("#sec_two").offset().top;
var page3Top = $("#sec_three").offset().top;
if (scrollPos >= page1Top && scrollPos < page2Top) {
$("#link_1").addClass("active");
$("#link_2").removeClass("active");
$("#link_3").removeClass("active");
} else {
$("#link_1").removeClass("active");
}
if (scrollPos >= page2Top && scrollPos < page3Top) {
$("#link_2").addClass("active");
$("#link_1").removeClass("active");
$("#link_3").removeClass("active");
} else {
$("#link_2").removeClass("active");
}
if (scrollPos >= page3Top) {
$("#link_3").addClass("active");
$("#link_1").removeClass("active");
$("#link_2").removeClass("active");
} else {
$("#link_3").removeClass("active");
}
});
});
* {
margin: 0;
padding: 0;
}
nav {
width: 100%;
background-color: black;
position: fixed;
top: 0;
}
nav ul {
width: 50%;
margin: 0 auto;
list-style-type: none;
text-align: center;
}
nav ul li {
display: inline;
width: 100%;
}
nav ul li a {
font-size: 40px;
color: white;
text-decoration: none;
}
nav ul li a {}
.sections {
width: 100%;
height: 2000px;
}
#sec_one {
background-color: blue;
}
#sec_two {
background-color: red;
}
#sec_three {
background-color: yellow;
}
.active {
background-color: #666666;
}
p {
color: white;
}
<nav>
<ul>
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
</ul>
<p></p>
</nav>
<div id="sec_one" class="sections"></div>
<div id="sec_two" class="sections"></div>
<div id="sec_three" class="sections"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Avoid the target of individual elements and the use of the rough ID's, you can evaluate the scrollTop and the offset().top of the element and based on the index of the section highlight the item you need:
$(window).scroll(function() {
var scrollPos = $(window).scrollTop(),
navH = $('nav').height();
$('.sections').each(function(i){
var offT = $(this).offset().top;
if((offT-scrollPos-navH) <= 0) {
$('.active').removeClass('active')
$('nav a').eq(i).addClass('active')
}
})
});
* { margin: 0; padding: 0;} nav { width: 100%; background-color: black; position: fixed; top: 0;} nav ul { width: 50%; margin: 0 auto; list-style-type: none; text-align: center;} nav ul li { display: inline; width: 100%;} nav ul li a { font-size: 40px; color: white; text-decoration: none;} .sections { width: 100%; height: 2000px;} #sec_one { background-color: blue;} #sec_two { background-color: red;} #sec_three { background-color: yellow;} .active { background-color: #666666;} p { color: white;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav>
<ul>
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
</ul>
<p></p>
</nav>
<div id="sec_one" class="sections"></div>
<div id="sec_two" class="sections"></div>
<div id="sec_three" class="sections"></div>

So, I'm assuming you want the links to link to different divs on the page and not a completely different page.
You can use ScrollSpy to accomplish this really easily. The documentation can be found here.
Here's some sample code for how you would do this on your page:
First, reference the scrollspy.js file. Be sure to use the relative url depending on where you choose to save the file.
<script src="scrollspy.js"></script>
Then, in your page script file, you could have something like this.
$('.sections').on('scrollSpy:enter', function() {
switch($(this).attr('id')) {
case "sec_one":
$("#link_1").addClass("active");
$("#link_2").removeClass("active");
$("#link_3").removeClass("active");
break;
case "sec_two":
$("#link_1").removeClass("active");
$("#link_2").addClass("active");
$("#link_3").removeClass("active");
break;
case "sec_three":
$("#link_1").removeClass("active");
$("#link_2").removeClass("active");
$("#link_3").addClass("active");
break;
}
}
$('.sections').scrollSpy();

Rewrote this a bit so you aren't calculating each individual element in your JS and you can just find which div you've scrolled into dynamically and modify the class of the matching nav element.
var $sections = $('.sections'),
$lis = $('nav li');
$(window).on('scroll', function(){
var scrollPos = $(window).scrollTop(),
navHeight = $('nav').outerHeight();
$sections.each(function() {
var top = $(this).offset().top,
bottom = top + $(this).outerHeight();
if (scrollPos > top - navHeight && scrollPos < bottom) {
var $target = $lis.eq($(this).index() - 1);
$lis.not($target).removeClass('active');
$target.addClass('active');
}
})
});
*{
margin: 0;
padding: 0;
}
nav{
width: 100%;
background-color: black;
position: fixed;
top: 0;
}
nav ul{
width: 50%;
margin: 0 auto;
list-style-type: none;
text-align: center;
}
nav ul li{
display: inline-block;
}
nav ul li a{
font-size: 40px;
color: white;
text-decoration: none;
display: inline-block;
}
.sections{
height: 200vh;;
}
#sec_one{
background-color: blue;
}
#sec_two{
background-color: red;
}
#sec_three{
background-color: yellow;
}
.active{
background-color: #666666;
}
p{
color: white;
}
<nav>
<ul>
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
</ul>
<p></p>
</nav>
<div id="sec_one" class="sections">
</div>
<div id="sec_two" class="sections">
</div>
<div id="sec_three" class="sections">
</div>
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>

Related

Looking to make a scrolling (left to right) highlighted in page navigation bar

I'm looking to make a left to right scrolling navigation menu that's items are highlighted as you scroll down the page. This navigation will look similarly to the following in page navigation on Chase.com (https://www.chase.com/digital/customer-service?jp_cmp=rb/tap/off/na/prt). Please note that I want this navigation to stay scrollable on all devices.
Below is what I have so far. I'm currently having the issue with getting the nav options to scroll into focus as they are highlighted.
window.onscroll = function() {
myFunction()
};
var navbar = document.getElementById("inpagenav");
var sticky = inpagenav.offsetTop;
function myFunction() {
if (window.pageYOffset >= sticky) {
inpagenav.classList.add("sticky")
} else {
inpagenav.classList.remove("sticky");
}
}
// cache the navigation links
var $navigationLinks = $('#inpagenav > ul > li > a');
// cache (in reversed order) the sections
var $sections = $($(".pagenavsection").get().reverse());
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
$sections.each(function() {
var id = $(this).attr('id');
sectionIdTonavigationLink[id] = $('#inpagenav > 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 myPadding = $('#inpagenav').height();
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 + myPadding >= 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 );
.mainimage {
text-align: center;
height: 300px;
background: gray
}
nav {
background: white;
}
.sticky {
position: fixed;
top: 0;
width: 100%;
}
nav ul {
list-style: none;
padding: 0rem;
overflow: auto;
white-space: nowrap;
margin: 0rem;
overflow-x: scroll;
}
nav ul::-webkit-scrollbar-thumb {
background: #000;
}
nav ul li {
padding-top: 1rem;
vertical-align: center;
display: inline-block;
}
nav ul li a {
text-decoration: none;
color: dodgerblue;
display: inline-block;
margin: 0rem 2rem;
padding-bottom: 1rem;
border-bottom: 3px solid transparent;
}
nav ul li a:hover {
color: #0054a4;
border-bottom: 3px solid #0054a4;
}
nav ul li .active {
color: #308ce3;
font-weight: bold;
border-bottom: 3px solid #308ce3;
}
#section1 {
text-align: center;
height: 500px;
padding: 4rem 0rem;
background: orange;
}
#section2 {
text-align: center;
height: 200px;
padding: 4rem 0rem;
background: green;
}
#section3 {
text-align: center;
height: 300px;
padding: 4rem 0rem;
background: blue;
}
#section4 {
text-align: center;
height: 500px;
padding: 4rem 0rem;
background: red;
}
#section5 {
text-align: center;
height: 500px;
padding: 4rem 0rem;
background: pink;
}
#section6 {
text-align: center;
height: 500px;
padding: 7rem 0rem;
background: purple;
}
#section7 {
text-align: center;
height: 500px;
padding: 7rem 0rem;
background: purple;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="menu.css">
<body>
<div id="contentwrapper">
<div class="mainimage">
<h1>The whole world</h1>
</div>
<nav id="inpagenav">
<ul>
<li>Section 1</li>
<li>Section 2</li>
<li>Section 3</li>
<li>Section 4</li>
<li>Section 5</li>
<li>Section 6</li>
</ul>
</nav>
<section id="section1" class="pagenavsection">
I'm section 1
</section>
<section id="section2" class="pagenavsection">
I'm section 2
</section>
<section id="section3" class="pagenavsection">
I'm section 3
</section>
<section id="section4" class="pagenavsection">
I'm section 4
</section>
<section id="section5" class="pagenavsection">
I'm section 5
</section>
<section id="section6" class="pagenavsection">
I'm section 6
</section>
<section id="section7">lo</section>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="ScrollMenu.js"></script>
</body>
</html>
You might just need to call $navigationLink.scrollIntoView() when you toggle the active class.
Per this answer, you may need to pass special options to scrollIntoView because the items are laid out horizontally.
$navigationLink.scrollIntoView({ inline: 'end' })
// end is my guess; allowable values are: start, center, end, or nearest (the default)

click button scroll to specific div

I have a page that has a fixed menu and content box(div).
When click the menu, content box scroll to specific div.
So far so good.
This is the sample here.
https://jsfiddle.net/ezrinn/8cdjsmb9/11/
The problem is when I wrap this whole div and, make them as show/hide toggle button, the scroll is not working.
This is the sample that not working.
https://jsfiddle.net/ezrinn/8cdjsmb9/10/
Also here is the snippet
$('.btn').click(function() {
$(".wrap").toggleClass('on');
});
var div_parent_class_name;
var divs_class;
var id_offset_map = {};
$(document).ready(function() {
div_parent_class_name = "wrap_scroll";
divs_class = "page-section";
var scroll_divs = $("." + div_parent_class_name).children();
id_offset_map.first = 0;
scroll_divs.each(function(index) {
id_offset_map["#" + scroll_divs[index].id] = scroll_divs[index].offsetTop
});
$('a').bind('click', function(e) {
e.preventDefault();
var target = $(this).attr("href")
$('.wrap_scroll').stop().animate({
scrollTop: id_offset_map[target]
}, 600, function() {
/* location.hash = target-20; */ //attach the hash (#jumptarget) to the pageurl
});
return false;
});
});
$(".wrap_scroll").scroll(function() {
var scrollPos = $(".wrap_scroll").scrollTop();
$("." + divs_class).each(function(i) {
var divs = $("." + divs_class);
divs.each(function(idx) {
if (scrollPos >= id_offset_map["#" + this.id]) {
$('.menu>ul>li a.active').removeClass('active');
$('.menu>ul>li a').eq(idx).addClass('active');
}
});
});
}).scroll();
body,
html {
margin: 0;
padding: 0;
height: 3000px;
}
.wrap { display:none;}
.wrap.on { display:block;}
.menu {
width: 100px;
position: fixed;
top: 40px;
left: 10px;
}
.menu a.active {
background: red
}
.wrap_scroll {
position: absolute;
top: 20px;
left: 150px;
width: 500px;
height: 500px;
overflow-y: scroll
}
#home {
background-color: #286090;
height: 200px;
}
#portfolio {
background: gray;
height: 600px;
}
#about {
background-color: blue;
height: 800px;
}
#contact {
background: yellow;
height: 1000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="btn">show/hide</button>
<div class="wrap">
<div class="menu">
<ul>
<li><a class="active" href="#home">Home</a> </li>
<li>Portfolio </li>
<li>About </li>
<li>Contact </li>
</ul>a
</div>
<div class="wrap_scroll">
<div class="page-section" id="home">hh</div>
<div class="page-section" id="portfolio">pp</div>
<div class="page-section" id="about">aa</div>
<div class="page-section" id="contact">cc</div>
</div>
</div>
What Do I need to fix the code? please help.
When you calculate your offset, the div is hidden with display: none. This results in the offsets being set/calculated to zero.
Here's a quick fix I threw together: https://jsfiddle.net/hrb58zae/
Basically, moved the logic to determine offset after clicking show/hide.
var setOffset = null;
...
if (!setOffset) {
var scroll_divs = $("." + div_parent_class_name).children();
id_offset_map.first = 0;
scroll_divs.each(function(index) {
id_offset_map["#" + scroll_divs[index].id] = scroll_divs[index].offsetTop
});
setOffset = true;
}
In your CSS, instead of using display: none and display: block, try using visible instead:
.wrap { visibility:hidden;}
.wrap.on { visibility:visible;}
This will hide the element without affecting the layout.
Updated fiddle: https://jsfiddle.net/a5u683es/
The problem was you are trying to update id_offset_map when content was hidden. When you use 'display:none' prop you won't get dimensions for that element and so its not working.
I updated the logic please check the fiddle https://jsfiddle.net/qfrsmnh5/
var id_offset_map = {};
var div_parent_class_name = "wrap_scroll";
var divs_class = "page-section";
var scroll_divs = $("." + div_parent_class_name).children();
function updateOffsets(){
id_offset_map.first = 0;
scroll_divs.each(function(index) {
id_offset_map["#" + scroll_divs[index].id] = scroll_divs[index].offsetTop
});
}
$(document).ready(function() {
$('.btn').click(function() {
$(".wrap").toggleClass('on');
if($(".wrap").hasClass("on")){
updateOffsets();
}
});
$('a').on('click', function(e) {
e.preventDefault();
var target = $(this).attr("href")
$('.wrap_scroll').stop().animate({
scrollTop: id_offset_map[target]
}, 600, function() {
/* location.hash = target-20; */ //attach the hash (#jumptarget) to the pageurl
});
return false;
});
});
$(".wrap_scroll").on('scroll',function() {
var scrollPos = $(".wrap_scroll").scrollTop();
$("." + divs_class).each(function(i) {
var divs = $("." + divs_class);
divs.each(function(idx) {
if (scrollPos >= id_offset_map["#" + this.id]) {
$('.menu>ul>li a.active').removeClass('active');
$('.menu>ul>li a').eq(idx).addClass('active');
}
});
});
}).scroll();
body,
html {
margin: 0;
padding: 0;
height: 3000px;
}
.wrap { display:none;}
.wrap.on { display:block;}
.menu {
width: 100px;
position: fixed;
top: 40px;
left: 10px;
}
.menu a.active {
background: red;
}
.wrap_scroll {
position: absolute;
top: 20px;
left: 150px;
width: 500px;
height: 500px;
overflow-y: scroll;
}
#home {
background-color: #286090;
height: 200px;
}
#portfolio {
background: gray;
height: 600px;
}
#about {
background-color: blue;
height: 800px;
}
#contact {
background: yellow;
height: 1000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="btn">show/hide</button>
<div class="wrap">
<div class="menu">
<ul>
<li><a class="active" href="#home">Home</a></li>
<li>Portfolio </li>
<li>About </li>
<li>Contact </li>
</ul>
</div>
<div class="wrap_scroll">
<div class="page-section" id="home">hh</div>
<div class="page-section" id="portfolio">pp</div>
<div class="page-section" id="about">aa</div>
<div class="page-section" id="contact">cc</div>
</div>
</div>
works perfectly, it's just that when you use display: none you can not do the offsetTop calculations because in fact the element is not rendered, I'm not sure if all the values ​​give 0 or undefined, I guess undefined, a solution is always calculate Positions using a function:
var div_parent_class_name;
var divs_class;
var id_offset_map = {};
function calcTops(){
div_parent_class_name = "wrap_scroll";
divs_class = "page-section";
var scroll_divs = $("." + div_parent_class_name).children();
id_offset_map.first = 0;
scroll_divs.each(function(index) {
id_offset_map["#" + scroll_divs[index].id] = scroll_divs[index].offsetTop
});
}
https://jsfiddle.net/561oe7rb/1/
is not the optimal way, but it is to give you an idea. Sorry for my English.
Just Checkout This Working page I have designed
jQuery(document).on('scroll', function(){
onScroll();
});
jQuery(document).ready(function($) {
div_slider();
showhide();
});
/*show hide content*/
function showhide(){
$('.toggle-wrapper button').on('click', function(){
$('.wrapper').toggle();
// div_slider();
})
}
/*scrolling page on header elements click*/
function div_slider(){
$('ul li a').on('click', function(e){
e.preventDefault();
$('ul li a').removeClass('active');
$(this).addClass('active');
var attrval = $(this.getAttribute('href'));
$('html,body').stop().animate({
scrollTop: attrval.offset().top
}, 1000)
});
}
/*adding active class on header elements on page scroll*/
function onScroll(event){
var scrollPosition = $(document).scrollTop();
$('ul li a').each(function () {
var scroll_link = $(this);
var ref_scroll_Link = $(scroll_link.attr("href"));
if (ref_scroll_Link.position().top <= scrollPosition && ref_scroll_Link.position().top + ref_scroll_Link.height() > scrollPosition) {
$('ul li a').removeClass("active");
scroll_link.addClass("active");
}
else{
scroll_link.removeClass("active");
}
});
}
body {
margin: 0;
}
.toggle-wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: #ccd2cc;
text-align: center;
}
.toggle-wrapper button {
background-color: #ED4C67;
color: #ffffff;
padding: 10px 20px;
border: 0;
cursor: pointer;
border-radius: 5px;
}
.toggle-wrapper button:active{
background-color: #B53471;
}
header {
background-color: #6C5CE7;
position: fixed;
top: 36px;
z-index: 99;
left: 0;
right: 0;
}
header ul {
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0;
margin: 0;
}
ul li {
flex: 1 100%;
display: flex;
justify-content: center;
}
.wrapper {
margin-top: 36px;
}
header a {
color: #ffffff;
padding: 15px;
display: block;
text-decoration: navajowhite;
text-transform: uppercase;
width: 100%;
text-align: center;
}
header a.active {
color: #000000;
background-color: #ffffff;
}
section {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
section.section1 {
background-color: #FFEAA7;
}
section.section2{
background-color:#FAB1A0;
}
section.section3{
background-color:#7F8C8D;
}
section.section4{
background-color:#4CD137;
}
section.section5{
background-color:#A3CB38;
}
section.section6{
background-color:#70A1FF;
}
section.section7{
background-color:#079992;
}
<div class="toggle-wrapper">
<button>Toggle</button>
</div>
<div class="wrapper" style="display: none;">
<header>
<ul>
<li><a class="active" href="#one">one</a></li>
<li>two</li>
<li>three</li>
<li>four</li>
<li>five</li>
<li>six</li>
<li>seven</li>
</ul>
</header>
<section class="section1" id="one">SECTION ONE</section>
<section class="section2" id="two">SECTION TWO</section>
<section class="section3" id="three">SECTION THREE</section>
<section class="section4" id="four">SECTION FOUR</section>
<section class="section5" id="five">SECTION FIVE</section>
<section class="section6" id="six">SECTION SIX</section>
<section class="section7" id="seven">SECTION SEVEN</section>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Menu want get fixed to the top on scroll

I'm trying to accomplish a simple effect of sticking the menu to top of the browser window when scrolling passes a certain point, but something went wrong and the menu wont get fixed to the top. From the libraries I'm using jQuery and animate it.
My code is as follows:
HTML:
<nav class="animatedParent">
<ul class="animated bounceInUp delay-750">
<li class="animated">O meni</li>
<li class="animated">Katalog</li>
<li class="animated">Razno</li>
</ul>
</nav>
CSS:
.fixedNav {
display: block;
position: fixed;
top: 0;
width: 100%;
background: rgba( 0, 0, 0, .8);
height: 100px;
}
nav {
width: 400px;
margin: 20px auto;
}
nav ul {
list-style: none;
}
nav ul li {
float: left;
overflow: auto;
width: 130px;
}
nav ul li a {
font-size: 35px;
font-family: 'Indie Flower', cursive;
color: #fff;
cursor: pointer;
transition: 500ms linear all;
}
nav ul li a:hover {
color: #123456;
transition: 500ms linear all;
}
JS (jQuery):
$(document).ready(function(){
$("nav ul li").mouseenter(function() {
$(this).addClass("wiggle");
});
$("nav ul li").mouseleave(function() {
$(this).removeClass("wiggle");
});
var nav = $("nav").offsetTop();
if($(window).scrollTop() > nav) {
$("nav").addClass("fixedNav");
console.log('Hello!');
} else {
$("nav").removeClass("fixedNav");
}
});
So first off, you only use the code once, which is when the document is loaded. You're going to want to check everytime you scroll the document as the code should obivously be triggered once you scroll a certain amount.
$(document).scroll(function(){
var nav = $("nav").height();
if($(window).scrollTop() > nav) {
$("nav").addClass("fixedNav");
} else {
$("nav").removeClass("fixedNav");
}
});
body {
background: black;
height:700px;
}
.fixedNav {
display: block;
position: fixed;
top: 0;
width: 100%;
background: rgba( 0, 0, 0, .8);
height: 100px;
}
nav {
display: block;
height: 100px;
width: 100%;
margin: 20px auto;
}
nav ul {
list-style: none;
}
nav ul li {
float: left;
overflow: auto;
width: 130px;
}
nav ul li a {
font-size: 35px;
font-family: 'Indie Flower', cursive;
color: #fff;
cursor: pointer;
transition: 500ms linear all;
}
nav ul li a:hover {
color: #123456;
transition: 500ms linear all;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav class="animatedParent nav">
<ul class="animated bounceInUp delay-750">
<li class="animated">O meni</li>
<li class="animated">Katalog</li>
<li class="animated">Razno</li>
</ul>
</nav>
You need to use the event scroll and check the offset there.
When the user is scrolling, toggleClass will add/remove the class based on the condition $window.scrollTop() > navOffset which will return true or false
var $window = $(window);
var $nav = $('nav');
var navOffset = $nav.offsetTop();
$window.on('scroll', function() {
$nav.toggleClass('fixedNav', $window.scrollTop() > navOffset);
});
add an scroll event to check your scroll position
for example:
$(document).scroll(()=>{...});
like here
This is a very plain demo, it only demonstrate wha i meant
You can use a library like scrollMonitor to accomplish your task as scroll monitoring have its own caveats.
You can let scrollMonitor to lock position of your menu when it leaves viewport, something like this:
var $menu = document.querySelector('nav'); // It is better to use CSS class name instead
var watcher = scrollMonitor.create($menu);
watcher.lock();
watcher.exitViewport(function() {
$menu.classList.add('fixedNav');
});
watcher.enterViewport(function() {
$menu.classList.remove('fixedNav');
});
Please refer this example as it closely matches your task.
You don't fire the check for the current scroll on scroll event. That's an event you're looking for.
Also you could check the scrollTop on the document (it's more error proof in jQuery), not on the window as it doesn't always work.
$(document).ready(function(){
$("nav ul li").mouseenter(function() {
$(this).addClass("wiggle");
});
$("nav ul li").mouseleave(function() {
$(this).removeClass("wiggle");
});
$(document).on('scroll', function() {
var nav = $("nav").offsetTop();
if($(document).scrollTop() > nav) {
$("nav").addClass("fixedNav");
console.log('Hello!');
} else {
$("nav").removeClass("fixedNav");
}
})
});
That is what you are looking for:
$(document).ready(function(){
$("nav ul li").mouseenter(function() {
$(this).addClass("wiggle");
}) ;
$("nav ul li").mouseleave(function() {
$(this).removeClass("wiggle");
}) ;
});
$(document).ready(fixedHeader) ;
$(window).scroll(fixedHeader) ;
function fixedHeader() {
var nav = parseInt($("nav").css("margin-top")) ;
if($(window).scrollTop() > nav) {
$("nav").addClass("fixedNav");
}
else {
$("nav").removeClass("fixedNav");
}
}
body{
height: 1000px;
}
.fixedNav {
display: block;
position: fixed;
top: 0;
width: 100%;
background: rgba( 0, 0, 0, .8);
height: 100px;
}
nav {
width: 400px;
margin: 20px auto;
}
nav ul {
list-style: none;
}
nav ul li {
float: left;
overflow: auto;
width: 130px;
}
nav ul li a {
font-size: 20px;
font-family: 'Indie Flower', cursive;
color: #fff;
cursor: pointer;
transition: 500ms linear all;
}
nav ul li a:hover {
color: #123456;
transition: 500ms linear all;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav class="animatedParent">
<ul class="animated bounceInUp delay-750">
<li class="animated">O meni</li>
<li class="animated">Katalog</li>
<li class="animated">Razno</li>
</ul>
</nav>

Small bug with click-to-scroll menu in jQuery

On my website I have set up a "click-to-scroll" menu with the logic of:
1. when menu link is clicked, scroll to corresponding anchor and add active class to $(this)
2. onscroll, toggle active class according to the current anchor's location
This all works fine, but there is a small bug in that when you click a link, the page flickers slightly and so do the active menu links. You can see and test it live at http://jcwd98.appspot.com/ (warning that it's in its early development stages, no mobile and probably looks pretty crappy right now).
I'm not sure what causes the page to flicker, but I do know that the reason the menu links flicker is because my code is telling it to add an active class to it when it scrolls over its corresponding section. Since the document has to first scroll over a section to get to the desired section, it adds an active class to other links before it arrives.
I don't want either of these scenarios.
jsFiddle
Code:
var section_padding = 45;
$("#menu ul li a").on("click", function(event) {
event.preventDefault;
$("#menu ul li a.active").removeClass("active");
$(this).addClass("active");
var target = this.hash;
var menu = target;
var cache_target = $(target);
var buffer = (cache_target.offset().top - section_padding);
$("html, body").stop().animate({
"scrollTop": buffer
}, 400, "swing");
});
function scroll(event) {
var scroll_pos = $(document).scrollTop();
$("#menu ul li a").each(function() {
var cur_link = $(this);
var ref_el = $(cur_link.attr("href"));
if( ref_el.position().top <= scroll_pos && ref_el.position().top + ref_el.height() + (section_padding * 2) > scroll_pos ) {
$("#menu ul li a").removeClass("active");
cur_link.addClass("active");
} else {
cur_link.removeClass("active");
}
});
}
$(document).on("scroll", scroll);
* {
margin: 0;
padding: 0;
}
#menu {
display: block;
height: 50px;
width: 100%;
position: fixed;
z-index: 100;
background: rgba(255, 255, 255, .8);
}
#menu ul {
display: block;
width: 100%;
height: 100%;
list-style: none;
}
#menu ul li {
display: block;
box-sizing: border-box;
height: 100%;
width: calc(100% / 5);
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
float: left;
text-align: center;
line-height: 50px;
}
#menu ul li a {
display: block;
text-decoration: none;
font-family: arial;
}
#menu ul li a:hover,
#menu ul li a.active {
background: #f0f0f0;
}
#sections {
display: block;
position: relative;
top: 50px;
}
section {
display: block;
height: 500px;
width: 100%;
background: #67D182;
padding: 45px 50px;
box-sizing: border-box;
}
#sections section:nth-child(even) {
background: #fff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<nav id="menu">
<ul>
<li>Top</li>
<li>About</li>
<li>Portfolio</li>
<li>Contact</li>
<li>Blog</li>
</ul>
</nav>
<div id="sections">
<section id="top">
<h2>#top</h2>
</section>
<section id="about">
<h2>#about</h2>
</section>
<section id="portfolio">
<h2>#portfolio</h2>
</section>
<section id="contact">
<h2>#contact</h2>
</section>
<section id="blog">
<h2>#blog</h2>
</section>
</div>
Any help would be greatly appreciated. :)
This happen because preventDefault is a function, then you only need to change:
event.preventDefault;
To:
event.preventDefault();
And this work fine.
FIDDLE: https://jsfiddle.net/lmgonzalves/ve5qr3bL/2/
EDIT:
You need to unbind the scroll event, and then bind it again when the animation be completed.
$("#menu ul li a").on("click", function(event) {
event.preventDefault();
$(document).off("scroll"); // here unbind
// code
$("html, body").stop().animate({
"scrollTop": buffer
}, 400, "swing", function() {
$(document).on("scroll", scroll); // here bind again
});
});
FIDDLE: https://jsfiddle.net/lmgonzalves/ve5qr3bL/3/

Jquery script for submenu, how can i fix it?

This code works great if i want to make toggle effect by clicking on the menu. You can see the result here.
$(document).ready(function () {
var $menu2 = $("#menu-2");
var $links = $('#menu-menu-1 .menu-item a').click(function () {
var submenu = $(this).next();
$subs.not(submenu).hide();
var isVisible = submenu.stop(true, true).is(':visible');
$menu2.stop(true, true);
if (isVisible) {
submenu.hide(500);
$menu2.slideUp(300);
} else {
$menu2.slideUp(300, function () {
$menu2.slideDown(300);
submenu.show(500);
});
}
});
var $subs = $links.next();
});
My problem is .menu-item a, with this the script executes the code for all links of the menu(also for links that don't have submenu links). But in my case i want to execute only for the links that contains submenu links.
If i try t replace .menu-item a with .menu > ul li a doesn't work.
The structure of the html(generated by php) code is like this:
<ul id="menu-menu-1">
<li class="menu">
<a>News</a> //first main menu
<ul class="sub-menu" style="display: none;"> //second grey menu
<li>
<a>Mondo</a>
<a>News Live</a>
<a>Quotidiani Cartacei</a>
</li>
</li>
</ul>
The relevant css:
.menu {
background-color: #F6F6EE;
border-radius: 1px;
height: 30px;
padding: 10px 10px 0 5px;
width: 100%;
}
.menu li a {
color: #716B6B;
display: block;
float: left;
font-size: 14px;
padding: 2px 17px;
text-decoration: none;
width: 100%;
}
#menu-2 {
background-color: #DCDCD5;
border-radius: 1px;
display: none;
height: 33px;
position: relative;
width: 100%;
z-index: -1;
}
How can i fix it?
You need to check, on the top of the function, if the current link has a submenu attached. Check the first two lines of the click callback:
$(document).ready(function () {
var $menu2 = $("#menu-2");
var $links = $('#menu-menu-1 .menu-item a').click(function () {
var submenu = $(this).next(".sub-menu");
var hasSubmenu = submenu.length >= 1;
if(hasSubmenu) {
handleLinkWithSubmenu.call(this, submenu);
} else {
handleLinkWithoutSubmenu.call(this);
}
});
var $subs = $links.next();
function handleLinkWithSubmenu(submenu) {
$subs.not(submenu).hide();
var isVisible = submenu.stop(true, true).is(':visible');
$menu2.stop(true, true);
if (isVisible) {
submenu.hide(500);
$menu2.slideUp(300);
} else {
$menu2.slideUp(300, function () {
$menu2.slideDown(300);
submenu.show(500);
});
}
}
function handleLinkWithoutSubmenu() {
$subs.hide();
}
});
If I understand your question correctly (only execute if selector has children), then replace with this:
var $links = $('#menu-menu-1 .menu-item a').click(function (e) {
if($links.children().length > 0){
e.preventDefault();
var submenu = $(this).next();
$subs.not(submenu).hide();
var isVisible = submenu.stop(true, true).is(':visible');
$menu2.stop(true, true);
if (isVisible) {
submenu.hide(500);
$menu2.slideUp(300);
} else {
$menu2.slideUp(300, function () {
$menu2.slideDown(300);
submenu.show(500);
});
}
}
});
I think something like this will work for what you need, it's a lot cleaner and more compact:
UPDATED:
http://jsfiddle.net/FZHC8/2/
<head>
<script src="jquery-1.10.2.min.js"></script>
<style>
.menu {
background-color: #F6F6EE;
border-radius: 1px;
height: 30px;
padding: 10px 10px 0 5px;
width: 100%;
}
a {
color: #716B6B;
display: block;
float: left;
font-size: 14px;
padding: 2px 17px;
width: 100%;
cursor:pointer;
}
#menu-2 {
background-color: #DCDCD5;
border-radius: 1px;
height: 33px;
position: relative;
width: 100%;
z-index: -1;
}
li{
list-style:none;
}
</style>
</head>
<body>
<ul id="menu-menu-1">
<li class="menu">
<a>News</a>
<ul class="sub-menu">
<li>
<a>Mondo</a>
<a>News Live</a>
<a>Quotidiani Cartacei</a>
</li>
</ul>
<a>News1</a>
<ul class="sub-menu">
<li>
<a>Mondo1</a>
<a>News Live1</a>
<a>Quotidiani Cartacei1</a>
</li>
</ul>
</li>
</ul>
<script>
(function () {
$("li > ul").hide();
$(".menu > a").click(function (e) {
$(this).parent().children(".sub-menu").slideUp(200);
$(this).next(".sub-menu").slideDown(200);
})
})();
</script>
</body>

Categories

Resources