This is weird. I'm playing with device orientation but can't seem to get rotateX and rotateY to work in Safari (iOS 8.0.2).
But! if I save it to my home screen with apple-web-app-capable meta tag it works fine.
This is my script:
$( document ).ready(function() {
window.addEventListener("deviceorientation", handleOrientation, true);
function handleOrientation(e) {
var alpha = e.alpha;
var beta = e.beta;
var gamma = e.gamma;
$('#z').val(Math.round(alpha)); // Z
$('#x').val(Math.round(beta)); // X
$('#y').val(Math.round(gamma)); // Y
document.getElementById("box").style.webkitTransform="rotateZ("+Math.round(-alpha)+"deg) rotateX("+Math.round(beta)+"deg) rotateY("+Math.round(-gamma)+"deg)";
}
});
you can see it here live (on your iPhone): http://kohlin.net/matte/orientation.html
Then try saving it to your home screen.
Any clues? A bug?
This is a bug in ios Safari browser.
To fix this, surround the box div with another tag. Then move the css:
-webkit-perspective: 500px;
to the containing div. This should make the rotating box visible.
Related
After we upgraded my iPhone to IOS11, I started seeing a cursor in a random position in my login window. This also happens on Chrome / IOS11. The position of the cursor is marked red on screenshots below.
Try adding position: fixed to the body of the page.
Piggybacking off of ybentz's answer. If you use the bootstrap modal, you can add this to your main.js file:
var savedScrollPosition;
$(document).on('show.bs.modal', '.modal', function() {
savedScrollPosition = $(window).scrollTop();
});
$(document).on('hidden.bs.modal', '.modal', function() {
window.scrollTo(0, savedScrollPosition);
});
And then this to your css because you'll already have the modal-open class being added anytime the modal pops:
body.modal-open {
position: fixed;
width: 100%;
}
Thanks for the help ybentz!! I would've responded to your comment, but I don't have the reputation to do so yet.
Ignacios Answer solved the Problem for me.
If i show an overlayer/modal i add the class fixed to the body.
Also add to css this rule:
body.fixed{
position: fixed;
}
I had the same problem and the position: fixed solution on the body does solve it so that's great. One thing to note though is that adding the class to the body causes the browser to "jump" to the top of the page so when you remove it when the popup/modal is closed it might be confusing for the user.
If your popup/modal is full screen on iOS what you can do to fix it is save the scroll position before adding the position: fixed class with something like this (using jQuery but can be done easily with vanilla js):
var savedScrollPosition = $(window).scrollTop()
$('body').addClass('has-fullscreen-modal')
and then restore it on popup close like this:
$('body').removeClass('has-fullscreen-modal')
window.scrollTo(0, savedScrollPosition)
and your css will be
body.has-fullscreen-modal {
position: fixed;
}
Hope that helps!
Personally, position: fixed scroll to top automatically. Quite annoying !
To avoid penalizing other devices and versions I apply this fix only to the appropriate versions of iOS.
**VERSION 1 - All modals fix**
For the javascript/jQuery
$(document).ready(function() {
// Detect ios 11_x_x affected
// NEED TO BE UPDATED if new versions are affected
var ua = navigator.userAgent,
iOS = /iPad|iPhone|iPod/.test(ua),
iOS11 = /OS 11_0|OS 11_1|OS 11_2/.test(ua);
// ios 11 bug caret position
if ( iOS && iOS11 ) {
// Add CSS class to body
$("body").addClass("iosBugFixCaret");
}
});
For the CSS
/* Apply CSS to iOS affected versions only */
body.iosBugFixCaret.modal-open { position: fixed; width: 100%; }
**VERSION 2 - Selected modals only**
I modified the function to fire only for selected modals with a class .inputModal
Only the modals with inputs should be impacted to avoid the scroll to top.
For the javascript/jQuery
$(document).ready(function() {
// Detect ios 11_x_x affected
// NEED TO BE UPDATED if new versions are affected
(function iOS_CaretBug() {
var ua = navigator.userAgent,
scrollTopPosition,
iOS = /iPad|iPhone|iPod/.test(ua),
iOS11 = /OS 11_0|OS 11_1|OS 11_2/.test(ua);
// ios 11 bug caret position
if ( iOS && iOS11 ) {
$(document.body).on('show.bs.modal', function(e) {
if ( $(e.target).hasClass('inputModal') ) {
// Get scroll position before moving top
scrollTopPosition = $(document).scrollTop();
// Add CSS to body "position: fixed"
$("body").addClass("iosBugFixCaret");
}
});
$(document.body).on('hide.bs.modal', function(e) {
if ( $(e.target).hasClass('inputModal') ) {
// Remove CSS to body "position: fixed"
$("body").removeClass("iosBugFixCaret");
//Go back to initial position in document
$(document).scrollTop(scrollTopPosition);
}
});
}
})();
});
For the CSS
/* Apply CSS to iOS affected versions only */
body.iosBugFixCaret.modal-open { position: fixed; width: 100%; }
For the HTML
Add the class inputModal to the modal
<div class="modal fade inputModal" tabindex="-1" role="dialog">
...
</div>
Nota bene
The javascript function is now self-invoking
REF : iOS 11 Safari bootstrap modal text area outside of cursor
I have fixed this issue with this CSS
#media(max-width:767px) {
body {
position:fixed !important;
overflow:auto !important;
height:100% !important;
}
}
I'm trying to handle a long list of <div>s and maintain scroll position in the list after navigating off and coming back. Essentially when a selection made is in the list I capture the listScrollPos and then try to reset it when I'm returning to the page (in Angular - so the list is re-rendered first).
vm.getAlbums = function() {
albumService.getAlbums()
.success(function (data) {
vm.albums = data;
$timeout(function () {
if (albumService.listScrollPos) {
$("#MainView").scrollTop(albumService.listScrollPos);
albumService.listScrollPos = 0;
}
}, 50); // delay required
})
.error(function(err) {
vm.error.error(err.message);
});
};
The process works fine in all browsers I tested - except on iOS in a WebView (Safari works fine). In other browsers the list displays and the scroll position is moved after the initial render. The pointer resets and all is good.
However, on iOS 8 either in Safari or a Web View in Cordova, the div turns white and shows 'empty'. If I touch the div anywhere it immediately displays at the correct scroll position.
IOW, the DOM appears to be updated and rendered, but the browser is somehow optimizing the scrolled content that was moved under program control.
Is there any way to force the browser to re-render the element after the scroll position was moved programmatically?
Ok, so after a bit more checking the problem is definitely isolated to the iOS WebView - Safari on iOS works fine without any of the following. But a Cordova app or a pinned iOS app exhibits this 'white out' behavior.
The workaround is to explicitly force the DOM to re-render the element using the 'scrollHeight reading trick'.
Here's the code that works:
vm.getAlbums = function() {
albumService.getAlbums()
.success(function (data) {
vm.albums = data;
setTimeout(function () {
if (albumService.listScrollPos) {
var el = $("#MainView");
el.scrollTop(albumService.listScrollPos);
albumService.listScrollPos = 0;
$timeout(function() {
var t = el[0].scrollHeight;
}, 1);
}
}, 1); // delay around animation 900
})
};
Notice the last $timeout() block that simply reads the scrollHeight of the element, which forces the re-render and properly displays the result.
There's a little jumpiness due to the slight rendering delay.
I have a bottom drawer which is fix positioned at the bottom. When tapped, the drawer will slide up and show more content. On iOS 7, when user taps on input tag or textarea tag, the virtual keyboard pops up. However, the drawer jumps up on the page instead of sticking to the bottom when keypad pops up. Please see the diagram below for illustration.
I firstly encountered the issue also on Safari, but I added the following code to change the fixed position to absolute when keypad is open:
// Apple.Device detects if it is an apple device
if (Modernizr.touch && Apple.Device) {
/* cache dom references */
var $body = jQuery('body');
/* bind events */
$(document)
.on('focus', 'input, textarea', function(e) {
$body.addClass('fixfixed');
})
.on('blur', 'input, textarea', function(e) {
$body.removeClass('fixfixed');
});
}
CSS code:
.fixfixed #drawer {
bottom: 0;
margin-left: auto;
margin-right: auto;
position: absolute;
left: 0;
right: 0;
}
This fix works on Safari on iOS 7 but it doesn't work on Chrome. Also, there is a weired behavior:
If there is an input tag on the page and I tap on it on iPad, then the virtual keyboard opens and the drawer jumps up. If the drawer happens to then cover the I clicked on, the click event actually fires on the drawer. Why is that?
How can I resolve this issue? (I've been searching for a while but how do I debug Chrome on iOS?)
Many thanks for your help!
Update
I've used the following code to detect if it is Chrome on iOS 7, if so, I hide the Drawer when the virtual keyboard is up, and re-display the drawer when virtual keyboard is down.
function iOSversion() {
if (/iP(hone|od|ad)/.test(navigator.platform)) {
// supports iOS 2.0 and later: <http://bit.ly/TJjs1V>
var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)];
}
}
var iosVersion = iOSversion();
if (navigator.userAgent.match('CriOS') && iosVersion[0] == '7') {
$(document).hammer().on('tap', 'input, textarea', function(e) {
$('body').addClass('chromefixfixed');
})
.on('blur', 'input, textarea', function(){
body.removeClass('chromefixfixed');
})
}
CSS code:
.chromefixfixed #drawer {
display: none;
}
Still the question remains: how do I get Chrome on iOS 7 to work like Chrome on Android (without hiding the drawer when keyboard is up)?
Thanks for the help!
Position fixed bottom and position absolute bottom are an absolute nightmare on iOS and android devices, in my experience it's just not possible to get it to render consistently even across the most modern devices, let alone the older ones that dominate the market currently. So much so, that as a dev I would ask a designer to rethink the layout because of it. I believe it's called "sidestepping the turd".
I am developing an iPad application with Phonegap and jQuery mobile. One of the functions required is to allow the user of the app to sign (their signature) in 2 boxes. I am using easel.js to handle the "drawing" on the canvas element.
The HTML structure of this page is something like:
<div class="signature-wrapper sw1">
<canvas class="flexBox" id="myCanvas" width="240" height="240"></canvas>
</div>
<div class="signature-wrapper sw2">
<canvas class="flexBox" id="myCanvas2" width="240" height="240"></canvas>
</div>
Where the 2 canvas elements are where the signatures will be drawn. Below each of these elements are buttons to clear and save each of the elements. When clicked, they fire functions to clear the canvas, or save the canvas to an image (the image is then placed over the canvas, and removed on clear)
The save functionality looks something like:
function saveStage1(){
var canvas = document.getElementById("myCanvas");
var imageData = canvas.toDataURL("image/png");
$('.sw1').append("<img src='" + imageData +"' style='position: absolute; left: 0px; top: 0px;' />");
}
Basically, it is just getting the content of the canvas and saving it to a PNG image, which is then appended to the wrapper for the canvas elements. There is additional code I have left out of the code here for clarity, dealing with localStorage and db.transactions.
This function is working as expected.
The next function is to clear the canvas:
function clearStage1(){
oldMidX, oldMidY, oldX, oldY = null;
delete window.currentShape;
stage.clear();
stage.removeAllChildren();
stage.update();
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(imageObj, 0, 0);
};
imageObj.src = 'images/signature-client.png';
$('.sw1 img').remove();
}
This clears the canvas, nulls some variables, and recreates the canvas.
This works 100% in chrome and safari, but does not work correctly on iPad as a Phonegap application. On iPad, you can draw, but once you have clicked save or clear you are unable to draw without navigating away and back to the screen. It seems there is a JavaScript error somewhere on the page on both the save and clear functions which is preventing any further JavaScript from executing without "reloading" the page.
I have setup my console in Safari on mac to look at xcode application, and there is no error in the log. Any help would be appreciated.
I am using iScroll on my mobile enable website (using iPhone here) to scroll inside a div.
In this this div, I have an iframe with a fixed height like this:
<body>
<div id="iscroller">
<iframe id="theIframe"></iframe>
Other stuff
</div>
</body>
Now, while scrolling within the div, everything works as expected but I cannot scroll when the scrolling gesture begins on the iframe.
The problem is described here pretty well: https://github.com/cubiq/iscroll/issues/41
So, I used the css workaround from that post by applying pointer-events:none to the iframe.
Now scrolling works perfectly but I cannot click any links which are defined within the iframe because all click/touch events on the iframe seems to be blocked due to pointer-events: none.
So, I thought:
"Ok, while the user scrolls, I need pointer-events:none. If he is
not scrolling (and instead clicking), I must set pointer-events:auto
in order to let the click/touch events pass."
So I did this:
CSS
#theIframe{pointer-events:none}
JavaScript
$("#theIframe").bind("touchstart", function(){
// Enable click before click is triggered
$(this).css("pointer-events", "auto");
});
$("#theIframe").bind("touchmove", function(){
// Disable click/touch events while scrolling
$(this).css("pointer-events", "none");
});
Even adding this doesn't work:
$("#theIframe").bind("touchend", function(){
// Re-enable click/touch events after releasing
$(this).css("pointer-events", "auto");
});
No matter what I do: Either scrolling doesn't work or clicking the link inside the iframe doesn't work.
Doesn't work. Any ideas?
I found the perfect solution. Works great on iOS and Android.
The basic idea is to put a div layer on top of that iframe. This way scrolling works smoothly.
If the user wants to tap/click on an element on that iframe I simply catch that click on the layer, save the x and y coordinates and trigger a click event on the iframe's content at these coordinates:
HTML:
<div id="wrapper">
<div id="layer"></div>
<iframe id="theIframe"></iframe>
</div>
Other stuff
CSS:
#layer{
position:absolute;
opacity:0;
width:100%;
height:100%;
top:0;
left:0;
right:0;
bottom:0;
z-index:2
}
JavaScript:
$('#layer').click(function(event){
var iframe = $('#theIframe').get(0);
var iframeDoc = (iframe.contentDocument) ? iframe.contentDocument : iframe.contentWindow.document;
// Find click position (coordinates)
var x = event.offsetX;
var y = event.offsetY;
// Trigger click inside iframe
var link = iframeDoc.elementFromPoint(x, y);
var newEvent = iframeDoc.createEvent('HTMLEvents');
newEvent.initEvent('click', true, true);
link.dispatchEvent(newEvent);
});
I found a solution for this, it happens to be close to what other guys already mentioned on github but this may be useful for whoever wants to find a fast working resolution for this problem.
I'm assuming a few things, like there's only one iscroll container, here represented as ID. This is not properly tested and needs refactor. It's working in my project, but I changed it here slightly for the example but I guess you'll easily understand what you need to do:
var $iscroll = $('#iscroll');
document.addEventListener('touchstart', function(e) {
if ($iscroll.find('iframe').length > 0){
$.each($iscroll.find('iframe'), function(k,v){
var $parent = $(v).parent().first();
if ($parent.find('.preventTouch').length == 0){
$('<div class="preventTouch" style="position:absolute; z-index:2; width:100%; height:100%;"></div>')
.prependTo($parent);
};
$parent
.css('position', 'relative').css('z-index', 1);
});
$iscroll.find('.preventTouch').on('click', function(e){
e.preventDefault();
e.stopPropagation();
return false;
});
};
};
document.addEventListener('touchend', function(e) {
if ($iscroll.find('iframe').length > 0){
setTimeout(function(){
var $iscroll = $('#iscroll');
$iscroll.find('.preventTouch').remove();
$iscroll.find('iframe').css('z-index', '');
$iscroll.find('.preventTouch').off('click');
}, 400);
};
};
Thanks for looking!