148 lines
5.2 KiB
JavaScript
148 lines
5.2 KiB
JavaScript
import { fillWildcards } from './utils/fill-wildcards.mjs';
|
|
import { removeNonTranslationalTransform } from './utils/unit-conversion.mjs';
|
|
import { frame } from '../../frameloop/frame.mjs';
|
|
|
|
const toResolve = new Set();
|
|
let isScheduled = false;
|
|
let anyNeedsMeasurement = false;
|
|
let isForced = false;
|
|
function measureAllKeyframes() {
|
|
if (anyNeedsMeasurement) {
|
|
const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement);
|
|
const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element));
|
|
const transformsToRestore = new Map();
|
|
/**
|
|
* Write pass
|
|
* If we're measuring elements we want to remove bounding box-changing transforms.
|
|
*/
|
|
elementsToMeasure.forEach((element) => {
|
|
const removedTransforms = removeNonTranslationalTransform(element);
|
|
if (!removedTransforms.length)
|
|
return;
|
|
transformsToRestore.set(element, removedTransforms);
|
|
element.render();
|
|
});
|
|
// Read
|
|
resolversToMeasure.forEach((resolver) => resolver.measureInitialState());
|
|
// Write
|
|
elementsToMeasure.forEach((element) => {
|
|
element.render();
|
|
const restore = transformsToRestore.get(element);
|
|
if (restore) {
|
|
restore.forEach(([key, value]) => {
|
|
element.getValue(key)?.set(value);
|
|
});
|
|
}
|
|
});
|
|
// Read
|
|
resolversToMeasure.forEach((resolver) => resolver.measureEndState());
|
|
// Write
|
|
resolversToMeasure.forEach((resolver) => {
|
|
if (resolver.suspendedScrollY !== undefined) {
|
|
window.scrollTo(0, resolver.suspendedScrollY);
|
|
}
|
|
});
|
|
}
|
|
anyNeedsMeasurement = false;
|
|
isScheduled = false;
|
|
toResolve.forEach((resolver) => resolver.complete(isForced));
|
|
toResolve.clear();
|
|
}
|
|
function readAllKeyframes() {
|
|
toResolve.forEach((resolver) => {
|
|
resolver.readKeyframes();
|
|
if (resolver.needsMeasurement) {
|
|
anyNeedsMeasurement = true;
|
|
}
|
|
});
|
|
}
|
|
function flushKeyframeResolvers() {
|
|
isForced = true;
|
|
readAllKeyframes();
|
|
measureAllKeyframes();
|
|
isForced = false;
|
|
}
|
|
class KeyframeResolver {
|
|
constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) {
|
|
this.state = "pending";
|
|
/**
|
|
* Track whether this resolver is async. If it is, it'll be added to the
|
|
* resolver queue and flushed in the next frame. Resolvers that aren't going
|
|
* to trigger read/write thrashing don't need to be async.
|
|
*/
|
|
this.isAsync = false;
|
|
/**
|
|
* Track whether this resolver needs to perform a measurement
|
|
* to resolve its keyframes.
|
|
*/
|
|
this.needsMeasurement = false;
|
|
this.unresolvedKeyframes = [...unresolvedKeyframes];
|
|
this.onComplete = onComplete;
|
|
this.name = name;
|
|
this.motionValue = motionValue;
|
|
this.element = element;
|
|
this.isAsync = isAsync;
|
|
}
|
|
scheduleResolve() {
|
|
this.state = "scheduled";
|
|
if (this.isAsync) {
|
|
toResolve.add(this);
|
|
if (!isScheduled) {
|
|
isScheduled = true;
|
|
frame.read(readAllKeyframes);
|
|
frame.resolveKeyframes(measureAllKeyframes);
|
|
}
|
|
}
|
|
else {
|
|
this.readKeyframes();
|
|
this.complete();
|
|
}
|
|
}
|
|
readKeyframes() {
|
|
const { unresolvedKeyframes, name, element, motionValue } = this;
|
|
// If initial keyframe is null we need to read it from the DOM
|
|
if (unresolvedKeyframes[0] === null) {
|
|
const currentValue = motionValue?.get();
|
|
// TODO: This doesn't work if the final keyframe is a wildcard
|
|
const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
|
|
if (currentValue !== undefined) {
|
|
unresolvedKeyframes[0] = currentValue;
|
|
}
|
|
else if (element && name) {
|
|
const valueAsRead = element.readValue(name, finalKeyframe);
|
|
if (valueAsRead !== undefined && valueAsRead !== null) {
|
|
unresolvedKeyframes[0] = valueAsRead;
|
|
}
|
|
}
|
|
if (unresolvedKeyframes[0] === undefined) {
|
|
unresolvedKeyframes[0] = finalKeyframe;
|
|
}
|
|
if (motionValue && currentValue === undefined) {
|
|
motionValue.set(unresolvedKeyframes[0]);
|
|
}
|
|
}
|
|
fillWildcards(unresolvedKeyframes);
|
|
}
|
|
setFinalKeyframe() { }
|
|
measureInitialState() { }
|
|
renderEndStyles() { }
|
|
measureEndState() { }
|
|
complete(isForcedComplete = false) {
|
|
this.state = "complete";
|
|
this.onComplete(this.unresolvedKeyframes, this.finalKeyframe, isForcedComplete);
|
|
toResolve.delete(this);
|
|
}
|
|
cancel() {
|
|
if (this.state === "scheduled") {
|
|
toResolve.delete(this);
|
|
this.state = "pending";
|
|
}
|
|
}
|
|
resume() {
|
|
if (this.state === "pending")
|
|
this.scheduleResolve();
|
|
}
|
|
}
|
|
|
|
export { KeyframeResolver, flushKeyframeResolvers };
|