start.mjs
7.28 KB
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import { secondsToMilliseconds } from 'motion-utils';
import { GroupAnimation } from '../animation/GroupAnimation.mjs';
import { NativeAnimation } from '../animation/NativeAnimation.mjs';
import { NativeAnimationWrapper } from '../animation/NativeAnimationWrapper.mjs';
import { getValueTransition } from '../animation/utils/get-value-transition.mjs';
import { mapEasingToNativeEasing } from '../animation/waapi/easing/map-easing.mjs';
import { applyGeneratorOptions } from '../animation/waapi/utils/apply-generator.mjs';
import { chooseLayerType } from './utils/choose-layer-type.mjs';
import { css } from './utils/css.mjs';
import { getViewAnimationLayerInfo } from './utils/get-layer-info.mjs';
import { getViewAnimations } from './utils/get-view-animations.mjs';
import { hasTarget } from './utils/has-target.mjs';
const definitionNames = ["layout", "enter", "exit", "new", "old"];
function startViewAnimation(builder) {
const { update, targets, options: defaultOptions } = builder;
if (!document.startViewTransition) {
return new Promise(async (resolve) => {
await update();
resolve(new GroupAnimation([]));
});
}
// TODO: Go over existing targets and ensure they all have ids
/**
* If we don't have any animations defined for the root target,
* remove it from being captured.
*/
if (!hasTarget("root", targets)) {
css.set(":root", {
"view-transition-name": "none",
});
}
/**
* Set the timing curve to linear for all view transition layers.
* This gets baked into the keyframes, which can't be changed
* without breaking the generated animation.
*
* This allows us to set easing via updateTiming - which can be changed.
*/
css.set("::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*)", { "animation-timing-function": "linear !important" });
css.commit(); // Write
const transition = document.startViewTransition(async () => {
await update();
// TODO: Go over new targets and ensure they all have ids
});
transition.finished.finally(() => {
css.remove(); // Write
});
return new Promise((resolve) => {
transition.ready.then(() => {
const generatedViewAnimations = getViewAnimations();
const animations = [];
/**
* Create animations for each of our explicitly-defined subjects.
*/
targets.forEach((definition, target) => {
// TODO: If target is not "root", resolve elements
// and iterate over each
for (const key of definitionNames) {
if (!definition[key])
continue;
const { keyframes, options } = definition[key];
for (let [valueName, valueKeyframes] of Object.entries(keyframes)) {
if (!valueKeyframes)
continue;
const valueOptions = {
...getValueTransition(defaultOptions, valueName),
...getValueTransition(options, valueName),
};
const type = chooseLayerType(key);
/**
* If this is an opacity animation, and keyframes are not an array,
* we need to convert them into an array and set an initial value.
*/
if (valueName === "opacity" &&
!Array.isArray(valueKeyframes)) {
const initialValue = type === "new" ? 0 : 1;
valueKeyframes = [initialValue, valueKeyframes];
}
/**
* Resolve stagger function if provided.
*/
if (typeof valueOptions.delay === "function") {
valueOptions.delay = valueOptions.delay(0, 1);
}
valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
const animation = new NativeAnimation({
...valueOptions,
element: document.documentElement,
name: valueName,
pseudoElement: `::view-transition-${type}(${target})`,
keyframes: valueKeyframes,
});
animations.push(animation);
}
}
});
/**
* Handle browser generated animations
*/
for (const animation of generatedViewAnimations) {
if (animation.playState === "finished")
continue;
const { effect } = animation;
if (!effect || !(effect instanceof KeyframeEffect))
continue;
const { pseudoElement } = effect;
if (!pseudoElement)
continue;
const name = getViewAnimationLayerInfo(pseudoElement);
if (!name)
continue;
const targetDefinition = targets.get(name.layer);
if (!targetDefinition) {
/**
* If transition name is group then update the timing of the animation
* whereas if it's old or new then we could possibly replace it using
* the above method.
*/
const transitionName = name.type === "group" ? "layout" : "";
let animationTransition = {
...getValueTransition(defaultOptions, transitionName),
};
animationTransition.duration && (animationTransition.duration = secondsToMilliseconds(animationTransition.duration));
animationTransition =
applyGeneratorOptions(animationTransition);
const easing = mapEasingToNativeEasing(animationTransition.ease, animationTransition.duration);
effect.updateTiming({
delay: secondsToMilliseconds(animationTransition.delay ?? 0),
duration: animationTransition.duration,
easing,
});
animations.push(new NativeAnimationWrapper(animation));
}
else if (hasOpacity(targetDefinition, "enter") &&
hasOpacity(targetDefinition, "exit") &&
effect
.getKeyframes()
.some((keyframe) => keyframe.mixBlendMode)) {
animations.push(new NativeAnimationWrapper(animation));
}
else {
animation.cancel();
}
}
resolve(new GroupAnimation(animations));
});
});
}
function hasOpacity(target, key) {
return target?.[key]?.keyframes.opacity;
}
export { startViewAnimation };
//# sourceMappingURL=start.mjs.map