I have a page that is set to height: 100vh
Now I want a function to be triggered when the user tries to scroll. onScroll doesn't work since it is impossible to scroll. How can I still get the onscroll event?
If it is any helpful, here is the pen
https://codepen.io/Sinanski/pen/wEbeMo?editors=0110
The wheel event might help you - although this won't trigger on scroll events from the keyboard. So you might put together something like this:
window.addEventListener("wheel", onScroll);
window.addEventListener("keyup", onKeyUp);
function onScroll(event) {
console.log("scroll")
}
function onKeyUp(event){
if(event.key == "ArrowUp" || event.key == "ArrowDown"){
onScroll(event);
}
}
Please note browser support for this event from the MDN page and test appropriately according to your needs.
Updated pen https://codepen.io/anon/pen/QVRxed?editors=0010
By definition, no scroll events will get fired if nothing can be scrolled.
Related
I need to disable the default iPAD scrolling (via capturing touchmove on the body) but still allow a list on my page to scroll.
I tried:
$('body').on('touchmove', function(e) { e.preventDefault(); });
$('itemList').on('touchmove', function(e) { alert('hi'); e.stopPropagation(); });
But it seems that itemList's touchmove is not being called at all. on the iPAD nothing gets scrolled.
see http://jsfiddle.net/e8dcJ
Any ideas how to solve this ?
Thanks!
maybe don't apply the event to the body, which covers everything. Instead, apply the event to a the various elements you want to prevent scrolling. Alternately, wrap everything in a DIV except the list and then set the position to fixed and add the event.
is there a function which we can execute some code after default event happened?
for example, i want to get the scrollTop value after default mouse wheel happened.
can jquery or javascript do this for me?
There is a broad range of event handlers in jQuery. You can use .scroll() to respond to scroll event. Full list of events [handlers].
$(window).scroll(function() {
alert('scrolled!');
});
scroll():
A scroll event is sent whenever the element's scroll position changes,
regardless of the cause. A mouse click or drag on the scroll bar,
dragging inside the element, pressing the arrow keys, or using the
mouse's scroll wheel could cause this event.
Mousewheel handling can be a bit of a pain. You should probably try using a jQuery Mousewheel Plugin.
This one by Brandon Aaron has worked well for me in the past.
$(document).on('mousewheel DOMMouseScroll', function() {
var top = $(this).scrollTop();
console.log(top);
});
FIDDLE
I'm using Brandon Aaron's jquery.mousewheel.js to attach events like this:
$('#mydiv').mousewheel(function(event, delta, deltaX, deltaY) { alert(deltaY); });
you can download it from https://github.com/brandonaaron/jquery-mousewheel/downloads
I want to add either a scroll event listener or a touchstart event listener. Initially I used the touch event to deactivate the scroll event listener as shown in the code below:
window.addEventListener('scroll', scrollStart, false);
window.addEventListener('touchstart', touchStart, false);
function scrollMenu() {
// do something
}
function touchStart(e) {
window.removeEventListener('scroll', scrollStart);
// do something
}
But I realized that on some occasions, the scroll event is triggered as soon as the page loads. Therefore, I cannot use the aforementioned method. Is there another way to check if the browser supports a touch event listener without adding the event?
Does Modernizr solve your problem? See example here for the various ways to detect touch events and each one's browser compatibility:
http://modernizr.github.com/Modernizr/touch.html
You should be able to check whether the ontouchstart attribute exists in the window:
if ("ontouchstart" in window) {
window.addEventListener('touchstart', touchStart, false);
} else {
window.addEventListener('scroll', scrollStart, false);
}
... I can't confirm the x-browser-ness of this though.
I've got this issue (I'm using jQuery but I'm not restricted to it):
I'm using a combo of Anchor navigation (#id) and Ajax requests. To get the pages to move into place (using anchor navigation) or to fetch information (using Ajax), I use the onhashchange event.
EDIT: I had a little typo. I forgot to check if the mouseDown flag was true and the hashchange event was triggered so I added that if statement.
with jQuery it looks like this: (of course this code is wrapped in a function and initialized on DOM load but it doesn't matter for the question)
$(window).bind('hashchange', function(e) { }
To ensure only browsers supporting the onhashchange reads the code I encapsulate it like this:
if ('onhashchange' in window) {
$(window).bind('hashchange', function(e) { }
}
My web app is made in such way that I only want the onhashchange event to trigger when I hit the back/forward buttons in the browser. To do that I do like this:
if ('onhashchange' in window) {
$(window).bind('mousedown hashchange', function(e) { }
}
Now if I click within the viewport I will trigger the mousedown event. If the mousedown event is triggered I know that I didn't click the browser back/forward buttons and I can stop the onhashchange event using a flag like this:
var mouseDown = false;
if ('onhashchange' in window) {
$(window).bind('mousedown hashchange', function(e) {
if (e.type === 'mousedown') {
mouseDown = true;
}
if (mouseDown && e.type === 'hashchange') {
// if the mousedown event was triggered and when the haschange event triggers,
// we need to stop the hashchange event and restore the mouseDown flag
mouseDown = false;
e.stopPropagation();
}
if (!mouseDown && e.type === 'hashchange') {
// Do the onhashchange stuff here
}
}
}
This causes a problem for IE since it seams you cannot bind mouse events to the window object (?). IE will never "see" the mousedown event.
To solve this IE issue I can take the "clientY" property. This property is passed in all event calls in IE and tells you the coordinates of the mouse. If e.clientY is less then 0, the mouse is outside the viewport and I will know that I triggered the onhashchange by clicking the browser back/forward buttons. It now looks like this:
var mouseDown = false;
if ('onhashchange' in window) {
$(window).bind('mousedown hashchange', function(e) {
// IE: Use e.clientY to check if the mouse position was within the viewport (i.e. not a nagative value for Y)
// !IE: Use e.type
if (e.type === 'mousedown' || e.clientY > 0 ) {
mouseDown = true;
}
if (mouseDown && e.type === 'hashchange') {
// if the mousedown event was triggered and when the haschange event triggers,
// we need to stop the hashchange event and restore the mouseDown flag
mouseDown = false;
e.stopPropagation();
}
if (!mouseDown && e.type === 'hashchange') {
// Do the onhashchange stuff here
}
}
}
This solution was working like a charm until I had to add support for navigating with the arrows on the keyboard. Now it doesn't matter where on the screen the mouse is. As long as the IE window is "active", the keydown event listening for keyboard input triggers when hitting the keyboard. This means that the clientY check does not work anymore as intended.
The Problem:
As far as I know, the onhashchange must be bound to the window object. All events must be processed within the same callback function if I want to be able to control one event by listening for another.
How can I get this to work?
So, simply put-
"how do I distinguish between a back/forward button press vs. navigation coming from interacting with the DOM".
You may want to have a flag such that when you are changing the hash part of the URL from code, you set this flag, ignore the hashchange event, then unset the flag. In which case the event will be ignored ( a kind of reverse solution as to what you're trying to do). You'd obviously want to wrap this in a function.
In general however, applications that use the hashchange event for navigation will often use the hashchange event as a means for changing the state of the application. Therefore, there is only one entry point and you do not need to distinguish between whether the event is generated by browser navigation vs. dom interaction. I'd probably recommend changing your approach.
I'd also point you to the fact that history can be supported across all browsers (even IE6 and IE7 using an iFrame hack). Take a look at the jQuery history plugin
The reference library to achieve this:
http://benalman.com/projects/jquery-bbq-plugin/
I used it and it works great
Think about putting "!" after "#" in the url, so that google can discover the ajax pages
http://code.google.com/web/ajaxcrawling/docs/getting-started.html
In doing a single page Javascript app with interactive DOM elements I've found that the "mouseover-mousemove-mousedown-mouseup-click" sequence happens all in a bunch after the "touchstart-touchmove-touchend" sequence of events.
I've also found that it is possible to prevent the "mouse*-click" events from happening by doing an "event.preventDefault()" during the touchstart event, but only then, and not during the touchmove and touchend. This is a strange design, because because it is not possible to know during the touchstart yet whether the user intents to drag or swipe or just tap/click on the item.
I ended up setting up a "ignore_next_click" flag somewhere tied to a timestamp, but this is obviously not very clean.
Does anybody know of a better way of doing this, or are we missing something?
Note that while a "click" can be recognized as a "touchstart-touchend" sequence (ie no "touchmove"), there are certain things, such as keyboard input focus, that can only happen during a proper click event.
Just prevent the touchend event. It will let the browser scroll the page when you touch the element but won't let it emit artificial mouse events.
element.addEventListener('touchend', event => {
event.preventDefault();
});
I've run into similar problems making cross-platform HTML5/JS apps. The only real answer for me was to preventDefault on the touch events, and actually manage the touch states and fire click, drags, etc. events on my own according to my logic. This sounds much much more daunting than it really is, but the mimicked click/mouse events work perfectly on most mobile browsers.
Click and the extra mouse sequence are all there for your convenience (and compatibility). My rule of thumb- if it's for your convenience but it's inconvenient, best kill it.
As far as the input boxes, they only need the touchend events. I've killed click/mouse events and was still able to let mobile browsers respond correctly to touches on inputs. If it's still giving you issues, you can modify the event handler to only supress events on non-inputs:
function touchHandler(event) {
var shouldIgnore = event.target != null
&& ( event.target.tagName.toLowerCase() == "input" || event.target.tagName.toLowerCase() == "textarea" );
if(!shouldIgnore) e.preventDefault();
}
I've made a solution myself, since I have not found a sufficient solution elsewhere:
var isTouch = ('ontouchstart' in window);
function kill(type){
window.document.body.addEventListener(type, function(e){
e.preventDefault();
e.stopPropagation();
return false;
}, true);
}
if( isTouch ){
kill('mousedown');
kill('mouseup');
kill('click');
kill('mousemove');
}
The check of isTouch lets things work as normal on mouse-input devices but kills the emulated events on Safari/iOS. The trick is to use useCapture = true in the call to addEventListener so we scoop up all the mouse events in the page without hacking the code all over the web app. See the docs for the function here: https://developer.mozilla.org/en-US/docs/DOM/EventTarget.addEventListener?redirectlocale=en-US&redirectslug=DOM%2Felement.addEventListener
Edit:
Now that libraries for handling this issue are better, you can just use something like Fastclick as an alternative (https://github.com/ftlabs/fastclick).
If you have to support devices which support both mouse and touch, another solution is to use a capture event listener which stops all mouse events which occur either
within a delay after the touch event
at the same position as the touch event
on the same target element as the touch event
The information (time, position or target element) of the touch event can be recorded in another capture event listener.
Wrapping your mouse-only code in a Window.matchesMedia function is the cleanest way I found.
if (window.matchMedia('(hover: hover), (any-hover: hover), (-moz-touch-enabled: 0)').matches) {
el.addEventListener('mouseover', ev => {
// mouse handler, no simulated hover
}
}
This works for preventing simulated hovers but will likely prevent simulated clicks, too.
Note: -moz-touch-enabled part required on Firefox as of version 58.
This solution allows you to listen for PointerEvents if they exist, followed by TouchEvents if they exist, followed by MouseEvents if neither of the other two exist. Mobile Safari will still raise both touchstart and mousedown, but you'll only be listening for touchstart.
if (window.PointerEvent) { /* decent browsers */
etouch.addEventListener('pointerdown', (e) => {
console.log('pointerdown');
});
}
else if (window.TouchEvent) { /* mobile Safari */
etouch.addEventListener('touchstart', (e) => {
console.log('touchstart');
});
}
else { /* desktop Safari */
etouch.addEventListener('mousedown', (e) => {
console.log('mousedown');
});
}
Using 'pointerwhatever' instead of 'mousewhatever' seems to work fine on current browsers (2019).
i.e. they invented a way of having the same code for all the entry devices.
Creating Fast Buttons for Mobile Web Applications has their solution to the problem.
Also beware that when using IE10 preventDefault() doesn't stop the ghost/synthetic/emulated mouse events after a MSPointerDown event, so a true cross-browser solution is harder.
You could try to quit the function on "click", "mousedown" or "mouseup" events when the device supports touch events.
use.addEventListener("click",function(e){
// EXIT FUNCTION IF DEVICE SUPPORTS TOUCH EVENTS
if ("ontouchstart" in document.documentElement) return false;
// YOURMOUSEONLY CODE HERE
});
Add an event listener to touchstart that adds attribute data-touched to the element. Add another event listener to click that checks for data-touched. If it's there, prevent default and remove it. Here's some JS from my implementation.
var menuLinks = document.querySelectorAll('#my-nav>li>a');
for (var i = 0; i < menuLinks.length; i++) {
var menuLink = menuLinks[i];
menuLink.addEventListener('touchstart', function () {
menuLink.setAttribute('data-touched', '');
});
menuLink.addEventListener('click', function (event) {
if (menuLink.hasAttribute('data-touched')) {
menuLink.removeAttribute('data-touched');
event.preventDefault();
}
});
pointer... events have a pointerType property that mouse... events lack. You can use this property to detect and ignore events that were generated by touch rather than by a mouse.
Before:
window.addEventListner('mousemove', (e) => {
/* No way to tell if this event came from a mouse or a finger */
console.log(':(');
});
After:
window.addEventListner('pointermove', (e) => {
if (e.pointerType !== 'mouse') return;
/* This event definitely came from a mouse */
console.log(':)');
});
You can take advantage of this property just by replacing your mouse... event listeners with pointer... listeners. pointer... events are well-supported in modern browsers (going back at least three years).