Introducing MooTools ScrollSpy
I've been excited to release this plugin for a long time. MooTools ScrollSpy is a unique but simple MooTools plugin that listens to page scrolling and fires events based on where the user has scrolled to in the page. Now you can fire specific functionality with just a few simple parameters.
The MooTools JavaScript Class
/* scroll spy plugin / class */ var ScrollSpy = new Class({ /* implements */ Implements: [Options,Events], /* options */ options: { min: 0, mode: 'vertical', max: 0, container: window, onEnter: $empty, onLeave: $empty, onTick: $empty }, /* initialization */ initialize: function(options) { /* set options */ this.setOptions(options); this.container = $(this.options.container); this.enters = this.leaves = 0; this.max = this.options.max; /* fix max */ if(this.max == 0) { var ss = this.container.getScrollSize(); this.options.max = this.options.mode == 'vertical' ? ss.y : ss.x; } /* make it happen */ this.addListener(); }, /* a method that does whatever you want */ addListener: function() { /* state trackers */ this.inside = false; this.container.addEvent('scroll',function() { /* if it has reached the level */ var position = this.container.getScroll(); var xy = this.options.mode == 'vertical' ? position.y : position.x; /* if we reach the minimum and are still below the max... */ if(xy >= this.options.min && xy ≪= this.max) { /* trigger Enter event if necessary */ if(!this.inside) { /* record as inside */ this.inside = true; this.enters++; /* fire enter event */ this.fireEvent('enter',[position,this.enters]); } /* trigger the "tick", always */ this.fireEvent('tick',[position,this.inside,this.enters,this.leaves]); } else { /* trigger leave */ if(this.inside) { this.inside = false; this.leaves++; this.fireEvent('leave',[position,this.leaves]); } } }.bind(this)); } });
Options for ScrollSpy include:
- min: (defaults to 0) The minimum value of the X or Y coordinate, depending on mode.
- max: (defaults to 0) The maximum value of the X or Y coordinate, depending on mode.
- mode: (defaults to 'vertical') Defines whether to listen to X or Y scrolling.
- container: (defaults to window) The element whose scrolling to listen to.
Events for ScrollSpy include:
- Tick: Fires on each scroll event within the min and max parameters. Receives as parameters:
- position: an object with the current X and Y position.
- inside: a Boolean value for whether or not the user is within the min and max parameters
- enters: the number of times the min / max has been entered.
- leaves: the number of times the min / max has been left.
- Enter: Fires every time the user enters the min / max zone.
- position: an object with the current X and Y position.
- enters: the number of times the min / max has been entered.
- Leave: Fires every time the user leaves the min / max zone.
- position: an object with the current X and Y position.
- leaves: the number of times the min / max has been left.
So now that we have the basics down, lets check out some example usages.
Example 1: "Top the Top"
In this example, when you scroll down a defined number of pixels, you get a "Scroll to Top" link in the lower right hand part of the screen. When you're back at the top, ScrollSpy is directed to hide the link.
The XHTML
<a href="#top" id="gototop" class="no-click no-print">Top of Page</a>
The CSS
#gototop { display:none; font-weight:bold; font-family:tahoma; font-size:10px; width:70px; background:url(/wp-content/themes/walshbook/images/add_content_spr.gif) 5px -8px no-repeat #eceff5; color:#3b5998; font-size:11px; text-decoration:none; position:fixed; right:5px; bottom:5px; padding:7px 7px 7px 20px; } #gototop:hover { text-decoration:underline; }
The MooTools / ScrollSpy JavaScript
window.addEvent('domready',function() { /* smooth */ new SmoothScroll({duration:500}); /* link management */ $('gototop').set('opacity','0').setStyle('display','block'); /* scrollspy instance */ var ss = new ScrollSpy({ min: 200, onEnter: function(position,state,enters) { $('gototop').fade('in'); }, onLeave: function(position,state,leaves) { $('gototop').fade('out'); }, container: window }); });
Example 2: "The Show"
When you click the link in this example, the window scrolls to the right. During the scrolling process, ScrollSpy shows and hides content blocks based on where in the scrolling process the window is.
The XHTML
<!-- SLIDER 1 --> <div style="position:relative; height:400px;"> <div id="slider1" class="slider" style="margin-left:1000px;"> <h2>ScrollSpy!</h2> <p> ScrollSpy is a new plugin that watches where you scroll and allows you to perform actions based on the the position of the given element! </p> </div> <!-- SLIDER 2 --> <div id="slider2" class="slider" style="margin-left:1600px;"> <h2>ScrollSpy 2!</h2> <p> Another area! </p> </div> <!-- SLIDER 3 --> <div id="slider3" class="slider" style="margin-left:2200px;"> <h2>ScrollSpy 3!</h2> <p> You've met another criteria!! </p> </div> <!-- SLIDER 4 --> <div id="slider4" class="slider" style="margin-left:2800px;"> <h2>ScrollSpy 4!</h2> <p> You've met the last criteria!! </p> </div> <div style="clear:both;"></div> </div> <!-- RIGHT-MOST AREA --> <div style="float:right;" id="right2"> </div>
The CSS
.slider { padding:10px; background:#eee; width:300px; height:300px; float:left; z-index:500; }
The MooTools / ScrollSpy JavaScript
window.addEvent('domready',function() { /* sliders */ var slide1 = new Fx.Slide('slider1',{ duration: 400, wheelStops: false }); slide1.hide(); var slide2 = new Fx.Slide('slider2',{ duration: 400, wheelStops: false }); slide2.hide(); var slide3 = new Fx.Slide('slider3',{ duration: 200, wheelStops: false }); slide3.hide(); var slide4 = new Fx.Slide('slider4',{ duration: 200, wheelStops: false }); slide4.hide(); /* scrollspy instance */ var ss1 = new ScrollSpy({ min: 400, max: 700, onEnter: function(position,state,enters) { slide1.slideIn(); }, onLeave: function(position,state,leaves) { slide1.slideOut(); }, container: window, mode: 'horizontal' }); /* scrollspy instance */ var ss2 = new ScrollSpy({ min: 900, max: 1500, onEnter: function(position,state,enters) { slide2.slideIn(); }, onLeave: function(position,state,leaves) { slide2.slideOut(); }, container: window, mode: 'horizontal' }); /* scrollspy instance */ var ss3 = new ScrollSpy({ min: 1500, max: 2300, onEnter: function(position,state,enters) { slide3.slideIn(); }, onLeave: function(position,state,leaves) { slide3.slideOut(); }, container: window, mode: 'horizontal' }); /* scrollspy instance */ var ss4 = new ScrollSpy({ min: 2200, max: 2500, onEnter: function(position,state,enters) { slide4.slideIn(); }, onLeave: function(position,state,leaves) { slide4.slideOut(); }, container: window, mode: 'horizontal' }); /* left to right scroll */ $('show2').addEvent('click',function(e) { e.stop(); var to = $('right2').getPosition(); to.y = 0; to.x = to.x - 300; var scroll = new Fx.Scroll(window,{ duration: 20000, offset: to }).start(); }); });
Example 3: "Team Colors"
This basic example displays a different background color depending on where you are in the page.
The XHTML
<div id="white" class="color"><h2>The White Nation</h2></div> <div id="red" class="color"><h2>The Red Nation</h2></div> <div id="blue" class="color"><h2>The Blue Nation</h2></div> <div id="green" class="color"><h2>The Green Nation</h2></div> <div id="black" class="color"><h2>The Black Nation</h2></div>
The CSS
.red { background:#f00; } .blue { background:#00f; } .green { background:#008000; } .black { background:#000; color:#fff; } .color { height:400px; }
The MooTools / ScrollSpy JavaScript
window.addEvent('domready',function() { var colors = $$('.color'); colors.each(function(color,i) { var pos = color.getCoordinates(); var ss = new ScrollSpy({ min: pos.top, max: pos.bottom, onEnter: function() { $$('div.content').setStyle('background-color',color.get('id')); } }); }); });
Example 4: "Position Pointer"
Starring world television actor Peter Griffin, this example displays imagery in different positions on the page based upon where the user scrolls.
The XHTML
<div class="zone"> <h2>Area 1</h2> <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p><p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat.</p> </div> <div style="float:right;" class="zone right"> <h2>Area 2</h2> <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p><p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat.</p> </div> <div style="clear:both;"></div> <div class="zone"> <h2>Area 3</h2> <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p><p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat.</p> </div>
The CSS
.zone { width:500px; }
The MooTools / ScrollSpy JavaScript
window.addEvent('domready',function() { /* collect zones */ var zones = $$('div.zone'); var imageOffset = { x: 200, y: 50 }; var posOffset = { x: 0, y: -150 } /* scroll spy */ zones.each(function(zone, i) { var pos = zone.getCoordinates(); var right = zone.hasClass('right'); var peter = new Element('img',{ opacity: 0, src: right ? '/demo/peter-left.jpg' : '/demo/peter-right.jpg', styles: { position: 'absolute', top: pos.top + imageOffset.y, left: right ? pos.left - imageOffset.x - 100 : pos.right + imageOffset.x } }).inject(document.body); var spy = new ScrollSpy({ min: pos.top + posOffset.y, max: pos.bottom + posOffset.y, onEnter: function(position) { peter.fade('in'); }, onLeave: function(position) { peter.fade('out'); } }); }); });
ScrollSpy gives you a great amount of functionality within a small plugin. Coming soon: ScrollSpy LazyLoad and ScrollSpy LoadMore! Please share ideas and comments!
Hi David,
none of your demos work here. FF3.1. (24″, 1900×1200, perhaps the problem?)
1,#3,#4 dont show any effect, #2 scrolls very weird to the right.
Fixed. Stupid
console.log
statements!Same here, none work, just the one that scrolls to the right.
FF 3.0.1 1024×768
Much better ;-)
good job!
This is a cool idea, but it almost seems unnecessary to create multiple instances of the class for “spying on” multiple elements on the page. I think the class could be abstracted a bit more to where one ScrollSpy instance could manage limitless amounts of targets instead of attaching tons of event listeners to the window scroll event.
The hard part would be cataloging these min and max values for everything to where you could easily identify when to fire what event for what item…
Something like
myScrollSpy.addSubject(subjectId, {min: value, max: value}, customeEventNameOrFunctionCall)
@Jem: I was going to do that but I ended up “dumbing down” the plugin to keep it simple. You could set the min to 0 and max to 0 (unlimited) and put all of your specific range logic within the onTick — that’s useless though. At that point, you may as well ditch this plugin and use the regular
window.addEvent('scroll');
event handling.That’s pretty cool, very similar to Google Reader, Facebook etc. Any plugins like this for JQuery?
Neal G: I tried a jQuery version but I was having issues getting the scroll value from the Event object. Haven’t tried in a while though.
Dude, this is brilliant. Very sweet idea!
coool!!!! I have been playing with mootools for 3 weeks now, and this is the best resource i found so far
really love that ^Top of Page thing.. very useful
anyhow, i haven’t getting around with plugins, do i have to make this myself? or should i include it like mootools-more?
Love it man! With regard to @Jem’s comment (though a good idea) I think it was smart to keep its usage simple. Plugin consumers want a straight forward implementation… good job.
1 is so simple yet so effective. Thank you~
Well, it took me a few minutes, but I’ve managed to reproduce these samples.
I’ve placed samples 1, 3 & 4 together, they work as expected.
Now to see what type of variations I can come up with.
I can see this used to fade in adverts at just the right moment.
Or on blogs to fade in the next excerpt as you arrive to it.
Lower info bars appearing when you are half way down a page.
previous / next in series as you reach the end of a page.
I’ll be watching to see how people will use this plugin,
thanx David
very smooth work.
Nice plugin David!!
Absolutely well done
Awe. Some. Thanks!
Awesome, cant wait to try example #2!
awesome! … well done. Hope you’re having the time (and enough motivation) to make a jquery version.
IE6 fix
Woah! That’s so great! Looking forward for a jQuery version!
I’d like to add an extra feature on it. I’ve got a large div element that needs to be relative on top after I’ve scrolled 200px. When I mouseenter this specific div I’d like to remove the relative behaviour. When I mouseleave the div the div has to be relative again.
How to apply this on the class?
This is just perfekt for a “flip-book” effekt by scolling down the page ^^
I like it :)
very nice article…keep on the good work
Great work! thanks for all script :)
this information most important for me.
thanks…
I was wondering if you had any plans to update the script to work on the iphone?
Awesome stuff mate, love the code!
wel… I’m having some problems with Google Chrome…spyScroll works great under Firefox, but example 3 seems to be not working properly under Chrome…
David, copy button copys text without new line breaks
Hey David !
Thanks a lot for this great class !
Just a word : i’m not sure, but you wrote thoses lines :
But you do not fix
this.max
but onlythis.options.max
. Therefore, shouldn’t you usethis.options.max
in the rest of the code, or modify you code such as :This is great! there is so much potential for this. I stumbled across a site using your tool to make it so things with fixed positioning won’t be fixed anymore but absolutely positioned when scrolling to the right if the content is only supposed to scroll vertically. However his method doesn’t account for a screen that is aligned to the center, leaving me with a broken menu alignment whenever I move the scrollbar.
This is the js
the HTML
the css
how can I make it so this only starts when someone scrolls horizontally (because of a small browser window or monitor) and goes “back to normal” if the screen is big enough where there are no horizontal scroll bars because the horizontal width doesn’t go past the browser size?
Love this plugin! Hey I am working on a site and would like to as you scroll down change not just the background color but also the background image is this possible using this plugin (As shown in example #3). Just wanted to see if their were some code gurus out there who know how to make this happen. THANKS!
It doesn’t work on my website. I have mootools 1.3.5. Could somebody get it work with mootools 1.3.5?
Thank you Simon André!
was the solution.
There’s a new version available, BTW: https://github.com/darkwing/ScrollSpy/blob/master/Source/ScrollSpy.js
Hello.
Very good plugin. Thanks!
Look please don’t work correctly function
stop()
in file ScrollSpy-yui-compressed.jsThis is pretty great, awesome touch/feel. This is a very Jr question, but do I have to grab the “MooTools JavaScript Class” first part of text in order to implement example 4 onto my site? Thanks!
Hi David,
Really nice plugin. Was wondering if you’re planning to make a wordpress plugin out of this. Sorry might be a noob question. But thought I’d ask! :)
Hey! The demo links are broken! :)
I’m trying to get ScrollSpy working with wordpress and am trying to acheive a similar effect to example 4. How do I go about that?
First, what a nice piece of code! I’m having an issue in Firefox 33.1 where the onEnter won’t trigger on a page with a bookmark in the url. The expected behavior will occur on a refresh but not from a different url to one with a #bookmark in it. It works perfectly in Chrome and IE.