15

The Goal:

To find the max viewport height of a device including the space of the address bar so that we can dynamically resize the min-body and push our content up.

The Problem:

Mobile browsers handle orientation states differently and update DOM properties on orientation change differently.

Detect rotation of Android phone in the browser with JavaScript

With Android phones, screen.width or screen.height also updates as the device is rotated.

|==============================================================================|
|     Device     | Events Fired      | orientation | innerWidth | screen.width |
|==============================================================================|
| iPad 2         | resize            | 0           | 1024       | 768          |
| (to landscape) | orientationchange | 90          | 1024       | 768          |
|----------------+-------------------+-------------+------------+--------------|
| iPad 2         | resize            | 90          | 768        | 768          |
| (to portrait)  | orientationchange | 0           | 768        | 768          |
|----------------+-------------------+-------------+------------+--------------|
| iPhone 4       | resize            | 0           | 480        | 320          |
| (to landscape) | orientationchange | 90          | 480        | 320          |
|----------------+-------------------+-------------+------------+--------------|
| iPhone 4       | resize            | 90          | 320        | 320          |
| (to portrait)  | orientationchange | 0           | 320        | 320          |
|----------------+-------------------+-------------+------------+--------------|
| Droid phone    | orientationchange | 90          | 320        | 320          |
| (to landscape) | resize            | 90          | 569        | 569          |
|----------------+-------------------+-------------+------------+--------------|
| Droid phone    | orientationchange | 0           | 569        | 569          |
| (to portrait)  | resize            | 0           | 320        | 320          |

Because of this it is clear that to find the max viewport height no matter what orientation, using a single function to return the max height of a device will never be constant over a range of devices.

Other problems I have discovered that don't make these two play nice:

  • The window.devicePixelRatio property can return inconsistent heights when dividing by window.outerHeight.
  • Delay window.setTimeout(function() {}, time) needs to be used to give DOM elements a chance to update after orientation change.
  • window.outerHeight is not updated on orientation changes for iOS devices. Using screen.availHeight as a fallback includes the bottom nav bar as total height.
  • Using a #header, #content, #footer structure forces you to dynamically recalculate the #content{min-height} to push the #footer down when the body is dyamically updated.

A Solution:

First let's take a look at DIV structure:

<style>
#header,#content,#footer{width:100%;}
</style>

<body>
<div id="header"></div>
<div id="content"></div>
<div id="footer"></div>
</body>

We want to prevent devices from scaling on their own:

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />

We need help to have the ability to return a max viewport height and hide address bar for iOS:

<script src="iOS.js" type="text/javascript"></script>

http://iosjs.com/

Then detect if the device supports orientation change and use resize as a fallback:

var iOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/i) ? true : false);
var android = (navigator.userAgent.match(/Android/i) ? true : false);
var supportsOrientationChange = "onorientationchange" in window;
var orientationEvent = supportsOrientationChange ? "orientationchange" : "resize"; 

The belly of the beast:

function updateOrientation()
{
    var orientation = (window.orientation);

    if(android)
    {
        window.setTimeout(function() {
            window.scrollTo(0,0);
            var size = window.outerHeight/window.devicePixelRatio;
            $('body').css('min-height', size + 'px');
            var headerHeight = $('#header').height();
            var footerHeight = $('#footer').height();
            var contentHeight = size - (headerHeight+footerHeight);
            $('#content').css('min-height', contentHeight + 'px');
            window.scrollTo(0,1);
        }, 200);
    }

    if(iOS)
    {
        window.setTimeout(function(){
            window.scrollTo(0,0);
            var size = iOS_getViewportSize();
            var headerHeight = $('#header').height();
            var footerHeight = $('#footer').height();
            var contentHeight = size.height - (headerHeight+footerHeight);
            $('#content').css('min-height', contentHeight + 'px');
            window.scrollTo(0,1);
        }, 0);
    }
}

Add event listeners for page load and orientation event:

if(iOS)
{
    iOS_addEventListener(window, "load", iOS_handleWindowLoad);
    iOS_addEventListener(window, "orientationchange", iOS_handleOrientationChange);
    iOS_addEventListener(window, "resize", iOS_handleReize);
}
addEventListener("load", function() 
{
    updateOrientation();
}, false);
addEventListener(orientationEvent, function() {
    updateOrientation();
}, false);

Proof is in the pudding:

iPhone 4 & 4s Portrait & Landscape

iPhone 4 & 4s Portrait iPhone 4 & 4s Landscape


Android Portrait & Landscape

Android Portrait Android Landscape


The goal here is to minify this solution or make it better.

Community
  • 1
  • 1
Dan Kanze
  • 18,485
  • 28
  • 81
  • 134
  • >> Please do not suggest Media Queries. Why not? One (very hacky) way you can use to accomplish this task is by having two CSS files for different orientation and use javascript to check element width when you think orientation changed. – Christian Sep 30 '12 at 21:44
  • @Christian the height is being dynamically calculated to support liquid layout cross browser. There are several elements I need to "recalculate" height for. Media Queries are being used where they can be used, this is not one of those places. – Dan Kanze Sep 30 '12 at 21:46
  • First of all, if you're calculating height manually, then the layout is not "liquid". Secondly, I see your problem is with calculating height rather than detecting the change, is this correct? If so, then my first suspicion would be that you need outerHeight property. – Christian Sep 30 '12 at 21:57
  • @Christian im not going to agrue with you about what you think my problem is. im telling you that my build is indeed liquid BUT there are elements that are being dynamically calculated so those child elements contained by the parent also need to be dynamically calculated. outerHeight has nothing to do with the problem. the problem, is that orientation change events do not pick up height/ width DOM changes in mobile safari browsers. thankyou for your efforts. – Dan Kanze Sep 30 '12 at 22:02

3 Answers3

1

This is a simple solution that will append the browsers width and height to the document body on load and window resize.

jQuery.event.add(window, "load", resize);
jQuery.event.add(window, "resize", resize);

  function resize() 
    {
      var h = jQuery(window).height();
      var w = jQuery(window).width();
      jQuery("body").css({"width": w, "height": h});
    }
stubbsy
  • 1
  • 1
0

You could try a self-invoking closure that monitors the change in orientation by itself. Something like this:

(function () {

    var CurrentHeight;

    (function CheckForOrientationChange() {

        //var NewHeight = window.screen.availHeight / window.devicePixelRatio;
        //var NewHeight = $(window).height();    

        var NewHeight = $('#WidthCheck').width();

        if (CurrentHeight && CurrentHeight!== NewHeight ) {

            alert(NewHeight); // change here
        }

        CurrentHeight = NewHeight;

        setTimeout(CheckForOrientationChange, 1000);

    })();

})();

Just drop this into the document ready function. For now it checks every second but you can shorten that interval. The jsfiddle is here and you can test it by changing the size of the browser to simulate a mobile browser's change and then you can adapt the code to handle your action.

frenchie
  • 51,731
  • 109
  • 304
  • 510
  • I see what you are doing here. Although, using if `(CurrentOrientation && CurrentOrientation !== NewOrientation)` is not allowing the ability for a user to pull height after orientation change in any state. For example, I view landscape I am returned 230px / I view portrait I am returned 450px. Is there a modification that you could make to this that would support that? – Dan Kanze Oct 02 '12 at 16:21
  • Well then it's an easy fix: what's the name of the function that's returning 230 and 450? – frenchie Oct 03 '12 at 01:18
  • I suppose what I'm saying is that I need a function to return the viewport height on orientation change. For example, with an iphone, I would expect to be returned 230px at landscape, and 450px at portrait. `var h = (window.screen.availHeight/window.devicePixelRatio); alert(h);` – Dan Kanze Oct 03 '12 at 13:17
  • See edit, just updated it with window.screen.availHeight/window.devicePixelRatio – frenchie Oct 03 '12 at 19:02
  • I'm sorry but this doesn't properly calculate the max viewport height for either Android or iPhone. – Dan Kanze Oct 04 '12 at 02:16
  • You just put that (window.screen.availHeight/window.devicePixelRatio); returns 230px or 450px!! So I took your function and modified the code accordingly to make it work with that function. Anyway, when you get the function's name and syntax, just replace it with the line (window.screen.availHeight/window.devicePixelRatio); and it'll work. Happy coding. – frenchie Oct 04 '12 at 13:07
  • Note, I changed it to work with jQuery; if I were you, I'd try that and see what you get (assuming jQuery is already on your page). – frenchie Oct 04 '12 at 13:11
  • Have you tried this yourself? I'm looking at this with both a iPhone and Android in front of me and it doesnt seem to return the results im looking for..I'm sorry if the question was unclear but im tyring to find the max viewport height of a device. The code I provided was mearly to provoke the idea of what I am trying to acheive. As mentioned above, the `window.screen.availHeight` does not work for iPhone. – Dan Kanze Oct 04 '12 at 15:31
  • No, I don't have an iPhone or Android to try it on. You are solving two issues: 1) determining the orientation's height and 2) detecting orientation changes. My solution does solve problem 2) and all you need to find is one line to get the value. Did you try with the jquery line? – frenchie Oct 04 '12 at 15:36
  • I wish it was this easy. The iPhone is buggy in updating DOM element properties after orientation change. The real problem is overcoming this obsticle. Testing with an iPhone in front of you seems like the only real way to replicate the issue I describe here. If the iPhone didn't have this issue I would also expect your logic to work. – Dan Kanze Oct 04 '12 at 15:41
  • I just thought of something that could do the trick. At the top of the body, you put a div with no content; give it ID WidthCheck and CSS {clear: both; height: 1px; background: transparent;} This will draw a line that spans the whole width. And then, every second, we're going to check the width of this div: if it's over 350px wide then we're on landscape and if it's less then wouldn't that mean we're on portrait? I think that could solve it. All you'd need to do is check for the width of that div. See update. – frenchie Oct 04 '12 at 15:49
  • You are assuming the width of the portrait is equal to the height of the landscape correct? – Dan Kanze Oct 04 '12 at 16:34
  • No, if we're just looking to determine if we've changed orientation, we don't care about the height. If we can determine that the width changed, then wouldn't that mean that the height also changed, and that therefore the orientation changed? – frenchie Oct 04 '12 at 17:00
0

Expected values returned in iOS simulator. I can't test for Android at the moment.

var supportsOrientationChange = "onorientationchange" in window;
var orientationEvent = supportsOrientationChange ? "orientationchange" : "resize";

window.onload = updateOrientation();

window.addEventListener(orientationEvent, function() {
    updateOrientation();
}, false);

function updateOrientation(){
    switch(window.orientation){
    case 0:
    alert(window.outerHeight); // Returns '356' with browser chrome
    break;

    case -90:
    alert('Landscape right');
    break;

    case 90:
    alert(window.outerHeight); // Returns '208' w browser chrome
    break;

    case 180:
    //alert('Portrait view - upside down');
    break;
    }
    var orientation = (window.orientation);
}

(Note: This code will not test in a browser.)

Dawson
  • 7,567
  • 1
  • 26
  • 25
  • is this a "simulator" or an emulator? "simulators" will not properly replicate this problem so i am sorry about that. – Dan Kanze Oct 04 '12 at 15:32
  • It's the 'iOS Simulator' that came with my Mac – Dawson Oct 04 '12 at 15:40
  • Does your iOS simulator invoke mobile safari with the same hardware constraints? Im sorry but simulators do not replicate this problem the way the actual device does. – Dan Kanze Oct 04 '12 at 15:43
  • Yes - Mobile Safari. It is the simulator that comes with Xcode, "the complete developer toolset for building Mac, iPhone, and iPad apps, including the Xcode IDE, Instruments, and iOS Simulator" <-- https://developer.apple.com/technologies/ios/ – Dawson Oct 04 '12 at 16:40
  • It does not come with the same hardware constraints, but it does respond correctly to media queries and other environment variables the same way an actual device does. – BoltClock Oct 06 '12 at 17:21
  • It may respond the same but in testing the method does not return the correct height for each orientation. – Dan Kanze Oct 07 '12 at 21:55