Javascript 当固定元素获得焦点时,ios8 中的 Safari 正在滚动屏幕
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29001977/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Safari in ios8 is scrolling screen when fixed elements get focus
提问by Sam Saffron
In IOS8 Safari there is a new bug with position fixed.
在 IOS8 Safari 中,有一个位置固定的新错误。
If you focus a textarea that is in a fixed panel, safari will scroll you to the bottom of the page.
如果您聚焦固定面板中的文本区域,safari 会将您滚动到页面底部。
This makes all sorts of UIs impossible to work with, since you have no way of entering text into textareas without scrolling your page all the way down and losing your place.
这使得各种 UI 无法使用,因为您无法在不向下滚动页面并丢失位置的情况下将文本输入到 textarea 中。
Is there any way to workaround this bug cleanly?
有没有办法干净地解决这个错误?
#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>
采纳答案by Sam Saffron
This is now fixed in iOS 10.3!
现在已在 iOS 10.3 中修复!
Hacks should no longer be needed.
不再需要黑客攻击。
回答by Mohammad AlBanna
Based on this good analysisof this issue, I've used this in htmland bodyelements in css:
基于对这个问题的很好的分析,我在 css 中使用了这个html和body元素:
html,body{
-webkit-overflow-scrolling : touch !important;
overflow: auto !important;
height: 100% !important;
}
I think it's working great for me.
我认为它对我很有用。
回答by Alexander O'Mara
The best solution I could come up with is to switch to using position: absolute;on focus and calculating the position it was at when it was using position: fixed;. The trick is that the focusevent fires too late, so touchstartmust be used.
我能想到的最佳解决方案是切换到使用position: absolute;焦点并计算它在使用position: fixed;. 诀窍是focus事件触发得太晚,所以touchstart必须使用。
The solution in this answer mimics the correct behavior we had in iOS 7 very closely.
此答案中的解决方案非常接近地模仿了我们在 iOS 7 中的正确行为。
Requirements:
要求:
The bodyelement must have positioning in order to ensure proper positioning when the element switches to absolute positioning.
的body元件必须具有定位以确保正确的定位时,元件切换到绝对定位。
body {
position: relative;
}
The Code(Live Example):
代码(现场示例):
The following code is a basic example for the provided test-case, and can be adapted for your specific use-case.
以下代码是提供的测试用例的基本示例,可以针对您的特定用例进行调整。
//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.
Caveat:
警告:
If the position: fixed;element has any other parent elements with positioning besides body, switching to position: absolute;may have unexpected behavior. Due to the nature of position: fixed;this is probably not a major issue, since nesting such elements is not common.
如果该position: fixed;元素除 之外还有任何其他具有定位的父元素body,则切换到position: absolute;可能会出现意外行为。由于这的性质,position: fixed;这可能不是主要问题,因为嵌套此类元素并不常见。
Recommendations:
建议:
While the use of the touchstartevent will filter out most desktop environments, you will probably want to use user-agent sniffing so that this code will only run for the broken iOS 8, and not other devices such as Android and older iOS versions. Unfortunately, we don't yet know when Apple will fix this issue in iOS, but I would be surprised if it is not fixed in the next major version.
虽然使用该touchstart事件会过滤掉大多数桌面环境,但您可能希望使用用户代理嗅探,以便此代码仅在损坏的 iOS 8 上运行,而不在其他设备(如 Android 和较旧的 iOS 版本)上运行。不幸的是,我们还不知道 Apple 什么时候会在 iOS 中修复这个问题,但如果它没有在下一个主要版本中修复,我会感到惊讶。
回答by Daniel Tonon
I found a method that works without the need to change to position absolute!
我找到了一种无需更改为绝对位置即可工作的方法!
Full uncommented code
完整的未注释代码
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);
}
});
Breaking it down
打破它
First you need to have the fixed input field toward the top of the page in the HTML (it's a fixed element so it should semantically make sense to have it near the top anyway):
首先,您需要在 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>
Then you need to save the current scroll position into global variables:
然后需要将当前滚动位置保存到全局变量中:
//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;
Then you need a way to detect iOS devices so it doesn't affect things that don't need the fix (function taken from https://stackoverflow.com/a/9039885/1611058)
然后你需要一种检测 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;
}
Now that we have everything we need, here is the fix :)
现在我们拥有了我们需要的一切,这是修复:)
//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);
}
});
回答by user3411121
I was able to fix this for select inputs by adding an event listener to the necessary select elements, then scrolling by an offset of one pixel when the select in question gains focus.
我能够通过向必要的选择元素添加一个事件侦听器,然后在有问题的选择获得焦点时滚动一个像素的偏移量来解决选择输入的这个问题。
This isn't necessarily a good solution, but it's much simpler and more reliable than the other answers I've seen here. The browser seems to re-render/re-calculate the position: fixed; attribute based on the offset supplied in the window.scrollBy() function.
这不一定是一个好的解决方案,但它比我在这里看到的其他答案更简单、更可靠。浏览器似乎重新渲染/重新计算位置:固定;基于 window.scrollBy() 函数中提供的偏移量的属性。
document.querySelector(".someSelect select").on("focus", function() {window.scrollBy(0, 1)});
回答by Matthew Levy
Much like Mark Ryan Sallee suggested, I found that dynamically changing the height and overflow of my background elementis the key - this gives Safari nothing to scroll to.
就像 Mark Ryan Sallee 建议的那样,我发现动态改变我的背景元素的高度和溢出是关键——这让 Safari 没有滚动到。
So after the modal's opening animation finishes, change the background's styling:
所以在模态的打开动画完成后,改变背景的样式:
$('body > #your-background-element').css({
'overflow': 'hidden',
'height': 0
});
When you close the modal change it back:
当您关闭模态时,将其改回:
$('body > #your-background-element').css({
'overflow': 'auto',
'height': 'auto'
});
While other answers are useful in simpler contexts, my DOM was too complicated (thanks SharePoint) to use the absolute/fixed position swap.
虽然其他答案在更简单的上下文中很有用,但我的 DOM 太复杂了(感谢 SharePoint),无法使用绝对/固定位置交换。
回答by Samuel
Cleanly? no.
干净吗?不。
I recently had this problem myself with a fixed search field in a sticky header, the best you can do at the moment is keep the scroll position in a variable at all times and upon selection make the fixed element's position absolute instead of fixed with a top position based on the document's scroll position.
我最近在粘性标题中使用固定搜索字段遇到了这个问题,目前您可以做的最好的事情是始终将滚动位置保持在变量中,并在选择时使固定元素的位置绝对而不是固定在顶部位置基于文档的滚动位置。
This is however very ugly and still results in some strange back and forth scrolling before landing on the right place, but it is the closest I could get.
然而,这非常难看,并且在降落到正确的位置之前仍然会导致一些奇怪的来回滚动,但这是我能得到的最接近的。
Any other solution would involve overriding the default scroll mechanics of the browser.
任何其他解决方案都涉及覆盖浏览器的默认滚动机制。
回答by Manoj Gorasya
I had the issue, below lines of code resolved it for me -
我遇到了问题,下面的代码行为我解决了-
html{
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
回答by Dima
None of these solutions worked for me because my DOM is complicated and I have dynamic infinite scroll pages, so I had to create my own.
这些解决方案都不适合我,因为我的 DOM 很复杂,而且我有动态无限滚动页面,所以我必须创建自己的。
Background:I am using a fixed header and an element further down that sticks below it once the user scrolls that far down. This element has a search input field. In addition, I have dynamic pages added during forward and backwards scroll.
背景:我正在使用一个固定的标题和一个更向下的元素,一旦用户向下滚动那么远就会粘在它下面。此元素有一个搜索输入字段。此外,我在向前和向后滚动期间添加了动态页面。
Problem:In iOS, anytime the user clicked on the input in the fixed element, the browser would scroll all the way to the top of the page. This not only caused undesired behavior, it also triggered my dynamic page add at the top of the page.
问题:在 iOS 中,只要用户点击固定元素中的输入,浏览器就会一直滚动到页面顶部。这不仅会导致不良行为,还会触发我在页面顶部添加动态页面。
Expected Solution:No scroll in iOS (none at all) when the user clicks on the input in the sticky element.
预期解决方案:当用户单击粘性元素中的输入时,iOS 中没有滚动(根本没有)。
Solution:
解决方案:
/*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));
Requirements:JQuery mobile is required for the startsroll and stopscroll functions to work.
要求:开始滚动和停止滚动功能需要使用 JQuery 移动版。
Debounce is included to smooth out any lag created by the sticky element.
包括去抖动以消除粘性元素造成的任何滞后。
Tested in iOS10.
在iOS10中测试。
回答by davidcondrey
A possible solution would be to replace the input field.
一个可能的解决方案是替换输入字段。
- Monitor click events on a div
- focus a hidden input field to render the keyboard
- replicate the content of the hidden input field into the fake input field
- 监视 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>

