My main menu adds 'scroll' class to '.mainMenu' when the offset is 25px from the top. This functions fine, but when a user refreshes the page anywhere besides the top of the page, '.scroll' isn't active, so the 'scroll' styles do not apply.
Use case:
User is on top of page, scrolls down, '.scroll' class is added, turning white background to black.
User refreshes window when on the middle of the page. The main menu turns back into a white background because scroll isn't added.
I'm looking for something like: 'If mainMenu class is 25px from the very top of the page, then add class scroll to the mainMenu div'.
Demo:
$(window).scroll(function() {
var scroll = $(window).scrollTop();
if (scroll >= 10) {
$(".mainMenu").addClass("scroll");
} else {
$(".mainMenu").removeClass("scroll");
}
});
.gap{
height: 800px;
}
ul {
list-style-type: none;
display: flex;
flex-direction: row;
}
ul li{
padding: 0px 20px;
}
.mainMenu{
transition: background 0.5s;
}
.mainMenu.scroll {
background:red;
z-index: 9999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="mainMenu">
<ul>
<li>link 1</li>
<li>link 1</li>
</ul>
</div>
<div class="gap"></div>
Just use the same code you use for the example (use case 1) but with a different event.
Like so:
$(function() { // called when page is refreshed
var scroll = $(window).scrollTop();
if (scroll >= 10) {
$(".mainMenu").addClass("scroll");
} else {
$(".mainMenu").removeClass("scroll");
}
});
Related
I added a sticky header and a smooth scrolling effect, and I cannot figure out how to fix the position so it counts with the header size. The things I have tried disable the sticky header completely.
I have tried to use several different techniques, although I am a newbie and it might be too hard for me to do by myself.
<div id="container">
<section id="sectionHome">
<!--Header and Logo-->
<header id="myHeader">
<logo>
<img src="Pictures/Marvel-logo-880x660.crop.png">
</logo>
</header>
<!--The Top Navigation Menu-->
<div id="mainNav">
<ul>
<li class="current">Home</li>
<li>Characters</li>
<li>Movies</li>
<li>More Info</li>
</ul>
</div>
</section>
//Smooth Scrolling in Main Nav
$(document).ready(function() {
$('#mainNav li a').click(function(e) {
var targetHref = $(this).attr('href');
$('html, body').animate({
scrollTop: $(targetHref).offset().top
}, 1000);
e.preventDefault();
});
});
// Sticky Header
window.onscroll = function() {
myFunction()
}; // When the user scrolls the page
var header = document.getElementById("sectionHome"); // Get the header and top nav
var sticky = header.offsetTop; // Get the offset position of the navbar
function myFunction() { // Add the sticky class to the header when you reach its scroll position. Remove "sticky" when you leave the scroll position
if (window.pageYOffset > sticky) {
header.classList.add("sticky");
} else {
header.classList.remove("sticky");
}
}
This was one thing I tried, but it disabled my sticky header:
$(document).ready(function() {
var headerHeight = $('header').outerHeight(); // Target your header navigation here
$('#main-nav li a').click(function(e) {
var targetHref = $(this).attr('href');
$('html, body').animate({
scrollTop: $(targetHref).offset().top - headerHeight // Add it to the calculation here
}, 1000);
e.preventDefault();
});
});
I thought I could set a value for the total header size and position it that way, although it disables the sticky header. How do I do this properly?
This is my webpage:
http://www.student.city.ac.uk/~aczc972
Best regards,
Danielle
I have added a sandbox how to do it using jQuery, generally speaking only one addition from my site is that I am checking what is the target e.g. scroll to top page, and if yes, I am running specified code for it:
if (targetHref === "#") {
$("html, body").animate(
{ scrollTop: 0 },
"1000"
);
} else {
$("html, body").animate({scrollTop: $(targetHref).offset().top},1000);
}
codesandbox.io/s/81l87w26w0
Subtract header height scroll to prevent covering content by header
scrollTop: $(targetHref).offset().top - 180"
You can also scroll to top of the page like:
Add id="home" to body and change href in:
<li class="current">Home</li>
to home i.e.
<li class="current">Home</li>
Should work with your code
This is not necessarily the best way to do this, but it's an example which is designed to illustrate how it can be done. You don't need jQuery to achieve this effect so it's worth trying it without.
The code below fixes the header, and adjusts the padding of the main wrapper to account for the size of the header. It then sets up listeners on elements with the class section-link. For those elements, the click event will scroll to the element with the id which corresponds to the data-section attribute for the element which was clicked.
You can ignore the css for this which was only added to illustrate how this might work.
const padForHeader = () => {
// find out how high the header element is
const headerHeight = document.getElementById('top-header').clientHeight;
// how much extra padding would we like?
const headerPadding = 20;
// add the two together to see how much padding we need to add
const headerBufferSize = headerHeight + headerPadding;
// set the marginTop property so that the header doesn't overlay content
document.querySelector('.wrapper').style.marginTop = `${headerBufferSize}px`;
};
padForHeader();
// when the window resizes, re-pad for the header
window.addEventListener('resize', padForHeader);
document
.querySelectorAll('.section-link')
.forEach(element => {
// we want to scroll 'smoothly' to the element
const scrollOptions = {
behavior: "smooth"
};
// we can read the data attribute to find the matching element's id
const elementIdToScrollTo = element.dataset.section;
// we can use the id we found to get the corresponding element
const elementToScrollTo = document.getElementById(elementIdToScrollTo);
// we can set the onclick property to scroll to the element we found
element.onclick = () => elementToScrollTo.scrollIntoView(scrollOptions);
});
.header {
background-color: red;
border: solid 2px grey;
border-radius: 5px;
font-family: arial;
margin: 0 auto;
position: fixed;
top: 0;
left: 0;
right: 0;
width: 97%;
}
.header>ul {
list-style: none;
color: rgba(250, 250, 240, 0.8);
}
.header>ul>li {
cursor: pointer;
display: inline-block;
position: relative;
top: 0px;
transition: all 0.3s ease;
width: 200px;
}
.header>ul>li:hover {
color: rgba(250, 250, 240, 1);
top: -1px;
}
.section {
background-color: rgba(20, 20, 30, 0.2);
height: 80vh;
border-bottom: solid 2px black;
padding-top: 50px;
}
<div class="wrapper">
<div id="top-header" class="header sticky">
<ul>
<li class="section-link" data-section="1">Item 1</li>
<li class="section-link" data-section="2">Item 2</li>
<li class="section-link" data-section="hello-world">Item hello world</li>
</ul>
</div>
<div id="1" class="section">
Test 1
</div>
<div id="2" class="section">
Test 2
</div>
<div id="hello-world" class="section">
Test 3
</div>
</div>
This JSFiddle by Gaurav Kalyan works well in Chrome, but in Safari and Firefox it activates the wrong menu item. Instead of highlighting the menu item clicked, it highlights the menu item before. So, for example, if you click on "Punkt 4", "Punkt 3" is highlighted instead. I haven’t been able to fix this. Can someone help? I've been trying to solve this for two weeks.
HTML
<section id="main">
<div class="target" id="1">TARGET 1</div>
<div class="target" id="2">TARGET 2</div>
<div class="target" id="3">TARGET 3</div>
<div class="target" id="4">TARGET 4</div>
</section>
<aside id="nav">
<nav>
Punkt 1
Punkt 2
Punkt 3
Punkt 4
</nav>
</aside>
CSS
* {
margin: 0;
padding: 0;
}
#main {
width: 75%;
float: right;
}
#main div.target {
background: #ccc;
height: 400px;
}
#main div.target:nth-child(even) {
background: #eee;
}
#nav {
width: 25%;
position: relative;
}
#nav nav {
position: fixed;
width: 25%;
}
#nav a {
border-bottom: 1px solid #666;
color: #333;
display: block;
padding: 10px;
text-align: center;
text-decoration: none;
}
#nav a:hover, #nav a.active {
background: #666;
color: #fff;
}
JavaScript
$('#nav nav a').on('click', function(event) {
$(this).parent().find('a').removeClass('active');
$(this).addClass('active');
});
$(window).on('scroll', function() {
$('.target').each(function() {
if($(window).scrollTop() >= $(this).offset().top) {
var id = $(this).attr('id');
$('#nav nav a').removeClass('active');
$('#nav nav a[href=#'+ id +']').addClass('active');
}
});
});
This works fine as is if the viewport height (the inner height of the browser window) is <= 400px. That is because when you click on the a link in the nav element, with an href of #4, the default browser behavior kicks in and the element with id="4" is scrolled to the top (as much as is possible).
When the viewport is the same height or smaller than the element being scrolled to, then when your scroll handler gets triggered, the if($(window).scrollTop() >= $(this).offset().top) condition evaluates as true, because the scrollTop will be exactly equal to the offset().top of the #4 div.
However, when the viewport is bigger than the content div (in your case, > 400px), when the browser tries to scroll the last div into view, it can completely do so whilst still displaying part of the bottom half of the previous div. Which means that the 3rd div will pass your scroll handler if check, not your fourth. (The offset top of the last div will not be <= the scrollTop of the window).
So what's the solution?
I would make it so that each target div is at least the same height as the viewport. You can achieve this on modern browsers using min-height: 100vh; (100% of the viewport height). That means when the last one is scrolled into view, it will completely fill the viewport, and the correct div will pass your scroll logic check correctly.
See here for a working fork.
Bonus tip
There is a number of things you can do to improve performance of this code. Cache the creation of jQuery variables, avoid the repeated work happening 4 times on every scroll event (which can happen very often), etc. It works okay for now, but it may become a bottleneck later.
My problem is along the lines of these previous issues on StackOverflow but with a slight difference.
Previous issues:
Stopping fixed position scrolling at a certain point?
Sticky subnav when scrolling past, breaks on resize
I have a sub nav that starts at a certain position in the page. When the page is scrolled the sub nav needs to stop 127px from the top. Most of the solutions I have found need you to specify the 'y' position of the sub nav first. The problem with this is that my sub nav will be starting from different positions on different pages.
This is the JS code i'm currently using. This works fine for one page but not all. Plus on mobile the values would be different again.
var num = 660; //number of pixels before modifying styles
$(window).bind('scroll', function () {
if ($(window).scrollTop() > num) {
$('.menu').addClass('fixed');
} else {
$('.menu').removeClass('fixed');
}
});
I'm looking for a solution that stops the sub nav 127px from the top no matter where on the page it started from.
You can use position: sticky and set the top of the sub-nav to 127px.
See example below:
body {
margin: 0;
}
.main-nav {
width: 100%;
height: 100px;
background-color: lime;
position: sticky;
top: 0;
}
.sub-nav {
position: sticky;
width: 100%;
height: 50px;
background-color: red;
top: 100px;
}
.contents {
width: 100%;
height: 100vh;
background-color: black;
color: white;
}
.contents p {
margin: 0;
}
<nav class="main-nav">Main-nav</nav>
<div class="contents">
<p>Contents</p>
</div>
<nav class="sub-nav">Sub-nav</nav>
<div class="contents">
<p>More contents</p>
</div>
Please see browser support for sticky here
You should change your code to the below, should work fine:
$(window).bind('scroll', function () {
if ($(window).scrollTop() > $(".menu").offset().top) {
$('.menu').addClass('fixed');
} else {
$('.menu').removeClass('fixed');
}
});
Maybe you can try this:
Find navigation div (.menu)
Find the top value of the .menu (vanilla JS would be menuVar.getBoundingClientRect().top, not sure how jQuery does this).
Get top value of browserscreen.
Calculate the difference - 127px.
When the user scrolls and reaches the top value of the menu -127px -> addClass('fixed').
I use this nice little JavaScript to make my navigation bar (which is normally sitting 230px down from the top) stick to the top of the page once the page is scrolled down that 230 px. It then gives the "nav" element a "fixed" position.
$(document).ready(function() {
$(window).bind('scroll', function() {
if ($(window).scrollTop() > 230) {
$('nav').addClass('fixed');
} else {
$('nav').removeClass('fixed');
}
});
});
nav {
width: 90%;
display: flex;
justify-content: center;
max-width: 1400px;
height: 85px;
background-color: rgba(249, 241, 228, 1);
margin: auto;
border-top-left-radius: 0em;
border-top-right-radius: 0em;
border-bottom-left-radius: 2em;
border-bottom-right-radius: 2em;
}
.fixed {
position: fixed;
border-top: 0;
top: 0;
margin: auto;
left: 0;
right: 0;
z-index: 4;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
</ul>
</nav>
Now, the problem: i have positioned the corresponding anchor targets
within the page and have given them some "padding-top" to account for the fixed navbar (about 90px), so that they don't disappear behind the bar when the page jumps to them after clicking.
.anchor {
padding-top: 90px;
}
<a class="anchor" id="three">
This works fine AS LONG AS the navbar is already fixed to the top.
But if you click on a link while the navbar is still in its original mid-page position (e.g. the first click the user will do), it just disregards the offset i gave the anchor target and jumps to a weird position where the anchor target is hidden behind the navbar (and not even aligned with the top of the page)!
If i THEN click on the link again (now in the fixed bar on top of the page), it corrects itself and displays the page as i want to. But that first click always misses - i can't figure out why! Please help
EDIT: WORKING DEMO here: http://www.myway.de/husow/problem/problem.html
1st Add a new class name spacebody to your first div with class="space"
<nav>
...
</nav>
<div class="space spacebody">
</div>
2nd JS use the following should fix your problem:
$(document).ready(function() {
$(window).bind('scroll', function() {
if ($(window).scrollTop() > 230) {
$('nav').addClass('fixed');
$('.spacebody').css('margin-top', '85px');
} else {
$('nav').removeClass('fixed');
$('.spacebody').css('margin-top', '0px');
}
});
});
Reason Why?
because when your nav is not fixed, it has a height of 85px, when you scroll down it has no height which is 0 height. Then everything below move up by 85px causing your to go below the target of ONE or TWO etc. It is not you are missing the first click, it is when the nav are not fixed and the click you will be scroll more down by 85px. If you scroll to top and click you will miss again.
You can easily see this if you change your CSS for nav with background-color: transparent;
With the code above should fix it when you nav become fixed to add a margin-top as 85px to the div below so they keep the same height as you clicked.
el.scrollIntoViewIfNeeded() scrolls to el if it's not inside of the visible browser area. In general it works fine but I'm having problems with using it with a fixed header.
I made an example snippet: (The method doesn't work in Firefox, so neither does the demo) https://jsfiddle.net/ahugp8bq/1/
In the beginning all three colored divs are displayed below the fixed header. But if you click "second" and then "first", the beginning of #first will be behind the header, which I don't want.
The problem seems to be that the position of #otherContainer (its padding-top) is pretty much ignored when scrolling up.
Actually, this is quite simple if you use the consistent and supported getBoundingClientRect().top + body.scrollTop way - all you now have to do is reduce the header from it, so just get it and calculate its height.
var header = document.getElementById('container')
var clicks = document.querySelectorAll('#container li');
var content = document.querySelectorAll('#otherContainer > div');
// Turn the clicks HTML NodeList into an array so we can easily foreach
Array.prototype.slice.call(clicks).forEach(function(element, index){
element.addEventListener('click', function(e){
e.preventDefault();
// Set the scroll to the top of the element (top + scroll) minus the headers height
document.body.scrollTop = content[index].getBoundingClientRect().top + document.body.scrollTop - header.clientHeight;
});
});
#container {
position: fixed;
background: yellow;
width: 100%;
height: 50px;
}
ul li {
display: inline;
cursor: pointer;
}
#otherContainer {
padding-top: 60px
}
#first, #second, #third {
height: 500px
}
#first {
background: red
}
#second {
background: green
}
#third {
background: blue
}
<div id="container">
<ul>
<li id="jumpToFirst">first</li>
<li id="jumpToSecond">second</li>
<li id="jumpToThird">third</li>
</ul>
</div>
<div id="otherContainer">
<div id="first"></div>
<div id="second"></div>
<div id="third"></div>
</div>