Last active
April 9, 2020 15:07
-
-
Save maximilliangeorge/d03db3660aa1e0f3ce13eeb267576a66 to your computer and use it in GitHub Desktop.
After Effects Expressions
This file contains hidden or 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
// MOMENTUM SCROLLING | |
// | |
// Nice easings without the hassle. Doesn't work well for very long distances. | |
// | |
// 1. Separate dimensions on layer to scroll (right click _> separate dimensions) | |
// 2. Apply the expression to the layers Y Position | |
// 3. Add keyframes. Easing will adapt to mimic momentum scrolling. | |
const c = thisLayer("Transform")("Y Position") | |
var NEWTON_ITERATIONS = 4; | |
var NEWTON_MIN_SLOPE = 0.001; | |
var SUBDIVISION_PRECISION = 0.0000001; | |
var SUBDIVISION_MAX_ITERATIONS = 10; | |
var kSplineTableSize = 11; | |
var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); | |
var float32ArraySupported = typeof Float32Array === 'function'; | |
function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } | |
function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } | |
function C (aA1) { return 3.0 * aA1; } | |
function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } | |
function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); } | |
function binarySubdivide (aX, aA, aB, mX1, mX2) { | |
var currentX, currentT, i = 0 | |
do { | |
currentT = aA + (aB - aA) / 2.0 | |
currentX = calcBezier(currentT, mX1, mX2) - aX | |
if (currentX > 0.0) { | |
aB = currentT | |
} else { | |
aA = currentT | |
} | |
} while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); | |
return currentT; | |
} | |
function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) { | |
for (var i = 0; i < NEWTON_ITERATIONS; ++i) { | |
var currentSlope = getSlope(aGuessT, mX1, mX2) | |
if (currentSlope === 0.0) { | |
return aGuessT | |
} | |
var currentX = calcBezier(aGuessT, mX1, mX2) - aX | |
aGuessT -= currentX / currentSlope | |
} | |
return aGuessT | |
} | |
function LinearEasing (x) { | |
return x | |
} | |
function bezier(mX1, mY1, mX2, mY2) { | |
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { | |
throw new Error('bezier x values must be in [0, 1] range') | |
} | |
if (mX1 === mY1 && mX2 === mY2) { | |
return LinearEasing | |
} | |
var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize) | |
for (var i = 0; i < kSplineTableSize; ++i) { | |
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2) | |
} | |
function getTForX (aX) { | |
var intervalStart = 0.0 | |
var currentSample = 1 | |
var lastSample = kSplineTableSize - 1 | |
for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { | |
intervalStart += kSampleStepSize | |
} | |
--currentSample | |
// Interpolate to provide an initial guess for t | |
var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]) | |
var guessForT = intervalStart + dist * kSampleStepSize | |
var initialSlope = getSlope(guessForT, mX1, mX2) | |
if (initialSlope >= NEWTON_MIN_SLOPE) { | |
return newtonRaphsonIterate(aX, guessForT, mX1, mX2) | |
} else if (initialSlope === 0.0) { | |
return guessForT; | |
} else { | |
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); | |
} | |
} | |
return function BezierEasing (x) { | |
if (x === 0 || x === 1) { | |
return x | |
} | |
return calcBezier(getTForX(x), mY1, mY2) | |
} | |
} | |
try { | |
let index = c.numKeys | |
while (c.key(index).time > time) { | |
index-- | |
} | |
const transform = (value, range1, range2) => { | |
return ( (value - range1[0]) / (range1[1] - range1[0]) ) * (range2[1] - range2[0]) + range2[0] | |
} | |
const k1 = c.key(index) | |
const k2 = c.key(index + 1) | |
const progress = (time - k1.time) / (k2.time - k1.time) | |
const duration = k2.time - k1.time | |
const distance = Math.abs(k2.value - k1.value) | |
const pxFactor = 1800 | |
const magnitude = duration * pxFactor / distance | |
const attack = 0.11 * magnitude | |
const ease = 1 - attack | |
const easing = bezier(attack, 0, attack / 1.5, 1) | |
transform(easing(progress), [0, 1], [k1.value, k2.value]) | |
} catch (e) { | |
value | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment