All files / animations index.ts

91.55% Statements 65/71
93.33% Branches 56/60
60% Functions 6/10
93.33% Lines 56/60

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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135                              2x                   38x 38x 38x 38x 38x 38x 38x 38x 38x 38x 38x 38x 38x   38x   38x 38x   38x 38x       38x   38x 2x     2x 2x     38x     21x   21x 6x 6x             15x 15x     21x 21x       35x 35x       806x   806x   806x 746x 746x   746x 25x   746x     806x   806x 116x   116x 81x             35x           38x 38x 38x     38x   38x   3x 3x        
import {
    AnimationOptions,
    Driver,
    DriverControls,
    KeyframeOptions,
} from "./types"
import { detectAnimationFromOptions } from "./utils/detect-animation-from-options"
import sync, { cancelSync, FrameData } from "framesync"
import { interpolate } from "../utils/interpolate"
import {
    loopElapsed,
    reverseElapsed,
    hasRepeatDelayElapsed,
} from "./utils/elapsed"
 
const framesync: Driver = (update) => {
    const passTimestamp = ({ delta }: FrameData) => update(delta)
 
    return {
        start: () => sync.update(passTimestamp, true, true),
        stop: () => cancelSync.update(passTimestamp),
    }
}
 
export function animate<V = number>({
    from,
    autoplay = true,
    driver = framesync,
    elapsed = 0,
    repeat: repeatMax = 0,
    repeatType = "loop",
    repeatDelay = 0,
    onPlay,
    onStop,
    onComplete,
    onRepeat,
    onUpdate,
    ...options
}: AnimationOptions<V>) {
    let { to } = options
    let driverControls: DriverControls
    let repeatCount = 0
    let computedDuration = (options as KeyframeOptions<V>).duration
    let latest: V
    let isComplete = false
    let isForwardPlayback = true
 
    let interpolateFromNumber: (t: number) => V
 
    const animator = detectAnimationFromOptions(options)
 
    if ((animator as any).needsInterpolation?.(from, to)) {
        interpolateFromNumber = interpolate([0, 100], [from, to], {
            clamp: false,
        }) as (t: number) => V
        from = 0 as any
        to = 100 as any
    }
 
    const animation = animator({ ...options, from, to } as any)
 
    function repeat() {
        repeatCount++
 
        if (repeatType === "reverse") {
            isForwardPlayback = repeatCount % 2 === 0
            elapsed = reverseElapsed(
                elapsed,
                computedDuration,
                repeatDelay,
                isForwardPlayback
            )
        } else {
            elapsed = loopElapsed(elapsed, computedDuration, repeatDelay)
            if (repeatType === "mirror") animation.flipTarget()
        }
 
        isComplete = false
        onRepeat && onRepeat()
    }
 
    function complete() {
        driverControls.stop()
        onComplete && onComplete()
    }
 
    function update(delta: number) {
        if (!isForwardPlayback) delta = -delta
 
        elapsed += delta
 
        if (!isComplete) {
            const state = animation.next(Math.max(0, elapsed))
            latest = state.value as any
 
            if (interpolateFromNumber)
                latest = interpolateFromNumber(latest as any)
 
            isComplete = isForwardPlayback ? state.done : elapsed <= 0
        }
 
        onUpdate?.(latest)
 
        if (isComplete) {
            if (repeatCount === 0) computedDuration ??= elapsed
 
            if (repeatCount < repeatMax) {
                hasRepeatDelayElapsed(
                    elapsed,
                    computedDuration,
                    repeatDelay,
                    isForwardPlayback
                ) && repeat()
            } else {
                complete()
            }
        }
    }
 
    function play() {
        onPlay?.()
        driverControls = driver(update)
        driverControls.start()
    }
 
    autoplay && play()
 
    return {
        stop: () => {
            onStop?.()
            driverControls.stop()
        },
    }
}