all articles

jquery not offer right window.height() on iOS

2015-07-08 @sunderls

iOS safari chrome jQuey windowHeight innerHeight js

description, bug

on iOS safari, jQuery does not provide us the right window height by $(window).height(). It seems very annoying.

workround

use window.innerHeight instead on iOS. or following code:

height = window.innerHeight || $(window).height();

why, investigation

so why jQuery does not offer us the right value ? I did some investigation. Since window.innerHeight is so simple to get the right value, I don't think jQuery engineers can just ignore this, there must be some very difficult problems.

dig into jQuery source code

https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/src/dimensions.js#L20

here is the snippet.

if ( jQuery.isWindow( elem ) ) {
    // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
    // isn't a whole lot we can do. See pull request at this URL for discussion:
    // https://github.com/jquery/jquery/pull/764
    return elem.document.documentElement[ "client" + name ];
}

as we can see, if we get height() for window, isWindow() will match and what we get is actually window.document.documentElement.clientHeight.

And here jQuery has pointed out the problem on iOS safari - there is nothing we can do. here is the disqussion: https://github.com/jquery/jquery/pull/764

what is document.documentElement.clientHeight.

documentElement is the root Element of document. if document is html, then documentElement is html tag. https://developer.mozilla.org/ja/docs/Web/API/Document/documentElement

To make it simple. document.childNodes has two elements : [ <!DOCTYPE html>, &lt;html lang="ja" ....&gt;..&lt;/html&gt; ];

SO document.documentElement === document.documentElement === document.childNodes[1] (for the legal html);

* clienHeight = css height + css padding - scrollbar The most important thing to know is that margin & border & scrollbar are all not counted in clientHeight. This makes sense, since these ares cannot display real content. https://developer.mozilla.org/ja/docs/Web/API/Element/clientHeight

older IE doesn't support innerHeight or innerWidth.

This the part of the reason. And taking compatibility into consideration, jQuery has kept using window.document.documentElement.clientHeight for $(window).height(), since this seems the very good way to do the thing as the spec says.

But iOS safari has bugs.

test code

we log the $(window).height() and window.innerHeight on the event resize.

var $log = $('#log');
function log(){
    $log.append('<p>$(widow).height() = ' + $(window).height() + ', wino.innerHeight=' + window.innerHeight + '</p>')
}
log();
$(window).on('resize', log);

here is the test result (** iPhone 5s (8.4))

test 1: load page in portrait

// when loaded (addressbar is maximized)
$(window).height() = 460;
window.innerHeight = 460;

// when scrolled up(addressbar is minimized)
$(window).height() = 460;
window.innerHeight = 529;

// when scrolled down(addressbar is maximized)
$(window).height() = 460;
window.innerHeight = 460;

// when rotated to landscape (addressbar is hidden)
$(window).height() = 320; window.innerHeight = 320;

// when scrolled up(addressbar is hidden)
// fires no resize event

// when scrolled down(addressbar is maximized)
$(window).height() = 320; window.innerHeight = 232;

test 2: load page in landscape

// when loaded (addressbar is maximized)
// seems 'resize' event is fired
$(window).height() = 320; window.innerHeight = 320;
$(window).height() = 320; window.innerHeight = 232;

// when scrolled up (addressbar is hidden)
$(window).height() = 320; window.innerHeight = 320;

// when scrolled up (addressbar is maximized)
$(window).height() = 320; window.innerHeight = 232;

as we can see, iOS safari only changes documentElement.clientHeight when rotating.

is this webkit bug? how about iOS chrome?

chrome has a little more complicate behavior about the addressbar here is the test result (** iPhone 5s (8.4))

test 1: load page in portrait

// when loaded (addressbar is maximized)
$(window).height() = 492;
window.innerHeight = 492; 
// window.innerHeight is not stable, if we drag page the trigger reloading. it offers:
// window.innerHeight = 493; 

// when scrolled up(addressbar is hidden)
// ios chrome fires no 'resize' event, we click button to trigger logging
$(window).height() = 492;
window.innerHeight = 549;

// when scrolled down(addressbar is maximized)
// ios chrome fires no 'resize' event, we click button to trigger logging
$(window).height() = 492;
window.innerHeight = 492;

// when rotated to landscape(no address bar in portrait):
// we got 3 'resize' event
$(window).height() = 300; window.innerHeight = 300;
$(window).height() = 548; window.innerHeight = 548;
$(window).height() = 300; window.innerHeight = 300;
// if we scroll down a bit
// $(window).height() = 244; window.innerHeight = 245;

// when rotated to landscape(maximized address bar in portrait):
// we got 3 'resize' event
$(window).height() = 244; window.innerHeight = 244;
$(window).height() = 492; window.innerHeight = 492;
$(window).height() = 244; window.innerHeight = 244;
// if scroll up a  bit
// $(window).height() = 244; window.innerHeight = 301;

test 2: load page in portrait auto fade

By the way, if we scroll up & down very fast, chrome won't trigger 'resize' but If we scroll slowly like 50% to the fadingout of addressbar and release, addressbar will fadein or fadeout automaticly, and at this time, it will trigger 'resize' event let's call this 'autofade', and test it again on Chrome

// when loaded (addressbar is maximized)
$(window).height() = 492;
window.innerHeight = 492; 

// scroll up to 50% of addressbar, let it fadeout automaticaly
// it triggers 'resize'
$(window).height() = 548;
window.innerHeight = 548; 

// scroll down to 50% of addressbar, let it fadeint automaticaly
// it triggers 'resize'
$(window).height() = 492;
window.innerHeight = 493; 

...
// I don't want to test it anymore

conclusion

This is a bug of safari. And on iOS device (both safari and chrome), do not trust jQuey $(window).height(), always use window.innerHeight.

while on Android, there seems no problem.