Safari в iOS 8 прокручує екран, коли фіксовані елементи отримують фокус


96

У 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>

1
Чи допомогло б встановлення z-індексу на #b?
Бен,

1
z індекс не допомагає, можливо, якийсь химерний трансформатор op css не допоможе в контексті стека, не впевнений.
Сем Шафран

1
для контексту - це дискусія щодо Дискурсу: meta.discourse.org/t/dealing-with-ios-8-ipad-mobile-safari-bugs/…
Сем Саффрон

79
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, тіло після стабілізації DOM. Такі речі, як scrollTop, працюють не дуже добре (я роблю автоматичну прокрутку), але знову ж таки, ви можете додавати / видаляти клас під час виконання прокрутки. Погана робота з командою Safari.
Амарш

1
Люди , дивлячись на цей варіант , можливо , також хочете перевірити transform: translateZ(0);з stackoverflow.com/questions/7808110 / ...
lkraav

1
Це вирішує проблему, але якщо у вас є анімація, вона буде виглядати дуже нестабільно. Можливо, краще обернути це в медіа-запиті.
mmla

Працював у мене на iOS 10.3.
quotesBro

Це не вирішує проблему. Ви повинні перехоплювати прокрутки , коли віртуальні шоу клавіатури і висоту зміна конкретного значення: stackoverflow.com/a/46044341/84661
Брайан Cannard

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

Я зміг це виправити для вхідних даних, додавши прослуховувач подій до необхідних елементів вибору, а потім прокручуючи зміщення на один піксель, коли вибраний об'єкт фокусується.

Це не обов'язково хороше рішення, але воно набагато простіше і надійніше, ніж інші відповіді, які я бачив тут. Здається, браузер повторно робить / перераховує позицію: фіксоване; атрибут на основі зміщення, наданого у функції window.scrollBy ().

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

2

Як і Марк Райан Саллі, я виявив, що це динамічно змінює висоту та перелив мого фонового елемента є ключовим фактором - це не дає Safari ні до чого прокручувати.

Тож після того, як анімація модалу відкриється, змініть стиль фону:

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

Коли ви закриваєте модальний, змініть його назад:

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

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


1

Чисто? немає.

Нещодавно я сам мав цю проблему з фіксованим полем пошуку в липкому заголовку, найкраще, що ви можете зробити на даний момент, - це постійно утримувати позицію прокрутки у змінній, а після вибору зробити фіксовану позицію абсолютною, а не фіксованою зверху положення на основі позиції прокрутки документа.

Це, однак, дуже потворно, і все ще призводить до якогось дивного прокручування вперед-назад, перш ніж приземлитися в потрібному місці, але це найближче, що я міг отримати.

Будь-яке інше рішення передбачало б заміну за замовчуванням механізму прокрутки браузера.


1

Тепер це виправлено в iOS 10.3!

Хакери більше не потрібні.


1
Чи можете ви вказати на будь-які примітки до випуску, які вказують на це як на виправлене?
bluepnume

Apple дуже закриті, вони закрили мій звіт про помилку, і я підтвердив, що зараз він працює належним чином, ось і все, що я отримав :)
Сем Саффрон

Я все ще маю цю проблему на iOS 11
zekia

0

Не мав справи з цією конкретною помилкою, але, можливо, поставив переповнення: 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.

В комплекті є розпирання, щоб згладити будь-яке відставання, створене липким елементом.

Перевірено в 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

У мене виникла проблема, нижче рядки коду вирішили це для мене -

html{

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

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.