motion: [BUG] Stop scrolling from interfering with dragging

Is your feature request related to a problem? Please describe. If you render a list of drag="x" components and try to scroll down on a touch device, a small amount of horizontal drag happens on each element that your finger touches.

Describe the solution you’d like A generic solution could be to allow the variant styles to override the styles from the drag.

let variants = {
  // this would override whatever x value the drag gesture is providing
  static: { x: 0 },
  swiping: { },
};

let Swipeable = () => {
  let [isSwiping, setIsSwiping] = useState(0);

  let onDrag = (event, info) => setIsSwiping(info.offset.x > 10);

  return (
    <motion.div drag="x" animate={isSwiping ? "swiping" : "static"} onDrag={onDrag} />
  );
}

Describe alternatives you’ve considered I also tried overriding by passing transform styles directly to the motion.div element.

A more specific version of this might be adding a dragThreshold prop that would specify the offsets that the gesture must pass before the onDragStart event is called.

Another option might be having some way to cancel the animation (but not the event) from the onDrag handler.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 10
  • Comments: 31 (1 by maintainers)

Most upvoted comments

I have a motion component that I want to drag on "x" for animations, but it takes enough of the screen on mobile that I want users to be able to drag it on "y" for scrolling. Do you have any advice or workaround ideas right now?

I just build a nice slider using the drag="x" functionality, when I ran into this issue. The component takes up most of the viewport, so you should be able to scroll down the page without trigging the drag effect.

Had to look into blockViewportScroll(), and it seems like a really aggressive method of preventing scrolling while dragging, since we have no way of controlling it.

I know this is one of those difficult issues, and we can agree that you don’t want scrolling to occur while dragging.

I made “fix” that determines if we are scrolling or dragging based on the initial velocity in onDragStart - but it’s not perfect. Might be better if it could sample a few events before locking it.

function Example() {
  const [allowScroll, setAllowScroll] = useState(false)
  useEffect(() => {
    if (allowScroll) {
      const handleTouch = event => {
        event.stopPropagation()
      }
      document.documentElement.addEventListener('touchmove', handleTouch)
      return () => {
        document.documentElement.removeEventListener('touchmove', handleTouch)
      }
    }
  }, [allowScroll])

  return <motion.div drag="x" onDragStart={(event, info) => {
          setAllowScroll(Math.abs(info.delta.y) > Math.abs(info.delta.x))
        }}
  />
}

Has anyone been able to solve this? Been stuck on this for hours.

I just want to maintain body scroll, but doesn’t matter if i set dragDirectionLock or not, it won’t allow me scroll up/down on mobile. I simply use drag="x".

Can confirm I also have this issue as well. Is this issue on the roadmap of getting fixed in the next major/minor version?

Any news on this?

Not sure if I fully understand why this can’t currently work - but for anybody who happens upon this thread looking for a solution, I did notice that the react-swipeable-views package does seem to achieve this to some extent.

What I would find useful is the ability to scroll vertically but at the same time use framer-motion for dragging on the x-axis. Perhaps it could be opt-in to avoid the problem mentioned by @InventingWithMonster ?

I think this is quite a common use case for many UIs (eg Tinder-style “swipe” interfaces)

Scrolling interfering with dragging because of pointercancel event. pointermove after pointerdown event fires onDragStart and after dragging without scroll pointerup event fires onDragEnd (this is what we want). Scrolling during dragging fires pointercancel event which fires onDragEnd.

Below example prevents animation when dragging horizontally and scrolling:

 onDragEnd={e => {
          if (e.type === 'pointercancel') return;
          animate(
            ref.current,
            { x: 200 },
            { duration: 0.2 },
          );
        }}

style={{ x }} {…bind()}

Hi! is there a way to make this work with motion dragcontraints? im tried this and the scrolling seems to work along with the x drag, but i have to take out the framer motion “drag” property to acomplish. This breaks the dragContraints property.

@danilockthar I just did this last night and it seemed to work…

const myComponent = ({children}) => {
  const animation = useAnimation({
    x: 0,
    transition: {
      type: "spring",
      stiffness: 1,
    },
  });
  const bind = useDrag(
    (state) => {
      if (state.dragging) {
        animation.start({ x: state.movement[0] });
      } else {
        animation.start({ x: 0 });
      }
    },
    {
      axis: "x",
    }
  );

  return (
    <motion.div
      {...bind()}
      animate={animation}
    >
      {children}
    </motion.div>
  )
}

As temporary solution we use react-use-gesture instead of drag properties and animate everything with framer-motion.