Created
August 25, 2023 23:31
-
-
Save snuffyDev/760aeba718593caa51be7e81e9726034 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { EasingFactory, EasingFunction, linear } from "./easing"; | |
import { MotionPath, PathTweenOptions } from "./motionPath"; | |
import type { KeyframeWithTransform } from "./types"; | |
import { skipFirstInvocation } from "./utils/function"; | |
import { is } from "./utils/is"; | |
// @ts-expect-error idk what broke but smth did | |
export interface TweenOptions extends KeyframeEffectOptions { | |
composite?: CompositeOperation; | |
delay?: number; | |
direction?: "normal" | "reverse" | "alternate" | "alternate-reverse"; | |
duration: number; | |
fill?: "none" | "forwards" | "backwards" | "both"; | |
iterations?: number | "infinite"; | |
playbackRate?: number; | |
easing?: string | EasingFactory; | |
} | |
export type CompositeOperation = | |
| "replace" | |
| "add" | |
| "accumulate" | |
| "auto" | |
| "none"; | |
/** | |
* Helper function for getting the CSS easing function from an easing factory or from a string | |
* | |
* If the easing is a string, it's assumed to be a CSS easing function and is returned as-is. | |
* @internal | |
* @param easing Easing function or string | |
* @returns CSS easing function (string) | |
*/ | |
const getEasingFunction = (easing: string | EasingFactory): string => { | |
if (is<EasingFactory>(easing, (thing) => typeof thing === "object")) { | |
return easing.css; | |
} | |
return easing; | |
}; | |
const createKeyframeEffect = ( | |
element: Element, | |
keyframes: KeyframeWithTransform[], | |
options: TweenOptions, | |
): KeyframeEffect => { | |
const effect = new KeyframeEffect( | |
element, | |
keyframes as Keyframe[], | |
options as KeyframeAnimationOptions, | |
); | |
return effect; | |
}; | |
const tick = () => | |
new Promise<number>((resolve) => requestAnimationFrame(resolve)); | |
const pathTween = ( | |
element: Element, | |
keyframes: KeyframeWithTransform[], | |
options: PathTweenOptions, | |
) => { | |
// TODO: use this somehow | |
let easing: string | EasingFunction = "linear"; | |
if (options.easing) { | |
const easingFactory = options.easing; | |
if (typeof easingFactory === "object") { | |
options.easing = easingFactory.css; | |
easing = easingFactory.calc; | |
} else if (typeof easingFactory === "string") { | |
easing = easingFactory; | |
} else { | |
easing = "linear"; | |
} | |
} | |
const motionPath = new MotionPath(element as HTMLElement, options.path, { | |
...options, | |
}); | |
motionPath.anchor = options.anchor ? options.anchor : "auto"; | |
const frames = motionPath.build( | |
keyframes, | |
typeof options.easing === "object" ? options.easing : linear, | |
); | |
const effect = createKeyframeEffect(element, frames, { | |
...options, | |
easing: options.easing ? getEasingFunction(options.easing) : "linear", | |
}); | |
const animation = new Animation(effect); | |
const createResizeHandler = skipFirstInvocation(async () => { | |
await tick(); | |
const newFrames = motionPath.build(keyframes, options.easing); | |
effect.setKeyframes(newFrames as Keyframe[]); | |
const currentTime = animation.currentTime; | |
const playState = animation.playState; | |
if (playState === "running") { | |
animation.pause(); | |
} | |
animation.effect = effect; | |
if (playState === "running") { | |
animation.currentTime = currentTime; | |
animation.play(); | |
} | |
}); | |
return { | |
get config() { | |
return options; | |
}, | |
play: () => { | |
animation.play(); | |
}, | |
pause: () => { | |
animation.pause(); | |
}, | |
cancel: () => { | |
animation.cancel(); | |
}, | |
finish: () => { | |
animation.finish(); | |
}, | |
finished: () => { | |
return animation.finished.finally(() => { | |
createResizeHandler(); | |
return Promise.resolve(animation); | |
}); | |
}, | |
onResize: () => { | |
return createResizeHandler(); | |
}, | |
}; | |
}; | |
export const tween = ( | |
element: Element, | |
keyframes: KeyframeWithTransform[], | |
options: TweenOptions | PathTweenOptions, | |
) => { | |
if ("path" in options) { | |
return pathTween(element, keyframes, options); | |
} | |
const effect = createKeyframeEffect(element, keyframes, options); | |
const animation = new Animation(effect, document.timeline); | |
return { | |
get config() { | |
return options; | |
}, | |
play: () => { | |
animation.play(); | |
}, | |
pause: () => { | |
animation.pause(); | |
}, | |
cancel: () => { | |
animation.cancel(); | |
}, | |
finish: () => { | |
animation.finish(); | |
}, | |
finished: () => { | |
return animation.finished; | |
}, | |
onResize: () => { | |
return; | |
}, | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment