From d4fd998515a9101d02a1b0f3fc6b4439f31f4653 Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 30 Oct 2024 18:12:23 +0100 Subject: [PATCH] Add support for SVG animations (#5) * add initial version of svg animation crossing * move animation forward for duration of view transition * remove `svg-anim` kind and move svg animation logic into `anim` kind * chore: add changeset --------- Co-authored-by: Martin Trapp <94928215+martrapp@users.noreply.github.com> --- .changeset/tidy-parents-explain.md | 11 ++++ src/vanilla.ts | 81 +++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 25 deletions(-) create mode 100644 .changeset/tidy-parents-explain.md diff --git a/.changeset/tidy-parents-explain.md b/.changeset/tidy-parents-explain.md new file mode 100644 index 0000000..e95b234 --- /dev/null +++ b/.changeset/tidy-parents-explain.md @@ -0,0 +1,11 @@ +--- +'@vtbag/element-crossing': patch +--- + +Extend `anim` expression with support for SVG animations. + +To transfer the SVG animation state to the new document use the `/svg` key for the `anim` expression + +```html +... +``` diff --git a/src/vanilla.ts b/src/vanilla.ts index ffc496e..bb4a3f4 100644 --- a/src/vanilla.ts +++ b/src/vanilla.ts @@ -1,6 +1,7 @@ import { ElementSpec, Spec } from './types'; const DEBUG = false; +const SVG_ANIM_KEY = '/svg'; const crossing = top!.__vtbag?.elementCrossing; init(); @@ -147,18 +148,34 @@ function elementSpec(element: HTMLElement) { }); break; case 'anim': - const animations = element - .getAnimations() - .filter((a) => a instanceof CSSAnimation && a.animationName === key); - if (animations.length > 1) { - console.error( - '[crossing]', - `retrieval: animation name ${key} is not unique for`, - element - ); - } - if (animations.length > 0) { - specs.push({ kind, key, value: '' + (animations[0].currentTime ?? 0) }); + if (key === SVG_ANIM_KEY) { + if (!(element instanceof SVGSVGElement)) { + console.error( + '[crossing]', + `retrieval: element is not an SVG element, but "${key}" was used for its key`, + element + ); + break; + } + + const currentTime = element.getCurrentTime(); + if (currentTime > 0) { + specs.push({ kind, key, value: currentTime.toString() }); + } + } else { + const animations = element + .getAnimations() + .filter((a) => a instanceof CSSAnimation && a.animationName === key); + if (animations.length > 1) { + console.error( + '[crossing]', + `retrieval: animation name ${key} is not unique for`, + element + ); + } + if (animations.length > 0) { + specs.push({ kind, key, value: '' + (animations[0].currentTime ?? 0) }); + } } break; case 'elem': @@ -218,21 +235,35 @@ function restore(values: ElementSpec[]) { (element as any)[s.key] = parseFloat(s.value ?? '0'); break; case 'anim': - const animations = element! - .getAnimations() - .filter((a) => a instanceof CSSAnimation && a.animationName === s.key); - if (animations.length > 1) { - console.warn( - '[crossing]', - `restore: animation name ${s.key} is not unique for`, - element + if (s.key === SVG_ANIM_KEY) { + if (!(element instanceof SVGSVGElement)) { + console.error( + '[crossing]', + `restore: element for key ${s.key} is not an SVG element`, + element + ); + break; + } + element.setCurrentTime( + parseFloat(s.value ?? '0') + (new Date().getTime() - elementSpec.timestamp) / 1000 + ); + } else { + const animations = element! + .getAnimations() + .filter((a) => a instanceof CSSAnimation && a.animationName === s.key); + if (animations.length > 1) { + console.warn( + '[crossing]', + `restore: animation name ${s.key} is not unique for`, + element + ); + } + animations.forEach( + (a) => + (a.currentTime = + ~~(s.value ?? '0') + (new Date().getTime() - elementSpec.timestamp)) ); } - animations.forEach( - (a) => - (a.currentTime = - ~~(s.value ?? '0') + (new Date().getTime() - elementSpec.timestamp)) - ); break; case 'elem': const crossing = top?.__vtbag?.elementCrossing;