在 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>
基于这个问题的 good analysis,我在 css 的 html
和 body
元素中使用了它:
html,body{
-webkit-overflow-scrolling : touch !important;
overflow: auto !important;
height: 100% !important;
}
我认为这对我很有用。
我能想到的最佳解决方案是切换到使用 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);
});
Here is the same code without the hack for comparison。
警告:
如果 position: fixed;
元素具有除 body
之外的任何其他定位父元素,则切换到 position: absolute;
可能会出现意外行为。由于 position: fixed;
的性质,这可能不是主要问题,因为嵌套此类元素并不常见。
建议:
虽然使用 touchstart
事件将过滤掉大多数桌面环境,但您可能希望使用用户代理嗅探,以便此代码仅在损坏的 iOS 8 上运行,而不是在其他设备上运行,例如 Android 和旧 iOS 版本.不幸的是,我们还不知道 Apple 什么时候会在 iOS 中修复这个问题,但如果它没有在下一个主要版本中修复,我会感到惊讶。
我找到了一种无需更改为绝对位置即可工作的方法!
完整的未注释代码
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);
}
});
我可以通过将事件侦听器添加到必要的选择元素来解决选择输入的问题,然后在相关选择获得焦点时滚动一个像素的偏移量。
这不一定是一个好的解决方案,但它比我在这里看到的其他答案更简单、更可靠。浏览器似乎重新渲染/重新计算位置:固定;属性基于 window.scrollBy() 函数中提供的偏移量。
document.querySelector(".someSelect select").on("focus", function() {window.scrollBy(0, 1)});
就像 Mark Ryan Sallee 建议的那样,我发现动态改变背景元素的高度和溢出是关键——这让 Safari 没有什么可滚动的。
所以在模态的打开动画完成后,改变背景的样式:
$('body > #your-background-element').css({
'overflow': 'hidden',
'height': 0
});
当您关闭模式时,将其改回:
$('body > #your-background-element').css({
'overflow': 'auto',
'height': 'auto'
});
虽然其他答案在更简单的上下文中很有用,但我的 DOM 太复杂(感谢 SharePoint),无法使用绝对/固定位置交换。
干净吗?不。
我最近自己在粘性标题中使用固定搜索字段时遇到了这个问题,目前您可以做的最好的事情是始终将滚动位置保持在变量中,并在选择时使固定元素的位置绝对而不是用顶部固定位置基于文档的滚动位置。
然而,这非常难看,并且在降落到正确的位置之前仍然会导致一些奇怪的来回滚动,但这是我能得到的最接近的。
任何其他解决方案都将涉及覆盖浏览器的默认滚动机制。
还没有处理这个特定的错误,但也许放了一个溢出:隐藏;当文本区域可见时(或仅处于活动状态,取决于您的设计)在正文上。这可能具有不让浏览器“向下”滚动到任何位置的效果。
一个可能的解决方案是替换输入字段。
监控 div 上的点击事件
聚焦隐藏的输入字段以呈现键盘
将隐藏输入字段的内容复制到假输入字段中
功能焦点(){ $('#hiddeninput').focus(); } $(document.body).load(focus); $('.fakeinput').bind("click",function() { focus(); }); $("#hiddeninput").bind("keyup blur", function (){ $('.fakeinput .placeholder').html(this.value); }); #hiddeninput { 位置:固定;顶部:0;左侧:-100vw;不透明度:0;高度:0px;宽度:0; } #hiddeninput:focus{ 大纲:无; } .fakeinput { 宽度:80vw;边距:15px 自动;高度:38px;边框:1px 实心#000;颜色:#000;字体大小:18px;填充:12px 15px 10px;显示:块;溢出:隐藏; } .placeholder { 不透明度:0.6;垂直对齐:中间; }
这些解决方案都不适合我,因为我的 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 才能使startsroll 和stopscroll 功能正常工作。
包含去抖动以消除粘性元素产生的任何滞后。
在 iOS10 中测试。
当#b可见时,我昨天刚刚通过将#a的高度设置为最大可见高度(在我的情况下是身体高度)来跳过这样的事情
前任:
<script>
document.querySelector('#b').addEventListener('focus', function () {
document.querySelector('#a').style.height = document.body.clientHeight;
})
</script>
ps:对不起,后来的例子,只是注意到它是需要的。
这已在 iOS 10.3 中修复!
黑客应该不再需要。
我遇到了问题,下面的代码行为我解决了它-
html{
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
transform: translateZ(0);