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>
Ceci est maintenant corrigé dans iOS 10.3!
Hacks ne devrait plus être nécessaire.
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.
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.
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 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 .
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.
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.
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);
}
});
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)});
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.
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.
J'ai eu le problème, au-dessous des lignes de code résolu pour moi -
html{
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
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.
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.
Une solution possible serait de remplacer le 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>
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.