Springify.js 🌿

Springify is a tiny lil' (under 1kb gzip) utility function for creating dynamic spring animations.

It's a bare bones but flexible utility that can be used to create a range of useful and fun interactions.

Install via npm

          
npm i springify
          
        

Basic usage

          
import { Springify } from 'springify';

const spring = new Springify();

// Subscribe to updates. This will fire every frame and pass through the current values.
spring.subscribe(({ output, velocity }) => {
  // output is the output value from the spring
  // velocity is the velocity our spring is moving at
  console.log(output);
  console.log(velocity);
});

// Subscribe to the end event when the spring settles. This will fire once each time the spring stops.
spring.subscribe(({ output }) => {
  // output is the output value from the spring once it has settled
  console.log(output);
}, 'end');

// Update the value and springify will beging animating from the initial input value to the new one.
spring.input = 500;
          
        

Default values

            
const spring = new Springify({
    input: 0, // the initial value
    stiffness: 0, // effective range from 0 - 100;
    damping: 30, // effective range from 0 - 100;
    mass: 20, // effective range from 0 - 100;
  });
            
          

Examples

Spring from --> to

Using static values

Try interrupting the animation mid way through.

            
import { Springify } from 'springify';

const sailboat = document.querySelector('.sailboat');
const sailAwayButton = document.querySelector('.sailboat--away');
const sailBackButton = document.querySelector('.sailboat--back');

let direction = 'right';

const springySailboat = new Springify({
  stiffness: 10,
  damping: 80,
  mass: 50,
});

springySailboat.subscribe(({ output, velocity }) => {
  sailboat.style.transform = `rotate(${velocity * -0.3}deg) scaleX(${direction === 'right' ? -1 : 1})`;
  sailboat.style.left = `${output}%`;
});

sailAway.addEventListener('click', () => {
  springySailboat.input = 100;
  direction = 'right';
});

sailBack.addEventListener('click', () => {
  springySailboat.input = 0;
  direction = 'left';
});
            
          

Button press

              
import { Springify } from 'springify';

const buttons = document.querySelectorAll('.demo-button');

buttons.forEach((button) => {
  const springyButton = new Springify({
    input: 1,
    stiffness: 20,
    damping: 30,
    mass: 10,
  });

  springyButton.subscribe(({ output, velocity }) => {
    button.style.transform = `scaleX(${output + velocity * 0.1}) scaleY(${output})`;
  });

  button.addEventListener('mousedown', () => {
    springyButton.input = 0.75;
  });

  button.addEventListener('touchstart', (e) => {
    e.preventDefault();
    springyButton.input = 0.75;
  });

  button.addEventListener('mouseup', () => {
    springyButton.input = 1.1;
  });

  button.addEventListener('mouseenter', () => {
    springyButton.input = 1.1;
  });

  button.addEventListener('mouseout', () => {
    springyButton.input = 1;
  });

  button.addEventListener('touchend', () => {
    springyButton.input = 1;
  });
});
              
            

Dynamic spring linked to scrolling

🕷
          
import { Springify } from 'springify';
                
const spider = document.querySelector('.spider');
const spiderArea = document.querySelector('.section--example-spider');

const springySpider = new Springify({
  stiffness: 30,
  damping: 50,
  mass: 10,
});

springySpider.subscribe(({ output, velocity }) => {
  spider.style.transform = `translateY(${output}px)`;
  spiderBody.style.transform = `rotate(${1 + Math.abs(velocity * 1)})`;
});

window.addEventListener('scroll', () => {
  springySpider.input = window.scrollY - spiderArea.offsetTop + window.innerHeight * 0.5;
});
          
        

Dynamic spring linked to mouse movements

🚁
          
import { Springify } from 'springify';

const helicopter = document.querySelector('.helicopter');
const helicopterDemo = document.querySelector('.section--example-helicopter');


const springyHelicopter = {
  x: new Springify(),
  y: new Springify(),
};

const helicopterTransform = (x: number, y: number, rotation: number) => {
  return `translate(${x}px, ${y}px) rotate(${rotation}deg)`;
};

springyHelicopter.x.subscribe(({ output, velocity }) => {
  helicopter.style.transform = helicopterTransform(output, springyHelicopter.y.output, velocity * 0.05);
});

const helicopterMove = (clientX, clientY) => {
  // normalize the mouse coordinates to the helicopter demo area
  const helicopterDemoRect = helicopterDemo.getBoundingClientRect();
  const relativeX = clientX - (helicopterDemoRect.left + helicopterDemoRect.width * 0.5);

  const relativeY = clientY - (helicopterDemoRect.top + helicopterDemoRect.height * 0.5);

  // Send our relative mouse values as the inputs to the springs
  springyHelicopter.x.input = relativeX;
  springyHelicopter.y.input = relativeY;
};

helicopterDemo.addEventListener('mousemove', (e) => {
  if (!e.clientX || !e.clientY) return;
  helicopterMove(e.clientX, e.clientY);
});

helicopterDemo.addEventListener('mouseout', () => {
  springyHelicopter.x.input = 0;
  springyHelicopter.y.input = 0;
});

helicopterDemo.addEventListener('touchmove', (e) => {
  if (e.touches === undefined) return;
  helicopterMove(e.touches[0].clientX, e.touches[0].clientY);
});