solidjs-community / solid-motionone

A tiny, performant animation library for SolidJS
MIT License
112 stars 3 forks source link

Add `useScroll` utility hook #9

Open jaydevelopsstuff opened 2 months ago

jaydevelopsstuff commented 2 months ago

This PR just adds useScroll as a utility function to get reactive updates from motionone's scroll listener. I tested it in browser and reactivity is working.

I haven't really contributed to or created any TS/Solid libraries before so I am sure there are some things that might need changing. It would be great if someone more experienced could do a proper review to make sure the code is idiomatic and functional.

Relevant issue: #8

jaydevelopsstuff commented 2 months ago

Any chance someone could review this? It's a pretty minor addition...

jaydevelopsstuff commented 2 months ago

Why stores? Couldn't it be as simple as this?

const [scroll, setScroll] = solid.createSignal<mo.Scroll | null>(null)
solid.onMount(() => mo.scroll(setScroll, options))
return scroll

I'm not really a fan of using stores in libraries.

I don't mind the utility overall though.

IIRC I tried using just signals originally but couldn't get the reactivity to work. Feel free to play around with it if you would like though.

The readme should be updated too, so that people can discover it.

Sounds good.

jaydevelopsstuff commented 1 month ago

The reason I didn't use stores is to allow destructuring like this const { time, scrollX, scrollY } = useScroll(), for convenience.

It's been almost a month, any chance this can be merged soon? I need to use it in prod soon.

strootje commented 4 weeks ago

I would love to see this merged, currently using a custom hook in my projects that uses signals.

export const useScroll = () => {
  const [time, setTime] = createSignal<number>(0);
  const [scrollX, setScrollX] = createSignal<AxisScrollInfo>(defaultScrollInfo, { equals: false });
  const [scrollY, setScrollY] = createSignal<AxisScrollInfo>(defaultScrollInfo, { equals: false });

  onMount(() => {
    scroll(({ time, x, y }) => {
      setTime(time);
      setScrollX(x);
      setScrollY(y);
    });
  });

  return { time, scrollX, scrollY };
};

The reason for the { equals: false } is needed is because the reference from scroll for the x and y objects are always the same reference. Solid does a === comparison and the references to the objects are always the same.

See the solid docs on signals: https://docs.solidjs.com/reference/basic-reactivity/create-signal#equals

Feel free to use my implementation.

thetarnav commented 4 weeks ago

Good to know that the {equals: false} is required for signals to work. That's a good reason to use stores instead. Although I don't enjoy that time is an accessor while the other properties are proxies. a {time: number, x: AxisScrollInfo, y: AxisScrollInfo} interface would work well exactly what motionone.scroll provides with a single store instance

const [store, setStore] = createStore({
    time: 0,
    x: {...zero_axis},
    y: {...zero_axis},
})

onMount(() => {
    onCleanup(
        scroll(e => {
            batch(() => {
                setStore('time', e.time)
                setStore('x', e.x)
                setStore('y', e.y)
            })
        }, options)
    )
})

return store

Writing this made me realize that batch and onCleanup were missing

thetarnav commented 4 weeks ago

Another option what would keep the same api but not require bringing in stores:

const [time,              set_time]              = createSignal(0)
const [x_current,         set_x_current]         = createSignal(0)
const [y_current,         set_y_current]         = createSignal(0)
const [x_offset,          set_x_offset]          = createSignal([])
const [y_offset,          set_y_offset]          = createSignal([])
const [x_progress,        set_x_progress]        = createSignal(0)
const [y_progress,        set_y_progress]        = createSignal(0)
const [x_scrollLength,    set_x_scrollLength]    = createSignal(0)
const [y_scrollLength,    set_y_scrollLength]    = createSignal(0)
const [x_velocity,        set_x_velocity]        = createSignal(0)
const [y_velocity,        set_y_velocity]        = createSignal(0)
const [x_targetOffset,    set_x_targetOffset]    = createSignal(0)
const [y_targetOffset,    set_y_targetOffset]    = createSignal(0)
const [x_targetLength,    set_x_targetLength]    = createSignal(0)
const [y_targetLength,    set_y_targetLength]    = createSignal(0)
const [x_containerLength, set_x_containerLength] = createSignal(0)
const [y_containerLength, set_y_containerLength] = createSignal(0)

onMount(() => {
    onCleanup(
        scroll(e => {
            batch(() => {
                set_time(e.time)
                set_x_current(e.x.current)
                set_y_current(e.y.current)
                set_x_offset(e.x.offset)
                set_y_offset(e.y.offset)
                set_x_progress(e.x.progress)
                set_y_progress(e.y.progress)
                set_x_scrollLength(e.x.scrollLength)
                set_y_scrollLength(e.y.scrollLength)
                set_x_velocity(e.x.velocity)
                set_y_velocity(e.y.velocity)
                set_x_targetOffset(e.x.targetOffset)
                set_y_targetOffset(e.y.targetOffset)
                set_x_targetLength(e.x.targetLength)
                set_y_targetLength(e.y.targetLength)
                set_x_containerLength(e.x.containerLength)
                set_y_containerLength(e.y.containerLength)
            })
        }, options)
    )
})

return {
    get time()              {return time()},
    x: {
        get current()         {return x_current()},
        get offset()          {return x_offset()},
        get progress()        {return x_progress()},
        get scrollLength()    {return x_scrollLength()},
        get velocity()        {return x_velocity()},
        get targetOffset()    {return x_targetOffset()},
        get targetLength()    {return x_targetLength()},
        get containerLength() {return x_containerLength()},
    },
    y: {
        get current()         {return y_current()},
        get offset()          {return y_offset()},
        get progress()        {return y_progress()},
        get scrollLength()    {return y_scrollLength()},
        get velocity()        {return y_velocity()},
        get targetOffset()    {return y_targetOffset()},
        get targetLength()    {return y_targetLength()},
        get containerLength() {return y_containerLength()},
    },
}
jaydevelopsstuff commented 4 weeks ago

Another option what would keep the same api but not require bringing in stores:


const [time,              set_time]              = createSignal(0)

const [x_current,         set_x_current]         = createSignal(0)

const [y_current,         set_y_current]         = createSignal(0)

const [x_offset,          set_x_offset]          = createSignal([])

const [y_offset,          set_y_offset]          = createSignal([])

const [x_progress,        set_x_progress]        = createSignal(0)

const [y_progress,        set_y_progress]        = createSignal(0)

const [x_scrollLength,    set_x_scrollLength]    = createSignal(0)

const [y_scrollLength,    set_y_scrollLength]    = createSignal(0)

const [x_velocity,        set_x_velocity]        = createSignal(0)

const [y_velocity,        set_y_velocity]        = createSignal(0)

const [x_targetOffset,    set_x_targetOffset]    = createSignal(0)

const [y_targetOffset,    set_y_targetOffset]    = createSignal(0)

const [x_targetLength,    set_x_targetLength]    = createSignal(0)

const [y_targetLength,    set_y_targetLength]    = createSignal(0)

const [x_containerLength, set_x_containerLength] = createSignal(0)

const [y_containerLength, set_y_containerLength] = createSignal(0)

onMount(() => {

  onCleanup(

      scroll(e => {

          batch(() => {

              set_time(e.time)

              set_x_current(e.x.current)

              set_y_current(e.y.current)

              set_x_offset(e.x.offset)

              set_y_offset(e.y.offset)

              set_x_progress(e.x.progress)

              set_y_progress(e.y.progress)

              set_x_scrollLength(e.x.scrollLength)

              set_y_scrollLength(e.y.scrollLength)

              set_x_velocity(e.x.velocity)

              set_y_velocity(e.y.velocity)

              set_x_targetOffset(e.x.targetOffset)

              set_y_targetOffset(e.y.targetOffset)

              set_x_targetLength(e.x.targetLength)

              set_y_targetLength(e.y.targetLength)

              set_x_containerLength(e.x.containerLength)

              set_y_containerLength(e.y.containerLength)

          })

      }, options)

  )

})

return {

  get time()              {return time()},

  x: {

      get current()         {return x_current()},

      get offset()          {return x_offset()},

      get progress()        {return x_progress()},

      get scrollLength()    {return x_scrollLength()},

      get velocity()        {return x_velocity()},

      get targetOffset()    {return x_targetOffset()},

      get targetLength()    {return x_targetLength()},

      get containerLength() {return x_containerLength()},

  },

  y: {

      get current()         {return y_current()},

      get offset()          {return y_offset()},

      get progress()        {return y_progress()},

      get scrollLength()    {return y_scrollLength()},

      get velocity()        {return y_velocity()},

      get targetOffset()    {return y_targetOffset()},

      get targetLength()    {return y_targetLength()},

      get containerLength() {return y_containerLength()},

  },

}

Whatever you think is best honestly, you should have commit access for this PR. I just want to get this merged; let me know if there's anything else I can do.