From 63cdd29166014c6109e4d77dca8d8fd64260cc9e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 6 Dec 2024 15:33:49 -0500 Subject: [PATCH 1/2] feat: add `onFrame` lifecycle function --- .changeset/tasty-kangaroos-greet.md | 5 +++++ packages/svelte/src/index-client.js | 26 ++++++++++++++++++++++++++ packages/svelte/types/index.d.ts | 8 ++++++++ 3 files changed, 39 insertions(+) create mode 100644 .changeset/tasty-kangaroos-greet.md diff --git a/.changeset/tasty-kangaroos-greet.md b/.changeset/tasty-kangaroos-greet.md new file mode 100644 index 000000000000..07b6efa2be1c --- /dev/null +++ b/.changeset/tasty-kangaroos-greet.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add `onFrame` lifecycle function diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index 587d76623331..34a9694a75fe 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -161,6 +161,32 @@ export function afterUpdate(fn) { init_update_callbacks(component_context).a.push(fn); } +/** + * The `onFrame` function schedules a callback to run on `requestAnimationFrame`. It must be called inside an effect (e.g. during component initialisation). + * + * `onFrame` does not run inside [server-side components](https://svelte.dev/docs/svelte/svelte-server#render). + * + * @template T + * @param {() => NotFunction | Promise> | (() => any)} fn + * @returns {void} + */ +export function onFrame(fn) { + onMount(() => { + let frame = -1; + + function next() { + frame = requestAnimationFrame(next); + fn(); + } + + next(); + + return () => { + cancelAnimationFrame(frame); + }; + }); +} + /** * Legacy-mode: Init callbacks object for onMount/beforeUpdate/afterUpdate * @param {ComponentContext} context diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index f6b5b21f8085..c7eb847c010e 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -408,6 +408,13 @@ declare module 'svelte' { * @deprecated Use [`$effect`](https://svelte.dev/docs/svelte/$effect) instead * */ export function afterUpdate(fn: () => void): void; + /** + * The `onFrame` function schedules a callback to run on `requestAnimationFrame`. It must be called inside an effect (e.g. during component initialisation). + * + * `onFrame` does not run inside [server-side components](https://svelte.dev/docs/svelte/svelte-server#render). + * + * */ + export function onFrame(fn: () => NotFunction | Promise> | (() => any)): void; /** * Synchronously flushes any pending state changes and those that result from it. * */ @@ -1671,6 +1678,7 @@ declare module 'svelte/motion' { * * * ``` + * @since 5.8.0 */ export class Spring { constructor(value: T, options?: SpringOpts); From 00601986e5ed42d669bbcbe69b6ec83fa4fc6536 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 6 Dec 2024 15:40:30 -0500 Subject: [PATCH 2/2] noop on server --- packages/svelte/src/index-server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/index-server.js b/packages/svelte/src/index-server.js index 0f1aff8f5aa7..af22f25d7f51 100644 --- a/packages/svelte/src/index-server.js +++ b/packages/svelte/src/index-server.js @@ -12,6 +12,7 @@ export function onDestroy(fn) { export { noop as beforeUpdate, noop as afterUpdate, + noop as onFrame, noop as onMount, noop as flushSync, run as untrack