web-dev-qa-db-fra.com

iOS 5 positionnement fixe et clavier virtuel

J'ai un site Web mobile qui a un div épinglé au bas de l'écran via position: fixed. Tout fonctionne correctement dans iOS 5 (je teste sur un iPod Touch) jusqu'à ce que je sois sur une page avec un formulaire. Lorsque je tape dans un champ de saisie et que le clavier virtuel apparaît, la position fixe de ma div est soudain perdue. La div défile maintenant avec la page tant que le clavier est visible. Une fois que je clique sur Terminé pour fermer le clavier, le div revient à sa position en bas de l'écran et obéit à la règle: règle fixe.

Quelqu'un d'autre a-t-il déjà vécu ce genre de comportement? Est-ce prévu? Merci.

135
jeffc

J'ai eu ce problème dans mon application. Voici comment je travaille autour de ça:

input.on('focus', function(){
    header.css({position:'absolute'});
});
input.on('blur', function(){
    header.css({position:'fixed'});
});

Je me contente de faire défiler l'écran vers le haut et de le positionner à cet endroit afin que l'utilisateur iOS ne remarque rien d'étrange. Enveloppez ceci dans la détection de certains agents utilisateurs afin que les autres utilisateurs ne subissent pas ce comportement.

48
Nick Retallack

J'ai eu un problème légèrement différent ipad où le clavier virtuel a poussé ma fenêtre d'affichage vers le haut hors écran. Puis, après la fermeture du clavier virtuel par l’utilisateur, ma fenêtre d’affichage était toujours hors écran. Dans mon cas, j'ai fait quelque chose comme ceci:

var el = document.getElementById('someInputElement');
function blurInput() {
    window.scrollTo(0, 0);
}
el.addEventListener('blur', blurInput, false);
15
ds111

C'est le code que nous utilisons pour résoudre le problème avec ipad. Il détecte fondamentalement les écarts entre la position de décalage et de défilement, ce qui signifie que "fixe" ne fonctionne pas correctement.

$(window).bind('scroll', function () {
    var $nav = $(".navbar")
    var scrollTop = $(window).scrollTop();
    var offsetTop = $nav.offset().top;

    if (Math.abs(scrollTop - offsetTop) > 1) {
        $nav.css('position', 'absolute');
        setTimeout(function(){
            $nav.css('position', 'fixed');
        }, 1);
    }
});
14
Hatch

Les éléments fixes de position ne mettent simplement pas à jour leur position lorsque le clavier est en position haute. J'ai trouvé qu'en incitant Safari à penser que la page avait été redimensionnée, les éléments se repositionneraient. Ce n'est pas parfait, mais au moins vous n'avez pas à vous soucier de passer à la «position: absolue» et de suivre les changements vous-même.

Le code suivant n'écoute que les cas où l'utilisateur est susceptible d'utiliser le clavier (en raison de la focalisation d'une entrée), et jusqu'à ce qu'il entende un flou, il ne fait qu'écouter les événements de défilement, puis effectue l'opération de redimensionnement. Semble fonctionner assez bien pour moi jusqu'à présent.

    var needsScrollUpdate = false;
    $(document).scroll(function(){
        if(needsScrollUpdate) {
            setTimeout(function() {
                $("body").css("height", "+=1").css("height", "-=1");
            }, 0);
        }
    });
    $("input, textarea").live("focus", function(e) {
        needsScrollUpdate = true;
    });

    $("input, textarea").live("blur", function(e) {
        needsScrollUpdate = false;
    });
12
Riley Dutton

Juste au cas où quelqu'un tomberait sur ce fil comme je l'ai fait lors de mes recherches. J'ai trouvé ce fil utile pour stimuler ma réflexion sur cette question.

C'était ma solution pour cela sur un projet récent. Il vous suffit de changer la valeur de "targetElem" en un sélecteur jQuery qui représente votre en-tête.

if(navigator.userAgent.match(/iPad/i) != null){

var iOSKeyboardFix = {
      targetElem: $('#fooSelector'),
      init: (function(){
        $("input, textarea").on("focus", function() {
          iOSKeyboardFix.bind();
        });
      })(),

      bind: function(){
            $(document).on('scroll', iOSKeyboardFix.react);  
                 iOSKeyboardFix.react();      
      },

      react: function(){

              var offsetX  = iOSKeyboardFix.targetElem.offset().top;
              var scrollX = $(window).scrollTop();
              var changeX = offsetX - scrollX; 

              iOSKeyboardFix.targetElem.css({'position': 'fixed', 'top' : '-'+changeX+'px'});

              $('input, textarea').on('blur', iOSKeyboardFix.undo);

              $(document).on('touchstart', iOSKeyboardFix.undo);
      },

      undo: function(){

          iOSKeyboardFix.targetElem.removeAttr('style');
          document.activeElement.blur();
          $(document).off('scroll',iOSKeyboardFix.react);
          $(document).off('touchstart', iOSKeyboardFix.undo);
          $('input, textarea').off('blur', iOSKeyboardFix.undo);
      }
};

};

Il y a un peu de retard dans la mise en place du correctif, car iOS arrête la manipulation du DOM pendant le défilement, mais cela fait l'affaire ...

6
Jory Cunningham

Cette question est vraiment ennuyeuse.

J'ai combiné certaines des techniques mentionnées ci-dessus et suis venu avec ceci:

$(document).on('focus', 'input, textarea', function() {
    $('.YOUR-FIXED-DIV').css('position', 'static');
});

$(document).on('blur', 'input, textarea', function() {
    setTimeout(function() {
        $('.YOUR-FIXED-DIV').css('position', 'fixed');
        $('body').css('height', '+=1').css('height', '-=1');
    }, 100);
});

J'ai deux barres de navigation fixes (en-tête et pied de page, en utilisant Twitter Bootstrap) . Les deux ont agi bizarrement quand le clavier est en haut et bizarre à nouveau quand le clavier est en bas.

Avec ce correctif chronométré/différé, cela fonctionne. Je trouve toujours un petit problème de temps en temps, mais il semble être suffisant pour le montrer au client.

Faites-moi savoir si cela fonctionne pour vous. Sinon, nous pourrions trouver autre chose. Merci.

4
escapedcat

Aucune des autres réponses que j'ai trouvées pour ce bogue n'a fonctionné pour moi. J'ai pu résoudre le problème simplement en faisant défiler la page vers le haut de 34 pixels, le montant de safari mobile le faisant défiler. avec jquery:

$('.search-form').on('focusin', function(){
    $(window).scrollTop($(window).scrollTop() + 34);
});

Cela prendra évidemment effet dans tous les navigateurs, mais cela l’empêchera de se briser sous iOS.

4
NealJMD

J'ai pris la réponse Jory Cunningham et je l'ai améliorée:

Dans de nombreux cas, ce n'est pas seulement un élément qui devient fou, mais plusieurs éléments positionnés de manière fixe. Dans ce cas, targetElem devrait être un objet jQuery contenant tous les éléments fixes que vous souhaitez "corriger". Ho, cela semble faire disparaître le clavier iOS si vous faites défiler ...

Inutile de mentionner que vous devriez utiliser cet événementAPR&EGRAVE;Sdocument DOM ready ou juste avant la balise de fermeture </body>.

(function(){
    var targetElem = $('.fixedElement'), // or more than one
        $doc       = $(document),
        offsetY, scrollY, changeY;

    if( !targetElem.length || !navigator.userAgent.match(/iPhone|iPad|iPod/i) )
        return;

    $doc.on('focus.iOSKeyboardFix', 'input, textarea, [contenteditable]', bind);

    function bind(){
        $(window).on('scroll.iOSKeyboardFix', react);
        react();
    }

    function react(){
        offsetY = targetElem.offset().top;
        scrollY = $(window).scrollTop();
        changeY = offsetY - scrollY;

        targetElem.css({'top':'-'+ changeY +'px'});

        // Instead of the above, I personally just do:
        // targetElem.css('opacity', 0);

        $doc.on('blur.iOSKeyboardFix', 'input, textarea, [contenteditable]', unbind)
            .on('touchend.iOSKeyboardFix', unbind);
    }

    function unbind(){
        targetElem.removeAttr('style');
        document.activeElement.blur();

        $(window).off('scroll.iOSKeyboardFix');
        $doc.off('touchend.iOSKeyboardFix blur.iOSKeyboardFix');
    }
})();
3
vsync

J'avais le même problème avec iOS7. Les éléments fixes du bas gâcheraient mon point de vue si la vue était floue.

Tout a commencé à fonctionner quand j'ai ajouté cette balise méta à mon HTML.

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,height=device-height" >

La partie qui a fait la différence était:

height=device-height

J'espère que ça aide quelqu'un.

3
pasevin

J'ai une solution similaire à @NealJMD, sauf que la mienne ne s'exécute que pour iOS et détermine correctement le décalage de défilement en mesurant le scollTop avant et après le défilement du clavier natif et en utilisant setTimeout pour permettre le défilement natif

var $window = $(window);
var initialScroll = $window.scrollTop();
if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
  setTimeout(function () {
    $window.scrollTop($window.scrollTop() + (initialScroll - $window.scrollTop()));
  }, 0);
}
2
ChrisWren

Oui, il semble que Apple n'ait pas si bien pensé à cela pour IOS5. Tous les éléments à position fixe deviennent relatifs à la page dès que le clavier virtuel apparaît. Ce serait probablement bien si les éléments revenaient à une position absolue car cela ne casserait pas la mise en page. Malheureusement, l'emplacement de ces éléments est beaucoup moins prévisible. 

J'ai ce problème exact avec mon en-tête fixe sur [SUPPRIMÉ]. Faites défiler la page, puis cliquez sur la case de recherche et bang ... layout mis en page. J'ai même essayé de le réparer en revenant au positionnement absolu sur l'événement focus, ce qui fonctionne mais je perds le focus (le clavier reste ouvert mais le curseur n'est plus dans la zone de recherche). 

Quoi qu'il en soit, je travaille dessus alors je vous ferai savoir si je le trie

À votre santé

2
John williams

Travaille pour moi 

if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
    $(document).on('focus', 'input, textarea', function() {
        $('header').css({'position':'static'});
    });
    $(document).on('blur', 'input, textarea', function() {
        $('header').css({'position':'fixed'});
    });
}
1
ShibinRagh

Ma réponse est que cela ne peut pas être fait.

Je vois 25 réponses mais aucune ne fonctionne dans mon cas. C'est pourquoi Yahoo et d'autres pages cachent l'en-tête fixe lorsque le clavier est activé. Et Bing désactive la totalité de la page (débordement-y: masqué).

Les cas décrits ci-dessus sont différents, certains ont des problèmes lors du défilement, d'autres sont flous ou ciblés. Certains ont un pied de page fixe ou un en-tête. Je ne peux pas tester maintenant chaque combinaison, mais vous pourriez vous rendre compte que cela ne peut pas être fait dans votre cas.

1
Szalai Laci

J'ai eu un problème similaire à @ ds111 s. Mon site Web a été poussé par le clavier mais ne s'est pas abaissé lorsque le clavier s'est fermé.

J'ai d'abord essayé la solution @ ds111 mais j'avais deux champs input. Bien sûr, d'abord le clavier disparaît, puis le flou se produit (ou quelque chose du genre). La seconde input était donc sous le clavier, lorsque le focus passait directement d’une entrée à l’autre.

De plus, le "saut" ne me convenait pas, car la page entière n'a que la taille de l'ipad. Alors j'ai fait le rouleau lisse.

Enfin, je devais attacher l’auditeur d’événement à toutes les entrées, même celles qui étaient actuellement masquées, d’où la variable live.

Dans l'ensemble, je peux expliquer l'extrait de code javascript suivant comme suit: Attachez l'écouteur d'événements de flou suivant au input et au textarea actuels et futurs (= live): Attendez un délai de grâce (= window.setTimeout(..., 10)) et faites défiler en douceur le début (= animate({scrollTop: 0}, ...)). ) mais seulement si "aucun clavier n’est affiché" (= if($('input:focus, textarea:focus').length == 0)).

$('input, textarea').live('blur', function(event) {
    window.setTimeout(function() {
        if($('input:focus, textarea:focus').length == 0) {
            $("html, body").animate({ scrollTop: 0 }, 400);
        }
    }, 10)
})

Sachez que le délai de grâce (= 10) est peut-être trop court ou que le clavier est toujours affiché, même si aucune variable input ou textarea n'est active. Bien sûr, si vous voulez que le défilement soit plus rapide ou plus lent, vous pouvez ajuster la durée (= 400)

1
Raul Pinto

C'est un problème difficile à résoudre. Vous pouvez essayer de masquer le pied de page sur le focus d'élément d'entrée et d'afficher le flou, mais cela n'est pas toujours fiable sur iOS. De temps en temps (une fois sur dix, par exemple, sur mon iPhone 4S), l'événement de focus semble ne pas se déclencher (ou peut-être qu'il y a une condition de concurrence) et le pied de page ne reste pas caché.

Après de nombreux essais et erreurs, j'ai proposé cette solution intéressante:

<head>
    ...various JS and CSS imports...
    <script type="text/javascript">
        document.write( '<style>#footer{visibility:hidden}@media(min-height:' + ($( window ).height() - 10) + 'px){#footer{visibility:visible}}</style>' );
    </script>
</head>

Pour l'essentiel: utilisez JavaScript pour déterminer la hauteur de la fenêtre du périphérique, puis créez de manière dynamique une requête multimédia CSS pour masquer le pied de page lorsque la hauteur de la fenêtre est réduite de 10 pixels. L'ouverture du clavier redimensionnant l'affichage du navigateur, cela n'échoue jamais sur iOS. Comme il utilise le moteur CSS plutôt que JavaScript, il est également beaucoup plus rapide et plus fluide!

Remarque: j'ai trouvé l'utilisation de 'visibilité: caché' moins glitchy que 'display: none' ou 'position: static', mais votre kilométrage peut varier.

1
Richard Kennard

J'ai corrigé la position fixe du contenu de ma mise en page principale Ipad de cette façon:

var mainHeight;
var main = $('.main');

// hack to detects the virtual keyboard close action and fix the layout bug of fixed elements not being re-flowed
function mainHeightChanged() {
    $('body').scrollTop(0);
}

window.setInterval(function () {
    if (mainHeight !== main.height())mainHeightChanged();
    mainHeight = main.height();
}, 100);
1
Brett

Dans notre cas, cela se corrigerait dès que l'utilisateur ferait défiler. C'est donc le correctif que nous avons utilisé pour simuler un parchemin sur blur sur toute input ou textarea

$(document).on('blur', 'input, textarea', function () {
    setTimeout(function () {
        window.scrollTo(document.body.scrollLeft, document.body.scrollTop);
    }, 0);
});
1
basarat

vraiment travaillé dur pour trouver cette solution de contournement qui, en bref, recherche les événements de focus et de flou sur les entrées et le défilement pour modifier de manière sélective le positionnement de la barre fixe lorsque les événements se produisent. Ceci est à l'épreuve des balles et couvre tous les cas (navigation avec <>, bouton de défilement, terminé). Remarque id = "nav" est mon div de pied de page fixe. Vous pouvez facilement le porter en js standard ou jquery. C'est un dojo pour ceux qui utilisent des outils électriques ;-)

define ([ "dojo/ready", "dojo/query", ], fonction (ready, requête) {

ready(function(){

    /* This addresses the dreaded "fixed footer floating when focusing inputs and keybard is shown" on iphone 
     * 
     */
    if(navigator.userAgent.match(/iPhone/i)){
        var allInputs = query('input,textarea,select');
        var d = document, navEl = "nav";
        allInputs.on('focus', function(el){
             d.getElementById(navEl).style.position = "static";
        });

        var fixFooter = function(){
            if(d.activeElement.tagName == "BODY"){
                d.getElementById(navEl).style.position = "fixed";
            }
        };
        allInputs.on('blur', fixFooter);
        var b = d.body;
        b.addEventListener("touchend", fixFooter );
    }

});

}); // fin définir

1
httpete

iOS9 - même problème.

TLDR - source du problème. Pour la solution, faites défiler vers le bas

J'ai eu un formulaire dans un position:fixed iframe avec id = 'subscribe-popup-frame'

Conformément à la question initiale, dans le champ de saisie, l'iframe irait en haut du document, par opposition au haut de l'écran.

Le même problème ne s'est pas produit en mode safari dev avec l'agent utilisateur défini sur un idevice. Il semble donc que le problème est causé par le clavier virtuel iOS lorsqu’il apparaît.

J'ai eu un peu de visibilité sur ce qui se passait en consignant la position de l'iframe sur la console (par exemple, $('#subscribe-popup-frame', window.parent.document).position()) et, de là, j'ai pu voir qu'IOS semblait régler la position de l'élément sur {top: -x, left: 0} lorsque le clavier virtuel est apparu (c'est-à-dire concentré sur l'élément d'entrée) .

Ma solution a donc été de prendre ce -x embêtant, d’inverser le signe, puis d’utiliser jQuery pour ajouter cette position top à l’iframe. S'il y a une meilleure solution, j'aimerais l'entendre, mais après avoir essayé une douzaine d'approches différentes, c'est la seule qui a fonctionné pour moi.

Inconvénient: Je devais définir un délai d'expiration de 500 ms (peut-être moins, mais je voulais être sûr) pour être sûr d'avoir capturé la valeur x finale après qu'iOS ait fait son tort avec la position de l'élément. En conséquence, l'expérience est très saccadée. . . mais au moins ça marche

Solution

        var mobileInputReposition = function(){
             //if statement is optional, I wanted to restrict this script to mobile devices where the problem arose
            if(screen.width < 769){
                setTimeout(function(){
                    var parentFrame = $('#subscribe-popup-frame',window.parent.document);
                    var parentFramePosFull = parentFrame.position();
                    var parentFramePosFlip = parentFramePosFull['top'] * -1;
                    parentFrame.css({'position' : 'fixed', 'top' : parentFramePosFlip + 'px'});
                },500);
            }    
        }   

Ensuite, appelez simplement mobileInputReposition dans quelque chose comme $('your-input-field).focus(function(){}) et $('your-input-field).blur(function(){})

0
martellalex

Ceci est toujours un gros bogue pour toutes les pages HTML avec de plus grands Modules Bootstrap dans iOS 8.3. Aucune des solutions proposées ci-dessus n’a fonctionné. Après avoir zoomé sur un champ situé sous le pli d’un grand modal, Mobile Safari et/ou WkWebView déplaceraient les éléments fixes à l’emplacement du défilement du corps HTML, les laissant mal alignés avec ceux où ils effectivement où aménagé.

Pour contourner le bogue, ajoutez un écouteur d'événement à l'une de vos entrées modales, par exemple:

$(select.modal).blur(function(){
  $('body').scrollTop(0);
});

Je suppose que cela fonctionne parce que le fait de forcer la hauteur de défilement du corps HTML ré-aligne la vue réelle avec celle attendue par iOS 8 WebView quant au contenu du div modal fixé.

0

Si quelqu'un recherchait un itinéraire complètement différent (comme si vous ne cherchiez même pas à épingler ce div "pied de page" lorsque vous faites défiler l'écran mais que vous souhaitiez simplement que le div reste au bas de la page), vous pouvez simplement définir la position du pied relatif. 

Cela signifie que même si le clavier virtuel apparaît sur votre navigateur mobile, votre pied de page restera simplement ancré au bas de la page, sans chercher à réagir à l'affichage ou à la fermeture du clavier virtuel. 

Il est évident que, si Safari fixe la position et que le pied de page suit la page lorsque vous faites défiler l'écran vers le haut ou vers le bas, cela semble mieux, mais en raison de ce bogue étrange sur Chrome, nous avons fini par changer le pied de page. 

0
Terry Bu

Au cas où quelqu'un voudrait essayer. J'ai le travail suivant pour moi sur un pied de page fixe avec un champ d'entrée.

<script>
    $('document').ready(
        function() {
            if (navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/webOS/i) || navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i)
                  || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/Windows Phone/i)) {
                var windowHeight = $(window).height();
                var documentHeight = $(document).height();

                $('#notes').live('focus', function() {
                    if (documentHeight > windowHeight) {
                        $('#controlsContainer').css({
                            position : 'absolute'
                        });
                        $("html, body").animate({
                            scrollTop : $(document).height()
                        }, 1);
                    }
                });
                $('#notes').live('blur', function() {
                    $('#controlsContainer').css({
                        position : 'fixed'
                    });
                    $("html, body").animate({
                        scrollTop : 0
                    }, 1);
                });
            }
        });
</script>
0
Bernard

Trouvé cette solution sur Github.

https://github.com/Simbul/baker/issues/504#issuecomment-12821392

Assurez-vous que vous avez un contenu défilable.

// put in your .js file
$(window).load(function(){
    window.scrollTo(0, 1);
});

// min-height set for scrollable content
<div id="wrap" style="min-height: 480px">
  // website goes here
</div>

La barre d'adresse se replie comme un bonus supplémentaire.

0
Leigh Mackay

Aucune des solutions de défilement ne semblait fonctionner pour moi. Au lieu de cela, ce qui a fonctionné est de définir la position du corps sur fixe lorsque l'utilisateur modifie du texte, puis de le restaurer en statique lorsque l'utilisateur a terminé. Cela empêche Safari de faire défiler votre contenu sur vous. Vous pouvez le faire soit sur le focus/flou du ou des éléments (indiqué ci-dessous pour un élément unique mais pour toutes les entrées, zones de texte), ou si un utilisateur fait quelque chose pour commencer à éditer, comme ouvrir un modal, sur cette action (par exemple, ouverture/fermeture modale).

$("#myInput").on("focus", function () {
    $("body").css("position", "fixed");
});

$("#myInput").on("blur", function () {
    $("body").css("position", "static");
});
0
Scott Semyan

J'ai le même problème. Mais j'ai réalisé que la position fixe est juste retardée et non cassée (du moins pour moi). Attendez 5 à 10 secondes et voyez si la div s’ajuste au bas de l’écran. Je crois que ce n'est pas une erreur mais une réponse retardée lorsque le clavier est ouvert.

0
Philip

J'ai essayé toutes les approches à partir de ce fil, mais si elles ne nous ont pas aidées, elles ont fait encore plus mal… .. À la fin, j'ai décidé de forcer le périphérique à perdre le focus:

$(<selector to your input field>).focus(function(){
    var $this = $(this);
    if (<user agent target check>) {
        function removeFocus () {
            $(<selector to some different interactive element>).focus();
            $(window).off('resize', removeFocus);
        }
        $(window).on('resize', removeFocus);
    }
});

et cela a fonctionné comme un charme et a corrigé mon formulaire de connexion collant.

S'il vous plaît NOTE: 

  1. Le code JS ci-dessus n'a pour but que de présenter mon idée. Pour exécuter cet extrait, veuillez remplacer les valeurs entre accolades angulaires (<>) par des valeurs adaptées à votre situation. 
  2. Ce code est conçu pour fonctionner avec jQuery v1.10.2
0
Anton Boritskiy