-
Notifications
You must be signed in to change notification settings - Fork 739
/
mix-values.ts
130 lines (115 loc) · 3.99 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
119
120
121
122
123
124
125
126
127
128
129
130
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
) {
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
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
*/
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))
}
}