I am currently on a project which use Locomotive Scroll for smooth-scrolling. All I wanted to do is to make my main navigation header menu fixed on load, then it hides when scroll down and reveal when scroll up. First I tried to create this Without Locomotive Scroll and it is working perfectly. But when I add locomotive scroll, the code is not working at all. It just view as another normal element. Not fixed nor reveal. If anyone can help me, it is highly appreciated. Thank you..!
Element Styles
#my-header {
position: fixed !important;
width: 100% !important;
z-index: 99999 !important;
}
Working code without Locomotive Scroll
;( function() {
// wait until all required libraries are available
let chck_if_gsap_loaded = setInterval( function() {
if(window.gsap && window.ScrollTrigger ) {
// register scrolTrigger
gsap.registerPlugin( ScrollTrigger );
// ... do your thing
my_stuff();
// clear interval
clearInterval( chck_if_gsap_loaded );
}
}, 500 );
function my_stuff() {
const site_header = document.querySelector('#my-header');
const my_stuff = gsap.from(site_header,{
yPercent: -100,
duration: 0.25,
ease: 'sine.out'
}).progress(1);
ScrollTrigger.create({
start: 'top top-=' + 100,
onUpdate: (self)=>{
if(self.direction === -1) my_stuff.play();
else my_stuff.reverse();
}
});
}
} )();
Not Working Code with Locomotive Scroll
;( function() {
let loco_scroll = {};
// wait until all required libraries are available
let chck_if_gsap_loaded = setInterval( function() {
if( window.sfe_loco_scroll && Object.keys( window.sfe_loco_scroll ).length !== 0 && window.gsap && window.ScrollTrigger ) {
// store to the local variable
loco_scroll = window.sfe_loco_scroll;
// register scrolTrigger
gsap.registerPlugin( ScrollTrigger );
// ... do your thing
my_stuff();
// clear interval
clearInterval( chck_if_gsap_loaded );
}
}, 500 );
function my_stuff() {
/* DON'T CHANGE THIS */
// each time Locomotive Scroll updates, tell ScrollTrigger to update too (sync positioning)
loco_scroll.on('scroll', ScrollTrigger.update);
// tell ScrollTrigger to use these proxy methods for the '.sfe-locomotive-scroll-wrapper' element since Locomotive Scroll is hijacking things
ScrollTrigger.scrollerProxy('.sfe-locomotive-scroll-wrapper', {
scrollTop(value) {
return arguments.length ? loco_scroll.scrollTo(value, 0, 0) : loco_scroll.scroll.instance.scroll.y;
}, // we don't have to define a scrollLeft because we're only scrolling vertically.
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
};
},
// LocomotiveScroll handles things completely differently on mobile devices - it doesn't even transform the container at all! So to get the correct behavior and avoid jitters, we should pin things with position: fixed on mobile. We sense it by checking to see if there's a transform applied to the container (the LocomotiveScroll-controlled element).
pinType: document.querySelector('.sfe-locomotive-scroll-wrapper').style.transform ? 'transform' : 'fixed'
});
/* DON'T CHANGE THIS END */
var site_header = document.querySelector('#my-header');
var my_stuff = gsap.from(site_header,{
yPercent: -100,
duration: 0.25,
ease: 'sine.out'
}).progress(1);
ScrollTrigger.create({
start: 'top top-=' + 100,
onUpdate: (self)=>{
if(self.direction === -1) my_stuff.play();
else my_stuff.reverse();
}
});
/* DON'T CHANGE THIS */
// each time the window updates, we should refresh ScrollTrigger and then update LocomotiveScroll.
ScrollTrigger.addEventListener( 'refresh', () => loco_scroll.update());
// after everything is set up, refresh() ScrollTrigger and update LocomotiveScroll because padding may have been added for pinning, etc.
ScrollTrigger.refresh();
/* DON'T CHANGE THIS END */
}
} )();
Actually I don't know exactly where should I insert Scroller Proxy and Trigger in this code. Also, there are NO ANY data-scroll* attributes for this element. (And I think it's don't required here).
Related
I have an svg which forms the basis of my horizontal scroller.
Within this svg, I have added the class .animate to the elements which I want to fade in up as the item comes into view. The .animate class for reference has been added to all the text items in the svg.
Currently, only the .animate elements that are in view initially fade in up. When I scroll down to continue the scroller, the other elements are static. They're not fading in or translating up or down in any way?
TL;DR, here is what I'm trying to achieve:
When the scroller pins in place, and the user continued to scroll down, start fading away .horizontalScroller__intro.
Once .horizontalScroller__intro has faded away, start the horizontal scroll for .horizontalScroller__items
Any elements with the class of .animate in my scroller will fade in up to its original position.
Note: I understand SO rules and preferences to post code here. But, my demo's contain a length SVG, which I cannot post here as it exceeds SO's character limit.
Here is a demo of my latest approach
From the scrollTrigger docs, containerAnimation is what helps achieve animations on horizontal scrollers, and is what I've tried to achieve.
However, in my demo above, I have the following issues:
.horizontalScroller__intro doesn't show initially, when it should, and should fade out on scroll.
The horizontal scroller doesn't work anymore
The .animate elements that are in view, do not fade in up
If I use timeline (see below snippet), then the intro fade out and scroller works. But, doesn't animate in the child elements, which is where I need containerAnimation
$(function() {
let container = document.querySelector(".horizontalScroller__items");
let tl = gsap.timeline({
scrollTrigger: {
trigger: ".horizontalScroller",
pin: ".horizontalScroller",
anticipatePin: 1,
scrub: true,
invalidateOnRefresh: true,
refreshPriority: 1,
end: '+=4000px',
markers: true,
}
});
tl.to('.horizontalScroller__intro', {
opacity: 0,
})
tl.to(container, {
x: () => -(container.scrollWidth - document.documentElement.clientWidth) + "px",
ease: "none",
})
});
I'm struggling to find a way in which I can make the intro fade in, the scroller scroll horizontally, and the .animate elements to fade in, or fade in up.
Edit:
#onkar ruikar, see notes based on your sandbox below:
When you scroll down and the comes into view, I want the initial .animate elements to scroll up into view (currently, once the text fade away, and then the horizontal scroller starts working, only then does the .animate that are suppose to be in view, fade in up
After the initial .animate elements have loaded, the next .animate elements that are part of the scroller, they do not fade in up. They're static. As each .animate element comes into view, then it should fade in up (I think it's currently triggering once, for all the elements).
See visual here:
In the above gif, you can see the first two text blocks are hidden, as soon as they're in view, I want them to fade up. Then the 3rd and 4th text blocks are static, when they should fade up as the user scrolls to that section.
You need to use onUpdate method on the scroll trigger.
onUpdate: self => console.log("progress", self.progress)
Based on the self.progress set opacity, x position etc.
Full demo on codesandbox. Click on "Open Sandbox" button on bottom right to see the code.
if ("scrollRestoration" in history) {
history.scrollRestoration = "manual";
}
$(function() {
let container = document.querySelector(".horizontalScroller__items");
let elements = gsap.utils.toArray(
document.querySelectorAll(".animate")
);
let intro = document.querySelector(".horizontalScroller__intro");
let svg = document.querySelector("svg");
let animDone = false;
window.scrollPercent = -1;
var scrollTween = gsap.to(container, {
ease: "none",
scrollTrigger: {
trigger: ".horizontalScroller",
pin: ".horizontalScroller",
anticipatePin: 1,
scrub: true,
invalidateOnRefresh: true,
refreshPriority: 1,
end: "+=600%",
markers: true,
onEnter: (self) => {
moveAnimate();
},
onLeaveBack: (self) => {
resetAnimate();
},
onUpdate: (self) => {
let p = self.progress;
if (p <= 0.25) {
let op = 1 - p / 0.23;
intro.style.opacity = op;
animDone = false;
}
if (p > 0.23) {
moveAnimate();
// we do not want to shift the svg by 100% to left
// want to shift it only by 100% - browser width
let scrollPercent =
(1 - window.innerWidth / svg.scrollWidth) * 100;
let shift = ((p - 0.22) * scrollPercent) / 0.78;
gsap.to(svg, {
xPercent: -shift
});
}
}
}
});
function resetAnimate() {
gsap.set(".animate", {
y: 150,
opacity: 0
});
}
resetAnimate();
function moveAnimate() {
for (let e of elements) {
if (ScrollTrigger.isInViewport(e, 0.4, true))
gsap.to(e, {
y: 0,
opacity: 1,
duration: 2
});
}
}
});
You need to set opacity 0 on .animate elements in CSS. And use end: '+=400%' instead of 4000px. Relative dimensions can be used in position based calculations easily.
I'm using Locomotive Scroll on a website. What i try to achieve is the following : when a section with the id #mysection is in the viewport, i'd like an icon to appear, then disappear when the section is out of the viewport.
I've tried searching in the docs and on the web ways to achieve this, but i can't figure out how to make it work. Right now, i've seen that you can launch events in the docs by using the following :
<!-- Using jQuery events -->
<div data-scroll data-scroll-call="EVENT_NAME">Trigger</div>
then in the javascript file :
import LocomotiveScroll from 'locomotive-scroll';
const scroll = new LocomotiveScroll();
scroll.on('call', func => {
// Using modularJS
this.call(...func);
// Using jQuery events
$(document).trigger(func);
// Or do it your own way 😎
});
So what i did is the following :
let serviceWebDigital;
let serviceGraphicDesign;
let serviceMarketing;
let serviceVideoMotion;
smoothScroll.on("call", (value, way, obj) => {
if (value === "serviceWebDigital") {
serviceWebDigital = TweenMax.to($(".arrow-service-1"), 1, { opacity: 1 });
} else {
serviceWebDigital = TweenMax.to($(".arrow-service-1"), 1, { opacity: 0 });
}
if (value === "serviceGraphicDesign") {
serviceGraphicDesign = TweenMax.to($(".arrow-service-2"), 1, { opacity: 1 });
} else {
serviceGraphicDesign = TweenMax.to($(".arrow-service-2"), 1, { opacity: 0 });
}
if (value === "serviceMarketing") {
serviceMarketing = TweenMax.to($(".arrow-service-3"), 1, { opacity: 1 });
} else {
serviceMarketing = TweenMax.to($(".arrow-service-3"), 1, { opacity: 0 });
}
if (value === "serviceVideoMotion") {
serviceVideoMotion = TweenMax.to($(".arrow-service-4"), 1, { opacity: 1 });
} else {
serviceVideoMotion = TweenMax.to($(".arrow-service-4"), 1, { opacity: 0 });
}
});
My issue is that when the elements enter in the viewport, the arrows appears fine. But when i scroll down then up, the arrows don't appear no more when the sections enter in the viewport. What am i missing?
Another issue is that i'd like to control when the elements appear. Right now, they appear right when the div elements enter the bottom of the viewport. Is there a way to create some "delay"? So they appear when the element is fully in the viewport for example?
EDIT :
When looking at the live code, it appears that the class that is added by locomotive is-inview when elements enter the viewport doesn't disappear when the element is out of view. That might be part of the issue here..
To quickly answer the questions about the "is-inview" class not being removed, you need to include the "data-scroll-repeat" attribute to the element.
eg: <div data-scroll data-scroll-repeat>content</div>
Regarding controlling when it triggers, I'd look at the data-scroll-offset="?, ?"
In the docs is says the 1st is bottom (when the elements enters) and 2nd is top (when the element exits). Can be a number for pixels or a percentage.
Hope that helps for part of your question at least.
I have built a drag-drop autoscroller where the user drags and element over a hidden div which triggers the scrolling action of the scrollable div. I am using scrollBy({top: <val>, behavior: 'smooth'} to get smooth scrolling and requestAnimationFrame to prevent the function from calling too often. This works fine in Firefox and should be supported in Chrome natively according to caniuse; however, it fails to work properly in chrome. It only fires the event once when the user leaves the hidden div. No errors in the console. console.log() indicates that the function containing the scrollBy() is being called. If I remove behavior: 'smooth' it works, but of course no smooth scrolling. same result if I remove the option and set the css scroll-behavior: smooth on the scrollable div. I'm at a complete loss. MWE of the scroll function (this is in a Vue app, so any this.'s are stored in a data object.
scroll: function () {
if ( this.autoScrollFn ) cancelAnimationFrame( this.autoScrollFn )
// this.toScroll is a reference to the HTMLElement
this.toScroll.scrollBy( {
top: 100,
behavior: 'smooth'
}
this.autoscrollFn = requestAnimationFrame( this.scroll )
}
Not sure what you did expect from your requestAnimationFrame call to do here, but here is what should happen:
scrollBy having its behavior set to smooth should actually start scrolling the target element only at next painting frame, just before the animation frames callback get executed (step 7 here).
Just after this first step of the smooth scrolling, your animation frame callback will fire (step 11), disabling the first smooth scrolling by starting a new one (as defined here).
repeat until it reaches the top-max, since you are never waiting enough for the smooth 100px scrolling to happen entirely.
This will indeed move in Firefox, until it reaches the end, because this browser has a linear smooth scrolling behavior and scrolls from the first frame.
But Chrome has a more complicated ease-in-out behavior, which will make the first iteration scroll by 0px. So in this browser, you will actually end up in an infinite loop, since at each iteration, you will have scrolled by 0, then disable the previous scrolling and ask again to scroll by 0, etc. etc.
const trigger = document.getElementById( 'trigger' );
const scroll_container = document.getElementById( 'scroll_container' );
let scrolled = 0;
trigger.onclick = (e) => startScroll();
function startScroll() {
// in Chome this will actually scroll by some amount in two painting frames
scroll_container.scrollBy( { top: 100, behavior: 'smooth' } );
// this will make our previous smooth scroll to be aborted (in all supporting browsers)
requestAnimationFrame( startScroll );
scroll_content.textContent = ++scrolled;
};
#scroll_container {
height: 50vh;
overflow: auto;
}
#scroll_content {
height: 5000vh;
background-image: linear-gradient(to bottom, red, green);
background-size: 100% 100px;
}
<button id="trigger">click to scroll</button>
<div id="scroll_container">
<div id="scroll_content"></div>
</div>
So if what you wanted was actually to avoid calling multiple times that scrolling function, your code would be broken not only in Chrome, but also in Firefox (it won't stop scrolling at after 100px there either).
What you need in this case is rather to wait until the smooth scroll ended.
There is already a question here about detecting when a smooth scrollIntoPage ends, but the scrollBy case is a bit different (simpler).
Here is a method which will return a Promise letting you know when the smooth-scroll ended (resolving when successfully scrolled to destination, and rejecting when aborted by an other scroll). The basic idea is the same as the one for this answer of mine:
Start a requestAnimationFrame loop, checking at every steps of the scrolling if we reached a static position. As soon as we stayed two frames in the same position, we assume we've reached the end, then we just have to check if we reached the expected position or not.
With this, you just have to raise a flag until the previous smooth scroll ends, and when done, lower it down.
const trigger = document.getElementById( 'trigger' );
const scroll_container = document.getElementById( 'scroll_container' );
let scrolling = false; // a simple flag letting us know if we're already scrolling
trigger.onclick = (evt) => startScroll();
function startScroll() {
if( scrolling ) { // we are still processing a previous scroll request
console.log( 'blocked' );
return;
}
scrolling = true;
smoothScrollBy( scroll_container, { top: 100 } )
.catch( (err) => {
/*
here you can handle when the smooth-scroll
gets disabled by an other scrolling
*/
console.error( 'failed to scroll to target' );
} )
// all done, lower the flag
.then( () => scrolling = false );
};
/*
*
* Promised based scrollBy( { behavior: 'smooth' } )
* #param { Element } elem
** ::An Element on which we'll call scrollIntoView
* #param { object } [options]
** ::An optional scrollToOptions dictionary
* #return { Promise } (void)
** ::Resolves when the scrolling ends
*
*/
function smoothScrollBy( elem, options ) {
return new Promise( (resolve, reject) => {
if( !( elem instanceof Element ) ) {
throw new TypeError( 'Argument 1 must be an Element' );
}
let same = 0; // a counter
// pass the user defined options along with our default
const scrollOptions = Object.assign( {
behavior: 'smooth',
top: 0,
left: 0
}, options );
// last known scroll positions
let lastPos_top = elem.scrollTop;
let lastPos_left = elem.scrollLeft;
// expected final position
const maxScroll_top = elem.scrollHeight - elem.clientHeight;
const maxScroll_left = elem.scrollWidth - elem.clientWidth;
const targetPos_top = Math.max( 0, Math.min( maxScroll_top, Math.floor( lastPos_top + scrollOptions.top ) ) );
const targetPos_left = Math.max( 0, Math.min( maxScroll_left, Math.floor( lastPos_left + scrollOptions.left ) ) );
// let's begin
elem.scrollBy( scrollOptions );
requestAnimationFrame( check );
// this function will be called every painting frame
// for the duration of the smooth scroll operation
function check() {
// check our current position
const newPos_top = elem.scrollTop;
const newPos_left = elem.scrollLeft;
// we add a 1px margin to be safe
// (can happen with floating values + when reaching one end)
const at_destination = Math.abs( newPos_top - targetPos_top) <= 1 &&
Math.abs( newPos_left - targetPos_left ) <= 1;
// same as previous
if( newPos_top === lastPos_top &&
newPos_left === lastPos_left ) {
if( same ++ > 2 ) { // if it's more than two frames
if( at_destination ) {
return resolve();
}
return reject();
}
}
else {
same = 0; // reset our counter
// remember our current position
lastPos_top = newPos_top;
lastPos_left = newPos_left;
}
// check again next painting frame
requestAnimationFrame( check );
}
});
}
#scroll_container {
height: 50vh;
overflow: auto;
}
#scroll_content {
height: 5000vh;
background-image: linear-gradient(to bottom, red, green);
background-size: 100% 100px;
}
.as-console-wrapper {
max-height: calc( 50vh - 30px ) !important;
}
<button id="trigger">click to scroll (spam the click to test blocking feature)</button>
<div id="scroll_container">
<div id="scroll_content"></div>
</div>
I'm trying to create a sliding sidebar and was wondering if there was a better way then what I am doing already.
<img id = "MenuIcon" src = "MenuIcon.png" alt = "Menu Icon" onclick = "slideSideBar()" />
At the moment I simply check if the sideSlideCount is even - if it is the sidebar must not be out, so when the function is called it slides out; if sideSlideCount is odd (i.e. % 2 != 0) then the sidebar should slide out of view.
var sideSlideCount = 0; // variable used with the side bar
var bodyHeight = $(window).height();
var bodyWidth = $(window).width();
console.log(bodyWidth);
function slideSideBar() {
if (sideSlideCount % 2 == 0) { // if sideSlideCount is even, i.e. the side bar is hidden
$("#SideBar").animate({width: bodyWidth / 6}, 600); // slide the bar out to 300 width, 0.6 seconds
$("#SideLinks").fadeTo(1000, 0.8); // fade the links into view, 1 second, to 100% opacity
}
else { // else, if the side bar is in view
$("#SideBar").fadeIn(300).animate({width: 0}, 600); // slide the bar back out of view (0 width), 0.6 seconds
$("#SideLinks").fadeTo(200, 0); // fade the links out of view, 0.2 seconds, to 0% opacity
}
sideSlideCount++; // increment the variable
}
You could simply make your code modular to avoid global variables. You should be looking into AMD modules, however to keep it simple you can create yourself a namespace where your code will live.
DEMO: http://jsfiddle.net/BzYuQ/
//Define a SlidingSidebar reusable component
!function (ns, $) {
function SlidingSidebar(el, animateDuration) {
this.$el = $(el);
this.animateDuration = animateDuration;
}
SlidingSidebar.prototype = {
constructor: SlidingSidebar,
toggle: function () {
this.$el.stop().animate({ width: 'toggle' }, this.animateDuration);
}
};
ns.SlidingSidebar = SlidingSidebar;
}(slideExampleSite = window.slideExampleSite || {}, jQuery);
$(function () {
//The following behavior could also be in a configurable "SlidebarFeature" module
//or you could integrate the button directly into the SlidingSidebar.
//I'm just showing how code can be modularized here.
var sideBar = new slideExampleSite.SlidingSidebar('#sidebar', 600);
$('#toggle-btn').click(function () {
sideBar.toggle();
});
});
Obviously in this case the SlidingSidebar component doesn't do much, but as your application grows, modularizing your code to get away from the $(function () {/*tons of code*/}); anti-pattern will pay off in many ways.
You didn't say what "better" is, but if the intention is just to avoid globals, you can use a closure:
var slideSideBar = (function() {
var sideSlideCount = false; // variable used with the side bar
var bodyHeight = $(window).height();
var bodyWidth = $(window).width();
console.log(bodyWidth);
// This function has access to all the above variables, but no other
// function does.
function slideSideBar() {
if (sideSlideCount) {
$("#SideBar").animate({width: bodyWidth / 6}, 600);
$("#SideLinks").fadeTo(1000, 0.8);
} else {
$("#SideBar").fadeIn(300).animate({width: 0}, 600);
$("#SideLinks").fadeTo(200, 0);
}
sideSlideCount = !sideSlideCount;
}
// Return a reference to the slideSideBar function so it's available
// globally, but access to variables is "private"
return slideSideBar;
}());
The only difference is that slideSideBar won't exist until the above has been executed, so don't try to call it until afterward.
Not sure on your definition of "better", but I see your using jQuery which has a nice $toggle feature which can help here.
function slidesideBar(){
$("#SideBar").toggle(function() {
$(this).animate({width: bodyWidth / 6}, 600); // slide the bar out to 300 width, 0.6 seconds
$("#SideLinks").fadeTo(1000, 0.8); // fade the links into view, 1 second, to 100% opacity
}, function() {
$(this).fadeIn(300).animate({width: 0}, 600); // slide the bar back out of view (0 width), 0.6 seconds
$("#SideLinks").fadeTo(200, 0); // fade the links out of view, 0.2 seconds, to 0% opacity
});​
}
Credit: jQuery Animation Toggle. Copy and paste:
When given a list of functions as arguments, the .toggle(fn1, fn2) method will alternate between the functions given starting with the first one. This automatically keeps track of the toggle state for you - you don't have to do that.
jQuery doc is here. There are multiple forms of .toggle() depending upon the arguments used so you don't always find the right one when searching the jQuery doc.
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)