feat: drive signs from a shared animation clock
This commit is contained in:
@@ -12,9 +12,23 @@ const initialSnapshot = (): ScrollDriveSnapshot => ({
|
||||
driveSpeed: 0.18,
|
||||
});
|
||||
|
||||
export function useScrollDrive(): Readonly<Ref<ScrollDriveSnapshot>> {
|
||||
export interface ScrollDriveBinding {
|
||||
snapshot: Readonly<Ref<ScrollDriveSnapshot>>;
|
||||
requestFrame(): void;
|
||||
subscribeFrame(
|
||||
listener: (now: number, snapshot: ScrollDriveSnapshot) => boolean,
|
||||
): () => void;
|
||||
}
|
||||
|
||||
export function useScrollDrive(): ScrollDriveBinding {
|
||||
const controller = createScrollDriveController();
|
||||
const snapshot = shallowRef<ScrollDriveSnapshot>(initialSnapshot());
|
||||
const subscribers = new Set<(
|
||||
now: number,
|
||||
snapshot: ScrollDriveSnapshot,
|
||||
) => boolean>();
|
||||
let requestFrame = (): void => {};
|
||||
let disposed = false;
|
||||
let teardown = (): void => {};
|
||||
|
||||
onMounted(() => {
|
||||
@@ -32,21 +46,29 @@ export function useScrollDrive(): Readonly<Ref<ScrollDriveSnapshot>> {
|
||||
};
|
||||
};
|
||||
|
||||
let scheduleFrame = (): void => {};
|
||||
|
||||
const runFrame = (now: number): void => {
|
||||
frameId = undefined;
|
||||
const input = readSample(now);
|
||||
snapshot.value = resetOnNextFrame
|
||||
const nextSnapshot = resetOnNextFrame
|
||||
? controller.reset(input)
|
||||
: controller.sample(input);
|
||||
snapshot.value = nextSnapshot;
|
||||
resetOnNextFrame = false;
|
||||
if (snapshot.value.acceleration > 0) scheduleFrame();
|
||||
let keepRunning = false;
|
||||
for (const subscriber of [...subscribers]) {
|
||||
keepRunning = subscriber(now, nextSnapshot) || keepRunning;
|
||||
}
|
||||
if (nextSnapshot.acceleration > 0 || keepRunning) scheduleFrame();
|
||||
};
|
||||
|
||||
const scheduleFrame = (): void => {
|
||||
scheduleFrame = (): void => {
|
||||
if (frameId === undefined && document.visibilityState !== 'hidden') {
|
||||
frameId = requestAnimationFrame(runFrame);
|
||||
}
|
||||
};
|
||||
requestFrame = scheduleFrame;
|
||||
|
||||
const handleVisibilityChange = (): void => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
@@ -70,6 +92,8 @@ export function useScrollDrive(): Readonly<Ref<ScrollDriveSnapshot>> {
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange, PASSIVE_LISTENER);
|
||||
|
||||
teardown = (): void => {
|
||||
disposed = true;
|
||||
requestFrame = (): void => {};
|
||||
window.removeEventListener('scroll', scheduleFrame, PASSIVE_LISTENER);
|
||||
window.removeEventListener('resize', scheduleFrame, PASSIVE_LISTENER);
|
||||
visualViewport?.removeEventListener('resize', scheduleFrame, PASSIVE_LISTENER);
|
||||
@@ -79,10 +103,19 @@ export function useScrollDrive(): Readonly<Ref<ScrollDriveSnapshot>> {
|
||||
cancelAnimationFrame(frameId);
|
||||
frameId = undefined;
|
||||
}
|
||||
subscribers.clear();
|
||||
};
|
||||
});
|
||||
|
||||
onUnmounted(() => teardown());
|
||||
|
||||
return readonly(snapshot);
|
||||
return {
|
||||
snapshot: readonly(snapshot),
|
||||
requestFrame: () => requestFrame(),
|
||||
subscribeFrame(listener) {
|
||||
if (disposed) return () => {};
|
||||
subscribers.add(listener);
|
||||
return () => subscribers.delete(listener);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user