Is it possible to force-stop momentum scrolling on iphone/ipad in javascript?
Extra: pretty sure this is pie in the sky, but for bonuspoints (honor and kudos), after dom-manipulation and a scrollTo applied, resume scroll with the same momentum before the forced stop. How to?
This is actually very possible when using fastclick.js. The lib removes the 300ms click delay on mobile devices and enables event capturing during inertia/momentum scrolling.
After including fastclick and attaching it to the body element, my code to stop scrolling and go to the top looks like this:
scrollElement.style.overflow = 'hidden';
scrollElement.scrollTop = 0;
setTimeout(function() {
scrollElement.style.overflow = '';
}, 10);
The trick is to set overflow: hidden, which stops the inertia/momentum scrolling. Please see my fiddle for a full implementation of stop scrolling during inertia/momentum.
Here is my code using jQuery animation (running as onclick event)
var obj=$('html, body'); // your element
if(!obj.is(':animated')) {
obj.css('overflow', 'hidden').animate({ scrollTop: 0 }, function(){ obj.css('overflow', ''); });
}
Tested on iPhone 6
I have found a way to CANCEL the BODY momentum scrolling by assigning the html.scrollTop property on touchend event. Looks like this:
let html = document.querySelector('html');
window.addEventListener( 'touchend', function( e ){
html.scrollTop = html.scrollTop;
});
Tested on iOS 13
UPD: The above solution fails on iOS 12, because the actual scrolling element is not "html" in this version.
The below code works for Both 12 & 13:
window.addEventListener( 'touchend', function( e ){
window.scroll( 0, window.scrollY );
});
Related
The page flickers on scroll, it works fine in Firefox but not in chrome. I tried disabling the chrome smooth scroll plugin but still doesn't work.
If you inspect the source code and search for 'scroll', you will find the following event bindings (unminified & beautified) :
$window.bind('scroll').resize();
$(window).bind('mousewheel DOMMouseScroll', function(event) {
if (/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())) {
var delta = event.originalEvent.wheelDelta;
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
};
$("html").stop().animate({
scrollTop: $("html").scrollTop() + (-delta * 1.7)
}, 200, 'linear');
}
});
The resize on scroll is a weird thing, but the second binding is definitely nonsense and is the one causing the issue :
every tiny scroll animation will be interrupted $("html").stop()
and overridden with a contradictory scroll animation .animate({scrollTop: $("html").scrollTop() + (-delta * 1.7)})
Before commenting/deleting this code, just unbind from the chrome console to check :
jQuery(window).unbind('mousewheel DOMMouseScroll');
When you scroll to the middle of the page and then refresh it, the browser will automatically scroll to the position you were on. I need to be able to differentiate between this automatic scroll and user scroll and attach different events to them.
I'm currently using
window.addEventListener('scroll', function(e) {});
to listen to the scroll event, but it gets triggered in both user and automatic scrolls.
Is there a way to tell between the two?
PS the suggested answer to similar question only uses mousewheel as an indication of user scroll, but if the user uses mouse to pull the scroll, it will fail
If you have two flags for both events, will that work?
I don't have a mouse to test this in whole unfortunately.
Try this fiddle
window.addEventListener( 'load', function() {
var mousewheel = 0;
var scroll = 0;
window.addEventListener( 'scroll', function(e) {
mousewheel = 0;
scroll = 1;
alert("mousewheel: " + mousewheel + ", scroll: " + scroll);
}, false);
window.addEventListener("mousewheel", function(e) {
mousewheel = 1;
scroll = 0;
alert("mousewheel: " + mousewheel + ", scroll: " + scroll);
}, false);
}, false);
As Manohar previously mentioned, you can use some combination of an onscroll event and an onwheel event. onscroll runs on all scroll events, whenever the content is interacted with. onwheel will run on mouse or trackpad scrolls, but not arrow scrolling. It's also worth mentioning that onwheel is not guaranteed to trigger an onscroll event, depending on the context. There's more detail provided in its documentation.
There's another similar question here with some additional suggestions on redesigning your app, while setting some boolean flags that can help determine who (computer or real user) is initiating the scroll.
I have a phonegap application that uses iOS native scrolling through -webkit-overflow-scrolling in a div. I want to be able to manually halt an ongoing scroll when the user clicks a button (to scroll back to the top of the page). Is this doable?
This is actually very possible when using fastclick.js. The lib removes the 300ms click delay on mobile devices and enables event capturing during inertia/momentum scrolling.
After including fastclick and attaching it to the body element, my code to stop scrolling and go to the top looks like this:
scrollElement.style.overflow = 'hidden';
scrollElement.scrollTop = 0;
setTimeout(function() {
scrollElement.style.overflow = '';
}, 10);
The trick is to set overflow: hidden, which stops the inertia/momentum scrolling. Please see my fiddle for a full implementation of stop scrolling during inertia/momentum.
Unfortunately this is not possible at the moment. The scroll event is triggered only when the scrolling has come to an end. As long as the momentum keeps moving the content no events are fired at all. You can see this in Figure 6-1 The panning gesture in Apple's "Safari Web Content Guide".
I also created a fiddle to demonstrate this behavior. The scrollTop value is set after iOS is done animating.
You can capture a touch event using 'touchstart' instead of 'click', as the click event sometimes doesn't seem to get fired until the momentum scroll completes. Try this jQuery solution:
$('#yourTrigger').on('touchstart', function () {
var $div = $('.yourScrollableDiv');
if ($div.scrollTop() === 0) {
return false; //if no scroll needed, do nothing.
}
$div.addClass('scrolling'); //apply the overflow:hidden style with a class
$div.animate({
scrollTop: 0
}, 600, function () {
$div.removeClass('scrolling'); //scrolling has finished, remove overflow:hidden
});
}
where the 'scrolling' class simply has the CSS property, overflow:hidden, which as #Patrick-Rudolph said, will halt any momentum scrolling in progress.
.scrolling {
overflow: hidden;
}
Note: It's best to use a callback function to tell when your scroll animation finishes, rather than setting a timer function.
I am using the most wonderful javascript tool iScroll4 http://cubiq.org/iscroll-4 on a mobile website for iOS and Android. Here is what my layout looks like:
The horizontally scroll-able area is making use of iScroll4 with the following settings:
var myScroll = new iScroll('frame', { hScrollbar: false, vScrollbar: false, vScroll: false })
The horizontal scrolling part works great. This issue is what happens when a user attempts to scroll up or down the page placing their finger on the horizontal scrolling area. So I need native vertical scrolling, and iScroll horizontal scrolling on the same area.
What I have tried so far:
Removing e.preventDefault() in the iScroll code (allows for native scrolling, but in BOTH axes).
Removing e.preventDefault() and then disabling horizontal scrolling page wide with this:
var touchMove;
document.ontouchstart = function(e){
touchMove = e.touches[0];
}
document.ontouchmove = function(e){
var theTouch = e.touches[0] || e.changedTouches[0];
var Xer = rs(touchMove.pageX - theTouch.pageX).toPos();
var Yer = rs(touchMove.pageY - theTouch.pageY).toPos();
touchMove = theTouch;
if(Yer > Xer){ e.preventDefault(); }
}
which seems to do nothing. How can I allow for native vertical scrolling in the horizontal scrolling area, without loosing the horizontal scrolling of iScroll? I am really stumped here. Thanks in advance.
(just for the record rs(foo).toPos() is a function that makes foo a positive number regardless of its value).
If you would like to achieve the effect described by Fresheyeball without hacking the core, and without changing from iScroll to swipeview, then iScroll 4 does offer you its event listeners to work with.
myScroll = new iScroll('scrollpanel', {
// other options go here...
vScroll: false,
onBeforeScrollMove: function ( e ) {
if ( this.absDistX > (this.absDistY + 5 ) ) {
// user is scrolling the x axis, so prevent the browsers' native scrolling
e.preventDefault();
} else {
// delegate the scrolling to window object
window.scrollBy( 0, -this.distY );
}
},
});
By doing so, the onBeforeScrollMove-Handler checks whether the scroll direction seems to be horizontal, and then prevents the default handler, thus effectively locking the scroll action to the X-Axis (try commenting it out, you'll see the difference). Otherwise, if the scroll direction needs to be vertical, we make the browser scroll via the window.scrollBy() method. This is not exactly native, but does the job just fine.
Hope that helps
Lukx
[EDIT]
My original solution, which didn't use window.scrollBy() ,did not work on slower Samsung phones, which is why I needed to adapt the answer.
Suggested edit to #Lukx's excellent solution. New versions of iScroll4 place the e.preventDefault() in onBeforeScrollMove which can be overridden. By placing the if block into this option, default is not prevented for vertical scrolling, and vertical can scroll natively.
myScroll = new iScroll('scrollpanel', {
// other options go here...
vScroll: false,
onBeforeScrollStart: function ( e ) {
if ( this.absDistX > (this.absDistY + 5 ) ) {
// user is scrolling the x axis, so prevent the browsers' native scrolling
e.preventDefault();
}
},
});
With iscroll 5, you can set eventPassthrough: true to achieve this. See http://iscrolljs.com/#configuring
OLD ANSWER
UPDATE a special pluggin has been written just to address this problem:
http://cubiq.org/swipeview
I found a way!
add a variable to the top of the document: if android is 15 and is iOS is 3
var scrollTolerance = ( rs().isDevice('android') )?15:3;
disable the original e.preventDefault(); for scrolling. This is under onBeforeScrollStart:
the in _move just under
timestamp = e.timeStamp || Date.now();
add this line
if( Math.sqrt(deltaX*deltaX) > scrollTolerance){e.preventDefault();}
What this does is the following:
the scrollTolerance sets, you guessed it, a tolerance for finger direction. We don't want to demand a perfect vertical angle to get the up down native scroll. Also iOS does not detect properly and will never be higher than 4 for some reason so I used 3. Then we disable iScroll's standard e.preventDefault(); which prevents native vertical scrolling on our bi-scrollable area. Then we insert e.preventDefault(); only upon move and based on finger direction from tolerance.
This does not work perfect. But is acceptable and works on iOS and Android. If anyone sees better ways please post here. This is something I (and assume others) need to use regularly, and we should have a perfect rock solid solution.
Thanks.
Please test this solution from Adam.
https://gist.github.com/hotmeteor/2231984
I think the trick is to add the check in onBeforeScrollMove. First get the initial touch position in onBeforeScrollTouchStart and then in onBeforeScrollMove check the new position and then disable the required scroll based on the difference.
iScroll 5 supports native scrolling of any axis!
http://iscrolljs.com/
on iScroll5 just set eventPassthrougt to true. That fixes it.
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.
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)
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
}
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);
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.
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.
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);
});