Safari di ios8 adalah layar gulir saat elemen tetap mendapatkan fokus

96

Di Safari iOS8 ada bug baru dengan posisi tetap.

Jika Anda memfokuskan textarea yang ada di panel tetap, safari akan menggulir Anda ke bagian bawah halaman.

Ini membuat semua jenis UI tidak mungkin digunakan, karena Anda tidak memiliki cara untuk memasukkan teks ke dalam textarea tanpa menggulir halaman Anda sepenuhnya ke bawah dan kehilangan tempat Anda.

Apakah ada cara untuk mengatasi bug ini dengan rapi?

#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>
Sam Saffron
sumber
1
Apakah menyetel indeks-z pada #b membantu?
Ben
1
z index tidak membantu, mungkin beberapa op css transformasi tidak akan banyak menggunakan konteks stack, tidak yakin.
Sam Saffron
1
untuk konteks di sini adalah pembahasan pada Discourse: meta.discourse.org/t/dealing-with-ios-8-ipad-mobile-safari-bugs/…
Sam Saffron
80
Safari iOS adalah IE baru
geedubb
4
@geedubb setuju. OS tolol apa pun yang mengikat versi browser defaultnya ke OS akan melanggar masalah yang telah melanda IE selama 7 tahun terakhir.
embun

Jawaban:

58

Berdasarkan analisis bagus dari masalah ini, saya telah menggunakan in htmldan bodyelemen di css:

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

Saya pikir ini bekerja dengan baik untuk saya.

Mohammad AlBanna
sumber
2
bekerja untuk saya juga. Ini mengacaukan banyak hal lain karena saya memanipulasi DOM saya saat dimuat, jadi saya membuat ini menjadi kelas, dan menambahkannya ke html, body setelah DOM telah stabil. Hal-hal seperti scrollTop tidak berfungsi dengan baik (saya melakukan scrolling otomatis), tetapi sekali lagi, Anda dapat menambah / menghapus kelas saat melakukan operasi scrolling. Pekerjaan buruk di bagian tim Safari.
Amarsh
1
Orang yang melihat opsi ini mungkin juga ingin menguji transform: translateZ(0);dari stackoverflow.com/questions/7808110/…
lkraav
1
Ini menyelesaikan masalah, tetapi jika Anda memiliki animasi, mereka akan terlihat sangat berombak. Mungkin lebih baik membungkusnya dalam kueri media.
mmla
Bekerja untuk saya di iOS 10.3.
kutipanBro
Itu tidak menyelesaikan masalah. Anda perlu mencegat pengguliran saat keyboard virtual muncul dan mengubah ketinggian ke nilai tertentu: stackoverflow.com/a/46044341/84661
Brian Cannard
36

Solusi terbaik yang dapat saya temukan adalah beralih menggunakan position: absolute;fokus dan menghitung posisi saat itu digunakan position: fixed;. Triknya adalah focusacara tersebut terlambat menyala, jadi touchstartharus digunakan.

Solusi dalam jawaban ini sangat mirip dengan perilaku yang benar yang kami miliki di iOS 7.

Persyaratan:

The bodyelemen harus memiliki positioning untuk memastikan posisi yang tepat ketika elemen beralih ke posisi mutlak.

body {
    position: relative;
}

Kode ( Contoh Langsung ):

Kode berikut adalah contoh dasar untuk kasus uji yang disediakan, dan dapat disesuaikan untuk kasus penggunaan spesifik Anda.

//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);
});

Berikut adalah kode yang sama tanpa peretasan untuk perbandingan .

Peringatan:

Jika position: fixed;elemen memiliki elemen induk lain dengan pemosisian di samping body, peralihan ke position: absolute;mungkin memiliki perilaku yang tidak terduga. Karena sifat position: fixed;ini mungkin bukan masalah besar, karena elemen bersarang tidak umum.

Rekomendasi:

Meskipun penggunaan touchstartacara akan memfilter sebagian besar lingkungan desktop, Anda mungkin ingin menggunakan sniffing agen pengguna sehingga kode ini hanya akan berjalan untuk iOS 8 yang rusak, dan bukan perangkat lain seperti Android dan versi iOS yang lebih lama. Sayangnya, kami belum tahu kapan Apple akan memperbaiki masalah ini di iOS, tetapi saya akan terkejut jika masalah ini tidak diperbaiki di versi utama berikutnya.

Alexander O'Mara
sumber
Saya ingin tahu apakah pembungkus ganda dengan div dan pengaturan tinggi ke% 100 pada div pembungkus transparan bisa mengelabui untuk menghindari ini ...
Sam Saffron
@ SamSaffron Bisakah Anda menjelaskan bagaimana teknik seperti itu dapat bekerja? Saya mencoba beberapa hal seperti ini tanpa hasil. Karena tinggi dokumen itu ambigu, saya tidak yakin bagaimana itu bisa bekerja.
Alexander O'Mara
Saya pikir hanya memiliki pembungkus tinggi 100% "tetap" dapat mengatasi hal ini, mungkin tidak
Sam Saffron
@ downvoter: Apakah saya melakukan kesalahan? Saya setuju ini adalah solusi yang buruk, tetapi menurut saya tidak ada solusi yang lebih baik.
Alexander O'Mara
4
Ini tidak berhasil untuk saya, bidang masukan masih bergerak.
Rodrigo Ruiz
8

Saya menemukan metode yang berfungsi tanpa perlu mengubah ke posisi absolut!

Kode lengkap tanpa komentar

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);
    }
});

Memecahnya

Pertama, Anda harus memiliki kolom input tetap di bagian atas halaman di HTML (ini adalah elemen tetap sehingga secara semantik masuk akal untuk diletakkan di dekat bagian atas):

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

Maka Anda perlu menyimpan posisi gulir saat ini ke dalam variabel global:

//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;

Maka Anda memerlukan cara untuk mendeteksi perangkat iOS agar tidak memengaruhi hal-hal yang tidak perlu diperbaiki (fungsi diambil dari 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;
}

Sekarang kita memiliki semua yang kita butuhkan, inilah perbaikannya :)

//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);
    }
});
Daniel Tonon
sumber
+1. Ini adalah perbaikan yang jauh lebih rumit daripada jawaban yang diterima, jika Anda memiliki hierarki DOM yang tidak sepele. Ini seharusnya memiliki lebih banyak suara positif
Anson Kao
Bisakah Anda memberikan ini juga dalam JS asli? Terima kasih banyak!
mesqueeb
@ SamSaffron, apakah jawaban ini benar-benar berhasil untuk Anda? dapatkah saya memiliki beberapa contoh di sini. itu tidak berhasil untukku?
Ganesh Putta
@ SamSaffron, apakah jawaban ini benar-benar memecahkan masalah Anda, dapatkah Anda mengirim beberapa contoh yang berhasil untuk Anda, saya sedang mengerjakan hal yang sama, tetapi itu tidak berhasil untuk saya.
Ganesh Putta
@GaneshPutta Ada kemungkinan bahwa pembaruan iOS yang lebih baru telah membuat ini tidak berfungsi lagi. Saya memposting ini 2,5 tahun yang lalu. Seharusnya tetap berfungsi jika Anda mengikuti semua instruksi dengan tepat: /
Daniel Tonon
4

Saya dapat memperbaiki ini untuk input terpilih dengan menambahkan event listener ke elemen pilih yang diperlukan, lalu menggulir dengan offset satu piksel saat pilihan tersebut mendapatkan fokus.

Ini belum tentu merupakan solusi yang baik, tetapi jauh lebih sederhana dan lebih dapat diandalkan daripada jawaban lain yang pernah saya lihat di sini. Browser tampaknya merender ulang / menghitung ulang posisi: tetap; atribut berdasarkan offset yang disediakan di fungsi window.scrollBy ().

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

Seperti yang disarankan Mark Ryan Sallee, saya menemukan bahwa mengubah ketinggian dan luapan elemen latar belakang saya secara dinamis adalah kuncinya - ini tidak memberi Safari apa pun untuk digulir.

Jadi setelah animasi pembukaan modal selesai, ubah gaya latar belakang:

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

Saat Anda menutup modal, ubah kembali:

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

Sementara jawaban lain berguna dalam konteks yang lebih sederhana, DOM saya terlalu rumit (terima kasih SharePoint) untuk menggunakan pertukaran posisi absolut / tetap.

Matthew Levy
sumber
1

Rapi? tidak.

Saya baru-baru ini mengalami masalah ini sendiri dengan bidang pencarian tetap di tajuk yang lengket, hal terbaik yang dapat Anda lakukan saat ini adalah menjaga posisi gulir dalam variabel setiap saat dan setelah pemilihan, buatlah posisi elemen tetap absolut alih-alih tetap dengan bagian atas posisi berdasarkan posisi gulir dokumen.

Namun ini sangat jelek dan masih menghasilkan beberapa pengguliran bolak-balik yang aneh sebelum mendarat di tempat yang tepat, tetapi itu yang paling dekat yang bisa saya dapatkan.

Solusi lain apa pun akan melibatkan penggantian mekanisme gulir default browser.

Samuel
sumber
0

Belum menangani bug khusus ini, tapi mungkin menaruh overflow: hidden; di badan saat area teks terlihat (atau hanya aktif, bergantung pada desain Anda). Ini mungkin memiliki efek tidak memberikan browser "ke bawah" di mana pun untuk menggulir.

Mark Ryan Sallee
sumber
1
Saya bahkan tidak bisa mendapatkan sentuhan awal untuk memicu cukup awal bahkan untuk mempertimbangkan peretasan itu :(
Sam Saffron
0

Solusi yang mungkin adalah mengganti bidang masukan.

  • Pantau peristiwa klik pada div
  • memfokuskan bidang masukan tersembunyi untuk membuat keyboard
  • mereplikasi konten bidang input tersembunyi ke dalam bidang input palsu

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> 


codepen

davidcondrey.dll
sumber
0

Tak satu pun dari solusi ini berhasil untuk saya karena DOM saya rumit dan saya memiliki halaman gulir dinamis tak terbatas, jadi saya harus membuatnya sendiri.

Latar Belakang: Saya menggunakan tajuk tetap dan elemen lebih jauh ke bawah yang menempel di bawahnya setelah pengguna menggulir sejauh itu ke bawah. Elemen ini memiliki kolom input pencarian. Selain itu, saya memiliki halaman dinamis yang ditambahkan selama gulir maju dan mundur.

Masalah: Di iOS, setiap kali pengguna mengklik input di elemen tetap, browser akan menggulir hingga ke bagian atas halaman. Ini tidak hanya menyebabkan perilaku yang tidak diinginkan, tetapi juga memicu penambahan halaman dinamis saya di bagian atas halaman.

Solusi yang Diharapkan: Tidak ada scroll di iOS (tidak sama sekali) saat pengguna mengklik input di elemen sticky.

Larutan:

     /*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));

Persyaratan: JQuery seluler diperlukan agar fungsi startroll dan stopscroll berfungsi.

Debounce disertakan untuk memuluskan jeda yang dibuat oleh elemen lengket.

Diuji di iOS10.

Dima
sumber
0

Saya baru saja melompati sesuatu seperti ini kemarin dengan mengatur ketinggian #a ke tinggi maksimum yang terlihat (tinggi badan dalam kasus saya) ketika #b terlihat

ex:

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

ps: maaf untuk telat contoh, baru sadar itu dibutuhkan.

Onur Uyar
sumber
14
Harap sertakan contoh kode untuk memperjelas bagaimana perbaikan Anda dapat membantu
roo2
@EruPenkman maaf baru memperhatikan komentar Anda, semoga membantu.
Onur Uyar
0

Ini sekarang diperbaiki di iOS 10.3!

Peretasan seharusnya tidak lagi dibutuhkan.

Sam Saffron
sumber
1
Dapatkah Anda menunjukkan catatan rilis yang menunjukkan bahwa hal ini sedang diperbaiki?
bluepnume
Apple sangat tertutup, mereka menutup laporan bug saya, saya mengonfirmasi bahwa sekarang berfungsi dengan baik, hanya itu yang saya dapatkan :)
Sam Saffron
1
Saya masih memiliki masalah ini di iOS 11
zekia
Tidak, ini masih menjadi masalah bahkan di iOS 13.
Dmitriy Khudorozhkov
0

Saya memiliki masalah, di bawah baris kode menyelesaikannya untuk saya -

html{

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

}
Manoj Gorasya
sumber