flackr / scroll-timeline

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

`ScrollTimeline` and `ViewTimeline` offsets via JS API #284

Closed ivodolenc closed 3 months ago

ivodolenc commented 3 months ago

Hi, I did a quick search through the issues section and found similar topics related to this. (as @bramus mentioned here and here)

As far as I understand, previous versions of polyfill had the ability to set the offset directly using the timeline options via the JS API.

Unfortunately, I don't think that's possible right now, since we have to set it via CSS in the animation itself.

So JS API can only be used for real stuff if its combined with CSS API and this limits the features a lot, i.e. JS API without offset doesn't have much space for more advanced animations and projects.

Setting offsets directly via the timeline options would be super useful for users who choose to only use the JS API, it's basically a must in my opinion, otherwise JS APIs don't make much sense.

For example:

// start ST via JS API

const timeline = new ScrollTimeline({
  source: document.documentElement,
  offset: [], // πŸ‘ˆπŸ» directly sets offsets via ST options
})

// later in the code `timeline` and `current time (progress)` will be observed separately via RAF,
// without the `CSS Animation API`

I'm wondering if it's possible to bring this feature back, or if you have a suggestion or example of how to include it via a separate util function or similar solution.

Thanks in advance.

bramus commented 3 months ago

As mentioned in the issues you linked to, offsets are part of the specification again. It was only for a certain period in time – in between v0 and the first rewrite of the spec – that these were no longer available.

The following example uses offsets in the JS API:

const $images = document.querySelectorAll('.revealing-image');

$images.forEach(($image) => {
    $image.animate(
        {
            opacity: [0, 1],
            clipPath: ['inset(45% 20% 45% 20%)', 'inset(0% 0% 0% 0%)'],
        },
        {
            fill: 'both',
            timeline: new ViewTimeline({
                subject: $image,
            }),
            rangeStart: 'entry 25%',
            rangeEnd: 'cover 50%',
        }
    );
});

These rangeStart and rangeEnd parameters are animation options, not options of the timeline. That way you can create one single timeline and then attach various animations to various parts of it.

In CSS you use the animation-range property.

For more info, go check out episode 4 of the video series I have on scroll-driven animations: https://scroll-driven-animations.style/#learn – it covers the concept of there ranges in detail.

ivodolenc commented 3 months ago

For the JS API I only want to use the timeline, I don't need the el.animate() API because I will animate the elements separately via JS with external library.

// so this would be redundant in my case
el.animate()

Something like this would be my use case:

import { scroll, animate } from 'lib'

// scroll would use `ScrollTimeline` under the hood and will watch the progress via RAF
scroll(
  ({ progress }) => {
    // custom animate JS API
    animate('.el', {
      x: [0, 600, 300],
      background: ['#cf3', '#0df'],
      opacity: [0, 1],
      duration: 1,
    })
  },
  {
    source: document.documentElement,
    offset: [], // πŸ‘ˆπŸ»  here you would adjust the offsets directly on the timeline options
  },
)

I've noticed that something similar can be achieved using delayStart and delayEnd on the animate() util, but these are not as advanced effects as with the range options.