Mobile viewport height after orientation change
Asked Answered
P

13

49

I am attaching a listener to the orientationchange event:

window.addEventListener('orientationchange', function () {
    console.log(window.innerHeight);
});

I need to get the height of the document after the orientationchange. However, the event is triggered before the rotation is complete. Therefore, the recorded height reflects the state before the actual orientation change.

How do I register an event that would allow me to capture element dimensions after the orientation change has been completed?

Paschal answered 17/9, 2012 at 2:3 Comment(1)
It works properly for Safari on iOS but not for Chrome on Android.Mervinmerwin
P
7

Orientation change needs a delay to pick up on the new heights and widths. This works 80% of the time.

window.setTimeout(function() {
    //insert logic with height or width calulations here.
}, 200);
Paschal answered 23/9, 2012 at 22:47 Comment(5)
Delay is ugly, but works.. if the delay is enough. Any better solutins to this?Pompano
Note that 200ms is nowhere near enough (iOS, Safari). The actual time is somewhere between 600-1000ms. What causes the variation remains unknown to me. See my answer that relies on a combination of setInterval and setTimeout instead and explains a technique for determining the true end of the orientation change.Schafer
used the same logic :pImperium
at the first, i use this method too, but finally my boss said 200ms is too slowFireresistant
it's such an awful, hack-ish solution. this will inevitably create problems if people with different internet connection speeds are using your websiteVoguish
A
37

Use the resize event

The resize event will include the appropriate width and height after an orientationchange, but you do not want to listen for all resize events. Therefore, we add a one-off resize event listener after an orientation change:

Javascript:

window.addEventListener('orientationchange', function() {
    // After orientationchange, add a one-time resize event
    var afterOrientationChange = function() {
        // YOUR POST-ORIENTATION CODE HERE
        // Remove the resize event listener after it has executed
        window.removeEventListener('resize', afterOrientationChange);
    };
    window.addEventListener('resize', afterOrientationChange);
});

jQuery:

$(window).on('orientationchange', function() {
    // After orientationchange, add a one-time resize event
    $(window).one('resize', function() {
        // YOUR POST-ORIENTATION CODE HERE
    });
});

Do NOT use timeouts

Timeouts are unreliable - some devices will fail to capture their orientation change within your hard-coded timeouts; this can be for unforeseen reasons, or because the device is slow. Fast devices will inversely have an unnecessary delay in the code.

Andersonandert answered 20/3, 2018 at 11:40 Comment(11)
This doesn't work on my IPad 3 because an orientation change never triggers a resize event.Peddada
@Peddada Thanks for the feedback. Can you confirm if a resize event fires at all, or simply not after an orientation change event? This may help others.Andersonandert
@CB Sometimes yes and sometimes no, I've since discovered. Resize would not fire at all if my viewport dimensions didn't change after rotating. And that can happen depending on meta tag viewport settings and how the app is scaled. I now scale my app down to smaller than the viewport during the orientation change event. That allows the viewport height to shrink down when going from portrait to landscape, and then onresize does fire, where I can scale back up to full screen. Android does not have this issue.Peddada
Ignore everything I said above. My app code was inside an iframe so instead of attaching to window.onresize it should have been parent.window.onresize.Peddada
This is by far the best answer.Sherrilsherrill
Just for testing purposes I tried this in chrome dev tools and for some reason chrome triggers 2 resize events on orientationchange. At the first one the window size is 1.3x bigger than is should be. The second one then has the correct window size. Wrapping the inner code in setTimeout(..., 0) seems to do to trick to get the correct window size. Not sure if something like that could also happen in mobile browsers.Showoff
@AlesTrunda $(window).one('resize', function() { // YOUR POST-ORIENTATION CODE HERE }, {once: true});Fireresistant
hi, everyone, I found this approach have some fatal problems. If you use it in chrome mobile mode, if you try to get window.innerHeight immediately after $(window).one('resize') is triggered, you can't get the right value, it will give you (rightValue * zoomRate). Another problem is that I always get wrong value when using it in my ipad, however, I don't know the reason for that know.Fireresistant
Same here. Doesn't properly work on iPhone Safari. Unless I add get innerHeight after 500ms, it gets incorrect heightLento
This is what I do for now: setVH() window.addEventListener('orientationchange', () => { setVH() for (let i = 1; i <= 10; i++) { setTimeout(setVH, i * 100) } })Lento
This solution assumes that the resize event fires after the orientationchange event, and with the final dimension values (such as window.innerHeight) in place. However, this does not seem to be reliably the case. On my Android Samsung Galaxy S21 it worked randomly only (tested on Samsung Internet listening to change event on screen.orientation). So while I like the presented concept in theory, unfortunately I'm back to timeouts again.Decorticate
M
21

Gajus' and burtelli's solutions are robust but the overhead is high. Here is a slim version that's reasonably fast in 2017, using requestAnimationFrame:

// Wait until innerheight changes, for max 120 frames
function orientationChanged() {
  const timeout = 120;
  return new window.Promise(function(resolve) {
    const go = (i, height0) => {
      window.innerHeight != height0 || i >= timeout ?
        resolve() :
        window.requestAnimationFrame(() => go(i + 1, height0));
    };
    go(0, window.innerHeight);
  });
}

Use it like this:

window.addEventListener('orientationchange', function () {
    orientationChanged().then(function() {
      // Profit
    });
});
Micra answered 16/6, 2017 at 2:11 Comment(4)
Brilliant solution!Helmut
I'm running in to issues with this on Safari. It looks like when the page first loads Safari does not always request an animation frame right away. There are also times when the event will not fire at all after a rotation.Berkeleianism
Had a problem checking my actual width when the orientation changed, as the width wasn't being updated on time. These snippets did it for me. Thanks!Midwifery
throws an error orientationChanged is not definedElna
S
16

There is no way to capture the end of the orientation change event because handling of the orientation change varies from browser to browser. Drawing a balance between the most reliable and the fastest way to detect the end of orientation change requires racing interval and timeout.

A listener is attached to the orientationchange. Invoking the listener starts an interval. The interval is tracking the state of window.innerWidth and window.innerHeight. The orientationchangeend event is fired when noChangeCountToEnd number of consequent iterations do not detect a value mutation or after noEndTimeout milliseconds, whichever happens first.

var noChangeCountToEnd = 100,
    noEndTimeout = 1000;

window
    .addEventListener('orientationchange', function () {
        var interval,
            timeout,
            end,
            lastInnerWidth,
            lastInnerHeight,
            noChangeCount;

        end = function () {
            clearInterval(interval);
            clearTimeout(timeout);

            interval = null;
            timeout = null;

            // "orientationchangeend"
        };

        interval = setInterval(function () {
            if (global.innerWidth === lastInnerWidth && global.innerHeight === lastInnerHeight) {
                noChangeCount++;

                if (noChangeCount === noChangeCountToEnd) {
                    // The interval resolved the issue first.

                    end();
                }
            } else {
                lastInnerWidth = global.innerWidth;
                lastInnerHeight = global.innerHeight;
                noChangeCount = 0;
            }
        });
        timeout = setTimeout(function () {
            // The timeout happened first.

            end();
        }, noEndTimeout);
    });

I am maintaining an implementation of orientationchangeend that extends upon the above described logic.

Schafer answered 9/11, 2014 at 14:43 Comment(0)
P
7

Orientation change needs a delay to pick up on the new heights and widths. This works 80% of the time.

window.setTimeout(function() {
    //insert logic with height or width calulations here.
}, 200);
Paschal answered 23/9, 2012 at 22:47 Comment(5)
Delay is ugly, but works.. if the delay is enough. Any better solutins to this?Pompano
Note that 200ms is nowhere near enough (iOS, Safari). The actual time is somewhere between 600-1000ms. What causes the variation remains unknown to me. See my answer that relies on a combination of setInterval and setTimeout instead and explains a technique for determining the true end of the orientation change.Schafer
used the same logic :pImperium
at the first, i use this method too, but finally my boss said 200ms is too slowFireresistant
it's such an awful, hack-ish solution. this will inevitably create problems if people with different internet connection speeds are using your websiteVoguish
G
3

I solved this issue combining a couple of the above solutions. Resize would fire 4-5 times. Using .one, it fired too early. A short time out added to Christopher Bull's solution did the trick.

$(window).on('orientationchange', function () {
  $(window).one('resize', function () {
    setTimeout(reference_to_function, 100);
  });
});
Gerenuk answered 1/10, 2019 at 23:54 Comment(0)
R
2

I used the workaround proposed by Gajus Kuizunas for a while which was reliable albeit a bit slow. Thanks, anyway, it did the job!

If you're using Cordova or Phonegap I found a faster solution - just in case someone else faces this problem in the future. This plugin returns the correct width/height values right away: https://github.com/pbakondy/cordova-plugin-screensize

The returned height and width reflect the actual resolution though, so you might have to use window.devicePixelRatio to get viewport pixels. Also the title bar (battery, time etc.) is included in the returned height. I used this callback function initally (onDeviceReady)

var successCallback = function(result){
    var ratio = window.devicePixelRatio;            
    settings.titleBar = result.height/ratio-window.innerHeight; 
    console.log("NEW TITLE BAR HEIGHT: " + settings.titleBar);
};

In your orientation change event handler you can then use:

height = result.height/ratio - settings.titleBar;

to get the innerHeight right away. Hope this helps someone!

Remunerative answered 6/1, 2016 at 12:26 Comment(0)
P
2

As Ramprakash Jagadeesan already mentioned, there is the Resize Observer Api.

To detect orientation change, just add the observer to your root element:

     const fullSizeImageObserver = new ResizeObserver(() => doSomething());
     fullSizeImageObserver.observe(document.getElementById("root-element"));
     function doSomething() {/* Do something with the updated height/width */}

This is how i used it:

const fullSizeImageObserver = new ResizeObserver(entries => {

  for (let entry of entries) {
      const heightAfterRotation = entry.target.clientHeight;
      // Execute code using the new height of the observed element after the device rotation completed.
  };
});

fullSizeImageObserver.observe(document.getElementById("element-to-observe"));

The resize might fire more than once. You can work around that by:

  for (let entry of entries) {
    if(Math.floor(entry.contentRect.height) === entry.target.clientHeight) { 
      const heightAfterRotation = entry.target.clientHeight;
      // Execute ONCE code using the new height of the observed element after the device rotation completed.
    };
  };

Visit https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver for more details and the browser support.

Paradrop answered 30/12, 2022 at 11:47 Comment(1)
This works best for me... no use of a delay, and always get the updated dimensions. Very responsivePoddy
A
1

It is an experimental feature and gives the right innerHeight and innerWidth after screen orientation is changed.

window.screen.orientation.addEventListener('change', function(){
    console.log(window.innerHeight)
})

I think listening to window resize is the safest way to implement screen orientation change logic.

Antiphonary answered 6/3, 2019 at 20:46 Comment(2)
experimental for which browsers on which operating systems??Voguish
nm i found the support table on mdn. too bad this isnt implemented everywhere right nowVoguish
E
1

It is important to note that orientationchange will not get the height after the change, but rather before. Use resize to accomplish this.

$(window).bind('orientationchange', function (e) {
    var windowHeight = $(window).innerHeight();
    console.log('Before Orientation: Height = ' + windowHeight);

    $(window).resize(function () {
        windowHeight = $(window).innerHeight();
        console.log('After Orientation: Height = ' + windowHeight);
    });
});
Etui answered 6/4, 2019 at 13:31 Comment(0)
J
1

In 2022 the following code is supported by all browsers and operating systems:

let counter=1;
let wih = window.innerHeight;
let wiw = window.innerWidth;
let orientationOnLoad;
let mql = window.matchMedia("(orientation: portrait)");
if(mql.matches) {  
    orientationOnLoad='Portrait';
} else {  
    orientationOnLoad='Landscape';
}
console.log('onload: '+orientationOnLoad+', '+wiw+'x'+wih);

mql.addEventListener('change', function(e) {
    let wih = window.innerHeight;
    let wiw = window.innerWidth;
    if(e.matches) {
        console.log('orientationchange event ('+counter+'): Portrait, '+wiw+'x'+wih);
    }
    else {
        console.log('orientationchange event ('+counter+'): Landscape, '+wiw+'x'+wih);
    }
    counter++;
});

window.matchMedia vs screen.orientation

Juanjuana answered 1/6, 2022 at 21:7 Comment(0)
G
0

I also faced the same problem. So I ended up using:

window.onresize = function(){ getHeight(); }
Gilberte answered 26/5, 2014 at 6:38 Comment(4)
and your function getHeight() does what?Remunerative
The problem with windows.resize is, that iOS devices fire when scrolling so it is useless for detecting the screen size.Ebba
what does getHeight function doBuckner
See Christopher Bull's answer. Check for window resize only once, right after the orientation change, and that way it doesn't fire again when the user scrolls and the viewport changes when the browser toolbars disappear.Myelencephalon
B
0

You can try this. This one works for me. But orientationchange is deprecated in few browsers. Please check the compatability.

window.addEventListener("orientationchange", () => {
  window.addEventListener("resize", () => {
     .... 
     // do you function... () {}
  })
});

You can try this too..

function setBackgroundImage() {
    .... your fun() .....
}
const myBgContainer = document.querySelector(".bgContainer");

new ResizeObserver(setBackgroundImage).observe(myBgContainer);
Bathe answered 19/12, 2022 at 13:51 Comment(0)
W
-1

For people who just want the innerHeight of the window object after the orientationchange there is a simple solution. Use window.innerWidth. The innerWidth during the orientationchange event is the innerHeight after completion of the orientationchange:

window.addEventListener('orientationchange', function () {
    var innerHeightAfterEvent = window.innerWidth;
    console.log(innerHeightAfterEvent);
    var innerWidthAfterEvent = window.innerHeight;
    console.log(innerWidthAfterEvent);
    console.log(screen.orientation.angle);
});

I am not sure if 180-degree changes are done in two 90 degree steps or not. Can't simulate that in the browser with the help of developer tools. But If you want to safeguard against a full 180, 270 or 360 rotation possibility it should be possible to use screen.orientation.angle to calculate the angle and use the correct height and width. The screen.orientation.angle during the event is the target angle of the orientationchange event.

Wifely answered 22/7, 2018 at 10:10 Comment(1)
it's not always as simple as that... for instance, on chrome in ios, one of the browser bars isn't present in landscape mode, so the height of landscape mode is greater than the width of portrait mode.Voguish

© 2022 - 2024 — McMap. All rights reserved.