If you have spent any time on this site, you have likely noticed the gears on the left side that rotate as you scroll through the site. This effect is produced through a combination of JavaScript and CSS3, in particular the css3 transform rule.
The code is fairly straightforward. Using jQuery, scroll events are captured and trigger the function that rotates the gears. I’ll walk through the various parts and the whole thing is at the bottom of this post via jsFiddle.
First, the initial css. I have something like this in my site’s main stylesheet:
#container {
width: 200px;
margin: 20px auto;
padding: 20px;
border: 1px dotted #ccc;
background-color: #fff;
position: relative;
}
/* Gears */
#gears {
position: fixed;
display: none;
}
.gear_md {
position: absolute;
width: 145px;
height: 145px;
}
#gear1 {
top: 50px;
left: -203px;
}
#gear2 {
top: 50px;
left: -64px;
}
#gear3 {
top: 173px;
left: -269px;
}
Notice that the #container element has position set to relative while #gears is position: fixed. As we will see in a moment, #container and #gears are adjacent divs. Setting #container to position: relative; pulls it above its sibling so that the gears appear to be behind the container. #gears starts as display: none; jQuery will make it visible.
#gears position is fixed but no position is set in css. jQuery will track the position of the container element and set the left position for #gears. The reason all three gears have negative left edges is the left edge of #gears lines up with the left edge of #container, so the gears have to be pulled out to the left to make them visible.
.gear_md is the class for the medium sized gear. It sets position: absolute; for all gears, allowing them to be positioned within the #gears div. The positioning of the three gears is up to you; I like the way this looks, you can adjust to taste. It is easy to add or remove as many gears as you like, just add additional rules from more gears in css, add another gear in the html, and duplicate a gear in JavaScript.
I have extra styling on #container; this is just to make it obvious while testing. The only rule really needed for #container is position: relative;
Now let’s see the html:
<div id="gears">
<img class="gear_md" id="gear1" src="http://croberts.me/wp-content/themes/croberts/images/gear.png" />
<img class="gear_md" id="gear2" src="http://croberts.me/wp-content/themes/croberts/images/gear.png" />
<img class="gear_md" id="gear3" src="http://croberts.me/wp-content/themes/croberts/images/gear.png" />
</div><!-- #gears -->
<div id="container">
<div id="content">
Add your content here. For testing, this needs to be enough to allow vertical scrolling.
</div><!-- #content -->
</div><!-- #container -->
Easy peasy, nothing fancy about the html. Make sure each of your gears uses the gear class and has a distinct id.
The image I created for my gears, gear.png, is a little larger than I wanted so reduced them via css. I should have just created smaller versions, but I kept it large so I could create a chain of large and small gears, if desired. Feel free to download and use it, but please load it in your own server, not from mine.
Now the JavaScript. This is where the magic happens.
// Control the spinning gears
$(document).ready(function() {
// Get the container position
var containerPos = $('#container').offset();
// Get the initial scroll position. This will be needed later when determining
// if we are scrolling up or down.
var scrollPos = $(window).scrollTop();
var degreeRotate = 0;
// We will use these to track how much we are rotating our gears. Need to track
// the gears separately since they will not be going the same direction
var gear1Rotate = 0;
var gear2Rotate = 0;
var gear3Rotate = 0;
$('#gears').css('display', 'block');
$('#gears').css('left', containerPos.left + "px");
browser_transform('#gear2', 11);
browser_transform('#gear3', 90);
$(document).scroll(function() {
// Are we moving up or down?
var newScroll = $(window).scrollTop();
if (scrollPos > newScroll) {
degreeRotate -= 5;
} else {
degreeRotate += 5;
}
// Calculate rotations. These will be slightly different for each gear, even
// for the ones spinning the same direction, in order to line up the teeth of
// the gears.
gear1Rotate = degreeRotate;
gear2Rotate = ((degreeRotate + 11) * -1);
gear3Rotate = ((degreeRotate + 90) * -1);
// Store the current scroll for comparison next scroll event.
scrollPos = newScroll;
browser_transform('#gear1', gear1Rotate);
browser_transform('#gear2', gear2Rotate);
browser_transform('#gear3', gear3Rotate);
});
});
// Handle automatic output of multiple vendor tags for css3 transforms
function browser_transform(transTarget, transValue)
{
$(transTarget).css('-ms-transform', 'rotate(' + transValue + 'deg)');
$(transTarget).css('-moz-transform', 'rotate(' + transValue + 'deg)');
$(transTarget).css('-webkit-transform', 'rotate(' + transValue + 'deg)');
$(transTarget).css('-o-transform', 'rotate(' + transValue + 'deg)');
$(transTarget).css('transform', 'rotate(' + transValue + 'deg)');
}
Comments in the code should explain most of what is happening.
When the dom is ready, jQuery(document).ready() gets called, triggering the initial code. The #container position is retrieved and #gears is positioned underneath it and is made visible. Default values are set for the gear rotations.
Because we are trying to make it look like the gears are turning each other, gear teeth need to line up. Before any scrolling takes place, two of the gears are rotated so that their teeth fit into the grooves of the adjacent gears:
browser_transform('#gear2', 11);
browser_transform('#gear3', 90);
The script then registers the scroll event. Every time the user scrolls, the script compares the current scroll position with the previous scroll position. If our position is greater, we are scrolling down. If less, we are scrolling up. The gears rotate in opposite directions to the user scroll.
I have my gears rotate 5 degrees at a time. Keep in mind that a complete rotation is 360 degrees. 5 degrees may seem small, but it moves this much every single time the user scrolls. The gears are rotated by the same amount, so the variable degreeRotate gets applied to all three with variations to account for lining up gear teeth and grooves:
gear1Rotate = degreeRotate;
gear2Rotate = ((degreeRotate + 11) * -1);
gear3Rotate = ((degreeRotate + 90) * -1);
Notice the two gears that are multiplied by -1, this is because the two side gears rotate opposite the direction of the center gear.
With rotation amounts calculated, a helper function is called:
browser_transform('#gear1', gear1Rotate);
Since the css3 transform rule is not yet fully supported, vendor prefixes should be added so the rotations work in a cross-section of browsers. This function does the trick:
function browser_transform(transTarget, transValue)
{
$(transTarget).css('-ms-transform', 'rotate(' + transValue + 'deg)');
$(transTarget).css('-moz-transform', 'rotate(' + transValue + 'deg)');
$(transTarget).css('-webkit-transform', 'rotate(' + transValue + 'deg)');
$(transTarget).css('-o-transform', 'rotate(' + transValue + 'deg)');
$(transTarget).css('transform', 'rotate(' + transValue + 'deg)');
}
Pass in the element to be rotated and how much rotation should be applied and the script (via jQuery) ensures the various vendor prefixes are applied.
There you have it, gears that rotate while scrolling through a document. Now see the whole thing via jsFiddle: