web-dev-qa-db-fra.com

Safari dans ios8 défile à l'écran lorsque des éléments fixes sont activés

Dans IOS8 Safari, un nouveau bogue a été corrigé.

Si vous mettez au point une zone de texte située dans un panneau fixe, Safari vous fera défiler jusqu'au bas de la page.

Cela rend impossible l'utilisation de toutes sortes d'interfaces utilisateur, car vous n'avez aucun moyen de saisir du texte dans des zones de texte sans faire défiler votre page jusqu'au bout et perdre votre emplacement.

Existe-t-il un moyen de contourner ce bogue proprement?

#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>
89
Sam Saffron

Ceci est maintenant corrigé dans iOS 10.3!

Hacks ne devrait plus être nécessaire.

2
Sam Saffron

Basé sur ceci bonne analyse de ce problème, je l’ai utilisé dans les éléments html et body de css:

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

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

54
Mohammad AlBanna

La meilleure solution que je puisse trouver est de passer de position: absolute; à la mise au point et de calculer la position à laquelle il se trouvait lorsqu'il utilisait position: fixed;. L'astuce est que l'événement focus se déclenche trop tard, de sorte que touchstart doit être utilisé.

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

Exigences:

L'élément body doit avoir un positionnement afin d'assurer un positionnement correct lorsque l'élément bascule en positionnement absolu.

body {
    position: relative;
}

Le code ( Exemple en direct ):

Le code suivant est un exemple de base pour le cas de test fourni 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 le hack pour la comparaison .

Caveat:

Si l'élément position: fixed; a d'autres éléments parents avec un positionnement autre que body, le passage à position: absolute; peut avoir un comportement inattendu. En raison de la nature de position: fixed;, il ne s'agit probablement pas d'un problème majeur, car l'imbrication de tels éléments n'est pas courante.

Recommandations:

Bien que l’utilisation de l’événement touchstart filtre la plupart des environnements de bureau, vous voudrez probablement utiliser le reniflement agent-utilisateur afin que ce code ne s’exécute que pour iOS 8 défectueux, et non pour d’autres périphériques tels que Android et les anciennes versions iOS. Malheureusement, nous ne savons pas encore quand Apple résoudra ce problème sous iOS, mais je serais surpris que le problème ne soit pas résolu dans la prochaine version majeure.

34
Alexander O'Mara

J'ai trouvé une méthode qui fonctionne sans qu'il soit nécessaire de changer de position absolue!

Code complet non commenté

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

Le décomposer

Tout d'abord, vous devez avoir le champ de saisie fixe vers le haut de la page dans le code HTML (c'est un élément fixe, il devrait donc être sémantique de le placer quand même près du haut):

<!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 dans les 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 appareils iOS afin qu’il n’affecte pas les choses qui n’ont pas besoin de la solution (fonction prise 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 la solution :)

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

J'ai pu résoudre ce problème pour certaines entrées en ajoutant un écouteur d'événement aux éléments de sélection nécessaires, puis en défilant d'un décalage d'un pixel lorsque l'élément sélectionné en question était activé.

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 refaire/recalculer la position: corrigé; attribut basé sur le décalage fourni dans la fonction window.scrollBy ().

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

Comme Mark Ryan Sallee l’a suggéré, j’ai constaté que changer la hauteur et le débordement de mon élément d’arrière-plan est la clé - c’est la clé - cela ne permet pas à Safari de faire défiler.

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

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

Lorsque vous fermez le modal, changez-le:

$('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 l'échange de positions absolu/fixe.

2
Matthew Levy

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 mieux que vous puissiez faire pour le moment est de conserver la position du défilement dans une variable à tout moment et de sélectionner, lors de la sélection, la position absolue de l'élément fixe au lieu de la fixer par le haut. position en fonction de la position de défilement du document.

C’est cependant très moche et donne toujours lieu à un étrange défilement avant d’atterrir au bon endroit, mais c’est le plus proche que je puisse obtenir.

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

1
Samuel

J'ai eu le problème, au-dessous des lignes de code résolu pour moi -

html{

 overflow: scroll; 
-webkit-overflow-scrolling: touch;

}
0
Manoj Gorasya

Je n'ai pas traité ce bogue particulier, mais peut-être un débordement: caché; sur le corps lorsque la zone de texte est visible (ou simplement active, selon votre conception). Cela peut avoir pour effet de ne pas donner au navigateur "vers le bas" où faire défiler.

0
Mark Ryan Sallee

Aucune de ces solutions ne fonctionnait pour moi parce que mon DOM est compliqué et que j'ai des pages de défilement infinies dynamiques, je devais donc créer les miennes.

Background: J'utilise un en-tête fixe et un élément plus bas qui reste en dessous une fois que l'utilisateur a fait défiler très bas. Cet élément a un champ de saisie de recherche. De plus, des pages dynamiques ont été ajoutées lors des défilements avant et arrière.

Problème: Sous iOS, à chaque fois que l'utilisateur cliquait sur l'entrée dans l'élément fixe, le navigateur faisait défiler l'écran jusqu'en haut de la page. Cela a non seulement provoqué un comportement indésirable, mais a également déclenché l'ajout de ma page dynamique en haut de la page.

Solution attendue: Pas de défilement dans iOS (pas du tout) 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));

Conditions requises: JQuery mobile est requis pour que les fonctions startsroll et stopscroll fonctionnent.

Debounce est inclus pour atténuer tout décalage créé par l'élément collant.

Testé sous iOS10.

0
Dima

Une solution possible serait de remplacer le champ de saisie.

  • Surveiller les événements de clic sur un div
  • concentrer un champ de saisie masqué pour restituer le clavier
  • répliquer le contenu du champ de saisie masqué dans le faux champ de saisie
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> 

codepen

0
davidcondrey

Je viens de sauter par-dessus quelque chose comme cela hier en réglant la hauteur de #a à la hauteur maximale visible (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 l'exemple en retard, j'ai juste remarqué que c'était nécessaire.

0
Onur Uyar