It's a bare bones but flexible utility that can be used to create a range of useful and fun interactions.
npm i springify
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;
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;
});
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';
});
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;
});
});
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;
});
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);
});