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
Related
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
I have 2 divs (left and right) and i want to scroll the left based on the right.
https://jsfiddle.net/3jdsazhg/2/
This works fine on desktop, but when i change to mobile, it's not smooth anymore...
This can be noticed very easily, by changing
_left.style.top = _content.scrollTop - (_content.scrollTop * ratioLeftRight) + 'px';
to
_left.style.top = _content.scrollTop + 'px';
Where it should act as a fixed positioned div
I would like to know the exact reason why this isn't smooth... I know that it's not the animation. Simple animation on the div is smooth, the issue comes up when it's based on scroll.
How can i make this animation smooth?
It's probably choppy because it's being fired ALOT when being scrolled, in fact i'm pretty sure IOS mobile pauses the javascript execution whilst the user is scrolling.
Instead I'd suggest using an interval, you could tweak the time between each interval to what feels good for your use-case.
Although it may seem intensive that it's firing this logic every X millisecond when using the scroll event you could be firing the event off hundreds of times per second, which is going to be far more intensive and noticeable to a user using a device with limit processing power.
(function () {
var interval = null,
//Currently set at 0.4 seconds, play with the code
//and change this value to see what works best for
//this use-case
time_between_interval = 400;
setInterval(scrollLogic, time_between_interval);
function scrollLogic () {
//The function body of what you're assigning
//to the scroll event.
}
//I have omitted clearing the interval but you would want to do that, perhaps on page change, or something.
//clearInterval(interval);
})();
I finally managed to think out a solution.
From my point of view, i'm guessing the mobile view fires the scroll event less often and because we are scrolling the wrapper, we first scroll the whole page and then scroll back with js the left part and because it's different from the desktop version, this issue becomes visible...
The solution was to change the left side to fixed position, and substract from the top instead of adding to it.
_left.style.top = -(_content.scrollTop * ratioLeftRight) + 'px';
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 ).
I am working on a touch based JS application, I've studied Flex and Royal slider as examples. I noticed that both sliders acting similarly when getting touchmove event:
var started,touched ;
el.bind('touchstart',function(e){
started = Number(new Date()) ;
// Get pageX and pageY etc...
}) ;
el.bind('touchmove',function(e){
touched = Number(new Date()) ;
if (started-touched > 500) {
// Handle touch moves etc...
}
}) ;
My JS app works seamless without these, but why do they need to do this? Why they are waiting 500ms to get move datas?
I believe this is some kind of sensitivity setting. You only want to register a touch-move (drag) event if the user has been moving his or her finger across the device for at least 500ms (in this example).
This could be useful to differentiate between taps and drags. Otherwise, if the user would slightly move his/her finger when clicking, for example, a button, the app would also register a drag. As some controls accept both events, this could lead to erroneous behaviour.
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.