-
Notifications
You must be signed in to change notification settings - Fork 739
/
mix-values.ts
118 lines (105 loc) · 3.64 KB
/
mix-values.ts
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
import { circOut, linear, mix, progress as calcProgress } from "popmotion"
import { percent } 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
export function mixValues(
target: ResolvedValues,
follow: ResolvedValues,
lead: ResolvedValues,
progress: number,
shouldCrossfadeOpacity: boolean,
isOnlyMember: boolean
) {
if (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 if (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
target[borderLabel] = Math.max(
mix(asNumber(followRadius), asNumber(leadRadius), progress),
0
)
if (percent.test(leadRadius) || percent.test(followRadius)) {
target[borderLabel] += "%"
}
}
/**
* Mix rotation
*/
if (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))
}
}