Javascript - Efficiency for scroll event, run code only once - javascript

These if statements are always making me dizzy. If have a page with a header, and a hidden header above that in the markup.
<div id="headerfloat" style="display:none;">
<p>Floated header</p>
</div>
<div id="header">
<p>Header</p>
</div>
The idea is that whenever the page is scrolled down more than 225px, the #headerfloat should appear, and dissapear when topScroll is less than 225px. And I managed to get this working with javascript and jQuery, but when I test it on iPhone, it's very sluggish. And I'm pretty sure it's because the code is run at each scroll event. And even if #headerfloat is visible, the code still executes. Even though it doesn't have to at that point.
So, I need to make sure the code only run once, when it's needed. My first thought was to add and remove classes like .open and .closed to #headerfloat. And run if statements on those during the scroll event. But is that the most efficient way of doing it?
My so far, ugly snippet:
jQuery(window).scroll(function () {
var headerfloat = $("#header_float");
var top = jQuery(window).scrollTop();
if (top > 225) // height of float header
{
if (!$(headerfloat).hasClass("closed")) {
$(headerfloat).addClass("boxshadow", "", 100, "easeInOutQuad").slideDown().addClass("open").removeClass("closed");
}
} else {
$(headerfloat).removeClass("boxshadow", "", 100, "easeInOutQuad").removeClass("closed").slideUp();
}
});
Edit: So after laconbass's awesome response, this is the code I ended up with:
var mainHeader = $('#header')
, top_limit = mainHeader.outerHeight()
, $window = $(window)
;
var header_float = $('#header_float')
bindEvent();
function bindEvent() {
$window.scroll( scrollEvent );
}
function scrollEvent() {
var top = $window.scrollTop();
// avoid any logic if nothing must be done
if ( top < top_limit && !header_float.is(':visible')
|| top > top_limit && header_float.is(':visible')
) return;
// unbind the scroll event to avoid its execution
// until slide animation is complete
$window.unbind( 'scroll' );
// show/hide the header
if ( top > top_limit ) {
header_float.slideDown( 400, bindEvent );
} else {
header_float.slideUp( 400, bindEvent );
}
};

The snippet you started from seems a bit ugly.
I've made one on jsfiddle for your pleasure and reference
I've assumed the following:
you want a fixed positioned header when the page scrolls down (aka fixed header).
fixed headed is a clone of the page main header, with the class fixed.
fixed header is shown when the page scrolls down more than the header height.
fixed header is hidden when the page scrolls up enough to show the main page header.
Performance tips:
cache the jQuery objects to avoid making a new query each time the event handler is executed.
unbind the event handler before the show/hide animations, rebind it after.
on the event handler, return as soon as posible to avoid unnecesary logic. Remember while JavaScript is executed the browser render process is blocked.
var mainHeader = $('header')
, header = mainHeader.clone().addClass('fixed').appendTo('body')
, top_limit = header.outerHeight()
;
bindEvents();
function bindEvents() {
$(window).scroll( scrollEvent );
}
function scrollEvent() {
var top = $(window).scrollTop();
// avoid any logic if nothing must be done
if ( top < top_limit && !header.is(':visible')
|| top > top_limit && header.is(':visible')
) return;
// unbind the scroll event to avoid its execution
// until slide animation is complete
$(window).unbind( 'scroll' );
// show/hide the header
if ( top > top_limit ) {
header.slideDown( 400, bindEvents );
} else {
header.slideUp( 400, bindEvents );
}
};
<header>
<h1>Awesome header</h1>
</header>
<div>
<!-- the page content -->
</div>
/* the real code needed */
header.fixed {
display: none;
position: fixed;
top: 0;
}

two way:
1,on scroll,and if you have done your want, remove the scroll event.
2,var a variable,default is false, on scroll, if the variable is false,to do you want,and set the variable to true; if the variable is true already, do nothing(or others you want)

Related

Why does my scroll function trigger on page load?

I'm trying to create a simple scroll effect where the page header hides when the page scrolls down and reappears on scroll up. The HTML:
<header class="siteHeader">...</header>
...is hidden by applying the CSS class "siteHeader--up."
I'm using jQuery. Here is my code:
$(function () {
var $siteHeader = $('.siteHeader');
var $window = $(window);
// to determine scroll direction. initializes to 0 on page load
var scrollReference = 0;
function fixedHeader () {
var scrollPosition = $window.scrollTop();
// if page is scrolling down, apply the CSS class
if (scrollPosition > scrollReference)
{
$siteHeader.addClass('siteHeader--up');
}
// otherwise, page is scrolling up. Remove the class
else
{
$siteHeader.removeClass('siteHeader--up');
}
// update reference point to equal where user stopped scrolling
scrollReference = scrollPosition
}
$window.scroll(function () {
fixedHeader();
});
});
This works fine for the most part. The problem is when I scroll down the page and then refresh the page. Somehow the scroll function is being triggered. The header will be visible for a moment and then hide (as though the page thinks it's being scrolled down). The function is being triggered on page load (confirmed with a console.log), but I don't understand why, because it's only supposed to fire on scroll.
Can someone help me understand what's going on and how I can prevent it?
Thanks!
That is the expected behavior. When the page is refreshed, the browser remembers the scroll position and it scrolls the page to that position, later on the scroll event is fired.
I think that this could be a workaround to solve your problem:
When the jQuery scroll event is fired you can get the timeStamp property and if this timeStamp is very close to the window.onload timeStamp, surely it can't be an event triggered by the user:
I've used a value of 50 milliseconds, test if it is sufficient, I think that it is.
var startTime = false;
$(function () {
var $siteHeader = $('.siteHeader');
var $window = $(window);
// to determine scroll direction. initializes to 0 on page load
var scrollReference = 0;
function fixedHeader () {
var scrollPosition = $window.scrollTop();
// if page is scrolling down, apply the CSS class
if (scrollPosition > scrollReference)
{
$siteHeader.addClass('siteHeader--up');
}
// otherwise, page is scrolling up. Remove the class
else
{
$siteHeader.removeClass('siteHeader--up');
}
// update reference point to equal where user stopped scrolling
scrollReference = scrollPosition
}
$window.on("load", function (evt) {
startTime = evt.timeStamp;
});
$window.on("scroll", function (evt) {
if(!startTime || evt.timeStamp - startTime < 50) return;
fixedHeader();
});
});
Try Loading the function on window load as well as in the scroll function:
$window.load(function(){
fixedHeader();
});
Or on document ready maybe:
$(document).ready(function () {
fixedHeader();
});
This should trigger and reset the values in the Variables you made and therefore determine whether to set the header to fixed or not, regardless of the scroll position.
Let me know if it works because i'm kinda curious too :)

scroll to a div when scrolling, and scroll to top when div disappear

I have 2 divs on my webpage. first div is "#pattern" (red one), and second on is "#projets".(blue one)
when use scrolls for the first time, the window scrolls automaticaly to the the second div "#projets". I'm using jquery scroll-To plugin.
it works nice, even if when the users scroll with a large amount of scroll there could be on offset from the "#projets" div... If someone has an idea to correct this would be nice, but that's not my main trouble...
Now i'm trying to scroll back to the top of the page ("#pattern" div) as soon as "#pattern" div reappears when scrolling, the red one. so basically it should be as soon as the offset from the top of my screen of my div "#projets" is supperior to 1.
I've tried so many solutions without results, using flags, multiple conditions... it can be the same kind of thing as on this page, but user should be abble to scroll freely inside the page, not scrolling from hash to hash :
http://www.thepetedesign.com/demos/onepage_scroll_demo.html
here is my html :
<div id="pattern"></div>
<div id="projets"></div>
my css :
#pattern {
height:300px;
width: 100%;
background-color:red
}
#projets {
height:800px;
width: 100%;
background-color:blue
}
and my jquery :
var flag=0 ;
$(window).on('scroll',function(){
var top_projets_position = $("#projets").offset().top - $(window).scrollTop();
if((flag==0) && $(window).scrollTop()>1){
$(window).scrollTo('#projets', 500);
flag=1;
}
if($(window).scrollTop()==0){
flag=0;
}
});
here is jsfiddle :
http://jsfiddle.net/jdf9q0sv/
hope someone can help me with this, I can't figure out what I'm doing wrong, maybe a wrong method ! thanks
It looks like you need to track 3 things:
The scroll direction occurs.
The area you are currently viewing.
If scroll animation is currently happening (we need to wait until it's done, or problems will occur).
http://jsfiddle.net/vx69t5Lt/
var prev_scroll = 0; // <-- to determine direction of scrolling
var current_view ="#pattern"; // <-- to determine what element we are viewing
var allowed = true; // <-- to prevent scrolling confusion during animation
var top_projets_position = $("#projets").offset().top + 1;
$(window).on('scroll',function(){
var current_scroll = $(window).scrollTop();
if(current_scroll < top_projets_position && current_view=="#projets" && current_scroll < prev_scroll){
scrollToTarget("#pattern");
}
if($(window).height() + current_scroll > top_projets_position && current_view=="#pattern" && current_scroll > prev_scroll){
scrollToTarget("#projets");
}
prev_scroll = current_scroll;
});
function scrollToTarget(selector){
if(allowed){
allowed = false;
$(window).scrollTo(selector, {
'duration':500,
'onAfter': function(){ allowed = true; current_view = selector;}
});
}
}
This is just a quick solution based on your original code. A better solution would be to do something more Object Oriented (OOP) and track values in an object. Perhaps take an array of elements on object creation, grab all the boundaries and use the boundaries in your scroll handler to determine when to scroll to the next div.

Javascript vertical scrolling function

I am trying to detect a scroll on my page using JavaScript. So that I can change classes and attributes of some elements when user has scrolled certain amount of page. This is my JS function:
function detectScroll() {
var header = document.querySelector(".headerOrig"),
header_height = getComputedStyle(header).height.split('px')[0],
fix_class = "changeColor";
if( window.pageYOffset > header_height ) {
header.classList.add(fix_class);
}
if( window.pageYOffset < header_height ) {
header.classList.remove(fix_class);
}
var change = window.setInterval(detectScroll, 5000);
}
and I am calling it when the page is loaded:
<body onload="detectScroll();">
However, I have this problem - I need to set up a really small interval so that the function gets called and the class is changed immediately. BUT then the page freezes and everything except the JS function works very slowly.
Is there any better way of achieving this in JavaScript?
Thanks for any advice/suggestion.
You are going to want to change a couple things. First, we can use onscroll instead of an interval. But you are also going to want to cache as much as possible to reduce the amount of calculations on your scroll. Even further, you should use requestAnimationFrame (or simply "debounce" in general for older browsers -- see the link). This ensures your work only happens when the browser is planning on repainting. For instance, while the user scrolls the actual scroll event may fire dozens of times but the page only repaints once. You only care about that single repaint and if we can avoid doing work for the other X times it will be all the more smoother:
// Get our header and its height and store them once
// (This assumes height is not changing with the class change).
var header = document.querySelector(".headerOrig");
var header_height = getComputedStyle(header).height.split('px')[0];
var fix_class = "changeColor";
// This is a simple boolean we will use to determine if we are
// waiting to check or not (in between animation frames).
var waitingtoCheck = false;
function checkHeaderHeight() {
if (window.pageYOffset > header_height) {
header.classList.add(fix_class);
}
if (window.pageYOffset < header_height) {
header.classList.remove(fix_class);
}
// Set waitingtoCheck to false so we will request again
// on the next scroll event.
waitingtoCheck = false;
}
function onWindowScroll() {
// If we aren't currently waiting to check on the next
// animation frame, then let's request it.
if (waitingtoCheck === false) {
waitingtoCheck = true;
window.requestAnimationFrame(checkHeaderHeight);
}
}
// Add the window scroll listener
window.addEventListener("scroll", onWindowScroll);
use onscroll instead of onload so you don't need to call the function with an interval.
Your dedectScroll function will be triggered automatically when any scroll appers if you use onscroll
<body onscroll="detectScroll();">
Your function is adding an interval recursively, you should add an event listener to the scroll event this way :
function detectScroll() {
var header = document.querySelector(".headerOrig"),
header_height = getComputedStyle(header).height.split('px')[0],
fix_class = "changeColor";
if( window.pageYOffset > header_height ) {
header.classList.add(fix_class);
}
if( window.pageYOffset < header_height ) {
header.classList.remove(fix_class);
}
}
window.addEventListener("scroll",detectScroll);

jQuery - on window scroll run a function without any delay

I have an issue with a jquery function. You can see a working demo here - http://dev.sreejesh.in/menuissue/ . As you can see when the user scrolls down to the page, I have written a jQuery function(which will triger on scroll) to check scroll pixel. When the browser scrolls to a certain pixel(height of the sidemenu block), the Menu block will stay fixed & rest of the content scrolls as normal.
The functionality is working now, however the problem is menublocks makes a jumps when this function runs. I think this is because of the delay in running the function. Hope you guys have any nice trick to fix this.
I used an if/else function to check the scroll pixel, so when the scrolled pixel is greater than menublock height it will add a class "fixed" .
I use the following code.
HTML
<div id="globalwrapper">
<div id="menubar">
---- Menu List items-----
</div>
<div id="mainblock">
----Main content area----
</div>
</div>
jQuery
$(document).ready(function(){
$(window).scroll(function() {
adjustScroll();
});
});
function adjustScroll(){
var windowHeight = $(window).height();
var menublockHeight = $('#menubar').height();
var scrollValue = $(document).scrollTop();
var posValue = menublockHeight - windowHeight;
var menuStatus = $('#menubar').css('left');
$('#menubar').css('minHeight', windowHeight);
$('#menubar').css('height', menublockHeight);
console.log(menuStatus);
$(document).scroll(function() {
if(menuStatus == '0px') {
if(scrollValue > posValue){
$('#menubar').addClass('fixed');
$('#menubar').css('marginTop', -posValue);
}else {
$('#menubar').removeClass('fixed');
$('#menubar').css('marginTop', '0px');
}
}
});
}
I think only CSS can solve this issue, add this style:
#menubar{
position: fixed;
}
just test on Google Chrome,you can have a try.

Integrating a Java based page key scrolling control in a page

I've created a site with a fixed header. I've discovered this causes one issue
when someone hits the page down/up key, the length of that scroll is too long
due to it not remving the height of the header (and a very small bit of padding below it) from the scroll length. So (for example), if you're at the beginning of the page and hit "page down", you'd have to manually scroll back up a bit to match where you previously left off and not miss any content.
I found what I thought was the solution to this problem in this Java based page
scroll control:
https://stackoverflow.com/a/6395433/1858759
http://jsfiddle.net/bpMCE/
It worked well enough in the demo page. However, no matter what I do (which given my beginner skill level in this sort of thing), I can't get it to control my pages. I had one other person take a look at it and offer suggestions, but none of them solved the problem. One thing he did do was adjust the "bar" content vs the original Javascript code. I've pasted this revised code below, to compare to the original linked above.
My pages with actual content are not hosted yet. A friend has hosted a "dummy" page I made with generic content but the same code as some of my other pages (I'm not quite ready to have the content public). Here's the link:
http://www.11fifty.com/Site_108/before.html
I'm totally stumped with this. I've found some great advice here from reading the archives as needed, so I hope someone can make sense of this. In addition, I hope it will help others that may want to correct for this in their own fixed header sites.
Thanks in advance...
(function(){
var content, header
function adjustScroll(event) {
var e, key, remainingSpace;
content = content || document.getElementById('content');
header = header || document.getElementById('header');
e = event || window.event;
key = e.which || e.keyCode;
if ( key === 33 ) { // Page up
remainingSpace = content.scrollHeight - content.scrollTop;
setTimeout(function () {
content.scrollTop = (remainingSpace >= content.scrollHeight -
header.offsetHeight) ? 0 : (content.scrollTop + header.offsetHeight);
}, 10);
}
if ( key === 34 ) { // Page down
remainingSpace = content.scrollHeight - content.scrollTop -
content.offsetHeight;
setTimeout(function () {
content.scrollTop = (remainingSpace <= header.offsetHeight) ?
content.scrollHeight : (content.scrollTop - header.offsetHeight);
}, 10);
}
}
document.onkeydown = adjustScroll;
}());
What you need to do is to add a class tag (let's name it class="new") to every item on your list and handle keypress events to scroll to next item or the previous one, this code may help you :
function scrollToNew () {
scrollTop = $(window).scrollTop();
$('.new').each(function(i, h2){ // loop through article headings
h2top = $(h2).offset().top; // get article heading top
if (scrollTop < h2top) { // compare if document is below heading
$.scrollTo(h2, 800); // scroll to in .8 of a second
return false; // exit function
}
});
}
jQuery(function () {
$("#next").click(scrollToNew);
$(document).keydown(function (evt) {
if (evt.keyCode == 40) { // down arrow
evt.preventDefault(); // prevents the usual scrolling behaviour
scrollToNew(); // scroll to the next new heading instead
} else if (evt.keyCode == 38) { // up arrow
evt.preventDefault();
scrollToLast();
}
}
});
More details : https://stackoverflow.com/a/2168876/2310699
In your sample page the class of the header is header not the id, therefore this is not working:
document.getElementById('header')
This goes for the content too. Change these 2 rows:
<div class="container">
<div class="header">
to
<div id="container">
<div id="header">
Why you are at it add a semi-colon to the end of this row:
var content, header
If this is not the case, then create your own jsfiddle to show your exact code.

Categories

Resources