【问题标题】:Safari in ios8 is scrolling screen when fixed elements get focus当固定元素获得焦点时,ios8 中的 Safari 正在滚动屏幕
【发布时间】:2015-05-14 03:34:51
【问题描述】:

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

如果您将焦点放在固定面板中的文本区域,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>

【问题讨论】:

  • 对 #b 设置 z-index 有帮助吗?
  • z 索引没有帮助,也许一些花哨的无操作 css 转换对堆栈上下文有很大影响,不确定。
  • iOS safari 是新的 IE
  • @geedubb 同意。任何将其默认浏览器版本与操作系统绑定的愚蠢操作系统都会与过去 7 年来困扰 IE 的问题发生冲突。

标签: javascript ios css safari ios8


【解决方案1】:

基于this issue的good analysis,我在css中的htmlbody元素中使用了这个:

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

我认为它对我很有用。

【讨论】:

  • 也为我工作。这搞砸了很多其他事情,因为我在加载时操作我的 DOM,所以我把它变成一个类,并在 DOM 稳定后将它添加到 html 正文中。 scrollTop 之类的东西效果不佳(我正在做自动滚动),但同样,您可以在进行滚动操作时添加/删除类。不过 Safari 团队的一部分工作很糟糕。
  • 查看此选项的人可能还想从stackoverflow.com/questions/7808110/… 测试transform: translateZ(0);
  • 这解决了问题,但如果你有动画,它们看起来会很不稳定。将其包装在媒体查询中可能会更好。
  • 在 iOS 10.3 上为我工作。
  • 它不能解决问题。您需要在虚拟键盘出现时拦截滚动并将高度更改为特定值:stackoverflow.com/a/46044341/84661
【解决方案2】:

我能想到的最佳解决方案是切换到使用position: absolute; 聚焦并计算它在使用position: fixed; 时的位置。诀窍是focus 事件触发得太晚,所以必须使用touchstart

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

要求:

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

body {
    position: relative;
}

The Code (Live 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 中修复这个问题,但如果它没有在下一个主要版本中修复,我会感到惊讶。

【讨论】:

  • 我想知道是否使用 div 进行双重包装并将透明包装 div 的高度设置为 %100 是否可以欺骗它避免这种情况...
  • @SamSaffron 你能解释一下这种技术是如何工作的吗?我尝试了一些这样的事情但没有成功。由于文档的高度不明确,我不确定它是如何工作的。
  • 我在想只是有一个“固定”的 100% 高度包装器可以解决这个问题,可能不是
  • @downvoter:我是不是搞错了什么?我同意这是一个糟糕的解决方案,但我认为没有更好的解决方案。
  • 这对我不起作用,输入字段仍在移动。
【解决方案3】:

我找到了一种方法,无需更改为绝对位置!

完整的未注释代码

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 层次结构,那么这比公认的答案要简单得多。这应该有更多的赞成
  • 你能在原生 JS 中也提供这个吗?非常感谢!
  • @SamSaffron,这个答案真的对你有用吗?我可以在这里举一些例子吗?它对我有用吗?
  • @SamSaffron,这个答案真的解决了你的问题吗,你能发一些对你有用的例子吗,我也在做同样的事情,但它对我有用。
  • @GaneshPutta 最近的 iOS 更新可能使此功能不再起作用。我在 2.5 年前发布了这个。如果您完全按照所有说明操作,它仍然可以工作:/
【解决方案4】:

我可以通过向必要的选择元素添加事件侦听器来解决选择输入的问题,然后在相关选择获得焦点时滚动一个像素的偏移量。

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

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

【讨论】:

    【解决方案5】:

    就像 Mark Ryan Sallee 建议的那样,我发现动态改变我的 背景元素 的高度和溢出是关键 - 这让 Safari 无法滚动。

    所以在模态框的打开动画完成后,改变背景的样式:

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

    当您关闭模态时,将其改回:

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

    虽然其他答案在更简单的上下文中很有用,但我的 DOM 过于复杂(感谢 SharePoint),无法使用绝对/固定位置交换。

    【讨论】:

      【解决方案6】:

      干净吗?没有。

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

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

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

      【讨论】:

        【解决方案7】:

        还没有处理这个特定的错误,但可能放了一个溢出:隐藏;当文本区域可见时(或仅处于活动状态,取决于您的设计)在正文上。这可能会导致浏览器无法“向下”滚动到任何位置。

        【讨论】:

        • 我什至无法让 touchstart 足够早地触发,甚至考虑到该 hack :(
        【解决方案8】:

        一个可能的解决方案是替换输入字段。

        • 监控 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

        【讨论】:

          【解决方案9】:

          这些解决方案都不适合我,因为我的 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 中测试。

          【讨论】:

            【解决方案10】:

            我昨天刚刚跳过了这样的事情,当 #b 可见时,将 #a 的高度设置为最大可见高度(在我的情况下是身体高度)

            例如:

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

            ps:抱歉后面的例子,只是注意到它是需要的。

            【讨论】:

            • 请包含一个代码示例,以明确您的修复如何提供帮助
            • @EruPenkman 抱歉刚刚注意到您的评论,希望对您有所帮助。
            【解决方案11】:

            这已在 iOS 10.3 中得到修复!

            黑客应该不再需要。

            【讨论】:

            • 您能否指出任何指出此问题已修复的发行说明?
            • Apple 非常保密,他们关闭了我的错误报告,我确认它现在可以正常工作了,这就是我得到的一切 :)
            • 我在 iOS 11 上仍然有这个问题
            • 不,即使在 iOS 13 上这仍然是一个问题。
            【解决方案12】:

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

            html{
            
             overflow: scroll; 
            -webkit-overflow-scrolling: touch;
            
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-10-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-05-21
              • 2022-12-31
              • 2011-10-21
              • 2019-09-13
              相关资源
              最近更新 更多