Safari ใน ios8 กำลังเลื่อนหน้าจอเมื่อองค์ประกอบคงที่ได้รับโฟกัส


96

ใน IOS8 Safari มีข้อผิดพลาดใหม่พร้อมตำแหน่งที่แก้ไข

หากคุณโฟกัสพื้นที่ข้อความที่อยู่ในแผงคงที่ Safari จะเลื่อนคุณไปที่ด้านล่างของหน้า

สิ่งนี้ทำให้ UI ทุกประเภทไม่สามารถใช้งานได้เนื่องจากคุณไม่มีวิธีป้อนข้อความลงในพื้นที่ข้อความโดยไม่ต้องเลื่อนหน้าลงจนสุดและสูญเสียตำแหน่งของคุณ

มีวิธีใดในการแก้ไขข้อบกพร่องนี้อย่างหมดจดหรือไม่?

#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-index ใน #b help หรือไม่?
เบ็น

1
ดัชนี z ไม่สามารถช่วยได้บางทีบางคนอาจไม่มีการแปลง op css มากนักกับบริบทสแต็กไม่แน่ใจ
Sam Saffron

1
สำหรับบริบทต่อไปนี้คือการอภิปรายใน Discourse: meta.discourse.org/t/dealing-with-ios-8-ipad-mobile-safari-bugs/…
Sam Saffron

80
iOS Safari คือ IE ใหม่
geedubb

4
@geedubb เห็นด้วย ระบบปฏิบัติการที่ผิดปกติใด ๆ ที่เชื่อมโยงเวอร์ชันเบราว์เซอร์เริ่มต้นกับระบบปฏิบัติการจะตกอยู่ในปัญหาที่รบกวน IE ในช่วง 7 ปีที่ผ่านมา
น้ำค้าง

คำตอบ:


58

จากการวิเคราะห์ที่ดีของปัญหานี้ฉันได้ใช้สิ่งนี้ในhtmlและbodyองค์ประกอบใน css:

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

ฉันคิดว่ามันทำงานได้ดีสำหรับฉัน


2
ทำงานให้ฉันด้วย สิ่งนี้ทำให้สิ่งอื่น ๆ สับสนมากมายเนื่องจากฉันจัดการ DOM ของฉันเมื่อโหลดดังนั้นฉันจึงทำให้สิ่งนี้เป็นคลาสและเพิ่มลงใน html ร่างกายหลังจากที่ DOM เสถียรแล้ว สิ่งต่างๆเช่น scrollTop ทำงานได้ไม่ดีนัก (ฉันกำลังทำการเลื่อนอัตโนมัติ) แต่อีกครั้งคุณสามารถเพิ่ม / ลบคลาสในขณะที่ทำการเลื่อนได้ ทำงานได้ไม่ดีในส่วนหนึ่งของทีม Safari
Amarsh

1
คนที่ดูตัวเลือกนี้อาจต้องการทดสอบtransform: translateZ(0);จากstackoverflow.com/questions/7808110/…
lkraav

1
วิธีนี้ช่วยแก้ปัญหาได้ แต่ถ้าคุณมีภาพเคลื่อนไหวมันจะดูขาด ๆ หาย ๆ อาจจะดีกว่าที่จะรวมไว้ในแบบสอบถามสื่อ
mmla

ทำงานให้ฉันบน iOS 10.3
quotesBro

มันไม่ได้แก้ปัญหา คุณต้องสกัดกั้นการเลื่อนเมื่อแป้นพิมพ์เสมือนปรากฏขึ้นและเปลี่ยนความสูงเป็นค่าเฉพาะ: stackoverflow.com/a/46044341/84661
Brian 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 การห่อแบบโปร่งใสอาจหลอกล่อให้หลีกเลี่ยงสิ่งนี้ ...
Sam Saffron

@SamSaffron คุณช่วยอธิบายได้ไหมว่าเทคนิคดังกล่าวได้ผลอย่างไร? ฉันลองทำบางสิ่งเช่นนี้แล้วไม่ประสบความสำเร็จ เนื่องจากความสูงของเอกสารไม่ชัดเจนฉันจึงไม่แน่ใจว่าจะใช้งานได้อย่างไร
Alexander O'Mara

ฉันคิดว่าแค่มีกระดาษห่อหุ้มที่มีความสูง 100% แบบ "คงที่" อาจจะแก้ปัญหานี้ได้อาจจะไม่ใช่
Sam Saffron

@downvoter: ฉันทำอะไรผิดพลาดหรือเปล่า? ฉันยอมรับว่านี่เป็นวิธีแก้ปัญหาที่แย่มาก แต่ฉันไม่คิดว่าจะมีวิธีใดดีกว่านี้
Alexander O'Mara

4
สิ่งนี้ไม่ได้ผลสำหรับฉันช่องป้อนข้อมูลยังคงเคลื่อนไหว
Rodrigo Ruiz

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 ที่ไม่สำคัญ สิ่งนี้น่าจะมีการโหวตเพิ่มขึ้น
Anson Kao

คุณสามารถระบุสิ่งนี้ใน JS ดั้งเดิมได้หรือไม่ ขอบคุณมาก!
mesqueeb

@ SamSaffron คำตอบนี้ใช้ได้ผลกับคุณจริงหรือ? ฉันขอตัวอย่างที่นี่ได้ไหม มันเหมาะกับฉันไหม
Ganesh Putta

@ SamSaffron คำตอบนี้ช่วยแก้ปัญหาของคุณได้จริงไหมคุณสามารถส่งตัวอย่างที่ใช้ได้ผลสำหรับคุณกำลังทำงานเหมือนกัน แต่มันก็ใช้ได้สำหรับฉัน
Ganesh Putta

@GaneshPutta เป็นไปได้ว่าการอัปเดต iOS ล่าสุดทำให้ไม่สามารถใช้งานได้อีกต่อไป ฉันโพสต์สิ่งนี้เมื่อ 2.5 ปีที่แล้ว แม้ว่าคุณจะทำตามคำแนะนำทั้งหมดอย่างถูกต้อง: /
Daniel Tonon

4

ฉันสามารถแก้ไขสิ่งนี้สำหรับอินพุตที่เลือกได้โดยการเพิ่มตัวฟังเหตุการณ์ให้กับองค์ประกอบการเลือกที่จำเป็นจากนั้นเลื่อนโดยการชดเชยหนึ่งพิกเซลเมื่อสิ่งที่เลือกนั้นได้รับโฟกัส

นี่ไม่ใช่วิธีแก้ปัญหาที่ดี แต่ง่ายกว่าและน่าเชื่อถือกว่าคำตอบอื่น ๆ ที่ฉันเคยเห็นที่นี่ ดูเหมือนว่าเบราว์เซอร์จะแสดงผล / คำนวณตำแหน่งใหม่อีกครั้ง: คงที่; แอตทริบิวต์ตามออฟเซ็ตที่ให้มาในฟังก์ชัน window.scrollBy ()

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

2

เหมือนที่ Mark Ryan Sallee แนะนำฉันพบว่าการเปลี่ยนความสูงและการล้นขององค์ประกอบพื้นหลังแบบไดนามิกเป็นกุญแจสำคัญซึ่งทำให้ Safari ไม่มีอะไรให้เลื่อน

ดังนั้นหลังจากอนิเมชั่นการเปิดของโมดอลเสร็จสิ้นให้เปลี่ยนสไตล์ของพื้นหลัง:

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

เมื่อคุณปิดโมดอลให้เปลี่ยนกลับ:

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

แม้ว่าคำตอบอื่น ๆ จะมีประโยชน์ในบริบทที่ง่ายกว่า แต่ DOM ของฉันก็ซับซ้อนเกินไป (ขอบคุณ SharePoint) ที่จะใช้การสลับตำแหน่งแบบสัมบูรณ์ / คงที่


1

สะอาด? ไม่.

ฉันเพิ่งประสบปัญหานี้ด้วยตัวเองด้วยช่องค้นหาคงที่ในส่วนหัวที่เหนียวสิ่งที่ดีที่สุดที่คุณสามารถทำได้ในขณะนี้คือรักษาตำแหน่งการเลื่อนในตัวแปรตลอดเวลาและเมื่อเลือกให้ตำแหน่งขององค์ประกอบคงที่เป็นค่าสัมบูรณ์แทนที่จะแก้ไขด้วยด้านบน ตำแหน่งตามตำแหน่งเลื่อนของเอกสาร

อย่างไรก็ตามนี่เป็นสิ่งที่น่าเกลียดมากและยังคงส่งผลให้มีการเลื่อนไปมาแปลก ๆ ก่อนที่จะลงจอดในตำแหน่งที่ถูกต้อง แต่มันใกล้เคียงที่สุดที่ฉันจะได้รับ

วิธีแก้ปัญหาอื่น ๆ จะเกี่ยวข้องกับการลบล้างกลไกการเลื่อนเริ่มต้นของเบราว์เซอร์


0

ยังไม่ได้จัดการกับข้อผิดพลาดนี้ แต่อาจทำให้ล้น: hidden; บนเนื้อความเมื่อมองเห็นพื้นที่ข้อความ (หรือใช้งานได้ขึ้นอยู่กับการออกแบบของคุณ) ซึ่งอาจมีผลทำให้เบราว์เซอร์ไม่ "ลง" เพื่อเลื่อนไปที่ใดก็ได้


1
ดูเหมือนว่าฉันจะไม่สามารถแตะเพื่อเริ่มต้นได้เร็วพอที่จะพิจารณาการแฮ็กนั้น :(
Sam Saffron

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> 


codepen


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 และ stopscroll ในการทำงาน

Debounce ถูกรวมไว้เพื่อขจัดความล่าช้าใด ๆ ที่สร้างขึ้นโดยองค์ประกอบ Sticky

ทดสอบใน iOS10


0

ฉันเพิ่งกระโดดข้ามอะไรแบบนี้เมื่อวานนี้โดยตั้งค่าความสูง #a เป็นความสูงสูงสุดที่มองเห็นได้ (ความสูงของร่างกายอยู่ในกรณีของฉัน) เมื่อ #b มองเห็นได้

เช่น:

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

ps: ขออภัยที่มาช้าเพิ่งสังเกตเห็นว่าจำเป็น


14
โปรดใส่ตัวอย่างโค้ดเพื่อให้ชัดเจนว่าการแก้ไขของคุณสามารถช่วยได้อย่างไร
roo2

@EruPenkman ขอโทษที่เพิ่งสังเกตความคิดเห็นของคุณหวังว่าจะช่วยได้
Onur Uyar

0

ตอนนี้ได้รับการแก้ไขแล้วใน iOS 10.3!

ไม่จำเป็นต้องใช้ Hacks อีกต่อไป


1
คุณสามารถชี้ไปที่บันทึกประจำรุ่นที่ชี้ให้เห็นว่าได้รับการแก้ไขแล้วหรือไม่
bluepnume

Apple เป็นความลับมากพวกเขาปิดรายงานข้อผิดพลาดของฉันฉันยืนยันว่าตอนนี้ใช้งานได้ดีนั่นคือทั้งหมดที่ฉันได้รับ :)
Sam Saffron

1
ฉันยังคงมีปัญหานี้ใน iOS 11
zekia

ไม่นี่ยังคงเป็นปัญหาแม้ใน iOS 13
Dmitriy Khudorozhkov

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.