iPad Web App: Detectar teclado virtual usando JavaScript no Safari?

147

Estou escrevendo um aplicativo Web para o iPad ( não um aplicativo normal da App Store - ele é escrito usando HTML, CSS e JavaScript). Como o teclado preenche uma grande parte da tela, faria sentido alterar o layout do aplicativo para caber no espaço restante quando o teclado é exibido. No entanto, não encontrei nenhuma maneira de detectar quando ou se o teclado é mostrado.

Minha primeira idéia foi assumir que o teclado está visível quando um campo de texto está em foco. No entanto, quando um teclado externo é conectado a um iPad, o teclado virtual não aparece quando um campo de texto recebe foco.

Nas minhas experiências, o teclado também não afetou a altura ou a altura de rolagem de nenhum dos elementos DOM, e não encontrei eventos ou propriedades proprietários que indiquem se o teclado está visível.

LKM
fonte
1
Hum, problema interessante. Tente iterar sobre os objetos da "janela" no Safari do iPad para ver se há algum objeto especial relacionado ao suporte do teclado.
David Murdoch
@ David que não funcionará, o teclado não é uma "janela" Javascript.
Kennytm
2
@KennyTM. Duh. Mas pode haver um sinalizador relacionado à exibição do teclado na tela em qualquer um dos objetos da janela. Vale a pena tentar.
David Murdoch
1
Eu tentei isso. Infelizmente não encontrou nada. Também comparou todas as propriedades da janela com três níveis de profundidade antes e depois de mostrar o teclado. Nenhuma das diferenças parecia relevante como indicadores para o teclado.
LKM
3
Existe uma resposta mais recente para isso?
fraxture

Respostas:

54

Eu encontrei uma solução que funciona, embora seja um pouco feia. Também não funciona em todas as situações, mas funciona para mim. Como estou adaptando o tamanho da interface do usuário ao tamanho da janela do iPad, o usuário normalmente não pode rolar. Em outras palavras, se eu definir o scrollTop da janela, ele permanecerá em 0.

Se, por outro lado, o teclado for exibido, a rolagem funcionará repentinamente. Para que eu possa definir o scrollTop, testar imediatamente seu valor e redefini-lo. Aqui está como isso pode parecer no código, usando o 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 ');
    });
});

Normalmente, você espera que isso não seja visível para o usuário. Infelizmente, pelo menos ao rodar no Simulador, o iPad visualmente (embora rapidamente) rola para cima e para baixo novamente. Ainda assim, funciona, pelo menos em algumas situações específicas.

Eu testei isso em um iPad, e parece funcionar bem.

LKM
fonte
Estou tendo um problema com meu aplicativo da web em que, quando a entrada é focada, a tela rola um pouco. Caso contrário, desativei a rolagem, mas ainda assim ela rola. Alguma ideia? Obrigado [ stackoverflow.com/questions/6740253/…
Andrew Samuelsen
Ainda não tentei isso, mas parece promissor. Não .scrollTop(1)funcionaria tão bem e seria menos óbvio?
ThinkingStiff
1
Isso é uma péssima idéia ... O teclado pode ser bluetooth e o virtual pode não ser exibido.
theSociableme
3
@ theSociableme: O objetivo principal desta solução é lidar com o teclado bluetooth corretamente. Se você ignorasse os teclados bluetooth, descobrir se o teclado virtual seria exibido seria fácil, pois você poderia apenas verificar se um campo estava focado.
LKM
5
@fraxture: Não conheço uma explicação abrangente (se você pesquisar e escrever uma, eu adoraria lê-la). As duas plataformas lidam com teclados na tela em seus principais navegadores de maneira muito diferente. O Android Chrome reduz a altura da janela de exibição para liberar espaço para o teclado, para que a página seja redimensionada quando o teclado é mostrado. O Safari do iOS sobrepõe a página com o teclado (o tamanho da página permanece o mesmo) e altera o funcionamento da rolagem. O Safari rola a página dentro da viewport e move a viewport simultaneamente, garantindo que a parte inferior da página esteja acima do teclado quando rolada até o fim.
LKM
32

Você pode usar o evento de foco para detectar a dispensa do teclado. É como borrão, mas bolhas. Ele será acionado quando o teclado fechar (mas também em outros casos, é claro). No Safari e Chrome, o evento pode ser registrado apenas com addEventListener, não com métodos herdados. Aqui está um exemplo que eu usei para restaurar um aplicativo Phonegap após a desativação do teclado.

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

Sem esse snippet, o contêiner do aplicativo permaneceu na posição de rolagem para cima até a atualização da página.

Por Aronsson requerido
fonte
1
melhor solução que eu encontrei para o meu problema
Sutulustus
1
Além disso, você pode usar a versão 'focusin' para detectar a abertura do teclado.
A.Çetin 5/09/19
15

talvez uma solução um pouco melhor seja vincular (com o jQuery no meu caso) o evento "blur" nos vários campos de entrada.

Isso ocorre quando o teclado desaparece, todos os campos do formulário ficam desfocados. Então, para a minha situação, este recorte resolveu o problema.

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

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

});

espero que ajude. Michele

Michele
fonte
Obrigado por esta resposta. Achei útil resolver um problema em que o teclado Safari do iPad fazia com que o cursor da área de texto fosse deslocado (deslocamento) fora da área de texto.
Kennbrodhagen
14

Se houver um teclado na tela, focalizar um campo de texto próximo à parte inferior da janela de visualização fará com que o Safari role o campo de texto para exibição. Pode haver alguma maneira de explorar esse fenômeno para detectar a presença do teclado (com um pequeno campo de texto na parte inferior da página que ganha foco momentaneamente, ou algo assim).

ianh
fonte
1
Essa é uma ideia engenhosa. Encontrei uma solução semelhante que também usa a posição atual de rolagem para detectar o teclado virtual.
LKM
brilhante! você salvou meu dia!
31513 Aztack
11

Durante o evento de foco, você pode rolar a altura do documento e magicamente a window.innerHeight é reduzida pela altura do teclado virtual. Observe que o tamanho do teclado virtual é diferente para as orientações paisagem versus retrato, portanto, você precisará redetectá-lo quando ele mudar. Eu recomendaria não lembrar desses valores, pois o usuário pode conectar / desconectar um teclado bluetooth a qualquer momento.

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

Observe que quando o usuário está usando um teclado bluetooth, a altura do teclado é 44, que é a altura da barra de ferramentas [anterior] [próxima].

Há um pouquinho de cintilação quando você faz essa detecção, mas não parece possível evitá-la.

Hafthor
fonte
5
Eu apenas tentei isso no iOS 8.2 e ele não funciona ... parou de funcionar em algum momento para o novo iOS?
Ming
1
Não funcionaram para mim também - redimensionamento não é acionado no iOS9.3
llamerr
8

Edit: Documentado pela Apple, embora eu não tenha conseguido fazê-lo funcionar: Comportamento do WKWebView com monitores do teclado : "No iOS 10, os objetos WKWebView correspondem ao comportamento nativo do Safari, atualizando sua propriedade window.innerHeight quando o teclado é exibido e não chama redimensionar eventos "(talvez seja possível usar foco ou foco mais atraso para detectar o teclado em vez de usar redimensionar).

Editar: o código pressupõe teclado na tela, não teclado externo. Deixá-lo porque as informações podem ser úteis para outras pessoas que se preocupam apenas com os teclados na tela. Use http://jsbin.com/AbimiQup/4 para visualizar os parâmetros da página.

Testamos para ver se o document.activeElementé um elemento que mostra o teclado (tipo de entrada = texto, área de texto etc.).

O código a seguir modifica as coisas para nossos propósitos (embora não seja geralmente correto).

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

O código acima é apenas aproximado: está errado para teclado dividido, teclado desencaixado, teclado físico. De acordo com o comentário na parte superior, você poderá fazer um trabalho melhor que o código fornecido no Safari (desde iOS8?) Ou WKWebView (desde iOS10) usando a window.innerHeightpropriedade

Descobri falhas em outras circunstâncias: por exemplo, concentre-se na entrada, vá para a tela inicial e volte à página; O iPad não deve diminuir a viewport; Como os navegadores antigos do IE não funcionam, o Opera não funcionou porque o Opera manteve o foco no elemento após o teclado ser fechado.

No entanto, a resposta marcada (alterar a barra de rolagem para medir a altura) tem efeitos colaterais desagradáveis ​​na interface do usuário se a viewport for zoomável (ou forçado o zoom ativado nas preferências). Eu não uso a outra solução sugerida (alterando o scrolltop) porque no iOS, quando a viewport é com zoom e rolagem para entrada focada, há interações de buggy entre rolagem e zoom e foco (que podem deixar uma entrada focada apenas fora da viewport - não visível).

robocat
fonte
Dependendo dos navegadores innerHeight para detectar quebras na tela cheia quando alguns elementos estão posicionados absolutamente. Não é de todo confiável.
Udo
5

Testado apenas no Android 4.1.1:

O evento de desfoque não é confiável para testar o teclado para cima e para baixo porque o usuário é a opção de ocultar explicitamente o teclado, o que não aciona um evento de desfoque no campo que causou a exibição do teclado.

redimensionar evento, no entanto, funciona como um encanto se o teclado subir ou descer por qualquer motivo.

café:

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

é acionado sempre que o teclado é exibido ou oculto por qualquer motivo.

Observe, porém, que no caso de um navegador Android (em vez de aplicativo), existe uma barra de URL retrátil que não aciona o redimensionamento quando é recolhida, mas altera o tamanho da janela disponível.

user1650613
fonte
+1 para o evento de desfoque que não é acionado ao descartar manualmente o teclado. Redimensionar é uma boa idéia e funcionaria bem para dispositivos Android.
Ankit Garg
Pode confirmar que isso funciona tanto no iPhone 5 (iOS 6.0.2) quanto no iPad 3 (iOS 6.0).
Diego Agulló
1
Acabado de testar no Chrome 41 no iOS6 no CrossBrowserTesting - o redimensionamento não é acionado pelo teclado virtual que aparece ou desaparece.
Dan Dascalescu 15/07/2015
4

Em vez de detectar o teclado, tente detectar o tamanho da janela

Se a altura da janela foi reduzida e a largura ainda é a mesma, significa que o teclado está ligado. Caso contrário, o teclado está desligado, você também pode adicionar a isso, testar se algum campo de entrada está focado ou não.

Tente este código, por exemplo.

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()));
    }   
});     
KA
fonte
2
Isso parece não funcionar mais no iOS 8. O teclado sobrepõe o conteúdo e, em muitos casos, o conteúdo rola para baixo, obscurecendo os campos de entrada inicialmente focados.
Rick Strahl
3
window height retorna a altura incluindo o teclado desde o iOS 7, na janela IOS6. a altura muda quando o teclado é aberto.
315 Michiel
1
Observe que a altura também muda quando a barra de endereço superior entra e sai da tela ao rolar. Você deve adicionar uma alteração mínima de altura de, eu diria, 200px (não testado).
oriadam
1

Esta solução lembra a posição de rolagem

    var currentscroll = 0;

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

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

        $(window).scrollTop(currentscroll);

        }
    });
WebsterDevelopine
fonte
1

Tente este:

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`
Nalini Amir
fonte
1

Conforme observado nas respostas anteriores, a variável window.innerHeight é atualizada corretamente agora no iOS10 quando o teclado aparece e, como não preciso do suporte para versões anteriores, criei o seguinte hack que pode ser um pouco mais fácil do que o discutido "soluções".

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

então você pode usar:

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

para verificar se o teclado está visível. Eu já o uso há algum tempo no meu aplicativo Web e funciona bem, mas (como todas as outras soluções), você pode encontrar uma situação em que falha porque o tamanho "esperado" não é atualizado corretamente ou algo assim.

Fluxo
fonte
Eu esperava que esse fosse o caso, mas não, ele não é atualizado.
Sam Saffron
0

Pesquisei e não encontrei nada de concreto para um "teclado mostrado" ou "um teclado descartado". Veja a lista oficial de eventos suportados . Consulte também a Nota técnica TN2262 para iPad. Como você provavelmente já sabe, há um evento corporal que onorientationchangevocê pode conectar para detectar paisagem / retrato.

Da mesma forma, mas um palpite ... você já tentou detectar o redimensionamento? As alterações na janela de exibição podem acionar esse evento indiretamente do teclado sendo exibido / oculto.

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

O que simplesmente alertaria a nova altura em qualquer evento de redimensionamento ....

slf
fonte
10
Infelizmente, nos meus testes, o teclado não acionou o evento de redimensionamento.
LKM
0

Eu não tentei isso sozinho, então é apenas uma idéia ... mas você tentou usar consultas de mídia com CSS para ver quando a altura da janela muda e depois alterar o design para isso? Eu imagino que o Safari mobile não esteja reconhecendo o teclado como parte da janela, e espero que funcione.

Exemplo:

@media all and (height: 200px){
    #content {height: 100px; overflow: hidden;}
}
Janae
fonte
2
Idéia muito inteligente. Infelizmente, nos meus testes, mostrar o teclado não afetou os valores de altura usados ​​para avaliar as consultas de mídia.
LKM
Posso confirmar: height: 250px funcionou para mim (no Android, pelo menos).
WoodrowShigeru
0

O problema é que, mesmo em 2014, os dispositivos lidam com eventos de redimensionamento da tela, bem como eventos de rolagem, inconsistentemente enquanto o teclado virtual está aberto.

Descobri que, mesmo se você estiver usando um teclado bluetooth, o iOS em particular aciona alguns bugs de layout estranhos; então, em vez de detectar um teclado virtual, bastava segmentar dispositivos muito estreitos e com telas sensíveis ao toque.

Utilizo consultas de mídia (ou window.matchMedia ) para detecção de largura e Modernizr para detecção de eventos de toque.

pixelbandito
fonte
0

Talvez seja mais fácil ter uma caixa de seleção nas configurações do seu aplicativo, onde o usuário pode alternar 'teclado externo conectado?'.

Em letras pequenas, explique ao usuário que os teclados externos atualmente não são detectáveis ​​nos navegadores atuais.

Ian White
fonte
1
Adicionar uma alternância como essa é o último recurso que não deve ser considerado aceitável, a menos que não haja outra solução que não interrompa o aplicativo. Isso não é algo que deve ser um bloqueador para a produção de um aplicativo em funcionamento.
Adam Leggett
-2

Bem, você pode detectar quando suas caixas de entrada estão focadas e sabe a altura do teclado. Também há CSS disponível para obter a orientação da tela, então acho que você pode cortá-la.

Você gostaria de lidar com o caso de um teclado físico de alguma forma.

dublê
fonte