diff --git a/src/index.js b/src/index.js index a8ea59a7..09f0d3ed 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,8 @@ import { } from "./scroll-timeline-base"; import { animate, + elementGetAnimations, + documentGetAnimations, ProxyAnimation } from "./proxy-animation.js"; @@ -61,6 +63,16 @@ function initPolyfill() { if (!Reflect.defineProperty(window, 'Animation', { value: ProxyAnimation })) { throw Error('Error installing Animation constructor.'); } + if (!Reflect.defineProperty(Element.prototype, "getAnimations", { value: elementGetAnimations })) { + throw Error( + "Error installing ScrollTimeline polyfill: could not attach WAAPI's getAnimations to DOM Element" + ); + } + if (!Reflect.defineProperty(document, "getAnimations", { value: documentGetAnimations })) { + throw Error( + "Error installing ScrollTimeline polyfill: could not attach WAAPI's getAnimations to document" + ); + } } initPolyfill(); diff --git a/src/proxy-animation.js b/src/proxy-animation.js index a40150c5..8665c4f1 100644 --- a/src/proxy-animation.js +++ b/src/proxy-animation.js @@ -5,6 +5,8 @@ import { relativePosition } from "./scroll-timeline-base"; +const nativeDocumentGetAnimations = document.getAnimations; +const nativeElementGetAnimations = window.Element.prototype.getAnimations; const nativeElementAnimate = window.Element.prototype.animate; const nativeAnimation = window.Animation; @@ -829,6 +831,20 @@ function fractionalEndDelay(details) { return 1 - relativePosition(details.timeline, endTime.rangeName, endTime.offset); } +// Map from an instance of ProxyAnimation to internal details about that animation. +// See ProxyAnimation constructor for details. +let proxyAnimations = new WeakMap(); + +// Clear cache containing the ProxyAnimation instances when leaving the page. +// See https://github.com/flackr/scroll-timeline/issues/146#issuecomment-1698159183 +// for details. +window.addEventListener('pagehide', (e) => { + proxyAnimations = new WeakMap(); +}, false); + +// Map from the real underlying native animation to the ProxyAnimation proxy of it. +let proxiedAnimations = new WeakMap(); + /** * Procedure for calculating an auto-aligned start time. * https://drafts.csswg.org/web-animations-2/#animation-calculating-an-auto-aligned-start-time @@ -886,8 +902,6 @@ function autoAlignStartTime(details) { // Create an alternate Animation class which proxies API requests. // TODO: Create a full-fledged proxy so missing methods are automatically // fetched from Animation. -let proxyAnimations = new WeakMap(); - export class ProxyAnimation { constructor(effect, timeline, animOptions={}) { const animation = @@ -895,6 +909,7 @@ export class ProxyAnimation { effect : new nativeAnimation(effect, animationTimeline); const isScrollAnimation = timeline instanceof ScrollTimeline; const animationTimeline = isScrollAnimation ? undefined : timeline; + proxiedAnimations.set(animation, this); proxyAnimations.set(this, { animation: animation, timeline: isScrollAnimation ? timeline : undefined, @@ -1817,4 +1832,24 @@ export function animate(keyframes, options) { } return proxyAnimation; -}; +} + +function replaceProxiedAnimations(animationsList) { + for (let i = 0; i < animationsList.length; ++i) { + let proxyAnimation = proxiedAnimations.get(animationsList[i]); + if (proxyAnimation) { + animationsList[i] = proxyAnimation; + } + } + return animationsList; +} + +export function elementGetAnimations(options) { + let animations = nativeElementGetAnimations.apply(this, [options]); + return replaceProxiedAnimations(animations); +} + +export function documentGetAnimations(options) { + let animations = nativeDocumentGetAnimations.apply(this, [options]); + return replaceProxiedAnimations(animations); +} diff --git a/src/scroll-timeline-css.js b/src/scroll-timeline-css.js index 2da22bb1..5007da01 100644 --- a/src/scroll-timeline-css.js +++ b/src/scroll-timeline-css.js @@ -140,37 +140,23 @@ export function initCSSPolyfill() { initMutationObserver(); - // Cache all Proxy Animations - let proxyAnimations = new WeakMap(); - // We are not wrapping capturing 'animationstart' by a 'load' event, // because we may lose some of the 'animationstart' events by the time 'load' is completed. window.addEventListener('animationstart', (evt) => { evt.target.getAnimations().filter(anim => anim.animationName === evt.animationName).forEach(anim => { - // Create a per-element cache - if (!proxyAnimations.has(evt.target)) { - proxyAnimations.set(evt.target, new Map()); - } - const elementProxyAnimations = proxyAnimations.get(evt.target); - - // Store Proxy Animation in the cache - if (!elementProxyAnimations.has(anim.animationName)) { - const result = createScrollTimeline(anim, anim.animationName, evt.target); - if (result && result.timeline && anim.timeline != result.timeline) { - elementProxyAnimations.set(anim.animationName, new ProxyAnimation(anim, result.timeline, result.animOptions)); + const result = createScrollTimeline(anim, anim.animationName, evt.target); + if (result) { + // If the CSS Animation refers to a scroll or view timeline we need to proxy the animation instance. + if (result.timeline && !(anim instanceof ProxyAnimation)) { + const proxyAnimation = new ProxyAnimation(anim, result.timeline, result.animOptions); + anim.pause(); + proxyAnimation.play(); } else { - elementProxyAnimations.set(anim.animationName, null); + // If the timeline was removed or the animation was already an instance of a proxy animation, + // invoke the set the timeline procedure on the existing animation. + anim.timeline = result.timeline; } } - - // Get Proxy Animation from cache - const proxyAnimation = elementProxyAnimations.get(anim.animationName); - - // Swap the original animation with the proxied one - if (proxyAnimation !== null) { - anim.pause(); - proxyAnimation.play(); - } }); }); diff --git a/test/expected.txt b/test/expected.txt index 1aaa8fb5..58cd0f39 100644 --- a/test/expected.txt +++ b/test/expected.txt @@ -79,10 +79,10 @@ FAIL /scroll-animations/css/animation-timeline-deferred.html Animation.timeline FAIL /scroll-animations/css/animation-timeline-deferred.html Animation.timeline returns null for inactive deferred timeline FAIL /scroll-animations/css/animation-timeline-deferred.html Animation.timeline returns null for inactive (overattached) deferred timeline FAIL /scroll-animations/css/animation-timeline-ignored.tentative.html Changing animation-timeline changes the timeline (sanity check) -FAIL /scroll-animations/css/animation-timeline-ignored.tentative.html animation-timeline ignored after setting timeline with JS (ScrollTimeline from JS) +PASS /scroll-animations/css/animation-timeline-ignored.tentative.html animation-timeline ignored after setting timeline with JS (ScrollTimeline from JS) FAIL /scroll-animations/css/animation-timeline-ignored.tentative.html animation-timeline ignored after setting timeline with JS (ScrollTimeline from CSS) -PASS /scroll-animations/css/animation-timeline-ignored.tentative.html animation-timeline ignored after setting timeline with JS (document timeline) -PASS /scroll-animations/css/animation-timeline-ignored.tentative.html animation-timeline ignored after setting timeline with JS (null) +FAIL /scroll-animations/css/animation-timeline-ignored.tentative.html animation-timeline ignored after setting timeline with JS (document timeline) +FAIL /scroll-animations/css/animation-timeline-ignored.tentative.html animation-timeline ignored after setting timeline with JS (null) FAIL /scroll-animations/css/animation-timeline-in-keyframe.html The animation-timeline property may not be used in keyframes PASS /scroll-animations/css/animation-timeline-multiple.html animation-timeline works with multiple timelines FAIL /scroll-animations/css/animation-timeline-none.html Animation with animation-timeline:none holds current time at zero @@ -957,4 +957,4 @@ FAIL /scroll-animations/view-timelines/view-timeline-sticky-block.html View time FAIL /scroll-animations/view-timelines/view-timeline-sticky-inline.html View timeline with sticky target, block axis. FAIL /scroll-animations/view-timelines/view-timeline-subject-size-changes.html View timeline with subject size change after the creation of the animation FAIL /scroll-animations/view-timelines/zero-intrinsic-iteration-duration.tentative.html Intrinsic iteration duration is non-negative -Passed 392 of 959 tests. +Passed 391 of 959 tests.