When I recoded and rebranded this site four months ago, I decided to make it completely free of frameworks: while the text and visible code samples for JQuery articles often remained unchanged, under the hood everything had to run vanilla .

One of those articles explored how to rotate elements on the page with scroll. This article presents an updated version using the code that actually runs underneath that demo, written in vanilla JavaScript… in many respects, an approach that is simpler and more efficient.

Setting The Gears

As with the previous example, the gears are placed on the page using separate id values. Since they are vector shapes, it makes sense to build and reference the gears as SVG images:

<div id="gearbox">
    <img src="gear.svg" alt id="leftgear">
    <img src="gear.svg" alt id="rightgear">

There are many possibilities for positioning the gears: very often they will have a fixed position on the page; it’s also possible to use the new CSS “scroll-to-top-then-fixed” positioning (aka position: sticky). In this example, I’ve used to separate the elements, and vw units to size them; otherwise, the gears use standard static positioning, and therefore completely scroll with the page.

#gearbox {
    display: flex;
    justify-content: space-between;
#leftgear, #rightgear {
    width: 20vw;
    max-width: 20%;
    height: auto;

Rotating the Gears

Rotating the elements themselves is quite straightforward: at the bottom of the page, add the following script.

var leftgear = document.getElementById("leftgear"),
rightgear = document.getElementById("rightgear");
window.addEventListener("scroll", function() {
    leftgear.style.transform = "rotate("+window.pageYOffset+"deg)";
    rightgear.style.transform = "rotate(-"+window.pageYOffset+"deg)";

Each gear rotates by the amount the window is scrolled in pixels, converted into degrees, the left gear rotating clockwise, and the right counter-clockwise. To speed up or slow down the rotation relative to the amount of scrolling, you could make window.pageYOffset part of a simple mathematical expression: multiplying or dividing it by 2, for example.

Avoid Grinding Your Gears

A common issue with any technique that manipulates scroll behaviour is “janking”: elements stuttering as they try to match user input with the browser’s repaint cycle. The easiest way to deal with this is to only move the elements when the browser is prepared to do so, via requestAnimationFrame. To do so, the addEventListener changes to listen for a custom event:

window.addEventListener("optimizedScroll", function() {

Above the event handler, add an anonymous function:

;(function() {
    var throttle = function(type, name, obj) {
        var obj = obj || window;
        var running = false;
        var func = function() {
            if (running) { return; }
            running = true;
            requestAnimationFrame(function() {
                obj.dispatchEvent(new CustomEvent(name));
                running = false;
        obj.addEventListener(type, func);
    throttle ("scroll", "optimizedScroll");

I’ll have much more to say about requestAnimationFrame in coming articles.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/pgzeor