Sticky nav bar with native JavaScript only - javascript

How would I make a sticky nav bar without using jQuery?
I want to do something like:
if (scrollheight == x)
navbar.addclass "sticky"

I have recently worked on sticky sidebar nav using EmberJS.
Anyway at the end I decided to extract the solution into vanilla javascript based pen as it seems sticky navigation is pretty common feature.
You can see it here: http://codepen.io/moubi/pen/ALpmwy
Github project: https://github.com/moubi/sticky-nav
Here is the js code I am using for the functionality:
// https://github.com/moubi/sticky-nav
const NAV_OFFSET = 30;
class StickyNav {
constructor() {
this.el = null;
this.anchor = null;
this.removedScrollClass = '';
this.events();
}
didRender() {
this.el = document.getElementsByTagName('nav')[0];
this.anchor = document.getElementsByClassName('nav-anchor')[0];
this.removedScrollClass = this.el.className;
this.onResize();
}
onResize() {
var { paddingLeft, paddingRight } = window.getComputedStyle(this.el.parentNode),
parentWidth = this.el.parentNode.offsetWidth - parseInt(paddingLeft) - parseInt(paddingRight);
this.el.style.width = `${parentWidth}px`;
}
onScroll() {
var scroll = Math.max(document.documentElement.scrollTop, document.body.scrollTop),
topOffset = this.anchor.offsetTop - NAV_OFFSET;
if (this.el.className.indexOf('scroll') != -1) {
if (scroll <= topOffset) {
this.el.className = this.removedScrollClass;
}
} else if (scroll >= topOffset) {
this.el.className += ' scroll';
}
}
events() {
window.addEventListener('load', () => { this.didRender(); });
window.addEventListener('scroll', () => { this.onScroll(); });
window.addEventListener('resize', () => { this.onResize(); });
}
}
new StickyNav();
This solution should work for top nav bar as well with some minor adjustments.
In the code I am actually adding scroll class, but in your case it should be sticky - at the end "sticky" sounds more reasonable.

Related

adding class to position:sticky but is adding class to every sticky element

Added this code to my sticky element using position:sticky
var $sticky = $('.grid-container .sticky'),
$stickyTo = $('.grid-container .stickyTo'),
stickyToTop = $stickyTo.offset().top,
stickyToBottom = stickyToTop + $stickyTo.outerHeight();
$(window).on('scroll', function() {
var scroll = $(window).scrollTop(),
stickyTop = $sticky.offset().top,
stickyBottom = $sticky.offset().top + $sticky.outerHeight();
if (stickyBottom >= stickyToBottom) {
if (scroll < stickyTop) {
//$sticky.addClass('fixed').removeClass('abs');
} else {
//$sticky.addClass('abs');
}
} else if (scroll > stickyToTop) {
$sticky.addClass('stuck');
} else if (scroll < stickyToTop) {
$sticky.removeClass('stuck');
}
});
and I have two sticky elements see image attached but when I scroll to the first section it adds a class "stuck" on the second section, how can I make that only on that section jquery is triggered and not affect the second/other section unless scrolled on
Here is My FIDDLE
Because $sticky in your case is a jQuery collection with two elements in it you have to use .each() loop to check each element separately. Then you probably don't need $stickyTo, because you have to check for each 'sticky' element's own .parent(). Something like this:
var $sticky = $('.grid-container .sticky'),
$(window).on('scroll', function() {
var scroll = $(window).scrollTop()
$sticky.each(function(){
const $this = $(this)
const stickyTop = $this.offset().top,
const stickyBottom = stickyTop + $sticky.outerHeight();
const $stickyTo = $this.parent()
const stickyToTop = $stickyTo.offset().top,
const stickyToBottom = stickyToTop + $stickyTo.outerHeight();
if (stickyBottom >= stickyToBottom) {
if (scroll < stickyTop) {
//$sticky.addClass('fixed').removeClass('abs');
} else {
//$sticky.addClass('abs');
}
} else if (scroll > stickyToTop) {
$sticky.addClass('stuck');
} else if (scroll < stickyToTop) {
$sticky.removeClass('stuck');
}
})
});
Please note though, this function is not really optimized. It would be better to use native javascript during the scroll. Or even better check native CSS position: sticky.

Why isn't my scrolling position working in react?

I'm trying to make a sticky nav with an active state while scrolling. So when you're scrolling over each section, the nav has an active state. Kind of like what is seen here:
https://codepen.io/rishabhp/pen/aNXVbQ
The problem I'm having is some of my numbers aren't correct. Here's the code:
handleScroll = () => {
let sections = document.querySelectorAll('.deal-details__container'),
nav = document.querySelectorAll('.overview-nav'),
navHeight = nav[0].clientHeight;
let totalScroll = document.body.getBoundingClientRect().top;
window.addEventListener('scroll', () => {
sections.forEach(section => {
let topOffset = section.getBoundingClientRect().top;
let top = topOffset - navHeight,
bottom = top + section.clientHeight;
if (totalScroll >= top && totalScroll <= bottom) {
this.setState({ activeSection: true });
} else {
this.setState({ activeSection: false });
}
});
});
console.log(totalScroll);
};
For instance my totalScroll inside of the forEach only amounts to -20 or something similar.
I feel like I'm missing something simple. Any thoughts?

How to remove jank when setting an element to a fixed position using JavaScript

I have a webpage that when scrolled down, the text freezes when it reaches the last paragraph of text but the images keep on scrolling. I've got the implementation working but there is a lot of jank when scrolling with a mouse wheel, not so much if I click and drag the scroll bar.
Are there any optimizations I can make to this code to make work as intended or is there a different way to accomplish the same task?
window.addEventListener('scroll', function (e) {
window.requestAnimationFrame(keepTextStationary);
//keepTextStationary(); // Less janky, but still horrible
});
function keepTextStationary() {
var textRect = writtenContent.getBoundingClientRect();
var imageRec = images.getBoundingClientRect();
if (textRect.bottom < window.innerHeight && document.documentElement.scrollTop > 0) {
writtenContent.style.position = 'relative';
writtenContent.style.bottom = (225 - document.documentElement.scrollTop) + 'px';
if (imagesTop === undefined) {
imagesTop = imageRec.y;
}
} else {
writtenContent.style.bottom = (225 - document.documentElement.scrollTop) + 'px';
}
if (imageRec.y >= imagesTop) {
writtenContent.style.position = '';
}
}
Here is the site so you can see the problem.
https://bowerbankninow.azurewebsites.net/exhibitions/oscar-perry-the-pheasant
You are causing layout trashing every time you call getBoundingClientRect. Try debouncing your scroll events:
var lastScrollY = 0;
var ticking = false;
function keepTextStationary() {
var textRect = writtenContent.getBoundingClientRect();
var imageRec = images.getBoundingClientRect();
if (textRect.bottom < window.innerHeight && lastScrollY > 0) {
writtenContent.style.position = 'relative';
writtenContent.style.bottom = (225 - lastScrollY) + 'px';
if (imagesTop === undefined) {
imagesTop = imageRec.y;
}
} else {
writtenContent.style.bottom = (225 - lastScrollY) + 'px';
}
if (imageRec.y >= imagesTop) {
writtenContent.style.position = '';
}
ticking = false;
}
function onScroll() {
lastScrollY = document.documentElement.scrollTop;
requestTick();
}
function requestTick() {
if (!ticking) {
requestAnimationFrame(keepTextStationary);
ticking = true;
}
}
window.addEventListener('scroll', onScroll );
See this article for in-depth explanation: https://www.html5rocks.com/en/tutorials/speed/animations/
You dont.
Relocations / styling in javascript take place after the CSS has been loaded. Bad practise. What you can do, is make it animated to make it look less horrible.
Why is pure CSS not an option ?

jQuery - sticky element is overlapping the footer, how to avoid it?

I have a sticky sidebar working on my project, but when you go to the bottom of the page, the sticky sidebar is overlapping my footer.
What I want is that when the sticky element reach the footer, then stop just right there so the user can see the entire footer.
here is a demonstration of what I have so far.
or a jsfiddle in case it is easier for you
this is the code:
var stickySidebar = $('.sticky');
if (stickySidebar.length > 0) {
var stickyHeight = stickySidebar.height(),
sidebarTop = stickySidebar.offset().top;
}
// on scroll move the sidebar
$(window).scroll(function () {
if (stickySidebar.length > 0) {
var scrollTop = $(window).scrollTop() + 70;
if (sidebarTop < scrollTop) {
stickySidebar.stop(true, false).animate({top: scrollTop - sidebarTop});
// stop the sticky sidebar at the footer to avoid overlapping
var sidebarBottom = stickySidebar.offset().top + stickyHeight,
stickyStop = $('.main-content').offset().top + $('.main-content').height();
if (stickyStop < sidebarBottom) {
var stopPosition = $('.main-content').height() - stickyHeight;
stickySidebar.stop(true, true).animate({top: stopPosition});
}
}
else {
stickySidebar.stop().animate({top: 0});
}
}
});
$(window).resize(function () {
if (stickySidebar.length > 0) {
stickyHeight = stickySidebar.height();
}
});
This is maybe not perfect, but I think it gives you the right idea, how to solve this problem. You just have to check, if the bottom of the sidebar is below the top position of the footer. Than stop the animation.
http://jsfiddle.net/hdj99b21/1/
[...]
var stickyTopPos = stickySidebar.offset().top;
var stickyBottomPos = stickyHeight + stickyTopPos;
var footerTopPos = $('footer').offset().top;
if(stickyBottomPos >= footerTopPos) {
var stopPosition = footerTopPos - stickyHeight;
stickySidebar.stop(true, true).css({top: stopPosition});
}
[...]

How to do scrollTop and toggleClass with Javascript and not jQuery?

In jQuery you can use method toggleClass and scrollTop but I´m trying to figure out how to do this with javascript.
I have a fixed <header> menu that I´m resizing when you scroll the window. So I want to add class="small" to the header with the help of javascript.
I have figured out the jQuery code:
$(document).on("scroll", function () {
$("header").toggleClass("small", $(document).scrollTop() > 100);
});
But I want to write this in javascript.
Below is what I´ve tried with so far but I´m stuck, any help appriciated:
function toggleMenu() {
var body = document.getElementsByTagName(body);
if (body > 100) {
document.getElementsByTagName('header').classList.toggle('small');
}
}
The following code will track if the user scrolls, and if they are 100px down the page, add the class small to all <header> tags.
window.onscroll = function() {
var el = document.getElementsByTagName('header');
var className = 'small';
for (var i=0; i<el.length; i++) {
if (el[i].classList) {
if (window.scrollY > 100)
el[i].classList.add(className);
else
el[i].classList.remove(className);
} else {
// IE Fix
if (window.scrollY > 100)
el[i].className += className;
else
el[i].className = str.replace(new RegExp("\\b"+className+"\\b","gi"),"");
}
}
}
Here's an example jsfiddle
If on the other hand you only have one <header> tag, you could add an id to it like so <header id='header'>, and run the following code. It will me a bit faster, but honestly you won't notice either way.
window.onscroll = function() {
var className = 'small';
var el = document.getElementById('header');
if (el.classList) {
if (window.scrollY > 100)
el.classList.add(className);
else
el.classList.remove(className);
} else {
// IE Fix
if (window.scrollY > 100)
el.className += className;
else
el.className = str.replace(new RegExp("\\b"+className+"\\b","gi"),"");
}
}
Here's an example jsfiddle
You did not mention browser compatibility so I'll assume it's IE8+
function handleScroll() {
var header = document.getElementsByTagName('header')[0],
var scrollTop = window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop;
if(scrollTop > 100) {
if(header.classList) { // Modern browsers
header.classList.add = 'small';
}
else { // IE8-
header.className = 'blop small'; // Assuming your header already has a class 'blop'
}
}
else {
if(header.classList) { // Modern browsers
header.classList.remove = 'small';
}
else { // IE8-
header.className = 'blop'; // Assuming your header already has a class 'blop'
}
}
}
window.addEventListener('scroll', handleScroll, false); // Modern browsers
window.attachEvent('onscroll', handleScroll); // IE8-
window.onscroll = handleScroll; // If the other 2 are not available which is not likely to happen if you need to be compatible with IE8+
You might want to consider adding an id to your header since you can have more than one in your page, easier to select :
<header id="blop">[...]</header>
document.getElementById('blop');

Categories

Resources