Tablet portrait/landscape toggles is not reexecuting jQuery? - javascript

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);

Related

Window resize menu bug

desktop view
desktop view when menu item has been clicked on mobile and then resized to desktop
I have an inline menu on top of the page, which transforms to "hamburger" icon with a drop-down menu when on mobile.
Here is the Jade
i.fa.fa-bars.fa-2x.header__icon.js-nav-toggle
nav.header__nav.js-nav(role="navigation")
ul
li.header__nav__item
a.js-track(href="#about", data-item="about") About
li.header__nav__item
a.js-track(href="#features", data-item="features") Benefits
li.header__nav__item
a.js-track(href="#howitworks", data-item="howitworks") How it works
li.header__nav__item
a.js-track(href="#options", data-item="options") Lease options
li.header__nav__item
a.js-track(href="#savings", data-item="savings") Savings
li.header__nav__item
a.js-track(href="#enquire", data-item="enquire") Enquire
li.header__nav__item.faq-menu
a.js-track(href="/faq") FAQs
In css I'm doing this transformation using media queries, so the icon appears.
+ I have some jquery to make it work (to make dropdown toggle when clicked on the menu icon on mobile view, toggle back when menu item is clicked, and condition to prevent toggling when menu item is clicked on desktop view).
So, here is the code:
$(document).ready(function() {
$('.js-nav-toggle').on('click', function(e) {
$('.js-nav').slideToggle(300);
e.preventDefault();
});
if ($(window).width() < 768) {
$('.header__nav__item').on('click', function(e) {
$('.js-nav').slideToggle(300);
});
}
});
The problem is that all that works perfectly only when page is loaded and not resized (laptop or mobile). But when you loaded the page on a wide window and then resized it to mobile it becomes bad. In this case it's not toggling back when I click any of the menu items (that's obvious as my jquery is only for "document ready".
And visa versa (when you resize from mobile to laptop view) incorrect behavior (if you clicked some menu on mobile the whole ul disappears (toggled) into nothing).
I tried to put the same jquery code to "on window resize" jquery handler, however it does not help.
$window.on('resize', function() {
if ($(window).width() < 768) {
$('.header__nav__item').on('click', function(e) {
$('.js-nav').slideToggle(300);
});
}
}, 150);
My assumption was that it should help at least when I resize from big screen to small. But...fail...
One more comment: every menu item just scrolls the page down to some section (one-page web-site), so the page is not reloaded.
Any thoughts and help are appreciated.
Thank you.
UPDATE
Added screenshots
Thanks to the answer below, the following code fixed the problem with desktop --> mobile resize.
$('.header__nav__item').on('click', function(e) {
if ($(window).width() < 768) {
$('.js-nav').slideToggle(300);
}
});
Tried to fix mobile --> desktop with the following code
$window.on('resize', function() {
if ($(window).width() >= 768 && ($('.js-nav').is(':hidden'))) {
$('.js-nav').html('Show all');
}
}, 150);
Does not work, even with $('.js-nav').show()
However, I've found another question, which is similar, and will try to restructure the code the same way soon (that will answer my question completely)
Display or hide elements on window resize using jQuery
I'm not sure if I fully understood your requirements, but at least to deal with the window resize problem that you stated here is a possible solution. You don't need to bind an event handler to resize event on window, just put your if statement that checks for current window width inside of your on click handler function:
$('.header__nav__item').on('click', function(e) {
if ($(window).width() < 768) {
$('.js-nav').slideToggle(300);
}
});
This way every time you click the window width will be checked dynamically.
2 Liner in Vanilla JS:
navbar.addEventListener('click', function() {
return (window.innerWidth <= 992) ? mob_navbar.classList.toggle('show') : null;
});

enhance javascript code for screen orientation

Having tried a load of different options found on SO, including jQuery mobile solutions (caused masses of conflicts with other jQuery) i found the following code would detect screen rotation and i could use it to add a class.
$(window).bind("resize", function(){
screenOrientation = ($(window).height() > $(window).width())? 90 : 0;
$('.centerBoxContentsSubCat').addClass('mobile_landscape');
});
However, I need to .removeClass when rotated the other way.
I tried duplicating the code, switching the positions of height and width, but this didn't work.
I tried changing the code to
$(window).bind("resize", function(){
if(screenOrientation = ($(window).height() > $(window).width())? 90 : 0){
$('.centerBoxContentsSubCat').addClass('mobile_landscape');
}else{
$('.centerBoxContentsSubCat').removeClass('mobile_landscape');
}
});
but that didn't work either.
I am actually using #media queries for css, but i need to force a change in a column count on screen rotate and all other attempts have failed to get even close.
Any suggestions on where I'm going wrong here?
There are two different ways you can handle this.
The first is with the event orientation change.
// Listen for orientation changes
window.addEventListener("orientationchange", function() {
// Announce the new orientation number
alert(window.orientation);
}, false);
The second is with matchMedia.
// Find matches
var mql = window.matchMedia("(orientation: portrait)");
// If there are matches, we're in portrait
if(mql.matches) {
// Portrait orientation
} else {
// Landscape orientation
}
// Add a media query change listener
mql.addListener(function(m) {
if(m.matches) {
// Changed to portrait
}
else {
// Changed to landscape
}
});
Source:
http://davidwalsh.name/orientation-change
If you need to support older browsers I recommend this library:
https://github.com/WickyNilliams/enquire.js/
You need to use the orientationchange event, not resize.

Can you detect "Tablet Mode" in Edge and IE11 using JavaScript on Windows 10?

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);
}

iOS 8 glitch effecting window resize

I've got some very simple code:
$(function() {
$(window).resize(function () {
if ($(window).width() < 500) {
$("#foo").show(); $("#foo2").hide();
} else if ($(window).width() > 501) {
$("#foo2").show(); $("#foo").hide();
}
}).resize();
});
All was working fine on desktop (all major browsers) and mobile (as many as I could test), till iOS 8 came out. Now when a users scrolls in Safari the javascript falls back to 'else if', creating 'foo2' and hiding 'foo' despite the browser not resizing. This is for a menu, as such the menu closes if the user scrolls which shouldn't be happening.
If I remove the window resize function all works as it should, however the menu doesn't update in real time if the user resizes the browser window.
Is there an alternative to window resize I can use to achieve the same effect?
...so, considering I get the problem as you describe it, you can avoid javascript and do it using pure css and media queries:
#media (max-width:500px) {
#foo {
display:block;
}
#foo2 {
display:none;
}
}
#media (min-width:501px) {
#foo2 {
display:block;
}
#foo {
display:none;
}
}
edit: ..this will definately have nothing to do with scrolling and will certainly be faster and cleaner
This might be a little late, but I'd store the width of the window on load and then check against that on the resize to ensure an actual resize took place horizontally. That would ensure that the code only fired when the browser changed size on the x axis.
var windowWidth = $(window).width();
$(window).resize(function(){
if (windowWidth !== $(window).width())
{
windowWidth = $(window).width();
// rest of your code goes here
}
});
Remember that the resize event could fire quite a lot while someone is resizing, so you may want to limit the whole thing using setInterval, but that's a separate discussion.

Can I disable IE10 history swipe gesture?

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.

Categories

Resources