Skip to content

Instantly share code, notes, and snippets.

@danielgolden
Created October 20, 2022 13:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielgolden/e71ad6fd19109ab34dd31f62cb25d920 to your computer and use it in GitHub Desktop.
Save danielgolden/e71ad6fd19109ab34dd31f62cb25d920 to your computer and use it in GitHub Desktop.
import "./custom-spring.css";
// import { Pane } from "tweakpane";
const box = document.querySelector(".box");
interface SpringOptions {
stiffness: number;
mass: number;
damping: number;
end: number;
start: number;
velocity: number;
style: Keyframe;
}
// Adapted from https://blog.maximeheckel.com/posts/the-physics-behind-spring-animations
const generateSpringKeyframes = (springOptions: SpringOptions) => {
/* Object position and velocity. */
let x = springOptions.start;
let v = springOptions.velocity;
/* Spring stiffness, in kg / s^2 */
let k = -springOptions.stiffness;
/* Damping constant, in kg / s */
let d = -springOptions.damping;
/* Framerate: we want 60 fps hence the framerate here is at 1/60 */
let frameRate = 1 / 60;
let positions = [];
let i = 0;
/* We loop 600 times, i.e. for 600 frames which is equivalent to 10s*/
while (i < 120) {
let Fspring = k * (x - springOptions.end);
let Fdamping = d * v;
let a = (Fspring + Fdamping) / springOptions.mass;
v += a * frameRate;
x += v * frameRate;
i++;
positions.push({
position: x,
frame: i,
});
}
const keyframes = positions.map(function (frame) {
const styleProperty: string = Object.keys(springOptions.style)[0];
let styleValue = springOptions.style[styleProperty];
styleValue = styleValue?.toString()?.replace("$", frame.position.toString());
return {
[styleProperty]: styleValue,
};
});
return keyframes;
};
const animationOptions: KeyframeAnimationOptions = {
duration: 700,
iterations: 1,
fill: "forwards",
};
const springDepth = { value: -50 };
const springPressOptions: SpringOptions = {
stiffness: 105,
mass: 2.2,
damping: 10.6,
end: springDepth.value,
start: 0,
velocity: 0,
style: { transform: `translate3d(0, 0, $px)` },
};
// const pane = new Pane();
// const pressControls = pane.addFolder({ title: "Mouse down options" });
// pressControls.addInput(springPressOptions, "end", { min: -150, max: 150, step: 1, label: "End" });
// pressControls.addInput(springPressOptions, "stiffness", { min: 0, max: 500, step: 1, label: "Stiffness" });
// pressControls.addInput(springPressOptions, "mass", { min: 0, max: 5, step: 0.1, label: "Mass" });
// pressControls.addInput(springPressOptions, "damping", { min: 1, max: 50, step: 1, label: "Damping" });
const springReleaseOptions: SpringOptions = {
stiffness: 105,
mass: 2.2,
damping: 10.6,
end: 0,
start: springDepth.value,
velocity: 0,
style: { transform: `translate3d(0, 0, $px)` },
};
// const releaseControls = pane.addFolder({ title: "Mouse up options" });
// releaseControls.addInput(springReleaseOptions, "end", { min: -150, max: 150, step: 1, label: "End" });
// releaseControls.addInput(springReleaseOptions, "stiffness", { min: 0, max: 500, step: 1, label: "Stiffness" });
// releaseControls.addInput(springReleaseOptions, "mass", { min: 0, max: 5, step: 0.1, label: "Mass" });
// releaseControls.addInput(springReleaseOptions, "damping", { min: 1, max: 50, step: 1, label: "Damping" });
box?.addEventListener("mousedown", () => {
box?.animate(generateSpringKeyframes(springPressOptions), animationOptions);
});
const springToNaturalState = (element: Element) => {
const matrix3d = getComputedStyle(element).transform;
const matrix3dArray = matrix3d.substring(9, matrix3d.length).split(",");
const cleanMatrix3d = matrix3dArray.map((item: string) => item.trim());
const currentTranslateZ = cleanMatrix3d[14];
springReleaseOptions.start = parseInt(currentTranslateZ);
element?.animate(generateSpringKeyframes(springReleaseOptions), animationOptions);
};
box?.addEventListener("click", (e) => {
springToNaturalState(e.target as Element);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment