Ответ 1
Теперь это исправлено в iOS 10.3!
Хаки больше не нужны.
В IOS8 Safari появилась новая ошибка с фиксированной позицией.
Если вы фокусируете текстовое поле, которое находится на фиксированной панели, сафари прокрутит вас до нижней части страницы.
Это делает невозможным работу с любыми пользовательскими интерфейсами, поскольку вы не можете вводить текст в текстовые поля, не прокручивая страницу до конца и теряя место.
Есть ли способ обхода этой ошибки?
#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>
Теперь это исправлено в iOS 10.3!
Хаки больше не нужны.
Основываясь на этом хорошем анализе этой проблемы, я использовал это в html
и body
элементах в css:
html,body{
-webkit-overflow-scrolling : touch !important;
overflow: auto !important;
height: 100% !important;
}
Я думаю, что он отлично работает для меня.
Лучшим решением, которое я мог бы придумать, является переход на использование position: absolute;
на фокусе и вычисление положения, в котором он находился, когда он использовал position: fixed;
. Фокус в том, что событие focus
срабатывает слишком поздно, поэтому touchstart
необходимо использовать.
Решение в этом ответе очень хорошо подражает правильному поведению, которое у нас было в iOS 7.
Элемент body
должен иметь позиционирование, чтобы обеспечить правильное позиционирование, когда элемент переключается на абсолютное позиционирование.
body {
position: relative;
}
Следующий код является базовым примером для предоставленного тестового случая и может быть адаптирован для вашего конкретного случая использования.
//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);
});
Вот такой же код без взлома для сравнения.
Если элемент position: fixed;
имеет любые другие родительские элементы с позиционированием помимо body
, переход на position: absolute;
может иметь неожиданное поведение. Из-за характера position: fixed;
это, вероятно, не является серьезной проблемой, поскольку вложение таких элементов не является обычным явлением.
В то время как использование события touchstart
будет отфильтровывать большинство настольных сред, вы, вероятно, захотите использовать нюхание агента пользователя, чтобы этот код работал только для сломанной iOS 8, а не для других устройств, таких как Android и более старые версии iOS. К сожалению, мы еще не знаем, когда Apple исправит эту проблему в iOS, но я был бы удивлен, если она не будет исправлена в следующей крупной версии.
Я нашел метод, который работает без необходимости изменения абсолютной позиции!
Полный код без комментирования
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);
}
});
Нарушение
Сначала вам нужно иметь фиксированное поле ввода в верхней части страницы в HTML (это фиксированный элемент, поэтому он должен иметь семантически смысл иметь его в верхней части в любом случае):
<!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>
Затем вам нужно сохранить текущую позицию прокрутки в глобальных переменных:
//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;
Затем вам нужен способ обнаружения устройств iOS, чтобы он не влиял на вещи, которые не нуждаются в исправлении (функция взята из fooobar.com/questions/22726/...)
//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;
}
Теперь, когда у нас есть все, что нам нужно, вот исправление:)
//when user touches the input
$('input[type=text]').on('touchstart', function(){
//only fire code if it 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);
}
});
Я смог исправить это для выбора входных данных, добавив прослушиватель событий к необходимым элементам выбора, затем прокручивая смещение одного пикселя, когда выбранный вопрос получает фокус.
Это не обязательно хорошее решение, но оно намного проще и надежнее, чем другие ответы, которые я видел здесь. Браузер, похоже, повторно отображает/пересчитывает позицию: исправлено; атрибут, основанный на смещении, предоставляемом функцией window.scrollBy().
document.querySelector(".someSelect select").on("focus", function() {window.scrollBy(0, 1)});
Как и Марк Райан Сэлли, я обнаружил, что динамическое изменение высоты и переполнения моего элемента фона - это ключ - это не дает Safari для прокрутки.
Итак, после окончания анимации модального открытия измените стиль фона:
$('body > #your-background-element').css({
'overflow': 'hidden',
'height': 0
});
Когда вы завершите изменение модальности:
$('body > #your-background-element').css({
'overflow': 'auto',
'height': 'auto'
});
В то время как другие ответы полезны в более простых контекстах, мой DOM был слишком сложным (спасибо SharePoint), чтобы использовать своп абсолютной/фиксированной позиции.
Чистоплотный? нет.
Недавно у меня была эта проблема с фиксированным полем поиска в липком заголовке, лучшее, что вы можете сделать в данный момент, всегда сохраняет положение прокрутки в переменной и после выбора делает фиксированную позицию элемента абсолютной, а не фиксированной с верхней позицией, основанной на позиции прокрутки документа.
Это, однако, очень уродливо и по-прежнему приводит к какой-то странной прокрутке вперед и назад, прежде чем приземлиться в нужном месте, но это самое близкое, что я мог бы получить.
Любое другое решение предполагает переопределение механики прокрутки по умолчанию браузера.
Не рассматривали эту ошибку, но, возможно, переполнение: hidden; на теле, когда текстовая область видима (или просто активна, в зависимости от вашего дизайна). Это может привести к тому, что браузер не будет никуда "вниз", чтобы прокрутить до.
Я только что перепрыгнул через что-то подобное вчера, установив высоту от #a до максимальной видимой высоты (высота тела была в моем случае), когда #b виден
ex:
<script>
document.querySelector('#b').addEventListener('focus', function () {
document.querySelector('#a').style.height = document.body.clientHeight;
})
</script>
ps: извините за поздний пример, просто заметил, что это необходимо.
Возможным решением было бы заменить поле ввода.
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>
Ни одно из этих решений не работало для меня, потому что мой DOM сложный, и у меня есть динамические бесконечные страницы прокрутки, поэтому я должен был создать свой собственный.
Фон: Я использую фиксированный заголовок и элемент, расположенный ниже, который находится ниже него, когда пользователь прокручивает это далеко вниз. Этот элемент имеет поле ввода поиска. Кроме того, у меня есть динамические страницы, добавленные во время прокрутки вперед и назад.
Проблема: В iOS, в любое время, когда пользователь нажал на вход в фиксированном элементе, браузер прокрутит весь путь до верхней части страницы. Это не только вызвало нежелательное поведение, но и вызвало добавление моей динамической страницы вверху страницы.
Ожидаемое решение: Нет прокрутки в iOS (вообще нет), когда пользователь нажимает на ввод в липком элементе.
Решение:
/*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));
Требования: JQuery mobile необходим для работы функций запуска и остановки.
Отладка включена для сглаживания любого запаздывания, созданного липким элементом.
Протестировано в iOS10.
У меня была проблема, ниже строки кода разрешили это для меня -
html{
overflow: scroll;
-webkit-overflow-scrolling: touch;
}