All files / animations/utils find-spring.ts

83.33% Statements 45/54
57.14% Branches 8/14
71.43% Functions 5/7
82% Lines 41/50

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113                    5x 5x 5x 5x 5x     9753x 9753x 9753x 9753x         9753x         9753x         9753x 9753x   9753x       9753x 214566x 214566x 214566x 214566x 214566x 214566x     9753x 107283x 107283x 107283x   107283x 107283x 107283x 107283x 107283x                                     9753x 9753x   9753x           9753x 9753x             5x           9753x 9753x 107283x   9753x       321870x    
import { warning } from "hey-listen"
import { clamp } from "../../utils/clamp"
import { SpringOptions } from "../types"
 
/**
 * This is ported from the Framer implementation of duration-based spring resolution.
 */
 
type Resolver = (num: number) => number
 
const safeMin = 0.001
export const minDuration = 0.01
export const maxDuration = 10.0
export const minDamping = 0.05
export const maxDamping = 1
 
export function findSpring({
    duration = 800,
    bounce = 0.25,
    velocity = 0,
    mass = 1,
}: SpringOptions) {
    let envelope: Resolver
    let derivative: Resolver
 
    warning(
        duration <= maxDuration * 1000,
        "Spring duration must be 10 seconds or less"
    )
 
    let dampingRatio = 1 - bounce
 
    /**
     * Restrict dampingRatio and duration to within acceptable ranges.
     */
    dampingRatio = clamp(minDamping, maxDamping, dampingRatio)
    duration = clamp(minDuration, maxDuration, duration / 1000)
 
    Eif (dampingRatio < 1) {
        /**
         * Underdamped spring
         */
        envelope = (undampedFreq) => {
            const exponentialDecay = undampedFreq * dampingRatio
            const delta = exponentialDecay * duration
            const a = exponentialDecay - velocity
            const b = calcAngularFreq(undampedFreq, dampingRatio)
            const c = Math.exp(-delta)
            return safeMin - (a / b) * c
        }
 
        derivative = (undampedFreq) => {
            const exponentialDecay = undampedFreq * dampingRatio
            const delta = exponentialDecay * duration
            const d = delta * velocity + velocity
            const e =
                Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration
            const f = Math.exp(-delta)
            const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio)
            const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1
            return (factor * ((d - e) * f)) / g
        }
    } else {
        /**
         * Critically-damped spring
         */
        envelope = (undampedFreq) => {
            const a = Math.exp(-undampedFreq * duration)
            const b = (undampedFreq - velocity) * duration + 1
            return -safeMin + a * b
        }
 
        derivative = (undampedFreq) => {
            const a = Math.exp(-undampedFreq * duration)
            const b = (velocity - undampedFreq) * (duration * duration)
            return a * b
        }
    }
 
    const initialGuess = 5 / duration
    const undampedFreq = approximateRoot(envelope, derivative, initialGuess)
 
    Iif (isNaN(undampedFreq)) {
        return {
            stiffness: 100,
            damping: 10,
        }
    } else {
        const stiffness = Math.pow(undampedFreq, 2) * mass
        return {
            stiffness,
            damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
        }
    }
}
 
const rootIterations = 12
function approximateRoot(
    envelope: Resolver,
    derivative: Resolver,
    initialGuess: number
): number {
    let result = initialGuess
    for (let i = 1; i < rootIterations; i++) {
        result = result - envelope(result) / derivative(result)
    }
    return result
}
 
export function calcAngularFreq(undampedFreq: number, dampingRatio: number) {
    return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio)
}