Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use passive event listener for touchmove (to prevent scrolling while swiping) #2

Merged
merged 4 commits into from
Jun 3, 2019

Conversation

xanido
Copy link
Contributor

@xanido xanido commented May 31, 2019

This small patch restores react-slider's ability to disable vertical scrolling when swiping through a slider on iOS.

Seems the touchmove event listener that React adds (when using the React's event system via event props) is not configured as a non-passive listener. iOS (starting with iOS 11.3) has made all root document touch event listeners passive by default to improve scrolling performance. React's event system attaches event handlers to the document root, which will result in a passive touchmove listener on iOS, which in turn means that preventDefault() has no effect.

The proposed solution in facebook/react#2043 should fix the issue, but for now adding the event listener directly to the node also solves the problem.

Please note, the {passive: false} in the addEventListener call in this PR is not technically necessary as only document root event listeners are passive by default on iOS, but it does explicitly communicate the intent.

@@ -188,6 +195,13 @@ export class InnerSlider extends React.Component {
// this.props.onLazyLoad([leftMostSlide])
// }
this.adaptHeight();

if (prevProps.touchMove !== this.props.touchMove) {
const method = touchMove ? "add" : "remove";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is touchMove defined? Should be this.props.touchMove?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, fixed!

@xanido xanido force-pushed the prevent-scroll-on-swipe-ios branch from 8b951b2 to adbd56e Compare May 31, 2019 03:44
@@ -704,7 +714,6 @@ export class InnerSlider extends React.Component {
onMouseUp: touchMove ? this.swipeEnd : null,
onMouseLeave: this.state.dragging && touchMove ? this.swipeEnd : null,
onTouchStart: touchMove ? this.swipeStart : null,
onTouchMove: this.state.dragging && touchMove ? this.swipeMove : null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you also need to preserve this logic around this.state.dragging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added it back in but in a slightly different manner to work around another iOS Safari quirk (touch listeners registered by other touch listeners don't seem to be able to be made non-passive).

@@ -97,6 +97,11 @@ export class InnerSlider extends React.Component {
slide.onblur = this.props.pauseOnFocus ? this.onSlideBlur : null;
}
);

if (this.props.touchMove) {
this.list.addEventListener("touchmove", this.swipeMove);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you remove { passive: false } because it's the default? I'd probably keep it and add a comment to explain that we need passive set to false to allow preventDefault and link to https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener for more details.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I felt that it was unnecessary to leave it in, as false is the default. I can add it back in to guard against a future change to the default (although I'd guess it'd be very unlikely to happen)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it back in and added note

Previously touchmove handler was registered from within a touchstart event.
Seems as though handlers registered from within touch handlers are
always passive, regardless of the passive param. Instead we keep the touchmove
handler around, and return early if not in a dragging state.
@xanido xanido force-pushed the prevent-scroll-on-swipe-ios branch from 82def76 to 878b61b Compare May 31, 2019 06:04
Copy link
Contributor

@mattcolman mattcolman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me! Awesome work @xanido !

@mattcolman
Copy link
Contributor

oh one last thing, might need to clean up touchmove in componentWillUnmount?

const method = this.props.touchMove ? "add" : "remove";
// swipeMove calls preventDefault which only has an effect on non-passive events
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
this.list[`${method}EventListener`]("touchmove", this.swipeMove, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isnt there a chance that there is a listener thats created but not removed here?

@xanido
Copy link
Contributor Author

xanido commented May 31, 2019 via email

@@ -118,6 +127,11 @@ export class InnerSlider extends React.Component {
this.callbackTimers.forEach(timer => clearTimeout(timer));
this.callbackTimers = [];
}
if(this.props.touchMove) {
this.list.removeEventListener("touchmove", this.swipeMove, {
passive: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to check if animating is true, if its true you can have passive true which will improve performance
as you are promising the handler won't call preventDefault to disable scrolling.

// swipeMove calls preventDefault which only has an effect on non-passive events
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
this.list.addEventListener("touchmove", this.swipeMove, {
passive: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to check if animating is true, if its true you can have passive true which will improve performance
as you are promising the handler won't call preventDefault to disable scrolling.

@jooj123
Copy link
Contributor

jooj123 commented Jun 2, 2019

why do you need to disable vertical scrolling when swiping ?

@mattcolman
Copy link
Contributor

That's the expected behaviour, if you are trying to swipe horizontal it won't also allow you to scroll vertically.

@jooj123 jooj123 merged commit 668dfd6 into DomainGroupOSS:master Jun 3, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants