Safari dans ios8 est un écran de défilement lorsque les éléments fixes se concentrent

Dans IOS8 Safari, il y a un nouveau bug avec la position fixée.

Si vous concentrez une zone de texte qui se trouve dans un panneau fixe, le safari vous déplacera vers le bas de la page.

Cela rend impossible l'utilisation de toutes sortes d'UI, car vous n'avez aucun moyen d'entrer du texte dans des zones de texte sans faire défiler votre page tout en bas et perdre votre place.

Existe-t-il un moyen de contourner ce bogue de manière propre?

#a { height: 10000px; background: linear-gradient(red, blue); } #b { position: fixed; bottom: 20px; left: 10%; width: 100%; height: 300px; } textarea { width: 80%; height: 300px; } 
 <html> <body> <div id="a"></div> <div id="b"><textarea></textarea></div> </body> </html> 

Sur la base de cette bonne analyse de ce problème, je l'ai utilisé dans body éléments html et body dans css:

 html,body{ -webkit-overflow-scrolling : touch !important; overflow: auto !important; height: 100% !important; } 

Je pense que ça fonctionne très bien pour moi.

La meilleure solution que je pourrais proposer est de passer à la position: absolute; utilisation position: absolute; Sur la mise au point et en calculant la position à laquelle il utilisait la position: fixed; . L' focus est que l'événement de focus au focus déclenche trop tard, il faut donc utiliser le touchstart .

La solution dans cette réponse imite le comportement correct que nous avons eu dans iOS 7 de très près.

Exigences:

L'élément du body doit avoir un positionnement afin d'assurer un positionnement correct lorsque l'élément passe au positionnement absolu.

 body { position: relative; } 

Le code ( exemple en direct ):

Le code suivant est un exemple de base pour la valise de test fournie et peut être adapté à votre cas d'utilisation spécifique.

 //Get the fixed element, and the input element it contains. var fixed_el = document.getElementById('b'); var input_el = document.querySelector('textarea'); //Listen for touchstart, focus will fire too late. input_el.addEventListener('touchstart', function() { //If using a non-px value, you will have to get clever, or just use 0 and live with the temporary jump. var bottom = parseFloat(window.getComputedStyle(fixed_el).bottom); //Switch to position absolute. fixed_el.style.position = 'absolute'; fixed_el.style.bottom = (document.height - (window.scrollY + window.innerHeight) + bottom) + 'px'; //Switch back when focus is lost. function blured() { fixed_el.style.position = ''; fixed_el.style.bottom = ''; input_el.removeEventListener('blur', blured); } input_el.addEventListener('blur', blured); }); 

Voici le même code sans hack pour la comparaison .

Caveat:

Si le position: fixed; L'élément a d'autres éléments parents avec positionnement en dehors du body , en passant à la position: absolute; Peut avoir un comportement inattendu. En raison de la nature du position: fixed; Ce n'est probablement pas un problème majeur, car nicher de tels éléments n'est pas commun.

Recommandations:

Bien que l'utilisation de l'événement touchstart filtre la plupart des environnements de bureau, vous voudrez probablement utiliser le sniffing de l'agent utilisateur pour que ce code ne s'exécute que pour l'iOS 8 cassé, et pas d'autres périphériques tels que Android et les anciennes versions iOS. Malheureusement, nous ne savons pas encore quand Apple va résoudre ce problème dans iOS, mais je serais surpris s'il ne l'est pas dans la prochaine version majeure.

J'ai trouvé une méthode qui fonctionne sans avoir à changer de position absolue!

Code complet complété

 var scrollPos = $(document).scrollTop(); $(window).scroll(function(){ scrollPos = $(document).scrollTop(); }); var savedScrollPos = scrollPos; function is_iOS() { var iDevices = [ 'iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod' ]; while (iDevices.length) { if (navigator.platform === iDevices.pop()){ return true; } } return false; } $('input[type=text]').on('touchstart', function(){ if (is_iOS()){ savedScrollPos = scrollPos; $('body').css({ position: 'relative', top: -scrollPos }); $('html').css('overflow','hidden'); } }) .blur(function(){ if (is_iOS()){ $('body, html').removeAttr('style'); $(document).scrollTop(savedScrollPos); } }); 

Rompant

Tout d'abord, vous devez avoir le champ de saisie fixe vers le haut de la page dans le HTML (c'est un élément fixe, de sorte qu'il devrait avoir un sens sémantique pour l'avoir près du sommet de toute façon):

 <!DOCTYPE HTML> <html> <head> <title>Untitled</title> </head> <body> <form class="fixed-element"> <input class="thing-causing-the-issue" type="text" /> </form> <div class="everything-else">(content)</div> </body> </html> 

Ensuite, vous devez enregistrer la position de défilement actuelle en variables globales:

 //Always know the current scroll position var scrollPos = $(document).scrollTop(); $(window).scroll(function(){ scrollPos = $(document).scrollTop(); }); //need to be able to save current scroll pos while keeping actual scroll pos up to date var savedScrollPos = scrollPos; 

Ensuite, vous avez besoin d'un moyen de détecter les périphériques iOS de sorte qu'il n'affecte pas les choses qui n'ont pas besoin de la correction (fonction prise à partir de https://stackoverflow.com/a/9039885/1611058 )

 //function for testing if it is an iOS device function is_iOS() { var iDevices = [ 'iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod' ]; while (iDevices.length) { if (navigator.platform === iDevices.pop()){ return true; } } return false; } 

Maintenant que nous avons tout ce dont nous avons besoin, voici le correctif 🙂

 //when user touches the input $('input[type=text]').on('touchstart', function(){ //only fire code if it's an iOS device if (is_iOS()){ //set savedScrollPos to the current scroll position savedScrollPos = scrollPos; //shift the body up a number of pixels equal to the current scroll position $('body').css({ position: 'relative', top: -scrollPos }); //Hide all content outside of the top of the visible area //this essentially chops off the body at the position you are scrolled to so the browser can't scroll up any higher $('html').css('overflow','hidden'); } }) //when the user is done and removes focus from the input field .blur(function(){ //checks if it is an iOS device if (is_iOS()){ //Removes the custom styling from the body and html attribute $('body, html').removeAttr('style'); //instantly scrolls the page back down to where you were when you clicked on input field $(document).scrollTop(savedScrollPos); } }); 

J'ai pu réparer ceci pour sélectionner des entrées en ajoutant un auditeur d'événement aux éléments sélectionnés nécessaires, puis en faisant défiler par un décalage d'un pixel lorsque le sélection en question gagne du focus.

Ce n'est pas nécessairement une bonne solution, mais c'est beaucoup plus simple et plus fiable que les autres réponses que j'ai vues ici. Le navigateur semble re-render / re-calculer la position: fixed; Attribut basé sur le décalage fourni dans la fonction window.scrollBy ().

 document.querySelector(".someSelect select").on("focus", function() {window.scrollBy(0, 1)}); 

Tout comme M. Ryan Sallee l'a suggéré, j'ai constaté que le changement dynamique de la hauteur et du débordement de mon élément de fond est la clé – cela ne donne rien à Safari.

Ainsi, une fois l'animation d'ouverture du mode terminée, modifiez le style de l'arrière-plan:

 $('body > #your-background-element').css({ 'overflow': 'hidden', 'height': 0 }); 

Lorsque vous fermez le changement modal, il est renvoyé:

 $('body > #your-background-element').css({ 'overflow': 'auto', 'height': 'auto' }); 

Alors que d'autres réponses sont utiles dans des contextes plus simples, mon DOM était trop compliqué (merci SharePoint) pour utiliser le swap de position absolue / fixe.

Propre? non.

J'ai récemment eu ce problème moi-même avec un champ de recherche fixe dans un en-tête collant, le meilleur que vous pouvez faire en ce moment est de garder la position de défilement dans une variable en tout temps et lors de la sélection, rendre la position de l'élément fixe absolue au lieu de la fixer avec un sommet Position basée sur la position de défilement du document.

Ceci est cependant très laid et aboutit à un étrange défilement avant et arrière avant d'atterrir au bon endroit, mais c'est le plus proche que je puisse obtenir.

Toute autre solution impliquerait de remplacer la mécanique de défilement par défaut du navigateur.

Ceci est maintenant corrigé dans iOS 10.3!

Les hacks ne devraient plus être nécessaires.

N'ont pas traité ce bug particulier, mais peut-être mettre un débordement: caché; Sur le corps lorsque la zone de texte est visible (ou juste active, selon votre conception). Cela peut avoir pour effet de ne pas donner au navigateur n'importe où "bas" pour faire défiler jusqu'à.

Une solution possible serait de remplacer le champ de saisie.

  • Suivre les événements sur un div
  • Concentrer un champ de saisie caché pour rendre le clavier
  • Réplique le contenu du champ de saisie caché dans le champ de saisie de faux
 function focus() { $('#hiddeninput').focus(); } $(document.body).load(focus); $('.fakeinput').bind("click",function() { focus(); }); $("#hiddeninput").bind("keyup blur", function (){ $('.fakeinput .placeholder').html(this.value); }); 
 #hiddeninput { position:fixed; top:0;left:-100vw; opacity:0; height:0px; width:0; } #hiddeninput:focus{ outline:none; } .fakeinput { width:80vw; margin:15px auto; height:38px; border:1px solid #000; color:#000; font-size:18px; padding:12px 15px 10px; display:block; overflow:hidden; } .placeholder { opacity:0.6; vertical-align:middle; } 
 <input type="text" id="hiddeninput"></input> <div class="fakeinput"> <span class="placeholder">First Name</span> </div> 

Aucune de ces solutions n'a fonctionné pour moi parce que mon DOM est compliqué et j'ai des pages de défilement infinies dynamiques, alors j'ai dû créer le mien.

Contexte: J'utilise un en-tête fixe et un élément en bas qui colle au-dessous de celui-ci une fois que l'utilisateur défile vers le bas. Cet élément comporte un champ de saisie de recherche. De plus, j'ai des pages dynamiques ajoutées lors du défilement vers l'avant et vers l'arrière.

Problème: dans iOS, chaque fois que l'utilisateur a cliqué sur l'entrée dans l'élément fixe, le navigateur défilerait tout le chemin vers le haut de la page. Cela a non seulement causé un comportement indésirable, mais aussi la création de ma page dynamique en haut de la page.

Solution attendue: Pas de défilement dans iOS (aucun) lorsque l'utilisateur clique sur l'entrée dans l'élément collant.

Solution:

  /*Returns a function, that, as long as it continues to be invoked, will not be triggered. The function will be called after it stops being called for N milliseconds. If `immediate` is passed, trigger the function on the leading edge, instead of the trailing.*/ function debounce(func, wait, immediate) { var timeout; return function () { var context = this, args = arguments; var later = function () { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; }; function is_iOS() { var iDevices = [ 'iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod' ]; while (iDevices.length) { if (navigator.platform === iDevices.pop()) { return true; } } return false; } $(document).on("scrollstop", debounce(function () { //console.log("Stopped scrolling!"); if (is_iOS()) { var yScrollPos = $(document).scrollTop(); if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px) $('#searchBarDiv').css('position', 'absolute'); $('#searchBarDiv').css('top', yScrollPos + 50 + 'px'); //50 for fixed header } else { $('#searchBarDiv').css('position', 'inherit'); } } },250,true)); $(document).on("scrollstart", debounce(function () { //console.log("Started scrolling!"); if (is_iOS()) { var yScrollPos = $(document).scrollTop(); if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px) $('#searchBarDiv').css('position', 'fixed'); $('#searchBarDiv').css('width', '100%'); $('#searchBarDiv').css('top', '50px'); //50 for fixed header } } },250,true)); 

Exigences: JQuery mobile est requis pour que les fonctions startroll et stopcroll fonctionnent.

Debounce est inclus pour lisser tous les délais créés par l'élément collant.

Testé dans iOS10.

Je viens de sauter sur quelque chose comme ça hier en fixant la hauteur de #a à la hauteur visible maximale (la hauteur du corps était dans mon cas) lorsque #b est visible

ex:

  <script> document.querySelector('#b').addEventListener('focus', function () { document.querySelector('#a').style.height = document.body.clientHeight; }) </script> 

Ps: désolé pour un exemple tardif, juste remarqué qu'il était nécessaire.

J'ai eu le problème, en dessous des lignes de code, le résolus pour moi –

 html{ overflow: scroll; -webkit-overflow-scrolling: touch; }