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

时间:2015-03-12 04:39:09

标签: javascript ios css safari ios8

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

如果您聚焦固定面板中的文本区域,Safari会将您滚动到页面底部。

这使得各种UI无法使用,因为您无法在不滚动页面并丢失位置的情况下将文本输入textareas。

有没有办法干净地解决这个bug?

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

12 个答案:

答案 0 :(得分:53)

根据此问题的good analysis,我在css中的htmlbody元素中使用了这个:

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

我认为它对我有用。

答案 1 :(得分:34)

我能想出的最佳解决方案是切换到焦点上使用position: absolute;并计算使用position: fixed;时的位置。诀窍是focus事件触发太晚,因此必须使用touchstart

此答案中的解决方案模仿了我们在iOS 7中的正确行为。

的要求:

body元素必须具有定位,以便在元素切换到绝对定位时确保正确定位。

body {
    position: relative;
}

The CodeLive Example):

以下代码是所提供测试用例的基本示例,可以根据您的具体用例进行调整。

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

Here is the same code without the hack for comparison

警告:

如果position: fixed;元素具有除body之外的任何其他父元素,则切换到position: absolute;可能会出现意外行为。由于position: fixed;的性质,这可能不是一个主要问题,因为嵌套这些元素并不常见。

建议:

虽然使用touchstart事件会过滤掉大多数桌面环境,但您可能希望使用用户代理嗅探,以便此代码仅针对损坏的iOS 8运行,而不是针对其他设备(如Android和较旧的iOS版本。不幸的是,我们还不知道Apple何时会在iOS中解决这个问题,但如果在下一个主要版本中没有修复它,我会感到惊讶。

答案 2 :(得分: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);
    }
});

答案 3 :(得分:4)

我能够通过向必要的选择元素添加事件侦听器来修复选择输入,然后在有问题的选择获得焦点时滚动一个像素的偏移量。

这不一定是一个好的解决方案,但它比我在这里看到的其他答案更简单,更可靠。浏览器似乎重新渲染/重新计算位置:固定;属性基于window.scrollBy()函数中提供的偏移量。

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

答案 4 :(得分: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)使用绝对/固定位置交换。

答案 5 :(得分:2)

现在已经在iOS 10.3中修复了!

不再需要黑客。

答案 6 :(得分:1)

干净地?号

我最近在粘性标题中使用固定搜索字段时出现此问题,此时您可以做的最好是始终将滚动位置保留在变量中并且在选择时使固定元素的位置绝对而不是固定具有基于文档滚动位置的顶部位置。

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

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

答案 7 :(得分:0)

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

答案 8 :(得分:0)

当#b可见时,我只是通过将#a的高度设置为最大可见高度(身高就在我的情况下)来跳过这样的事情

前:

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

ps:对不起,最近的例子,只是注意到它是必要的。

答案 9 :(得分: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

答案 10 :(得分: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中测试。

答案 11 :(得分:0)

我遇到了问题,下面的代码行为我解决了这个问题 -

html{

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

}