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

18

169

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.

Roanne answered 7/4, 2010 at 14:12 Comment(5)
Hm, interesting problem. Try iterating over "window"'s objects on iPad's Safari to see if there are any special objects related to keyboard support.Shrubbery
@David that won't work, the keyboard is not a Javascript "window".Polyandry
@KennyTM. Duh. But there may be a flag related to the on-screen keyboard display in any of the window's objects. It is worth a shot.Shrubbery
I tried that. Didn't find anything, unfortunately. Also compared all of the window properties three levels deep before and after showing the keyboard. None of the differences seemed relevant as indicators for the keyboard.Roanne
Is there a newer answer for this??Krieg
R
56

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.

Roanne answered 8/4, 2010 at 15:16 Comment(10)
i'm having an issue with my web app where when the input is focused on, the screen scrolls up a bit. I've otherwise disabled scrolling, but still this scrolls. Any ideas? Thanks [#6740753Millibar
I haven't tried this yet, but it looks promising. Wouldn't .scrollTop(1) work just as well and be less obvious?Mobster
Yes, scrollTop(1) should work as well; any value above 0 should work.Roanne
this might be a good approach for input boxes, problem is that afaik this will not work on <SELECT> elementsWept
This is bad idea... Keyboard might be bluetooth and virtual might not be displayed.Sundown
Can someone give a clear explanation of why this works? The explanation in the answer is confusing. The code seems suggests that on a desktop browser one cannot set $(window).scrollTop. Why is that?Krieg
@theSociableme: The whole point of this solution is to handle the bluetooth keyboard properly. If you ignored bluetooth keyboards, figuring out whether the virtual keyboard was shown would be easy, since you could just check if a field had focus.Roanne
@fraxture: I don't think it's confusing — it doesn't address desktop browsers at all. Maybe you're missing what exact problem this solves? It has nothing to do with desktop browsers. It figures out whether the keyboard is visible on an iPad. It works because when the keyboard is visible on a full-screen web app on iOS, you can scroll, even though the page is exactly as tall as the screen. This is due to the odd way in which iOS handles onscreen keyboards.Roanne
@Roanne thanks! I wasn't confused about the problem, but rather the explanation of why this works, but what you said at the end there gets me part of the way to understanding. Do you know of any good comprehensive explanation of the way iOS handles onscreen keyboards? Better yet, do you know of one that compares it against Android? Thanks again.Krieg
@fraxture: Don't know of a comprehensive explanation (if you research and write one, I'd love to read it). The two platforms handle onscreen keyboards in their main browsers very differently. Android Chrome shrinks the viewport height to make room for the keyboard, so the page resizes when the keyboard is shown. iOS Safari overlays the page with the keyboard (page size stays the same), and changes how scrolling works. Safari both scrolls the page inside the viewport, and concurrently moves the viewport, ensuring that the bottom of the page is above the keyboard when scrolled all the way down.Roanne
K
36

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.

You can use e.target to check the originating element.

Katekatee answered 19/10, 2013 at 8:27 Comment(4)
best fix I found for my problemCyclo
This often doesn't work on the Chrome 41/iOS 6 emulator on CrossBrowserTesting.Time
Also you can use, 'focusin' version for detecting keyboard open.Argument
No, unfortunately this does not address the original problem, since focusout is triggered upon loss of focus due to any cause. Thus, it does not help in determining whether the virtual keyboard was ever open or if an external keyboard was used and the field had focus with no virtual keyboard in use.Precession
S
14

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).

Sattler answered 8/4, 2010 at 9:31 Comment(1)
That's an ingenious idea. I found a similar solution that also uses the current scroll position to detect the virtual keyboard.Roanne
S
14

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

Shelah answered 16/10, 2010 at 17:38 Comment(1)
Thanks for this answer. I found it useful to resolve a problem where the iPad Safari keyboard caused the textarea cursor to be displaced (offset) outside the textarea.Hayfield
A
13

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 https://mcmap.net/q/143483/-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 shouldnt make viewport smaller; old IE browsers won't work, Opera didnt work because Opera kept focus on element after keyboard closed.

However the tagged answer (changing scrolltop to measure height) has nasty UI side effects if viewport zoomable (or force-zoom enabled in preferences). I don't use the other suggested solution (changing scrolltop) because on iOS, when viewport is zoomable and scrolling to focused input, there are buggy interactions between scrolling & zoom & focus (that can leave a just focused input outside of viewport - not visible).

Allelomorph answered 7/6, 2012 at 5:2 Comment(1)
Depending on the browsers innerHeight to detect fullscreen breaks when some elements are positioned absolutely. Not reliable at all.Mycetozoan
T
13

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);
Talyah answered 24/5, 2021 at 8:26 Comment(1)
This is is one of the 2 best approaches today. window.visualViewport (if present) fires resize events when browser chrome hides/shows, and when the on-screen keyboard shows/hides, on both Android and iOS. It also fires when other events occur, like browser resizing and pinch zoom. The other good option is web.dev/virtualkeyboard, but it's Android/Chrome-only.Ides
C
11

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.

Carniola answered 11/7, 2013 at 22:51 Comment(3)
I just tried this in iOS 8.2 and it doesn't work ... did it stop working at some stage for new iOS?Rhaetic
Haven't worked for me either - resize is not fired in iOS9.3Esp
The virtualKeyboardHeight function helped me avoid scrolling away from a searchfield which goes fullscreen on mobile devices when typed. It was always pushed out of the screen on iOS by the keyboard when the input field was within the lower 60% of the screen. Other scroll functions I tried didn't help at all.Justiceship
E
5

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.

Extrorse answered 6/9, 2012 at 1:49 Comment(3)
+1 for blur event not firing on manually dismissing the keyboard. Resize is a good idea and it would work well for Android devices.Rudimentary
Can confirm that this works on both an iPhone 5 (iOS 6.0.2) and an iPad 3 (iOS 6.0).Wagtail
Just tested on Chrome 41 on iOS6 on CrossBrowserTesting - resize is not triggered by the virtual keyboard appearing or disappearing.Time
L
3

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()));
    }   
});     
Luculent answered 10/9, 2014 at 13:52 Comment(3)
That doesn't seem to work anymore in iOS 8. The keyboard overlays the content and in many cases the content scrolls down obscuring initially focused input fields.Isallobar
window height returns the height including the keyboard since iOS 7, in IOS6 window.height does change when the keyboard opens.Thirzi
Note that height is also changing when the top address bar slides in and out of the screen when scrolling. You should add a min height change of, I'd say, 200px (not tested).Wicklow
E
1

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`
Eximious answered 28/8, 2014 at 9:31 Comment(0)
A
1

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)

        
        
Amazement answered 23/12, 2020 at 8:12 Comment(0)
C
0

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);

        }
    });
Crosspollination answered 15/5, 2013 at 5:7 Comment(0)
P
0

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.

Plastid answered 5/8, 2014 at 16:36 Comment(0)
A
0

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.

Aerugo answered 17/4, 2017 at 20:36 Comment(3)
I was hoping this was the case, but no, it does not get updated.Frangible
In iOS 14, window.innerHeight seems to be unaffected by virtual keyboard state, unfortunately.Precession
Since ResizeObserver is supported on all relevant browsers now this might be the way to go in 2022.Aerugo
T
0

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.

Topknot answered 13/12, 2017 at 5:43 Comment(1)
Adding a toggle such as this is a last resort that should not be considered acceptable at all unless there is no other solution that will not break the app. This is not something that should be a blocker to producing a functioning app.Bifocal
S
-1

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;}
}
Seamanship answered 7/4, 2010 at 18:36 Comment(2)
Very clever idea. Unfortunately, in my tests, showing the keyboard did not affect the height values used to evaluate media queries.Roanne
I can confirm: height: 250px worked for me (on Android, at least).Textbook
D
-1

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....

Dori answered 8/4, 2010 at 14:24 Comment(1)
Unfortunately, in my tests, the keyboard did not trigger the resize event.Roanne
D
-2

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.

Dichotomy answered 8/4, 2010 at 11:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.