I have this code and it works exactly as I want. The menu bar sits on top and recognizes the section it is on or in. You can click the links in the yellow menu to move between the sections.
Demos: http://jsfiddle.net/spadez/2atkZ/9/ and http://jsfiddle.net/spadez/2atkZ/9/embedded/result/
$(function () {
var $select = $('#select');
var $window = $(window);
var isFixed = false;
var init = $select.length ? $select.offset().top : 0;
$window.scroll(function () {
var currentScrollTop = $window.scrollTop();
if (currentScrollTop > init && isFixed === false) {
isFixed = true;
$select.css({
top: 0,
position: 'fixed'
});
$('body').css('padding-top', $select.height());
} else if (currentScrollTop <= init) {
isFixed = false;
$select.css('position', 'relative');
$('#select span').removeClass('active');
$('body').css('padding-top', 0);
}
//active state in menu
$('.section').each(function(){
var eleDistance = $(this).offset().top;
if (currentScrollTop >= eleDistance-$select.outerHeight()) {
var makeActive = $(this).attr('id');
$('#select span').removeClass('active');
$('#select span.' + makeActive).addClass('active');
}
});
});
$(".nav").click(function (e) {
var divId = $(this).data('sec');
$('body').animate({
scrollTop: $(divId).offset().top - $select.height()
}, 500);
});
});
However, the code itself gets quite laggy as soon as you start putting any content in the boxes. According to help I've received, it's that I am repeatedly changing page layout properties via the animation and querying page layout properties in the scroll handler, thus triggering a large number of forced layouts.
User Tibos said that:
You could get a big improvement by disabling the scroll handler during
the click animation and instead triggering the effects with no checks
made (set the active class on the clicked element).
Could anyone show me how I can achieve this optimization?
Demo page and another concept demo: http://codepen.io/vsync/pen/Kgcoa
The KEY here is to position your select inside another element, so when it get's FIXED to the screen, it won't affect the other elements by the sudden lack of height it once physically occupied. I've added a little CSS also. It's important to use the jQuery version 1.11 or above, because they fixed a but that caused the same class to be added again and again, regardless if an element already has it. bad for performance. Also, I've used a for loop and not jquery each loop on the sections elements, because a for loop is much faster due to the lack of function callback an each function has. Also, a very important thing is to make sure that every element that can be cached is actually cached, so we don't look for it endlessly in the DOM...
For showing which section we're on, I'm looping on all the sections, starting for the last one, which is important to, and checking if each top has passed the window's top using getBoundingClientRect method for knowing such thing. This helps knowing where we are.
var pos,
$el = $('#select'),
navItems = $el.find('.nav'),
menuHeight = $el[0].clientHeight,
scrollY,
sections = $('.section').toArray(), // cache sections elements
pointOfAttachment = $('.jumbo')[0].clientHeight - menuHeight;
// Bind events
$(window).on('scroll.nav', updateNav)
.on('resize.nav', updateNav);
function updateNav(){
scrollY = window.pageYOffset || document.documentElement.scrollTop;
for( var i = sections.length; i--; ){
if( sections[i].getBoundingClientRect().top < 0 ){
navItems.filter('.' + sections[i].id).addClass('active').siblings().removeClass('active');
break;
}
navItems.removeClass('active');
}
if( scrollY > pointOfAttachment )
$el.addClass('fixed');
else
$el.removeClass('fixed');
}
Why is this optimal?
The key for good performance is to do as minimal as possible to achieve your goal, and minimal here means accessing the DOM as little as possible and changing it even less times than accessing it. This is HIGHLY optimal code which ONLY changes the DOM when a change is required and never in any other situation.
You shouldn't need to do this on each scroll event. You could throttle the event and only run the callback something like every 20ms. The user shouldn't notice. You can do this with underscore, or write your own solution.
Another thing that would ease the lagg is to move as much out of the scroll event callback as possible. You don't need to query $('body') all the time for example, save that to a variable.
Related
I added a class to <header> via JS when the page is scrolled like this:
$(window).scroll(function(){
var top = $(window).scrollTop();
if(top>=1){
$("header").addClass('bg-header');
}
else
if($("header").hasClass('bg-header')){
$("header").removeClass('bg-header');
}
});
The issue is that let's say for example I reload from the footer section of the page (or any other section that is lower than the very top of the page), then the header looses the class, and only gets it back after I scroll down. How would I make it so that it wouldn't loose the added class after reload ?
There are two approaches: you can manually fire the scroll event on page load/DOM ready, or simply move all the logic into a function which is called by the scroll event and during page load/DOM ready.
Solution 1: Call custom function during scroll + page
$(function() {
// Define custom function that contains all the logic
var customScrollCallback = function() {
var top = $(window).scrollTop();
if (top >= 1) {
$("header").addClass('bg-header');
} else if ($("header").hasClass('bg-header')) {
$
};
};
$(window).scroll(function() {
// Call custom function during scroll
customScrollCallback();
});
// Call custom function at runtime :) (this is the trick!)
customScrollCallback();
});
Solution 2: Trigger scroll event manually (not ideal)
The reason why this solution is not ideal is because there might be other plugins/scripts on the page that are listening to the scroll event. By manually triggering it, you are subverting the default behaviour of the event (because the event is fired without any actual scrolling).
$(function() {
$(window)
.scroll(function() {
var top = $(window).scrollTop();
if (top >= 1) {
$("header").addClass('bg-header');
} else if ($("header").hasClass('bg-header')) {
$("header").removeClass('bg-header');
}
})
.trigger('scroll');
});
I'm using the $.messager from EasyUI, and everytime I click on the button the dialog displays, but the page scrolls down.
Here is the code im using :
$(function () {
var button = $('.form_button');
button.click(function(e) {
var user_id = $(this).attr('user_id');
$.messager.defaults.ok = 'Yes';
$.messager.defaults.cancel = 'No';
$.messager.confirm('Confirm','Are you sure you want to block this user?',function(r){
if (r){
alert(user_id);
}
});
e.preventDefault();
});
});
Update you can get current scroll position by $(window).scrollTop()
var currentPos = $(window).scrollTop();
$(window).animate({scrollTop:currentPos }, '500');
you should use following in your function when you click that button.
scrollTop will take your window at normal position (when the page was loaded)
$(window).animate({scrollTop:0}, '500');
This is a bug in the framework, as the demos on its home page exhibit the same behaviour. You should file a bug.
Fortunately, you can make several workarounds though. Scrolling to the top may be the easiest, but IMHO that's a UX nightmare. You should calculate the center of the current viewport, and show the window there. Maybe this can be done in CSS too, I am not sure.
I needed a jQuery function to fix my div when the page is scrolled.
I found this:
var fixed = false;
var topTrigger = $('#sticker').offset().top;
$(document).scroll(function() {
if( $(this).scrollTop() >= topTrigger ) {
if( !fixed ) {
fixed = true;
$('#sticker').css({'position':'fixed', 'top':'0'});
}
} else {
if( fixed ) {
fixed = false;
$('#sticker').css({'position':'relative'});
}
}
});
Now, since I'm not a super beginner with jQuery, I tried to skim it and understand it. The only things I don't understand are the things related to the var:fixed. I tried to delete the var and the if statement related to that and the function works perfectly.
My question : why is that variable there, what does it mean, what feature does it add to the entire function?
Why should I keep it there instead of deleting everything related to that variable?
The scroll event will be fired multiple times as the user scrolls. If you keep on changing the DOM attributes, then the performance of the site may slow down.
To avoid applying the style multiple times, they are having a flag called fixed. So once the user has scrolled a particular height, they will trigger change the DOM to be fixed. Later they need not again change the CSS style.
Only if the user scrolls back less than the threshold they need to change the style again.
I have some jquery code that is picking up some issues in firebug chrome.
any help would be great, please update fiddle.
please see the link with fiddle.
http://jsfiddle.net/jwhTd/
image
/* SHOW CATEGORIES
===================================================================*/
$('.normal-btn\.interest').click(function(e){
// Prevent the event from bubbling up the DOM tree
e.stopPropagation();
$('.categories-wrap').fadeIn(); // must be hidden, to fade in
});
$(document, '.normal-btn\.interest').click(function(){
$('.categories-wrap').fadeOut(); // must be visible, to fade out
});
var offset = $(".sticky-header").offset();
var sticky = document.getElementById("sticky-header")
var additionalPixels = 50;
$(window).scroll(function () {
if ($(window).scrollTop() > offset.top - additionalPixels) {
$('#profile-container').addClass('fixed');
} else {
$('#profile-container').removeClass('fixed');
}
});
It's telling you exactly what is wrong. offset is undefined. You probably expect that it has a value, check why it doesn't have one.
You get more errors though. Something about slider and another about an invalid .top access somewhere.
it looks like most of that code is not in the document.ready. the var offset = $(".sticky-header").offset(); needs to be executed once the dom is ready.
Your code:
var offset = $(".sticky-header").offset();
var sticky = document.getElementById("sticky-header")
var additionalPixels = 50;
The first line selects all elements with the class of sticky-header, then gets the offset of the first one. The .offset() function returns undefined in the event that the selector matches zero elements, which appears to be the case here due to the error you're getting later on.
On the next line you're selecting an element with an id of sticky-header, which makes me think that perhaps your first line should be
var offset = $('#sticky-header').offset();
instead, which uses an ID selector rather than a class one.
UPDATE:
Here is a jsbin example demonstrating the problem.
UPDATE 2:
And here is the fixed version thanks to fudgey.
Basically, I have the following javascript which scrolls the window to an anchor on the page:
// get anchors with href's that start with "#"
$("a[href^=#]").live("click", function(){
var target = $($(this).attr("href"));
// if the target exists: scroll to it...
if(target[0]){
// If the page isn't long enough to scroll to the target's position
// we want to scroll as much as we can. This part prevents a sudden
// stop when window.scrollTop reaches its maximum.
var y = Math.min(target.offset().top, $(document).height() - $(window).height());
// also, don't try to scroll to a negative value...
y=Math.max(y,0);
// OK, you can scroll now...
$("html,body").stop().animate({ "scrollTop": y }, 1000);
}
return false;
});
It works perfectly......until I manually try to scroll the window. When the scrollbar or mousewheel is scrolled I need to stop the current scroll animation...but I'm not sure how to do this.
This is probably my starting point...
$(window).scroll(e){
if(IsManuallyScrolled(e)){
$("html,body").stop();
}
}
...but I'm not sure how to code the IsManuallyScrolled function. I've checked out e (the event object) in Google Chrome's console and AFAIK there is not way to differentiate between a manual scroll and jQuery's animate() scroll.
How can I differentiate between a manual scroll and one called via jQuery's $.fn.animate function?
Try this function:
$('body,html').bind('scroll mousedown wheel DOMMouseScroll mousewheel keyup', function(e){
if ( e.which > 0 || e.type == "mousedown" || e.type == "mousewheel"){
$("html,body").stop();
}
})
Also, did you see this tutorial?
Update: Modern browsers now use "wheel" as the event, so I've included it in the code above.
I had your same issue some a few days ago.You shouldn't be using jquery's animate function if you want to obtain that result, you have to simulate the animation using a polling function.
I made this class which is supposed to provide a smooth scrolldown when ScrollDown.slow() is called.
ScrollDown.current=$(window).scrollTop();
ScrollDown.lastValue;
ScrollDown.lastType;
ScrollDown.enabled=true;
ScrollDown.custom=function(value,rate){ //let's say value==='bottom' and rate=10
if(value==='bottom'){
value=$(document).height()-$(window).height();
}
ScrollDown.current=$(window).scrollTop();
ScrollDown.lastValue=value;
(function poll(){
setTimeout(function(){
var prev=$(window).scrollTop(); //This is the critical part
/*I'm saving again the scroll position of the window, remember
10 ms have passed since the polling has started
At this rate, if the user will scroll up for down pre!==ScrollDown.current
And that means I have to stop scrolling.*/
ScrollDown.current++; //increasing the scroll variable so that it keeps scrolling
$(window).scrollTop(ScrollDown.current);
if(ScrollDown.current<ScrollDown.lastValue && ScrollDown.enabled){
//ScrollDown.current<ScrollDown.lastValue basically checks if it's reached the bottom
if(prev!==ScrollDown.current-1){
/*I'm checking if the user
scrolled up or down while the polling has been going on,
if the user scrolls up then prev<ScrollDown.current-1,
if the user scrolls down then prev>ScrollDown.current-1
and at the next poll() the scrolling will stop
because ScrollDown.enabled will bet set to false by ScrollDown.stop()*/
ScrollDown.stop();
}
poll();
}
},rate);
})();
};
ScrollDown.stop=function(){
ScrollDown.enabled=false;
};
ScrollDown.continue=function(){
ScrollDown.enabled=true;
switch (ScrollDown.lastType){
case "fast":
ScrollDown.fast(ScrollDown.lastValue);
break;
case "normal":
ScrollDown.normal(ScrollDown.lastValue);
break;
case "slow":
ScrollDown.slow(ScrollDown.lastValue);
break;
}
};
ScrollDown.fast=function(value){
if(!ScrollDown.enabled){
ScrollDown.continue();
}else{
ScrollDown.lastType='fast';
ScrollDown.custom(value,1);
}
};
ScrollDown.normal=function(value){
if(!ScrollDown.enabled){
ScrollDown.continue();
}else{
ScrollDown.lastType='normal';
ScrollDown.custom(value,10);
}
};
ScrollDown.slow=function(value){
if(!ScrollDown.enabled){
ScrollDown.continue();
}else{
ScrollDown.lastType='slow';
ScrollDown.custom(value,50);
}
};
function ScrollDown(){}
So if you were to call ScrollDown.slow('bottom') it would start scrolling slowly till it reaches the bottom of your page unless you scroll up or down manually, then it stops.
You could set a variable to indicate that your call to animate was active, then check that variable inside the scroll handler.
window.IsAutoScrolling = true;
$("html,body").stop().animate({ "scrollTop": y }, 1000);
// Do something to set IsAutoScrolling = false, when the animation is done.
$(window).scroll(e){
if(!window.IsAutoScrolling){
$("html,body").stop();
}