The spring effect in JavaScript


In the last weeks I was trying to figure out a way to make things scroll nicer and that doesnt affect negatively the performance. When I start I didnt know exactly what was the result I wanted, so after a few frustrated attempts coding something by my self I start looking for some sort of custom easing functions, how they works, etc. This is how I got to this blog about Physics and JavaScript, specifically to this article about car suspension (Hooke’s Law). And I thought it may works great on scrolling.

After trying several times to use this on scrolling I finally made my first successful attempt, and it was pretty much what I had in mind: http://code.juan.me/demos/springy/basic/html/. In the demo you can see on the left, the green squares are being eased with the spring effect, the one on the right has no easing (so you can see the difference).

Looking at the first attempt (and being an Android user) I realized there was something familiar with it, it took me a while to realize that it was exactly what the Messages App of iOS 7 does, so of course I couldnt resist to do this: http://code.juan.me/demos/springy/messages/html/. And now we can say that Apple is using car suspension physics in some of their Apps (?).

I also wanted to try this in a sort of more real use case, so I made two more demos using a grid prototype, this one with images: http://code.juan.me/demos/springy/grid-prototype/html/ and this one more clean in white: http://code.juan.me/demos/springy/grid-prototype-2/html/

The Code

Note: Ill only go through the parts of code related to the spring effect of the first demo, if you want to see more how the demo was made feel free to take a look at the full code.

var options  = {
    stiffness: 100,
    friction: 20,
    threshold: 0.03
}, 
inMotion = Array(false, false, false, false, false, false)

First at all we define the spring properties, the params stiffness and friction talk by them self. The threshold option is basically how long you want to keep the spring working, so like if acceleration is equal or less than 0.03 it will stop. The inMotion array specifies which of the squares are currently in motion.

core.onScrolling = function(e) {
    lastScrollY = scrollY;
    scrollY = window.scrollY;

    for (var i = 0; i <= 5; i++) inMotion[i] = true;

    if (!animating) {
        animating = true;
        requestAnimFrame(core.stepScrolling);           
    }
}

When the scrolling starts we set the flag of motion for each square in true and we also call core.stepScrolling using requestAnimFrame.

core.stepScrolling = function() {
    $.each($boxItems, function(i, v) {
        var $thisItem = $(v);

        if (inMotion[i]) {
            if (scrollY - lastScrollY > 0) {
                acceleration[i] = (options.stiffness + (i * 5)) * ((scrollY + 50)  + (i * 40) - parseInt($thisItem.css("top"))) - options.friction * velocity[i];
            } else {
                acceleration[i] = (options.stiffness - (i * 5)) * ((scrollY + 50) + (i * 40) - parseInt($thisItem.css("top"))) - options.friction * velocity[i];
            }
            velocity[i] += acceleration[i] * frameRate;
            newTop[i] += velocity[i] * frameRate;
            inMotion[i] = Math.abs(acceleration[i]) >= options.threshold || Math.abs(velocity[i]) >= options.threshold;

            $thisItem.css({top: newTop[i]});
        } else {
            core.completeScrolling();
        }
    });
    if (animating) requestAnimFrame(core.stepScrolling);
}

Here we go through all the square elements, if it is still in motion we calculate and set the acceleration, velocity and new position for it. When the squares are not in motion we call core.completeScrolling.

core.completeScrolling = function() {
    for (var i = 0; i <= 5; i++) {
        acceleration[i] = 0;
        velocity[i] = 0;
        inMotion[i] = false;
    }
    animating = false;
    cancelAnimationFrame(core.stepScrolling);
}

And finally, on completeScrolling we reset all our variables and stop the Request Animation Frame that was calling core.stepScrolling.

Update — Jan 27, 2014.

I put these demos in a GitHub repo because some people wanted to contribute with a few improvements in performance :)

https://github.com/juancabrera/demos

Related Posts

Up Again

How to deploy a Node Lambda function

React Asset Loader

Using ES6 modules in the browser

My front-end codebase

Javascript 360