115

I can't seem to capture the scroll event on an iPad. None of these work, what I am doing wrong?

window.onscroll=myFunction;

document.onscroll=myFunction;

window.attachEvent("scroll",myFunction,false);

document.attachEvent("scroll",myFunction,false);

They all work even on Safari 3 on Windows. Ironically, EVERY browser on the PC supports window.onload= if you don't mind clobbering existing events. But no go on iPad.

ck_
  • 3,353
  • 5
  • 31
  • 33

6 Answers6

156

The iPhoneOS does capture onscroll events, except not the way you may expect.

One-finger panning doesn’t generate any events until the user stops panning—an onscroll event is generated when the page stops moving and redraws—as shown in Figure 6-1.

Similarly, scroll with 2 fingers fires onscroll only after you've stopped scrolling.

The usual way of installing the handler works e.g.

window.addEventListener('scroll', function() { alert("Scrolled"); });
// or
$(window).scroll(function() { alert("Scrolled"); });
// or
window.onscroll = function() { alert("Scrolled"); };
// etc 

(See also https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)

Cœur
  • 37,241
  • 25
  • 195
  • 267
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 25
    Thanks, that forced me to look further at the problem. The real answer was detecting the top of the viewport on the iphone/ipad only works with `window.pageYOffset` and not `document.body.scrollTop||document.documentElement.scrollTop` like every other browser/hardware in existence. – ck_ May 19 '10 at 07:55
  • 6
    @ck_ Unfortunatelly on IPAD `window.pageYOffset` does not get updated in the deceleration phase, but only when scroll finishes. – DATEx2 Dec 16 '13 at 14:50
  • 2
    It is bad practice to directly overwrite window.onscroll. Adding an event listener would be better. Use window.addEventListener('scroll', function (){ alert('scrolled!'); }); – Kevin Dice Jul 21 '14 at 19:54
  • 4
    ehm .. actually, window.onscroll is firing on my ipad all the time now, while panning, and after panning, while decelerating. did something change ? – commonpike Apr 21 '15 at 21:18
  • Safari (and maybe others) changed to using WKWebView with iOS 8. Chrome and Cordova still use UIWebView, so they still exhibit the issue with continuous scroll events not being issued. There's a WKWebView plug-in for Cordova though. – jiku Oct 21 '15 at 19:12
114

For iOS you need to use the touchmove event as well as the scroll event like this:

document.addEventListener("touchmove", ScrollStart, false);
document.addEventListener("scroll", Scroll, false);

function ScrollStart() {
    //start of scroll event for iOS
}

function Scroll() {
    //end of scroll event for iOS
    //and
    //start/end of scroll event for other browsers
}
George Filippakos
  • 16,359
  • 15
  • 81
  • 92
  • 40
    This event is only fired when the user is actively scrolling it is not fired during the deceleration phase of momentum scrolling. – Jon Tirsen Mar 24 '12 at 10:26
  • i changed the code to include the end scroll detection code for iOS – George Filippakos Mar 26 '12 at 08:51
  • You might also want to add a listener for "touchend" which will act similar to scroll on desktop. – BingeBoy Feb 13 '14 at 04:41
  • If you want to get an update right away, add event.preventDefault() to the end of your touchmove handler. But then you have to manually implement scrolling, rather than let the OS do it. Which is rarely recommendable. – Tom Auger Mar 21 '14 at 17:35
  • 1
    Note that this does not provide access to the during scroll phase – Ben Sewards Apr 07 '15 at 16:12
  • 3
    it's been three years. I can't get a realtime update of scroll position during deceleration. how am I supposed to write my maps-like app????? – FlavorScape May 13 '16 at 05:06
40

Sorry for adding another answer to an old post but I usually get a scroll event very well by using this code (it works at least on 6.1)

element.addEventListener('scroll', function() {
    console.log(this.scrollTop);
});

// This is the magic, this gives me "live" scroll events
element.addEventListener('gesturechange', function() {});

And that works for me. Only thing it doesn't do is give a scroll event for the deceleration of the scroll (Once the deceleration is complete you get a final scroll event, do as you will with it.) but if you disable inertia with css by doing this

-webkit-overflow-scrolling: none;

You don't get inertia on your elements, for the body though you might have to do the classic

document.addEventListener('touchmove', function(e) {e.preventDefault();}, true);
Dave Mackintosh
  • 2,738
  • 2
  • 31
  • 40
6

I was able to get a great solution to this problem with iScroll, with the feel of momentum scrolling and everything https://github.com/cubiq/iscroll The github doc is great, and I mostly followed it. Here's the details of my implementation.

HTML: I wrapped the scrollable area of my content in some divs that iScroll can use:

<div id="wrapper">
  <div id="scroller">
    ... my scrollable content
  </div>
</div>

CSS: I used the Modernizr class for "touch" to target my style changes only to touch devices (because I only instantiated iScroll on touch).

.touch #wrapper {
  position: absolute;
  z-index: 1;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  overflow: hidden;
}
.touch #scroller {
  position: absolute;
  z-index: 1;
  width: 100%;
}

JS: I included iscroll-probe.js from the iScroll download, and then initialized the scroller as below, where updatePosition is my function that reacts to the new scroll position.

# coffeescript
if Modernizr.touch
  myScroller = new IScroll('#wrapper', probeType: 3)
  myScroller.on 'scroll', updatePosition
  myScroller.on 'scrollEnd', updatePosition

You have to use myScroller to get the current position now, instead of looking at the scroll offset. Here is a function taken from http://markdalgleish.com/presentations/embracingtouch/ (a super helpful article, but a little out of date now)

function getScroll(elem, iscroll) {   
  var x, y;

  if (Modernizr.touch && iscroll) {
    x = iscroll.x * -1;
    y = iscroll.y * -1;   
  } else {
    x = elem.scrollTop;
    y = elem.scrollLeft;   
  }

  return {x: x, y: y}; 
}

The only other gotcha was occasionally I would lose part of my page that I was trying to scroll to, and it would refuse to scroll. I had to add in some calls to myScroller.refresh() whenever I changed the contents of the #wrapper, and that solved the problem.

EDIT: Another gotcha was that iScroll eats all the "click" events. I turned on the option to have iScroll emit a "tap" event and handled those instead of "click" events. Thankfully I didn't need much clicking in the scroll area, so this wasn't a big deal.

Melinda Weathers
  • 2,649
  • 25
  • 31
1

Since iOS 8 came out, this problem does not exist any more. The scroll event is now fired smoothly in iOS Safari as well.

So, if you register the scroll event handler and check window.pageYOffset inside that event handler, everything works just fine.

Matthias Bohlen
  • 598
  • 6
  • 12
  • 1
    It does exists yet in some use cases like using Cordova. https://developer.telerik.com/featured/scroll-event-change-ios-8-big-deal/ – Diosney Dec 19 '18 at 15:21
  • 8
    My experience is that this still remains a problem with current iOS devices in 2019. – Chris Apr 02 '19 at 17:16
  • Still experiencing this problem with iOS devices running iOS 15 – Philip Jun 16 '22 at 13:41
0

After some testing on the ios, I found that this is the way to go for ios and desktop, if you are not worried of that delay of 120ms on desktop. Works like a charm.

let isScrolling;   
document.addEventListener("scroll", () => {
  // Clear our timeout throughout the scroll
  window.clearTimeout( isScrolling );

  // Set a timeout to run after scrolling ends
  isScrolling = setTimeout(function() {

    // Run the callback
    console.log( 'Scrolling has stopped.' );

  }, 120);
});