I am creating a full screen web app which will have some modules/widgets which make use of the new iOS 5 overflow:scroll features. What I want is to disable that 'bouncy' effect when scrolling the html/body (since it is full screen) but keep that effect only on the scrollable elements.
to smooth the effects of scrollable elements I have:
html, body { overflow: hidden; }
.scrollable {
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
and then the following script which disables the touch scroll effect:
$(document).bind('touchmove', function (e) {
if (e.target === document.documentElement) {
e.preventDefault();
}
});
although this doesn't seem to work at all, because when scrolling an element to very bottom end or top it also scrolls the documentElement
.
Is there any way to only disable that effect for the body html element?
Here it is a good example of how this affects the functionality:
http://dl.dropbox.com/u/1928164/ios5/index.html
It's unfortunate that -webkit-overflow-scrolling
doesn't handle this better. You need to track the y
position to make it work. I put the class scroll
on anything I want to scroll on my page, such as <ul>
elements. Wrap a <div>
around the <ul>
that fills the viewport with overflow-y: auto
. Don't put overflow
or height
on the <ul>
. The <ul>
will expand as tall as its contents and it's the <div>
that is actually doing the scrolling. -webkit-overflow-scrolling
is inherited, so put it as far up the DOM as you want.
Demo: http://jsfiddle.net/ThinkingStiff/FDqH7/
Script:
var swipeY = 0;
function onTouchMove( event ) {
var scroll = event.target.closestByClassName( 'scroll' );
if ( scroll ) {
var top = scroll.positionTop - scroll.parentNode.positionTop,
heightDifference = ( 0 - scroll.offsetHeight + scroll.parentNode.offsetHeight );
if( ( top >= 0 ) && ( event.touches[0].screenY > swipeY ) ) {
event.preventDefault(); //at top, swiping down
} else if( ( top <= heightDifference ) && ( event.touches[0].screenY < swipeY ) ) {
event.preventDefault(); //at bottom, swiping up
};
} else {
event.preventDefault();
};
};
function onTouchStart( event ) {
swipeY = event.touches[0].screenY;
};
Element.prototype.closestByClassName = function ( className ) {
return this.className && this.className.split( ' ' ).indexOf( className ) > -1
? this
: ( this.parentNode.closestByClassName && this.parentNode.closestByClassName( className ) );
};
window.Object.defineProperty( Element.prototype, 'positionTop', {
get: function () {
return this.offsetTop - this.parentNode.scrollTop;
}
} );
document.getElementById( 'viewport' ).addEventListener( 'touchmove', onTouchMove, false );
document.getElementById( 'viewport' ).addEventListener( 'touchstart', onTouchStart, false );
HTML:
<div id="viewport">
<div id="scroll-view">
<ul class="scroll">
<li>scroll scroll scroll scroll scroll </li>
<li>scroll scroll scroll scroll scroll </li>
<li>scroll scroll scroll scroll scroll </li>
. . .
</ul>
</div>
</div>
CSS:
#viewport {
border: 1px solid black;
height: 460px;
width: 320px;
-webkit-overflow-scrolling: touch;
}
#scroll-view {
height: 100%;
overflow-y: auto;
width: 100%;
}
Here's a similar answer to ThinkingStiff's, except that it doesn't mandate your html structure. It looks for any elements that have overflowed content and enables scrolling only when the user is interacting with them.
Downsides:
Once the user hits the top or bottom limit of the scrollable node, bouncing within that node will not occur (but it will if you flick from below/above the limits). This probably means that it does not satisfy your requirements for pull to refresh :(
There's an odd 2px difference that I noticed in my test cases when calculating scroll offsets. Not sure where that came from, and you might need to tweak the value
CoffeeScript:
# Vertical scrolling behavior overrides.
#
# This disables vertical scrolling on the page for touch devices, unless the user is scrolling
# within an overflowed node. This requires some finessing of the touch events.
#
# **NOTE:** This code ends up disabling bounce behavior if the user tries to scroll on a node that
# is already at its upper or lower limit.
window$ = $(window)
initialY = null
nodeStack = []
# When a user begins a (potential) drag, we jot down positional and node information.
#
# The assumption is that page content isn't going to move for the duration of the drag, and that
# it would also be awkward if the drag were to change/stop part way through due to DOM
# modifications.
window$.bind 'touchstart', (evt) ->
initialY = evt.originalEvent.pageY
nodeStack = $(evt.target).parents().andSelf().filter(':not(body, html)').get().reverse()
nodeStack = nodeStack.map (node) -> $(node)
window$.bind 'touchend touchcancel', (evt) ->
initialY = null
nodeStack = []
# We override the `touchmove` event so that we only allow scrolls in allowable directions,
# depending on where the user first began the drag.
window$.bind 'touchmove', (evt) ->
return evt.preventDefault() if initialY == null
# A positive direction indicates that the user is dragging their finger down, thus wanting the
# content to scroll up.
direction = evt.originalEvent.pageY - initialY
for node$ in nodeStack
nodeHeight = node$.height()
# For some reason, the node's scrollHeight is off by 2 pixels in all cases. This may require
# tweaking depending on your DOM. Concerning.
scrollHeight = node$[0].scrollHeight - 2
nodeScrollTop = node$.scrollTop()
# If we have a scrollable element, we want to only allow drags under certain circumstances:
if scrollHeight > nodeHeight
# * The user is dragging the content up, and the element is already scrolled down a bit.
return if direction > 0 and nodeScrollTop > 0
# * And the reverse: the user is dragging the content down, and the element is up a bit.
return if direction < 0 and nodeScrollTop < scrollHeight - nodeHeight
# Otherwise, the default behavior is to disable dragging.
evt.preventDefault()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With