iPad Web App: Detect Virtual Keyboard Using JavaScript in Safari?

JavascriptIphoneIpadSafariWeb Applications

Javascript Problem Overview


I'm writing a web app for the iPad (not a regular App Store app - it's written using HTML, CSS and JavaScript). Since the keyboard fills up a huge part of the screen, it would make sense to change the app's layout to fit the remaining space when the keyboard is shown. However, I have found no way to detect when or whether the keyboard is shown.

My first idea was to assume that the keyboard is visible when a text field has focus. However, when an external keyboard is attached to an iPad, the virtual keyboard does not show up when a text field receives focus.

In my experiments, the keyboard also did not affect the height or scrollheight of any of the DOM elements, and I have found no proprietary events or properties which indicate whether the keyboard is visible.

Javascript Solutions


Solution 1 - Javascript

I found a solution which works, although it is a bit ugly. It also won't work in every situation, but it works for me. Since I'm adapting the size of the user interface to the iPad's window size, the user is normally unable to scroll. In other words, if I set the window's scrollTop, it will remain at 0.

If, on the other hand, the keyboard is shown, scrolling suddenly works. So I can set scrollTop, immediately test its value, and then reset it. Here's how that might look in code, using jQuery:

$(document).ready(function(){
	$('input').bind('focus',function() {
		$(window).scrollTop(10);
		var keyboard_shown = $(window).scrollTop() > 0;
		$(window).scrollTop(0);

		$('#test').append(keyboard_shown?'keyboard ':'nokeyboard ');
	});
});

Normally, you would expect this to not be visible to the user. Unfortunately, at least when running in the Simulator, the iPad visibly (though quickly) scrolls up and down again. Still, it works, at least in some specific situations.

I've tested this on an iPad, and it seems to work fine.

Solution 2 - Javascript

You can use the focusout event to detect keyboard dismissal. It's like blur, but bubbles. It will fire when the keyboard closes (but also in other cases, of course). In Safari and Chrome the event can only be registered with addEventListener, not with legacy methods. Here is an example I used to restore a Phonegap app after keyboard dismissal.

 document.addEventListener('focusout', function(e) {window.scrollTo(0, 0)});

Without this snippet, the app container stayed in the up-scrolled position until page refresh.

Solution 3 - Javascript

If there is an on-screen keyboard, focusing a text field that is near the bottom of the viewport will cause Safari to scroll the text field into view. There might be some way to exploit this phenomenon to detect the presence of the keyboard (having a tiny text field at the bottom of the page which gains focus momentarily, or something like that).

Solution 4 - Javascript

maybe a slightly better solution is to bind (with jQuery in my case) the "blur" event on the various input fields.

This because when the keyboard disappear all form fields are blurred. So for my situation this snipped solved the problem.

$('input, textarea').bind('blur', function(e) {

       // Keyboard disappeared
       window.scrollTo(0, 1);

});

hope it helps. Michele

Solution 5 - Javascript

Edit: Documented by Apple although I couldn't actually get it to work: WKWebView Behavior with Keyboard Displays: "In iOS 10, WKWebView objects match Safari’s native behavior by updating their window.innerHeight property when the keyboard is shown, and do not call resize events" (perhaps can use focus or focus plus delay to detect keyboard instead of using resize).

Edit: code presumes onscreen keyboard, not external keyboard. Leaving it because info may be useful to others that only care about onscreen keyboards. Use http://jsbin.com/AbimiQup/4 to view page params.

We test to see if the document.activeElement is an element which shows the keyboard (input type=text, textarea, etc).

The following code fudges things for our purposes (although not generally correct).

function getViewport() {
	if (window.visualViewport && /Android/.test(navigator.userAgent)) {
		// https://developers.google.com/web/updates/2017/09/visual-viewport-api	Note on desktop Chrome the viewport subtracts scrollbar widths so is not same as window.innerWidth/innerHeight
		return {
			left: visualViewport.pageLeft,
			top: visualViewport.pageTop,
			width: visualViewport.width,
			height: visualViewport.height
		};
	}
	var viewport = {
			left: window.pageXOffset,	// http://www.quirksmode.org/mobile/tableViewport.html
			top: window.pageYOffset,
			width: window.innerWidth || documentElement.clientWidth,
			height: window.innerHeight || documentElement.clientHeight
	};
	if (/iPod|iPhone|iPad/.test(navigator.platform) && isInput(document.activeElement)) {		// iOS *lies* about viewport size when keyboard is visible. See http://stackoverflow.com/questions/2593139/ipad-web-app-detect-virtual-keyboard-using-javascript-in-safari Input focus/blur can indicate, also scrollTop: 
		return {
			left: viewport.left,
			top: viewport.top,
			width: viewport.width,
			height: viewport.height * (viewport.height > viewport.width ? 0.66 : 0.45)	// Fudge factor to allow for keyboard on iPad
		};
	}
	return viewport;
}


function isInput(el) {
	var tagName = el && el.tagName && el.tagName.toLowerCase();
	return (tagName == 'input' && el.type != 'button' && el.type != 'radio' && el.type != 'checkbox') || (tagName == 'textarea');
};

The above code is only approximate: It is wrong for split keyboard, undocked keyboard, physical keyboard. As per comment at top, you may be able to do a better job than the given code on Safari (since iOS8?) or WKWebView (since iOS10) using window.innerHeight property.

I have found failures under other circumstances: e.g. give focus to input then go to home screen then come back to page; iPad

Solution 6 - Javascript

During the focus event you can scroll past the document height and magically the window.innerHeight is reduced by the height of the virtual keyboard. Note that the size of the virtual keyboard is different for landscape vs. portrait orientations so you'll need to redetect it when it changes. I would advise against remembering these values as the user could connect/disconnect a bluetooth keyboard at any time.

var element = document.getElementById("element"); // the input field
var focused = false;

var virtualKeyboardHeight = function () {
    var sx = document.body.scrollLeft, sy = document.body.scrollTop;
    var naturalHeight = window.innerHeight;
    window.scrollTo(sx, document.body.scrollHeight);
    var keyboardHeight = naturalHeight - window.innerHeight;
    window.scrollTo(sx, sy);
    return keyboardHeight;
};

element.onfocus = function () {
    focused = true;
    setTimeout(function() { 
        element.value = "keyboardHeight = " + virtualKeyboardHeight() 
    }, 1); // to allow for orientation scrolling
};

window.onresize = function () {
    if (focused) {
        element.value = "keyboardHeight = " + virtualKeyboardHeight();
    }
};

element.onblur = function () {
    focused = false;
};

Note that when the user is using a bluetooth keyboard, the keyboardHeight is 44 which is the height of the [previous][next] toolbar.

There is a tiny bit of flicker when you do this detection, but it doesn't seem possible to avoid it.

Solution 7 - Javascript

Only tested on Android 4.1.1:

blur event is not a reliable event to test keyboard up and down because the user as the option to explicitly hide the keyboard which does not trigger a blur event on the field that caused the keyboard to show.

resize event however works like a charm if the keyboard comes up or down for any reason.

coffee:

$(window).bind "resize", (event) ->  alert "resize"

fires on anytime the keyboard is shown or hidden for any reason.

Note however on in the case of an android browser (rather than app) there is a retractable url bar which does not fire resize when it is retracted yet does change the available window size.

Solution 8 - Javascript

Instead of detecting the keyboard, try to detect the size of the window

If the height of the window was reduced, and the width is still the same, it means that the keyboard is on. Else the keyboard is off, you can also add to that, test if any input field is on focus or not.

Try this code for example.

var last_h = $(window).height(); //  store the intial height.
var last_w = $(window).width(); //  store the intial width.
var keyboard_is_on = false;
$(window).resize(function () {
    if ($("input").is(":focus")) {
        keyboard_is_on =
               ((last_w == $(window).width()) && (last_h > $(window).height()));
    }   
});		

Solution 9 - Javascript

Here's a small library I created you can use to make a viewport that Just Works(TM)

https://github.com/adamjgrant/a-viewport-that-works

Solution 10 - Javascript

The visual viewport API is made for reacting to virtual keyboard changes and viewport visibility.

> The Visual Viewport API provides an explicit mechanism for querying and modifying the properties of the window's visual viewport. The visual viewport is the visual portion of a screen excluding on-screen keyboards, areas outside of a pinch-zoom area, or any other on-screen artifact that doesn't scale with the dimensions of a page.

function viewportHandler() {
  var viewport = event.target;
  console.log('viewport.height', viewport.height)
}

window.visualViewport.addEventListener('scroll', viewportHandler);
window.visualViewport.addEventListener('resize', viewportHandler);

Solution 11 - Javascript

Try this one:

var lastfoucsin;

$('.txtclassname').click(function(e)
{
  lastfoucsin=$(this);

//the virtual keyboard appears automatically

//Do your stuff;

});


//to check ipad virtual keyboard appearance. 
//First check last focus class and close the virtual keyboard.In second click it closes the wrapper & lable

$(".wrapperclass").click(function(e)
{
        
if(lastfoucsin.hasClass('txtclassname'))
{
   
lastfoucsin=$(this);//to avoid error

return;

}
 
//Do your stuff 
$(this).css('display','none');
});`enter code here`

Solution 12 - Javascript

The idea is to add fixed div to bottom. When virtual keyboard is shown/hidden scroll event occurs. Plus, we find out keyboard height

const keyboardAnchor = document.createElement('div')
keyboardAnchor.style.position = 'fixed'
keyboardAnchor.style.bottom = 0
keyboardAnchor.style.height = '1px'
document.body.append(keyboardAnchor)
        
window.addEventListener('scroll', ev => {
  console.log('keyboard height', window.innerHeight - keyboardAnchor.getBoundingClientRect().bottom)
}, true)

        
        

Solution 13 - Javascript

This solution remembers the scroll position

	var currentscroll = 0;

	$('input').bind('focus',function() {
		currentscroll = $(window).scrollTop();
	});
	
	$('input').bind('blur',function() {
		if(currentscroll != $(window).scrollTop()){
		
		$(window).scrollTop(currentscroll);
			
		}
	});

Solution 14 - Javascript

The problem is that, even in 2014, devices handle screen resize events, as well as scroll events, inconsistently while the soft keyboard is open.

I've found that, even if you're using a bluetooth keyboard, iOS in particular triggers some strange layout bugs; so instead of detecting a soft keyboard, I've just had to target devices that are very narrow and have touchscreens.

I use media queries (or window.matchMedia) for width detection and Modernizr for touch event detection.

Solution 15 - Javascript

As noted in the previous answers somewhere the window.innerHeight variable gets updated properly now on iOS10 when the keyboard appears and since I don't need the support for earlier versions I came up with the following hack that might be a bit easier then the discussed "solutions".

//keep track of the "expected" height
var windowExpectedSize = window.innerHeight;

//update expected height on orientation change
window.addEventListener('orientationchange', function(){
    //in case the virtual keyboard is open we close it first by removing focus from the input elements to get the proper "expected" size
	if (window.innerHeight != windowExpectedSize){
		$("input").blur();
		$("div[contentEditable]").blur();     //you might need to add more editables here or you can focus something else and blur it to be sure
		setTimeout(function(){
			windowExpectedSize = window.innerHeight;
		},100);
	}else{
		windowExpectedSize = window.innerHeight;
	}
});

//and update the "expected" height on screen resize - funny thing is that this is still not triggered on iOS when the keyboard appears
window.addEventListener('resize', function(){
    $("input").blur();  //as before you can add more blurs here or focus-blur something
	windowExpectedSize = window.innerHeight;
});

then you can use:

if (window.innerHeight != windowExpectedSize){ ... }

to check if the keyboard is visible. I've been using it for a while now in my web app and it works well, but (as all of the other solutions) you might find a situation where it fails because the "expected" size is not updated properly or something.

Solution 16 - Javascript

Perhaps it's easier to have a checkbox in your app's settings where the user can toggle 'external keyboard attached?'.

In small print, explain to the user that external keyboards are currently not detectable in today's browsers.

Solution 17 - Javascript

I haven't attempted this myself, so its just an idea... but have you tried using media queries with CSS to see when the height of the window changes and then change the design for that? I would imagine that Safari mobile isn't recognizing the keyboard as part of the window so that would hopefully work.

Example:

@media all and (height: 200px){
    #content {height: 100px; overflow: hidden;}
}

Solution 18 - Javascript

I did some searching, and I couldn't find anything concrete for a "on keyboard shown" or "on keyboard dismissed". See the official list of supported events. Also see Technical Note TN2262 for iPad. As you probably already know, there is a body event onorientationchange you can wire up to detect landscape/portrait.

Similarly, but a wild guess... have you tried detecting resize? Viewport changes may trigger that event indirectly from the keyboard being shown / hidden.

window.addEventListener('resize', function() { alert(window.innerHeight); });

Which would simply alert the new height on any resize event....

Solution 19 - Javascript

Well, you can detect when your input boxes have the focus, and you know the height of the keyboard. There is also CSS available to get the orientation of the screen, so I think you can hack it.

You would want to handle the case of a physical keyboard somehow, though.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLKMView Question on Stackoverflow
Solution 1 - JavascriptLKMView Answer on Stackoverflow
Solution 2 - JavascriptPer Quested AronssonView Answer on Stackoverflow
Solution 3 - JavascriptianhView Answer on Stackoverflow
Solution 4 - JavascriptMicheleView Answer on Stackoverflow
Solution 5 - JavascriptrobocatView Answer on Stackoverflow
Solution 6 - JavascriptHafthorView Answer on Stackoverflow
Solution 7 - Javascriptuser1650613View Answer on Stackoverflow
Solution 8 - JavascriptK.AView Answer on Stackoverflow
Solution 9 - JavascriptAdam GrantView Answer on Stackoverflow
Solution 10 - Javascriptdavid_adlerView Answer on Stackoverflow
Solution 11 - JavascriptNalini AmirView Answer on Stackoverflow
Solution 12 - JavascriptRuce BeeView Answer on Stackoverflow
Solution 13 - JavascriptWebsterDevelopineView Answer on Stackoverflow
Solution 14 - JavascriptpixelbanditoView Answer on Stackoverflow
Solution 15 - JavascriptFlowView Answer on Stackoverflow
Solution 16 - JavascriptIan WhiteView Answer on Stackoverflow
Solution 17 - JavascriptJanaeView Answer on Stackoverflow
Solution 18 - JavascriptslfView Answer on Stackoverflow
Solution 19 - JavascriptstuntmouseView Answer on Stackoverflow