All files / src/projection/animation mix-values.ts

77.5% Statements 31/40
52.63% Branches 30/57
83.33% Functions 5/6
80.56% Lines 29/36

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 13138x 38x       38x 38x   38x 10x   38x 10x   38x               7x                         7x                     7x 28x 28x 28x   28x   7x 7x     7x       7x 5x         5x 3x     2x             7x                   56x                                                     38x 38x             76x              
import { circOut, linear, mix, progress as calcProgress } from "popmotion"
import { percent, px } from "style-value-types"
import { ResolvedValues } from "../../render/types"
import { EasingFunction } from "../../types"
 
const borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"]
const numBorders = borders.length
 
const asNumber = (value: string | number) =>
    typeof value === "string" ? parseFloat(value) : value
 
const isPx = (value: string | number) =>
    typeof value === "number" || px.test(value)
 
export function mixValues(
    target: ResolvedValues,
    follow: ResolvedValues,
    lead: ResolvedValues,
    progress: number,
    shouldCrossfadeOpacity: boolean,
    isOnlyMember: boolean
) {
    Iif (shouldCrossfadeOpacity) {
        target.opacity = mix(
            0,
            // (follow?.opacity as number) ?? 0,
            // TODO Reinstate this if only child
            (lead.opacity as number) ?? 1,
            easeCrossfadeIn(progress)
        )
        target.opacityExit = mix(
            (follow.opacity as number) ?? 1,
            0,
            easeCrossfadeOut(progress)
        )
    } else Iif (isOnlyMember) {
        target.opacity = mix(
            (follow.opacity as number) ?? 1,
            (lead.opacity as number) ?? 1,
            progress
        )
    }
 
    /**
     * Mix border radius
     */
    for (let i = 0; i < numBorders; i++) {
        const borderLabel = `border${borders[i]}Radius`
        let followRadius = getRadius(follow, borderLabel)
        let leadRadius = getRadius(lead, borderLabel)
 
        if (followRadius === undefined && leadRadius === undefined) continue
 
        followRadius ||= 0
        leadRadius ||= 0
 
        const canMix =
            followRadius === 0 ||
            leadRadius === 0 ||
            isPx(followRadius) === isPx(leadRadius)
 
        if (canMix) {
            target[borderLabel] = Math.max(
                mix(asNumber(followRadius), asNumber(leadRadius), progress),
                0
            )
 
            if (percent.test(leadRadius) || percent.test(followRadius)) {
                target[borderLabel] += "%"
            }
        } else {
            target[borderLabel] = leadRadius
        }
    }
 
    /**
     * Mix rotation
     */
    Iif (follow.rotate || lead.rotate) {
        target.rotate = mix(
            (follow.rotate as number) || 0,
            (lead.rotate as number) || 0,
            progress
        )
    }
}
 
function getRadius(values: ResolvedValues, radiusName: string) {
    return values[radiusName] ?? values.borderRadius
}
 
// /**
//  * We only want to mix the background color if there's a follow element
//  * that we're not crossfading opacity between. For instance with switch
//  * AnimateSharedLayout animations, this helps the illusion of a continuous
//  * element being animated but also cuts down on the number of paints triggered
//  * for elements where opacity is doing that work for us.
//  */
// if (
//     !hasFollowElement &&
//     latestLeadValues.backgroundColor &&
//     latestFollowValues.backgroundColor
// ) {
//     /**
//      * This isn't ideal performance-wise as mixColor is creating a new function every frame.
//      * We could probably create a mixer that runs at the start of the animation but
//      * the idea behind the crossfader is that it runs dynamically between two potentially
//      * changing targets (ie opacity or borderRadius may be animating independently via variants)
//      */
//     leadState.backgroundColor = followState.backgroundColor = mixColor(
//         latestFollowValues.backgroundColor as string,
//         latestLeadValues.backgroundColor as string
//     )(p)
// }
 
const easeCrossfadeIn = compress(0, 0.5, circOut)
const easeCrossfadeOut = compress(0.5, 0.95, linear)
 
function compress(
    min: number,
    max: number,
    easing: EasingFunction
): EasingFunction {
    return (p: number) => {
        // Could replace ifs with clamp
        if (p < min) return 0
        if (p > max) return 1
        return easing(calcProgress(min, max, p))
    }
}