javascript - Safari en ios8 es la pantalla de desplazamiento cuando los elementos fijos se enfocan
css (12)
En IOS8 Safari hay un nuevo error con posición fija.
Si enfoca un área de texto que está en un panel fijo, safari lo desplazará al final de la página.
Esto hace que sea imposible trabajar con todo tipo de interfaces de usuario, ya que no tiene forma de ingresar texto en áreas de texto sin desplazarse por la página y perder su lugar.
¿Hay alguna forma de solucionar este error limpiamente?
#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>
¡Encontré un método que funciona sin la necesidad de cambiar a la posición absoluta!
Código completo sin comentar
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);
}
});
Desglosándolo
Primero debe tener el campo de entrada fijo hacia la parte superior de la página en el HTML (es un elemento fijo, por lo que debería tener sentido semántico tenerlo cerca de la parte superior de todos modos):
<!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>
Luego debe guardar la posición de desplazamiento actual 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;
Entonces necesita una forma de detectar dispositivos iOS para que no afecte las cosas que no necesitan la solución (función tomada de https://.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;
}
Ahora que tenemos todo lo que necesitamos, aquí está la solución :)
//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);
}
});
¡Esto ahora está arreglado en iOS 10.3!
Los trucos ya no deberían ser necesarios.
¿Limpiamente? No.
Hace poco tuve este problema con un campo de búsqueda fijo en un encabezado fijo, lo mejor que puede hacer en este momento es mantener la posición de desplazamiento en una variable en todo momento y al seleccionarla, la posición del elemento fijo es absoluta en lugar de fijada con un encabezado posición basada en la posición de desplazamiento del documento.
Sin embargo, esto es muy feo y todavía resulta en un extraño desplazamiento hacia adelante y hacia atrás antes de aterrizar en el lugar correcto, pero es lo más cerca que pude llegar.
Cualquier otra solución implicaría anular la mecánica de desplazamiento predeterminada del navegador.
Al igual que Mark Ryan Sallee sugirió, descubrí que cambiar dinámicamente la altura y el desbordamiento de mi elemento de fondo es la clave, esto le da a Safari nada a lo que desplazarse.
Entonces, una vez que finalice la animación de apertura del modal, cambie el estilo del fondo:
$(''body > #your-background-element'').css({
''overflow'': ''hidden'',
''height'': 0
});
Cuando cierre el modal, cámbielo de nuevo:
$(''body > #your-background-element'').css({
''overflow'': ''auto'',
''height'': ''auto''
});
Si bien otras respuestas son útiles en contextos más simples, mi DOM era demasiado complicado (gracias a SharePoint) para usar el intercambio de posición absoluto / fijo.
Ayer salté sobre algo como esto estableciendo la altura de #a a la altura máxima visible (la altura del cuerpo era en mi caso) cuando #b es visible
ex:
<script>
document.querySelector(''#b'').addEventListener(''focus'', function () {
document.querySelector(''#a'').style.height = document.body.clientHeight;
})
</script>
ps: perdón por un ejemplo tardío, solo noté que era necesario.
Basado en este
buen análisis
de este problema, he usado esto en
html
y elementos del
body
en css:
html,body{
-webkit-overflow-scrolling : touch !important;
overflow: auto !important;
height: 100% !important;
}
Creo que me está funcionando muy bien.
La mejor solución que se me ocurre es cambiar a la
position: absolute;
uso
position: absolute;
en foco y calculando la posición en la que estaba cuando estaba usando la
position: fixed;
.
El truco es que el evento de
focus
se dispara demasiado tarde, por
touchstart
que se debe usar el
touchstart
.
La solución en esta respuesta imita el comportamiento correcto que teníamos en iOS 7 muy de cerca.
Requisitos:
El elemento del
body
debe tener posicionamiento para garantizar un posicionamiento adecuado cuando el elemento cambia a posicionamiento absoluto.
body {
position: relative;
}
El código ( ejemplo en vivo ):
El siguiente código es un ejemplo básico para el caso de prueba proporcionado, y puede adaptarse para su caso de uso específico.
//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);
});
Aquí está el mismo código sin el truco para comparar .
Consideración:
Si la
position: fixed;
element tiene cualquier otro elemento padre con posicionamiento además del
body
, cambiando a
position: absolute;
Puede tener un comportamiento inesperado.
Debido a la naturaleza del
position: fixed;
Esto probablemente no sea un problema importante, ya que anidar dichos elementos no es común.
Recomendaciones:
Si bien el uso del evento de
touchstart
filtrará la mayoría de los entornos de escritorio, es probable que desee utilizar la detección de agentes de usuario para que este código solo se ejecute para el iOS 8 roto, y no para otros dispositivos como Android y versiones anteriores de iOS.
Desafortunadamente, aún no sabemos cuándo Apple solucionará este problema en iOS, pero me sorprendería si no se soluciona en la próxima versión principal.
Ninguna de estas soluciones funcionó para mí porque mi DOM es complicado y tengo páginas dinámicas de desplazamiento infinito, así que tuve que crear la mía.
Antecedentes: estoy usando un encabezado fijo y un elemento más abajo que se pega debajo de él una vez que el usuario se desplaza hacia abajo. Este elemento tiene un campo de entrada de búsqueda. Además, tengo páginas dinámicas agregadas durante el desplazamiento hacia adelante y hacia atrás.
Problema: en iOS, cada vez que el usuario hace clic en la entrada del elemento fijo, el navegador se desplaza hasta la parte superior de la página. Esto no solo causó un comportamiento no deseado, sino que también activó la adición dinámica de mi página en la parte superior de la página.
Solución esperada: No hay desplazamiento en iOS (ninguno) cuando el usuario hace clic en la entrada en el elemento adhesivo.
Solución:
/*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));
Requisitos: JQuery mobile es necesario para que funcionen las funciones beginroll y stopscroll.
Se incluye el rebote para suavizar cualquier retraso creado por el elemento adhesivo.
Probado en iOS10.
No he tratado con este error en particular, pero tal vez puse un desbordamiento: oculto; en el cuerpo cuando el área de texto es visible (o simplemente activa, dependiendo de su diseño). Esto puede tener el efecto de no darle al navegador ningún lugar "abajo" para desplazarse.
Pude arreglar esto para las entradas de selección agregando un detector de eventos a los elementos de selección necesarios y luego desplazándome por un desplazamiento de un píxel cuando la selección en cuestión gana el foco.
Esta no es necesariamente una buena solución, pero es mucho más simple y confiable que las otras respuestas que he visto aquí. El navegador parece volver a procesar / volver a calcular la posición: fijo; atributo basado en el desplazamiento proporcionado en la función window.scrollBy ().
document.querySelector(".someSelect select").on("focus", function() {window.scrollBy(0, 1)});
Tuve el problema, debajo de las líneas de código lo resolvió por mí:
html{
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
Una posible solución sería reemplazar el campo de entrada.
- Supervisar eventos de clic en un div
- enfocar un campo de entrada oculto para representar el teclado
- replicar el contenido del campo de entrada oculto en el campo de entrada falso
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>