固定元素获得焦点时,ios8中的Safari正在滚动屏幕


96

在IOS8 Safari中,有一个已修复位置错误的新错误。

如果您将焦点放在固定面板中的文本区域,则safari会将您滚动到页面底部。

这使各种UI都无法使用,因为您无法将文本一直输入到textareas中,而无需一直向下滚动页面并失去位置。

有什么办法可以彻底解决此错误?

#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
在#b上设置z-index会有所帮助吗?

1
z索引无济于事,也许有些花哨的没有op css转换对堆栈上下文很有帮助,不确定。
Sam Saffron


79
iOS Safari是新的IE
geedubb,2015年

4
@geedubb同意。任何将其默认浏览器版本与该操作系统绑定在一起的moronic OS都将受到过去7年困扰IE的问题的侵犯。
2015年

Answers:


58

基于对此问题的良好分析,我在CSS 中的htmlbody元素中使用了此元素:

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

我认为这对我来说很棒。


2
也为我工作。自从我在加载时操作DOM以来,这就搞砸了很多其他事情,因此我将其放入一个类中,并在DOM稳定之后将其添加到html正文中。诸如scrollTop之类的东西不能很好地工作(我正在执行自动滚动),但是同样,您可以在执行滚动操作时添加/删除类。尽管Safari团队的工作很差。
Amarsh

1
使用此选项的人可能还想transform: translateZ(0);stackoverflow.com/questions/7808110/…中
lkraav

1
这可以解决问题,但是如果您有动画,它们将看起来非常不稳定。将其包装在媒体查询中可能会更好。
mmla

在iOS 10.3上为我工作。
–quotsBro

它不能解决问题。当虚拟键盘显示时,您需要拦截滚动并将高度更改为特定值: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版本)上运行。不幸的是,我们尚不知道苹果何时会在iOS中解决此问题,但如果下一个主要版本未解决该问题,我将感到惊讶。


我想知道是否用div双重包装并将透明包装div上的高度设置为%100可以欺骗它避免这种情况...
Sam Saffron

@SamSaffron您能否阐明这种技术如何工作?我尝试了一些类似的尝试,但没有成功。由于文档的高度模棱两可,因此我不确定它如何工作。
亚历山大·奥玛拉

我当时只是想用一个“固定的” 100%高度的包装纸解决此问题,可能
无法解决

@downvoter:我做错了吗?我同意这是一个糟糕的解决方案,但我认为没有更好的解决方案。
Alexander O'Mara 2015年

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层次结构,则此解决方案比接受的答案要复杂得多。这应该有更多的支持
Anson Kao

您也可以在本机JS中提供吗?非常感谢!
mesqueeb

@SamSaffron,这个答案真的对你有用吗?我可以在这里举一些例子吗?这对我有用吗?
Ganesh Putta'2

@SamSaffron,这个答案是否真的解决了您的问题,您能否发送一些对您有用的示例,是否正在为您工作,但这确实对我有用。
Ganesh Putta'2

@GaneshPutta可能是更新的iOS更新导致此功能不再起作用。我在2.5年前发布了此帖子。但是,如果您完全按照所有说明进行操作,它仍然应该工作:/
Daniel Tonon

4

通过将事件侦听器添加到必要的select元素,然后在所讨论的select获得焦点时滚动一个像素的偏移量,我能够解决此问题。

这不一定是一个好的解决方案,但是它比我在这里看到的其他答案更简单,更可靠。浏览器似乎重新渲染/重新计算了位置:属性基于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

干净吗 没有。

我最近在粘性标头中有一个固定的搜索字段,因此遇到了这个问题,目前您能做的最好的事情就是始终将滚动位置保持在变量中,并在选择后将固定元素的位置设为绝对位置而不是用顶部固定位置基于文档的滚动位置。

但是,这非常难看,并且在到达正确的位置之前仍会导致一些奇怪的来回滚动,但这是我能获得的最接近的结果。

任何其他解决方案都将涉及覆盖浏览器的默认滚动机制。


1

现在,此问题已在iOS 10.3中修复!

不再需要黑客。


1
您是否可以指出任何已修正的发行说明?
bluepnume

苹果非常机密,他们关闭了我的错误报告,我确认它现在可以正常工作了,这就是我得到的全部:)
Sam Saffron

我在iOS 11上仍然遇到此问题
zekia

0

尚未处理此特定错误,但可能会出现溢出:隐藏;当文本区域可见时(或只是活动,取决于您的设计)在主体上。这可能会导致浏览器无法“向下”滚动到任何位置。


1
我什至似乎都无法尽早触发touchstart甚至考虑到该骇客事件:(
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> 


码笔


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 2015年

@EruPenkman很抱歉刚刚注意到您的评论,希望对您有所帮助。
Onur Uyar'3

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.