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.
Related
I want the user to be able to scroll left and right by scrolling normally which can be achieved by scrolling with the shift key held. is there a way to run the event with the "shiftKey" variable set to true even when it is not held down?
You have to capture scroll event as wheel event, because scroll event will not work without the actual scrollbar and apply scroll data to the scroll left position.
var variation = 0;
document.addEventListener("wheel", function (e) {
variation += parseInt(e.deltaY);
console.log(variation);
//document.getElementById("body");
document.documentElement.scrollLeft = document.body.scrollLeft = (variation);
return false;
}, true);
Try this jsfiddle link which I have created jsfiddle
Background
I'm trying to achieve something, but it's really driving me crazy. So any help would be appreciated!
I've created a scene in Matter.js that will be placed in a container further down on the page. I want the visitor to be able to interact with the scene, dragging and dropping the bodies. But allowing interaction creates the problem where Matter.js prevents the user from scrolling whenever the mouse is over the canvas.
So to work around this, I'm using the following code:
mouseConstraint.mouse.element.removeEventListener("mousewheel", mouseConstraint.mouse.mousewheel);
mouseConstraint.mouse.element.removeEventListener("DOMMouseScroll", mouseConstraint.mouse.mousewheel);
This makes it possible for the user to scroll through the page and still being able to interact with the scene by clicking and dragging the bodies, as it's only the scroll event listeners that are being removed. At least on desktop.
The problem
However, on mobile, the touch event is the event that makes it possible for the user to scroll on the page, so that would require me to also remove the touchmove, touchstart and touchend event listeners from the mouse constraint in Matter.js. Like this:
mouseConstraint.mouse.element.removeEventListener('touchmove', mouseConstraint.mouse.mousemove);
mouseConstraint.mouse.element.removeEventListener('touchstart', mouseConstraint.mouse.mousedown);
mouseConstraint.mouse.element.removeEventListener('touchend', mouseConstraint.mouse.mouseup);
And here's where the problem occurs. If I remove the touch events, the user can scroll by the canvas, but the user can't interact with the scene as that requires the touch events to be activated.
So I'm wondering if there is any way to have the touch events added but only allow them to work on certain bodies in the scene? I've found that I can use mouseConstraint.body as a boolean in order to know if a body has been clicked/touched or not. Could this be used in some way with this as a base?:
Matter.Events.on(mouseConstraint, "mousedown", function(event) {
if (mouseConstraint.body) {
console.log("Body clicked!");
}
});
This is the solution that I came up with. It's not perfect, but it's better than nothing.
let touchStart;
mouseConstraint.mouse.element.addEventListener('touchstart', (event) => {
if (!mouseConstraint.body) {
touchStart = event;
}
});
mouseConstraint.mouse.element.addEventListener('touchend', (event) => {
if (!mouseConstraint.body) {
const startY = touchStart.changedTouches[0].clientY;
const endY = event.changedTouches[0].clientY;
const delta = Math.abs(startY - endY);
if (delta > 80) {
window.scrollTo(0, 600);
}
}
});
You listen for the touch start event and store that in a variable. Then in the touchend event you check if the swipe is large enough to constitute a scroll, then scroll to the content.
i listen for touchstart and touchend events to make my app more responsive for mobile.
the problem is, if you 'flick scroll' (the page is still scrolling even after finger has left screen), and then stop the scroll with a tap - if there is an event on touchend attached to the element you tapped, it will fire.
I need a way to detect if the touchstart or touchend has stopped a scroll, so i can stop any events firing.
I tried setting a variable on scroll (i noticed scroll event on mobile only fires after scroll has finished, i.e page has stopped even on momentum scrolling):
$(window).scroll(function(){
cancelled_scrolling = true;
setTimeout(function(){
cancelled_scrolling = false;
},200);
});
however, when i tap it seems the touchend fires before the .scroll() event, as this doesn't work:
$('body').on('touchend', function(){
if(cancelled_scrolling){
alert('ahahahah');
return false;
}
//code to trigger events depending on event.target after here
});
how can I achieve this?
EDIT:
found an answer to this -
step1 - save the scrollTop on touchend
step2 - on touchstart, check the saved scrollTop against a new scrollTop
if they don't match, the page was scrolled even after the touchend event occurred
On touchStart, keep track of the scrollTop and scrollLeft for each parent node. On touchMove, check if any of these values have changed. If so, cancel the touch. This is slightly better than just checking on touchEnd because maybe they scrolled the page and then unscrolled.
You can also see this logic implemented here: https://github.com/JedWatson/react-tappable/blob/cf755ea0ba4e90dfa6ac970316ff7c35633062bd/src/TappableMixin.js#L120
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 );
});
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);
});