Safari в ios8 прокручивает экран, когда фиксированные элементы получают фокус

96

В iOS8 Safari появилась новая ошибка с исправлением положения.

Если вы сфокусируете текстовое поле, которое находится на фиксированной панели, 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>
Сэм Шафран
источник
1
Поможет ли установка z-индекса на #b?
Бен
1
z index не помогает, может быть, какое-то причудливое преобразование без операции css будет много работать с контекстами стека, не уверен.
Сэм Саффрон,
1
для контекста обсуждение Discourse: meta.discourse.org/t/dealing-with-ios-8-ipad-mobile-safari-bugs/…
Сэм Шафран,
80
iOS Safari - это новый IE
geedubb
4
@geedubb согласился. любая дебильная ОС, которая связывает свою версию браузера по умолчанию с ОС, столкнется с проблемами, которые преследовали IE в течение последних 7 лет.
dewd

Ответы:

58

Исходя из этого хорошего анализа этого вопроса, я использовал это htmlи bodyэлементы в CSS:

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

Думаю, у меня это отлично работает.

Мохаммад АльБанна
источник
2
у меня тоже сработало. Это испортило много других вещей, так как я манипулирую своей DOM при загрузке, поэтому я превращаю это в класс и добавляю его в html, body после того, как DOM стабилизируется. Такие вещи, как scrollTop, работают не очень хорошо (я выполняю автоматическую прокрутку), но, опять же, вы можете добавить / удалить класс при выполнении операций прокрутки. Однако плохая работа со стороны команды Safari.
Amarsh
1
Люди , глядя на этот вариант , возможно , также хотите проверить transform: translateZ(0);с stackoverflow.com/questions/7808110/...
lkraav
1
Это решает проблему, но если у вас есть анимация, она будет выглядеть очень прерывистой. Может быть, лучше обернуть это медиа-запросом.
mmla 07
У меня работало на iOS 10.3.
quoteBro
Это не решает проблемы. Вам нужно перехватить прокрутку, когда появляется виртуальная клавиатура, и изменить высоту на определенное значение: stackoverflow.com/a/46044341/84661
Брайан Каннард
36

Лучшее решение, которое я мог придумать, - это переключиться на использование 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, но я был бы удивлен, если она не будет исправлена ​​в следующей основной версии.

Александр О'Мара
источник
Интересно, может ли двойное обертывание с помощью div и установка высоты на% 100 на прозрачном обертывающем div обмануть его, чтобы избежать этого ...
Сэм Саффрон,
@SamSaffron Не могли бы вы пояснить, как может работать такая техника? Я пробовал несколько таких вещей безуспешно. Поскольку высота документа неоднозначна, я не уверен, как это могло бы работать.
Александр О'Мара
Я думал, что просто "фиксированная" обертка со 100% высотой может обойти это, а может и нет
Сэм Саффрон
@downvoter: Я что-то не так понял? Я согласен, что это ужасное решение, но я не думаю, что есть лучшие.
Александр О'Мара
4
У меня это не сработало, поле ввода все еще перемещается.
Родриго Руис
8

Я нашел метод, который работает без необходимости менять абсолютное положение!

Полный код без комментариев

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, чтобы он не влиял на вещи, которые не нуждаются в исправлении (функция взята из 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;
}

Теперь, когда у нас есть все необходимое, вот и исправление :)

//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);
    }
});
Дэниел Тонон
источник
+1. Это значительно менее сложное исправление, чем принятый ответ, если у вас нетривиальная иерархия DOM. Это должно иметь больше голосов,
Энсон Као
Не могли бы вы сделать это и в родном JS? Спасибо!
mesqueeb
@ SamSaffron, этот ответ действительно сработал для тебя? Могу я привести здесь пример. это не сработало для меня?
Ганеш Путта
@ SamSaffron, действительно ли этот ответ решил вашу проблему, можете ли вы прислать пример, который сработал для вас, я работаю над тем же, но он не сработал для меня.
Ганеш Путта
@GaneshPutta Возможно, из-за недавнего обновления iOS это больше не работает. Я разместил это 2,5 года назад. Тем не менее, он все равно должен работать, если вы точно следовали всем инструкциям: /
Дэниел Тонон
4

Мне удалось исправить это для входов выбора, добавив прослушиватель событий к необходимым элементам выбора, а затем прокрутив его со смещением в один пиксель, когда выбранный элемент получает фокус.

Это не обязательно хорошее решение, но оно намного проще и надежнее, чем другие ответы, которые я видел здесь. Кажется, что браузер повторно отображает / пересчитывает позицию: fixed; атрибут, основанный на смещении, указанном в функции window.scrollBy ().

document.querySelector(".someSelect select").on("focus", function() {window.scrollBy(0, 1)});
user3411121
источник
2

Как и предположил Марк Райан Салли, я обнаружил, что ключевым моментом является динамическое изменение высоты и переполнения моего фонового элемента - это не дает Safari возможности прокручивать.

Итак, после завершения анимации открытия модального окна измените стиль фона:

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

Когда вы закроете модальное окно, верните его обратно:

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

В то время как другие ответы полезны в более простых контекстах, моя DOM была слишком сложной (спасибо SharePoint), чтобы использовать замену абсолютной / фиксированной позиции.

Мэтью Леви
источник
1

Чисто? нет.

У меня недавно была эта проблема с фиксированным полем поиска в липком заголовке, лучшее, что вы можете сделать на данный момент, - это постоянно сохранять позицию прокрутки в переменной и после выбора сделать фиксированное положение элемента абсолютным, а не фиксированным с помощью вершины позиция на основе позиции прокрутки документа.

Однако это очень уродливо и по-прежнему приводит к некоторой странной прокрутке вперед и назад перед тем, как приземлиться в нужном месте, но это самое близкое, что я мог найти.

Любое другое решение потребует переопределения механизма прокрутки по умолчанию в браузере.

Самуэль
источник
0

Не разобрался с этой конкретной ошибкой, но, возможно, поставил overflow: hidden; на теле, когда текстовая область видна (или просто активна, в зависимости от вашего дизайна). Это может привести к тому, что браузеру не будет «вниз» для прокрутки.

Марк Райан Салли
источник
1
Кажется, я даже не могу получить сенсорный запуск, чтобы сработать достаточно рано, чтобы даже подумать об этом взломе :(
Сэм Саффрон,
0

Возможное решение - заменить поле ввода.

  • Мониторинг событий кликов в div
  • сфокусировать скрытое поле ввода, чтобы отобразить клавиатуру
  • копировать содержимое скрытого поля ввода в поддельное поле ввода

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> 


кодовый ключ

Дэвидондрей
источник
0

Ни одно из этих решений не помогло мне, потому что моя модель 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 необходим для работы функций startroll и stopcroll.

Debounce включен, чтобы сгладить любую задержку, создаваемую липким элементом.

Проверено в iOS10.

Дима
источник
0

Я только что перепрыгнул через что-то подобное вчера, установив высоту #a на максимальную видимую высоту (высота тела была в моем случае), когда #b виден

пример:

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

ps: извините за поздний пример, только заметил, что это было необходимо.

Онур Уяр
источник
14
Включите пример кода, чтобы прояснить, как ваше исправление может помочь
roo2
@EruPenkman извините, только что заметил ваш комментарий, надеюсь, что это поможет.
Онур Уяр
0

Теперь это исправлено в iOS 10.3!

Хаки больше не нужны.

Сэм Шафран
источник
1
Можете ли вы указать на какие-либо примечания к выпуску, в которых это исправлено?
bluepnume 05
Apple очень скрытна, они закрыли мой отчет об ошибке, я подтвердил, что теперь он работает правильно, это все, что у меня есть :)
Сэм
1
У меня все еще есть эта проблема на iOS 11
zekia
Нет, это все еще проблема даже на iOS 13.
Дмитрий Худорожков
0

У меня была проблема, ниже строки кода разрешили ее для меня -

html{

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

}
Манодж Горася
источник