Last active
October 13, 2016 16:37
-
-
Save astashov/b90c0888c25272399262 to your computer and use it in GitHub Desktop.
Rebound.js-like spring animation in SCSS/SASS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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