metafizzy / flickity

:leaves: Touch, responsive, flickable carousels
https://flickity.metafizzy.co
7.53k stars 602 forks source link

iOS 11.3 bug - page scrolls while dragging #740

Open levymetal opened 6 years ago

levymetal commented 6 years ago

https://github.com/metafizzy/flickity/issues/457 is back. iOS 11.3 using Safari, Flickity 2.1.1.

Seems to be a problem with iOS 11.3, not a regression in Flickity.

No test case required, the default CodePen demos the issue, as does the Flickity homepage https://codepen.io/desandro/pen/azqbop https://flickity.metafizzy.co

desandro commented 6 years ago

Thanks for reporting this bug! Here's the deal:

iOS 11.3 uses passive event listeners by default. Flickity and its dependencies should use { passive: false } when adding event listeners. (Thx Stack Overflow). This will be coming in the next release.

HOWEVER, iOS 11.3.0 has Bug 184250, which breaks Flickity's behavior of using preventDefault on touchmove but not touchstart. Good news is that this bug has been addressed by WebKit lama Dean Jackson. You can add yourself to the CC list to the WebKit bug to help promote this issue.

For now, my options with iOS 11.3.0 are either prevent all scrolling when you touch inside a Flickity carousel, or keep current behavior, and allow page scrolling to occur with carousel dragging. I choose to keep current behavior.

Add a 👍 reaction to this issue if you ran into this issue as well so I can know how prevalent the issue is. Do not add +1 comments — They will be deleted.

Related: Shopify/draggable#198

thekevbot commented 6 years ago

I found a workaround for this using the dragStart and dragEnd events:

$carousel.on( 'dragStart.flickity', function( event, pointer ) {
    document.ontouchmove = function (e) {
        e.preventDefault();
    }
});
$carousel.on( 'dragEnd.flickity', function( event, pointer ) {
    document.ontouchmove = function (e) {
        return true;
    }
});
desandro commented 6 years ago

WebKit Bug 184250 has been addressed, received a patch, and is now listed as Resolved. Keep an eye on this bug and future Safari releases to see when this patch lands.

danazkari commented 6 years ago

Hello @desandro!

So I'm working on a project where we're using this amazing library, and we were wondering if you've gotten any further insight on this issue about WebKit, their bug page doesn't say too much about the plan on that specific patch.

Thanks!

desandro commented 6 years ago

Sorry, I don't have any insights on updates with this bug at the moment. Anyone with iOS 11.4 installed care to report?

Spone commented 6 years ago

I just upgraded to iOS 11.4.1 and I still have the issue.

datune commented 6 years ago

Same issue on both iOS Chrome and Safari.

@desandro Is it possible to implement an option so we can choose where to enable / disable the behaviour?

desandro commented 6 years ago

Good news! From Comment 31 on WebKit bug 184250

The fix should be included in the iOS 12 betas.

So when iOS 12 goes out, hopefully this will be resolved.

xcrap commented 6 years ago

Just to tell I'm using iOS 12.0 (Beta 8) and still have this issues.

alexbouchardd commented 6 years ago

Also seeing the issue in the public release of iOS 12. Just to make sure we are talking about the same issue, here is a video:

https://drive.google.com/file/d/1X6A9Rd9DtAQ6OvkQ-liLcRK8QnSd6PDf/view?usp=sharing

datune commented 6 years ago

Confirmed. It is not fixed in iOS 12, which really, like, really sucks. Not even the very latest Chrome on iOS12 makes any difference. @desandro Is there nothing we can do?

SamHall commented 6 years ago

I solved it by enclosing the carousel in the div id "flick" and added this code:

document.getElementById("flick").addEventListener('touchmove', function(e) { e.preventDefault(); }, { passive: false });

jatinwaichal commented 5 years ago

Hi all, Is there an update on this issue? I am still facing the issue on iOS 12

AlexandraKlein commented 5 years ago

@jatinwaichal which solve have you implemented? Mine works perfectly for me.

alexbouchardd commented 5 years ago

@AlexandraKlein Solution worked perfectly fine for me as well. Had to tweak it slightly because I am not using jquery

// this.flkty = new Flickity(element)
this.flkty.on('dragStart', () => (document.ontouchmove = e => e.preventDefault()));
this.flkty.on('dragEnd', () => (document.ontouchmove = () => true));
sheriffderek commented 5 years ago

Ok. So, I'm not crazy? This is good news. : )

I always have such a great experience with Flickity - but in this latest project / I'm definitely feeling whatever this iOS change is.

I was doing some comparisons here: code: https://codepen.io/pxlagency/pen/xmbJyw?editors=0010 And I've added @alexbouchardd 's and @SamHall 's solutions / but I can't tell if it's working as expected. The first one seems to completely disable scrolling in that area / which is rough if you have a slider that takes up most of the screen / and I can't tell if the second one is helping. For viewing on phone: https://s.codepen.io/pxlagency/debug/xmbJyw

In our actual project / we're using vue-flickity / but that wouldn't really change anything - and is why I made this example in a sandbox.

Any ideas?

markdavies commented 5 years ago

The dragStart / dragEnd fix doesn't work for me on Safari iOS12. Is there no way around this at all?

Yakoot commented 5 years ago

any ideas how to fix it?

chessydk commented 5 years ago

Btw, a comment in the original IOS bug (Bug 184250), which is now closed, mentions another related IOS bug which has no fix-plans yet: https://bugs.webkit.org/show_bug.cgi?id=185656 and could cause similar problem. So for this reason I hope this bug will receive high priority (the workarounds suggested are also not working for me on IOS 12.1.4)

alex-figge-schuster commented 5 years ago

Any solution how to handle this iOS bug by flickity? Can't you detect the scroll direction and block or allow vertical page scroll?

SamHall commented 5 years ago

My 10/23/18 post appears to work. I didn't end up using the page, but it's live and you can see it here: https://chattanoogahistory.com/gallerynew5

alex-figge-schuster commented 5 years ago

@SamHall thank you. Your solution seems to work in your case, but it won't work if you have a fullpage flickity instance (or at least one which takes up most of the viewport). In this case you can't scroll the page because every vertical scroll gets picked up by your implementation. I would need a solution which allows vertical page scroll by detecting the scroll direction(s) and the scroll distance. So, when a users starts scrolling at 200, 100 (x, y) and scrolls to 220, 200 it should assume that the user want's to scroll vertical (so flickity should not grap the scrolling event respectively pass it through). But when the user starts at 200, 100 and scrolls to 280, 90 for example, flickity should grap the event.

SamHall commented 5 years ago

Thank you for clarifying ‘vertical page scroll’.

woahdae commented 5 years ago

I've got a workaround / temporary fix.

The bug referred to up top (iOS 11.3.0 Bug #184250) is about event.preventDefault() not working for event listeners registered while inside another event listener. Flickity does just this, adding and removing a touchmove listener from inside touchstart/touchend listeners. It doesn't have to be that way - you could listen to all touchmove events and not do anything if you don't need to.

Anyways, I don't have the time/desire to refactor Flickity for a workaround, and the maintainers might not want that either. Here's a bit of code that will block vertical scrolling inside .carousel-cell elements if the swipe is also somewhat horizontal (using Flickity.defaults.dragThreshold):

https://gist.github.com/woahdae/aa579f1eced44fb89e8fd1b7183d67de

;(function() {
  var touchingCarousel = false
    , touchStartCoords

  document.body.addEventListener('touchstart', function(e) {
    if (e.target.closest('.carousel-cell')) {
      touchingCarousel = true
    } else {
      touchingCarousel = false
      return
    }

    touchStartCoords = {
      x: e.touches[0].pageX,
      y: e.touches[0].pageY
    }
  })

  document.body.addEventListener('touchmove', function(e) {
    if (!(touchingCarousel && e.cancelable)) return

    var moveVector = {
      x: e.touches[0].pageX - touchStartCoords.x,
      y: e.touches[0].pageY - touchStartCoords.y
    }

    if (Math.abs(moveVector.x) > Flickity.defaults.dragThreshold)
      e.preventDefault()

  }, {passive: false})

  // Polyfill for Element.closest
  // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
  if (!Element.prototype.matches) {
    Element.prototype.matches =
      Element.prototype.msMatchesSelector ||
      Element.prototype.webkitMatchesSelector
  }

  if (!Element.prototype.closest) {
    Element.prototype.closest =
      function(s) {
        var el = this

        do {
          if (el.matches(s)) return el
          el = el.parentElement || el.parentNode
        } while (el !== null && el.nodeType === 1)
        return null
      }
  }
})();

(the above is written for clarity, not OO best practices etc)

Ross7377 commented 5 years ago

Works perfect for me

            var tapArea, startX ;
            tapArea = document.querySelectorAll('.gallery');
            startX = 0;
            for (var item of tapArea) {
                item.ontouchstart = function(e) {
                    startX = e.touches[0].clientX;
                };
                item.ontouchmove = function(e) {
                    if (Math.abs(e.touches[0].clientX - startX) > 5 && e.cancelable ) {
                        e.preventDefault();
                    }
                };
            }
wilbertcaba commented 5 years ago

@Ross7377 It worked for me too. Thanks for the temporary fix!

wilbertcaba commented 5 years ago

Works perfect for me

            var tapArea, startX ;
            tapArea = document.querySelectorAll('.gallery');
            startX = 0;
            for (var item of tapArea) {
                item.ontouchstart = function(e) {
                    startX = e.touches[0].clientX;
                };
                item.ontouchmove = function(e) {
                    if (Math.abs(e.touches[0].clientX - startX) > 5 && e.cancelable ) {
                        e.preventDefault();
                    }
                };
            }

@Ross7377 However, for the heads up, this is breaking Flickity on IE11. I had to wrap it in conditional comments:

<!--[if !IE]-->[your script...]<!--<![endif]-->
timothyallan commented 5 years ago

Yeah, you'd need to use a for loop, or polyfill the for ... of loop. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

davidhellmann commented 5 years ago

iOS 13 it looks fine here. iOS 12 we also have the problem. We have the solution from @thekevbot included but doesn't work.

saetia commented 5 years ago

adding this css fixes the issue on iOS 13:

.slider {
    touch-action: none; -webkit-user-select: none; -webkit-user-drag: none;
}
davidhellmann commented 5 years ago

@saetia yes but with this css you cant scroll Y-axis.

seansmyth commented 4 years ago

Seems to be an issue on Firefox on Android for me.

andrey-scada commented 4 years ago

@Ross7377, thank you so much for your solution!

vasanthangel4 commented 3 years ago

@andrey-scada: scrolling issue happened in ios 15, dots also not working

vasanthangel4 commented 3 years ago

Hi all, if anyone have fixed the iOS 15 issue

vasanthangel4 commented 3 years ago

Hi all, Is there an update on this scroller issue in iOS 15 I am still facing the issue on iOS 15

bansavage commented 2 years ago

@woahdae 's fix is working for me today (may 2022)

gregg-cbs commented 2 years ago

I hope someone comes up with a better fix for this issue soon :)

gregg-cbs commented 2 years ago

I am wondering if something like this will work?

// stop page from scrolling when swiping flickity
let scrollY;

flkty.on('dragStart', () => {
  scrollY = window.scrollY;
});

flkty.on('dragMove', () => {
  window.scrollTo(0, scrollY)
});