Skip to content

Instantly share code, notes, and snippets.

@astashov
Last active October 13, 2016 16:37
Show Gist options
  • Save astashov/b90c0888c25272399262 to your computer and use it in GitHub Desktop.
Save astashov/b90c0888c25272399262 to your computer and use it in GitHub Desktop.
Rebound.js-like spring animation in SCSS/SASS
$solver_timestamp_sec: 0.01;
// Rebound.js-like spring animations in SCSS.
// There is a bunch of functions, which helps generating keyframes for you spring
// animations, if you (like me) really want to avoid doing that in JavaScript.
//
// It only generates values for one spring, with given friction, tension and end value
// (i.e. it doesn't support spring systems)
// Friction and tension are matched to the values used in Origami, so you can use whatever
// your designers put in a Quartz Composer file in "Bouncy Animation" blocks :)
//
// Usage:
//
// // Generate the values of the spring first, with tension = 50, friction = 6 and end-value = 1
// $spring-50-6-1-values: spring-values(50, 6, 1);
//
// @include keyframes(some-animation) {
// // We will reduce the number of values to 100 / 5 = 20, so,
// // we'll generate keyframes for 0%, 5%, 10%, 15%, etc.
// $step: 5;
// $normalized-values: spring-normalized-values($spring-50-6-1-values, $step);
//
// $i: 0;
// @each $value in $normalized-values {
// #{percentage(min($i * $step, 100) / 100)} {
// // $value will have values in range [0..~1.2], and will end up with 1
// @include translateY($value * 10px);
// }
// $i: $i + 1;
// }
// }
//
// .some-node {
// @include animation-name(some-animation);
// @include animation-duration(spring-duration($spring-50-6-1-values));
// }
@function spring-duration($values) {
@return length($values) * $solver_timestamp_sec * 1s;
}
@function spring-normalized-values($values, $step) {
$count: length($values);
$rel-step: 100 / $count;
$skip: $step / $rel-step;
$i: 1;
$normalized-values: ();
@while $i <= $count {
$normalized-values: append($normalized-values, nth($values, round($i)));
$i: $i + $skip;
}
$normalized-values: append($normalized-values, nth($values, $count));
@return $normalized-values;
}
@function spring-values($tension, $friction, $endValue) {
$tension: ($tension - 30.0) * 3.62 + 194.0; // Converting from Origami tension
$friction: ($friction - 8.0) * 3.0 + 25.0; // Converting from Origami friction
$values: ();
$position: 0;
$velocity: 0;
$wasAtRest: false;
$isAtRest: false;
@while (not ($isAtRest and $wasAtRest)) {
// Calculations shamelessly borrowed from Facebook's rebound.js
$aVelocity: $velocity;
$aAcceleration: ($tension * ($endValue - $position)) - $friction * $velocity;
$tempPosition: $position + $aVelocity * $solver_timestamp_sec * 0.5;
$tempVelocity: $velocity + $aAcceleration * $solver_timestamp_sec * 0.5;
$bVelocity: $tempVelocity;
$bAcceleration: ($tension * ($endValue - $tempPosition)) - $friction * $tempVelocity;
$tempPosition: $position + $bVelocity * $solver_timestamp_sec * 0.5;
$tempVelocity: $velocity + $bAcceleration * $solver_timestamp_sec * 0.5;
$cVelocity: $tempVelocity;
$cAcceleration: ($tension * ($endValue - $tempPosition)) - $friction * $tempVelocity;
$tempPosition: $position + $cVelocity * $solver_timestamp_sec * 0.5;
$tempVelocity: $velocity + $cAcceleration * $solver_timestamp_sec * 0.5;
$dVelocity: $tempVelocity;
$dAcceleration: ($tension * ($endValue - $tempPosition)) - $friction * $tempVelocity;
$dxdt: 1.0 / 6.0 * ($aVelocity + 2.0 * ($bVelocity + $cVelocity) + $dVelocity);
$dvdt: 1.0 / 6.0 * ($aAcceleration + 2.0 * ($bAcceleration + $cAcceleration) + $dAcceleration);
$position: $position + $dxdt * $solver_timestamp_sec;
$velocity: $velocity + $dvdt * $solver_timestamp_sec;
$values: append($values, $position);
$wasAtRest: $isAtRest;
$isAtRest: _spring-is-at-rest($tension, $velocity, $position, $endValue);
}
@return $values
}
// Private functions
@function _abs($value) {
@return sqrt(pow($value, 2))
}
@function _spring-is-at-rest($tension, $velocity, $position, $endValue) {
@return _abs($velocity) < 0.001 and (_abs($endValue - $position) <= 0.001 or $tension == 0)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment