Safari trong ios8 đang cuộn màn hình khi các phần tử cố định lấy nét


96

Trong iOS8 Safari có một lỗi mới với vị trí đã được sửa.

Nếu bạn tập trung một vùng văn bản nằm trong một bảng điều khiển cố định, safari sẽ cuộn bạn xuống cuối trang.

Điều này làm cho tất cả các loại giao diện người dùng không thể hoạt động, vì bạn không có cách nào để nhập văn bản vào các textareas mà không phải cuộn hết trang của bạn xuống và mất vị trí của bạn.

Có cách nào để giải quyết lỗi này một cách sạch sẽ không?

#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
Việc đặt chỉ mục z trên #b có giúp ích gì không?
Ben

1
Chỉ mục z không có ích gì, có thể một số biến đổi không op css ưa thích sẽ nhiều với bối cảnh ngăn xếp, không chắc chắn.
Sam Saffron

1
cho ngữ cảnh ở đây là cuộc thảo luận về Discourse: meta.discourse.org/t/dealing-with-ios-8-ipad-mobile-safari-bugs/…
Sam Saffron

79
iOS safari là IE mới
geedubb

4
@geedubb đã đồng ý. bất kỳ hệ điều hành moronic nào gắn phiên bản trình duyệt mặc định của nó với hệ điều hành sẽ phạm lỗi với các vấn đề đã gây ra cho IE trong 7 năm qua.
dewd

Câu trả lời:


58

Dựa trên phân tích tốt về vấn đề này, tôi đã sử dụng điều này trong htmlbodycác phần tử trong css:

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

Tôi nghĩ nó làm việc rất tốt cho tôi.


2
đã làm việc cho tôi quá. Điều này làm hỏng rất nhiều thứ khác vì tôi đang thao tác DOM khi tải, vì vậy tôi đặt nó thành một lớp và thêm nó vào html, body sau khi DOM đã ổn định. Những thứ như scrollTop không hoạt động tốt (tôi đang thực hiện tự động cuộn), nhưng một lần nữa, bạn có thể thêm / xóa lớp trong khi thực hiện các thao tác cuộn. Tuy nhiên, công việc kém của một phần đội Safari.
Amarsh

1
Những người nhìn vào tùy chọn này cũng có thể muốn kiểm tra transform: translateZ(0);từ stackoverflow.com/questions/7808110/…
lkraav

1
Điều này giải quyết được vấn đề, nhưng nếu bạn có hình ảnh động, chúng sẽ trông rất lộn xộn. Có thể tốt hơn nếu gói nó trong một truy vấn phương tiện.
mmla

Làm việc cho tôi trên iOS 10.3.
trích dẫnBro

Nó không giải quyết được vấn đề. Bạn cần phải đánh chặn di chuyển khi chương trình bàn phím ảo lên và thay đổi chiều cao đến giá trị cụ thể: stackoverflow.com/a/46044341/84661
Brian Cannard

36

Giải pháp tốt nhất tôi có thể đưa ra là chuyển sang sử dụng position: absolute;lấy nét và tính toán vị trí của nó khi sử dụng position: fixed;. Bí quyết là focussự kiện xảy ra quá muộn, vì vậy touchstartphải được sử dụng.

Giải pháp trong câu trả lời này bắt chước hành vi chính xác mà chúng tôi đã có trong iOS 7 rất chặt chẽ.

Yêu cầu:

Phần bodytử phải có định vị để đảm bảo vị trí thích hợp khi phần tử chuyển sang vị trí tuyệt đối.

body {
    position: relative;
}

( Ví dụ trực tiếp ):

Đoạn mã sau là một ví dụ cơ bản cho trường hợp thử nghiệm được cung cấp và có thể được điều chỉnh cho trường hợp sử dụng cụ thể của bạn.

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

Đây là mã tương tự không có hack để so sánh .

Cảnh báo:

Nếu position: fixed;phần tử có bất kỳ phần tử mẹ nào khác có định vị bên cạnh body, việc chuyển sang position: absolute;có thể có hành vi không mong muốn. Do bản chất của position: fixed;điều này có lẽ không phải là một vấn đề lớn, vì việc lồng các phần tử như vậy không phổ biến.

Khuyến nghị:

Mặc dù việc sử dụng touchstartsự kiện sẽ lọc ra hầu hết các môi trường máy tính để bàn, nhưng có thể bạn sẽ muốn sử dụng tính năng hít tác nhân người dùng để mã này chỉ chạy cho iOS 8 bị hỏng chứ không phải các thiết bị khác như Android và các phiên bản iOS cũ hơn. Thật không may, chúng ta vẫn chưa biết khi nào Apple sẽ khắc phục sự cố này trong iOS, nhưng tôi sẽ rất ngạc nhiên nếu nó không được khắc phục trong phiên bản chính tiếp theo.


Tôi tự hỏi nếu gói đôi với một div và thiết lập chiều cao đến 100% trên gói div minh bạch có thể lừa nó vào tránh này ...
Sam Saffron

@SamSaffron Bạn có thể làm rõ kỹ thuật như vậy có thể hoạt động như thế nào không? Tôi đã thử một vài điều như thế này mà không thành công. Vì chiều cao của tài liệu không rõ ràng, tôi không chắc nó có thể hoạt động như thế nào.
Alexander O'Mara

Tôi đã suy nghĩ đơn giản là có một "cố định" 100% chiều cao bao bọc có thể làm việc xung quanh này, có thể không
Sam Saffron

@downvoter: Tôi có gặp lỗi gì không? Tôi đồng ý rằng đây là một giải pháp tồi tệ, nhưng tôi không nghĩ có giải pháp nào tốt hơn.
Alexander O'Mara

4
Điều này không hiệu quả với tôi, trường đầu vào vẫn di chuyển.
Rodrigo Ruiz

8

Tôi đã tìm thấy một phương pháp hoạt động mà không cần thay đổi vị trí tuyệt đối!

Toàn bộ mã không có chú thích

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

Phá vỡ nó

Trước tiên, bạn cần phải có trường nhập cố định ở đầu trang trong HTML (đó là một phần tử cố định, vì vậy, về mặt ngữ nghĩa vẫn nên để nó gần đầu trang):

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

Sau đó, bạn cần lưu vị trí cuộn hiện tại vào các biến toàn cục:

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

Sau đó, bạn cần một cách để phát hiện thiết bị iOS để nó không ảnh hưởng đến những thứ không cần sửa (chức năng được lấy từ 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;
}

Bây giờ chúng tôi có mọi thứ chúng tôi cần, đây là bản sửa lỗi :)

//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. Đây là một bản sửa lỗi ít phức tạp hơn đáng kể so với câu trả lời được chấp nhận, nếu bạn có hệ thống phân cấp DOM không tầm thường. Điều này sẽ có nhiều phiếu tán thành hơn
Anson Kao

Bạn có thể cung cấp điều này trong JS gốc không? Cám ơn rất nhiều!
mesqueeb

@SamSaffron, câu trả lời này có thực sự phù hợp với bạn không? tôi có thể có một số ví dụ ở đây. nó không làm việc cho tôi?
Ganesh Putta

@ SamSaffron, câu trả lời này có thực sự giải quyết được vấn đề của bạn không, bạn có thể gửi một số ví dụ phù hợp với bạn không, tôi đang làm việc giống nhau, nhưng nó không hiệu quả với tôi.
Ganesh Putta

@GaneshPutta Có thể bản cập nhật iOS mới hơn đã khiến tính năng này không hoạt động nữa. Tôi đã đăng điều này 2,5 năm trước. Nó vẫn nên làm việc mặc dù nếu bạn theo tất cả các hướng dẫn chính xác: /
Daniel Tonon

4

Tôi có thể khắc phục điều này cho các đầu vào được chọn bằng cách thêm trình xử lý sự kiện vào các phần tử được chọn cần thiết, sau đó cuộn theo độ lệch của một pixel khi lựa chọn được đề cập đạt tiêu điểm.

Đây không hẳn là một giải pháp tốt, nhưng nó đơn giản và đáng tin cậy hơn nhiều so với các câu trả lời khác mà tôi đã thấy ở đây. Trình duyệt dường như kết xuất lại / tính toán lại vị trí: fixed; dựa trên độ lệch được cung cấp trong hàm window.scrollBy ().

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

2

Giống như Mark Ryan Sallee đã đề xuất, tôi thấy rằng thay đổi động chiều cao và tràn của phần tử nền của tôi là chìa khóa - điều này khiến Safari không có gì để cuộn đến.

Vì vậy, sau khi hoạt ảnh mở đầu của phương thức kết thúc, hãy thay đổi kiểu của nền:

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

Khi bạn đóng phương thức, hãy thay đổi nó trở lại:

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

Trong khi các câu trả lời khác hữu ích trong các ngữ cảnh đơn giản hơn, DOM của tôi quá phức tạp (cảm ơn SharePoint) để sử dụng hoán đổi vị trí tuyệt đối / cố định.


1

Sạch sẽ? Không.

Gần đây tôi đã tự mình gặp sự cố này với trường tìm kiếm cố định trong tiêu đề cố định, điều tốt nhất bạn có thể làm vào lúc này là giữ vị trí cuộn trong một biến mọi lúc và khi chọn, hãy đặt vị trí của phần tử cố định là tuyệt đối thay vì cố định bằng một đầu vị trí dựa trên vị trí cuộn của tài liệu.

Tuy nhiên, điều này rất xấu và vẫn dẫn đến một số thao tác cuộn qua lại kỳ lạ trước khi hạ cánh đúng nơi, nhưng đó là cách gần nhất mà tôi có thể nhận được.

Bất kỳ giải pháp nào khác sẽ liên quan đến việc ghi đè cơ chế cuộn mặc định của trình duyệt.


1

Điều này hiện đã được khắc phục trong iOS 10.3!

Hack không còn cần thiết nữa.


1
Bạn có thể chỉ ra bất kỳ ghi chú phát hành nào cho thấy điều này đã được sửa không?
bluepnume

Apple là rất bí mật, họ đóng cửa báo cáo lỗi của tôi, tôi khẳng định nó hiện đang làm việc đúng đắn, đó là tất cả tôi nhận :)
Sam Saffron

Tôi vẫn gặp sự cố này trên iOS 11
zekia

0

Chưa xử lý lỗi cụ thể này, nhưng có thể đặt một lỗi tràn: ẩn; trên nội dung khi vùng văn bản hiển thị (hoặc chỉ hoạt động, tùy thuộc vào thiết kế của bạn). Điều này có thể có tác dụng không cho trình duyệt "xuống" bất kỳ nơi nào để cuộn đến.


1
Tôi thậm chí không thể có vẻ để có được touchstart để kích hoạt đủ sớm để thậm chí xem xét có hack :(
Sam Saffron

0

Một giải pháp khả thi là thay thế trường đầu vào.

  • Giám sát các sự kiện nhấp chuột trên div
  • tập trung vào một trường nhập ẩn để hiển thị bàn phím
  • sao chép nội dung của trường nhập ẩn vào trường nhập giả

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


0

Không có giải pháp nào trong số này phù hợp với tôi vì DOM của tôi phức tạp và tôi có các trang cuộn vô hạn động, vì vậy tôi phải tạo của riêng mình.

Thông tin cơ bản: Tôi đang sử dụng một tiêu đề cố định và một phần tử nằm sâu bên dưới nó khi người dùng cuộn xuống phía dưới. Phần tử này có một trường đầu vào tìm kiếm. Ngoài ra, tôi có các trang động được thêm vào khi cuộn tới và cuộn ngược.

Vấn đề: Trong iOS, bất cứ lúc nào người dùng nhấp vào dữ liệu nhập trong phần tử cố định, trình duyệt sẽ cuộn đến đầu trang. Điều này không chỉ gây ra hành vi không mong muốn mà còn kích hoạt thêm trang động của tôi ở đầu trang.

Giải pháp mong đợi: Không có cuộn trong iOS (không có cuộn nào cả) khi người dùng nhấp vào đầu vào trong phần tử cố định.

Giải pháp:

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

Yêu cầu: Cần có JQuery mobile để các chức năng startroll và stopcroll hoạt động.

Debounce được bao gồm để làm phẳng mọi độ trễ do yếu tố dính tạo ra.

Đã thử nghiệm trong iOS10.


0

Tôi vừa nhảy qua một cái gì đó như thế này hôm qua bằng cách đặt chiều cao #a thành chiều cao hiển thị tối đa (trong trường hợp của tôi là chiều cao cơ thể) khi #b hiển thị

Ví dụ:

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

ps: xin lỗi vì ví dụ muộn, chỉ cần nhận thấy nó là cần thiết.


14
Vui lòng bao gồm một ví dụ về mã để làm rõ cách sửa lỗi của bạn có thể giúp ích gì
roo2

@EruPenkman xin lỗi chỉ nhận thấy bình luận của bạn, hy vọng rằng sẽ giúp.
Onur Uyar

0

Tôi đã gặp sự cố, các dòng mã dưới đây đã giải quyết cho tôi -

html{

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

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.