iScroll with native scrolling on one axis - javascript

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.

Related

How to create forced scrolling to anchors on a website on scroll

I have a site where I have each section as 100vh so it fills the height of the screen perfectly. The next step I wanted to implement was disabling the regular scrolling, and on scroll force the screen to jump smoothly to the top of the next 100vh section. Here is the example of this animation / feature:
https://www.quay.com.au/
I was having a hard time finding any answers for this as most things just deal with smooth scrolling when clicking on anchors, not actually forcing div relocation when the user scrolls up / down.
I just wanted to know what code I would need do this...
Thanks, been using stack overflow for a while but first post, let me know if there is anything I can do to make this more clear.
disclaimer: this solution needs some testing and probably a bit of improvements, but works for me
if you don't want to use a plugin and prefer a vanilla JavaScript solution I hacked together a small example how this can be achieved with JS features in the following codepen:
https://codepen.io/lehnerchristian/pen/QYPBbX
but the main part is:
function(e) {
console.log(e);
const delta = e.deltaY;
// check which direction we should scroll
if (delta > 0 && currentlyVisible.nextElementSibling) {
// scroll downwards
currentlyVisible = currentlyVisible.nextElementSibling;
} else if (delta < 0 && currentlyVisible.previousElementSibling) {
// scroll upwards
currentlyVisible = currentlyVisible.previousElementSibling;
} else {
return false;
}
// perform scroll
currentlyVisible.scrollIntoView({ behavior: 'smooth' });
e.preventDefault();
e.stopPropagation();
}
what it does is that it listens for the wheel event and then calls the callback, which intercepts the scroll event. inside the callback the direction is determined and then Element.scrollIntoView() is called to let the browser do the actual scrolling
check https://caniuse.com/#search=scrollintoview for browser support, if you're going for this solution

Is there a reliable way to detect scrolling, on all devices, using javascript/jQuery?

I've been struggling with this for a while, and I'm surprised that doing this isn't more straightforward...
I need to detect when the user scrolls a page, either with the mouse, scrollbar or by touch on mobile devices. jQuery has their scroll() function which works alright, but it requires that the page is actually scrolling. I want to detect the scrolling wether the page is scrolling or not (say I reach the end of the page, and there is nowhere left to scroll too.. I still want to know if the user is trying to scroll)
I found another question that had asked something similar, along the lines of detecting scroll input even when the page isn't scrolling, and I got this chunk of code:
if (document.addEventListener) {
document.addEventListener("mousewheel", MouseWheelHandler(), false);
document.addEventListener("DOMMouseScroll", MouseWheelHandler(), false);
} else {
sq.attachEvent("onmousewheel", MouseWheelHandler());
}
function MouseWheelHandler() {
return function (e) {
var e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
if (delta < 0) {
// increase scroll amount
} else {
// decrease scroll amount
}
}
return false;
}
At first, this seemed to do the trick, but I'm finding it doesn't really return balanced results with different types of mice, and didn't work too smoothly with touch events, which is the core aspect of this question.
I'm using this in a project that does a lot of fancy events on scroll, with the actual page not actually scrolling at all... But I'm running into the problem of it being incredibly slow with all my standard mice, but incredibly fast on my Apple Magic Mouse. I know that there will naturally be some difference here, as the magic mice do scroll quicker, but the difference is far more off balance than it is between the mice normally.
I'm hoping there is a way to improve upon this to get a more reliable result, with all sorts of different inputs. Any suggestions?
Edit:
To clarify, in order for an answer to work for me, it needs to work on an element which is not scrollable. I have a page which does not scroll at all, but which has other events that fire when the user scrolls. This means that I cannot use properties that are based on the window's scroll position (such as scrollTop()).
You should use window.onscroll most usage and then create a new listener to deal specifically with top and bottom scroll conditions I would suggest using a mousewheel event for desktop browsers and a specifically coded touch responder like below to detect if they are trying to scroll, what direction and if that is possible at the current window.scrollY value.
var isOverScroll = function isOverScroll ( touchStartY, touchEndY ) {
if ( Math.abs( touchStartY - touchEndY ) < 5 ) &&
( ( window.scrollY = window.innerHeight && touchStartY - touchEndY > 0 ) ||
( window.scrollY = 0 && touchStartY - touchEndY < 0 ) ) ) {
return false;
}
return true;
}
There is no way to detect scrollbar events, combine this with your current code and only trigger mouse wheel and touch events if the scrollY position is at either 0 or max.
On a side note if you are trying to get rid of the scroll bar completely that is a very bad idea as it is both a wonderful tool for users as well as something that is a standard part of the ui. If you trying to do a scrollable fullpage app and don't want a scroll bar try using slides. Either way don't continue setting the scroll value thats slow instead just move the whole body using css:
transition: transform3d( 0, YOURSCROLLVALUE , 0);
One possible solution is using a plugin for scrolling like
ISCROLL
in this example here :
Example
they use the feature pull to refresh , which will fire upon reaching the maximum scroll available , here by you can use any custom function (even if your item is not scrollable ).

Allow vertical scrolling with touchstart when event.preventDefault enabled

I am currently having an issue in allowing vertical scrolling when event.preventdefault is enabled.
I am trying to add swipe functionality to my mobile page, I have tried may frameworks like hammer.js, swipe.js etc, and all of them require event.preventDefault enabled to detect left and right swipes.
When event.preventDefault is enabled, the swipes detect perfectly, however you lose the ability to vertical scroll when you are on that element. i.e you cannot move the screen up or down on a mobile device, when your finger starts on the swipe element.
I have tried building my own little script which works well, but again has the issue of vertical scrolling, that is an issue.
var el = document.getElementById('navigation');
el.ontouchstart = function(e){
e.preventDefault();
el.innerHTML = "touch start";
};
el.ontouchend = function(e){
el.innerHTML = "touch end";
};
el.ontouchmove = function(e){
el.innerHTML = "touch moved";
};
el.ontouchcancel = function(e){
el.innerHTML = "touch cancel";
};
Any ideas???
It's a common issue where you want the native browser behaviour to work alongside the interaction behaviour that people come to expect from a touchscreen device.
If you want to use a library you might need to hack it open as you WILL need to prevent the defaults to prevent the page from jumping all over the place when using touch events, but at other times you want to omit it as you want to prevent the page from remaining in a static position, obscuring other content.
Ideally you want add a clause to the handler that instructs them whether or not to prevent the default behaviour the browser executes upon receiving the event.
Swiping for instance, is a behaviour that should occur in a short time frame (if you are taking for instance one whole second in moving your finger from one area to the other instead of, let's say, 120 ms, you're not actually swiping, but dragging. Thinking about time frames may help you here, for instance:
var threshold = 150, interactionStart;
el.ontouchstart = function( e )
{
// store timestamp of interaction start
interactionStart = +new Date;
};
el.touchmove = function( e )
{
// get elapsed time in ms since start
var delta = +new Date - interactionStart;
if ( delta > threshold )
{
e.preventDefault();
}
else {
// super cool magic here
}
};
Whether 150 ms is the threshold you want depends on the action, as you see there is no fixed answer for your question as the actual implementation depends on what your application needs in terms of touch interactions.
You could also consider not blocking the events default when the user is scrolling more along the vertical axis (i.e. compare whether the delta position of the events Y offset (relative to the start Y offset) is larger than the events X offset (relative to the start X offset) to detect whether the users is moving left or right or up or down (for instance if you have a carousel that can swipe horizontally (where the default behaviour is blocked so the page won't move up/down during the horizontal scroll) but want the page to scroll vertically when the user is obviously dragging the page among the vertical axis).
The library jquery.touchSwipe solves that.
The library: https://github.com/mattbryson/TouchSwipe-Jquery-Plugin
An example page where swiping and scrolling are combined: http://labs.rampinteractive.co.uk/touchSwipe/demos/Page_scrolling.html

Can stellar.js readjust element offsets on window resize?

Is there a way to reinitialize stellar.js on browser/window resize so that the element offsets get readjusted?
First of all, it sounds like you might be having an issue with horizontal alignment (assuming it's a vertical site). If so, it's likely that you only need to disable horizontal scrolling:
$.stellar({
horizontalScrolling: false
});
If this doesn't solve your issue, Stellar.js has an undocumented feature that allows you to refresh the plugin.
For example, let's assume you used Stellar.js like this:
$.stellar();
You can refresh it with the following:
$.stellar('refresh');
So, to refresh it on resize, you could do something like this:
$(window).resize(function() {
$.stellar('refresh');
});
Hopefully this should fix everything for you.
After a bit of sleuthing, I've figured this one out. In my case, I have 'slides', which contain my stellar elements, and they are sized to full width/height of the viewport. I needed to resize them for tablet orientation change.
$(window).resize(function(){
winHeight = $(window).height();
$("#scrollWrapper > div").height(winHeight);
// Find out all my elements that are being manipulated with stellar
var particles = $(window).data('plugin_stellar').particles;
// Temporarily stop stellar so we can move our elements around
// data('plugin_stellar') let's me access the instance of stellar
// So I can use any of its methods. See stellar's source code
$(window).data('plugin_stellar').destroy();
$.each(particles, function(i, el){
// destroy() sets the positions to their original PIXEL values.
// Mine were percentages, so I need to restore that.
this.$element.css('top', '');
// Once the loop is finished, re-initialize stellar
if(particles.length - 1 == i){
$(window).data('plugin_stellar').init();
}
});
});
If it doesn't matter that the elements get set to their original pixel values for left/top, then you can just call destroy & init one after the other:
$(window).data('plugin_stellar').destroy();
$(window).data('plugin_stellar').init();
If you instantiate stellar on an element (i.e., $("#element").stellar(); instead of $.stellar();) then replace "window" with your selector.
I also noticed odd offsets on mobiles that may be caused by the way Firefox/Chrome resizes the webview when scrolling down, when the location bar becomes visible again?
The answer to your question is in a section of the documentation: "Configuring everything":
// Refreshes parallax content on window load and resize
responsive: false,
So, this is false by default. To enable this, use .stellar( {responsive:true} )
The real question is... why is this disabled by default? It seemed to fix the problem I was noticing, except for iOS.

Disable vertical bounce effect in an ipad web app

Is there a way to disable the bounce effect in a scrolling div?
So far I have tried these things but none worked. Please help!
How to disable vertical bounce/scroll on iPhone in a mobile web application
Can't disable bounce with UIScrollView and pagingEnabled=YES
ipad safari: disable scrolling, and bounce effect?
Disable UITableView vertical bounces when scrolling
And
http://www.iphonedevsdk.com/forum/iphone-sdk-development/996-turn-off-scrolling-bounces-uiwebview.html
Thanks!
If you're using Cordova 1.7, just open the Cordova.plist file and set the key UIWebViewBounce to NO.
Open your phoneGap project's config.xml file and change UIWebViewBounce from default true to false:
<preference name="UIWebViewBounce" value="false" />
Can't imagine why the default is true...
Based on your comment, the code your are using is the disable scrolling altogether. If you want scrolling, but without the bounce effect, try something like this:
var xStart, yStart = 0;
document.getElementById("scrollableDiv").addEventListener('touchstart',function(e) {
xStart = e.touches[0].screenX;
yStart = e.touches[0].screenY;
});
document.getElementById("scrollableDiv").addEventListener('touchmove',function(e) {
var xMovement = Math.abs(e.touches[0].screenX - xStart);
var yMovement = Math.abs(e.touches[0].screenY - yStart);
if((yMovement * 3) > xMovement) {
e.preventDefault();
}
});
I found this solution here. Let me know if it works for you.
This will help you out place the .scroll class on the element you wish to still have scrolling.
Whats happening is all touch moves are disabled by default. If the element you wish to scroll has the .scroll class on it then it sets the gate to true to allow it to pass.
On touch end you reset the gate to false.
This works on IOS5 and 6 and could work in Chrome and Safari
Look # this post to extend it How to prevent page scrolling when scrolling a DIV element?
The only catch to this is that if you over scroll the scrollable element the elastic effect allows the scroll to be passed up the tree while scroll is set to true. Manually setting the scroll position gets overridden by the dreaded bounce effect.
I bet those Apple friggers have a native implementation of scroll running in a set time out with each step hard wired in. So if you scroll to -20, I think it hard wires each step into a loop not checking where it was. Scrolling to -20 -19 -18 etc in sequence. We must think of a way around this! ( in fact typing it out load I have an idea! )
$(function(){
var scroll = false
var scroll_element;
$('body').on('touchmove',function(e){
if(!scroll){
e.preventDefault()
}
})
$('body').on('touchend',function(e){
scroll = false
})
$('.scroll').on('touchstart',function(e){
scroll_element = this
scroll = true
})
})
I know this may not be the best way but it works.
Here is what I did -
#scrollableDiv {
position:fixed;
top:50px;
width:300px;
height:500px;
word-wrap: break-word;
overflow-y: scroll;
}
document.getElementById("scrollableDiv").innerHTML = longText;
document.getElementById("scrollableDiv").scrollTop = 0;

Categories