I have a surface web app that uses touch panning (container divs have "overflow: auto" style) and I'm using the built-in paging scroll styles:
-ms-scroll-snap-points-x: snapInterval(0px, 1366px);
-ms-scroll-snap-type: mandatory;
My app has a 300% width child container resulting in 3 pages that snap on page boundaries.
This works great for high-performance paging scrolling, except when the user is on the first page and they swipe to the right, which activates the browser's built-in back gesture, exiting my web app and going into the user's IE10 history.
I'm able to disable the back gesture using:
-ms-touch-action: none;
But that also disables touch scrolling so the page is no longer draggable. If I use:
-ms-touch-action: pan-x;
Then the scrolling works again but the browser back gesture reappears which is a really annoying user experience. Is there a way to allow panning but not the history gesture?
The solution is simple, you just need to add a CSS style that prevents scroll behavior from bubbling up from child elements that have reached their scroll limit, to parent elements (where the scroll eventually turns into a top-level history navigation).
The docs (http://msdn.microsoft.com/en-us/library/windows/apps/hh466007.aspx) state that the default is:
-ms-scroll-chaining: none;
However the default appears to really be:
-ms-scroll-chaining: chained;
I set that style to none by default and chained on the elements in my carousel that really should be chained, which disabled history navigation gestures in my app:
* {
-ms-scroll-chaining: none;
}
.carousel * {
-ms-scroll-chaining: chained;
}
You need to set -ms-touch-action: none; on all elements.
This will instead fire events to your JavaScript handlers, (if there are any), but will prevent ALL new actions including: panning, zooming, and sliding. This is best if you'd like to custom tailor how your app utilizes touch.
Not an ideal or elegant solution, but can you use the MSPointerDown, MSPointerMove and MSPointerUp event listeners to detect a swipe and preventDefault?
// Touch events
target.addEventListener('MSPointerDown', callback);
target.addEventListener('MSPointerMove', callback);
target.addEventListener('MSPointerUp', callback);
Edit: The following doesn't prevent swipe causing navigation on Windows Phone 8.1, although it does prevent swipe navigation for me on a windows 8.1 tablet. Leaving answer here, since it might be partially useful to someone.
If your page is larger than the viewport (touch to pan), then the following CSS works (edit: only on 8.1 tablet):
html {
-ms-scroll-chaining: none;
}
If your page is smaller than then viewport (i.e. the page is not scrollable/pannable e.g. zoomed out) then the above doesn't work.
However code similar to the following works (edit: only on 8.1 tablet) for me:
CSS:
html.disable-ie-back-swipe {
overflow: scroll;
-ms-scroll-chaining: none;
}
JavaScript:
if (navigator.msMaxTouchPoints) {
if (/Windows Phone/.test(navigator.userAgent)) {
document.documentElement.classList.add('disable-ie-back-swipe');
} else {
try {
var metroTestElement = document.createElement('div');
metroTestElement.style.cssText = 'position:absolute;z-index:-1;top:0;right:0;bottom:-10px;width:1px';
document.body.appendChild(metroTestElement);
if (Math.round(window.outerWidth - metroTestElement.offsetLeft) === 1) {
document.documentElement.classList.add('disable-ie-back-swipe');
}
document.body.removeChild(metroTestElement);
} catch (e) { // window.outerWidth throws error if in IE showModalDialog
}
}
}
Notes on the JavaScript:
Testing navigator.msMaxTouchPoints checks that this is IE10/IE11 and that the device uses touch.
Edit: detects Windows Phone and sets disable-ie-back-swipe however the CSS doesn't actually disable the feature on Windows Phone 8.1 ARRRGH.
The metroTestElement tests for modern (modern doesn't have scrollbars so right is 1 pixel, whereas desktop has scrollbars so right is 18 pixels or so depending on scrollbar width).
The code only disables the back swipe if IE11 and modern is used.
It seems that either html or body can be used for the CSS rules, and I am unsure which is actually better (IMHO I usually think of the body as the page and not scrollable, and html as the viewport/window, but actually depends upon IE implementation details).
Edit 2: This IE feature is called "flip ahead". Corporates may be able to disable it using group policy - see http://www.thewindowsclub.com/enable-disable-flip-feature-internet-explorer-10
On a project I was working on needed exactly this but needed to scroll in certain areas (not just general scrolling but overflow). Seems the following does work, tested on IE11 (Metro) Surface 2 and IE11 WinPhone on Lumia 930. Also with touch mouse which does the horizontal scrolling too.
Please see this demo here: http://jsbin.com/fediha/1/edit?html,css,output
The trick to disable history back/forward) seems to be to disable "pan-x" on any element except the ones you want to scroll horizontally. Excerpt from CSS:
* {
/* disallow pan-x! */
touch-action: pan-y pinch-zoom double-tap-zoom;
/* maybe add cross-slide-x cross-slide-y? */
}
.scroller-x,
.scroller-x * {
/* horizontal scrolling only */
touch-action: pan-x;
}
.scroller-y,
.scroller-y * {
/* vertical scrolling only */
touch-action: pan-y;
}
On rare instances history back is still triggered but that is really rare (could only do this by wildly flicking on the tablet and even then not it does only happen sometimes).
touch-action is IE11 only, on IE10 you'd need -ms-touch-action but IE10 is not used that much anymore and I have no test device with it.
Related
I'm seeing some odd behavior that recently surfaced for me after the recent Chrome v67 update (tested in both Mac El Cap and on Windows 10 so far).
Using JQuery Mobile, I have some collapsibles nested inside a panel widget acting as a menu. This was working flawlessly for some time, but when I got temporarily sidetracked and then returned to the project, I noticed that now, in Chrome (v67 of which was released in late May, while I was away from the project), I was getting random 1px vertical spaces (white horizontal lines) between some menu items when opening the menu and then expanding a collapsible. It's impossible to predict which items will be effected, and even inspecting them is difficult, as the extra space disappears/resolves on rollover or when opening the inspector.
So far, I'm not seeing this behavior in Safari or FF (Mac), nor in Chrome v66 (Windows). While I'm unable to do more extensive cross browser testing at the moment, from what I'm seeing I'm operating under the assumption that something in the latest Chrome update is causing this issue.
I've sometimes seen similar unpredictable spacing issues when the browser's view is zoomed in/out slightly, but I've confirmed I'm viewing this at actual size.
It doesn't appear to be a CSS issue, as I'm not seeing a change in the computed values of an element after the space issue resolves on rollover. It appears for all the world to be some kind of Chrome rendering problem.
I realize this is a rather specific set of circumstances, but has anyone else experienced anything similar in Chrome v67, and if so have you found a way to resolve it?
Here is a fiddle that demonstrates this odd behavior (it's easiest to see when opening "List 2 of 6"):
https://jsfiddle.net/halfacre/p349ghvf/5/
EDIT: The issue seems to be originating from the math in my JS. This script is meant to smooth-scroll the chosen submenu to the top of the viewport (as these are long lists, and without the visual cue it's easy to get lost), and was working beautifully until the latest Chrome update. I'm including said JS below. As I'm not a programmer or particularly stellar at math, this script is crossing my eyes a bit... could this be due to remainders or rounding issues?
$(document).ready(function() {
$(document).on("collapsibleexpand", ".ui-collapsible", function(e) {
var self = $(this),
menu = $("#mainmenu"),
pageY = $(document).scrollTop(),
content = $(this).children(".ui-collapsible-content");
content.hide();
content.slideDown({
duration: 300,
step: function(now, fx) {
if (fx.prop == "height") {
var pct = ((100 * now) / fx.end),
itemTop = $(self).offset().top,
menuScrollTop = $(menu).scrollTop(),
amt = (itemTop - pageY) / 100 * pct;
menu.scrollTop(menuScrollTop + amt);
}
}
}
);
e.stopPropagation(); // don't bubble up
});
$(document).on("collapsiblecollapse", ".ui-collapsible", function(e) {
var content = $(this).children('.ui-collapsible-content');
content.slideUp(300);
e.stopPropagation(); // don't bubble up
});
});
Thanks for taking a look.
EDIT 2: Screenshot of what I'm seeing:
If I am not wrong here, I believe the question is how to zero-pad the JQM collapsible, i.e. the listviewand the collapsible heading inside the menu panel.
First, keep in mind that JQM is dynamically enhancing each div which has an attribute data-role by adding some DOM content and the corresponding CSS classes.
You can look at the JQM source code by searching for mobile.collapsible and You will find what's happen at widget instancing in the _enhance() function and what's happen when You click the collapsible heading in the _handleExpandCollapse() function.
Now, I strongly believe there is somewhere a conflict among the JQM classes and Your CSS styles:
li, ul {padding:0!important; }
h3{ margin:0!important;}
But, sadly, I am not able to explain to You why this happens, as You said, just only with the latest Chrome version. I also noticed these thin random lines, which belongs to the panel-content background.
Here is my proposal to reset the space between the panel inner and the content:
.ui-panel-inner { padding: 0 !important; }
.ui-panel-inner .ui-collapsible-content { padding: 0 !important; }
.ui-panel-inner .ui-listview { margin: 0 !important; }
.ui-panel-inner .ui-listview > .ui-li-static { padding: 0 !important; }
.ui-panel-inner .ui-listview > li h3 { margin: 0 !important; }
Instead of defining new rules, I am overriding the JQM styles. Now, I can't see any thin random line anymore.
Here is the Fiddle: https://jsfiddle.net/98b4r3w5/ any feedback welcome.
Moreover: a note aside, if You need a centered page content and a smaller footer, I would do it the same way:
.ui-content { text-align:center; }
.ui-footer .ui-title { padding: 0 !important; }
On some of our pages, we've got some legacy JQuery that detects when a page is scrolling and then sets the height of the main element, to position the footer correctly on both mobile and desktop:
function scrollPage(event){
base.keepMainMarginOverFooter();
// other functions
}
$(window).on('scroll',scrollPage);
This is the offending function:
keepMainMarginOverFooter:function() {
// Adjusts container height so footer is clickable but can also be revealed on mobile
var h = 0;
$('.fp-section.visible').children().each(function() {
h+= $(this).outerHeight()
});
$('#main').css({ height: h });
}
And it seems that there's a bug in Chrome (Version 61.0.3163.100) that seems to trigger if you're actively scrolling during any event which triggers the above code (we also have some JQuery functions that , which automatically takes you to the end of the #main section, ie the bottom of the page. Or at least I guess it's a bug - this doesn't seem to happen in Safari (Version 11.0 12604.1.38.1.7), and it doesn't happen if you wait a couple of seconds after page load before scrolling.
It's tempting to remove this function for something simpler, but it's in the middle of enough dependencies that that wouldn't be trivial.
Is this a known issue with a decent workaround?
I am thinking of making my UI to dynamically change to a more touch-friendly layout when the user switches "Tablet Mode" on, and switch back to our "desktop" layout if they turn Tablet Mode off.
That requires (1) detecting tablet mode in JavaScript (2) detecting the on/off change of tablet mode.
I prefer pure JavaScript and DOM (not jQuery, Modernizr etc).
Reason: We have a high density (desktop like) user interface, which we can't easily just change. I wish to add spacing to be more touch friendly when in "Tablet mode". This is the same as the Windows 10 taskbar adds extra padding between icons when in Tablet mode (presumably other Windows 10 apps will act this way?!)
Edit: I did some viewport research, as it looks like the zero width scrollbar is the trick for detecting Tablet Mode (or Metro). http://pastebin.com/ExPX7JgL
Tablet mode: scrollbar width is 0 in Edge.
Not tablet mode: scrollbar width is not zero in Edge.
Working pure JavaScript code here.
This works for Edge (IE12) on Windows 10, but not Internet Explorer 11.
A reliable way to detect that the tablet mode has changed is here.
Note that scrollbar width can be zero for other reasons (iOS, Android, Windows Phone, Safari OSX, or if -ms-overflow-style: none, amongst other reasons). Modernizr 3 has a hiddenscrollbar feature detection which detects if zero width scrollbars used.
Note that Edge scrollbars act and display differently if you are using touch, rather than using a mouse/touchpad. (You can even get both thin and old-school styles of scrollbar showing at once if you scroll then change into tablet mode quickly)! Beware that I suspected the Edge debugger of interfering with scrollbar detection (but it probably due to me changing between touch and touchpad).
I would discourage you from doing platform specific things like that.
Even in Windows 10 apps, the general design guideline is to change the UI based on view size, and change interactions based on input device, but not the actual view.
You should use pointer events instead.
It's a W3C standard that receives events from stylus/mouse/touch. It has a pointer-type property you could use to detect which one is interacting with your site.
(Supported in Firefox / Opera / IE, and soon Chrome)
Using a calc() that depended on the scrollbar thickness seemed to work, but was unreliable at detecting the scrollbar resizing. Just adding it here in case the idea helps.
.metrics-edge-outer {
position: absolute;
height: 50px;
width: 50px;
}
.metrics-edge-1,
.metrics-edge-2 {
width: 100px; /* cause bottom scrollbar */
}
.metrics-edge-1 {
height: calc(50px + 50px - 100% - 2px);
/* bistable - if scrollbar has thickness then once scrollbar shows then it stays showing - when scrollbar thickness goes to zero due to tablet mode then height shrinks to 48px (and scroll event happens) */
}
.metrics-edge-2 {
height: calc(200% - 50px + 2px);
/* bistable - if scrollbar has zero thickness then once area is scrollable it stays is scrollable - if scrollbar thickness goes to 17px due to non-tablet mode then height goes to less than 48px (and scroll event happens) */
}
And the code to go with it (not even syntax checked because edited from framework):
var tabletModeNode;
function detectTabletMode() { // Also see http://www.backalleycoder.com/resize-demo.html
var innerDiv = core.div({
className: (getScrollbarThickness() > 0) ? 'metrics-edge-1' : 'metrics-edge-2'
});
tabletModeNode = core.div({
className: 'metrics-edge-outer',
tabIndex: -1
}, [innerDiv]);
this.node.appendChild(tabletModeNode);
redetectTabletMode();
tabletModeNode.addEventListener('scroll', function() {
if (!tabletModeNode.scrollTop) {
alert('on tablet mode');
redetectTabletMode();
}
});
}
var tabletTimer;
function redetectTabletMode: function() {
tabletModeNode.style.overflow = 'scroll';
tabletModeNode.scrollTop = 1;
clearTimeout(tabletTimer);
tabletTimer = setTimeout(function() { // Wait until after CSS rules have run (and inner div is bigger than outer div)
tabletModeNode.style.overflow = 'auto';
}, 1000);
}
I have an issue when toggling a tablet from portrait to landscape and vice versa, the tablet doesn't reexecute jQuery codes.
Here is the scenario:
<div class="links">
<ul class="hide">
<li></li>
</ul>
</div>
Initially, the ul element is hidden through CSS. Then I give this code to be opened on click event.
if ($(window).width() <= 800) { // portrait orientation
$(".links").click(function() {
$(this).find('ul').slideToggle();
});
}
It works good so far. If I don't click anything, it still works fine even when I keep toggling to landscape back and forth. The issue comes when on portrait orientation, I click the element to open it and click it again to close.
It seems after the second click, the ul element got additional style="display:none" (initially this ain't there because it's handled by CSS). This is why the ul element got hidden when I switch back to landscape. Then I tried to give this code below.
if ($(window).width() > 800) { // landscape orientation
if ($('.links ul').is(":hidden")) {
$('.links ul').css("display","block");
}
}
However, it seems this code never got executed whenever I switch to landscape orientation leaving the ul element hidden. Is there a work around so that the browser will execute code every time I toggle the orientation?
You can use .resize() jQuery handler to check the height of window when resize the screen,
$(document).ready(function(){
$(window).resize(function()
{
if ($(window).width() > 800)
{ // landscape orientation
if ($('.links ul').is(":hidden"))
{
$('.links ul').css("display","block");
}
}
});
});
Depending on your browser support requirements, you could use a combination of orientation change and matchMedia.
According to MDN: window.matchMedia mobile support is reasonable:
Feature Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile
Basic support 3.0 6.0 (6.0) Not supported 12.1 5
There are also a couple of matchmedia polyfills available depending on your needs:
matchMedia.js
media-match
if you combine this with orientationchange you could do something like:
window.addEventListener("orientationchange", function(evt) {
if (window.matchMedia("(min-width: 400px)").matches) {
/* the view port is at least 400 pixels wide */
} else {
/* the view port is less than 400 pixels wide */
}
}, false);
This should give you pretty decent control over your orientation changes.
If you are willing to use an additional library Enquire.js has some great support for both matching and unmatching events. It's small and no jQuery required.
You could set up an event listener such as:
window.addEventListener("orientationchange", function() {
alert("orientationchange");
}, false);
I have a page with a section to sketch a drawing in. But the touchmove events, at least the vertical ones, are also scrolling the page (which degrades the sketching experience) when using it on a mobile browser. Is there a way to either a) disable & re-enable the scrolling of the page (so I can turn it off when each line is started, but turn it back on after each is done), or b) disable the default handling of touchmove events (and presumably the scrolling) that go to the canvas the sketch is drawn in (I can't just disable them completely, as the sketching uses them)?
I've used jquery-mobile vmouse handlers for the sketch, if that makes a difference.
Update: On an iPhone, if I select the canvas to be sketched in, or just hold my finger for a bit before drawing, the page doesn't scroll, and not because of anything I coded in the page.
Set the touch-action CSS property to none, which works even with passive event listeners:
touch-action: none;
Applying this property to an element will not trigger the default (scroll) behavior when the event is originating from that element.
Note: As pointed out in the comments by #nevf, this solution may no longer work (at least in Chrome) due to performance changes. The recommendation is to use touch-action which is also suggested by #JohnWeisz's answer.
Similar to the answer given by #Llepwryd, I used a combination of ontouchstart and ontouchmove to prevent scrolling when it is on a certain element.
Taken as-is from a project of mine:
window.blockMenuHeaderScroll = false;
$(window).on('touchstart', function(e)
{
if ($(e.target).closest('#mobileMenuHeader').length == 1)
{
blockMenuHeaderScroll = true;
}
});
$(window).on('touchend', function()
{
blockMenuHeaderScroll = false;
});
$(window).on('touchmove', function(e)
{
if (blockMenuHeaderScroll)
{
e.preventDefault();
}
});
Essentially, what I am doing is listening on the touch start to see whether it begins on an element that is a child of another using jQuery .closest and allowing that to turn on/off the touch movement doing scrolling. The e.target refers to the element that the touch start begins with.
You want to prevent the default on the touch move event however you also need to clear your flag for this at the end of the touch event otherwise no touch scroll events will work.
This can be accomplished without jQuery however for my usage, I already had jQuery and didn't need to code something up to find whether the element has a particular parent.
Tested in Chrome on Android and an iPod Touch as of 2013-06-18
There is a little "hack" on CSS that also allows you to disable scrolling:
.lock-screen {
height: 100%;
overflow: hidden;
width: 100%;
position: fixed;
}
Adding that class to the body will prevent scrolling.
document.addEventListener('touchstart', function(e) {e.preventDefault()}, false);
document.addEventListener('touchmove', function(e) {e.preventDefault()}, false);
This should prevent scrolling, but it will also break other touch events unless you define a custom way to handle them.
The ultimate solution would be setting overflow: hidden; on document.documentElement like so:
/* element is an HTML element You want catch the touch */
element.addEventListener('touchstart', function(e) {
document.documentElement.style.overflow = 'hidden';
});
document.addEventListener('touchend', function(e) {
document.documentElement.style.overflow = 'auto';
});
By setting overflow: hidden on start of touch it makes everything exceeding window hidden thus removing availability to scroll anything (no content to scroll).
After touchend the lock can be freed by setting overflow to auto (the default value).
It is better to append this to <html> because <body> may be used to do some styling, plus it can make children behave unexpectedly.
EDIT:
About touch-action: none; - Safari doesn't support it according to MDN.
try overflow hidden on the thing you don't want to scroll while touch event is happening. e.g set overflow hidden on Start and set it back to auto on end.
Did you try it ? I'd be interested to know if this would work.
document.addEventListener('ontouchstart', function(e) {
document.body.style.overflow = "hidden";
}, false);
document.addEventListener('ontouchmove', function(e) {
document.body.style.overflow = "auto";
}, false);
I found that ev.stopPropagation(); worked for me.
To my surprise, the "preventDefault()" method is working for me on latest Google Chrome (version 85) on iOS 13.7. It also works on Safari on the same device and also working on my Android 8.0 tablet.
I am currently implemented it for 2D view on my site here:
https://papercraft-maker.com
this worked for me on iphone
$(".owl-carousel").on('touchstart', function (e) {
e.preventDefault();
});
the modern way (2022) of doing this is using pointer events as outlined here in the mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events
Pointer events build on touchstart and other touch events and actually stop scroll events by default along with other improvements.