Is there any way to completely disable web page scrolling in an iPhone web app? I've tried numerous things posted on google, but none seem to work.
Here's my current header setup:
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
document.body.addEventListener('touchmove', function(e){ e.preventDefault(); });
doesn't seem to work.
Change to the touchstart event instead of touchmove. Under One Finger Events it says that no events are sent during a pan, so touchmove may be too late.
I added the listener to document, not body.
Example:
document.ontouchstart = function(e){
e.preventDefault();
}
document.addEventListener('touchstart', function (e) {
e.preventDefault();
});
Do not use the ontouchmove property to register the event handler as you are running at risk of overwriting an existing event handler(s). Use addEventListener instead (see the note about IE on the MDN page).
Beware that preventing default for the touchstart event on the window or document will disable scrolling of the descending areas.
To prevent the scrolling of the document but leave all the other events intact prevent default for the first touchmove event following touchstart:
var firstMove;
window.addEventListener('touchstart', function (e) {
firstMove = true;
});
window.addEventListener('touchmove', function (e) {
if (firstMove) {
e.preventDefault();
firstMove = false;
}
});
The reason this works is that mobile Safari is using the first move to determine if body of the document is being scrolled. I have realised this while devising a more sophisticated solution.
In case this would ever stop working, the more sophisticated solution is to inspect the touchTarget element and its parents and make a map of directions that can be scrolled to. Then use the first touchmove event to detect the scroll direction and see if it is going to scroll the document or the target element (or either of the target element parents):
var touchTarget,
touchScreenX,
touchScreenY,
conditionParentUntilTrue,
disableScroll,
scrollMap;
conditionParentUntilTrue = function (element, condition) {
var outcome;
if (element === document.body) {
return false;
}
outcome = condition(element);
if (outcome) {
return true;
} else {
return conditionParentUntilTrue(element.parentNode, condition);
}
};
window.addEventListener('touchstart', function (e) {
touchTarget = e.targetTouches[0].target;
// a boolean map indicating if the element (or either of element parents, excluding the document.body) can be scrolled to the X direction.
scrollMap = {}
scrollMap.left = conditionParentUntilTrue(touchTarget, function (element) {
return element.scrollLeft > 0;
});
scrollMap.top = conditionParentUntilTrue(touchTarget, function (element) {
return element.scrollTop > 0;
});
scrollMap.right = conditionParentUntilTrue(touchTarget, function (element) {
return element.scrollWidth > element.clientWidth &&
element.scrollWidth - element.clientWidth > element.scrollLeft;
});
scrollMap.bottom =conditionParentUntilTrue(touchTarget, function (element) {
return element.scrollHeight > element.clientHeight &&
element.scrollHeight - element.clientHeight > element.scrollTop;
});
touchScreenX = e.targetTouches[0].screenX;
touchScreenY = e.targetTouches[0].screenY;
disableScroll = false;
});
window.addEventListener('touchmove', function (e) {
var moveScreenX,
moveScreenY;
if (disableScroll) {
e.preventDefault();
return;
}
moveScreenX = e.targetTouches[0].screenX;
moveScreenY = e.targetTouches[0].screenY;
if (
moveScreenX > touchScreenX && scrollMap.left ||
moveScreenY < touchScreenY && scrollMap.bottom ||
moveScreenX < touchScreenX && scrollMap.right ||
moveScreenY > touchScreenY && scrollMap.top
) {
// You are scrolling either the element or its parent.
// This will not affect document.body scroll.
} else {
// This will affect document.body scroll.
e.preventDefault();
disableScroll = true;
}
});
The reason this works is that mobile Safari is using the first touch move to determine if the document body is being scrolled or the element (or either of the target element parents) and sticks to this decision.
If you are using jquery 1.7+, this works well:
$("donotscrollme").on("touchmove", false);
This should work. No more gray areas at the top or bottom:)
<script type="text/javascript">
function blockMove() {
event.preventDefault() ;
}
</script>
<body ontouchmove="blockMove()">
But this also disables any scrollable areas. If you want to keep your scrollable areas and still remove the rubber band effect at the top and bottom, see here: https://github.com/joelambert/ScrollFix.
Disable:
document.ontouchstart = function(e){ e.preventDefault(); }
Enable:
document.ontouchstart = function(e){ return true; }
'self.webView.scrollView.bounces = NO;'
Just add this one line in the 'viewDidLoad' of the mainViewController.m file of your application. you can open it in the Xcode and add it .
This should make the page without any rubberband bounces still enabling the scroll in the app view.
The page has to be launched from the Home screen for the meta tag to work.
document.ontouchmove = function(e){
e.preventDefault();
}
is actually the best choice i found out it allows you to still be able to tap on input fields as well as drag things using jQuery UI draggable but it stops the page from scrolling.
I tried above answers and particularly Gajus's but none works. Finally I found the answer below to solve the problem such that only the main body doesn't scroll but other scrolling sections inside my web app all work fine.
Simply set position fixed for your body:
body {
height: 100%;
overflow: hidden;
width: 100%;
position: fixed;
}
Related
I need to find a way to determine if a link has been activated via a mouse click or a keypress.
Save
The idea is that if they are using a mouse to hit the link then they can keep using the mouse to choose what they do next. But if they tabbing around the page and they tab to the Save link, then I'll open then next line for editing (the page is like a spreadsheet with each line becoming editable using ajax).
I thought the event parameter could be queried for which mouse button is pressed, but when no button is pressed the answer is 0 and that's the same as the left mouse button. They I thought I could get the keyCode from the event but that is coming back as undefined so I'm assuming a mouse event doesn't include that info.
function submitData(event, id)
{
alert("key = "+event.keyCode + " mouse button = "+event.button);
}
always returns "key = undefined mouse button = 0"
Can you help?
Could check if event.screenX and event.screenY are zero.
$('a#foo').click(function(evt) {
if (evt.screenX == 0 && evt.screenY == 0) {
window.alert('Keyboard click.');
} else {
window.alert('Mouse click.');
}
});
Demo on CodePen
I couldn't find a guarantee that it works in all browsers and all cases, but it has the benefit of not trying to detect a "click" done via the keyboard. So this solution detects "click" more reliably at the cost of detecting if it's from keyboard or mouse somewhat less reliably. If you prefer the reverse, look as the answer from #Gonzalo.
Note: One place I found using this method is Chromium
You can use event.detail
if(event.detail === 0) {
// keypress
} else {
// mouse event
}
You can create a condition with event.type
function submitData(event, id)
{
if(event.type == 'mousedown')
{
// do something
return;
}
if(event.type == 'keypress')
{
// do something else
return;
}
}
Note: You'll need to attach an event which supports both event types. With JQuery it would look something like $('a.save').bind('mousedown keypress', submitData(event, this));
The inline onClick="" will not help you as it will always pass that click event since that's how it's trapped.
EDIT: Here's a working demo to prove my case with native JavaScript: http://jsfiddle.net/AlienWebguy/HPEjt/
I used a button so it'd be easier to see the node highlighted during a tab focus, but it will work the same with any node.
You can differentiate between a click and a keyboard hit capturing and discarding the keydown event originated at the moment of the key press:
jQuery(function($) {
$("a#foo").keydown(function() {
alert("keyboard");
return false;
}).click(function() {
alert("mouse");
return false;
})
})
http://jsfiddle.net/NuP2g/
I know this is an old question but given how much time I lost looking for a working, no jquery and IE-compatible solution, I think it won't be a bad idea to put it here (where I came first).
I tested this and found it working fine :
let mouseDown = false;
element.addEventListener('mousedown', () => {
mouseDown = true;
});
element.addEventListener('mouseup', () => {
mouseDown = false;
});
element.addEventListener('focus', (event) => {
if (mouseDown) {
// keyboard
} else {
// mouse
}
});
Source link : https://www.darrenlester.com/blog/focus-only-on-tab
Wasn't able to come up with solution relying entirely on the events but you can position an anchor tag over a button and give it a tabindex of -1. This gives you a button that can be focused and engaged with keyboard enter/spacebar, as well as giving you a clickable surface that gives you an option to differentiate the two codepaths.
.button {
position: relative;
}
.anchor {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
<button id="button" class="button">
button
<a class="anchor" href="#example" tabindex="-1"></a>
</button>
I use the following
const isKeyboardClick = nativeEvent.detail === 0 && !nativeEvent.pointerType;
Works in evergreen browsers via detail and IE11 via pointerType. Does not work for the case where e.g. radio button <input> is wrapped by a <label> element.
Nowadays, you can make use of instanceof which even has full browser support.
function onMouseOrKeyboardSubmit(event, id) {
if (event instanceof KeyboardEvent) {
alert("Submitted via keyboard");
} else if (event instanceof MouseEvent) {
alert("Submitted via mouse");
} else {
alert("Unexpected submit event");
}
}
Handle the mouseup event.
If you get a click right afterwards, it was probably done with the mouse.
My question is similiar to this How to prevent page scrolling when scrolling a DIV element? but I'm wondering if there is an approach with css and/or react that does not require jQuery.
I want to disable page scrolling on a mouseWheel event when the cursor is over one particular div.
The div is a graph which zooms on a mouseWheel event, and is rendered by a React component.
I've tried e.preventDefault however chrome tells me
Unable to preventDefault inside passive event listener due to target being treated as passive
Can anyone help? Thanks in advance.
EDIT: Found a simple solution for anyone looking.
changeScroll(){
let style = document.body.style.overflow
document.body.style.overflow = (style === 'hidden') ? 'auto':'hidden'
}
<div
onMouseEnter={this.changeScroll}
onMouseLeave={this.changeScroll} />
<ReactComponent/>
</div>
Thanks! I was looking for a current answer for managing it.
My ReactJS solution was to add and remove the event when onMouseEnter/Leave is detected. Additionally, with the use of passive, taken from this answer link.
Principal component:
<Wrapper
onWheel={this.handleScroll}
onMouseEnter={this.disableScroll}
onMouseLeave={this.enableScroll}
> ...</Wrapper>
handleScroll():
public handleScroll = (event) => {
if (event.deltaY > 0) {
this.decreaseValue()
} else {
this.increaseValue()
}
}
enableScroll():
public enableScroll = () => {
document.removeEventListener('wheel', this.preventDefault, false)
}
disableScroll():
public disableScroll = () => {
document.addEventListener('wheel', this.preventDefault, {
passive: false,
})
}
preventdefault():
public preventDefault(e: any) {
e = e || window.event
if (e.preventDefault) {
e.preventDefault()
}
e.returnValue = false
}
I know there are many similar posts, but still I haven't get to the code I need.
Basically, I want to make a presentation the first time the user scrolls down. For that, I want to prevent the default action of scroll and (if it's scrolling down) make an animation to the next div.
window.scrolledToRed = false
window.scrolledToGreen = false
window.scrollTo = (to, guard ) =>
$('html, body').animate({
scrollTop: $(to).offset().top
}, 1000, =>
window[guard] = true
)
window.addEventListener 'wheel', (e) ->
if (e.wheelDelta < 0)
if (!window.scrolledToRed)
scrollTo('.red', 'scrolledToRed')
else if (!window.scrolledToYellow)
scrollTo('.green', 'scrolledToGreen')
I've created a Fiddle that represents the problem:
https://jsfiddle.net/pn6zqgwu/2/
When the user scrolls down the first time I want to take him to the red div and the next time to the green one.
None of the solutions I've tried really worked, since it was both "jumping" and scrolling where I want.
Any idea of how to solve the problem?
Thanks in advanced
Maybe you need to call e.preventDefault() to prevent browser default scroll behavior
I have made a fiddle for you, you can make more checks and add animations
var redTouched = false;
var greenTouched = false;
function scrollCb() {
event.preventDefault();
console.log(event.wheelDelta)
if (event.wheelDelta < 0) {
if (!redTouched) {
$(window).scrollTop($('.red').position().top);
redTouched = true;
} else if (redTouched && !greenTouched) {
$(window).scrollTop($('.green').position().top);
greenTouched = true;
} else if (redTouched && greenTouched) {
window.removeEventListener('mousewheel', scrollCb)
}
} else {
window.removeEventListener('mousewheel', scrollCb)
}
window.addEventListener('mousewheel', scrollCb);
https://jsfiddle.net/jacobjuul/b0k03wtr/
I'd like to set up Hammer.js so that I can respond to horizontal pan events. My first attempt looks like this:
var mc = new Hammer(document.body);
mc.on("panleft panright", runBind(this, 'updatePosition'));
mc.on("panend", runBind(this, 'finalisePosition'));
This almost gets the behaviour that I'm looking for: if I pan left or right, the updatePosition function is called, and when I stop panning the finalisePosition function is called.
But these functions are also triggered if the gesture drifts left or right while scrolling vertically. For example, suppose I touch near the top of the screen then drag my finger down half the screen: that should register as a scroll event. Now suppose that I continue by dragging diagonally: downwards and to the left. In this scenario, I'd like to ignore the horizontal part of the gesture and treat the gesture as a vertical scroll event only, but Hammer.js is triggering the panright and panleft events as before.
My next attempt looks like this:
var mc = new Hammer(document.body, {
recognizers: [
[Hammer.Swipe],
[
Hammer.Pan,
{event: 'panvertical', direction: Hammer.DIRECTION_VERTICAL}
],
[
Hammer.Pan, // RecognizerClass
{direction: Hammer.DIRECTION_HORIZONTAL}, // options
['swipe'], // recognizeWith
['panvertical'] // requireFailure
],
]
});
mc.on("panleft panright", runBind(this, 'updatePosition'));
mc.on("panend", runBind(this, 'finalisePosition'));
This specifies that the horizontal pan events should only be triggered if the panvertical event has failed. Sure enough, this prevents the problem I described above. If I begin a vertical scrolling gesture then start to move horizontally, the panleft and panright events are not triggered. But this version has a more serious probelem: the default scroll behaviour doesn't happen! As a result it's impossible to scroll the app.
Can anyone suggest a better solution?
I'm using Hammer.js version 2.0.4.
I've had the same problem and
I've managed to make it work with this really ugly code
var first = false, lock = false;
var containerHandler = function(event) {
if(event.type == 'panend' || event.type == 'pancancel') {
// iOS bug fix
lock = false;
first = false;
} else if(event.type == 'swipe' && event.direction & Hammer.DIRECTION_VERTICAL) {
// iOS bug fix
lock = true;
first = true;
}
else if(event.type == 'panmove') {
// iOS bug fix ...
if(first === false && event.direction & Hammer.DIRECTION_VERTICAL) {
lock = true;
}
first = true;
if(lock === true)
return;
//your code etc...
};
var focusMC = new Hammer.Manager(mainContainer[0], {domEvents:true});
var pan = new Hammer.Pan({threshold: 5, direction:Hammer.DIRECTION_HORIZONTAL});
focusMC.add( pan );
focusMC.on('panstart panmove panend pancancel swipe', containerHandler);
small threshold is important..
As far as I remember this issue was only on iOS Safari, but on WP it worked correctly even without this code
Edit: Instead of the solution above, try forcing the touch-action property
to pan-y
I'm keeping both answers as they should all work
Hammer.js will automatically infer a touch-action property based on your recognizers. This might make the application more responsive, but it won't prevent vertical page scrolling just because a user is interacting with the element.
I had the same problem you were having, and found a dead simple solution that works pretty well for a workaround.
var isScrolling = false;
var galleryHammer = new Hammer(element, {
recognizers: [
[Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }]
]
});
// Making the event listener passive means we don't get any delays between
// the user scrolling and the browser having checked if it should prevent
// that event
window.addEventListener('scroll', function() {
isScrolling = true;
}, { passive: true });
window.addEventListener('touchend', function() {
isScrolling = false;
});
galleryHammer.on('pan', function(event) {
if (isScrolling) {
return;
}
// Normal logic...
});
This worked for me (the code comes from a class, and this.hm is simply an Hammer instance):
this.hm.on('panleft', function(e){ // ...and same for panright
if(e.pointerType == 'touch' && (Math.abs(e.deltaY) > Math.abs(e.deltaX))){ return false; }
// do stuff
}
The extra pointerType check is there because I didn't have any issue on desktop computer (mouse events). So the whole is applied ony on touch devices/events.
I'm creating a scrolling effect using JQuery and I'm wondering if it's possible to distinguish between the user scrolling vs. programmatically scrolling.
I have something like this:
$('#element').on('scroll',function(e){
$('#element').stop(true); // stop previous scrolling animation
$('#element').animate({ // start new scrolling animation (maybe different speed, different direction, etc)
scrollTop:...
});
});
However, this event is triggered during every step of the animation. How can I tell if this event was triggered by the user or by the animation?
Use a variable to determine when you are scrolling programmatically
Example:
var programScrolling = false;
$('#element').on('scroll',function(e){
if (programScrolling) {
return;
}
$('#element').stop(true); // stop scrolling animation
programScrolling = true;
$('#element').animate({
scrollTop:...
});
programScrolling = false;
});
Not sure if that is exactly what you want, but the concept should work.
I would make functions for different kinds of scrollings to detect them and call a scroll handler for all of them, like so:
JS Fiddle
$(window).bind('mousewheel DOMMouseScroll', function(event){
var direction;
if (event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0) {
direction = 'up';
}
else {
direction = 'down';
}
scrollHandler(direction, 'mouseWheel');
event.preventDefault();
});
var scrollHandler = function(direction, origin) {
var height = $(document).scrollTop();
var movement = (direction == 'up') ? -100 : 100;
console.log(origin);
$('body').stop(true);
$('body').animate({
scrollTop: height + movement
}, 250);
};
Then you can do different stuff according to the origin of the event!
You could also check if the user scrolls to the same direction that the screen is scrolling and do something different, or whatever you want with the info passed by the mousewheel event.
Original mousewheel event function copied from THIS answer
I would suggest possibly using the .originalEvent method. The downside is, this is very browser dependent. See here. Hopefully the following helps:
$('#element').scroll(function(e){
var humanScroll = e.originalEvent === undefined;
if(humanScroll) {
$(this).stop(true);
}
})