防止页面在焦点切换时滚动

25

我是移动web/dev的新手。 我的应用程序正在使用jquery-mobile、phonegap和Compass (scss)。

我在登录页面遇到问题:

标志和字段包含在标准的'div'容器中(data-role="content" data-type="vertical")。背景颜色被染色了。

当从登录字段切换到密码字段时,页面会滑动上去,这不是我想要发生的。我希望我的标志和字段保持原位,就像Skype iOS App的登录页面一样。

以下是发生的情况:

bug description

我尝试了几种技巧,尝试阻止滚动事件或强制页面滚动到0,0,但都没有成功。

现在我正在考虑新的策略,也许使用标志和字段的相对位置来捕获焦点事件,以在键盘向上滑动时自己滚动页面(通过动画化顶部相对位置坐标)。

尽管这似乎是可行的,但我想知道Skype iOS App团队是否使用了这种解决方法...

欢迎就此特定情况使用的技术提供建议!

谢谢,

Fred


我相信Skype iOS应用程序使用本地视图来处理登录字段,而不是像Phonegap那样使用WebView。也许可以尝试将页面的body高度更改为100%,或者在页面打开时将登录/密码放在上半部分。 - Geek Num 88
@GeekNum88 确定。Soleshoe正在寻找使用WebView的解决方案。 - Zulakis
3
@Soleshoe 做得很棒,表现出色。 - Mr. Alien
我注意到当键盘弹出时,顶部相对定位的元素不会向上滑动(这就是我考虑自己处理元素滑动的原因)。 - soleshoe
我已经对焦点切换和页面向上滑动时出现的白色带子进行了一些研究,并且发现这与我的页脚的错误构造有关(在模型中未显示)。我必须以正确的方式重构我的页脚,使用data-tap-toggle =“true” 和自定义CSS转换。页面在焦点切换时仍然会滚动,但是白带不再是问题。 - soleshoe
显示剩余3条评论
6个回答

40

@mohsinulhaq 在 Firefox 66 (2019) 中,preventScroll 也能正常工作。 - crayze
1
更新:我错了 - 在Firefox中,preventScroll似乎只在水平滚动时有效,垂直滚动仍然适用 :/ 在Chrome中,preventScroll适用于两个方向。 - crayze
1
我已经花了几个小时来寻找一个非常相似的问题的解决方案,而这个方法非常有效。应该将其标记为答案,立即执行。 - DevMike
preventDefault()在焦点事件上有什么作用吗?元素:焦点事件:"focus事件不可取消。" - undefined

5

我还没有测试过,但是e.preventDefault()可以阻止这个问题吗?通常你使用e.preventDefault()来停止默认的滚动/拖拽行为。


$(document).bind('focus', function(e){
  e.preventDefault();
});

或者更好。
$(element).bind('focus', function(e){
  e.preventDefault();
});

或者

$(document).bind('touchstart', function(e){
  e.preventDefault();
});

使用输入字段是否更好?

$(":input").live({
  focus: function(e) {
    e.preventDefault();
  }
});

第一种和第二种解决方案都与我的问题无关。第三种解决方案防止用户切换焦点... 第四种解决方案确实防止了在登录字段上调用 .focus() 时键盘滑动(但当触摸字段时键盘仍会滑动,问题仍然存在)... - soleshoe

2
我最终没有找到任何编码解决方案来处理我的问题,但我注意到手术定位可以让我在切换字段时冻结屏幕。整个登录屏幕可用空间不得超过键盘可见时可用的空间,即200像素。此外,为了防止在切换字段时滚动,最后一个输入字段中心位置不能超过100像素。使用CSS可以在填充和边距上进行调整以实现所需的结果。通过删除键盘的“字段导航栏”,应该可以获得额外的垂直空间,但我也不知道如何做:/

请查看此解决方案:https://dev59.com/Bmw15IYBdhLWcg3wO5WX#39584526 - Dominik

2
我遇到了类似的问题(点击/聚焦时页面滚动),花费了一些时间才找到解决方案,现在我在这里记录下来。
我有一个自定义表单字段,当点击它时会将页面滚动到顶部。原来,该输入框使用了“position: absolute; top: -99999999px;”进行隐藏(因为如果你正确地隐藏它,那么聚焦事件就不起作用)。这将隐藏的输入框放置在页面顶部。然后当我们点击标签并使输入框聚焦时,页面被滚动到顶部。
我搜索了诸如“聚焦/单击滚动到顶部”之类的东西,但我找不到任何好的方法。解决方案是改用“right: -99999999px”。我避免使用典型的“left: -9999999px”,因为显然在使用“direction: rtl;”时会影响网站。

1

对于focus(),有一个preventScroll选项:
el.focus({preventScroll: true});

如果您需要支持不支持focus-options的浏览器(IE11,Safari<14?),请使用此polyfill

!function(){

    let supported = false;
    document.createElement('i').focus({
        get preventScroll() {
            supported = true;
        },
    });
    if (!supported) {
        let original = HTMLElement.prototype.focus;
        Element.prototype.focus = HTMLElement.prototype.focus = function(options){
            if (options.preventScroll) {
                const map = new Map();
                let p = this;
                while (p = p.parentNode) map.set(p, [p.scrollLeft, p.scrollTop]);
                original.apply(this, arguments);
                map.forEach(function(pos, el){
                    // todo: test: only if changed? does it trigger scroll?
                    // IE flickers
                    el.scrollLeft = pos[0]
                    el.scrollTop  = pos[1]
                });
            } else {
                original.apply(this, arguments);
            }
        }
    }

}();

如果人们正在使用一个具有焦点的库,而不是我们手动使用 .focus(),那该怎么办? - trainoasis

0

在几个地方搜索后,我没有看到最好的解决方案。我尝试了自己的解决方法,可以很好地防止滚动到焦点元素,所以我想在这里分享。

我的想法是当用户聚焦于我的目标元素时,使用position: fixed阻止整个body。然后,立即使用setTimeout(我利用Event Loop)移除该阻止器。

以下是我的代码示例

Javascript版本

function blockScroller(event) {
  const body = document.getElementsByTagName("body")[0]
  body.classList.add("scroller-blocker")

  setTimeout(() => {
    body.classList.remove("scroller-blocker")
  })
}
select {
  width: 200px;
  height: 55em;
  overflow: auto;
}

.scroller-blocker {
  position: fixed;
  height: 100%;
}
<br><br><br><br><br><br><br><br><br>
<select multiple onfocus="blockScroller(event)">
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
</select>

jQuery 版本

function blockScroller(event) {
  const body = $("body")
  body.addClass("scroller-blocker")

  setTimeout(() => {
    body.removeClass("scroller-blocker")
  })
}
select {
  width: 200px;
  height: 55em;
  overflow: auto;
}

.scroller-blocker {
  position: fixed;
  height: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<br><br><br><br><br><br><br><br><br>
<select multiple onfocus="blockScroller(event)">
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
</select>

为了比较,这里是没有上述解决方法的版本

select {
  width: 200px;
  height: 55em;
  overflow: auto;
}

.scroller-blocker {
  position: fixed;
  height: 100%;
}
<br><br><br><br><br><br><br><br><br>
<select multiple>
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
  <option selected="">Lorem</option>
  <option selected="">ipsum</option>
  <option selected="">dolor</option>
  <option selected="">sit</option>
  <option selected="">amet</option>
</select>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接