Introducing MooTools ScrollSpy
Written by David Walsh on Wednesday, May 27, 2009
Click here to learn what has changed to make your code framework-compatible.
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 ? '/dw-content/peter-left.jpg' : '/dw-content/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!
Follow via RSS Epic Discussion
Be Heard!
I want to hear what you have to say! Share your comments and questions below.











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
html{
background:url(../images/blankimage.gif) fixed; /* Blank image used to stop vibration */
}
#gototop{
position:absolute;
top : expression(-this.offsetHeight+document.documentElement.clientHeight+document.documentElement.scrollTop+”px”+4);
}
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…