-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
animationstart hook not effective if lazy loaded #260
Comments
Turns out this is also a problem when we fetch remote stylesheets. The stylesheets themselves are already loaded, but the |
We can use |
I tested this out and that works except for the condition of them running. Most of them have a default duration of |
Maybe we could temporarily change the fill mode to |
An alternate option would be to have a very small snippet that is run synchronously which simply collects a list of started animations and then passes them in to be set up by the polyfill after its loaded. |
The syncronous snippet might be hard, we can't collect it immediately if say a framework like React mounts or re-renders and that triggers an animation after the snippet but before the polyfill is loaded (or remote stylesheets fetched and parsed). The |
Yes! This should avoid any visible side effects. Finished animations will remain finished whereas calling play restarts them. |
This wouldn't be a problem, you'd simply keep a list of animations to check later to see if they should be scroll driven, e.g. let startedAnimations = new Set();
window.addEventListener('animationstart', (evt) => {
evt.target.getAnimations().filter(anim => anim.animationName === evt.animationName).forEach(anim => {
startAnimations.add(anim);
});
}); Then after the polyfill loads you'd process each of startedAnimations as if they had an animationstart event, i.e. calling the code here: https://github.com/flackr/scroll-timeline/blob/master/src/scroll-timeline-css.js#L174 |
Let me give that a shot! |
It did seem to work, actually. Was also running into another issue where the syntax parser had invalid syntax so stopped processing all animations. That's probably correct, but challenging in a production scenario.
|
@calinoracation Hi! Can you share any complete example of making it work? I am trying to use this polyfill in my NextJS project and as far as I understand my problem is similar. I am also interested in your workaround (calling play on animations) |
After days of hard work I've managed to "bring back to life" CSS scroll-driven animations for browsers without native support in my NextJS project. Let me share tips that might be useful: Useful notes about this polyfill usage (and NextJS tips)How to make it work in NextJS (app router)
<div
data-animated={"true"} // Mark your animated blocks like so
className={s.oncomingCont}
/>
'use client';
import { useState, useEffect } from 'react';
// These are our debug & logging libraries, you don't need them
import Bugsnag from '@bugsnag/js';
import { CodeBud } from '@appklaar/codebud';
export type UseScrollTimelinePolyfillResult = 'polyfillNotNeeded' | 'polyfillApplied' | 'polyfillApplyingFailed';
/**
* A custom hook that loads the ScrollTimeline polyfill and upgrades animations
* on elements with the `data-animated="true"` attribute.
* @returns {UseScrollTimelinePolyfillResult} Result status.
*
* @remarks
* - Hook applies the polyfill only if
* - The polyfill is dynamically imported from the `scroll-timeline-polyfill` package.
* - The hook uses `requestAnimationFrame` to ensure animations are upgraded in the next frame.
* - Animations that are in the `finished` or `idle` state are reset and played again.
*/
export const useScrollTimelinePolyfill = (): UseScrollTimelinePolyfillResult => {
const [status, setStatus] = useState<UseScrollTimelinePolyfillResult>('polyfillNotNeeded');
useEffect(() => {
const loadPolyfill = async () => {
try {
// Don't enable polyfill if browser claims support
if (CSS.supports("animation-timeline: --works"))
return;
// Relative path is the only way scroll-timeline-polyfill/dist/scroll-timeline.js import works for me. Anyway, make sure that you pass correct path.
await import('./../../node_modules/scroll-timeline-polyfill/dist/scroll-timeline.js' as any); // no TS declaration file, so as any
// Regular NextJS check that we're not on server-side
if (typeof window === 'undefined')
return;
if ('ScrollTimeline' in window) { // If polyfill applied
console.log('ScrollTimeline is now available. Upgrading animations...');
// Find all elements marked with data attribute "data-animated"
const elements = document.querySelectorAll<HTMLElement>('[data-animated="true"]');
// console.log('elements found:', elements.length);
let animationsToUpgrade: Animation[] = [];
elements.forEach((el, k) => {
// Get all animations related to element
const animations = el.getAnimations?.() || [];
// console.log(`animations found(${k}):`, animations.length);
animationsToUpgrade = animationsToUpgrade.concat(animations);
});
if (animationsToUpgrade.length > 0) {
requestAnimationFrame(() => {
animationsToUpgrade.forEach((anim) => {
// console.log('Upgrading animation:', anim);
if (anim.playState === 'finished' || anim.playState === 'idle') {
anim.cancel(); // Reset the animation
}
anim.play();
});
});
}
setStatus('polyfillApplied');
return;
} else {
throw new Error('Failed to import the scroll-timeline-polyfill: window.ScrollTimeline is still not available');
}
} catch (error) {
CodeBud.captureEvent('Failed to load the ScrollTimeline polyfill', { error, where: 'useScrollTimelinePolyfill hook' });
Bugsnag.leaveBreadcrumb('Failed to load the ScrollTimeline polyfill', { error });
Bugsnag.notify('useScrollTimelinePolyfill failure');
console.error('Failed to load the ScrollTimeline polyfill:', error);
setStatus('polyfillApplyingFailed');
return;
}
};
loadPolyfill();
}, []);
return status;
};
'use client';
import { useScrollTimelinePolyfill } from '~/hooks/useScrollTimelinePolyfill';
export type HeadlessEnableScrollTimelinePolyfillProps = {};
const HeadlessEnableScrollTimelinePolyfill: React.FC<HeadlessEnableScrollTimelinePolyfillProps> = ({}) => {
useScrollTimelinePolyfill();
return null;
};
export { HeadlessEnableScrollTimelinePolyfill }; And use it inside your page / layout like so: import { HeadlessEnableScrollTimelinePolyfill } from '~/components/Utils/HeadlessEnableScrollTimelinePolyfill';
export default async function Home({ params }: HomeProps) {
return (
<div className={s.container}>
<HeadlessEnableScrollTimelinePolyfill />
</div>
);
} Useful tips
.slide0AnimationPreset {
animation-timeline: scroll(root block);
animation-fill-mode: both;
animation-duration: auto;
animation-timing-function: linear;
animation-range: 4% 90%; /* example value */
animation-name: slideLeftChangeWithoutAppearingAnimation;
}
@supports (animation-timeline: scroll()) {
.slide0AnimationPreset {
animation-range-start: 10vh; /* example value */
animation-range-end: 110vh; /* example value */
}
}
|
We are asynchronously loading, so the start event has already happened when the polyfill loads in Safari.
I tried various workarounds like waiting for the polyfill to load and switching from play state paused to running.. Perhaps completely not applying until then is an option, but that's tricky for authors.
My current workaround is to apply a data attribute to each element and once loaded query them all and call play on the animations. That does seem to work.
The text was updated successfully, but these errors were encountered: