flackr / scroll-timeline

A polyfill of ScrollTimeline.
Apache License 2.0
968 stars 95 forks source link

Minimal JS only version #173

Open axyz opened 1 year ago

axyz commented 1 year ago

Hi, I was wondering if it could be possible to provide an npm module for this, as well as an option to only load the JS API, ignoring the CSS support (tree shackable init functions?).

This way it could be possible to have 0 bundle size overhead on Chrome and Edge, while on Safari and Firefox the JS polyfill could be lazyloaded with a small additional bundle.

ivodolenc commented 7 months ago

Hi @flackr, awesome work on this project! 👍

... as well as an option to only load the JS API, ...

I'm interested in this too.

I'm building some JS scrolling behaviors where the ScrollTimeline API is the perfect solution and works really great.

... while on Safari and Firefox the JS polyfill could be lazyloaded with a small additional bundle.

As mentioned above, some browsers don't have a built-in implementation (we all need Houdini API asap, mozilla come on  😀), so I could conditionaly import only scroll-timeline-base.js without CSS, Animation and ViewTimeline support in order to reduce the size of the polyfill as much as possible. Polyfill in this case should literally only depend on the ScrollTimeline JS API.

I looked through the code a bit and tried to separate just the JS ScrollTimeline but it requires a good knowledge of the whole project since the CSS OM is involved to convert the units.

Is it possible to expect these to be separate files so we can only import what we need?

Also, if needed, I can create a github repo with my split attempt if you're interested in helping or seeing if it works?

porfirioribeiro commented 7 months ago

I did it on my project by adding a git dependency

    "scroll-timeline": "git://github.com/flackr/scroll-timeline.git#fa54f02a9f2873d9b79415a7588686368554ea61",

And a minimal polyfil initiatior:

import { ScrollTimeline, ViewTimeline } from 'scroll-timeline/src/scroll-timeline-base';
import {
  animate,
  elementGetAnimations,
  documentGetAnimations,
  ProxyAnimation,
  // @ts-ignore
} from 'scroll-timeline/src/proxy-animation.js';

export function initPolyfill() {
  if (!Reflect.defineProperty(window, 'ScrollTimeline', { value: ScrollTimeline })) {
    throw Error('Error installing ScrollTimeline polyfill: could not attach ScrollTimeline to window');
  }
  if (!Reflect.defineProperty(window, 'ViewTimeline', { value: ViewTimeline })) {
    throw Error('Error installing ViewTimeline polyfill: could not attach ViewTimeline to window');
  }

  if (!Reflect.defineProperty(Element.prototype, 'animate', { value: animate })) {
    throw Error("Error installing ScrollTimeline polyfill: could not attach WAAPI's animate to DOM Element");
  }
  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");
  }
}

That i lazyload before rendering my Vue App:

const initPromise = !('ScrollTimeline' in window)
  ? import('./utils/scroll-timeline-polyfill').then((m) => m.initPolyfill())
  : Promise.resolve();

initPromise.then(() => {
  //render app
});

Ensuring that the JS is only loaded if the browser needs it. Of course this only works for JS based animations, as it does not bring the CSS polyfill

ivodolenc commented 7 months ago

I believe all this can be reduced to only 3-4kb (maybe less) if we want to use only the ScrollTimeline class (without el.animate(), CSS OM or ViewTimeline features because in this case it is not needed).

I just reduced scroll-timeline-base.js and it works fine (did only quick tests in firefox), but I'm converting it to typescript.

I will post a repo here with the results and hope to hear back from the author if it can be used as a safe polyfill.

The only concern for now is how to timeline.cancel() or timeline.disconnect() the class when we don't need it anymore, like when we have a route change etc.

The API would look like this:

// start timeline
const timeline = new ScrollTimeline({ source: el, axis: 'y' })

// cancel timeline
timeline() // or -> timeline.cancel()
ivodolenc commented 7 months ago

Here is the repository. Feel free to check it out. Feedback is of course welcome.