Motion: [FEATURE] use closest scroll container in useViewportScroll

Created on 11 Jul 2019  路  6Comments  路  Source: framer/motion

Is your feature request related to a problem? Please describe.
useViewportScroll only works with the body element.

Describe the solution you'd like
I'd like if useViewportScroll could optionally accept a ref to an element and use the closest scroll container.

Describe alternatives you've considered
I noticed that the docs suggest rolling this yourself, but I think it could be helpful to provide out of the box.


I'd be happy to submit a PR with this functionality, please let me know 馃槉.

feature

Most helpful comment

I've also ended up needing this feature. If help is needed with the PR I'm happy to chip in.

All 6 comments

Yeah we鈥檇 take a PR for this!

Awesome! I should be able to get something together tomorrow 馃榿

I've also ended up needing this feature. If help is needed with the PR I'm happy to chip in.

Is anything happening with this, really need this

Or: is there away to do this manually @souporserious @InventingWithMonster ?

Edit: Did not see the work done with this PR and this PR, but I'll just leave my answer here anyway.

You could do something like this, inspired by the the source.

export function useViewportScrollForElement(element) {
  const viewportMotionValues = {
    scrollX: motionValue(0),
    scrollY: motionValue(0),
    scrollXProgress: motionValue(0),
    scrollYProgress: motionValue(0)
  };
  const setProgress = (offset, maxOffset, value) => {
    value.set(!maxOffset || !offset ? 0 : offset / maxOffset);
  };
  const hasEventListenerRef = useRef(false);
  useEffect(() => {
    if (!element) return;
    const updateScrollValues = () => {
      window.requestAnimationFrame(() => {
        const xOffset = element.scrollLeft;
        const yOffset = element.scrollTop;
        // Set absolute positions
        viewportMotionValues.scrollX.set(xOffset);
        viewportMotionValues.scrollY.set(yOffset);

        // Set 0-1 progress
        setProgress(
          xOffset,
          element.clientWidth - element.clientHeight,
          viewportMotionValues.scrollXProgress
        );
        setProgress(
          yOffset,
          element.scrollHeight - element.clientHeight,
          viewportMotionValues.scrollYProgress
        );
      });
    };
    if (!hasEventListenerRef.current) {
      hasEventListenerRef.current = true;
      if (typeof window === "undefined") return;
      updateScrollValues();
      window.addEventListener("resize", updateScrollValues);
      window.addEventListener("scroll", updateScrollValues, true);
    }
    return () => {
      hasEventListenerRef.current = false;
      window.removeEventListener("resize", updateScrollValues);
      window.removeEventListener("scroll", updateScrollValues, true);
    };
  }, [element, viewportMotionValues]);

  return viewportMotionValues;
}

And use it like this for example:

const { scrollYProgress } = useViewportScroll(scrollRef.current || document.getElementById('scroll-container'));

Regarding getting the nearest scrollcontainer this code can be used with this scrollparent npm package.

Keep in mind I haven't thoroughly tested the code, but it works so far for my needs.

Was this page helpful?
0 / 5 - 0 ratings