Sorry for the terrible title, but I'm not sure how else to describe what I'm trying to build. I'm using some code I found on this site, basically what I'm trying to do is build a left handed navigation menu, that highlights the appropriate section as the user scrolls to it.
$(document).ready(function() {
var topRange = 200, // measure from the top of the viewport to X pixels down
edgeMargin = 20, // margin above the top or margin from the end of the page
animationTime = 600, // time in milliseconds
contentTop = []; //array of sidebar links
$('nav ul').append('<div id="slider"></div>');
var sliderTop = $("nav ul li a").eq(0).parent().position().top;
var sliderLeft = $("nav ul li a").eq(0).parent().position().left;
var sliderHeight = $("nav ul li a").eq(0).parent().outerHeight();
$('#slider').css({
'height': sliderHeight,
'left': sliderLeft,
'top': sliderTop,
'width': '100%'
});
// Stop animated scroll if the user does something
$('html,body').bind('scroll mousedown DOMMouseScroll mousewheel keyup', function(e) {
if (e.which > 0 || e.type == 'mousedown' || e.type == 'mousewheel') {
$('html,body').stop();
}
})
// Set up content an array of locations
$('#sidebar').find('a').each(function() {
contentTop.push($($(this).attr('href')).offset().top);
})
// Animate menu scroll to content
$('#sidebar').find('a').click(function() {
var sel = this,
newTop = Math.min(contentTop[$('#sidebar a').index($(this))], $(document).height() - $(window).height()); // get content top or top position if at the document bottom
$('html,body').stop().animate({
'scrollTop': newTop
}, animationTime, function() {
window.location.hash = $(sel).attr('href');
});
return false;
})
//scroll function
function scroller() {
var winTop = $(window).scrollTop(),
bodyHt = $(document).height(),
vpHt = $(window).height() + edgeMargin; // viewport height + margin
$.each(contentTop, function(i, loc) {
if ((loc > winTop - edgeMargin && (loc < winTop + topRange || (winTop + vpHt) >= bodyHt))) {
//animate slider
x = $("#sidebar li").eq(i).position();
$("#slider").animate({
top: (x.top)
}, 100);
}
})
}
//scroll event handler
$(window).scroll(scroller)
})
I have most of it working, however when you actually click a link on the menu the animation is very slow to catch up with the actual scrolling. I understand why this is happening, because it updates the position one at a time after each section is reached, but I'm wondering if there's a way to make this animation faster, and more fluid. I've attached a fiddle with my code, thank you in advance for your help!
http://jsfiddle.net/jamesmyers/6mbmq1pe/
You will get a better slider animation effect by temporarily detaching the scroll handler and scrolling the slider directly, with the same animationTime as for the main animation.
To do this, you also need to :
namespace the scroll event .nav, to allow safe use of .off()
stop() the slider animation in the "if the user does something" block
I've also included a few efficiency savings in the way contentTop and #slider are set up but these are not actually necessary.
$(document).ready(function() {
var topRange = 200, // measure from the top of the viewport to X pixels down
edgeMargin = 20, // margin above the top or margin from the end of the page
animationTime = 600, // time in milliseconds
contentTop, //array of sidebar links
navLinkWrapper = $("nav ul li a").eq(0).parent();
var $slider = $("<div id=\"slider\" />").css({
'height': navLinkWrapper.outerHeight(),
'left': navLinkWrapper.position().left,
'top': navLinkWrapper.position().top,
'width': '100%'
}).appendTo($('nav ul'));
// Stop animated scroll if the user does something
$('html,body').on('scroll mousedown DOMMouseScroll mousewheel keyup', function(e) {
if (e.which > 0 || e.type == 'mousedown' || e.type == 'mousewheel') {
$('html,body').stop();
$slider.stop(); //<<<<<<<
}
});
// Set up content an array of locations
contentTop = $('#sidebar a').map(function() {
return $($(this).attr('href')).offset().top;
});
// Animate menu scroll to content
$('#sidebar a').on('click', function(e) {
e.preventDefault();
$(window).off('scroll.nav', scroller); //<<<<<<<
$slider.stop().animate({ //<<<<<<<
top: ($(this).closest("li").position().top) //<<<<<<<
}, animationTime); //<<<<<<<
var sel = this,
newTop = Math.min(contentTop[$('#sidebar a').index($(this))], $(document).height() - $(window).height()); // get content top or top position if at the document bottom
$('html,body').stop().animate({
'scrollTop': newTop
}, animationTime, function() {
window.location.hash = $(sel).attr('href');
$(window).on('scroll.nav', scroller); //<<<<<<<
});
});
//scroll function
function scroller() {
var winTop = $(window).scrollTop(),
bodyHt = $(document).height(),
vpHt = $(window).height() + edgeMargin; // viewport height + margin
$.each(contentTop, function(i, loc) {
if ((loc > winTop - edgeMargin && (loc < winTop + topRange || (winTop + vpHt) >= bodyHt))) {
//animate slider
$slider.animate({
top: ($("#sidebar li").eq(i).position().top)
}, 100);
}
});
}
//scroll event handler
$(window).on('scroll.nav', scroller); //<<<<<<<
});
Updated fiddle
Related
I have a navigation area that turns into a sticky header once you scroll past it and I also have a smooth scrolling function to scroll the page to specific sections when you click on different links. The problem I am having is that if you click one of the links before the navigation becomes sticky, the page scrolls too far past the section because the offset is using the initial height of the navigation rather than the sticky height. The same thing happens in the opposite direction as well, if you click on the link to the first section, it scrolls too far up because it uses the height of the sticky nav as the offset. Is there an easy way to solve for this?
$(function() {
// Smooth scrolling
$('nav li').click(function() {
var navHeight = $('nav').height();
var section = $(this).attr('class');
var target = $('#'+section);
if (target.length) {
$('html,body').animate({
scrollTop: target.offset().top - navHeight
}, 750);
return false;
}
});
// Sticky nav
var navTop = $('nav').offset().top;
var stickyNav = function() {
var scrollTop = $(window).scrollTop();
if (scrollTop > navTop) {
$('nav').addClass('sticky');
} else {
$('nav').removeClass('sticky');
}
};
stickyNav();
$(window).scroll(function() {
stickyNav();
});
});
JSFiddle example
You might be able to just add the 'sticky' class to the nav when the user clicks on an element.
$(function() {
// Smooth scrolling
$('nav li').click(function() {
var section = $(this).attr('class');
var target = $('#'+section);
if (target.length) {
if (target.offset().top > navTop){
$('nav').addClass('sticky');
}
var navHeight = $('nav').height();
$('html,body').animate({
scrollTop: target.offset().top - navHeight
}, 750);
return false;
}
});
// Sticky nav
var navTop = $('nav').offset().top;
var stickyNav = function() {
var scrollTop = $(window).scrollTop();
if (scrollTop > navTop) {
$('nav').addClass('sticky');
} else {
$('nav').removeClass('sticky');
}
};
stickyNav();
$(window).scroll(function() {
stickyNav();
});
});
I'm trying to replicate the scroll event found on http://blkboxlabs.com/, when the user scrolls it animates the screen to the next full height section, depending on if they scroll up or down.
I've got similar functionality, but it is much less smooth, and if the user scrolls enough it will skip 2 sections, as opposed to stopping at the next section.
var didScroll;
var lastScrollTop = 0;
var delta = 5;
$(window).scroll(function(event){
didScroll = true;
});
setInterval(function() {
if (didScroll) {
hasScrolled();
didScroll = false;
}
}, 800);
function hasScrolled() {
var st = $(document).scrollTop();
var winTop = $(window).scrollTop();
var winBottom = winTop + ($(window).height());
// Make sure they scroll more than delta
/*if(Math.abs(lastScrollTop - st) <= delta)
return;*/
// If they scrolled down and are past the navbar, add class .nav-up.
// This is necessary so you never see what is "behind" the navbar.
if (st > lastScrollTop){
// Scroll Down
$('.fullHeightScrollAssist').each(function(index, element) {
var elTop = $(this).offset().top;
var elBottom = elTop + $(this).outerHeight();
if(elTop > winTop && elTop < winBottom){
$('.fullHeightScrollAssist').removeClass('activeFullScreen');
$(this).addClass('activeFullScreen');
$('html,body').animate({scrollTop: elTop}, 700);
};
});
} else {
// Scroll Up
$('.fullHeightScrollAssist').each(function(index, element) {
var elTop = $(this).offset().top;
var elBottom = elTop + $(this).outerHeight();
if(elBottom > winTop && elTop < winTop){
$('.fullHeightScrollAssist').removeClass('activeFullScreen');
$(this).addClass('activeFullScreen');
$('html,body').animate({scrollTop: elTop}, 700);
};
});
}
lastScrollTop = st;
}
You can see my example at https://jsfiddle.net/raner/vhn3oskt/2/
What I'm trying to accomplish:
1. run the animate scrollTop function immediately on scroll, only once.
2. kill any further scrolling once animation starts, to keep it from skipping the next section.
For anyone else who might like to know, here's a plugin I found that did something close to what I was looking for and was a lot smoother than my initial attempt.
http://www.jqueryscript.net/demo/jQuery-Plugin-For-Smooth-Scroll-Snapping-panelSnap/demos/
I have a div that I want to keep position: fixed when scrolling between two points.
For example, it should remain fixed only between the height of it's container div
I've done the following:
$window.scroll(function(e) {
pos = $('.container-element').height();
if ($window.scrollTop() > pos) {
$(scroll-element).css({
position: 'relative',
});
} else {
$(scroll-element).css({
position: 'fixed',
});
}
});
However, this doesn't stop the scroll-element from becoming relative on reaching the end of the container-element. What should I do to achieve the intended behavior?
EDIT:
JSFiddle: http://jsfiddle.net/09760d60/
I think You should remove fixed position when $(window).scrollTop() > containerHeight-childHeight
$(document).ready(function(){
$(window).scroll(function(e) {
containerHeight = $('.container-element').height();
childHeight = $(".scroll-element").height();
if ($(window).scrollTop() > containerHeight-childHeight) {
$('.scroll-element').removeClass('fixed');
} else {
$('.scroll-element').addClass('fixed');
}
});
});
Please check updated fiddle
http://jsfiddle.net/PrashantShirke/1u991v1j/
You should check the top and bottom bounds of your container, and compare it with the top and bottom bounds of your scroll element :
$(document).ready(function(){
$(window).scroll(function(e) {
containerTop = $('.container-element').offset().top;
containerBottom = $('.container-element').height()+$('.container-element').offset().top;
scrollEl = $('.scroll-element').height();
if ($(window).scrollTop() >= containerTop && $(window).scrollTop()+scrollEl <= containerBottom) {
$('.scroll-element').css({
"top":$(window).scrollTop()+"px"
});
}
});
});
Exemple
$(window).scrollTop() < containerTop: scroll element is at top of content
$(window).scrollTop()+scrollEl > containerBottom: bottom of scroll element is at bottom of content
If scroll element has to move, adjust its top property while being absolutelly positioned by CSS.
I think it would be more robust to check the bottoms of the container and window, not the heights of the container and child.
$(document).ready(function(){
var $window = $(window);
var $container = $('.container-element');
var $scroll = $('.scroll-element');
var containerBox = $container[0].getBoundingClientRect();
$window.scroll(function(e) {
var scrollBottom = $window.scrollTop() + $window.height();
var canSeeContainerBottom = scrollBottom > containerBox.bottom;
$scroll.css('position', canSeeContainerBottom ? 'relative' : 'fixed');
});
});
http://jsfiddle.net/bryandowning/09760d60/14/
I have a div that follows the browser as it scrolls and is contained within another div.
The following function keeps my div in place and moving when the user scrolls:
$(function() {
var offset = $("#sidebar").offset();
var topPadding = 15;
$(window).scroll(function() {
if ($(window).scrollTop() > offset.top) {
$("#sidebar").stop().animate({
marginTop: $(window).scrollTop() - offset.top + topPadding
});
} else {
$("#sidebar").stop().animate({
marginTop: 0
});
};
});
});
This works pretty well, except when the browser height is less than 400px, then it gets buggy.
So I thought I'd include a simple line to get it to only run when the browser is >=400 as such:
if (window.innerHeight >= 400) {
$(function() {
var offset = $("#sidebar").offset();
var topPadding = 15;
$(window).scroll(function() {
if ($(window).scrollTop() > offset.top) {
$("#sidebar").stop().animate({
marginTop: $(window).scrollTop() - offset.top + topPadding
});
} else {
$("#sidebar").stop().animate({
marginTop: 0
});
};
});
});
}
This seems to work fine so long as the initial browser height is greater than 400px. If the browser window is resized from it's initial height, the code will still execute, which is counter to what I want.
Essentially, is there a way to dynamically track the browser size and only run this code when the height is larger than 400px and not run when less than 400px?
Thanks for any help!
Just move the check into your event handler:
$(function() {
var offset = $("#sidebar").offset();
var topPadding = 15;
$(window).scroll(function() {
if (window.innerHeight >= 400) { // <=== Moved it here
if ($(window).scrollTop() > offset.top) {
$("#sidebar").stop().animate({
marginTop: $(window).scrollTop() - offset.top + topPadding
});
} else {
$("#sidebar").stop().animate({
marginTop: 0
});
}
}
});
});
Now the check is done when you're processing the scroll event, rather than just once at the outset preventing the scroll from being hooked at all.
Separately, because you're doing this on the scroll event, which happens pretty often, I'd try to minimize the number of times you wrap or look up elements. Instead of doing it on every event, since window isn't going to change and I suspect your sidebar isn't going to change either, wrap/look them up once:
$(function() {
var offset = $("#sidebar").offset();
var topPadding = 15;
var sideBar = $("#sidebar");
var $window = $(window);
$window.scroll(function() {
if ($window.height() >= 400) { // <=== Moved it here
if ($window.scrollTop() > offset.top) {
sidebar.stop().animate({
marginTop: $window.scrollTop() - offset.top + topPadding
});
} else {
sidebar.stop().animate({
marginTop: 0
});
}
}
});
});
That also makes it reasonable to use jQuery's height function on window for cross-browser compatibility.
To track the browser width & height as it is being resized:
$(window).resize(function()
{
var viewportWidth = $(window).width();
var viewportHeight = $(window).height();
// More code here...
};
However, I think there is a better way to keep your div in place, one that does not involve Javascript. Have you tried using position:fixed?
I need to do parallax scrolling on window scroll and also on navigation click..
In navigation click page is animating on top to show target.
How to get window scroll unit on click when page is animating to top for target position.
/*with this code i am trying to get window scroll unit */
$glob(document).ready(function() {
$glob("#lookbook_navi a").bind("click",function(event){
event.preventDefault();
var target = $glob(this).attr("href");
var objWindow = $glob(window);
$window = $glob(window);
alert($window.scrollTop());
$glob("#page").animate({
"top": -($glob(target).position().top)
}, animSpeed);
});
});
/With this code i am getting unit of window scrolling../
$glob(document).ready(function(){
// Cache the Window object
$window = $glob(window);
$glob('div[data-type="lookBookTab"]').each(function(){
var $bgobj = $glob(this); // assigning the object
$glob(window).scroll(function() {
// Scroll the background at var speed
// the yPos is a negative value because we're scrolling it UP!
var yPos = -($window.scrollTop() / $bgobj.data('speed'));
// Put together our final background position
var coords = '50% '+ yPos + 'px';
// Move the background
$bgobj.css({ backgroundPosition: coords });
}); // window scroll Ends
});
Try this one
$('a[href^="#"]').on('click',function (e) {
e.preventDefault();
var target = this.hash,
$target = $(target);
$('html, body').stop().animate({
'scrollTop': $target.offset().top
}, 500, 'swing', function () {
window.location.hash = target;
});
});