Closed danprince closed 4 years ago
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 .
Thanks @BenjaminCorey
Where did you import the
useAnimation
hook from? Framer Motion'suseAnimation
hook does not accept any arguments. Is it from a different lib?
Hey @omarryhan do you found the way how to handle it? I have stucked with tha same problem. Ca't drag scrollable container on mobile...
Hey @Dm1Korneev
Here's the code I used: https://github.com/omarryhan/trendzz/blob/master/components/WithSlide/Component.tsx
And for the live demo, go to: https://trendzz.netlify.app and try swiping a repo card to the left.
Hope that helps.
Edit:
On desktop, you'll need to click and drag from either the very top of the card or the very bottom. Because if you click in the middle, it will trigger an onclick event which will open a new tab.
Any news on this?
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 },
);
}}
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.
Describe alternatives you've considered I also tried overriding by passing
transform
styles directly to themotion.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 theonDragStart
event is called.Another option might be having some way to cancel the animation (but not the event) from the
onDrag
handler.