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.
Are you finding that scrolling is happening at all when you drag on a component? It should be blocked - ideally we'd only block if you touched in the same direction as defined in drag but there's been issues getting this to work reliably.
You can combine dragDirectionLock with a drag axis so at least, for now, it shouldn't move if the user is swiping in the opposite direciton
<motion.div dragDirectionLock drag="x" />
Thanks! That's at least a step in the right direction :+1:
I had been testing by scrolling in screen space below the list, but that's actually a much bigger issue if/when the list occupies the whole screen. Guessing there's not a way to unblock scrolling right now then?
Not at the moment sadly. I believe there might be a possible fix. But the problem ultimately is that to detect whether a user is dragging and in which direction we need to block the touch event from scrolling, or at least start blocking it before scrolling. But there's a distance a user can swipe within a single event that initiates scrolling. Once it's started, it's unblockable. Mobile browsers 👍
What about allowing the user to control whether blockViewportScroll() is called, via a prop?
In my case, there's no horizontal scroll space available, so a drag on the x axis isn't going to affect it. Scrolling on the y axis shouldn't interfere because of the direction lock, right?
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))
}}
/>
}
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)
Alternatively, for anyone looking for vertical scrolling while being able to drag an element horizontally, you can use react-spring. I have set up a quick codesandbox example:
Has this been implemented yet? I am also facing the same issue ... I have a list of items that is longer than the screen and need to scroll down but he drag="x" keeps preventing 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)) }} /> }
Thank you very much, this solved my Carousel issue ❤ Have you seen any issues with it? The X drag was hit a little bit in responsiveness, but it's worth being able to scroll.
Thank you very much, this solved my Carousel issue ❤ Have you seen any issues with it? The X drag was hit a little bit in responsiveness, but it's worth being able to scroll.
Do you have a working example of this? Can't get this to work in a reliable manner
I have this that used to work (in 1.6.7), but not in the latest version.
const Example = () => {
const clientYStart = useRef(null);
const clientXStart = useRef(null);
const horizontalScrollStart = useRef(0);
const isDragging = useRef(false);
const isScrolling = useRef(false);
return (
<div
onTouchStart={e => {
// Track current direction
const touch = e.targetTouches[0];
clientYStart.current = touch.clientY;
clientXStart.current = touch.clientX;
horizontalScrollStart.current = horizontalScroll.get();
}}
onTouchMove={e => {
const touch = e.targetTouches[0];
const deltaY = Math.abs(touch.clientY - clientYStart.current);
const deltaX = Math.abs(touch.clientX - clientXStart.current);
if (isDragging.current) {
return;
}
if (isScrolling.current) {
e.stopPropagation();
return;
}
if (deltaX > deltaY) {
isDragging.current = true;
} else {
isScrolling.current = true;
e.stopPropagation();
}
}}
onTouchEnd={e => {
if (isScrolling.current) {
horizontalScroll.set(horizontalScrollStart.current);
}
isDragging.current = false;
isScrolling.current = false;
}}>
{items.map(item => (
<motion.div drag={'x'} style={{ x: horizontalScroll }}></motion.div>
))}
</div>
);
};
I'm also dealing with this right now. I have drag='x' with dragDirectionLock enabled on an element that can have overflow-y which is set to scroll, but on mobile I'm not able to scroll it. Perhaps implementing a way to cancel a drag in progress would be helpful? For example, a way to cancel the drag in the onDirectionLock callback if the axis returned is 'y'. Not sure if that would be feasible though; just spitballing.
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".
As temporary solution we use react-use-gesture instead of drag properties and animate everything with framer-motion.
@dimitriirybakov Can you show the basic pattern how you use that to fix this? I am not above copying as this one has stumped me.
@InventingWithMonster well, it's basically something like this
const x = useMotionValue(0);
const bind = useDrag((state) => x.set(state.movement[0]));
return <div style={{ x }} {...bind()}/>;
Can confirm I also have this issue as well. Is this issue on the roadmap of getting fixed in the next major/minor version?
i also have this issue with a list taking all my mobile screen and i cant scroll y-axis because of the listItems having drag=x..
any progress?
Has anyone been able to solve this? Been stuck on this for hours.
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.
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>
)
}
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> ) }
This looks great Benjamin ! Thank you very much. It works great :D
Thanks @BenjaminCorey
Where did you import the useAnimation hook from? Framer Motion's useAnimation hook does not accept any arguments. Is it from a different lib?
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)) }} /> }
Using this solution with vertical list I have the same problem, the event gets locked infinitely onDrag which I am only able to exit by clicking outside.
I think I'm having the opposite issue here in that I have my own scroll-blocking in place using https://www.npmjs.com/package/body-scroll-lock. And framer-motion's built-in blocking appears to break mine after the first drag by colliding with body-scroll-lock. Not sure why yet, perhaps both are modifying body style? Is there a way to tell framer-motion not to attempt any touch scroll blocking because I'm already handling it? My entire UI is in a fullscreen modal - so I know that when the modal is open I want to only allow touch events through that initiated inside the model.
The way it works is by applying touch-action style to the element - maybe
you can override it with an !important rule and see if that fixes it?
On Tue, 6 Oct 2020 at 05:18, Emery Denuccio notifications@github.com
wrote:
>
>
I think I'm having the opposite issue here in that I have my own
scroll-blocking in place using
https://www.npmjs.com/package/body-scroll-lock. And framer-motion's
built-in blocking appears to break mine after the first drag by colliding
with body-scroll-lock. Not sure why yet, perhaps both are modifying body
style?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/framer/motion/issues/185#issuecomment-704003534, or
unsubscribe
https://github.com/notifications/unsubscribe-auth/AB34WKQOHFXWBM3HWSS56KTSJKD7VANCNFSM4IBETH2A
.
Most helpful comment
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?